Chapter 8 Class Design Notes Flashcards

1
Q

Inheritance

A

Inheritance is the process by which a subclass automatically includes any public or protected members of the class, including primitives, objects, or methods, defined in the parent class.

When one class inherits from a parent class, all public and protected members are automatically available as part of the child class.

Package-private members are available if the child class is in the same package as the parent class.

private members are restricted to the class they are defined in and are never available via inheritance. This doesn’t mean the parent class doesn’t have private members that can hold data or modify an object; it just means the child class has no direct reference to them.

  • Subclass includes any public or protected members defined in the parent class
  • Package-private members are available if the child class is in the same package as the parent class.
  • private members are restricted to the class they are defined in and are never available via inheritance.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

inheritance is transitive

A

If child class X inherits from parent class Y, which in turn inherits from a parent class Z, then class X would be considered a subclass, or descendant, of class Z. By comparison, X is a direct descendant only of class Y, and Y is a direct descendant only of class Z.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

SINGLE VS. MULTIPLE INHERITANCE

A

Java supports single inheritance, by which a class may inherit from only one direct parent class. Java also supports multiple levels of inheritance, by which one class may extend another class, which in turn extends another class. You can have any number of levels of inheritance, allowing each descendant to gain access to its ancestor’s members.

Java does allow one exception to the single inheritance rule that you’ll see in Chapter 9—a class may implement multiple interfaces.

  • Java support single inheritance
  • Java also support multiple levels of inheritance.
  • A class may implement multiple interfaces
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

How to prevent a class from being extended?

A

It is possible in Java to prevent a class from being extended by marking the class with the final modifier.
If you try to define a class that inherits from a final class, then the class will fail to compile.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

INHERITING OBJECT

A
  • In Java, all classes inherit from a single class: java.lang.Object, or Object for short.
  • The key is that when Java sees you define a class that doesn’t extend another class, it automatically adds the syntax extends java.lang.Object to the class definition.
  • Object is alway no top of the inhert tree.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Defining and extending a class

A
public abstract class ElephantSeal extends Seal {
    // Methods and Variables defined here
}
  1. public or default (package-private) access modifier
  2. abstract or final keyword (optional)
  3. class keyword (required)
  4. Class name (required)
  5. extends parent class (optional)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

EXTENDING A CLASS

public class Animal {
   private int age;
   protected String name;
   public int getAge() {
      return age;
   }
   public void setAge(int newAge) {
      age = newAge;
   }
}
 
public class Lion extends Animal {
   public void setProperties(int age, String n) {
      setAge(age);
      name = n;
   }
   public void roar() {
      System.out.print(name + ", age " + getAge() + ", says: Roar!");
   }
   public static void main(String[] args) {
      var lion = new Lion();
      lion.setProperties(3, "kion");
      lion.roar();
   }
}
A

the Lion program prints the following:
kion, age 3, says: Roar!

Cannot access private variable

public class Lion extends Animal {
   ...
   public void roar() {
      System.out.print("Lions age: "+age);  // DOES NOT COMPILE
   }
   ...
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

APPLYING CLASS ACCESS MODIFIERS

A

Top-level class can only have public or default (package-private) access modifier

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

> [!NOTE:]
An inner class is a class defined inside of another class and is the opposite of a top-level class. In addition to public and package-private access, inner classes can also have protected and private access. We will discuss inner classes in Chapter 9.
As you might recall, a Java file can have many top-level classes but at most one public top-level class. In fact, it may have no public class at all. There’s also no requirement that the single public class be the first class in the file. One benefit of using the package-private access is that you can define many classes within the same Java file.

A
  • Inner class can have public, protected, default (package-private) and private access modifier.
  • java file can have many top-level classes but at most one public top-level class.
  • One benefit of using the package-private access is that you can define many classes within the same java file.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

> [!NOTE:]
For simplicity, any time you see multiple public classes or interfaces defined in the same code sample in this book, assume each class is defined in its own Java file.

A

test

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

ACCESSING THE THIS REFERENCE

A
  • The this reference refers to the current instance of the class and can be used to access any member of the class, including inherited members.
  • It can be used in any instance method, constructor, and instance initializer block.
  • It cannot be used when there is no implicit instance of the class, such as in a static method or static initializer block.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

What do you think the following program prints?

public class Flamingo {
    private String color;
    public void setColor(String color) {
        color = color;
}
    public static void main(String... unused) {
        Flamingo f = new Flamingo();
        f.setColor("PINK");
        System.out.println(f.color);
    }
}
A

Output:

null

The assignment completes successfully within the method, but the value of the instance variable color is never modified and is null when printed in the main() method.

public void setColor(String color) {
this.color = color;
}

The this reference refers to the current instance of the class and can be used to access any member of the class, including inherited members. It can be used in any instance method, constructor, and instance initializer block. It cannot be used when there is no implicit instance of the class, such as in a static method or static initializer block.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q
1: public class Duck {
2:     private String color;
3:     private int height;
4:     private int length;
5:
6:     public void setData(int length, int theHeight) {
7:         length = this.length; // Backwards – no good!
8:         height = theHeight; // Fine because a different name
9:         this.color = "white"; // Fine, but this. not necessary
10:     }
11:
12:     public static void main(String[] args) {
13:         Duck b = new Duck();
14:         b.setData(1,2);
15:         System.out.print(b.length + " " + b.height + " " + b.color);
16: } }
A

This code compiles and prints the following:
0 2 white

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

How do we reference the version in the parent class instead of the current class?

A

To achieve this, you can use the super reference or keyword.

ex:

class Mammal {
    String type = "mammal";
}
public class Bat extends Mammal {
    String type = "bat";
    public String getType() {
        return super.type + ":" + this.type;
    }
    public static void main(String... zoo) {
        System.out.print(new Bat().getType());
    }
}

The program prints mammal:bat

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

What does the following program output?

1:  class Insect {
2:     protected int numberOfLegs = 4;
3:     String label = "buggy";
4:  }
5:  
6:  public class Beetle extends Insect {
7:     protected int numberOfLegs = 6;
8:     short age = 3;
9:     public void printData() {
10:       System.out.print(this.label);
11:       System.out.print(super.label);
12:       System.out.print(this.age);
13:       System.out.print(super.age);
14:       System.out.print(numberOfLegs);
15:    }
16:    public static void main(String []n) {
17:       new Beetle().printData();
18:    }
19: }
A

That was a trick question—this program code would not compile!
line 13 does not complie.

while this includes current and inherited members, super only includes inherited members.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

Constructors Rules

A
  1. a constructor is a special method that matches the name of the class and has no return type.
  2. Like method parameters, constructor parameters can be any valid class, array, or primitive type, including generics, but may not include var.
  3. A class can have multiple constructors, so long as each constructor has a unique signature. (constructor parameters must be distinct.)
  4. During compilation, java generate default no-argument constructor if a class is declared without any constructors.
  5. default constructor has an empty parameter list and an empty body.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

Can you tell why these two are not valid constructors for the Bunny class?

public class Bunny {
public bunny() { } // DOES NOT COMPILE
public void Bunny() { }
}
A

The first one doesn’t match the class name because Java is case sensitive.

The second method is a perfectly good method but is not a constructor because it has a return type.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
18
Q

The following does not compile:

class Bonobo {
    public Bonobo(var food) { // DOES NOT COMPILE
    }
}
A

Like method parameters, constructor parameters can be any valid class, array, or primitive type, including generics, but may not include var.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
19
Q

Constructor Overloading

A

A class can have multiple constructors, so long as each constructor has a unique signature. In this case, that means the constructor parameters must be distinct. Like methods with the same name but different signatures,
declaring multiple constructors with different signatures is referred to as constructor overloading.

ex:

public class Turtle {
private String name;
    public Turtle() {
        name = "John Doe";
    }
    public Turtle(int age) {}
    public Turtle(long age) {}
    public Turtle(String newName, String... favoriteFoods) {
        name = newName;
    }
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
20
Q

DEFAULT CONSTRUCTOR

A

Every class in Java has a constructor whether you code one or not. If you don’t include any constructors in the class, Java will create one for you without any parameters. This Java-created constructor is called the default constructor and is added anytime a class is declared without any constructors. We often refer to it as the default no-argument constructor for clarity.

It is only in the compiled file with the .class extension that it makes an appearance.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
21
Q

> [!NOTE:]
Having only private constructors in a class tells the compiler not to provide a default no-argument constructor.
It also prevents other classes from instantiating the class.
This is useful when a class has only static methods or the developer wants to have full control of all calls to create new instances of the class.
Remember, static methods in the class, including a main() method, may access private members, including private constructors.

A

test

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
22
Q

CALLING OVERLOADED CONSTRUCTORS WITH THIS()

public class Hamster {
   private String color;
   private int weight;
   public Hamster(int weight) {                 // First constructor
      this.weight = weight;
      color = "brown";
   }
   public Hamster(int weight, String color) {   // Second constructor
      this.weight = weight;
      this.color = color;
   }
}
A

Constructors can be called only by writing new before the name of the constructor.
Cannot call constructor like normal method.

public Hamster(int weight) {
    Hamster(weight, "brown"); // DOES NOT COMPILE
}

When this() is used with parentheses, Java calls another constructor on the same instance of the class.

public Hamster(int weight) {
    this(weight, "brown");
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
23
Q

this() rule

A

Calling this() has one special rule you need to know. If you choose to call it, the this() call must be the first statement in the constructor. The side effect of this is that there can be only one call to this() in any constructor.

ex:

3: public Hamster(int weight) {
4: System.out.println("in constructor");
5: // Set weight and default color
6: this(weight, "brown"); // DOES NOT COMPILE
7: }
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
24
Q

Consider the following definition of the Gopher class:

public class Gopher {
    public Gopher(int dugHoles) {
        this(5); // DOES NOT COMPILE
    }
}
A

The compiler is capable of detecting that this constructor is calling itself infinitely.

Since this code can never terminate, the compiler stops and reports this as an error.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
25
Q
public class Gopher {
    public Gopher() {
        this(5); // DOES NOT COMPILE
    }
    public Gopher(int dugHoles) {
        this(); // DOES NOT COMPILE
    }
}
A

the constructors call each other, and the process continues infinitely. Since the compiler can detect this, it reports this as an error.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
26
Q

THIS VS. THIS()

A

Despite using the same keyword, this and this() are very different.

The first, this, refers to an instance of the class, while the second, this(), refers to a constructor call within the class.

The exam may try to trick you by using both together, so make sure you know which one to use and why.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
27
Q

CALLING PARENT CONSTRUCTORS WITH SUPER()

A
public class Animal {
   private int age;
   public Animal(int age) {
      super();     // Refers to constructor in java.lang.Object
      this.age = age;
   }
}
 
public class Zebra extends Animal {
   public Zebra(int age) {
      super(age);  // Refers to constructor in Animal
   }
   public Zebra() {
      this(4);     // Refers to constructor in Zebra with int argument
   }
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
28
Q

super() rule

A

calling super() can only be used as the first statement of the constructor.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
29
Q

SUPER VS. SUPER()

A
  • Like this and this(), super and super() are unrelated in Java.
  • The first, super, is used to reference members of the parent class,
  • while the second, super(), calls a parent constructor.
  • Anytime you see the super keyword on the exam, make sure it is being used properly.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
30
Q

Understanding Compiler Enhancements

the first line of every constructor is a call to either this() or super()

How did these classes compile?

public class Donkey {}

public class Donkey {
    public Donkey() {}
}

public class Donkey {
    public Donkey() {
        super();
    }
}
A

Java compiler automatically inserts a call to the no-argument constructor super() if you do not explicitly call this() or super() as the first line of a constructor.

For example, the following three class and constructor definitions are equivalent, because the compiler will automatically convert them all to the last example:

public class Donkey {}

public class Donkey {
    public Donkey() {}
}

public class Donkey {
    public Donkey() {
        super();
    }
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
31
Q

ARE CLASSES WITH ONLY PRIVATE CONSTRUCTORS CONSIDERED FINAL?

A

Remember, a final class cannot be extended.

What happens if you have a class that is not marked final but only contains private constructors—can you extend the class?
The answer is “yes,” but only an inner class defined in the class itself can extend it. An inner class is the only one that would have access to a private constructor and be able to call super(). Other top-level classes cannot extend such a class.

Don’t worry—knowing this fact is not required for the exam. We include it here for those who were curious about declaring only private constructors.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
32
Q

Missing a Default No-Argument Constructor

What happens if the parent class doesn’t have a no-argument constructor?

A
public class Mammal {
    public Mammal(int age) {}
}
public class Elephant extends Mammal { // DOES NOT COMPILE
}

Since Elephant does not define any constructors, the Java compiler will attempt to insert a default no-argument constructor. As a second compile-time enhancement, it will also auto-insert a call to super() as the first line of the default no-argument constructor.

public class Elephant extends Mammal {
    public Elephant() {
        super(); // DOES NOT COMPILE
    }
}

Fix :

public class Elephant extends Mammal {
    public Elephant() {
        super(10);
    }
}

For example, the following class compiles because Elephant now has a no-argument constructor, albeit one defined explicitly:

public class AfricanElephant extends Elephant {}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
33
Q

SUPER() ALWAYS REFERS TO THE MOST DIRECT PARENT

A
  • A class may have multiple ancestors via inheritance.
  • In our previous example, AfricanElephant is a subclass of Elephant, which in turn is a subclass of Mammal.
  • For constructors, though, super() always refers to the most direct parent.
  • In this example, calling super() inside the AfricanElephant class always refers to the Elephant class, and never the Mammal class.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
34
Q

CONSTRUCTORS AND FINAL FIELDS

public class MouseHouse {
    private final int volume;
    private final String name = "The Mouse House";
    {
        volume = 10;
    }
}
A
  • final static variables must be assigned a value exactly once.
  • You saw this happen in the line of the declaration and in a static initializer.
  • Instance variables marked final follow similar rules. They can be assigned values in the line in which they are declared or in an instance initializer.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
35
Q
public class MouseHouse {
    private final int volume;
    private final String type;
    public MouseHouse() {
        this.volume = 10;
        type = "happy";
    }
}
A

In our MouseHouse implementation, the values for volume and type are assigned in the constructor.
Remember that the this keyword is optional since the instance variables are part of the class declaration, and there are no constructor parameters with the same name.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
36
Q

Unlike local final variables, which are not required to have a value unless they are actually used, final instance variables must be assigned a value.

Default values are not used for these variables.

If they are not assigned a value in the line where they are declared or in an instance initializer, then they must be assigned a value in the constructor declaration.

Failure to do so will result in a compiler error on the line that declares the constructor.

A
  • final instance variable
  • must be assigned a value
    • assinged a value in the line where they are declared
    • instance initializer
    • constructor
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
37
Q
public class MouseHouse {
    private final int volume;
    private final String type;
    {
        this.volume = 10;
    }
    public MouseHouse(String type) {
        this.type = type;
    }
    public MouseHouse() { // DOES NOT COMPILE
        this.volume = 2; // DOES NOT COMPILE
    }
}
A
  • In this example, the first constructor that takes a String argument compiles. Although a final instance variable can be assigned a value only once, each constructor is considered independently in terms of assignment.
  • The second constructor does not compile for two reasons.
    • First, the constructor fails to set a value for the type variable. The compiler detects that a value is never set for type and reports an error on the line where the constructor is declared.
    • Second, the constructor sets a value for the volume variable, even though it was already assigned a value by the instance initializer. The compiler reports this error on the line where volume is set.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
38
Q

> [!NOTE]
On the exam, be wary of any instance variables marked final.
Make sure they are assigned a value in the line where they are declared, in an instance initializer, or in a constructor.
They should be assigned a value only once, and failure to assign a value is considered a compiler error in the constructor.

A
  • Instance variables marked final
  • Assigned a value only once
    • declaration line
    • instance initializer
    • constructor
  • should be assigned a value only once, and failure to assign a value is considered a compiler error in the constructor
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
39
Q

How to fix MouseHouse() constructor?

public class MouseHouse {
    private final int volume;
    private final String type;
    {
        this.volume = 10;
    }
    public MouseHouse(String type) {
        this.type = type;
    }
    public MouseHouse() { // DOES NOT COMPILE
        this.volume = 2; // DOES NOT COMPILE
    }
}
A

What about final instance variables when a constructor calls another constructor in the same class? In that case, you have to follow the constructor logic pathway carefully, making sure every final instance variable is assigned a value exactly once. We can replace our previous bad constructor with the following one that does compile:

public MouseHouse() {
    this(null);
}

This constructor does not perform any assignments to any final instance variables, but it calls the MouseHouse(String) constructor, which we observed compiles without issue. We use null here to demonstrate that the variable does not need to be an object value. We can assign a null value to final instance variables, so long as they are explicitly set.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
40
Q

REVIEWING CONSTRUCTOR RULES

A
  1. The first statement of every constructor is a call to an overloaded constructor via this(), or a direct parent constructor via super().
  2. If the first statement of a constructor is not a call to this() or super(), then the compiler will insert a no-argument super() as the first statement of the constructor.
  3. Calling this() and super() after the first statement of a constructor results in a compiler error.
  4. If the parent class doesn’t have a no-argument constructor, then every constructor in the child class must start with an explicit this() or super() constructor call.
  5. If the parent class doesn’t have a no-argument constructor and the child doesn’t define any constructors, then the child class will not compile.
  6. If a class only defines private constructors, then it cannot be extended by a top-level class.
  7. All final instance variables must be assigned a value exactly once by the end of the constructor. Any final instance variables not assigned a value will be reported as a compiler error on the line the constructor is declared.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
41
Q

ORDER OF INITIALIZATION

With inheritance, though, the order of initialization for an instance gets a bit more complicated. We’ll start with how to initialize the class and then expand to initializing the instance.

A
  1. Class Initialization
    The most important rule with class initialization is that it happens at most once for each class. The class may also never be loaded if it is not used in the program.
    • If there is a superclass Y of X, then initialize class Y first.
    • Process all static variable declarations in the order they appear in the class.
    • Process all static initializers in the order they appear in the class.
  2. Instance Initialization
    First, start at the lowest-level constructor where the new keyword is used.
    Remember, the first line of every constructor is a call to this() or super(), and if omitted, the compiler will automatically insert a call to the parent no-argument constructor super(). Then, progress upward and note the order of constructors. Finally, initialize each class starting with the superclass, processing each instance initializer and constructor in the reverse order in which it was called.
    1. If there is a superclass Y of X, then initialize the instance of Y first.
    2. Process all instance variable declarations in the order they appear in the class.
    3. Process all instance initializers in the order they appear in the class.
    4. Initialize the constructor including any overloaded constructors referenced with this().
42
Q

Taking a look at an example, what does the following program print?

public class Animal {
    static { System.out.print("A"); }
}

public class Hippo extends Animal {
    static { System.out.print("B"); }
    public static void main(String[] grass) {
        System.out.print("C");
        new Hippo();
        new Hippo();
        new Hippo();
    }
}
A
  • It prints ABC exactly once.
  • Since the main() method is inside the Hippo class, the class will be initialized first, starting with the superclass and printing AB.
  • Afterward, the main() method is executed, printing C.
  • Even though the main() method creates three instances, the class is loaded only once.
43
Q

What if you instead called Hippo inside another program?

public class Animal {
    static { System.out.print("A"); }
}

public class Hippo extends Animal {
    static { System.out.print("B"); }
    public static void main(String[] grass) {
        System.out.print("C");
        new Hippo();
        new Hippo();
        new Hippo();
    }
}

public class HippoFriend {
    public static void main(String[] grass) {
        System.out.print("C");
        new Hippo();
    }
}
A

Assuming the class isn’t referenced anywhere else, this program will likely print CAB, with the Hippo class not being loaded until it is needed inside the main() method.

We say likely, because the rules for when classes are loaded are determined by the JVM at runtime. For the exam, you just need to know that a class must be initialized before it is referenced or used. Also, the class containing the program entry point, aka the main() method, is loaded before the main() method is executed.

44
Q

See if you can figure out what the following application outputs:

1: public class ZooTickets {
2:     private String name = "BestZoo";
3:     { System.out.print(name+"-"); }
4:     private static int COUNT = 0;
5:     static { System.out.print(COUNT+"-"); }
6:     static { COUNT += 10; System.out.print(COUNT+"-"); }
7:
8:     public ZooTickets() {
9:         System.out.print("z-");
10:     }
11:
12:     public static void main(String... patrons) {
13:         new ZooTickets();
14:     }
15: }
A

The output is as follows:
0-10-BestZoo-z-

  • First, we have to initialize the class.
    • Since there is no superclass declared, which means the superclass is Object,
    • we can start with the static components of ZooTickets.
    • In this case, lines 4, 5, and 6 are executed, printing 0- and 10-.
  • Next, we initialize the instance.
    • Again, since there is no superclass declared,
    • we start with the instance components. Lines 2 and 3 are executed, which prints BestZoo-.
    • Finally, we run the constructor on lines 8–10, which outputs z-.
45
Q

Let’s try a simple example with inheritance.

class Primate {
    public Primate() {
        System.out.print("Primate-");
    }
}
class Ape extends Primate {
    public Ape(int fur) {
        System.out.print("Ape1-");
    }
    public Ape() {
        System.out.print("Ape2-");
    }
}
public class Chimpanzee extends Ape {
    public Chimpanzee() {
        super(2);
        System.out.print("Chimpanzee-");
    }
    public static void main(String[] args) {
        new Chimpanzee();
    }
}
A

The compiler inserts the super() command as the first statement of both the Primate and Ape constructors. The code will execute with the parent constructors called first and yields the following output:
Primate-Ape1-Chimpanzee-

  • Notice that only one of the two Ape() constructors is called.
  • You need to start with the call to new Chimpanzee() to determine which constructors will be executed.
  • Remember, constructors are executed from the bottom up, but since the first line of every constructor is a call to another constructor, the flow actually ends up with the parent constructor executed before the child constructor.
46
Q

The next example is a little harder. What do you think happens here?

1: public class Cuttlefish {
2:     private String name = "swimmy"; //5
3:     { System.out.println(name); } //6
4:     private static int COUNT = 0; // 1
5:     static { System.out.println(COUNT); } // 2
6:     { COUNT++; System.out.println(COUNT); } //7
7:
8:     public Cuttlefish() {
9:         System.out.println("Constructor"); //8
10:     }
11:
12:     public static void main(String[] args) {
13:         System.out.println("Ready"); //3
14:         new Cuttlefish(); //4
15:     }
16: }
A

The output looks like this:

0
Ready
swimmy
1
Constructor
  • There is no superclass declared, so we can skip any steps that relate to inheritance.
  • We first process the static variables and static initializers—lines 4 and 5, with line 5 printing 0.
  • Now that the static initializers are out of the way, the main() method can run, which prints Ready.
  • Lines 2, 3, and 6 are processed, with line 3 printing swimmy and
  • line 6 printing 1.
  • Finally, the constructor is run on lines 8–10, which print Constructor.
47
Q

What does the following output?

1: class GiraffeFamily {
2:     static { System.out.print("A"); } //1
3:     { System.out.print("B"); } //3
4:
5:     public GiraffeFamily(String name) {
6:         this(1);
7:         System.out.print("C"); //5
8:     }
9:
10:     public GiraffeFamily() {
11:         System.out.print("D");
12:     }
13:
14:     public GiraffeFamily(int stripes) {
15:         System.out.print("E"); //4
16:     }
17: }
18: public class Okapi extends GiraffeFamily {
19:     static { System.out.print("F"); } //2
20:
21:     public Okapi(int stripes) { 
22:         super("sugar"); 
23:         System.out.print("G"); //7
24:     }
25:     { System.out.print("H"); } //6
26:
27:     public static void main(String[] grass) {
28:         new Okapi(1);
29:         System.out.println(); 
30:         new Okapi(2);
31:     }
32: }
A

The program prints the following :

AFBECHG
BECHG
  • Let’s walk through it. Start with initializing the Okapi class. Since it has a superclass GiraffeFamily, initialize it first, printing A on line 2.
  • Next, initialize the Okapi class, printing F on line 19.
  • After the classes are initialized, execute the main() method on line 27.
  • The first line of the main() method creates a new Okapi object, triggering the instance initialization process.
  • Per the first rule, the superclass instance of GiraffeFamily is initialized first.
  • Per our third rule, the instance initializer in the superclass GiraffeFamily is called, and B is printed on line 3.
  • Per the fourth rule, we initialize the constructors. In this case, this involves calling the constructor on line 5, which in turn calls the overloaded constructor on line 14. The result is that EC is printed, as the constructor bodies are unwound in the reverse order that they were called.
  • The process then continues with the initialization of the Okapi instance itself.
  • Per the third and fourth rules, H is printed on line 25,
  • and G is printed on line 23, respectively.
  • The process is a lot simpler when you don’t have to call any overloaded constructors.
  • Line 29 then inserts a line break in the output. Finally,
  • line 30 initializes a new Okapi object. The order and initialization are the same as line 28, sans the class initialization, so BECHG is printed again.
  • Notice that D is never printed, as only two of the three constructors in the superclass GiraffeFamily are called.

This example is tricky for a few reasons. There are multiple overloaded constructors, lots of initializers, and a complex constructor pathway to keep track of. Luckily, questions like this are rare on the exam. If you see one, just write down what is going on as you read the code.

48
Q

> [!NOTE]
When taking the exam, pay close attention to any question involving two or more classes related by inheritance.
Before even attempting to answer the question, you should check that the constructors are properly defined using the previous set of rules.
You should also verify the classes include valid access modifiers for members.
Once those are verified, you can continue answering the question.

A

test

49
Q

CALLING INHERITED MEMBERS

A
  • Java classes may use any public or protected member of the parent class, including methods, primitives, or object references.
  • If the parent class and child class are part of the same package, then the child class may also use any package-private members defined in the parent class.
  • Finally, a child class may never access a private member of the parent class, at least not through any direct reference. As you saw earlier in this chapter, a private member age was accessed indirectly via a public or protected method.
50
Q

INHERITING METHODS

A

test

51
Q

Overriding a Method

A

In Java, overriding a method occurs when a subclass declares a new implementation for an inherited method with the same signature and compatible return type. Remember that a method signature includes the name of the method and method parameters.

When you override a method, you may reference the parent version of the method using the super keyword. In this manner, the keywords this and super allow you to select between the current and parent versions of a method, respectively.

52
Q
public class Canine {
    public double getAverageWeight() {
        return 50;
    }
}
public class Wolf extends Canine {
    public double getAverageWeight() {
        return super.getAverageWeight()+20;
    }
    public static void main(String[] args) {
        System.out.println(new Canine().getAverageWeight());
        System.out.println(new Wolf().getAverageWeight());
    }
}
A

In this example, in which the child class Wolf overrides the parent class Canine, the method getAverageWeight() runs, and the program displays the following:

50.0
70.0
53
Q

METHOD OVERRIDING AND RECURSIVE CALLS

what would the following code output if we removed the super keyword?

public class Canine {
    public double getAverageWeight() {
        return 50;
    }
}
public class Wolf extends Canine {
    public double getAverageWeight() {
		    //return super.getAverageWeight()+20; <-- if we remove super keyword will create infinite call and produe a StackOverflowError at runtime.
        return getAverageWeight()+20; // StackOverflowError
    }
    public static void main(String[] args) {
        System.out.println(new Canine().getAverageWeight());
        System.out.println(new Wolf().getAverageWeight());
    }
}
A

In this example, the compiler would not call the parent Canine method; it would call the current Wolf method since it would think you were executing a recursive method call. A recursive method is one that calls itself as part of execution. It is common in programming but must have a termination condition that triggers the end to recursion at some point or depth. In this example, there is no termination condition; therefore, the application will attempt to call itself infinitely and produce a StackOverflowError at runtime.

54
Q

To override a method, you must follow a number of rules. The compiler performs the following checks when you override a method:

Rules to override a method

A
  1. The method in the child class must have the same signature as the method in the parent class.
  2. The method in the child class must be at least as accessible as the method in the parent class.
  3. The method in the child class may not declare a checked exception that is new or broader than the class of any exception declared in the parent class method.
  4. If the method returns a value, it must be the same or a subtype of the method in the parent class, known as covariant return types.
55
Q

DEFINING SUBTYPE AND SUPERTYPE

A

When discussing inheritance and polymorphism, we often use the word subtype rather than subclass, since Java includes interfaces. A subtype is the relationship between two types where one type inherits the other. If we define X to be a subtype of Y, then one of the following is true:

  • X and Y are classes, and X is a subclass of Y.
  • X and Y are interfaces, and X is a subinterface of Y.
  • X is a class and Y is an interface, and X implements Y (either directly or through an inherited class).

Likewise, a supertype is the reciprocal relationship between two types where one type is the ancestor of the other. Remember, a subclass is a subtype, but not all subtypes are subclasses.

56
Q

The first rule of overriding a method is somewhat self-explanatory. If two methods have the same name but different signatures, the methods are overloaded, not overridden.

Overloaded methods are considered independent and do not share the same polymorphic properties as overridden methods.

A

test

57
Q

OVERLOADING VS. OVERRIDING

A
  • Overloading and overriding a method are similar in that they both involve redefining a method using the same name.
  • They differ in that an overloaded method will use a different list of method parameters.
  • This distinction allows overloaded methods a great deal more freedom in syntax than an overridden method would have.
58
Q

For example, compare the overloaded fly() with the overridden eat() in the Eagle class.

public class Bird {
    public void fly() {
        System.out.println("Bird is flying");
    }
    public void eat(int food) {
        System.out.println("Bird is eating "+food+" units of food");
    }
}
public class Eagle extends Bird {
    public int fly(int height) {
        System.out.println("Bird is flying at "+height+" meters");
        return height;
    }
    public int eat(int food) { // DOES NOT COMPILE
        System.out.println("Bird is eating "+food+" units of food");
        return food;
    }
}
A
  • The fly() method is overloaded in the subclass Eagle, since the signature changes from a no-argument method to a method with one int argument. Because the method is being overloaded and not overridden, the return type can be changed from void to int.
  • The eat() method is overridden in the subclass Eagle, since the signature is the same as it is in the parent class Bird—they both take a single argument int. Because the method is being overridden, the return type of the method in the Eagle class must be compatible with the return type for the method in the Bird class. In this example, the return type int is not a subtype of void; therefore, the compiler will throw an exception on this method definition.
59
Q

Any time you see a method on the exam with the same name as a method in the parent class, determine whether the method is being overloaded or overridden first; doing so will help you with questions about whether the code will compile.

A

test

60
Q

What’s the purpose of the second rule about access modifiers? Let’s try an illustrative example:

public class Camel {
    public int getNumberOfHumps() {
        return 1;
    }
}
public class BactrianCamel extends Camel {
    private int getNumberOfHumps() { // DOES NOT COMPILE
        return 2;
    }
}
public class Rider {
    public static void main(String[] args) {
        Camel c = new BactrianCamel();
        System.out.print(c.getNumberOfHumps());
    }
}
A
  • In this example, BactrianCamel attempts to override the getNumberOfHumps() method defined in the parent class but fails because the access modifier private is more restrictive than the one defined in the parent version of the method.
  • Let’s say BactrianCamel was allowed to compile, though. Would the call to getNumberOfHumps() in Rider.main() succeed or fail? As you will see when we get into polymorphism later in this chapter, the answer is quite ambiguous. The reference type for the object is Camel, where the method is declared public, but the object is actually an instance of type BactrianCamel, which is declared private.
  • Java avoids these types of ambiguity problems by limiting overriding a method to access modifiers that are as accessible or more accessible than the version in the inherited method.
61
Q

if a broader checked exception is declared in the overriding method, the code will not compile. Let’s try an example:

public class Reptile {
    protected void sleepInShell() throws IOException {}
    protected void hideInShell() throws NumberFormatException {}
    protected void exitShell() throws FileNotFoundException {}
}
public class GalapagosTortoise extends Reptile {
    public void sleepInShell() throws FileNotFoundException {}
    public void hideInShell() throws IllegalArgumentException {}
    public void exitShell() throws IOException {} // DOES NOT COMPILE
}
A
  • In this example, we have three overridden methods. These overridden methods use the more accessible public modifier, which is allowed per our second rule over overridden methods. The overridden sleepInShell() method declares FileNotFoundException, which is a subclass of the exception declared in the inherited method, IOException. Per our third rule of overridden methods, this is a successful override since the exception is narrower in the overridden method.
  • The overridden hideInShell() method declares an IllegalArgumentException, which is a superclass of the exception declared in the inherited method, NumberFormatException. While this seems like an invalid override since the overridden method uses a broader exception, both of these exceptions are unchecked, so the third rule does not apply.
  • The third overridden exitShell() method declares IOException, which is a superclass of the exception declared in the inherited method, FileNotFoundException. Since these are checked exceptions and IOException is broader, the overridden exitShell() method does not compile in the GalapagosTortoise class. We’ll revisit these exception classes, including memorizing which ones are subclasses of each other, in Chapter 10.
62
Q

The fourth and final rule around overriding a method is probably the most complicated, as it requires knowing the relationships between the return types. The overriding method must use a return type that is covariant with the return type of the inherited method.
Let’s try an example for illustrative purposes:

public class Rhino {
    protected CharSequence getName() {
        return "rhino";
    }
    protected String getColor() {
        return "grey, black, or white";
    }
}

class JavanRhino extends Rhino {
    public String getName() {
        return "javan rhino";
    }
    public CharSequence getColor() { // DOES NOT COMPILE
        return "grey";
    }
}
A
  • The subclass JavanRhino attempts to override two methods from Rhino: getName() and getColor().
  • Both overridden methods have the same name and signature as the inherited methods.
  • The overridden methods also have a broader access modifier, public, than the inherited methods. Per the second rule, a broader access modifier is acceptable.
  • From Chapter 5, you should already know that String implements the CharSequence interface, making String a subtype of CharSequence.
  • Therefore, the return type of getName() in JavanRhino is covariant with the return type of getName() in Rhino.
  • On the other hand, the overridden getColor() method does not compile because CharSequence is not a subtype of String. To put it another way, all String values are CharSequence values, but not all CharSequence values are String values. For example, a StringBuilder is a CharSequence but not a String. For the exam, you need to know if the return type of the overriding method is the same or a subtype of the return type of the inherited method.
63
Q

> [!NOTE]
A simple test for covariance is the following:
Given an inherited return type A and an overriding return type B, can you assign an instance of B to a reference variable for A without a cast? If so, then they are covariant.
This rule applies to primitive types and object types alike.
If one of the return types is void, then they both must be void, as nothing is covariant with void except itself.
The last three rules of overriding a method may seem arbitrary or confusing at first, but as you’ll see later in this chapter when we discuss polymorphism, they are needed for consistency. Without these rules in place, it is possible to create contradictions within the Java language.

A

test

64
Q

Overriding a Generic Method

A

Review of Overloading a Generic Method
In Chapter 7, you learned that you cannot overload methods by changing the generic type due to type erasure. To review, only one of the two methods is allowed in a class because type erasure will reduce both sets of arguments to (List input).

public class LongTailAnimal {
    protected void chew(List<Object> input) {}
    protected void chew(List<Double> input) {} // DOES NOT COMPILE
}
65
Q

Review of Overloading a Generic Method

A

Cannot overload methods by changing the generic type due to type erasure. To review, only one of the two methods is allowed in a class because type erasure will reduce both sets of arguments to (List input).

public class LongTailAnimal {
    protected void chew(List<Object> input) {}
    protected void chew(List<Double> input) {} // DOES NOT COMPILE
}

For the same reason, you also can’t overload a generic method in a parent class.

public class LongTailAnimal {
    protected void chew(List<Object> input) {}
}
public class Anteater extends LongTailAnimal {
    protected void chew(List<Double> input) {} // DOES NOT COMPILE
}

Both of these examples fail to compile because of type erasure. In the compiled form, the generic type is dropped, and it appears as an invalid overloaded method.

66
Q

Generic Method Parameters

A

you can override a method with generic parameters, but you must match the signature including the generic type exactly. For example, this version of the Anteater class does compile because it uses the same generic type in the overridden method as the one defined in the parent class:

public class LongTailAnimal {
    protected void chew(List<String> input) {}
}
public class Anteater extends LongTailAnimal {
    protected void chew(List<String> input) {}
}
67
Q

The generic type parameters have to match, but what about the generic class or interface? Take a look at the following example. From what you know so far, do you think these classes will compile?

public class LongTailAnimal {
    protected void chew(List<Object> input) {}
}
public class Anteater extends LongTailAnimal {
    protected void chew(ArrayList<Double> input) {}
}
A

Yes, these classes do compile.
However, they are considered overloaded methods, not overridden methods, because the signature is not the same.
Type erasure does not change the fact that one of the method arguments is a List and the other is an ArrayList.

68
Q

GENERICS AND WILDCARDS

A

Java includes support for generic wildcards using the question mark (?) character. It even supports bounded wildcards.

void sing1(List<?> v) {} // unbounded wildcard
void sing2(List<? super String> v) {} // lower bounded wildcard
void sing3(List<? extends String> v) {} // upper bounded wildcard

Using generics with wildcards, overloaded methods, and overridden methods can get quite complicated. Luckily, wildcards are out of scope for the 1Z0-815 exam. They are required knowledge, though, when you take the 1Z0-816 exam.

69
Q

Generic Return Types

A

When you’re working with overridden methods that return generics, the return values must be covariant.
In terms of generics, this means that the return type of the class or interface declared in the overriding method must be a subtype of the class defined in the parent class. The generic parameter type must match its parent’s type exactly.

70
Q

Given the following declaration for the Mammal class, which of the two subclasses, Monkey and Goat, compile?

public class Mammal {
    public List<CharSequence> play() { ... }
    public CharSequence sleep() { ... }
}
public class Monkey extends Mammal {
    public ArrayList<CharSequence> play() { ... }
}
public class Goat extends Mammal {
    public List<String> play() { ... } // DOES NOT COMPILE
    public String sleep() { ... }
}
A
  • The Monkey class compiles because ArrayList is a subtype of List.
  • The play() method in the Goat class does not compile, though. For the return types to be covariant, the generic type parameter must match. Even though String is a subtype of CharSequence, it does not exactly match the generic type defined in the Mammal class. Therefore, this is considered an invalid override.
  • Notice that the sleep() method in the Goat class does compile since String is a subtype of CharSequence.
  • This example shows that covariance applies to the return type, just not the generic parameter type.
71
Q

For the exam, it might be helpful for you to apply type erasure to questions involving generics to ensure that they compile properly. Once you’ve determined which methods are overridden and which are being overloaded, work backward, making sure the generic types match for overridden methods. And remember, generic methods cannot be overloaded by changing the generic parameter type only.

A

test

72
Q

Redeclaring private Methods

A

What happens if you try to override a private method?

In Java, you can’t override private methods since they are not inherited. Just because a child class doesn’t have access to the parent method doesn’t mean the child class can’t define its own version of the method. It just means, strictly speaking, that the new method is not an overridden version of the parent class’s method.

Java permits you to redeclare a new method in the child class with the same or modified signature as the method in the parent class. This method in the child class is a separate and independent method, unrelated to the parent version’s method, so none of the rules for overriding methods is invoked.

73
Q

Let’s return to the Camel example we used in the previous section and show two related classes that define the same method:

public class Camel {
    private String getNumberOfHumps() {
        return "Undefined";
    }
}
public class DromedaryCamel extends Camel {
    private int getNumberOfHumps() {
        return 1;
    }
}
A

This code compiles without issue.
Notice that the return type differs in the child method from String to int. In this example, the method getNumberOfHumps() in the parent class is redeclared, so the method in the child class is a new method and not an override of the method in the parent class. As you saw in the previous section, if the method in the parent class were public or protected, the method in the child class would not compile because it would violate two rules of overriding methods. The parent method in this example is private, so there are no such issues.

74
Q

Hiding Static Methods

A

A hidden method occurs when a child class defines a static method with the same name and signature as an inherited static method defined in a parent class.

Method hiding is similar but not exactly the same as method overriding. The previous four rules for overriding a method must be followed when a method is hidden. In addition, a new rule is added for hiding a method:

5. The method defined in the child class must be marked as static if it is marked as static in a parent class.

Put simply, it is method hiding if the two methods are marked static, and method overriding if they are not marked static. If one is marked static and the other is not, the class will not compile.

75
Q

Method hiding rule

public class Bear {
    public static void eat() {
        System.out.println("Bear is eating");
    }
}
public class Panda extends Bear {
    public static void eat() {
        System.out.println("Panda is chewing");
    }
    public static void main(String[] args) {
        eat();
    }
}
A

In this example, the code compiles and runs.
The eat() method in the Panda class hides the eat() method in the Bear class, printing “Panda is chewing” at runtime. Because they are both marked as static, this is not considered an overridden method. That said, there is still some inheritance going on. If you remove the eat() method in the Panda class, then the program prints “Bear is eating” at runtime.

76
Q

Let’s contrast this with an example that violates the fifth (method hiding) rule:

public class Bear {
    public static void sneeze() {
        System.out.println("Bear is sneezing");
    }
    public void hibernate() {
        System.out.println("Bear is hibernating");
    }
    public static void laugh() {
        System.out.println("Bear is laughing");
    }
}
public class Panda extends Bear {
    public void sneeze() { // DOES NOT COMPILE
        System.out.println("Panda sneezes quietly");
    }
    public static void hibernate() { // DOES NOT COMPILE
        System.out.println("Panda is going to sleep");
    }
    protected static void laugh() { // DOES NOT COMPILE
        System.out.println("Panda is laughing");
    }
}
A

In this example, sneeze() is marked static in the parent class but not in the child class. The compiler detects that you’re trying to override using an instance method. However, sneeze() is a static method that should be hidden, causing the compiler to generate an error.

In the second method, hibernate() is an instance member in the parent class but a static method in the child class. In this scenario, the compiler thinks that you’re trying to hide a static method. Because hibernate() is an instance method that should be overridden, the compiler generates an error.

Finally, the laugh() method does not compile. Even though both versions of method are marked static, the version in Panda has a more restrictive access modifier than the one it inherits, and it breaks the second rule for overriding methods. Remember, the four rules for overriding methods must be followed when hiding static methods.

77
Q

Creating final Methods

A

final methods cannot be replaced.

By marking a method final, you forbid a child class from replacing this method. This rule is in place both when you override a method and when you hide a method. In other words, you cannot hide a static method in a child class if it is marked final in the parent class.

78
Q

Let’s take a look at an example (final method):

public class Bird {
    public final boolean hasFeathers() {
        return true;
    }
    public final static void flyAway() {}
}
public class Penguin extends Bird {
    public final boolean hasFeathers() { // DOES NOT COMPILE
        return false;
    }
    public final static void flyAway() {} // DOES NOT COMPILE
}
A

In this example, the instance method hasFeathers() is marked as final in the parent class Bird, so the child class Penguin cannot override the parent method, resulting in a compiler error. The static method flyAway() is also marked final, so it cannot be hidden in the subclass. In this example, whether or not the child method used the final keyword is irrelevant—the code will not compile either way.

This rule applies only to inherited methods. For example, if the two methods were marked private in the parent Bird class, then the Penguin class, as defined, would compile. In that case, the private methods would be redeclared, not overridden or hidden.

79
Q

WHY MARK A METHOD AS FINAL?

A

Although marking methods as final prevents them from being overridden, it does have advantages in practice. For example, you’d mark a method as final when you’re defining a parent class and want to guarantee certain behavior of a method in the parent class, regardless of which child is invoking the method.

The reason methods are not commonly marked as final in practice, though, is that it may be difficult for the author of a parent class method to consider all of the possible ways her child class may be used. For example, although all adult birds have feathers, a baby chick doesn’t; therefore, if you have an instance of a Bird that is a chick, it would not have feathers. For this reason, the final modifier is often used when the author of the parent class wants to guarantee certain behavior at the cost of limiting polymorphism.

80
Q

HIDING VARIABLES

A

Java doesn’t allow variables to be overridden. Variables can be hidden, though.

A hidden variable occurs when a child class defines a variable with the same name as an inherited variable defined in the parent class. This creates two distinct copies of the variable within an instance of the child class: one instance defined in the parent class and one defined in the child class.

81
Q

What do you think the following application prints?

class Carnivore {
    protected boolean hasFur = false;
}
public class Meerkat extends Carnivore {
    protected boolean hasFur = true;
    public static void main(String[] args) {
        Meerkat m = new Meerkat();
        Carnivore c = m;
        System.out.println(m.hasFur);
        System.out.println(c.hasFur);
    }
}
A

It prints true followed by false.
Confused?

Both of these classes define a hasFur variable, but with different values. Even though there is only one object created by the main() method, both variables exist independently of each other. The output changes depending on the reference variable used.

82
Q

Understanding Polymorphism

A
  • Java supports polymorphism, the property of an object to take on many different forms.
  • To put this more precisely, a Java object may be accessed using a reference with the same type as the object, a reference that is a superclass of the object, or a reference that defines an interface the object implements, either directly or through a superclass.
  • Furthermore, a cast is not required if the object is being reassigned to a super type or interface of the object.
83
Q

INTERFACE PRIMER

A
  • An interface can define abstract methods.
  • A class can implement any number of interfaces.
  • A class implements an interface by overriding the inherited abstract methods.
  • An object that implements an interface can be assigned to a reference for that interface.
84
Q

Let’s illustrate this polymorphism property with the following example:

public class Primate {
    public boolean hasHair() {
        return true;
    }
}
public interface HasTail {
    public abstract boolean isTailStriped();
}
public class Lemur extends Primate implements HasTail {
    public boolean isTailStriped() {
        return false;
    }
    public int age = 10;
    public static void main(String[] args) {
        Lemur lemur = new Lemur();
        System.out.println(lemur.age);
        HasTail hasTail = lemur;
        System.out.println(hasTail.isTailStriped());
        Primate primate = lemur;
        System.out.println(primate.hasHair());
    }
}
A

This code compiles and prints the following output:

10
false
true

The most important thing to note about this example is that only one object, Lemur, is created and referenced. Polymorphism enables an instance of Lemur to be reassigned or passed to a method using one of its supertypes, such as Primate or HasTail.

Once the object has been assigned to a new reference type, only the methods and variables available to that reference type are callable on the object without an explicit cast. For example, the following snippets of code will not compile:

HasTail hasTail = lemur;
System.out.println(hasTail.age); // DOES NOT COMPILE
Primate primate = lemur;
System.out.println(primate.isTailStriped()); // DOES NOT COMPILE

In this example, the reference hasTail has direct access only to methods defined with the HasTail interface; therefore, it doesn’t know the variable age is part of the object. Likewise, the reference primate has access only to methods defined in the Primate class, and it doesn’t have direct access to the isTailStriped() method.

85
Q

Once the object has been assigned to a new reference type, only the methods and variables available to that reference type are callable on the object without an explicit cast. For example, the following snippets of code will not compile:

HasTail hasTail = lemur;
System.out.println(hasTail.age); // DOES NOT COMPILE
Primate primate = lemur;
System.out.println(primate.isTailStriped()); // DOES NOT COMPILE
A

In this example, the reference hasTail has direct access only to methods defined with the HasTail interface; therefore, it doesn’t know the variable age is part of the object. Likewise, the reference primate has access only to methods defined in the Primate class, and it doesn’t have direct access to the isTailStriped() method.

86
Q

OBJECT VS. REFERENCE

A

In Java, all objects are accessed by reference, so as a developer you never have direct access to the object itself.
Conceptually, though, you should consider the object as the entity that exists in memory, allocated by the Java runtime environment. Regardless of the type of the reference you have for the object in memory, the object itself doesn’t change.

  1. The type of the object determines which properties exist within the object in memory.
  2. The type of the reference to the object determines which methods and variables are accessible to the Java program.
87
Q

CASTING OBJECTS

A
  1. Casting a reference from a subtype to a supertype doesn’t require an explicit cast.
  2. Casting a reference from a supertype to a subtype requires an explicit cast.
  3. The compiler disallows casts to an unrelated class.
  4. At runtime, an invalid cast of a reference to an unrelated type results in a ClassCastException being thrown.
88
Q

> [!NOTE]
While the compiler can enforce rules about casting to unrelated types for classes, it cannot do the same for interfaces, since a subclass may implement the interface.
We’ll revisit this topic in the next chapter. For now, you just need to know the third rule on casting applies to class types only, not interfaces.

A

test

89
Q

Casting is not without its limitations. Even though two classes share a related hierarchy, that doesn’t mean an instance of one can automatically be cast to another. Here’s an example:

public class Rodent {}
public class Capybara extends Rodent {
    public static void main(String[] args) {
        Rodent rodent = new Rodent();
        Capybara capybara = (Capybara)rodent; // ClassCastException
    }
}
A

This code creates an instance of Rodent and then tries to cast it to a subclass of Rodent, Capybara. Although this code will compile, it will throw a ClassCastException at runtime since the object being referenced is not an instance of the Capybara class. The thing to keep in mind in this example is the Rodent object created does not inherit the Capybara class in any way.

90
Q

When reviewing a question on the exam that involves casting and polymorphism, be sure to remember what the instance of the object actually is. Then, focus on whether the compiler will allow the object to be referenced with or without explicit casts.

A

test

91
Q

THE INSTANCEOF OPERATOR

A

instanceof operator, which can be used to check whether an object belongs to a particular class or interface and to prevent ClassCastExceptions at runtime.

the following code snippet doesn’t throw an exception at runtime and performs the cast only if the instanceof operator returns true:

if(rodent instanceof Capybara) {
    Capybara capybara = (Capybara)rodent;
}
92
Q

Just as the compiler does not allow casting an object to unrelated types, it also does not allow instanceof to be used with unrelated types. We can demonstrate this with our unrelated Bird and Fish classes:

public static void main(String[] args) {
    Fish fish = new Fish();
    if (fish instanceof Bird) { // DOES NOT COMPILE
        Bird bird = (Bird) fish; // DOES NOT COMPILE
    }
}

In this snippet, neither the instanceof operator nor the explicit cast operation compile.

A

test

93
Q

POLYMORPHISM AND METHOD OVERRIDING

A

test

94
Q

As an example, what do you think the following code snippet outputs?

class Penguin {
    public int getHeight() { return 3; }
    public void printInfo() {
        System.out.print(this.getHeight());
    }
}
public class EmperorPenguin extends Penguin {
    public int getHeight() { return 8; }
    public static void main(String []fish) {
        new EmperorPenguin().printInfo();
    }
}
A

If you said 8, then you are well on your way to understanding polymorphism.

  • In this example, the object being operated on in memory is an EmperorPenguin.
  • The getHeight() method is overridden in the subclass, meaning all calls to it are replaced at runtime.
  • Despite printInfo() being defined in the Penguin class, calling getHeight() on the object calls the method associated with the precise object in memory, not the current reference type where it is called.
  • Even using the this reference, which is optional in this example, does not call the parent version because the method has been replaced.

The facet of polymorphism that replaces methods via overriding is one of the most important properties in all of Java. It allows you to create complex inheritance models, with subclasses that have their own custom implementation of overridden methods. It also means the parent class does not need to be updated to use the custom or overridden method. If the method is properly overridden, then the overridden version will be used in all places that it is called.

Remember, you can choose to limit polymorphic behavior by marking methods final, which prevents them from being overridden by a subclass.

95
Q

CALLING THE PARENT VERSION OF AN OVERRIDDEN
METHOD

A

there is one exception to overriding a method where the parent method can still be called, and that is when the super reference is used.

96
Q

How can you modify our EmperorPenguin example to print 3, as defined in the Penguin getHeight() method?

You could try calling super.getHeight() in the printInfo() method of the Penguin class.

class Penguin {
...
public void printInfo() {
System.out.print(super.getHeight()); // DOES NOT COMPILE
}
}

Unfortunately, this does not compile, as super refers to the superclass of Penguin, in this case Object.

A

The solution is to override printInfo() in the EmperorPenguin class and use super there.

public class EmperorPenguin extends Penguin {
...
    public void printInfo() {
        System.out.print(super.getHeight());
    }
...
}

This new version of EmperorPenguin uses the getHeight() method declared in the parent class and prints 3.

97
Q

OVERRIDING VS. HIDING MEMBERS

A

While method overriding replaces the method everywhere it is called, static method and variable hiding does not.

Strictly speaking, hiding members is not a form of polymorphism since the methods and variables maintain their individual properties.

Unlike method overriding, hiding members is very sensitive to the reference type and location where the member is being used.

98
Q

Let’s take a look at an example:

class Penguin {
    public static int getHeight() { return 3; }
    public void printInfo() {
        System.out.println(this.getHeight());
    }
}
public class CrestedPenguin extends Penguin {
    public static int getHeight() { return 8; }
    public static void main(String... fish) {
        new CrestedPenguin().printInfo();
    }
}
A

The CrestedPenguin example is nearly identical to our previous EmporerPenguin example, although as you probably already guessed, it prints 3 instead of 8.

The getHeight() method is static and is therefore hidden, not overridden.

The result is that calling getHeight() in CrestedPenguin returns a different value than calling it in the Penguin, even if the underlying object is the same.

Contrast this with overriding a method, where it returns the same value for an object regardless of which class it is called in.

99
Q

What about the fact that we used this to access a static method in this.getHeight()?

A

As discussed in Chapter 7, while you are permitted to use an instance reference to access a static variable or method, it is often discouraged. In fact, the compiler will warn you when you access static members in a non-static way. In this case, the this reference had no impact on the program output.

100
Q

Besides the location, the reference type can also determine the value you get when you are working with hidden members.
Ready? Let’s try a more complex example:

class Marsupial {
    protected int age = 2;
    public static boolean isBiped() {
        return false;
    }
}
public class Kangaroo extends Marsupial {
    protected int age = 6;
    public static boolean isBiped() {
        return true;
    }
    public static void main(String[] args) {
        Kangaroo joey = new Kangaroo();
        Marsupial moey = joey;
        System.out.println(joey.isBiped());
        System.out.println(moey.isBiped());
        System.out.println(joey.age);
        System.out.println(moey.age);
    }
}
A

The program prints the following:

true
false
6
2

Remember, in this example, only one object, of type Kangaroo, is created and stored in memory. Since static methods can only be hidden, not overridden, Java uses the reference type to determine which version of isBiped() should be called, resulting in joey.isBiped() printing true and moey.isBiped() printing false.
Likewise, the age variable is hidden, not overridden, so the reference type is used to determine which value to output. This results in joey.age returning 6 and moey.age returning 2.

101
Q

DON’T HIDE MEMBERS IN PRACTICE

Although Java allows you to hide variables and static methods, it is considered an extremely poor coding practice. As you saw in the previous example, the value of the variable or method can change depending on what reference is used, making your code very confusing, difficult to follow, and challenging for others to maintain. This is further compounded when you start modifying the value of the variable in both the parent and child methods, since it may not be clear which variable you’re updating.

When you’re defining a new variable or static method in a child class, it is considered good coding practice to select a name that is not already used by an inherited member.

Redeclaring private methods and variables is considered less problematic, though, because the child class does not have access to the variable in the parent class to begin with.

For the exam, make sure you understand these examples as they show how hidden and overridden methods are fundamentally different. In practice, overriding methods is the cornerstone of polymorphism and is an extremely powerful feature.

A

test