Inheritance is one of the fundamental mechanisms for code reuse in OOP. It allows new classes to be derived from an existing class. The new class (also called subclass, subtype, derived class, child class) can inherit members from the old class (also called superclass, supertype, base class, parent class). The subclass can add new behavior and properties and, under certain circumstances, modify its inherited behavior.
In Java, implementation inheritance is achieved by extending classes (i.e., adding new fields and methods) and modifying inherited members (see Section 7.2, p. 288). Inheritance of members is closely tied to their declared accessibility. If a superclass member is accessible by its simple name in the subclass (without the use of any extra syntax like super
), that member is considered inherited. This means that private, overridden, and hidden members of the superclass are not inherited (see Section 7.2, p. 288). Inheritance should not be confused with the existence of such members in the state of a subclass object (see Example 7.1).
The superclass is specified using the extends
clause in the header of the subclass declaration. The subclass only specifies the additional new and modified members in its class body. The rest of its declaration is made up of its inherited members. If no extends
clause is specified in the header of a class declaration, the class implicitly inherits from the java.lang.Object
class (see Section 10.2, p. 424). This implicit inheritance is assumed in the declaration of the Light
class at (1) in Example 7.1. Also in Example 7.1, the subclass TubeLight
at (2) explicitly uses the extends
clause and only specifies additional members to what it already inherits from the superclass Light
(which, in turn, inherits from the Object
class). Members of the superclass Light
that are accessible by their simple names in the subclass TubeLight
, are inherited by the subclass.
Private members of the superclass are not inherited by the subclass and can only be indirectly accessed. The private field indicator
of the superclass Light
is not inherited, but exists in the subclass object and is indirectly accessible.
Using appropriate accessibility modifiers, the superclass can limit which members can be accessed directly and, thereby, inherited by its subclasses (see Section 4.9, p. 138). As shown in Example 7.1, the subclass can use the inherited members as if they were declared in its own class body. This is not the case for members that are declared private
in the superclass. Members that have package accessibility in the superclass are also not inherited by subclasses in other packages, as these members are accessible by their simple names only in subclasses within the same package as the superclass.
Since constructors (see Section 7.5, p. 302) and initializer blocks (see Section 9.7, p. 406) are not members of a class, they are not inherited by a subclass.
Extending generic classes is discussed in Section 14.2, p. 668.
Example 7.1 Extending Classes: Inheritance and Accessibility
class Light { // (1)
// Instance fields:
int noOfWatts; // wattage
private boolean indicator; // on or off
protected String location; // placement
// Static field:
private static int counter; // no. of Light objects created
// Constructor:
Light() {
noOfWatts = 50;
indicator = true;
location = "X";
counter++;
}
// Instance methods:
public void switchOn() { indicator = true; }
public void switchOff() { indicator = false; }
public boolean isOn() { return indicator; }
private void printLocation() {
System.out.println("Location: " + location);
}
// Static methods:
public static void writeCount() {
System.out.println("Number of lights: " + counter);
}
//...
}
//____________________________________________________
class TubeLight extends Light { // (2) Subclass uses the extends clause.
// Instance fields:
private int tubeLength = 54;
private int colorNo = 10;
// Instance methods:
public int getTubeLength() { return tubeLength; }
public void printInfo() {
System.out.println("Tube length: " + getTubeLength());
System.out.println("Color number: " + colorNo);
System.out.println("Wattage: " + noOfWatts); // Inherited.
// System.out.println("Indicator: " + indicator); // Not Inherited.
System.out.println("Indicator: " + isOn()); // Inherited.
System.out.println("Location: " + location); // Inherited.
// printLocation(); // Not Inherited.
// System.out.println("Counter: " + counter); // Not Inherited.
writeCount(); // Inherited.
}
// ...
}
//____________________________________________________
public class Utility { // (3)
public static void main(String[] args) {
new TubeLight().printInfo();
}
}
Output from the program:
Tube length: 54
Color number: 10
Wattage: 50
Indicator: true
Location: X
Number of lights: 1
In Java, a class can only extend one other class; i.e., it can only have one immediate superclass. This kind of inheritance is sometimes called single or linear implementation inheritance. The name is appropriate, as the subclass inherits the implementations of its superclass members. The inheritance relationship can be depicted as an inheritance hierarchy(also called class hierarchy). Classes higher up in the hierarchy are more generalized, as they abstract the class behavior. Classes lower down in the hierarchy are more specialized, as they customize the inherited behavior by additional properties and behavior. Figure 7.1 illustrates the inheritance relationship between the class Light
, which represents the more general abstraction, and its more specialized subclasses. The java.lang.Object
class is always at the top of any Java inheritance hierarchy, as all classes, with the exception of the Object
class itself, inherit (either directly or indirectly) from this class.
Inheritance defines the relationship is-a (also called the superclass–subclass relationship) between a superclass and its subclasses. This means that an object of a subclass is-a superclass object, and can be used wherever an object of the superclass can be used. This is often employed as a litmus test for choosing inheritance in object-oriented design. It has particular consequences on how objects can be used.
An object of the TubeLight
class is-an object of the superclass Light
. Referring to Figure 7.1, an object of the TubeLight
class can be used wherever an object of the superclass Light
can be used. The inheritance relationship is transitive: if class B
extends class A,
then a class C
, which extends class B
, will also inherit from class A
via class B
. An object of the SpotLightBulb
class is-an object of the class Light
. The is-a relationship does not hold between peer classes: an object of the LightBulb
class is not an object of the class TubeLight
and vice versa.
Whereas inheritance defines the relationship is-a between a superclass and its subclasses, aggregation defines the relationship has-a (also called the whole–part relationship) between an instance of a class and its constituents (also called parts). Aggregation comprises the usage of objects. An instance of class Light
has (or uses) the following parts: a field to store its wattage (noOfWatts
), a field to store whether it is on or off (indicator
), and a String
object to store its location (denoted by the field reference location
). In Java, a composite object cannot contain other objects. It can only store reference values of its constituent objects in its fields. This relationship defines an aggregation hierarchy (also called object hierarchy) that embodies the has-a relationship. Constituent objects can be shared between objects, and their lifetimes can be dependent or independent of the lifetime of the composite object. Inheritance and aggregation are compared in Section 7.13, p. 342.
A class defines a reference type. Therefore the inheritance hierarchy can be regarded as a type hierarchy, embodying the supertype-subtype relationship between reference types. In the context of Java, the supertype-subtype relationship implies that the reference value of a subtype object can be assigned to a supertype reference, because a subtype object can be substituted for a supertype object. This assignment involves a widening reference conversion (see Section 5.1, p. 161), as references are assigned up the inheritance hierarchy. Using the reference types in Example 7.1, the following code assigns the reference value of an object of the subtype TubeLight
to the reference light
of the supertype Light
:
Light light = new TubeLight(); // (1) widening reference conversion
We can now use the reference light
to invoke those methods on the subtype object that are inherited from the supertype Light
:
light.switchOn(); // (2)
Note that the compiler only knows about the declared type of the reference light
, which is Light
, and ensures that only methods from this type can be called using the reference light
. However, at runtime, the reference light
will refer to an object of the subtype TubeLight
when the call to the method switchOn()
is executed. It is the type of the object that the reference is referring to at runtime that determines which method is executed. The subtype object inherits the switchOn()
method from its supertype Light
, and it is this method that is executed. The type of the object that the reference refers to at runtime is often called the dynamic type of the reference.
One might be tempted to invoke methods exclusive to the TubeLight
subtype via the supertype reference light
:
light.getTubeLength(); // (3) Not OK.
However, this will not work, as the compiler does not know what object the reference light
is denoting. It only knows the declared type of the reference. As the declaration of the class Light
does not have a method called getTubeLength()
, this method call at (3) results in a compile-time error. As we shall see later in this chapter, eliciting subtype-specific behavior using a supertype reference requires a narrowing reference conversion with an explicit cast (Section 7.11, p. 327).
The rest of this chapter will elaborate on various aspects of OOP, and understanding them is founded in understanding the consequences of the subtype-supertype relationship.
Under certain circumstances, a subclass may override instance methods that it would otherwise inherit from a superclass. Overriding such a method allows the subclass to provide its own implementation of the method. When the method is invoked on an object of the subclass, it is the method implementation in the subclass that is executed. The overridden method in the superclass is not inherited by the subclass, and the new method in the subclass must abide by the following rules of method overriding:
• The new method definition must have the same method signature, i.e., the method name, and the types and the number of parameters, including their order, are the same as in the overridden method.
Whether parameters in the overriding method should be final
is at the discretion of the subclass (see Section 3.9, p. 95). A method’s signature does not comprise the final
modifier of parameters, only their types and order.
• The return type of the overriding method can be a subtype of the return type of the overridden method (called covariant return).
• The new method definition cannot narrow the accessibility of the method, but it can widen it (see Section 4.9, p. 138).
• The new method definition can only throw all or none, or a subset of the checked exceptions (including their subclasses) that are specified in the throws
clause of the overridden method in the superclass (see Section 6.9, p. 259).
These requirements also apply to interfaces, where a subinterface can override abstract method declarations from its superinterfaces (see Section 7.6, p. 309). The implications of generics on overriding methods is discussed in Section 14.12, p. 718.
In Example 7.2, the new definition of the getBill()
method at (6) in the subclass TubeLight
has the same signature and the same return type as the method at (2) in the superclass Light
. The new definition specifies a subset of the exceptions (ZeroHoursException
) thrown by the overridden method (the exception class Invalid HoursException
is a superclass of NegativeHoursException
and ZeroHoursException
). The new definition also widens the accessibility (public
) from what it was in the overridden definition (protected
). The overriding method also declares the parameter to be final
, but this has no bearing in overriding the method.
The astute reader will have noticed the @Override
annotation preceding the method definition at (6). The compiler will now report an error if the method definition does not override an inherited method. The annotation helps to ensure that the method definition overrides, and not overloads another method silently (see Section 14.12, p. 718).
Invocation of the method getBill()
on an object of subclass TubeLight
using references of the subclass and the superclass at (14) and (15), results in the new definition at (6) being executed, since both references are aliases of the TubeLight
object created at (11).
tubeLight.getBill(5); // (14) Invokes method at (6).
light1.getBill(5); // (15) Invokes method at (6).
Not surprisingly, the invocation of the method getBill()
on an object of superclass Light
using a reference of the superclass at (16), results in the overridden definition at (2) being executed:
light2.getBill(5); // (16) Invokes method at (2).
In Example 7.2, the definition of the method makeInstance()
at (8) overrides the method definition at (3). Note that the method signatures are the same, but the return type at (8) is a subtype of the return type at (3). The method at (8) returns an object of the subtype TubeLight
, whereas the method at (3) returns an object of the supertype Light
. This is an example of covariant return.
Depending on whether we call the method makeInstance()
on an object of the subtype TubeLight
or that of the supertype Light
, the respective method definition will be executed. The code at (17) and (18) illustrates what object is returned by the method, depending on which method definition is executed.
Note that covariant return only applies to reference types, not to primitive types. For example, changing the return type of the getBill()
method at (6) to float
, will result in a compile-time error. There is no subtype relationship between primitive types.
Example 7.2 Overriding, Overloading, and Hiding
//Exceptions
class InvalidHoursException extends Exception {}
class NegativeHoursException extends InvalidHoursException {}
class ZeroHoursException extends InvalidHoursException {}
class Light {
protected String billType = "Small bill"; // (1) Instance field
protected double getBill(int noOfHours)
throws InvalidHoursException { // (2) Instance method
if (noOfHours < 0)
throw new NegativeHoursException();
double smallAmount = 10.0, smallBill = smallAmount * noOfHours;
System.out.println(billType + ": " + smallBill);
return smallBill;
}
public Light makeInstance() { // (3) Instance method
return new Light();
}
public static void printBillType() { // (4) Static method
System.out.println("Small bill");
}
}
//____________________________________________________
class TubeLight extends Light {
public static String billType = "Large bill"; // (5) Hiding field at (1).
@Override
public double getBill(final int noOfHours)
throws ZeroHoursException { // (6) Overriding instance method at (2).
if (noOfHours == 0)
throw new ZeroHoursException();
double largeAmount = 100.0, largeBill = largeAmount * noOfHours;
System.out.println(billType + ": " + largeBill);
return largeBill;
}
public double getBill() { // (7) Overloading method at (6).
System.out.println("No bill");
return 0.0;
}
@Override
public TubeLight makeInstance() { // (8) Overriding instance method at (3).
return new TubeLight();
}
public static void printBillType() { // (9) Hiding static method at (4).
System.out.println(billType);
}
}
//____________________________________________________
public class Client {
public static void main(String[] args) throws InvalidHoursException { // (10)
TubeLight tubeLight = new TubeLight(); // (11)
Light light1 = tubeLight; // (12) Aliases.
Light light2 = new Light(); // (13)
System.out.println("Invoke overridden instance method:");
tubeLight.getBill(5); // (14) Invokes method at (6).
light1.getBill(5); // (15) Invokes method at (6).
light2.getBill(5); // (16) Invokes method at (2).
System.out.println(
"Invoke overridden instance method with covariant return:");
System.out.println(
light2.makeInstance().getClass()); // (17) Invokes method at (3).
System.out.println(
tubeLight.makeInstance().getClass()); // (18) Invokes method at (8).
System.out.println("Access hidden field:");
System.out.println(tubeLight.billType); // (19) Accesses field at (5).
System.out.println(light1.billType); // (20) Accesses field at (1).
System.out.println(light2.billType); // (21) Accesses field at (1).
System.out.println("Invoke hidden static method:");
tubeLight.printBillType(); // (22) Invokes method at (9).
light1.printBillType(); // (23) Invokes method at (4).
light2.printBillType(); // (24) Invokes method at (4).
System.out.println("Invoke overloaded method:");
tubeLight.getBill(); // (25) Invokes method at (7).
}
}
Output from the program:
Invoke overridden instance method:
Large bill: 500.0
Large bill: 500.0
Small bill: 50.0
Invoke overridden instance method with covariant return:
class Light
class TubeLight
Access hidden field:
Large bill
Small bill
Small bill
Invoke hidden static method:
Large bill
Small bill
Small bill
Invoke overloaded method:
No bill
Here are a few more facts to note about overriding. A subclass must use the keyword super
in order to invoke an overridden method in the superclass (see p. 295).
An instance method in a subclass cannot override a static
method in the superclass. The compiler will flag this as an error. A static method is class-specific and not part of any object, while overriding methods are invoked on behalf of objects of the subclass. However, a static method in a subclass can hide a static
method in the superclass (see below).
A final
method cannot be overridden, because the modifier final
prevents method overriding. An attempt to override a final
method will result in a compile-time error. An abstract
method, on the other hand, requires the non-abstract subclasses to override the method, in order to provide an implementation.
The accessibility modifier private
for a method means that the method is not accessible outside the class in which it is defined; therefore, a subclass cannot override it. However, a subclass can give its own definition of such a method, which may have the same signature as the method in its superclass.
Method overriding should not be confused with method overloading (see Section 3.3, p. 47).
Method overriding always requires the same method signature (name and parameter types) and the same or covariant return types. Overloading occurs when the method names are the same, but the parameter lists differ. Therefore, to overload methods, the parameters must differ either in type, order, or number. As the return type is not a part of the method signature, just having different return types is not enough to overload methods.
Only non-final instance methods in the superclass that are directly accessible from the subclass can be overridden. Both instance and static methods can be overloaded in the class they are defined in or in a subclass of their class.
Invoking an overridden method in the superclass from a subclass requires a special syntax (e.g., the keyword super
). This is not necessary for invoking an overloaded method in the superclass from a subclass. If the right kinds of arguments are passed in the method call occurring in the subclass, the overloaded method in the superclass will be invoked. In Example 7.2, the method getBill()
at (2) in class Light
is overridden in class TubeLight
at (6) and overloaded at (7). When invoked at (25), the definition at (7) is executed.
For overloaded methods, which method implementation will be executed at runtime is determined at compile time (see Section 7.10, p. 324), but for overridden methods, the method implementation to be executed is determined at runtime (see Section 7.12, p. 340). Table 7.1 provides a comparison between overriding and overloading.
A subclass cannot override fields of the superclass, but it can hide them. The subclass can define fields with the same name as in the superclass. If this is the case, the fields in the superclass cannot be accessed in the subclass by their simple names; therefore, they are not inherited by the subclass. Code in the subclass can use the keyword super
to access such members, including hidden fields. A client can use a reference of the superclass to access members that are hidden in the subclass, as explained below. Of course, if the hidden field is static, it can also be accessed by the superclass name.
The following distinction between invoking instance methods on an object and accessing fields of an object must be noted. When an instance method is invoked on an object using a reference, it is the class of the current object denoted by the reference, not the type of reference, that determines which method implementation will be executed. In Example 7.2 at (14), (15), and (16), this is evident from invoking the overridden method getBill()
: the method from the class corresponding to the current object is executed, regardless of the reference type. When a field of an object is accessed using a reference, it is the type of the reference, not the class of the current object denoted by the reference, that determines which field will actually be accessed. In Example 7.2 at (19), (20), and (21), this is evident from accessing the hidden field billType
: the field accessed is declared in the class corresponding to the reference type, regardless of the object denoted by the reference.
In contrast to method overriding, where an instance method cannot override a static method, there are no such restrictions on the hiding of fields. The field billType
is static
in the subclass, but not in the superclass. The type of the fields need not be the same either, it is only the field name that matters in the hiding of fields.
A static method cannot override an inherited instance method, but it can hide a static method if the exact requirements for overriding instance methods are fulfilled (see Section 7.2, p. 288). A hidden superclass static method is not inherited. The compiler will flag an error if the signatures are the same, but the other requirements regarding return type, throws
clause, and accessibility are not met. If the signatures are different, the method name is overloaded, not hidden.
A call to a static
or final
method is bound to a method implementation at compile time (private
methods are implicitly final
). Example 7.2 illustrates invocation of static methods. Analogous to accessing fields, the method invoked in (22), (23), and (24) is determined by the class of the reference. In (22) the class type is TubeLight
, therefore, the static method printBillType()
at (9) in this class is invoked. In (23) and (24), the class type is Light
and the hidden static method printBillType()
at (4) in that class is invoked. This is borne out by the output from the program.
A hidden static method can always be invoked by using the superclass name in the subclass declaration. Additionally, the keyword super
can be used in non-static code in the subclass declaration to invoke hidden static methods.
The this
reference is available in non-static code and refers to the current object. When an instance method is invoked, the this
reference denotes the object on which the method is called (see Section 3.3, p. 45). The keyword super
can also be used in non-static code (e.g., in the body of an instance method), but only in a subclass, to access fields and invoke methods from the superclass (see Table 4.1, p.130). The keyword super
provides a reference to the current object as an instance of its superclass. In method invocations with super
, the method from the superclass is invoked regardless of the actual type of the object or whether the current class overrides the method. It is typically used to invoke methods that are overridden and to access members that are hidden in the subclass. Unlike the this
keyword, the super
keyword cannot be used as an ordinary reference. For example, it cannot be assigned to other references or cast to other reference types.
In Example 7.3, the declaration of the method demonstrate()
at (9) in the class NeonLight
makes use of the super
keyword to access members higher up in its inheritance hierarchy. This is the case when the banner()
method is invoked at (10). This method is defined at (4) in the class Light
and not in the immediate superclass TubeLight
of the subclass NeonLight
. The overridden method getBill()
and its overloaded version at (6) and (8) in the class TubeLight
are invoked, using super
at (11) and (12), respectively.
The class NeonLight
is a subclass of the class TubeLight
, which is a subclass of the class Light
, which has a field named billType
and a method named getBill
defined at (1) and (2), respectively. One might be tempted to use the syntax super.super.getBill(20)
in the subclass NeonLight
to invoke this method, but this is not a valid construct. One might also be tempted to cast the this
reference to the class Light
and try again as shown at (13). The output shows that the method getBill()
at (6) in the class TubeLight
was executed, not the one from the class Light
. The reason is that a cast only changes the type of the reference (in this case to Light
), not the class of the object (which is still NeonLight
). Method invocation is determined by the class of the current object, resulting in the inherited method getBill()
in the class TubeLight
being executed. There is no way to invoke the method getBill()
in the class Light
from the subclass NeonLight
.
At (14) the keyword super
is used to access the field billType
at (5) in the class TubeLight
. At (15) the field billType
from the class Light
is accessed successfully by casting the this
reference, because it is the type of the reference that determines which field is accessed. From non-static code in a subclass, it is possible to directly access fields in a class higher up the inheritance hierarchy by casting the this
reference. However, it is futile to cast the this
reference to invoke instance methods in a class higher up the inheritance hierarchy, as illustrated above in the case of the overridden method getBill()
.
Finally, the calls to the static methods at (16) and (17) using the super
and this
references, exhibit runtime behavior analogous to accessing fields, as discussed earlier.
Example 7.3 Using the super
Keyword
//Exceptions
class InvalidHoursException extends Exception {}
class NegativeHoursException extends InvalidHoursException {}
class ZeroHoursException extends InvalidHoursException {}
class Light {
protected String billType = "Small bill"; // (1)
protected double getBill(int noOfHours)
throws InvalidHoursException { // (2)
if (noOfHours < 0)
throw new NegativeHoursException();
double smallAmount = 10.0, smallBill = smallAmount * noOfHours;
System.out.println(billType + ": " + smallBill);
return smallBill;
}
public static void printBillType() { // (3)
System.out.println("Small bill");
}
public void banner() { // (4)
System.out.println("Let there be light!");
}
}
//____________________________________________________
class TubeLight extends Light {
public static String billType = "Large bill"; // (5) Hiding static field at (1).
@Override
public double getBill(final int noOfHours)
throws ZeroHoursException { // (6) Overriding instance method at (2).
if (noOfHours == 0)
throw new ZeroHoursException();
double largeAmount = 100.0, largeBill = largeAmount * noOfHours;
System.out.println(billType + ": " + largeBill);
return largeBill;
}
public static void printBillType() { // (7) Hiding static method at (3).
System.out.println(billType);
}
public double getBill() { // (8) Overloading method at (6).
System.out.println("No bill");
return 0.0;
}
}
//____________________________________________________
class NeonLight extends TubeLight {
// ...
public void demonstrate() throws InvalidHoursException { // (9)
super.banner(); // (10) Invokes method at (4)
super.getBill(20); // (11) Invokes method at (6)
super.getBill(); // (12) Invokes method at (8)
((Light) this).getBill(20); // (13) Invokes method at (6)
System.out.println(super.billType); // (14) Accesses field at (5)
System.out.println(((Light) this).billType); // (15) Accesses field at (1)
super.printBillType(); // (16) Invokes method at (7)
((Light) this).printBillType(); // (17) Invokes method at (3)
}
}
//____________________________________________________
public class Client {
public static void main(String[] args)
throws InvalidHoursException {
NeonLight neonRef = new NeonLight();
neonRef.demonstrate();
}
}
Output from the program:
Let there be light!
No bill
Large bill: 2000.0
Large bill: 2000.0
Large bill
Small bill
Large bill
Small bill
7.1 Which statements are true?
Select the two correct answers.
(a) In Java, the extends
clause is used to specify the inheritance relationship.
(b) The subclass of a non-abstract
class can be declared abstract
.
(c) All members of the superclass are inherited by the subclass.
(d) A final
class can be abstract
.
(e) A class in which all the members are declared private
, cannot be declared public
.
7.2 Which statements are true?
Select the two correct answers.
(a) A class can only be extended by one class.
(b) Every Java object has a public
method named equals
.
(c) Every Java object has a public
method named length
.
(d) A class can extend any number of classes.
(e) A non-final
class can be extended by any number of classes.
7.3 Which statements are true?
Select the two correct answers.
(a) A subclass must define all the methods from the superclass.
(b) It is possible for a subclass to define a method with the same name and parameters as a method defined by the superclass.
(c) It is possible for a subclass to define a field with the same name as a field defined by the superclass.
(d) It is possible for two classes to be the superclass of each other.
7.4 Given the following classes and declarations, which statements are true?
// Classes
class Foo {
private int i;
public void f() { /* ... */ }
public void g() { /* ... */ }
}
class Bar extends Foo {
public int j;
public void g() { /* ... */ }
}
// Declarations:
Foo a = new Foo();
Bar b = new Bar();
Select the three correct answers.
(a) The Bar
class is a subclass of Foo
.
(b) The statement b.f();
is legal.
(c) The statement a.j = 5;
is legal.
(d) The statement a.g();
is legal.
(e) The statement b.i = 3;
is legal.
7.5 Which statement is true?
Select the one correct answer.
(a) Private methods cannot be overridden in subclasses.
(b) A subclass can override any method in a superclass.
(c) An overriding method can declare that it throws checked exceptions that are not thrown by the method it is overriding.
(d) The parameter list of an overriding method can be a subset of the parameter list of the method that it is overriding.
(e) The overriding method must have the same return type as the overridden method.
7.6 Given classes A
, B
, and C
, where B
extends A
, and C
extends B
, and where all classes implement the instance method void doIt()
. How can the doIt()
method in A
be called from an instance method in C
?
Select the one correct answer.
(a) doIt();
(b) super.doIt();
(c) super.super.doIt();
(d) this.super.doIt();
(e) A.this.doIt();
(f) ((A) this).doIt();
(g) It is not possible.
7.7 What would be the result of compiling and running the following program?
// Filename: MyClass.java
public class MyClass {
public static void main(String[] args) {
C c = new C();
System.out.println(c.max(13, 29));
}
}
class A {
int max(int x, int y) { if (x>y) return x; else return y; }
}
class B extends A{
int max(int x, int y) { return super.max(y, x) - 10; }
}
class C extends B {
int max(int x, int y) { return super.max(x+10, y+10); }
}
Select the one correct answer.
(a) The code will fail to compile because the max()
method in B
passes the arguments in the call super.max(y, x)
in the wrong order.
(b) The code will fail to compile because a call to a max()
method is ambiguous.
(c) The code will compile and print 13
, when run.
(d) The code will compile and print 23
, when run.
(e) The code will compile and print 29
, when run.
(f) The code will compile and print 39
, when run.
7.8 Which is the simplest expression that can be inserted at (1), so that the program prints the value of the text
field from the Message
class?
// Filename: MyClass.java
class Message {
// The message that should be printed:
String text = "Hello, world!";
}
class MySuperclass {
Message msg = new Message();
}
public class MyClass extends MySuperclass {
public static void main(String[] args) {
MyClass object = new MyClass();
object.print();
}
public void print() {
System.out.println( /* (1) INSERT THE SIMPLEST EXPRESSION HERE */ );
}
}
Select the one correct answer.
(a) text
(b) Message.text
(c) msg.text
(d) object.msg.text
(e) super.msg.text
(f) object.super.msg.text
7.9 Which method declarations, when inserted at (7), will not result in a compile-time error?
class MySuperclass {
public Integer step1(int i) { return 1; } // (1)
protected String step2(String str1, String str2) { return str1; } // (2)
public String step2(String str1) { return str1; } // (3)
public static String step2() { return "Hi"; } // (4)
public MyClass makeIt() { return new MyClass(); } // (5)
public MySuperclass makeIt2() { return new MyClass(); } // (6)
}
public class MyClass extends MySuperclass {
// (7) INSERT METHOD DECLARATION HERE
}
Select the two correct answers.
(a) public int step1(int i) { return 1; }
(b) public String step2(String str2, String str1) { return str1; }
(c) private void step2() { }
(d) private static void step2() { }
(e) private static String step2(String str) { return str; }
(f) public MySuperclass makeIt() { return new MySuperclass(); }
(g) public MyClass makeIt2() { return new MyClass(); }
7.10 What would be the result of compiling and running the following program?
class Vehicle {
static public String getModelName() { return "Volvo"; }
public long getRegNo() { return 12345; }
}
class Car extends Vehicle {
static public String getModelName() { return "Toyota"; }
public long getRegNo() { return 54321; }
}
public class TakeARide {
public static void main(String args[]) {
Car c = new Car();
Vehicle v = c;
System.out.println("|" + v.getModelName() + "|" + c.getModelName() +
"|" + v.getRegNo() + "|" + c.getRegNo() + "|");
}
}
Select the one correct answer.
(a) The code will fail to compile.
(b) The code will compile and print |Toyota|Volvo|12345|54321|
, when run.
(c) The code will compile and print |Volvo|Toyota|12345|54321|
, when run.
(d) The code will compile and print |Toyota|Toyota|12345|12345|
, when run.
(e) The code will compile and print |Volvo|Volvo|12345|54321|
, when run.
(f) The code will compile and print |Toyota|Toyota|12345|12345|
, when run.
(g) The code will compile and print |Volvo|Toyota|54321|54321|
, when run.
7.11 What would be the result of compiling and running the following program?
final class Item {
Integer size;
Item(Integer size) { this.size = size; }
public boolean equals(Item item2) {
if (this == item2) return true;
return this.size.equals(item2.size);
}
}
public class SkepticRide {
public static void main(String[] args) {
Item itemA = new Item(10);
Item itemB = new Item(10);
Object itemC = itemA;
System.out.println("|" + itemA.equals(itemB) +
"|" + itemC.equals(itemB) + "|");
}
}
Select the one correct answer.
(a) The code will fail to compile.
(b) The code will compile and print |false|false|
, when run.
(c) The code will compile and print |false|true|
, when run.
(d) The code will compile and print |true|false|
, when run.
(e) The code will compile and print |true|true|
, when run.
Constructors are discussed in Section 3.4, p. 48. Other uses of the keywords this
and super
can be found in Section 7.2, p. 288, and Section 8.3, p. 360.
Constructors cannot be inherited or overridden. They can be overloaded, but only in the same class. Since a constructor always has the same name as the class, each parameter list must be different when defining more than one constructor for a class. In Example 7.4, the class Light
has three overloaded constructors. In the nondefault constructor at (3), the this
reference is used to access the fields shadowed by the parameters. In the main()
method at (4), the appropriate constructor is invoked depending on the arguments in the constructor call, as illustrated by the program output.
Example 7.4 Constructor Overloading
class Light {
// Fields:
private int noOfWatts; // wattage
private boolean indicator; // on or off
private String location; // placement
// Constructors:
Light() { // (1) Explicit default constructor
noOfWatts = 0;
indicator = false;
location = "X";
System.out.println("Returning from default constructor no. 1.");
}
Light(int watts, boolean onOffState) { // (2) Non-default
noOfWatts = watts;
indicator = onOffState;
location = "X";
System.out.println("Returning from non-default constructor no. 2.");
}
Light(int noOfWatts, boolean indicator, String location) { // (3) Non-default
this.noOfWatts = noOfWatts;
this.indicator = indicator;
this.location = location;
System.out.println("Returning from non-default constructor no. 3.");
}
}
//____________________________________________________
public class DemoConstructorCall {
public static void main(String[] args) { // (4)
System.out.println("Creating Light object no. 1.");
Light light1 = new Light();
System.out.println("Creating Light object no. 2.");
Light light2 = new Light(250, true);
System.out.println("Creating Light object no. 3.");
Light light3 = new Light(250, true, "attic");
}
}
Output from the program:
Creating Light object no. 1.
Returning from default constructor no. 1.
Creating Light object no. 2.
Returning from non-default constructor no. 2.
Creating Light object no. 3.
Returning from non-default constructor no. 3.
Example 7.5 illustrates the use of the this()
construct, which is used to implement local chaining of constructors in the class when an instance of the class is created. The first two constructors at (1) and (2) from Example 7.4 have been rewritten using the this()
construct in Example 7.5 at (1) and (2), respectively. The this()
construct can be regarded as being locally overloaded, since its parameters (and hence its signature) can vary, as shown in the body of the constructors at (1) and (2). The this()
call invokes the local constructor with the corresponding parameter list. In the main()
method at (4), the appropriate constructor is invoked depending on the arguments in the constructor call when each of the three Light
objects are created. Calling the default constructor to create a Light
object results in the second and third constructors being executed as well. This is confirmed by the output from the program. In this case, the output shows that the third constructor completed first, followed by the second, and finally the default constructor that was called first. Bearing in mind the definition of the constructors, the constructors are invoked in the reverse order; i.e., invocation of the default constructor immediately leads to invocation of the second constructor by the call this(0, false)
, and its invocation leads to the third constructor being called immediately by the call this(watt, ind, "X")
, with the completion of the execution in the reverse order of their invocation. Similarly, calling the second constructor to create an instance of the Light
class results in the third constructor being executed as well.
Java requires that any this()
call must occur as the first statement in a constructor. The this()
call can be followed by any other relevant code. This restriction is due to Java’s handling of constructor invocation in the superclass when an object of the subclass is created. This mechanism is explained in the next subsection.
Example 7.5 The this()
Constructor Call
class Light {
// Fields:
private int noOfWatts;
private boolean indicator;
private String location;
// Constructors:
Light() { // (1) Explicit default constructor
this(0, false);
System.out.println("Returning from default constructor no. 1.");
}
Light(int watt, boolean ind) { // (2) Non-default
this(watt, ind, "X");
System.out.println("Returning from non-default constructor no. 2.");
}
Light(int noOfWatts, boolean indicator, String location) { // (3) Non-default
this.noOfWatts = noOfWatts;
this.indicator = indicator;
this.location = location;
System.out.println("Returning from non-default constructor no. 3.");
}
}
//____________________________________________________
public class DemoThisCall {
public static void main(String[] args) { // (4)
System.out.println("Creating Light object no. 1.");
Light light1 = new Light(); // (5)
System.out.println("Creating Light object no. 2.");
Light light2 = new Light(250, true); // (6)
System.out.println("Creating Light object no. 3.");
Light light3 = new Light(250, true, "attic"); // (7)
}
}
Output from the program:
Creating Light object no. 1.
Returning from non-default constructor no. 3.
Returning from non-default constructor no. 2.
Returning from default constructor no. 1.
Creating Light object no. 2.
Returning from non-default constructor no. 3.
Returning from non-default constructor no. 2.
Creating Light object no. 3.
Returning from non-default constructor no. 3.
The super()
construct is used in a subclass constructor to invoke a constructor in the immediate superclass. This allows the subclass to influence the initialization of its inherited state when an object of the subclass is created. A super()
call in the constructor of a subclass will result in the execution of the relevant constructor from the superclass, based on the signature of the call. Since the superclass name is known in the subclass declaration, the compiler can determine the superclass constructor invoked from the signature of the parameter list.
A constructor in a subclass can access the class’s inherited members by their simple names. The keyword super
can also be used in a subclass constructor to access inherited members via its superclass. One might be tempted to use the super
keyword in a constructor to specify initial values of inherited fields. However, the super()
construct provides a better solution to initialize the inherited state.
In Example 7.6, the non-default constructor at (3) of the class Light
has a super()
call (with no arguments) at (4). Although the constructor is not strictly necessary, as the compiler will insert one—as explained below—it is included for expositional purposes. The non-default constructor at (6) of the class TubeLight
has a super()
call (with three arguments) at (7). This super()
call will match the non-default constructor at (3) of the superclass Light
. This is evident from the program output.
Example 7.6 The super()
Constructor Call
class Light {
// Fields:
private int noOfWatts;
private boolean indicator;
private String location;
// Constructors:
Light() { // (1) Explicit default constructor
this(0, false);
System.out.println(
"Returning from default constructor no. 1 in class Light");
}
Light(int watt, boolean ind) { // (2) Non-default
this(watt, ind, "X");
System.out.println(
"Returning from non-default constructor no. 2 in class Light");
}
Light(int noOfWatts, boolean indicator, String location) { // (3) Non-default
super(); // (4)
this.noOfWatts = noOfWatts;
this.indicator = indicator;
this.location = location;
System.out.println(
"Returning from non-default constructor no. 3 in class Light");
}
}
//____________________________________________________
class TubeLight extends Light {
// Instance variables:
private int tubeLength;
private int colorNo;
// Constructors:
TubeLight(int tubeLength, int colorNo) { // (5) Non-default
this(tubeLength, colorNo, 100, true, "Unknown");
System.out.println(
"Returning from non-default constructor no. 1 in class TubeLight");
}
TubeLight(int tubeLength, int colorNo, int noOfWatts,
boolean indicator, String location) { // (6) Non-default
super(noOfWatts, indicator, location); // (7)
this.tubeLength = tubeLength;
this.colorNo = colorNo;
System.out.println(
"Returning from non-default constructor no. 2 in class TubeLight");
}
}
//____________________________________________________
public class Chaining {
public static void main(String[] args) {
System.out.println("Creating a TubeLight object.");
TubeLight tubeLightRef = new TubeLight(20, 5); // (8)
}
}
Output from the program:
Creating a TubeLight object.
Returning from non-default constructor no. 3 in class Light
Returning from non-default constructor no. 2 in class TubeLight
Returning from non-default constructor no. 1 in class TubeLight
The super()
construct has the same restrictions as the this()
construct: if used, the super()
call must occur as the first statement in a constructor, and it can only be used in a constructor declaration. This implies that this()
and super()
calls cannot both occur in the same constructor. The this()
construct is used to chain constructors in the same class. The constructor at the end of such a chain can invoke a superclass constructor using the super()
construct. Just as the this()
construct leads to chaining of constructors in the same class, the super()
construct leads to chaining of subclass constructors to superclass constructors. This chaining behavior guarantees that all superclass constructors are called, starting with the constructor of the class being instantiated, all the way to the top of the inheritance hierarchy, which is always the Object
class. Note that the body of the constructor is executed in the reverse order to the call order, as super()
can only occur as the first statement in a constructor. This ensures that the constructor from the Object
class is completed first, followed by the constructors in the other classes down to the class being instantiated in the inheritance hierarchy. This is called (subclass–superclass) constructor chaining. The output from Example 7.6 clearly illustrates this chain of events when an object of the class TubeLight
is created.
If a constructor at the end of a this()
-chain (which may not be a chain at all if no this()
call is invoked) does not have an explicit call to super()
, the call super()
(without the parameters) is implicitly inserted by the compiler to invoke the default constructor of the superclass. In other words, if a constructor has neither a this()
nor a super()
call as its first statement, the compiler inserts a super()
call to the default constructor in the superclass. The code
class A {
public A() {}
// ...
}
class B extends A {
// no constructors
// ...
}
is equivalent to
class A {
public A() { super(); } // (1)
// ...
}
class B extends A {
public B() { super(); } // (2)
// ... }
where the default constructors with calls to the default superclass constructor are inserted in the code.
If a superclass only defines non-default constructors (i.e., only constructors with parameters), its subclasses cannot rely on the implicit super()
call being inserted. This will be flagged as a compile-time error. The subclasses must then explicitly call a superclass constructor, using the super()
construct with the right arguments.
class NeonLight extends TubeLight {
// Field
String sign;
NeonLight() { // (1)
super(10, 2, 100, true, "Roof-top"); // (2) Cannot be commented out.
sign = "All will be revealed!";
}
// ...
}
The above declaration of the subclass NeonLight
provides a constructor at (1). The call of the constructor at (2) in the superclass TubeLight
cannot be omitted. If it is omitted, any insertion of a super()
call (with no arguments) in this constructor will try to match a default constructor in the superclass TubeLight
, which only provides non-default constructors. The class NeonLight
will not compile unless an explicit valid super()
call is inserted at (2).
If the superclass provides only non-default constructors (that is, does not have a default constructor), this has implications for its subclasses. A subclass that relies on its own implicit default constructor will fail to compile. This is because the implicit default constructor of the subclass will attempt to call the (non-existent) default constructor in the superclass. A constructor in a subclass must explicitly use the super()
call, with the appropriate arguments, to invoke a non-default constructor in the superclass. This is because the constructor in the subclass cannot rely on an implicit super()
call to the default constructor in the superclass.
7.12 Which constructors can be inserted at (1) in MySub
without causing a compile-time error?
class MySuper {
int number;
MySuper(int i) { number = i; }
}
class MySub extends MySuper {
int count;
MySub(int count, int num) {
super(num);
this.count = count;
}
// (1) INSERT CONSTRUCTOR HERE
}
Select the one correct answer.
(a) MySub() {}
(b) MySub(int count) { this.count = count; }
(c) MySub(int count) { super(); this.count = count; }
(d) MySub(int count) { this.count = count; super(count); }
(e) MySub(int count) { this(count, count); }
(f) MySub(int count) { super(count); this(count, 0); }
7.13 Which statement is true?
Select the one correct answer.
(a) A super()
or this()
call must always be provided explicitly as the first statement in the body of a constructor.
(b) If both a subclass and its superclass do not have any declared constructors, the implicit default constructor of the subclass will call super()
when run.
(c) If neither super()
nor this()
is declared as the first statement in the body of a constructor, this()
will implicitly be inserted as the first statement.
(d) If super()
is the first statement in the body of a constructor, this()
can be declared as the second statement.
(e) Calling super()
as the first statement in the body of a constructor of a subclass will always work, since all superclasses have a default constructor.
7.14 What will the following program print when run?
// Filename: MyClass.java
public class MyClass {
public static void main(String[] args) {
B b = new B("Test");
}
}
class A {
A() { this("1", "2"); }
A(String s, String t) { this(s + t); }
A(String s) { System.out.println(s); }
}
class B extends A {
B(String s) { System.out.println(s); }
B(String s, String t) { this(t + s + "3"); }
B() { super("4"); };
}
Select the one correct answer.
(a) It will just print Test
.
(b) It will print Test
followed by Test
.
(c) It will print 123
followed by Test
.
(d) It will print 12
followed by Test
.
(e) It will print 4
followed by Test
.
Extending classes using single implementation inheritance creates new class types. A superclass reference can refer to objects of its own type and its subclasses strictly according to the inheritance hierarchy. Because this relationship is linear, it rules out multiple implementation inheritance, i.e., a subclass inheriting from more than one superclass. Instead Java provides interfaces, which not only allow new named reference types to be introduced, but also permit multiple interface inheritance.
A top-level interface has the following general syntax:
<accessibility modifier> interface <interface name>
<extends interface clause> // Interface header
{ // Interface body
<constant declarations>
<abstract method declarations>
<nested class declarations>
<nested interface declarations>
}
In the interface header, the name of the interface is preceded by the keyword interface
. The interface name can also include a list of formal type parameters (see Section 14.2, p. 666). In addition, the interface header can specify the following information:
The interface body can contain member declarations which comprise:
• constant declarations (see Section 7.6, p. 314)
• abstract method declarations (see Section 7.6, p. 313)
• nested class and interface declarations (see Section 8.1, p. 352)
An interface does not provide any implementation and is, therefore, abstract
by definition. This means that it cannot be instantiated. Declaring an interface abstract
is superfluous and seldom done.
The member declarations can appear in any order in the interface body. Since interfaces are meant to be implemented by classes, interface members implicitly have public
accessibility and the public
modifier can be omitted.
Interfaces with empty bodies can be used as markers to tag classes as having a certain property or behavior. Such interfaces are also called ability interfaces. Java APIs provide several examples of such marker interfaces: java.lang.Cloneable
, java.io.Serializable
, java.util.EventListener
.
An interface defines a contract by specifying a set of abstract method declarations, but provides no implementations (see Section 4.10, p. 150). The methods in an interface are all implicitly abstract
and public
by virtue of their definition. Only the modifiers abstract
and public
are allowed, but these are invariably omitted. An abstract method declaration has the following form:
<optional type parameter list> <return type> <method name> (<parameter list>)
<throws clause>
The optional list of formal type parameters is specified for generic method declarations (see Section 14.8, p. 697).
Example 7.7 declares two interfaces: IStack
at (1) and ISafeStack
at (5). These interfaces are discussed in the subsequent subsections.
Example 7.7 Interfaces
interface IStack { // (1)
void push(Object item);
Object pop();
}
//____________________________________________________
class StackImpl implements IStack { // (2)
protected Object[] stackArray;
protected int tos; // top of stack
public StackImpl(int capacity) {
stackArray = new Object[capacity];
tos = -1;
}
public void push(Object item) { stackArray[++tos] = item; } // (3)
public Object pop() { // (4)
Object objRef = stackArray[tos];
stackArray[tos] = null;
tos--;
return objRef;
}
public Object peek() { return stackArray[tos]; }
}
//____________________________________________________
interface ISafeStack extends IStack { // (5)
boolean isEmpty();
boolean isFull();
}
//____________________________________________________
class SafeStackImpl extends StackImpl implements ISafeStack { // (6)
public SafeStackImpl(int capacity) { super(capacity); }
public boolean isEmpty() { return tos < 0; } // (7)
public boolean isFull() { return tos >= stackArray.length-1; } // (8)
}
//____________________________________________________
public class StackUser {
public static void main(String[] args) { // (9)
SafeStackImpl safeStackRef = new SafeStackImpl(10);
StackImpl stackRef = safeStackRef;
ISafeStack isafeStackRef = safeStackRef;
IStack istackRef = safeStackRef;
Object objRef = safeStackRef;
safeStackRef.push("Dollars"); // (10)
stackRef.push("Kroner");
System.out.println(isafeStackRef.pop());
System.out.println(istackRef.pop());
System.out.println(objRef.getClass());
}
}
Output from the program:
Kroner
Dollars
class SafeStackImpl
Any class can elect to implement, wholly or partially, zero or more interfaces. A class specifies the interfaces it implements as a comma-separated list of unique interface names in an implements
clause in the class header. The interface methods must all have public
accessibility when implemented in the class (or its subclasses). A class can neither narrow the accessibility of an interface method nor specify new exceptions in the method’s throws
clause, as attempting to do so would amount to altering the interface’s contract, which is illegal. The criteria for overriding methods also apply when implementing interface methods (see Section 7.2, p. 288).
A class can provide implementations of methods declared in an interface, but to reap the benefits of interfaces, the class must also specify the interface name in its implements
clause.
In Example 7.7, the class StackImpl
implements the interface IStack
. It both specifies the interface name using the implements
clause in its class header at (2) and provides the implementation for the methods in the interface at (3) and (4). Changing the public
accessibility of these methods in the class will result in a compile-time error, as this would narrow their accessibility.
A class can choose to implement only some of the methods of its interfaces (i.e., give a partial implementation of its interfaces). The class must then be declared as abstract
(see Section 4.8, p. 135). Note that interface methods cannot be declared static
, because they comprise the contract fulfilled by the objects of the class implementing the interface. Interface methods are always implemented as instance methods.
The interfaces a class implements and the classes it extends (directly or indirectly) are called supertypes of the class. Conversely, the class is a subtype of its supertypes. Classes implementing interfaces introduce multiple interface inheritance into their implementation inheritance hierarchy. However, note that regardless of how many interfaces a class implements directly or indirectly, it only provides a single implementation of a member that might have been declared in multiple interfaces.
An interface can extend other interfaces, using the extends
clause. Unlike extending classes, an interface can extend several interfaces. The interfaces extended by an interface (directly or indirectly) are called superinterfaces. Conversely, the interface is a subinterface of its superinterfaces. Since interfaces define new reference types, superinterfaces and subinterfaces are also supertypes and subtypes, respectively.
A subinterface inherits all methods from its superinterfaces, as their method declarations are all implicitly public
. A subinterface can override abstract method declarations from its superinterfaces. Overridden methods are not inherited. Abstract method declarations can also be overloaded, analogous to method overloading in classes.
Example 7.7 provides an example of multiple interface inheritance. In Example 7.7, the interface ISafeStack
extends the interface IStack
at (5). The class SafeStackImpl
both extends the StackImpl
class and implements the ISafeStack
interface at (6). Both the implementation and the interface inheritance hierarchies for classes and interfaces defined in Example 7.7 are shown in Figure 7.2.
In UML, an interface resembles a class. One way to differentiate between them is to use an “interface” stereotype as in Figure 7.2. Interface inheritance is depicted in a similar manner to implementation inheritance, but uses an unbroken inheritance arrow. Thinking in terms of types, every reference type in Java is a subtype of the Object
type. This means that any interface type is also a subtype of the Object
type. We have augmented Figure 7.2 with an extra inheritance arrow to show this subtype relation.
It is instructive to note how the class SafeStackImpl
implements the ISafeStack
interface: it inherits implementations of the push()
and pop()
methods from its superclass StackImpl
, and provides its own implementation of the isFull()
and isEmpty()
methods from the ISafeStack
interface. The interface ISafeStack
inherits two abstract method declarations from its superinterface IStack
. All its methods are implemented by the SafeStackImpl
class. The class SafeStackImpl
implicitly implements the IStack
interface: it implements the ISafeStack
interface that it inherits from the IStack
interface. This is readily evident from the diamond shape of the inheritance hierarchy in Figure 7.2. There is only one single implementation inheritance into the class SafeStackImpl
, namely from its superclass StackImpl
.
Note that there are three different inheritance relations at work when defining inheritance among classes and interfaces:
1. Single implementation inheritance hierarchy between classes: a class extends another class (subclasses–superclasses).
2. Multiple inheritance hierarchy between interfaces: an interface extends other interfaces (subinterfaces–superinterfaces).
3. Multiple interface inheritance hierarchy between interfaces and classes: a class implements interfaces.
Although interfaces cannot be instantiated, references of an interface type can be declared. The reference value of an object can be assigned to references of the object’s supertypes. In Example 7.7, an object of the class SafeStackImpl
is created in the main()
method of the class StackUser
at (9). The reference value of the object is assigned to references of all the object’s supertypes, which are used to manipulate the object. Polymorphic behavior of supertype references is discussed in Section 7.12, p. 340.
An interface can also define named constants. Such constants are defined by field declarations and are considered to be public
, static
, and final
. These modifiers can be omitted from the declaration. Such a constant must be initialized with an initializer expression (see Section 9.8, p. 406).
An interface constant can be accessed by any client (a class or interface) using its fully qualified name, regardless of whether the client extends or implements its interface. However, if a client is a class that implements this interface or an interface that extends this interface, then the client can also access such constants directly by their simple names, without resorting to the fully qualified name. Such a client inherits the interface constants. Typical usage of constants in interfaces is illustrated in Example 7.8, showing both direct access and use of fully qualified names in the print statements at (1) and (2), respectively.
Extending an interface that has constants is analogous to extending a class having static variables. In particular, these constants can be hidden by the subinterfaces. In the case of multiple inheritance of interface constants, any name conflicts can be resolved by using fully qualified names for the constants involved.
When defining a set of related constants, the recommended practice is to use an enumerated type (Section 3.5, p. 54), rather than named constants in an interface.
Example 7.8 Variables in Interfaces
interface Constants {
double PI_APPROXIMATION = 3.14;
String AREA_UNITS = "sq.cm.";
String LENGTH_UNITS = "cm.";
}
//____________________________________________________
public class Client implements Constants {
public static void main(String[] args) {
double radius = 1.5;
// (1) Using direct access:
System.out.printf("Area of circle is %.2f %s%n",
PI_APPROXIMATION * radius*radius, AREA_UNITS);
// (2) Using fully qualified name:
System.out.printf("Circumference of circle is %.2f %s%n",
2.0 * Constants.PI_APPROXIMATION * radius, Constants.LENGTH_UNITS);
}
}
Output from the program:
Area of circle is 7.06 sq.cm.
Circumference of circle is 9.42 cm.
7.15 Which statements about interfaces are true?
Select the two correct answers.
(a) Interfaces allow multiple implementation inheritance.
(b) Interfaces can be extended by any number of interfaces.
(c) Interfaces can extend any number of interfaces.
(d) Members of an interface are never static.
(e) Members of an interface can always be declared static
.
7.16 Which of these field declarations are legal within the body of an interface?
Select the three correct answers.
(a) public static int answer = 42;
(b) int answer;
(c) final static int answer = 42;
(d) public int answer = 42;
(e) private final static int answer = 42;
7.17 Which statements about the keywords extends
and implements
are true?
Select the two correct answers.
(a) The keyword extends
is used to specify that an interface inherits from another interface.
(b) The keyword extends
is used to specify that a class implements an interface.
(c) The keyword implements
is used to specify that an interface inherits from another interface.
(d) The keyword implements
is used to specify that a class inherits from an interface.
(e) The keyword implements
is used to specify that a class inherits from another class.
7.18 Which statement is true about the following code?
// Filename: MyClass.java
abstract class MyClass implements Interface1, Interface2 {
public void f() { }
public void g() { }
}
interface Interface1 {
int VAL_A = 1;
int VAL_B = 2;
void f();
void g();
}
interface Interface2 {
int VAL_B = 3;
int VAL_C = 4;
void g();
void h();
}
Select the one correct answer.
(a) MyClass
only implements Interface1
. Implementation for void h()
from Interface2
is missing.
(b) The declarations of void g()
in the two interfaces conflict, therefore, the code will not compile.
(c) The declarations of int VAL_B
in the two interfaces conflict, therefore, the code will not compile.
(d) Nothing is wrong with the code, it will compile without errors.
7.19 Which declaration can be inserted at (1) without causing a compilation error?
interface MyConstants {
int r = 42;
int s = 69;
// (1) INSERT CODE HERE
}
Select the two correct answers.
(a) final double circumference = 2 * Math.PI * r;
(b) int total = total + r + s;
(c) int AREA = r * s;
(d) public static MAIN = 15;
(e) protected int CODE = 31337;
Table 7.2 summarizes the types found in Java. Only primitive data and reference values can be stored in variables. Only class and array types can be explicitly instantiated to create objects.
Arrays are objects in Java. Array types (boolean[]
, Object[]
, StackImpl[]
) implicitly augment the inheritance hierarchy. The inheritance hierarchy depicted in Figure 7.2 can be augmented by the corresponding array types. The resulting type hierarchy is shown in Figure 7.3. An array type is shown as a “class” with the []
notation appended to the name of the element type. The class SafeStackImpl
is a subclass of the class StackImpl
. The corresponding array types, SafeStackImpl[]
and StackImpl[]
, are shown as subtype and supertype, respectively, in the type hierarchy. Figure 7.3 also shows array types corresponding to some of the primitive data types.
From the type hierarchy in Figure 7.3, we can summarize the following:
• All reference types are subtypes of the Object
type. This applies to classes, interfaces, enum, and array types, as these are all reference types.
• All arrays of reference types are also subtypes of the array type Object[]
, but arrays of primitive data types are not. Note that the array type Object[]
is also a subtype of the Object
type.
• If a non-generic reference type is a subtype of another non-generic reference type, the corresponding array types also have an analogous subtype-supertype relationship. This is called the subtype covariance relationship. This relationship however does not hold for parameterized types (see Section 14.4, p. 673).
• There is no subtype-supertype relationship between a type and its corresponding array type.
We can create an array of an interface type, but we cannot instantiate an interface (as is the case with abstract
classes). In the declaration statement below, the reference iSafeStackArray
has type ISafeStack[]
(i.e., an array of the interface type ISafeStack
).
ISafeStack[] iSafeStackArray = new ISafeStack[5];
The array creation expression creates an array whose element type is ISafeStack
. The array object can accommodate five references of the type ISafeStack
. However, the declaration statement does not initialize these references to refer to any objects, but they are initialized to the default value null
.
An array reference exhibits polymorphic behavior like any other reference, subject to its location in the type hierarchy (see Section 7.12, p. 340). However, a runtime check is necessary when objects are inserted in an array, as the following example illustrates.
The following assignment is valid, as a supertype reference (StackImpl[]
) can refer to objects of its subtype (SafeStackImpl[]
):
StackImpl[] stackImplArray = new SafeStackImpl[2]; // (1)
Since StackImpl
is a supertype of SafeStackImpl
, the following assignment is also valid:
stackImplArray[0] = new SafeStackImpl(10); // (2)
The assignment at (2) inserts a SafeStackImpl
object in the SafeStackImpl[]
object (i.e., the array of SafeStackImpl
) created at (1).
Since the type of stackImplArray[i]
, (0
≤ i < 2
), is StackImpl
, it should be possible to do the following assignment as well:
stackImplArray[1] = new StackImpl(20); // (3) ArrayStoreException
At compile time there are no problems, as the compiler cannot deduce that the array variable stackImplArray
will actually denote a SafeStackImpl[]
object at runtime. However, the assignment at (3) results in an ArrayStoreException
to be thrown at runtime, as a SafeStackImpl[]
object cannot possibly contain objects of type StackImpl
.
In order to make the array store check feasible at runtime, an array retains information about its declared element type at runtime.
A review of Section 5.1, p. 160, on conversions is recommended before proceeding with this section.
Reference values, like primitive values, can be assigned, cast, and passed as arguments. Conversions can occur in the following contexts:
• assignment
• method invocation
• casting
The rule of thumb for the primitive data types is that widening conversions are permitted, but narrowing conversions require an explicit cast. The rule of thumb for reference values is that widening conversions up the type hierarchy are permitted, but narrowing conversions down the hierarchy require an explicit cast. In other words, conversions that are from a subtype to its supertypes are allowed, other conversions require an explicit cast or are otherwise illegal. There is no notion of promotion for reference values.
Unchecked conversions involving generic and raw types are discussed in Section 14.2, p. 670.
In the context of assignments, the following conversions are permitted (Table 5.1, p. 163):
• widening primitive and reference conversions (long
← int
, Object
← String
)
• boxing conversion of primitive values, followed by optional widening reference conversion (Integer
← int
, Number
← Integer
← int
)
• unboxing conversion of a primitive value wrapper object, followed by optional widening primitive conversion (long
← int
← Integer
)
And only for assigment conversions, we have the following:
• narrowing conversion for constant expressions of non-long
integer type, with optional boxing (Byte
← byte
← int
)
Note that the above rules imply that a widening conversion cannot be followed by any boxing conversion, but the converse is permitted.
Widening reference conversions typically occur during assignment up the type hierarchy, with implicit conversion of the source reference value to that of the destination reference type:
Object obj = "Up the tree"; // Widening reference conversion: Object <--String
String str1 = obj; // Not ok. Narrowing reference conversion requires a cast.
String str2 = new Integer(10); // Illegal. No relation between String and Integer.
The source value can be a primitive value, in which case the value is boxed in a wrapper object corresponding to the primitive type. If the destination reference type is a supertype of the wrapper type, a widening reference conversion can occur:
Integer iRef = 10; // Only boxing
Number num = 10L; // Boxing, followed by widening: Number <---Long <---long
Object obj = 100; // Boxing, followed by widening: Object <---Integer <---int
More examples of boxing during assignment can be found in Section 5.1, p. 162.
Example 7.9 Assigning and Passing Reference Values
interface IStack { /* From Example 7.7 */ }
interface ISafeStack extends IStack { /* From Example 7.7 */ }
class StackImpl implements IStack { /* From Example 7.7 */ }
class SafeStackImpl extends StackImpl
implements ISafeStack { /* From Example 7.7 */ }
public class ReferenceConversion {
public static void main(String[] args) {
// Reference declarations:
Object objRef;
StackImpl stackRef;
SafeStackImpl safeStackRef;
IStack iStackRef;
ISafeStack iSafeStackRef;
// SourceType is a class type:
safeStackRef = new SafeStackImpl(10);
objRef = safeStackRef; // (1) Always possible
stackRef = safeStackRef; // (2) Subclass to superclass assignment
iStackRef = stackRef; // (3) StackImpl implements IStack
iSafeStackRef = safeStackRef; // (4) SafeStackImpl implements ISafeStack
// SourceType is an interface type:
objRef = iStackRef; // (5) Always possible
iStackRef = iSafeStackRef; // (6) Subto super-interface assignment
// SourceType is an array type:
Object[] objArray = new Object[3];
StackImpl[] stackArray = new StackImpl[3];
SafeStackImpl[] safeStackArray = new SafeStackImpl[5];
ISafeStack[] iSafeStackArray = new ISafeStack[5];
int[] intArray = new int[10];
// Reference value assignments:
objRef = objArray; // (7) Always possible
objRef = stackArray; // (8) Always possible
objArray = stackArray; // (9) Always possible
objArray = iSafeStackArray; // (10) Always possible
objRef = intArray; // (11) Always possible
// objArray = intArray; // (12) Compile-time error
stackArray = safeStackArray; // (13) Subclass array to superclass array
iSafeStackArray = safeStackArray;// (14) SafeStackImpl implements ISafeStack
// Method Invocation Conversions:
System.out.println("First call:");
sendParams(stackRef, safeStackRef, iStackRef,
safeStackArray, iSafeStackArray); // (15)
// Call Signature: sendParams(StackImpl, SafeStackImpl, IStack,
// SafeStackImpl[], ISafeStack[]);
System.out.println("Second call:");
sendParams(iSafeStackArray, stackRef, iSafeStackRef,
stackArray, safeStackArray); // (16)
// Call Signature: sendParams(ISafeStack[], StackImpl, ISafeStack,
// StackImpl[], SafeStackImpl[]);
}
public static void sendParams(Object objRefParam, StackImpl stackRefParam,
IStack iStackRefParam, StackImpl[] stackArrayParam,
final IStack[] iStackArrayParam) { // (17)
// Signature: sendParams(Object, StackImpl, IStack, StackImpl[], IStack[])
// Print class name of object denoted by the reference at runtime.
System.out.println(objRefParam.getClass());
System.out.println(stackRefParam.getClass());
System.out.println(iStackRefParam.getClass());
System.out.println(stackArrayParam.getClass());
System.out.println(iStackArrayParam.getClass());
}
}
Output from the program:
First call:
class SafeStackImpl
class SafeStackImpl
class SafeStackImpl
class [LSafeStackImpl;
class [LSafeStackImpl;
Second call:
class [LSafeStackImpl;
class SafeStackImpl
class SafeStackImpl
class [LSafeStackImpl;
class [LSafeStackImpl;
The rules for reference value assignment are stated, based on the following code:
SourceType srcRef;
// srcRef is appropriately initialized.
DestinationType destRef = srcRef;
If an assignment is legal, the reference value of srcRef
is said to be assignable (or assignment compatible) to the reference of DestinationType
. The rules are illustrated by concrete cases from Example 7.9. Note that the code in Example 7.9 uses reference types from Example 7.7, p. 311.
• If the SourceType
is a class type, the reference value in srcRef
may be assigned to the destRef
reference, provided the DestinationType
is one of the following:
• DestinationType
is a superclass of the subclass SourceType
.
• DestinationType
is an interface type that is implemented by the class SourceType
.
objRef = safeStackRef; // (1) Always possible
stackRef = safeStackRef; // (2) Subclass to superclass assignment
iStackRef = stackRef; // (3) StackImpl implements IStack
iSafeStackRef = safeStackRef; // (4) SafeStackImpl implements ISafeStack
• If the SourceType
is an interface type, the reference value in srcRef
may be assigned to the destRef
reference, provided the DestinationType
is one of the following:
• DestinationType
is Object
.
• DestinationType
is a superinterface of subinterface SourceType
.
objRef = iStackRef; // (5) Always possible
iStackRef = iSafeStackRef; // (6) Subinterface to superinterface assignment
• If the SourceType
is an array type, the reference value in srcRef
may be assigned to the destRef
reference, provided the DestinationType
is one of the following:
• DestinationType
is Object
.
• DestinationType
is an array type, where the element type of the SourceType
is assignable to the element type of the DestinationType
.
objRef = objArray; // (7) Always possible
objRef = stackArray; // (8) Always possible
objArray = stackArray; // (9) Always possible
objArray = iSafeStackArray; // (10) Always possible
objRef = intArray; // (11) Always possible
// objArray = intArray; // (12) Compile-time error
stackArray = safeStackArray; // (13) Subclass array to superclass array
iSafeStackArray = safeStackArray;// (14) SafeStackImpl implements ISafeStack
The rules for assignment are enforced at compile time, guaranteeing that no type conversion error will occur during assignment at runtime. Such conversions are type safe. The reason the rules can be enforced at compile time is that they concern the declared type of the reference (which is always known at compile time) rather than the actual type of the object being referenced (which is known at runtime).
The conversions for reference value assignment are also applicable for method invocation conversions, except for the narrowing conversion for constant expressions of non-long
integer type (Table 5.1, p. 163). This is reasonable, as parameters in Java are passed by value (see Section 3.7, p. 81), requiring that values of actual parameters must be assignable to formal parameters of compatible types.
In Example 7.9, the method sendParams()
at (17) has the following signature, showing the types of the formal parameters:
sendParams(Object, StackImpl, IStack, StackImpl[], IStack[])
The method call at (15) has the following signature, showing the types of the actual parameters:
sendParams(StackImpl, SafeStackImpl, IStack, SafeStackImpl[], ISafeStack[]);
Note that the assignment of the values of the actual parameters to the corresponding formal parameters is legal, according to the rules for assignment discussed earlier.
The method call at (16) provides another example of the parameter passing conversion. It has the following signature:
sendParams(ISafeStack[], StackImpl, ISafeStack, StackImpl[], SafeStackImpl[]);
Analogous to assignment, the rules for parameter passing conversions are based on the reference type of the parameters and are enforced at compile time. The output in Example 7.9 shows the class of the actual objects referenced by the formal parameters at runtime, which in this case turns out to be either SafeStackImpl
or SafeStackImpl[]
. The characters [L
in the output indicate a onedimensional array of a class or interface type (see the Class.getName()
method in the Java API documentation).
In this subsection, we take a look at some aspects regarding overloaded method resolution, i.e., how the compiler determines which overloaded method will be invoked by a given method call at runtime.
Resolution of overloaded methods selects the most specific method for execution. One method is more specific than another method if all actual parameters that can be accepted by the one can be accepted by the other. If there is more than one such method, the call is ambiguous. The following overloaded methods illustrate this situation.
private static void flipFlop(String str, int i, Integer iRef) { // (1)
out.println(str + " ==> (String, int, Integer)");
}
private static void flipFlop(String str, int i, int j) { // (2)
out.println(str + " ==> (String, int, int)");
}
Their method signatures are, as follows:
flipFlop(String, int, Integer) // See (1) above
flipFlop(String, int, int) // See (2) above
The following method call is ambiguous:
flipFlop("(String, Integer, int)", new Integer(4), 2004); // (3) Ambiguous call.
It has the call signature:
flipFlop(String, Integer, int) // See (3) above
The method at (1) can be called with the second argument unboxed and the third argument boxed, as can the method at (2) with only the second argument unboxed. In other words, for the call at (3), none of the methods is more specific than the other one. Example 7.10 illustrates a simple case of how method resolution is done to choose the most specific one of the overloaded methods. The method testIfOn()
is overloaded at (1) and (2) in the class Overload
. The call client.testIfOn(tubeLight)
at (3) satisfies the parameter lists in both implementations given at (1) and (2), as the reference tubeLight
, which denotes an object of the class TubeLight
, can also be assigned to a reference of its superclass Light
. The most specific method, (2), is chosen, resulting in false
being written on the terminal. The call client.testIfOn(light)
at (4) only satisfies the parameter list in the implementation given at (1), resulting in true
being written on the terminal.
Example 7.10 Choosing the Most Specific Method (Simple Case)
class Light { /* ... */ }
class TubeLight extends Light { /* ... */ }
public class Overload {
boolean testIfOn(Light aLight) { return true; } // (1)
boolean testIfOn(TubeLight aTubeLight) { return false; } // (2)
public static void main(String[] args) {
TubeLight tubeLight = new TubeLight();
Light light = new Light();
Overload client = new Overload();
System.out.println(client.testIfOn(tubeLight));// (3) ==> method at (2)
System.out.println(client.testIfOn(light)); // (4) ==> method at (1)
}
}
Output from the program:
false
true
The algorithm used by the compiler for the resolution of overloaded methods incorporates the following phases:
1. It first performs overload resolution without permitting boxing, unboxing, or the use of a varargs call.
2. If phase (1) fails, it performs overload resolution allowing boxing and unboxing, but excluding the use of a varargs call.
3. If phase (2) fails, it performs overload resolution combining a varargs call, boxing, and unboxing.
Example 7.11 provides some insight into how the compiler determines the most specific overloaded method using the phases outlined above. The example has six overloaded declarations of the method action()
. The signature of each method is given by the local variable signature
in each method. The first formal parameter of each method is the signature of the call that invoked the method. The printout from each method thus allows us to see which method call resolved to which method.
The main()
method contains ten calls, (8) to (17), of the action()
method. In each call, the first argument is the signature of that method call.
An important thing to note is that the compiler chooses a non-varargs call over a varargs call, as seen in the calls from (8) to (12).
(String) => (String) (8) calls (1)
(String, int) => (String, int) (9) calls (2)
(String, Integer) => (String, int) (10) calls (2)
(String, int, byte) => (String, int, int) (11) calls (3)
(String, int, int) => (String, int, int) (12) calls (3)
An unboxing conversion (Integer
to int
) takes place for the call at (10). A widening primitive conversion (byte
to int
) takes place for the call at (11).
Varargs calls are chosen from (13) to (17):
(String, int, long) => (String, Number[]) (13) calls (5)
(String, int, int, int) => (String, Integer[]) (14) calls (4)
(String, int, double) => (String, Number[]) (15) calls (5)
(String, int, String) => (String, Object[]) (16) calls (6)
(String, boolean) => (String, Object[]) (17) calls (6)
When a varargs call is chosen, the method determined has the most specific varargs parameter that is applicable for the actual argument. For example, in the method call at (14), the type Integer[]
is more specific than Number[]
or Object[]
. Note also the boxing of the elements of the implicitly created array in the calls from (13) to (17).
Example 7.11 Overloaded Method Resolution
import static java.lang.System.out;
class OverloadResolution {
public void action(String str) { // (1)
String signature = "(String)";
out.println(str + " => " + signature);
}
public void action(String str, int m) { // (2)
String signature = "(String, int)";
out.println(str + " => " + signature);
}
public void action(String str, int m, int n) { // (3)
String signature = "(String, int, int)";
out.println(str + " => " + signature);
}
public void action(String str, Integer... data) { // (4)
String signature = "(String, Integer[])";
out.println(str + " => " + signature);
}
public void action(String str, Number... data) { // (5)
String signature = "(String, Number[])";
out.println(str + " => " + signature);
}
public void action(String str, Object... data) { // (6)
String signature = "(String, Object[])";
out.println(str + " => " + signature);
}
public static void main(String[] args) {
OverloadResolution ref = new OverloadResolution();
ref.action("(String)"); // (8) calls (1)
ref.action("(String, int)", 10); // (9) calls (2)
ref.action("(String, Integer)", new Integer(10)); // (10) calls (2)
ref.action("(String, int, byte)", 10, (byte)20); // (11) calls (3)
ref.action("(String, int, int)", 10, 20); // (12) calls (3)
ref.action("(String, int, long)", 10, 20L); // (13) calls (5)
ref.action("(String, int, int, int)", 10, 20, 30); // (14) calls (4)
ref.action("(String, int, double)", 10, 20.0); // (15) calls (5)
ref.action("(String, int, String)", 10, "what?"); // (16) calls (6)
ref.action("(String, boolean)", false); // (17) calls (6)
}
}
Output from the program:
(String) => (String) (8) calls (1)
(String, int) => (String, int) (9) calls (2)
(String, Integer) => (String, int) (10) calls (2)
(String, int, byte) => (String, int, int) (11) calls (3)
(String, int, int) => (String, int, int) (12) calls (3)
(String, int, long) => (String, Number[]) (13) calls (5)
(String, int, int, int) => (String, Integer[]) (14) calls (4)
(String, int, double) => (String, Number[]) (15) calls (5)
(String, int, String) => (String, Object[]) (16) calls (6)
(String, boolean) => (String, Object[]) (17) calls (6)
The type cast expression for reference types has the following syntax:
(<destination type>) <reference expression>
where the <reference expression> evaluates to a reference value of an object of some reference type. A type cast expression checks that the reference value refers to an object whose type is compatible with the <destination type>, i.e. its type is a subtype of the <destination type>. If this is not the case, a ClassCastException
is thrown. The literal null
can be cast to any reference type. The construct (
<destination type>)
is usually called the cast operator.
The following conversions can be applied to the operand of a cast operator:
• both widening and narrowing reference conversions, followed optionally by an unchecked conversion
• both boxing and unboxing conversions
The implications that generics have for the cast operator, and the unchecked conversions that can occur, are discussed in Section 14.13, p. 724.
Boxing and unboxing conversions that can occur during casting is illustrated by the following code:
// (1) Boxing and casting: Number <-- Integer <-- int:
Number num = (Number) 100;
// (2) Casting, boxing, casting: Object <-- Integer <-- int <--double:
Object obj = (Object) (int) 10.5;
// (3) Casting, unboxing, casting: double <--- int <-- Integer <-- Object:
double d = (double) (Integer) obj;
Note that the resulting object from the cast expressions in (1) and (2) is an Integer
. The boxing conversions from int
to Integer
in (1) and (2) are implicit, and the unboxing conversion from Integer
to int
in (3) is also implicit.
The binary instanceof
operator can be used for comparing types. It has the following syntax (note that the keyword is composed of only lowercase letters):
<reference expression> instanceof
<destination type>
The instanceof
operator returns true
if the left-hand operand (that is, the reference value that results from the evaluation of <reference expression>) can be a subtype of the right-hand operand (<destination type>). It always returns false
if the left-hand operand is null
. If the instanceof
operator returns true
, the corresponding type cast expression will always be valid. Both the type cast expression and the instanceof
operators require a compile-time check and a runtime check, as explained below.
The compile-time check determines whether there is a subclass-superclass relationship between the source and the destination types. Given that the type of the <reference expression> is <source type>, the compiler determines whether a reference of <source type> and a reference of <destination type> can refer to objects of a reference type that are a common subtype of both <source type> and <destination type> in the type hierarchy. If this is not the case, then obviously there is no relationship between the types, and neither the cast nor the instanceof
operator application would be valid. At runtime, the <reference expression> evaluates to a reference value of an object. It is the type of the actual object that determines the outcome of the operation, as explained earlier.
What implications generics has for the instanceof
operator is discussed in Section 14.13, p. 723.
With the classes Light
and String
as <source type> and <destination type>, respectively, there is no subtype-supertype relationship between the <source type> and <destination type>. The compiler would reject casting a reference of type Light
to type String
or applying the instanceof
operator, as shown at (2) and (3) in Example 7.12 References of the classes Light
and TubeLight
can refer to objects of the class TubeLight
(or its subclasses) in the inheritance hierarchy depicted in Figure 7.2. Therefore, it makes sense to apply the instanceof
operator or cast a reference of the type Light
to the type TubeLight
as shown at (4) and (5), respectively, in Example 7.12.
At runtime, the result of applying the instanceof
operator at (4) is false
, because the reference light1
of the class Light
will actually denote an object of the subclass LightBulb,
and this object cannot be denoted by a reference of the peer class TubeLight
. Applying the cast at (5) results in a ClassCastException
for the same reason. This is the reason why cast conversions are said to be unsafe, as they may throw a ClassCastException
at runtime. Note that if the result of the instanceof
operator is false
, the cast involving the operands will also throw a ClassCastException
.
In Example 7.12, the result of applying the instanceof
operator at (6) is also false
, because the reference light1
will still denote an object of the class LightBulb
, whose objects cannot be denoted by a reference of its subclass SpotLightBulb
. Thus applying the cast at (7) causes a ClassCastException
to be thrown at runtime.
The situation shown at (8), (9), and (10) illustrates typical usage of the instanceof
operator to determine what object a reference is denoting so that it can be cast for the purpose of carrying out some specific action. The reference light1
of the class Light
is initialized to an object of the subclass NeonLight
at (8). The result of the instanceof
operator at (9) is true
, because the reference light1
will denote an object of the subclass NeonLight
, whose objects can also be denoted by a reference of its superclass TubeLight
. By the same token, the cast at (10) is also valid. If the result of the instanceof
operator is true
, the cast involving the operands will also be valid.
Example 7.12 The instanceof
and Cast Operators
class Light { /* ... */ }
class LightBulb extends Light { /* ... */ }
class SpotLightBulb extends LightBulb { /* ... */ }
class TubeLight extends Light { /* ... */ }
class NeonLight extends TubeLight { /* ... */ }
public class WhoAmI {
public static void main(String[] args) {
boolean result1, result2, result3, result4, result5;
Light light1 = new LightBulb(); // (1)
// String str = (String) light1; // (2) Compile-time error.
// result1 = light1 instanceof String; // (3) Compile-time error.
result2 = light1 instanceof TubeLight; // (4) false. Peer class.
// TubeLight tubeLight1 = (TubeLight) light1; // (5) ClassCastException.
result3 = light1 instanceof SpotLightBulb; // (6) false: Superclass
// SpotLightBulb spotRef = (SpotLightBulb) light1;// (7) ClassCastException
light1 = new NeonLight(); // (8)
if (light1 instanceof TubeLight) { // (9) true
TubeLight tubeLight2 = (TubeLight) light1; // (10) OK
// Can now use tubeLight2 to access an object of the class NeonLight,
// but only those members that the object inherits or overrides
// from the class TubeLight.
}
}
}
As we have seen, the instanceof
operator effectively determines whether the reference value in the reference on the left-hand side refers to an object whose class is a subtype of the type of the reference specified on the right-hand side. At runtime, it is the type of the actual object denoted by the reference on the left-hand side that is compared with the type specified on the right-hand side. In other words, what matters at runtime is the type of the actual object denoted by the reference, not the declared type of the reference.
Example 7.13 provides more examples of the instanceof
operator. It is instructive to go through the print statements and understand the results printed out. The literal null
is not an instance of any reference type, as shown in the print statements (1), (2), and (16). An instance of a superclass is not an instance of its subclass, as shown in the print statement (4). An instance of a class is not an instance of a totally unrelated class, as shown in the print statement (10). An instance of a class is not an instance of an interface type that the class does not implement, as shown in the print statement (6). Any array of non-primitive type is an instance of both Object
and Object[]
types, as shown in the print statements (14) and (15), respectively.
Example 7.13 Using the instanceof
Operator
interface IStack { /* From Example 7.7 */ }
interface ISafeStack extends IStack { /* From Example 7.7 */ }
class StackImpl implements IStack { /* From Example 7.7 */ }
class SafeStackImpl extends StackImpl
implements ISafeStack { /* From Example 7.7 */ }
public class Identification {
public static void main(String[] args) {
Object obj = new Object();
StackImpl stack = new StackImpl(10);
SafeStackImpl safeStack = new SafeStackImpl(5);
IStack iStack;
System.out.println("(1): " +
(null instanceof Object)); // Always false.
System.out.println("(2): " +
(null instanceof IStack)); // Always false.
System.out.println("(3): " +
(stack instanceof Object)); // true: instance of subclass of Object.
System.out.println("(4): " +
(obj instanceof StackImpl)); // false: Object not subtype of StackImpl.
System.out.println("(5): " +
(stack instanceof StackImpl)); // true: instance of StackImpl.
System.out.println("(6): " +
(obj instanceof IStack)); // false: Object does not implement IStack.
System.out.println("(7): " +
(safeStack instanceof IStack));// true: SafeStackImpl implements IStack.
obj = stack; // Assigning subclass to superclass.
System.out.println("(8): " +
(obj instanceof StackImpl)); // true: instance of StackImpl.
System.out.println("(9): " +
(obj instanceof IStack)); // true: StackImpl implements IStack.
System.out.println("(10): " +
(obj instanceof String)); // false: No relationship.
iStack = (IStack) obj; // Cast required: superclass assigned subclass.
System.out.println("(11): " +
(iStack instanceof Object)); // true: instance of subclass of Object.
System.out.println("(12): " +
(iStack instanceof StackImpl)); // true: instance of StackImpl.
String[] strArray = new String[10];
// System.out.println("(13): " +
// (strArray instanceof String);// Compile-time error, no relationship.
System.out.println("(14): " +
(strArray instanceof Object)); // true: array subclass of Object.
System.out.println("(15): " +
(strArray instanceof Object[])); // true: array subclass of Object[].
System.out.println("(16): " +
(strArray[0] instanceof Object));// false: strArray[0] is null.
System.out.println("(17): " +
(strArray instanceof String[])); // true: array of String.
strArray[0] = "Amoeba strip";
System.out.println("(18): " +
(strArray[0] instanceof String));// true: instance of String.
}
}
Output from the program:
7.20 Which statement about the program is true?
// Filename: MyClass.java
public class MyClass {
public static void main(String[] args) {
A[] arrA;
B[] arrB;
arrA = new A[10];
arrB = new B[20];
arrA = arrB; // (1)
arrB = (B[]) arrA; // (2)
arrA = new A[10];
arrB = (B[]) arrA; // (3)
}
}
class A {}
class B extends A {}
Select the one correct answer.
(a) The program will fail to compile because of the assignment at (1).
(b) The program will throw a java.lang.ClassCastException
in the assignment at (2), when run.
(c) The program will throw a java.lang.ClassCastException
in the assignment at (3), when run.
(d) The program will compile and run without errors, even if the cast operator (B[])
in the statements at (2) and (3) is removed.
(e) The program will compile and run without errors, but will not do so if the cast operator (B[])
in statements at (2) and (3) is removed.
7.21 What is the label of the first line that will cause compilation to fail in the following program?
// Filename: MyClass.java
class MyClass {
public static void main(String[] args) {
MyClass a;
MySubclass b;
a = new MyClass(); // (1)
b = new MySubclass(); // (2)
a = b; // (3)
b = a; // (4)
a = new MySubclass(); // (5)
b = new MyClass(); // (6)
}
}
class MySubclass extends MyClass {}
Select the one correct answer.
(a) (1)
(b) (2)
(c) (3)
(d) (4)
(e) (5)
(f) (6)
7.22 Given the following type and reference declarations, which assignment is legal?
// Type declarations:
interface I1 {}
interface I2 {}
class C1 implements I1 {}
class C2 implements I2 {}
class C3 extends C1 implements I2 {}
// Reference declarations:
C1 obj1;
C2 obj2;
C3 obj3;
Select the one correct answer.
(a) obj2 = obj1;
(b) obj3 = obj1;
(c) obj3 = obj2;
(d) I1 a = obj2;
(e) I1 b = obj3;
(f) I2 c = obj1;
7.23 Given the following class and reference declarations, what can be said about the statement y = (Sub) x
?
// Class declarations:
class Super {}
class Sub extends Super {}
// Reference declarations:
Super x;
Sub y;
Select the one correct answer.
(a) Illegal at compile time.
(b) Legal at compile time, but might be illegal at runtime.
(c) Definitely legal at runtime, but the cast operator (Sub)
is not strictly needed.
(d) Definitely legal at runtime, and the cast operator (Sub)
is needed.
7.24 Given the following class declarations and declaration statements, which assignment is legal at compile time?
// Class declarations:
interface A {}
class B {}
class C extends B implements A {}
class D implements A {}
// Declaration statements:
B b = new B();
C c = new C();
D d = new D();
Select the one correct answer.
(a) c = d;
(b) d = c;
(c) A a = d;
(d) d = (D) c;
(e) c = b;
7.25 Which letters will be printed when the following program is run?
// Filename: MyClass.java
public class MyClass {
public static void main(String[] args) {
B b = new C();
A a = b;
if (a instanceof A) System.out.println("A");
if (a instanceof B) System.out.println("B");
if (a instanceof C) System.out.println("C");
if (a instanceof D) System.out.println("D");
}
}
class A {}
class B extends A {}
class C extends B {}
class D extends C {}
Select the three correct answers.
(a) A
will be printed.
(b) B
will be printed.
(c) C
will be printed.
(d) D
will be printed.
7.26 Given three classes A
, B
, and C
, where B
is a subclass of A
, and C
is a subclass of B
, which one of these boolean expressions is true
only when an object denoted by reference o
has actually been instantiated from class B
, as opposed to from A
or C
?
Select the one correct answer.
(a) (o instanceof B) && (!(o instanceof A))
(b) (o instanceof B) && (!(o instanceof C))
(c) !((o instanceof A) || (o instanceof B))
(d) (o instanceof B)
(e) (o instanceof B) && !((o instanceof A) || (o instanceof C))
7.27 When run, the following program will print all the letters I
, J
, C,
and D
. True or false?
public class MyClass {
public static void main(String[] args) {
I x = new D();
if (x instanceof I) System.out.println("I");
if (x instanceof J) System.out.println("J");
if (x instanceof C) System.out.println("C");
if (x instanceof D) System.out.println("D");
}
}
interface I{}
interface J{}
class C implements I {}
class D extends C implements J {}
Select the one correct answer.
(a) True.
(b) False.
7.28 What will be the result of compiling and running the following program?
public class RQ200_10 {
public static void main(String[] args) {
Integer iRef;
iRef = 786; //(1)
iRef = (Integer)(2007 - 786); //(2)
iRef = (int)3.14; //(3)
iRef = (Integer)3.14; //(4)
iRef = (Integer)(int)3.14; //(5)
}
}
Select the one correct answer.
(a) The code will fail to compile because of errors in at least one of the lines (1), (2), and (3).
(b) The code will fail to compile because of errors in both the lines (4) and (5).
(c) The code will fail to compile because of error in line (4).
(d) The code will fail to compile because of error in line (5).
(e) The code will compile, but throw a ClassCastException
.
(f) The code will compile and execute normally.
7.29 What will the program print when compiled and run?
public class RQ200_60 {
public static void main(String[] args) {
Integer i = -10;
Integer j = -10;
System.out.print(i==j);
System.out.print(i.equals(j));
Integer n = 128;
Integer m = 128;
System.out.print(n==m);
System.out.print(n.equals(m));
}
}
Select the one correct answer.
(a) falsetruefalsetrue
(b) truetruetruetrue
(c) falsetruetruetrue
(d) truetruefalsetrue
(e) None of the above.
7.30 What will the program print when compiled and run?
public class RQ200_70 {
public static void main(String[] args) {
Integer i = new Integer(-10);
Integer j = new Integer(-10);
Integer k = -10;
System.out.print(i==j);
System.out.print(i.equals(j));
System.out.print(i==k);
System.out.print(i.equals(k));
}
}
Select the one correct answer.
(a) falsetruefalsetrue
(b) truetruetruetrue
(c) falsetruetruetrue
(e) None of the above.
7.31 Given:
public class RQ200_20 {
private Map<String, Integer> accounts = new HashMap<String, Integer>();
public int getBalance(String accountName) {
Integer total = (Integer) accounts.get(accountName); // (1)
if (total == null) total = new Integer(0); // (2)
return total.intValue(); // (3)
}
public void setBalance(String accountName, int amount) {
accounts.put(accountName, new Integer(amount)); // (4)
}
}
Which statements can be replaced so that the program still compiles and runs without errors?
Select the three correct answers.
(a) Replace (1)–(3) with:
int total = accounts.get(accountName);
if (total == null) total = 0;
return total;
(b) Replace (1)–(3) with:
int total = accounts.get(accountName);
return total == null ? 0 : total;
(c) Replace (1)–(3) with:
return accounts.get(accountName);
(d) Replace (4) with:
accounts.put(accountName, amount);
(e) Replace (4) with:
accounts.put(accountName, amount.intValue());
7.32 What is the result of compiling and running the following program?
class YingYang {
void yingyang(Integer i) {
System.out.println("Integer: " + i);
}
void yingyang(Integer[] ints) {
System.out.println("Integer[]: " + ints[0]);
}
void yingyang(Integer... ints) {
System.out.println("Integer...: " + ints[0]);
}
}
public class RQ800_50 {
public static void main(String[] args) {
YingYang yy = new YingYang();
yy.yingyang(10);
yy.yingyang(10,12);
yy.yingyang(new Integer[] {10, 20});
yy.yingyang(new Integer(10), new Integer(20));
}
}
Select the one correct answer.
(a) The class YingYang
does not compile because of errors.
(b) The program compiles and prints:
Integer: 10
Integer...: 10
Integer...: 10
Integer...: 10
(c) The program compiles and prints:
Integer: 10
Integer...: 10
Integer[]: 10
Integer...: 10
7.33 What is the result of compiling and running the following program?
public class RQ800_60 {
static void printFirst(Integer... ints) {
System.out.println("Integer...: " + ints[0]);
}
static void printFirst(Number... nums) {
System.out.println("Number...: " + nums[0]);
}
static void printFirst(Object... objs) {
System.out.println("Object...: " + objs[0]);
}
public static void main(String[] args) {
printFirst(10);
printFirst((byte)20);
printFirst('3', '0'),
printFirst("40");
printFirst((short)50, 55);
printFirst((Number[])new Integer[] {70, 75});
}
}
Select the one correct answer.
(a) The program does not compile because of ambiguous method calls.
(b) The program compiles and prints:
(c) The program compiles and prints:
Integer...: 10
Number...: 20
Object...: 3
Object...: 40
Number...: 50
Number...: 70
(d) The program compiles and prints:
Integer...: 10
Integer...: 20
Integer...: 3
Object...: 40
Number...: 50
Number...: 70
7.34 What is the result of compiling and running the following program?
public class RQ800_80 {
static String compute(long... ls) { return "ONE"; }
static String compute(Long... ls) { return "TWO"; }
static String compute(Integer i1, Integer i2) { return "THREE"; }
static String compute(Long l1, Long l2) { return "FOUR"; }
static String compute(Number n1, Number n2) { return "FIVE"; }
public static void main(String[] args) {
System.out.println(compute((byte)5, (byte)10) + ", " + compute(5, 10));
System.out.println(compute(5L, 10) + ", " + compute(5L, 10L));
}
}
Select the one correct answer.
(a) The program does not compile because of errors.
(b) The program compiles and prints:
THREE, THREE
FOUR, FOUR
(c) The program compiles and prints:
FIVE, THREE
FIVE, FOUR
(d) The program compiles and prints:
FIVE, THREE
ONE, TWO
(e) The program compiles and prints:
ONE, THREE
ONE, ONE
Which object a reference will actually denote during runtime cannot always be determined at compile time. Polymorphism allows a reference to denote objects of different types at different times during execution. A supertype reference exhibits polymorphic behavior since it can denote objects of its subtypes.
When a non-private
instance method is invoked on an object, the method definition actually executed is determined both by the type of the object at runtime and the method signature. Dynamic method lookup is the process of determining which method definition a method signature denotes during runtime, based on the type of the object. However, a call to a private
instance method is not polymorphic. Such a call can only occur within the class and gets bound to the private
method implementation at compile time.
The inheritance hierarchy depicted in Figure 7.4 is implemented in Example 7.14. The implementation of the method draw()
is overridden in all subclasses of the class Shape
. The invocation of the draw()
method in the two loops at (3) and (4) in Example 7.14 relies on the polymorphic behavior of references and dynamic method lookup. The array shapes
holds Shape
references denoting a Circle
, a Rectangle
and a Square
, as shown at (1). At runtime, dynamic lookup determines the draw()
implementation to execute, based on the type of the object denoted by each element in the array. This is also the case for the elements of the array drawables
at (2), which holds IDrawable
references that can be assigned the reference value of any object of a class that implements the IDrawable
interface. The first loop will still work without any change if objects of new subclasses of the class Shape
are added to the array shapes
. If they did not override the draw()
method, an inherited version of the method would be executed. This polymorphic behavior applies to the array drawables
, where the subtype objects are guaranteed to have implemented the IDrawable
interface.
Polymorphism and dynamic method lookup form a powerful programming paradigm that simplifies client definitions, encourages object decoupling, and supports dynamically changing relationships between objects at runtime.
Example 7.14 Polymorphism and Dynamic Method Lookup
interface IDrawable {
void draw();
}
class Shape implements IDrawable {
public void draw() { System.out.println("Drawing a Shape."); }
}
class Circle extends Shape {
public void draw() { System.out.println("Drawing a Circle."); }
}
class Rectangle extends Shape {
public void draw() { System.out.println("Drawing a Rectangle."); }
}
class Square extends Rectangle {
public void draw() { System.out.println("Drawing a Square."); }
}
class Map implements IDrawable {
public void draw() { System.out.println("Drawing a Map."); }
}
public class PolymorphRefs {
public static void main(String[] args) {
Shape[] shapes = {new Circle(), new Rectangle(), new Square()}; // (1)
IDrawable[] drawables = {new Shape(), new Rectangle(), new Map()}; // (2)
System.out.println("Draw shapes:");
for (Shape shape : shapes) // (3)
shape.draw();
System.out.println("Draw drawables:");
for (IDrawable drawable : drawables) // (4)
drawable.draw();
}
}
Output from the program:
Figure 7.5 is a UML class diagram showing several aggregation relationships and one inheritance relationship. The class diagram shows a queue defined by aggregation and a stack defined by inheritance. Both are based on linked lists. A linked list is defined by aggregation. A non-generic implementation of these data structures is shown in Example 7.15. The purpose of the example is to illustrate inheritance and aggregation, not industrial-strength implementation of queues and stacks. The class Node
at (1) is straightforward, defining two fields: one denoting the data and the other denoting the next node in the list. The class LinkedList
at (2) keeps track of the list by managing a head and a tail reference. Nodes can be inserted in the front or back, but deleted only from the front of the list.
Example 7.15 Implementing Data Structures by Inheritance and Aggregation
class Node { // (1)
private Object data; // Data
private Node next; // Next node
// Constructor for initializing data and reference to the next node.
Node(Object data, Node next) {
this.data = data;
this.next = next;
}
// Methods:
public void setData(Object obj) { data = obj; }
public Object getData() { return data; }
public void setNext(Node node) { next = node; }
public Node getNext() { return next; }
}
//____________________________________________________
class LinkedList { // (2)
protected Node head = null;
protected Node tail = null;
// Methods:
public boolean isEmpty() { return head == null; }
public void insertInFront(Object dataObj) {
if (isEmpty()) head = tail = new Node(dataObj, null);
else head = new Node(dataObj, head);
}
public void insertAtBack(Object dataObj) {
if (isEmpty())
head = tail = new Node(dataObj, null);
else {
tail.setNext(new Node(dataObj, null));
tail = tail.getNext();
}
}
public Object deleteFromFront() {
if (isEmpty()) return null;
Node removed = head;
if (head == tail) head = tail = null;
else head = head.getNext();
return removed.getData();
}
}
//____________________________________________________
class QueueByAggregation { // (3)
private LinkedList qList;
// Constructor
QueueByAggregation() {
qList = new LinkedList();
}
// Methods:
public boolean isEmpty() { return qList.isEmpty(); }
public void enqueue(Object item) { qList.insertAtBack(item); }
public Object dequeue() {
if (qList.isEmpty()) return null;
return qList.deleteFromFront();
}
public Object peek() {
return (qList.isEmpty() ? null : qList.head.getData());
}
}
//____________________________________________________
class StackByInheritance extends LinkedList { // (4)
public void push(Object item) { insertInFront(item); }
public Object pop() {
if (isEmpty()) return null;
return deleteFromFront();
}
public Object peek() {
return (isEmpty() ? null : head.getData());
}
}
//____________________________________________________
public class Client { // (5)
public static void main(String[] args) {
String string1 = "Queues are boring to stand in!";
int length1 = string1.length();
QueueByAggregation queue = new QueueByAggregation();
for (int i = 0; i<length1; i++)
queue.enqueue(new Character(string1.charAt(i)));
while (!queue.isEmpty())
System.out.print(queue.dequeue());
System.out.println();
String string2 = "!no tis ot nuf era skcatS";
int length2 = string2.length();
StackByInheritance stack = new StackByInheritance();
for (int i = 0; i<length2; i++)
stack.push(new Character(string2.charAt(i)));
stack.insertAtBack(new Character('!')); // (6)
while (!stack.isEmpty())
System.out.print(stack.pop());
System.out.println();
}
}
Output from the program:
Queues are boring to stand in!
Stacks are fun to sit on!!
Choosing between inheritance and aggregation to model relationships can be a crucial design decision. A good design strategy advocates that inheritance should be used only if the relationship is-a is unequivocally maintained throughout the lifetime of the objects involved; otherwise, aggregation is the best choice. A role is often confused with an is-a relationship. For example, given the class Employee
, it would not be a good idea to model the roles an employee can play (such as a manager or a cashier) by inheritance if these roles change intermittently. Changing roles would involve a new object to represent the new role every time this happens.
Code reuse is also best achieved by aggregation when there is no is-a relationship. Enforcing an artificial is-a relationship that is not naturally present is usually not a good idea. This is illustrated in Example 7.15 at (6). Since the class StackByInheritance
at (4) is a subclass of the class LinkedList
at (2), any inherited method from the superclass can be invoked on an instance of the subclass. Also, methods that contradict the abstraction represented by the subclass can be invoked, as shown at (6). Using aggregation in such a case results in a better solution, as demonstrated by the class QueueByAggregation
at (3). The class defines the operations of a queue by delegating such requests to the underlying class LinkedList
. Clients implementing a queue in this manner do not have access to the underlying class and, therefore, cannot break the abstraction.
Both inheritance and aggregation promote encapsulation of implementation, as changes to the implementation are localized to the class. Changing the contract of a superclass can have consequences for the subclasses (called the ripple effect) and also for clients who are dependent on a particular behavior of the subclasses.
Polymorphism is achieved through inheritance and interface implementation. Code relying on polymorphic behavior will still work without any change if new subclasses or new classes implementing the interface are added. If no obvious is-a relationship is present, polymorphism is best achieved by using aggregation with interface implementation.
In this section, we provide a brief explanation of some basic concepts in object-oriented (OO) design. The reader is encouraged to consult the vast body of literature that is readily available on this subject.
An object has properties and behaviors that are encapsulated inside the object. The services it offers to its clients comprises its contract, or public interface. Only the contract defined by the object is available to the clients. The implementation of its properties and behavior is not a concern of the clients. Encapsulation helps to make clear the distinction between an object’s contract and implementation. This has major consequences for program development. The implementation of an object can change without implications for the clients. Encapsulation also reduces complexity, as the internals of an object are hidden from the clients who cannot alter its implementation.
Encapsulation is achieved through information hiding, by making judicious use of language features provided for this purpose. Information hiding in Java can be achieved at different levels of granularity:
• method or block level
Localizing information in a method hides it from the outside.
• class level
The accessibility of members declared in a class can be controlled through member accessibility modifiers. One much-advocated information-hiding technique is to prevent direct access by clients to data maintained by an object. The fields of the object are private and its contract defines public methods for the services provided by the object. Such tight encapsulation helps to separate the use from the implementation of a class.
• package level
Classes that belong together can be grouped into relevant packages by using the package
statement. Inter-package accessibility of classes can be controlled by class accessibility modifiers.
Cohesion is an inter-class measure of how well-structured and closely-related the functionality is in a class. The objective is to design classes with high cohesion, that perform well-defined and related tasks (also called functional cohesion). The public methods of a highly cohesive class typically implement a single specific task that is related to the purpose of the class. For example, in an MVC-based application, the respective classes for the Model, the View, and the Controller should be focused on providing functionality that only relates to their individual purpose. A method in one class should not perform a task that should actually be implemented by one of the other two classes.
Lack of cohesion in a class means that the purpose of the class is not focused, and unrelated functionality is ending up in the class (also called coincidental cohesion)—which will eventually impact the maintainability of the application.
Coupling is a measure of intra-class dependencies. Objects need to interact with each other, therefore dependencies between classes are inherent in OO design. However, these dependencies should be minimized in order to achieve loose coupling, which aids in creating extensible applications.
One major source of intra-class dependencies is the exposure of implementation details of an object. Such details can be utilized by other objects, and this dependency can impede changes in the implementation, resulting in less extensible applications.
High cohesion and loose coupling help to achieve the main goals of OO design: maintainability, reusability, extensibility, and reliability.
7.35 What will be the result of compiling and running the following program?
public class Polymorphism {
public static void main(String[] args) {
A ref1 = new C();
B ref2 = (B) ref1;
System.out.println(ref2.f());
}
}
class A { int f() { return 0; } }
class B extends A { int f() { return 1; } }
class C extends B { int f() { return 2; } }
Select the one correct answer.
(a) The program will fail to compile.
(b) The program will compile but will throw a ClassCastException
, when run.
(c) The program will compile and print 0
, when run.
(d) The program will compile and print 1
, when run.
(e) The program will compile and print 2
, when run.
7.36 What will be the result of compiling and running the following program?
public class Polymorphism2 {
public static void main(String[] args) {
A ref1 = new C();
B ref2 = (B) ref1;
System.out.println(ref2.g());
}
}
class A {
private int f() { return 0; }
public int g() { return 3; }
}
class B extends A {
private int f() { return 1; }
public int g() { return f(); }
}
class C extends B {
public int f() { return 2; }
}
Select the one correct answer.
(a) The program will fail to compile.
(b) The program will compile and print 0
, when run.
(c) The program will compile and print 1
, when run.
(d) The program will compile and print 2
, when run.
(e) The program will compile and print 3
, when run.
7.37 Which statements about the program are true?
public interface HeavenlyBody { String describe(); }
class Star {
String starName;
public String describe() { return "star " + starName; }
}
class Planet extends Star {
String name;
public String describe() {
return "planet " + name + " orbiting star " + starName;
}
}
Select the two correct answers:
(a) The code will fail to compile.
(b) The code defines a Planet
is-a Star
relationship.
(c) The code will fail to compile if the name starName
is replaced with the name bodyName
throughout the declaration of the Star
class.
(d) The code will fail to compile if the name starName
is replaced with the name name
throughout the declaration of the Star
class.
(e) An instance of Planet
is a valid instance of HeavenlyBody
.
7.38 Given the following code, which statement is true?
public interface HeavenlyBody { String describe(); }
class Star implements HeavenlyBody {
String starName;
public String describe() { return "star " + starName; }
}
class Planet {
String name;
Star orbiting;
public String describe() {
return "planet " + name + " orbiting " + orbiting.describe();
}
}
Select the one correct answer:
(a) The code will fail to compile.
(b) The code defines a Planet
has-a Star
relationship.
(c) The code will fail to compile if the name starName
is replaced with the name bodyName
throughout the declaration of the Star
class.
(d) The code will fail to compile if the name starName
is replaced with the name name
throughout the declaration of the Star
class.
(e) An instance of Planet
is a valid instance of a HeavenlyBody
.
7.39 Which statement is not true?
Select the one correct answer.
(a) Maximizing cohesion and minimizing coupling are the hallmarks of a welldesigned application.
(b) Coupling is an inherent property of any non-trivial OO design.
(c) Adhering to the JavaBeans naming standard can aid in achieving encapsulation.
(d) Dependencies between classes can be minimized by hiding implementation details.
(e) Each method implementing a single task will result in a class that has high cohesion.
(f) None of the above.
The following information was included in this chapter:
• inheritance and its implications in OOP
• overriding and hiding of superclass members
• method overriding versus method overloading
• usage of the super
reference to access superclass members
• usage of this()
and super()
calls, including constructor chaining
• interfaces and multiple interface inheritance
• subtype-supertype relationship
• conversions when assigning, casting, and passing reference values
• identifying the type of objects using the instanceof
operator
• polymorphism and dynamic method lookup
• inheritance (is-a) versus aggregation (has-a)
• best practices for OO design: tight encapsulation, loose coupling, and high cohesion in classes.
7.1 Declare an interface called Function
that has a method named evaluate
that takes an int
parameter and returns an int
value.
Create a class called Half
that implements the Function
interface. The implementation of the method evaluate()
should return the value obtained by dividing the int
argument by 2.
In a client, create a method that takes an arbitrary array of int
values as a parameter, and returns an array that has the same length, but the value of an element in the new array is half that of the value in the corresponding element in the array passed as the parameter. Let the implementation of this method create an instance of Half
, and use this instance to calculate values for the array that is returned.
7.2 Rewrite the method that operated on arrays from the previous exercise: the method should now also accept a Function
reference as an argument, and use this argument instead of an instance of the Half
class.
Create a class called Print
that implements the method evaluate()
in the Function
interface. This method simply prints the int
value passed as argument, and returns this value.
Now, write a program that creates an array of int
values from 1 to 10, and does the following:
• Prints the array using an instance of the Print
class and the method described earlier.
• Halves the values in the array and prints the values again, using the Half
and Print
classes, and the method described above.