Chapter 5 Inheritance
5.1 Classes, superclasses, and subclasses
The hall mark of inheritance—“is-a”
Eg. Every manager is a employee.
5.1.1 Defining Subclasses
The existing class: Superclass, base class, or parent class.
The new class: Subclass, derived class, or child class.
Subclasses have more functionality than their superclasses.
When designing classes, you place the most general methods in the superclasses and more specialized methods in its subclasses.
5.1.2 Overriding Methods
super calls the method of superclass.
super is a keyword that directs compiler to invoke the superclass method.
Inheritance can never take away any fields or methods.
5.1.3 Subclass Constructors
super has a meaning that “call the constructor of the superclass with its parameters”.
The subclass’s constructor cannot access the private fields of super classes.
The call using super must be the first statement in the constructor for the subclass.
If the subclass constructor does not call a superclass constructor explicitly, the no-argument constructor is invoked. If the superclass does not have a no-argument constructor and the subclass constructor does not call another superclass constructor explicitly, the Java compiler reports an error.
The virtual machine knows about the actual type of the object, an therefore invoke the correct method.
Polymorphism: An object variable can refer to multiple actual types.
Dynamic binding: Automatically selecting the appropriate methods at runtime.
5.1.4 Inheritance Hierarchies
Inheritance chain: the path from a particular class to its ancestors in the inheritance hierarchies.
5.1.5 Polymorphism
The substitution principle: You can use a subclass object whenever the program expects a superclass object.
Object variables are polymorphic.
You cannot assign a superclass reference to a subclass variable.
Caution: Arrays of subclass references can be converted to arrays of superclass references without a cast. All arrays remember the element type with which they were created, and they monitor that only compatible references are stored into them. Attempting to store a superclass’s reference causes an ArrayStoreException.
5.1.6 Understanding Method Calls
/*
f—short for function; C—short for ClassName; x—short for a object variable
*/
1. The compiler looks at the declared type of the object and the method name f in the class C and all accessible methods called f in the superclass of C.
2. The compiler determines the types of the arguments that are supplied in the method call.
Overloading Resolution: Best match?
Yes --> Be called.
No --> 1) Not Found--> Error
2) All match
3. Static binding: If the method is static, private, final or a constructor, then the compiler knows exactly which method to call.
4. When the program runs and uses the dynamic binding to call a method, the virtual machine must call the version of the method that is appropriate for the actual type of the object to which x refers. The virtual machine precomputes for each class a method table that lists all method signatures and actual methods to be called. Virtual machine makes a table lookup when a method is called.
If you define a method in a subclass that has the same signature as a superclass method, you override the superclass method.
When you override a method, you need to keep the return type compatible. A subclass may change type to a subtype of the original type.
An important property of Dynamic binding: it makes programs extensible without the need for modifying the existing code.
Caution: When you override a method, the subclass must be at least as visible as the superclass method. Particularly, if the superclass method is public, the subclass method must be declared as public.
5.1.7 Preventing Inheritance: final Classes and Methods
Classes cannot be extended are called final classes, and you use final modifier in the definition of the class to indicate this.
If a class is declared final, only methods, not fields are automatically final.
The reason for using final: to make sure its semantics cannot be changed in the subclasses.
If a method is not overridden, and it’s short, then a compiler can optimize the method call away—a process called inlining.
5.1.8 Casting
Make a cast of an object reference:
TargetClassName objectVariable = (TargetClassName) objectReference;
The only reason why you make a cast:
To use an object in its full capacity after its actual type has been temporarily forgotten.
If you assign a superclass reference to a subclass variable, you must use a cast so that your promise can be checked at runtime.
You can only cast within an inheritance hierarchy.
If you cast down an inheritance hierarchy chain and “lied” about what an object contains, the Java runtime system notices the broken promise and generates a ClassCastException.
Use instanceof operator to check before casting from a superclass to a subclass.
if (... instanceof SubclassName)
{
// make a cast
...
}
If x is null, then x instanceof C returns false.
It’s best to minimize the use of casts and the instanceof operators. Ask yourself if there is an indication of a design flaw in the superclass.
5.1.9 Abstract Classes
For added clarity, a class itself has one or more abstract methods must itself be declared abstract.
Abstract classes can have fields and concrete methods. You should always move common fields and methods (whether abstract or not) to the superclass (whether abstract or not).
Abstract classes act like placeholders for methods that are implemented in the subclasses.
2 choices to extend abstract classes:
- Leave some or all of methods undefined; then you must tag the subclass abstract as well.
- Define all methods.
A class can be declared as abstract though it has no abstract method.
Abstract classes cannot be instantiated. However, you can create objects of concrete subclasses. You can still create object variables of an abstract class, but such a variable must refer to a non-abstract subclass.
5.1.10 Protected Access
When you want to restrict a method to subclasses only or, less commonly, to allow subclass methods to access a superclass field. In that case, you declare a class feature as protected.
Visible to the package and all subclasses—protected.
5.2 Object: The Cosmic Superclass
Every class extends Object.
You can use a variable of type Object to refer to objects of any type.
A variable of type Object is only useful as a generic holder for arbitrary values.
In Java, only primitive types (numbers, characters, and boolean values) are not objects.
All array types, no matter whether they are arrays of objects or arrays of primitive types, are class types that extend the Object class.
5.2.1 The equals Method
The equals method in the Object class tests whether one object is considered equal to another, and it determines whether two object references are identical.
The getClass method returns the class of an object.
To guard against the possibility that arguments are null, use the Objects.equals method. The call Objects.equals(a,b) returns true if both arguments are null, false if only one is null, and calls a.equals(b) otherwise.
When you define the equals method for a subclass, first call equals on the superclass. If the superclass fields are equal, you are ready to compare the instance fields of the subclass.
5.2.2 Equality Testing and Inheritance
Properties of the equals method:
- Reflexive: for any non-null reference x, x.equals(x) should return true.
- Symmetric: for any reference x and y, x.equals(y) returns ture if and only if y.equals(x) returns true.
- Transitive: for any reference x, y, and z, if x.equals(y) returns ture and y.equals(z) returns true, then x.equals(z) returns true.
- Consistent: if the objects to which x and y refer have not changed, the repeated calls to x.equals(y) return the same value.
- For any non-null reference x, x.equals(null) should return false.
Nobody should redefine the semantics of set equality.
Two distinct scenarios:
- If subclasses have their own notion of equality, then the symmetry requirement forces you to use the getClass test.
- If the notion of equality is fixed in superclass, then you can use the instanceof test and allow objects of different classes to be equal to one another.
A recipe for writing perfect equals methods:
- Name the explicit parameter otherObject—later, you will need to cast it to another variable that you should call other.
- Test whether this happens to be identical to otherObject.
-
if (this == otherObject) return true;
- Test whether otherObject is null and return false if it is.
if (otherObject == null) return false;
- Compare the classes of this and otherObject. If the semantics of equals can change in subclasses, use the getClass test:
if(getClass() != otherObject.getClass()) return false;
If the same semantics holds for all subclasses, you can use instanceof test:
if (otherObject instanceof ClassName) return false;
- Now compare the fields, as required by your notion of equality. Use == for primitive type fields, Objects.equals for object fields. Return true if all fields match, false otherwise.
return field1 == other.field1 && Objects.equals(field2, other.field2) && ...;
If you have fields of array type, you can use the static Arrays.equals() method to check that the corresponding array elements are equal.
Caution: You can protect yourself against the type of error(You didn't override a method but defined a completely unrelated method.) by tagging methods that are intended to override superclass methods with @Override :
// example
@Override public boolean equals(Object other)
5.2.3 The hashCode Method
A hash code is an integer that is derived from an object’s memory address.
Each object has a default hash code.
For strings, the hash codes are derived from their contents.
The hashCode method should return an integer which can be negative.
Better ways to use the hashCode method:
- Use the null-safe method Objects.hashCode. It returns 0 if its argument is null and the result of calling hashcode on the argument otherwise. Also, use the static Double.hashCode method to avoid creating a Double object.
- When you need to combine multiple hash values, call Objects.hash with all of them. It will call Objects.hashCode for each argument and combine the values.
public int hashCode() { return Objecs.hash(arg1, arg2, arg3, …); }
Definitions of equals and hashCode must be compatible: If x.equals(y) is true, then x.hashCode() must return the same value as y.hashCode().
If you have fields of an array type, you can use the static Arrays.hashCode method to compute a hash code composed of the hash codes of the array elements.
5.2.4 The toString Method
toString method: returns a string representing the value of this object.
Most(not all) toString method follow this format: the name of the class, then the field values enclosed in square brackets.
You can call getClass().getName() to obtain a string with the class name.
The subclass programmer should define its own toSrtring method and add subclass fields.
The toString method is ubiquitous because whenever an object is concatenated by the + operator, Java compiler automatically invokes the toString method to obtain a string representation of the object.
Object class defines the toString method to print the class name and the hash code of the object.
To print an array, use the Arrays.toString method; to print a multidimensional array, use the Arrays.deepToString method.
5.3 Generic Array Lists
The ArrayList class is automatically adjusts its capacity as you add and remove elements, without any additional code.
ArrayList is a generic class with a type parameter.
Declare and construct an ArrayList holds objects:
ArrayList<TypeParameter> objVar = new ArrayList<TypeParameter>();
As of Java SE 7, you can omit the type parameter on the right hand side
Use the add method to add new elements to an ArrayList.
If you call add and the internal array is full, the array list automatically creates a bigger array and copies all the objects from the smaller to the bigger array.
If you already know or have a good guess, how many elements you want to store, call the ensureCapacity method before filling the array list.
objVar.ensureCapacity(100); // 100 for example
You can also pass an initial capacity to the ArrayList constructor:
ArrayList<TypeParameter> objVar = new ArrayList<>(100); //100 for example
Important distinction between the capacity of an array list and the size of an array: if you allocate an array with x entries, then the array has x slots, ready for use. An array list with a capacity of x has the potential of holding x elements (and in fact, more than x, at the cost of additional reallocations)—but at the beginning, even after its initial construction, an array list holds no elements at all.
The size method return the actual number of elements in the array list.
trimToSize method adjusts the size of the memory block to use exactly as much storage space as is required to hold the current number of elements.
/*
trim
/ trɪm; trɪm/ v (-mm-)
(a) [Tn, Tn.p] make (sth) neat or smooth by cutting
away irregular parts
*/
Once you trim the size of an array list, adding new elements will move the block again, which takes time. You should only use trimToSize onlu you are sure you won’t add any more elements to the array list.
5.3.1 Accessing Array List Elements
You use the get and set methods to access or change the elements of an array list.
The index values are zero based.
Caution:
Do not call list.set(i,x) until the size of the array list is larger than i.
When there were no generic classes , the get method of the raw ArrayList class returns an Obejct. Consequently, callers of get had to cast the returned value into the desired tyoe. The raw ArrayList’s add and set methods accept objects of any type.
Flexible Growth and Convenient Element Access:
1. Make an array list and add all elements.
2. Use the toArray method to copy the elements into an array. Sometimes you need to add elements in the middle of an array list. Use the add method with an index parameter.
int n = sample.size() / 2;
sample.add(n, e);
The elements at location n and above are shifted up to make room for the new entry. If the new size of the array list after the insertion exceeds the capacity, the array list reallocates its storage array.
Remove an element from the middle of an array list:
sample.remove(n);
You can use the "for-each" loop to traverse the contents of an array list:
for (Sample s : sampleArrayList)
... // do sth. with s
5.3.2 Compatibility between Typed and Raw Array Lists
You will always want to use type parameters for added safety.
In this section, you will see how to interoperate with legacy code that does not using type parameters.
You can pass a typed array list to a method that has an argument of ArrayList type without errors or warnings from the compiler. (Not completely safe)
For compatibility, the compiler translates all typed array lists into raw ArrayList objects after checking that the type rules are not violated. In a running programme, there is no type parameter in the virtual machine.
Once you studied and satisfied with the compiler warnings, you can tag the variable that receives the cast with the @SuppressWarnings(“unchecked”) annotation.
5.4 Object Wrappers and Autoboxing
All primitive types have class counterparts. These kinds of classes are usually called wrappers. The wrapper classes are Integer, Long, Double, Float, Short, Byte, Character and Boolean. (The first six inherit from the Number class.) They are immutable and final –so you cannot subclass them.
Caution:
Efficiency: ArrayList<Integer> < int[]
You would only to use this construction for small collections when programmer convenience is more important than efficiency.
When you assign an Integer object into an int value, it is automatically unboxed.
Automatic boxing and unboxing even works with arithmetic expressions.
The only difference between primitive types and wrapper classes:
The == operator applied to wrapper objects only tests whether the objects have identical memory locations. Call the equals method to when comparing wrapper objects.
Wrapper class references can be null, it is possible for autoboxing to throw a NullPointerException.
To convert a string to an integer, use the parseInt method.
Caution: You cannot use these wrapper classes to create a method that modifies numeric parameters. You can use one of the holder type defined in the org.omg.CORBA package: IntHolder, BooleanHolder, and so on. Each holder type has a public field through which you can access the stored value.
5.5 Methods with a Variable Number
It’s possible to provide methods that can be called with a variable of parameters (sometimes called “varargs” methods).
It’s legal to pass an array as the last parameter of a method with variable parameters.
/*
radix: 进制
*/
5.6 Enumeration Classes
Use == to compare values of enumerated types.
You can add constructors, fields and methods to an enumerated type.
The constructors are only invoked when enumerated constants are constructed.
All enumerated types are subclasses of the class Enum. The most useful one is toString, which returns the name of the enumerated constant.
Each enumerated type has as static values method that returns an array of all values of the enumeration.
The ordinal method yields the position of an enumerated constant in the enum declaration, counting from zero.
/*
ordinal: 序数的。
*/
5.7 Reflection
A program that can analyze capabilities of classes is called reflective.
You can use it to:
- analyze capabilities of classes at runtime;
- inspect objects at runtime—for example, a toString method for all classes;
- implement generic array manipulation code; and
- take advantage of Method objects that works just like function pointers.
5.7.1 The Class Class
While your program is running, the Java runtime system always maintains what is called runtime type identification on all objects. This information keeps track of the class to which each object belongs to. Virtual machine uses runtime type identification to select correct method to execute. Also, you can access this information with the Class class.
The getClass() method in the Object class returns an instance of Class type.
A Class object describes the properties of a particular class. For example, the statement
System.out.println(s.getClass().getName() + “ ”+ s.getName);
If the class is in a package, the package name is part of the class name.
You can obtain a Class object corresponding to a class name by using the static forName method.
Use this method if the class name is stored in as string that varies at runtime. This works if className is the name of a class or interface. Otherwise, the forName method throws a checked exception.
If T is any Java type(or the void keyword), then T.class is the matching class object.
You can use == to compare class objects.
The newInstance() method lets you create an instance of a class on the fly.
/*
on the fly: while in the progress or motion.
*/
For example,
s.getClass().newInstance();
creates a new instance of the same class type as s.
The newInstance method calls the no-argument constructor to initialize the newly created object. An exception is thrown if the class does not have a no-argument constructor.
A combination of forName and newInstance lets you create an object from a class name stored in a string.
You must use the newInstance method stored in the Constructor class if you need to provide parameters for the constructor of a class you want to create by name.
5.7.2 A Primer on Catching Exceptions
Avoiding these mistakes rather than coding handlers in them.
We print the stack trace of by using the printStackTrace method of the Throwable class. Throwable is the superclass of the Exception class.
5.7.3 Using Reflection to Analyze the Capabilities of Classes
The 3 classes Field, Method, and Constructor in the java.lang.reflect package describe the fields, methods and constructors of a class, respectively. All of them have a getName method that returns the name of the item; a getModifiers method that returns an integer, with various bits turned on and off, that describes the modifiers used such as public and static. You can then use static methods (like isPublic, isPrivate, etc.) in the Modifier class (in the java.lang.reflect package) to analyze the integer that getModifiers returns. The Field class has a method getType that returns an object of Class type, that describes the field type.
The getFields, getMethods, and getConstructors methods of the Class class return arrays of the public fields, methods and constructors that the class supports. This includes public members of superclasses. The getDeclaredFields, getDeclaredMethods, and getDeclaredConstructors methods of the Class class return arrays consisting of fields, methods and constructors that are declared in the class.(without members of superclasses)
5.7.4 Using Reflection to Analyze Objects at Runtime
The key method to achieve contents of the fields is the get method in the Field class. If f is an object of type Field and obj is an object of the class of which f is a field, then f.get(obj) returns an object whose value is the current value of the field obj.
You can only use get method to get the values of accessible field.
The default behaviour of the reflection mechanism is to respect Java access control. However, if a Java program is not controlled by a security manager that disallows it, you can override access control. To do this, invoke the setAccessible method on a Field, Method, or Constructor object.
The setAccessible method is a method of the AccessibleObject class.
/*
whereby
where·by
S3 /weəˈbaɪ US wer-/ adv formal
by means of which or according to which
*/
The reflection mechanism automatically wraps the field value into the appropriate wrapper class.
5.7.5 Using Reflection to Write Generic Array Code
The Array class in the java.lang.reflect package allows you to create arrays dynamically. For example, this is used in the implementation of the copyOf method in the Array class.
Sample[] s = new Sample[100];
...
// s is full
s = Arrays.copyOf(s, 2 * s.length);
/*
promising
prom·is·ing /ˈprɔmɪsɪŋ US ˈprɑː-/ adj
showing signs of being successful or good in the future
*/
An array of objects cannot be cast to an array of other types because an array that started its life as an . The virtual machine would generate a ClassCastException at runtime.
To write this kind of generic array code, we need to make a new array of the same type as the original way. The key is the newInstance method of the Array class that constructs a new array. You must supply the type for the entries and the desired length as parameters to this method. We obtain the length by calling Arrays.getLength(s). To get the component of type of the new array:
- Get the class object of s
- Confirm that it is indeed an array
- Use the getComponentType method of Class class to find the right type for the array.(only for class objects that represent arrays)
public static Object goodCopyOf(Object s, int newLength)
{
Class cl = s.getClass();
if (!cl.isArray()) return null;
Class componentType = cl.getComponentType();
int length = Array.getLength(s);
Object newArray = Array.newInstance(componentType, newLength);
System.arraycopy(s, 0, newArray, 0, Math.min(length, newLength));
}
This kind of method can be used to grow arrays of any type, not just arrays of objects.
5.7.6 Invoking Arbitrary Methods
The reflection mechanism allows you to call arbitrary methods.
The Method class has an invoke method that lets you call the method that is wrapped in the current Method object. The signature for it is
Object invoke(Object obj, Object … args)
For a static method, you can set the implicit parameter to null.
If the return type is a primitive type, the invoke method will return the wrapper type instead.
How to obtain a Method object?
- Call the getDeclaredMethods and search through the returned array of Method objects until you find the method you want.
- Or, you can call the getMethod method of the Class class. You must supply the parameter types of the desired method. The signature for getMethod is
Method getMethod(String name, Class … parameterTypes)
5.8 Design Hints for Inheritance
- Place common operations and fields in the superclass.
- Don't use protected fields.
- Use inheritance to model the “is-a”relationship.
- Don't use inheritance unless all inherited methods make sense.
- Don't change the expected behaviour when you override a method.
- Use polymorphism, not type information.
Whenever you find the code of the form
if (x is of type 1)
action1(x);
else if (x is of type 2)
action2(x);
7. Don't overuse reflection.