Class Flashcards
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?
- 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.
REVIEWING CONSTRUCTOR RULES
- The first statement of every
constructor
is a call to an overloaded constructor viathis()
, or a direct parent constructor viasuper()
. -
If the first statement of a
constructor
is not a call tothis()
orsuper()
, then the compiler will insert a no-argumentsuper()
as the first statement of theconstructor
. - Calling
this() and super()
after the first statement of aconstructor
results in a compiler error. -
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. - 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.
- If a class only defines
private
constructors, then it cannot be extended by a top-level class. - 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.
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 :
- 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.
> [!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.
- covariance test
- assing instance of B to a reference variable for A without cast
- This rule applices to primitive types and object types
- 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 wildcard
Generic Return Types
- 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.
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() { ... } }
- 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.
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
- 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.
Hiding Static Methods
- 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 asstatic
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.
Creating final Methods
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.
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
- 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.
OBJECT VS. REFERENCE
- 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.
- For example, since all objects inherit java.lang.Object, they can all be reassigned to java.lang.Object, as shown in the following example:
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.