private constructors
> [!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.
ARE CLASSES WITH ONLY PRIVATE CONSTRUCTORS CONSIDERED FINAL?
REVIEWING CONSTRUCTOR RULES
constructor is a call to an overloaded constructor via this(), or a direct parent constructor via super().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.this() and super() after the first statement of a constructor results in a compiler error.this() or super() constructor call.private constructors, then it cannot be extended by a top-level class.To override a method, you must follow a number of rules.
The compiler performs the following checks when you override a method:
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.
DEFINING SUBTYPE AND SUPERTYPE
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 :
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.
> [!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.
Review of Overloading a Generic Method
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.
Generic Method Parameters
Can override a method with generic parameters, but you must match the signature including the generic type exactly.
public class LongTailAnimal {
protected void chew(List<String> input) {}
}
public class Anteater extends LongTailAnimal {
protected void chew(List<String> input) {}
}public class LongTailAnimal {
protected void chew(List<Object> input) {}
}
public class Anteater extends LongTailAnimal {
protected void chew(ArrayList<Double> input) {} //OVERLOAD
}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.
GENERICS AND WILDCARDS
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 wildcardGeneric Return Types
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() { ... }
}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.
Redeclaring private Methods
private methods since they are not inherited.Hiding Static Methods
static method with the same name and signature as an inherited static method defined in a parent class.static if it is marked as static in a parent class.static,static and the other is not, the class will not compile.Creating final Methods
final methods cannot be replaced.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
}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.
HIDING VARIABLES
OBJECT VS. REFERENCE
Lemur lemur = new Lemur(); Object lemurAsObject = lemur;
We can summarize this principle with the following two rules:
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.
CASTING OBJECTS
We summarize these concepts into a set of rules for you to memorize for the exam:
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.
public class Rodent {}
public class Capybara extends Rodent {
public static void main(String[] args) {
Rodent rodent = new Rodent();
Capybara capybara = (Capybara)rodent; // ClassCastException
}
}THE INSTANCEOF OPERATOR
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;
}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.
POLYMORPHISM AND METHOD OVERRIDING
In Java, polymorphism states that when you override a method, you replace all calls to it, even those defined in the parent class.
Output : 8
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();
}
}CALLING THE PARENT VERSION OF AN OVERRIDDEN METHOD
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.
class Penguin {
...
public void printInfo() {
System.out.print(super.getHeight()); // DOES NOT COMPILE
}
}this does not compile, as super refers to the superclass of Penguin, in this case Object. 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.
OVERRIDING VS. HIDING MEMBERS
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();
}
}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);
}
}The program prints the following:
true false 6 2