← Data abstraction CS Skill Tree

Accessing super class methods and fields

A child object carries the parent's fields inside it. The child's code is still not allowed to touch all of them. Inheriting a field and being allowed to read it are 2 separate questions. The parent decides the second one by leaving some members open to its subclasses and keeping the rest shut.

Every example below uses 1 small family of classes. Plant is the parent. Cactus and Fern extend it.

Private is private to the class, not to the family

public abstract class Plant
{
    private String name;
    private int waterDays;
}

public class Fern extends Plant
{
    public Fern(String name)
    {
        this.name = name;   // does not compile
    }
}

The compiler stops on that line:

Fern.java:5: error: name has private access in Plant
        this.name = name;   // does not compile
            ^

A Fern object really does hold a name field. The line still fails. private means private to the class that declared the field, and Fern is a different class in a different file.

The reason is the guarantee. Plant made the fields private so that only Plant's own code can change them. A subclass written next year would otherwise be a way around that guarantee. So the fields stay shut, and the parent offers specific ways in.

The 4 levels, and the one that means family

ModifierWho can reach it
privateonly code inside the same class
none writtenany class in the same package
protectedany class in the same package, plus subclasses anywhere
publicanybody

protected exists for this situation. A protected member is left out of the class's public offer and is open to the classes that extend it. A protected constructor is the common case. Nobody should build a plain Plant, and every subclass has to be able to call its constructor.

public abstract class Plant
{
    private String name;
    private int waterDays;

    protected Plant(String name, int waterDays)
    {
        this.name = name;
        this.waterDays = waterDays;
    }
}

The fields are still shut, and there is exactly 1 way to fill them. Make that constructor private and no subclass can call it, so the subclasses do not compile. Make it public and code outside the family can call it too. That compiles, and it opens more than the parent needed to open.

super(...) and where it goes

A child constructor reaches the parent's constructor with super, written as a call, with the arguments the parent's constructor takes.

public class Fern extends Plant
{
    public Fern(String name)
    {
        super(name, 3);
    }
}

public class Cactus extends Plant
{
    public Cactus()
    {
        super("cactus", 14);
    }
}

Fern passes up the name it was given and a fixed 3. Cactus takes no arguments and passes up 2 fixed values. The order of the arguments is the order of the parameters on Plant's constructor.

Put the super call first in the constructor. One object is being built, and the parent's half has to be filled in before the child's code uses it.

Java versionA statement above super(...)
24 and earlierdoes not compile
25 and latercompiles, when the statement does not use the object being built (a check on an argument, or a print)

The APE does not say which Java version its machines run, and the API documentation it links is Java SE 19. Write super(...) as the first statement and the constructor compiles on every version.

Java also fills the call in when you leave it out. With no super call, the compiler inserts super(), with no arguments, at the top of the constructor. Plant's only constructor takes a String and an int, so the inserted call has nothing to reach:

Fern.java:4: error: constructor Plant in class Plant cannot be applied to given types;
    {
    ^
  required: String,int
  found:    no arguments
  reason: actual and formal argument lists differ in length

The error points at the opening brace of a constructor where you wrote no call at all. Read it as: Java guessed super(), and Plant has no constructor that takes nothing.

The only other call allowed in that place

A constructor may call this(...) instead, which runs another constructor of the same class. That is how a class with 2 constructors keeps its real setup in 1 of them. Following the chain of this(...) calls always ends at a super(...) call before any child body runs.

Going up for a method instead of a field

super also names the parent's version of a method you overrode.

@Override
public String toString()
{
    return super.toString() + ", keep out of direct sun";
}

Inside Fern, that prints Boston fern, water every 3 days, keep out of direct sun for a fern named Boston fern. The first part comes from Plant's toString, which can read the private fields because it is Plant's own code.

Without the word super, toString() means Fern's own method, and the method calls itself until the program stops with a StackOverflowError. With it, the call goes to the version Fern replaced.

Fern never reads name or waterDays to build that string. It asks the parent for the string.

What the parent hands you

Way inWhat it is for
a protected or public constructor, called as super(...)setting the parent's fields when the object is built
the parent's public and protected methods, such as getName()reading and changing the parent's state afterward
super.method()running the parent's version of a method you overrode

Direct access to a private field is not on the list.

In the practice APE

The Inheritance problem uses this lesson. Its header comments are the specification.

Here is the same kind of task on the Plant family. The comments are the specification, and you write the 2 constructors.

// Plant: the constructor sets both fields. It may be called only by
// subclasses of Plant and by classes in the same package.
____ Plant(String name, int waterDays)

// Orchid extends Plant. An Orchid is built from a name alone.
// Every orchid is watered every 7 days.
public Orchid(String name)
Answer
protected Plant(String name, int waterDays)
{
    this.name = name;
    this.waterDays = waterDays;
}

public Orchid(String name)
{
    super(name, 7);
}

"Only by subclasses and by classes in the same package" is the row for protected in the table above. Orchid cannot assign the private fields, so it passes both values up, in the order Plant's parameters list them.

Readiness check

Work each one on paper and write the answer down before you open it.

TraceWhich lines compile

Plant is abstract, has 2 private fields, a protected 2-argument constructor, and a public toString. Fern extends Plant.

  1. Inside Fern's constructor: this.waterDays = 3; Does it compile?
  2. Inside Fern's constructor: super(name, 3); as the first statement. Does it compile?
  3. Inside Fern, a method writes return super.toString(); Does it compile?
  4. In another package, in a class that does not extend Plant, code writes new Plant("ivy", 5); Does it compile, and how many reasons are there?
Answers

1. No. waterDays is private to Plant, and Fern is a different class.

2. Yes. That is the parent's way in, called first.

3. Yes. toString is public on Plant, and super. names the parent's version.

4. No, for 2 reasons, and the compiler reports only the first: Plant is abstract; cannot be instantiated. The constructor is protected, so a class outside the package that is not a subclass cannot call it. And Plant is abstract, so new Plant(...) is illegal everywhere.

TraceThe first line

Plant's only constructor is protected Plant(String name, int waterDays).

  1. Fern's constructor has no super call at all. What happens, and what does the error message mention?
  2. Plant is given a second constructor that takes no arguments. Now what happens to the constructor from question 1?
  3. Fern's constructor starts with System.out.println("building"); and then calls super(name, 3);. What happens under Java 21, and what happens under Java 25?
  4. Fern has 2 constructors, and one of them starts with this("Boston fern");. Is that legal?
Answers

1. It does not compile. Java inserted super(), and Plant has no constructor that takes no arguments. The message says required: String,int and found: no arguments.

2. It compiles now. The inserted super() calls the new empty constructor, so the fields keep their default values: null for the name and 0 for the days. Nothing warns you that the 2 values never arrived, which is the reason to write the call yourself.

3. Under Java 21 it does not compile, because the call to super must be the first statement. Under Java 25 it compiles and prints building before Plant's constructor runs, because the print does not use the object being built.

4. Yes. The chain of this(...) calls reaches a super(...) call before any body runs.

TraceThe order things run in

A chain of 3 classes. Each constructor prints 1 line. Plant's constructor also prints what isIndoor() returns while the object is being built.

public abstract class Plant
{
    private String name;

    protected Plant(String name)
    {
        System.out.println("plant");
        System.out.println("indoor during build: " + this.isIndoor());
        this.name = name;
    }

    public abstract boolean isIndoor();
}

public class Fern extends Plant
{
    public Fern(String name)
    {
        super(name);
        System.out.println("fern");
    }

    @Override
    public boolean isIndoor()
    {
        return true;
    }
}

public class TreeFern extends Fern
{
    private boolean outside = true;

    public TreeFern()
    {
        super("tree fern");
        System.out.println("tree fern");
    }

    @Override
    public boolean isIndoor()
    {
        return !this.outside;
    }
}
  1. What does new TreeFern(); print, in order?
  2. Which constructor's body finishes first, and which finishes last?
  3. After the object is built, what does its isIndoor() return, and why did the line printed during the build say something different?
Answers

1. 4 lines: plant, indoor during build: true, fern, tree fern.

2. Plant's body finishes first and TreeFern's finishes last. The super calls go up the chain, and the bodies run on the way back down.

3. It returns false. The object is a TreeFern from the start, so the call inside Plant's constructor ran TreeFern's isIndoor. At that moment outside still held its default value, false, because TreeFern's own field setup runs after the super call returns. So !this.outside was true during the build.

Further reading