by Ursego » 05 Mar 2019, 07:38
- Code: Select all
// ####### Constructor:
// You can define a constructor using all four access levels: public, protected, default, and private.
// A constructor can't be defined using non-access modifiers static, abstract, or final.
// A constructor must not define any return type - not even void. Instead, it creates and returns an object of the class in which it's defined. If you write "return" in a constructor, it means "return this". If you define a return type for a constructor, it'll no longer be treated as a constructor. Instead, it'll be treated as a regular method, even though it shares the same name as its class.
// Default Constructor:
// 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, and only calling the default constructor of the super-class:
public Thing() {
super();
}
// A default constructor is only automatically created if there are no constructors present. But if you modify the class later by adding a constructor to it, the Java compiler will remove the default, noargument constructor that it initially added to the class. Having a private constructor in a class tells the compiler not to provide a default noargument constructor. It also prevents other classes from instantiating the class. This is useful when a class only has static methods or the class wants to control all calls to create new instances of itself ("factory").
// The accessibility of a default constructor matches the accessibility of its class. Java creates a public default constructor for a public class. It creates a default constructor with package access for a class with package-level access.
// IMPORTANT!!! If the parent class doesn't have a no-argument constructor, an explicit call to a parent constructor must be provided in the child's constructors (otherwise - compilation error). Pay close attention on the exam to any class that defines a constructor with arguments and doesn't define a no-argument constructor.
// Subclasses don't inherit constructors.
// When you create a subclass and don't put any explicit constructor declarations in your code, Java creates a default noargument constructor for you. If you create a subclass and define any constructors yourself, Java doesn't add a default constructor for the subclass (and the subclass doesn't inherit any constructors, either).
// When "this" is used in a constructor as if it were a method name, Java calls another constructor on the same instance of the class:
public Hamster(int weight, String color) { // the two-args overload of the constructor
this.weight = weight;
this.color = color;
}
public Hamster(int weight) { // the one-arg overload of the constructor
this(weight, "brown"); // call the two-args overload of the constructor
}
// If you call another constructor with this(), that call must be the first noncommented statement in the constructor:
public Hamster(int weight) {
System.out.println("in constructor");
this(weight, "brown"); // DOES NOT COMPILE
}
// Even though the println statement doesn't change any variables, it is still a Java statement and is not allowed to be inserted before the call to this(). The comment just fine. Comments don't run Java statements and are allowed anywhere.
// You can't call multiple constructors from a constructor - only one this() can present.
// The constructor is part of the initialization process, so it is allowed to assign final instance variables in it (but not final static!). By the time the constructor completes, all final instance variables must have been set.
// Order of Initialization:
// Unfortunately, you do have to memorize this list:
// 1. If there is a superclass, initialize it first.
// 2. Static variable declarations and static initializers in the order they appear in the file (on the first use only).
// 3. Instance variable declarations and instance initializers in the order they appear in the file.
// 4. The constructor.
// The four rules apply only if an object is instantiated. If the class is referred to without a new call, only rules 1 and 2 apply. The other two rules relate to instances and constructors. They have to wait until there is code to instantiate the object.
// super():
// Java compiler automatically inserts a call to the no-argument constructor super() if the first statement is not a call to the parent constructor. The following three classes are equivalent, because the compiler will automatically convert the first two to the last example:
public class Donkey {
}
public class Donkey {
public Donkey() {}
}
public class Donkey {
public Donkey() {
super();
}
}
// What happens if the parent class doesn't have a no-argument constructor? Recall that the no-argument constructor is not required and only inserted automatically if there is no constructor defined in the class by a programmer. In this case, the Java compiler will not help and you must create at least one constructor in your child class that explicitly calls a parent constructor via the super() command. You should be wary of any exam question in which the parent class defines a constructor that takes arguments and doesn't define a no-argument constructor.
// Constructor Definition Rules:
// 1. The first statement of every constructor is a call to another constructor within the class using this(),
// or a call to a constructor in the direct parent class using super().
// As you see, only one of them can be used, but never both.
// 2. The super() call is not allowed to be used after the first statement of the constructor.
// 3. If no super() call is declared in a constructor, Java will insert a no-argument super() as the first statement of the constructor.
// 4. If the parent doesn't have a no-argument constructor and the child doesn't define any constructors, the compiler will throw an error
// and try to insert a default no-argument constructor into the child class.
// 5. If the parent doesn't have a no-argument constructor, the compiler requires an explicit call to a parent constructor in each child constructor.
// The parent constructor is always executed before the child constructor. For example, try to determine what the following code outputs:
class Primate {
public Primate() {
System.out.println("Primate");
}
}
class Ape extends Primate {
public Ape() {
System.out.println("Ape");
}
}
public class Chimpanzee extends Ape {
public static void main(String[] args) {
new Chimpanzee();
}
}
// The compiler first inserts the super() command as the first statement of both the Primate and Ape constructors. Next, the compiler inserts a default no-argument constructor in the Chimpanzee class with super() as the first statement of the constructor. So, here is what is finally created by Java:
class Primate {
public Primate() {
super();
System.out.println("Primate");
}
}
class Ape extends Primate {
public Ape() {
super();
System.out.println("Ape");
}
}
public class Chimpanzee extends Ape {
public Chimpanzee() {
super();
}
public static void main(String[] args) {
new Chimpanzee();
}
}
// The code will execute with the parent constructors called first and yields the following output:
// Primate
// Ape
// "super()" vs "super":
// "super()" is a statement that explicitly calls a parent constructor and may only be used in the first line of a constructor of a child class. Can be used only in a constructor.
// "super" is a keyword used to reference a member defined in a parent class (in the same manner as "this" keyword is used to reference a member defined in the current class). Can be used in any method of the child class.
// ####### Method Overriding:
// "super" calls the version of the method which would be in the current class if the current class would not override it. That means, that if the method was overridden a few times through the hierarchy, then only the closest implementation (i.e. the last one in the ancestors chain) is accessible with "super". There is no way to access other, earlier overrides.
// 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 version of a method, respectively. We illustrate this with the following example:
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()); // 50.00
System.out.println(new Wolf().getAverageWeight()); // 70.00
}
}
// The compiler performs the following checks when you override a nonprivate method:
// 1. The method in the child class must have the same signature as the method in the parent class.
// If two methods have the same name but different signatures, the methods are overloaded, not overridden.
// 2. The method in the child class must be at least as accessible or more accessible than the method in the parent class.
// For example, if parent's method is protected, the override in an descendant can be protected or public, but not private or default.
// 3. The method in the child class may not throw a checked exception that is new or broader than the class of any exception thrown
// in the parent class method.
// 4. If the method returns a value, it must be the same or a subclass of the method in the parent class, known as covariant return types.
// Illustration of rule 3:
public class InsufficientDataException extends Exception {}
public class Reptile {
protected boolean hasLegs() throws InsufficientDataException {
throw new InsufficientDataException();
}
protected double getWeight() throws Exception {
return 2;
}
}
public class Snake extends Reptile {
protected boolean hasLegs() { // OK - no new exception is introduced
return false;
}
protected double getWeight() throws InsufficientDataException{ // OK - InsufficientDataException is not broader than Exception
// (oppositely - it is narrower)
return 2;
}
}
// Both parent and child classes define two methods, hasLegs() and getWeight(). The first method, hasLegs(), throws an exception InsufficientDataException in the parent class but doesn't throw an exception in the child class. This does not violate the third rule, though, as no new exception is defined. In other words, a child method may hide or eliminate a parent method's exception without issue. The second method, getWeight(), throws Exception in the parent class and InsufficientDataException in the child class. This is also permitted, as InsufficientDataException is a subclass of Exception.
// Let's review some examples that do violate the third rule of overriding methods:
public class InsufficientDataException extends Exception {}
public class Reptile {
protected double getHeight() throws InsufficientDataException {
return 2;
}
protected int getLength() {
return 10;
}
}
public class Snake extends Reptile {
protected double getHeight() throws Exception { // DOES NOT COMPILE - Exception is not a subclass of InsufficientDataException
return 2;
}
protected int getLength() throws InsufficientDataException { // DOES NOT COMPILE - a new exception is introduced (the parent class didn't have it)
return 10;
}
}
// Redeclaring private Methods:
// The previous section defined the behavior if you override a public or protected method in the class. Now let's expand our discussion to private methods. It is not possible to override a private method in a parent class since the parent method is not accessible from the child class. 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, 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 are invoked. THE TYPE OF THE VARIABLE (NOT OF THE ACTUAL OBJECT) DEFINES WHICH VERSION IS INVOKED. Example:
public abstract class Bird {
private void fly() { System.out.println("Bird is flying"); } // private!
public static void main(String[] args) {
Bird bird = new Pelican();
bird.fly(); // prints "Bird is flying" since the var is of type Bird; it would print "Pelican is flying" if the var would be of type Pelican,
// or if fly() in Bird would be protected (in the last case it would be an overriding, not a redeclaring).
}
}
class Pelican extends Bird {
protected void fly() { System.out.println("Pelican is flying"); } // absolutely not related to the fly() method of the Bird class
}
// Hiding Static Methods:
// A hidden method occurs when a child class defines a static method with the same name and signature as a static method defined in a parent class. Unlike overriding a method, in which a child method replaces the parent method in calls defined in both the parent and child, hidden methods only replace parent methods in the calls defined in the child class. Method hiding is similar but not exactly the same as method overriding. First, the four previous rules for overriding a method must be followed when a method is hidden. In addition, a new rule is added for hiding a method, namely that the usage of the static keyword must be the same between parent and child classes:
// 5. The method defined in the child class must be marked as static if it is marked as static in the parent class (method hiding).
// Likewise, the method must not be marked as static in the child class if it is not marked as static in the parent class (method overriding).
// NON-STATIC IN ANCESTOR AND STATIC IN DESCENDANT (OR VICE VERSA) WILL GIVE A COMPILATION ERROR. THEY MUST BE SAME IN BOTH!
// Hiding static methods is fraught with pitfalls and potential problems and as a practice should be avoided. Though you might see questions on the exam that contain hidden static methods that are syntactically correct, avoid hiding static methods in your own work, since it tends to lead to confusing and difficult-to-read code. You should not reuse the name of a static method in your class if it is already used in the parent class.
// Overriding vs. Hiding Methods:
// Overriding a method: a child method replaces the parent method in calls defined in both the parent and child.
// Hidden methods only replace parent methods in the calls defined in the child class.
public class Marsupial {
public static boolean isBiped() {return false;} // static!
public void getMarsupialDescription() {
System.out.println("Marsupial walks on two legs: " + isBiped()); // call isBiped defined in the type of the would-be run-time pointer
}
}
public class Kangaroo extends Marsupial {
public static boolean isBiped() {return true;} // must be static as well!
public void getKangarooDescription() {
System.out.println("Kangaroo hops on two legs: " + isBiped()); // call isBiped defined in the type of the would-be run-time pointer
}
public static void main(String[] args) {
Kangaroo joey = new Kangaroo();
joey.getMarsupialDescription(); // Marsupial walks on two legs: true (isBiped of Kangaroo was called since that is the type of the pointer joey)
joey.getKangarooDescription(); // Kangaroo hops on two legs: true
}
}
// In this example, the isBiped() method is overridden, not hidden, in the child class. Therefore, it is replaced at runtime in the parent class with the call to the child class's method. This example used polymorphism.
// Creating final methods:
// You can create a method with the final keyword. By doing so you forbid a child class from overriding 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 PARENT CLASS IF IT IS MARKED AS final. You'd mark a method as final when you're defining a parent class and want to guarantee certain very precise behavior of a method in the parent class, regardless of which child is invoking the method.
// Hiding Variables:
// When you hide an instance or static variable, you define a variable with the same name as a variable in a parent class. This creates two copies of the variable within an instance of the child class: one instance defined for the parent reference and another defined for the child reference. As when hiding a static method, you can't override a variable; you can only hide it. Also similar to hiding a static method, the rules for accessing the parent and child variables are quite similar. If you're referencing the variable from within the parent class, the variable defined in the parent class is used. Alternatively, if you're referencing the variable from within a child class, the variable defined in the child class is used. Likewise, you can reference the parent value of the variable with an explicit use of the super keyword. The descendant object contains two copies of the variable with the same name: one defined in the parent class and one defined in the child class. These variables are kept separate from each other, allowing the descendant object to reference both independently (with this and with super).
// Although Java allows you to hide a variable defined in a parent class with one defined in a child class, it is considered an extremely poor coding practice. Hiding variables makes the code very confusing and difficult to read. When defining a new variable in a child class, it is considered good coding practice to select a name for the variable that is not already a public, protected, or default variable in use in a parent class. Hiding private variables is considered less problematic because the child class did not have access to the variable in the parent class to begin with.
// Abstract classes & methods:
// An abstract class is a class that is marked with the abstract keyword and cannot be instantiated. An abstract method is a method marked with the abstract keyword defined in an abstract class, for which no implementation is provided in the class in which it is declared. An abstract class may include nonabstract methods and variables. In fact, an abstract class is not required to include any abstract methods. An abstract method may only be defined in an abstract class. An abstract method cannot have implementation (i.e. the semicolon appears immediately after the signature - no curly braces at all!). For example, the following code won't compile because an abstract method is not defined within an abstract class:
public class Chicken {
public abstract void peck(); // DOES NOT COMPILE
}
// The exam creators are fond of questions like this one, which mixes nonabstract classes with abstract methods. They are also fond of questions with methods marked as abstract for which an implementation is also defined.
public abstract class Turtle {
public abstract void swim() {}; // DOES NOT COMPILE - even empty body is considered an implementation; must be: public abstract void swim();
public abstract int getAge() { // DOES NOT COMPILE
return 10;
}
}
// A concrete class is the first nonabstract subclass that extends an abstract class. It is required to implement all inherited abstract methods. When you see a concrete class extending an abstract class on the exam, check that it implements all of the required abstract methods. Abstract classes can extend other abstract classes and are not required to provide implementations for any of the abstract methods. A concrete subclass is not required to provide an implementation for an abstract method if an intermediate abstract class provides the implementation. Here's one way to think about this: if an intermediate class provides an implementation for an abstract method, that method is inherited by subclasses as a concrete method, not as an abstract one. In other words, the subclasses do not consider it an inherited abstract method because it is no longer abstract by the time it reaches the subclasses.
// Abstract Class Definition Rules:
// 1. Abstract classes cannot be instantiated directly.
// 2. Abstract classes may be defined with any number, including zero, of abstract and non-abstract methods.
// 3. Abstract classes may not be marked as private or final.
// 4. An abstract class that extends another abstract class inherits all of its abstract methods as its own abstract methods.
// 5. The first concrete class that extends an abstract class must provide an implementation for all of the inherited abstract methods.
// Abstract Method Definition Rules:
// 1. Abstract methods may only be defined in abstract classes.
// 2. Abstract methods may not be declared private or final (that would prevent them from being implemented!).
// 3. Abstract methods must not provide a method body/implementation in the abstract class for which is it declared.
// 4. Implementing an abstract method in a subclass follows the same rules for overriding a method. For example, the name and signature must be the same, and the visibility of the method in the subclass must be at least as accessible as the method in the parent class.
// ####### Interface:
// The modifiers abstract and public are assumed for default and static methods. In other words, whether or not you provide them, the compiler will automatically insert them as part of the method definition. Prior to Java 8 ALL interface methods would be assumed to be abstract. Since Java 8 now includes default and static methods and they are never abstract, you cannot assume the abstract modifier will be implicitly applied to ALL methods by the compiler.
// The first concrete class that implements an interface (or extends an abstract class that implements an interface but doesn't implemet all its methods), must provide an implementation for all of the inherited abstract methods.
// You must implement an abstract method of an interface using the explicit access modifier public!!!!!!!!!!!!!!!
// You can explicitly cast any object to an interface, even if it doesn't implement it to make the code compile. But if the object's class doesn't implement the interface, the code will throw a ClassCastException at runtime:
Jumpable var = (Jumpable)(new Animal()); // compiles anyway; will throw ClassCastException if Animal class doesn't implement Jumpable interface
// Interfaces allow multiple inheritance:
public interface HasTail {public int getTailLength();}
public interface HasWhiskers {public int getNumberOfWhiskers();}
public interface Seal extends HasTail, HasWhiskers {}
// Any class that implements the Seal interface must provide an implementation for all methods in the parent interfaces - in this case, getTailLength() and getNumberOfWhiskers().
// If you define a class that implements two interfaces that contain the same abstract method, one implementation of that abstract method in the class will satisfy both the interfaces.
// What about an abstract class that implements an interface? In this scenario, the abstract class is treated in the same way as an interface extending another interface. In other words, the abstract class inherits the abstract methods of the interface but is not required to implement them. That said, like an abstract class, the first concrete class to extend the abstract class must implement all the inherited abstract methods of the interface:
public interface HasTail {public int getTailLength();}
public interface HasWhiskers {public int getNumberOfWhiskers();}
public abstract class HarborSeal implements HasTail, HasWhiskers {} // compiles successfully since it's abstract - implementing is optional
public class LeopardSeal implements HasTail, HasWhiskers {} // DOES NOT COMPILE - not abstract, so must implement all the methods of all the interfaces!
// Although a class can implement an interface, a class cannot extend an interface - the only connection between a class and an interface is "class implements interface". Likewise, whereas an interface can extend another interface, an interface cannot implement another interface:
public interface CanRun {}
public class Cheetah extends CanRun {} // DOES NOT COMPILE - classes can only implement interfaces, not extend
public class Hyena {}
public interface HasFur extends Hyena {} // DOES NOT COMPILE - interfaces can extend only other interfaces, but not classes
// It is prohibited to a class define two methods with the same name and input parameters but different return types. So, a class cannot implement (and interface cannot extend) 2 interfaces which have methods with same name and signature, but differ by return type:
public interface Herbivore {public int eatPlants();}
public interface Omnivore {public void eatPlants();}
public interface Supervore extends Herbivore, Omnivore {} // DOES NOT COMPILE
public abstract class AbstractBear implements Herbivore, Omnivore {} // DOES NOT COMPILE
// Interface Variables (in fact, constants):
// Like interface methods, interface variables are assumed to be public. Unlike interface methods, though, interface variables are also assumed to be static and final. Here are two interface variables rules:
// 1. Interface variables are assumed to be public, static, and final. Therefore, marking a variable as private or protected will trigger a compiler error, as will marking any variable as abstract.
// 2. The value of an interface variable must be set when it is declared since it is marked as final.
// In this manner, interface variables are essentially constants defined on the interface level. Because they are assumed to be static, they are accessible even without an instance of a class which implements the interface. The following two interface definitions are equivalent, because the compiler will automatically convert them both to the second example:
public interface CanSwim {
int MAXIMUM_DEPTH = 100;
final static boolean UNDERWATER = true;
public static final String TYPE = "Submersible";
}
public interface CanSwim {
public static final int MAXIMUM_DEPTH = 100;
public static final boolean UNDERWATER = true;
public static final String TYPE = "Submersible";
}
// As we see in this example, the compiler will automatically insert public static final to any constant interface variables it finds missing those modifiers.
public interface CanDig {
private int MAXIMUM_DEPTH = 100; // DOES NOT COMPILE - private conflicts with assumed public
protected abstract boolean UNDERWATER = false; // DOES NOT COMPILE - protected conflicts with assumed public, abstract conflicts with assumed final
public static String TYPE; // DOES NOT COMPILE - constants must be initialized
}
// Default Interface Methods:
// A default method is a method defined within an interface with the default keyword in which a method body is provided. Contrast default methods with "regular" methods in an interface, which are assumed to be abstract and may not have a method body. Classes have the option to override the default method if they need to, but they are not required to do so. If the class doesn't override the method, the default implementation will be used. By providing a default implementation of the method, though, the interface becomes backward compatible with the existing codebase, while still providing those individuals who do want to use the new method with the option to override it:
public interface IsWarmBlooded {
boolean hasScales(); // normal abstract method
public default double getTemperature() {return 10.0;} // default method
}
// The following are the default interface method rules:
// 1. A default method may only be declared within an interface and not within a class (even an abstract class).
// 2. A default method must be marked with the default keyword. If a method is marked as default, it must provide a method body.
// 3. A default method is not assumed to be static, final, or abstract, as it may be used or overridden by a class that implements the interface.
// 4. Like all methods in an interface, a default method is assumed to be public and will not compile if marked as private or protected.
// 5. While overriding a default method in a class, which implements the interface, you must not use the keyword default.
public interface Carnivore {
public default void eatMeat(); // DOES NOT COMPILE - marked as default but doesn't provide a method body
public int getRequiredFoodAmount() {return 13;} // DOES NOT COMPILE - provides a method body but is not marked with the default keyword
}
// Unlike interface variables, which are assumed static class members, default methods cannot be marked as static and require an instance of the class implementing the interface to be invoked. They cannot be marked as final (because they are allowed to be overridden in subclasses) or abstract (because they have body).
// When an interface extends another interface that contains a default method, it may choose to ignore the default method, in which case the default implementation for the method will be used. Alternatively, the interface may override the definition of the default method using the standard rules for method overriding, such as not limiting the accessibility of the method and using covariant returns. Finally, the interface may redeclare the method as abstract, requiring classes that implement the new interface to explicitly provide a method body. Analogous options apply for an abstract class that implements an interface. For example, the following class overrides one default interface method and redeclares a second interface method as abstract:
public interface HasFins {
public default int getNumberOfFins() {return 4;}
public default double getLongestFinLength() {return 20.0;}
public default boolean doFinsHaveScales() {return true;}
}
public interface SharkFamily extends HasFins {
public default int getNumberOfFins() {return 8;} // override it
public double getLongestFinLength(); // replace the default method with a new abstract method, forcing any class,
// implementing the interface, to provide an implementation of the method
public boolean doFinsHaveScales() {return false;} // DOES NOT COMPILE - has a body, but is not marked as default
}
// Default Methods and Multiple Inheritance:
// If a class implements two interfaces that have default methods with the same name and signature, the compiler will throw an error:
public interface Walk {
public default int getSpeed() {return 5;}
}
public interface Run {
public default int getSpeed() {return 10;}
}
public class Cat implements Walk, Run { // DOES NOT COMPILE
public static void main(String[] args) {
System.out.println(new Cat().getSpeed()); // it would be not clear which version to call
}
}
// There is an exception to this rule, though: if the subclass overrides the duplicate default methods, the code will compile without issue - the ambiguity about which version of the method to call has been removed. For example, the following modified implementation of Cat will compile and output 1:
public class Cat implements Walk, Run {
public int getSpeed() {return 1;}
public static void main(String[] args) {
System.out.println(new Cat().getSpeed());
}
}
// You can see that having a class that implements or inherits two duplicate default methods forces the class to implement a new version of the method, or the code will not compile. This rule holds true even for abstract classes that implement multiple interfaces, because the default method could be called in a concrete method within the abstract class.
// Static Interface Methods:
// These methods are defined explicitly with the static keyword and function nearly identically to static methods defined in classes. In fact, there is really only one distinction between a static method in a class and an interface. A static method defined in an interface is not inherited in any classes that implement the interface. Here are the static interface method rules you need to be familiar with:
// 1. Like all methods in an interface, a static method is assumed to be public and will not compile if marked as private or protected.
// 2. To reference the static method, a reference to the name of the interface must be used. A STATIC METHOD IN AN INTERFACE CAN'T BE CALLED USING A REFERENCE VARIABLE - IT MUST BE CALLED USING THE INTERFACE NAME. Unlike an interface, if you define a static method in a base class, it can be accessed using either a reference variable or the class name.
// The following is an example of a static method defined in an interface:
public interface Hop {
static int getJumpHeight() {return 8;}
}
public class Bunny implements Hop {
public void printDetails() {
System.out.println(this.getJumpHeight()); // DOES NOT COMPILE without a reference to the name of the interface,
// even though Bunny implements Hop - Bunny doesn't inherit getJumpHeight()!
System.out.println(Hop.getJumpHeight()); // compiles successfully with a reference to the interface name
}
}
// A class that implements two interfaces containing static methods with the same signature will still compile at runtime, because the static methods are not inherited by the subclass and must be accessed with a reference to the interface name. Contrast this with the behavior you saw for default interface methods (the code would compile if the subclass overrode the default methods and would fail to compile otherwise). Static interface methods have none of the same multiple inheritance issues and rules as default interface methods do.
// If an interface defines a static method, the class that implements it can define a static method with the same name, but the method in the interface isn't related to the method defined in the class. Static methods in a class and the interface that it implements are not related to each other. A static method in a class doesn't hide or override the static method in the interface that it implements.
// A class can implement multiple interfaces with the same constant names, only if a reference to the constants isn't ambiguous (i.e. interfaceName.CONSTANT rather than CONSTANT).
// ####### Polymorphism:
// An object may be accessed using a reference with the same type as the object, a reference with the type of a superclass of the object, or a reference with the type of 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. Regardless of the type of the reference you have for the object in memory, the object itself doesn't change (it only becomes pointed by other variables).
// Once the object has been assigned a new reference type, only the methods and variables available to that reference type are callable on the object without an explicit cast:
public class Primate {
public boolean hasHair() {return true;}
}
public interface HasTail {
public boolean isTailStriped();
}
public class Lemur extends Primate implements HasTail {
public int age = 10;
public boolean isTailStriped() {return false;}
public static void main(String[] args) {
Lemur lemur = new Lemur(); // a reference with the same type as the object
System.out.println(lemur.age); // 10
HasTail hasTail = lemur; // a reference with the type of an interface the object implements
System.out.println(hasTail.isTailStriped()); // false
System.out.println(hasTail.age); // DOES NOT COMPILE - the reference hasTail has direct access only to methods defined with the HasTail interface;
// it doesn't know which members exist in objects which implement it;
// therefore, it doesn't know the variable age is part of the object
Primate primate = lemur; // a reference with the type of a superclass of the object
System.out.println(primate.hasHair()); // true
System.out.println(primate.isTailStriped()); // DOES NOT COMPILE - the reference primate has access only to methods defined
// in the ancestor (Primate) class; it doesn't know which interfaces are implemented in descenants;
// therefore, it doesn't know about existence of the isTailStriped() method in that descendant (Lemur)
}
}
// Casting Objects:
// An instance can be automatically cast to a superclass or interface reference without an explicit cast. Alternatively, an explicit cast is required if the reference is being narrowed to a subclass of the object. In the previous example, we created a single instance of a Lemur object and accessed it via superclass and interface references. Once we changed the reference type, though, we lost access to more specific methods defined in the subclass that still exist within the object. We can reclaim those references by casting the object back to the specific subclass it came from:
Primate primate = lemur;
Lemur lemur2 = primate; // DOES NOT COMPILE - we try to convert an ancestor type reference (Primate) back to
// a descendant type reference (Lemur) without an explicit cast
Lemur lemur3 = (Lemur)primate; // success when we explicitly cast...
System.out.println(lemur3.age); // ...and we gain back access to all the methods available to the descendant class (Lemur)
// Here are some basic rules to keep in mind when casting variables:
// 1. Casting an object from a subclass to a superclass doesn't require an explicit cast (any Lemur is a Primate for sure).
// 2. Casting an object from a superclass to a subclass requires an explicit cast (it's not guaranteed, that the Primate is a Lemur - for example,
// it can be a monkey; an explicit cast says: "you can assign safely - I know 100%, that the animal, pointed by primate variable, is a lemur!").
// 3. The compiler will not allow casts to unrelated types:
public class Bird {}
public class Book {
public static void main(String[] args) {
Book book = new Book();
Bird bird = (Bird)book; // DOES NOT COMPILE - the classes Book and Bird don't belong to a same hierarchy branch
}
}
// 4. Even when the code compiles without issue, an exception may be thrown at runtime if the object being cast is not actually an instance of that class. Even though two classes share a related hierarchy, that doesn't mean an instance of one can automatically be cast to another!
public class Primate {}
public class Lemur extends Primate {
public static void main(String[] args) {
Primate primate = new Primate();
Lemur lemur = (Lemur)primate; // compiles successfully, but throws ClassCastException at runtime; the object,
// referenced by primate var, is not an instance of the Lemur class!
}
}
// Although this topic is out of scope for the OCA exam, keep in mind that the "instanceof" operator can be used to check whether an object belongs to a particular class and to prevent ClassCastExceptions at runtime. Unlike the previous example, the following code snippet doesn't throw an exception at runtime:
if (primate instanceof Lemur) {
Lemur lemur = (Lemur)primate;
}
// 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.
// Virtual Methods:
// A virtual method is a method in which the specific implementation is not determined until runtime. In fact, all non-final, non-static, and non-private Java methods are considered virtual methods, since any of them can be overridden at runtime. If you call a method on an object that overrides a method, you get the overridden method, even if the call to the method is on a parent reference or within the parent class. In other words, THE VERSION OF THE OBJECT'S ACTUAL TYPE WILL BE CALLED (NOT OF THE POINTER'S TYPE):
public class Bird {
public String getName() {return "Unknown";}
public void displayInformation() {
System.out.println("The bird name is: " + getName()); // it can be the version of this class, or a descendant's class (if getName()
// is overridden in a descendant); that is unknown in compile time
}
}
public class Peacock extends Bird {
public String getName() {return "Peacock";} // overridden in this descendant!
public static void main(String[] args) {
Bird bird = new Peacock();
bird.displayInformation(); // "The bird name is: Peacock" - the version of the object's actual type (Peacock)
// is called, not of the pointer's type (Bird)!!!!!!!
}
}
// Polymorphism and Method Overriding:
// The first rule of overriding is that an overriding method must be at least as accessible as the method, overridden by it. Let's assume this rule is not necessary and consider the following example:
public class Animal {
public String getName() {return "Animal";}
}
public class Gorilla extends Animal {
protected String getName() {return "Gorilla";} // DOES NOT COMPILE since protected is less accessible than public;
// but let's suppose it compiles successfully
}
public class ZooKeeper {
public static void main(String[] args) {
Animal animal = new Gorilla();
System.out.println(animal.getName()); // the reference animal.getName() is allowed because the method is public in the Animal class.
// The version of Gorilla will be called (since the object, pointed by animal, is Gorilla); but the version
// of Gorilla is protected and, therefore, invisible for ZooKeeper! Java eliminates this contradiction,
// disallowing a method from being overridden by a less accessible version.
}
}
// Likewise, a subclass cannot declare an overridden method with a new or broader exception than in the superclass, since the method may be accessed using a reference to the superclass. For example, if an instance of the subclass is passed to a method using a superclass reference, then the enclosing method would not know about any new checked exceptions that exist on methods for this object, potentially leading to compiled code with checked (by the classification) exceptions which are in the reality not checked, i.e. that are never handled (with "catch") or thrown outwards (with "throws"). Therefore, the Java compiler disallows overriding methods with new or broader exceptions.
// Finally, overridden methods must use covariant return types for the same kinds of reasons as just discussed. If an object is cast to a superclass reference and the overridden method is called, the return type must be compatible with the return type of the parent method. If the return type in the child is too broad, it will result an inherent cast exception when accessed through the superclass reference. For example, if the return type of a method is Double in the parent class and is overridden in a subclass with a method that returns Number (a superclass of Double), then the subclass method would be allowed to return any valid Number, including Integer, another subclass of Number. If we are using the object with a reference to the superclass, that means an Integer could be returned when a Double was expected. Since Integer is not a subclass of Double, this would lead to an implicit cast exception as soon as the value was referenced. Java solves this problem by only allowing covariant return types for overridden methods.
// ####### Exceptions:
// java.lang.Object
// |
// java.lang.Throwable
// / \
// / \
// java.lang.Exception java.lang.Error
// |
// java.lang.RuntimeException
// <<< Checked exception - inherited directly from java.lang.Exception: >>>
// * "Checked" means "checked at compile time, i.e. processed by a programmer in the code". Since checked exceptions are anticipated, Java enforces that the programmer do something to show the exception was thought about. In other words: if a method throws (or calls a method which throws) a checked exception, then it should handle that exception (using "try-catch" block) or pass it up the stack (using "throws" keyword in the signature), otherwise the program will give a compilation error.
// * Intended for issues a programmer might reasonably be expected to recover from - for example, to display an explanatory message about a business situation.
// * These are technical or business exceptions that are INTERNAL TO THE APPLICATION, and that the application usually can anticipate or recover from.
// * Thrown by Java (built in technical exceptions) or by coders (custom created business rule exceptions - like CandidateTooYong or SalaryTooBigException).
// * In fact, a business rule exception is not necesseraly something bad - it can be a way of program logic branching (an instruction what to do in different circumstances). For example, if OutOfHoursOfOperationException thrown, the program can update other tables (compared to the normal hours). In such a situation, no error message is displayed.
// * Examples of Checked exception:
// FileNotFoundException Thrown programmatically when code tries to reference a file that does not exist
// IOException Thrown programmatically when there's a problem reading or writing a file
// * More examples: PrinterException, PrintException, SQLException, ServerNotActiveException, SOAPException, URISyntaxException.
// <<< Unchecked (aka runtime) exception - inherited from java.lang.RuntimeException: >>>
// * "Unchecked" means "unchecked at compile time, i.e. NOT required to be processed by a programmer in the code" (but Java will automatically check at runtime).
// * A method is not obliged to handle (i.e. "catch" or pass outwards with "throws") unchecked exceptions. In fact, a huge amount of problems can arise at runtime - it would be impractical to handle all possible exception. For example, a NullPointerException can happen when you try to call a member on a null reference. This can occur in any method. If you had to handle runtime exceptions everywhere, every single method would have that clutter!
// * These are technical exceptions that are INTERNAL TO THE APPLICATION, and that the application usually cannot anticipate or recover from.
// * Usually thrown by JVM, but can be thrown by the programmer as well (for example, IllegalArgumentException if the method discovered, that an argument contains an illegal value, or NullPointerException if the method discovered, that an argument contains null - they are unchecked and, therefore, are not needed to be added tho the "throws" section of the signature).
// * It doesn't mean that if compiler doesn't check exceptions, inherited from java.lang.RuntimeException, we cannot handle them:
class Example {
public static void main(String args[]) {
try {
int arr[] = {1,2,3,4,5};
System.out.println(arr[7]);
}
catch(ArrayIndexOutOfBoundsException e) {
System.out.println("The specified index does not exist in array.");
}
}
}
// * Examples:
// ArithmeticException - Thrown by the JVM when code attempts to divide by zero, and both the numbers are integers.
// If one of the numbers is decimal (the divided number, or divide by 0.0, not by 0), the expression will NOT throw ArithmeticException -
// instead, it will return Infinity (if the divided number is positive) or -Infinity (if the divided number is negative).
// ArrayIndexOutOfBoundsException - Thrown by the JVM when code uses an illegal index to access an array
// IndexOutOfBoundsException - Thrown by the JVM when code uses an illegal index to access an ArrayList
// ClassCastException - Thrown by the JVM when an attempt is made to cast an exception to a subclass of which it is not an instance
// IllegalArgumentException - Even though it's a runtime exception, programmers usually use it to indicate that a method has been passed
// an inappropriate argument (when throw, pass an explanatory message as the exception's argument)
// NullPointerException - Thrown by the JVM when there is a null reference where an object is required
// NumberFormatException - Thrown by the programmer when an attempt is made to convert a string to a numeric type
// but the string doesn't have an appropriate format
// <<< Unchecked (severe) exception - inherited from java.lang.Error: >>>
// * A serious problem that a reasonable application should not try to catch. Most such errors are abnormal conditions - something went so horribly wrong that your program should not attempt to recover from it. For example, the disk drive "disappeared". These are conditions that you aren't likely to encounter.
// * These are technical exceptions that are EXTERNAL TO THE APPLICATION, and that the application usually cannot anticipate or recover from. For example, suppose that an application successfully opens a file for input, but is unable to read the file because of a hardware or system malfunction. The unsuccessful read will throw java.io.IOError. An application might choose to catch this exception, in order to notify the user of the problem - but it also might make sense for the program to print a stack trace and exit.
// * Thrown by JVM only (not by the programmer!).
// * Examples of Severe unchecked exception:
// ExceptionInInitializerError - when a static initializer throws an exception and doesn't handle it
// StackOverflowError - when a method calls itself too many times (this is called infinite recursion because the method typically
// calls itself without end)
// NoClassDefFoundError - when a class that the code uses is available at compile time but not runtime
// * More examples: ThreadDeath, VirtualMachineError, IOError (thrown when a serious I/O error has occurred).
// The try-catch block must have curly braces even if there is only one statement in each section:
try // DOES NOT COMPILE
fall();
catch (Exception e)
System.out.println("get up");
// The exam will try to trick you with missing clauses or clauses in the wrong order:
try { // DOES NOT COMPILE because the catch and finally blocks are in the wrong order
fall();
} finally {
System.out.pr
} catch (Exception e) {
System.out.println("get up");
}
try { // DOES NOT COMPILE because there must be a catch or finally block
fall();
}
try { // just fine - catch is not required if finally is present
fall();
} finally {
System.out.println("all better");
}
// There is one exception to "the finally block always runs after the catch block" rule: Java defines a method that you call as System.exit(0);. The integer parameter is the error code that gets returned. System.exit tells Java, "Stop. End the program right now". When System.exit is called in the try or catch block, finally does not run.
// Multiple catch blocks are also allowed, provided no superclass exception type appears in an earlier catch block than its subclass. Java looks at the catch blocks in the order they appear. If it is impossible for one of the catch blocks to be executed, a compiler error about unreachable code occurs. This happens when a superclass is caught before a subclass:
class ExhibitClosed extends RuntimeException { }
class ExhibitClosedForLunch extends ExhibitClosed { }
public void visitMonkeys() {
try {
seeAnimal();
} catch (ExhibitClosed e) { // the more specific ExhibitClosedForLunch exception will be caught here since it is a subclass of ExhibitClosed -
// which means there is no way for the second catch block to ever run!
System.out.print("not today");
} catch (ExhibitClosedForLunch e) {// DOES NOT COMPILE - Java correctly tells us there is an unreachable catch block
System.out.print("try back later");
}
}
// If both catch and finally throw an exception, the one from finally gets thrown (the exception from the catch block gets forgotten about):
try {
throw new RuntimeException(); // it causes to execute the catch
} catch (RuntimeException e) {
throw new RuntimeException(); // another exception thrown (if there were no finally block, it will be ultimately thrown outwards)
} finally {
throw new Exception(); // The finally block runs after the try block. Since the finally block throws an exception of its own, this one gets ultimately thrown outwards.
// The RuntimeException from the catch block gets forgotten about.
}
// Next we are going to show you the hardest example you can be asked related to exceptions:
public String exceptions() {
String result = "";
String v = null;
try {
try {
result += "before ";
v.length();
result += "after ";
} catch (NullPointerException e) {
result += "catch ";
throw new RuntimeException();
} finally {
result += "finally ";
throw new Exception();
}
} catch (Exception e) {
result += "done";
}
return result; // "before catch finally done"
}
// When a class overrides a method from a superclass or implements a method from an interface, it's not allowed to add new CHECKED exceptions to the method signature:
class CanNotHopException extends Exception { }
class Hopper {
public void hop() { }
}
class Bunny extends Hopper {
public void hop() throws CanNotHopException { } // DOES NOT COMPILE
}
// Imagine what would happen if subclasses could add checked exceptions - you could write code that calls Hopper's hop() method and not handle any exceptions. Then if Bunny was used in its place, the code wouldn't know to handle or declare CanNotHopException.
// This rule applies only to checked exceptions. The following code is legal because it has an unchecked exception in the subclass's version:
class Hopper {
public void hop() { }
}
class Bunny extends Hopper {
public void hop() throws IllegalStateException { } // OK - IllegalStateException is unchecked
}
// The reason that it's okay to declare new runtime exceptions in a subclass method is that the declaration is redundant. Methods are free to throw any unchecked exceptions they want without mentioning them in the method declaration.
// When overriding a method, the method in the subclass is allowed to throw (declare) FEWER exceptions than the original version (in the superclass or interface). This is legal because callers are already handling them:
class Hopper {
public void hop() throws CanNotHopException { }
}
class Bunny extends Hopper {
public void hop() { } // OK; not-declaring an exception means that the method will never throw it - no problem!
}
// A subclass not declaring an exception is similar to a method declaring it throws an exception that it never actually throws. This is perfectly legal.
// Similarly, a class is allowed to declare a subclass of an exception type. The idea is the same. The superclass or interface has already taken care of a broader type:
class Hopper {
public void hop() throws Exception { }
}
class Bunny extends Hopper {
public void hop() throws CanNotHopException { } // OK; since a caller can handle any Exception, it can handle CanNotHopException as well!
}
// Bunny could declare that it throws Exception directly, or it could declare that it throws a more specific type of Exception. It could even declare that it throws nothing at all.
// Printing an Exception:
// There are three ways to print an exception. You can let Java print it out, print just the message, or print where the stack trace comes from:
private static void hop() {
throw new RuntimeException("cannot hop");
}
public static void main(String[] args) {
try {
hop();
}
catch (Exception e) {
System.out.println(e); // "java.lang.RuntimeException: cannot hop" - toString(), implemeted in Exception, returns exception's name and error message
System.out.println(e.getMessage()); // "cannot hop"
e.printStackTrace(); // "java.lang.RuntimeException: cannot hop
// at trycatch.Handling.hop(Handling.java:15)
// at trycatch.Handling.main(Handling.java:7)"
}
}
// Will a finally block execute even if the try or the catch block defines a return statement? Yes. The return statement does not return the control to the calling method before execution of the finally block completes. If both the catch and finally blocks define return statements, the calling method will receive a value from the finally block.
// Watch out for code that returns a value from the catch block and modifies it in the finally block. If a catch block returns a primitive data type, the finally block can't modify the value being returned by it. If a catch block returns an object, the finally block can modify the state of the object being returned by it.
// Predicate interface and its test() method.
// This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference. Represents a predicate (boolean-valued function) of one argument. This is a functional interface whose functional method is test(Object) which returns true if the input argument matches the predicate, otherwise false:
test(T t) // evaluates this predicate on the given argument
// The return type of the functional method test in the functional interface Predicate is boolean. The following Lambda expression is trying to return a String value and so the code fails compilation:
Predicate<String> aSeason = (s) -> s == "Summer" ? season1.name : season2.name;