Chapter 3 Extending Classes

1) The collection of methods and fields that are accessible from outside a class, together with the description of how those members are expected to behave, is often referred to as the class's contract.

Class extension provides two forms of inheritance:

a) inheritance of contract or type, whereby the subclass acquires the type of the superclass and so can be used polymorphically wherever the superclass could be used;

b) inheritance of implementation, whereby the subclass acquires the implementation of the superclass in terms of its accessible fields and methods.

 

2) Variables of type Object can refere to any object, whether it is a class instance or an array.

public class Attr {
	private final String name;
	private Object value = null;
	public Attr(String name){
		this.name = name;
	}
	public Attr(String name, Object value){
		this(name);
		this.value = value;
	}
	public String toString(){
		return name + "," + (value == null ? "null" : value.toString());
	}
	
	public static void main(String[] args){
		Attr tmp = new Attr("Red");
		System.out.println(tmp);
	}
}

The name field of Attr is declared as final, so it is neither be initialized as:

private final String name = "black";

or be initialized in the constructor, and once it is initialized, it is immutable.

 

3) The extended class’s constructors must delegate construction of the inherited state by either implicitly  or explicitly invoking a superclass constructor.

  If you do not invoke a superclass constructor or one of your own constructors as your constructor's first executable statement, the superclass's no-arg constructor is automatically invoked before any statements of the new constructor are executed. That is, your constructor is treated as if

super();

were its first statement. If the superclass doesn't have a no-arg constructor, you must explicitly invoke another constructor.

Example 1:

class ClassA {
	ClassA(){
		System.out.println("A 1");
	}
}

public class ClassB extends ClassA{
	private int c;
	ClassB(){
		this.c = 0;
		System.out.println("B 1");
	}
	
	ClassB(int c) {
		this.c = c;
		System.out.println("B 2");
	}
	
	public static void main(String[] args) {
		ClassB b1 = new ClassB();
		System.out.println("------------------");
		ClassB b2 = new ClassB(0);
	}
}

Output 1:

A 1
B 1
------------------
A 1
B 2

Example 2:

class SuperClass{
	SuperClass(int a){System.out.println("SuperClass");}
}

public class SubClass extends SuperClass{
	SubClass(int a){
		//super(a); //compile error: must explicitly invoke the SuperClass's constructor, 
		          //because SuperClass has no default constructor
		System.out.println("SubClass");
	}
}

4) Constructor Order:

  1. Invoke a superclass's constructor.
  2. Initialize the fields using their initializers and any initialization blocks.
  3. Execute the body of the constructor.

Example 1:

public class Tmp {
	private int a = 100; //2: initializer
	{
		System.out.println("Initialization Block"); //2: initialization block
	}
	Tmp(){
		// 1: Object Constructor Run
		// 2: Initializer and Initialization Block:
		System.out.println("2. a: " + a);
		a = 0; // 3: Constructor Body Run
		System.out.println("3. a: " + a); 
	}
	
	public static void main(String[] args) {
		Tmp a = new Tmp();
	}
}

Output 1:

Initialization Block
2. a: 100
3. a: 0

 

Example 2:

 

class X {
	protected int xMask = 0x00ff;
	protected int fullMask;

	public X() {
		fullMask = xMask;
	}

	public int mask(int orig) {
		return (orig & fullMask);
	}
}

class Y extends X {
	protected int yMask = 0xff00;

	public Y() {

		fullMask |= yMask;
	}
}

Execution Sequence:

QQ截圖20140220120606

 

***Your constructors should avoid invoking overridable methods that are not private, static, or final. Because if you override these methods later, when invoke subclass’s constructor, superclass’s constructor will call the overriding method instead of the overridden one, while the fields of the subclass have not been initialized yet, and crash or error may persist.

Example 3:

class A {
	A(){
		System.out.println("A Constructor");
		func(); //here will invoke B's func instead of A's
	}
	void func(){System.out.println("A Func");}
}

public class B extends A {
	B(){
		System.out.println("B Constructor");
	}
	void func(){System.out.println("B Func");}
	public static void main(String[] args) {
		B b = new B();
	}
}

 

Output 3:

A Constructor

B Func

B Constructor

 

Example 4:

package section2;

public class Order {
	private int a = 100; // Initializer
	
	// Initialization Block
	{
		System.out.println(a);
		System.out.println("Initialization Block");
		System.out.println(a);
	}
	
	Order(){}

	public static void main(String[] args) {
		Order a = new Order();
	}
}

 

Output 4:

100
Initialization Block
100

  According to the previous example, the initializer may be executed earlier than the initialization block.

 

5) Overriding

  • Overloading a method is what you have already learned: providing more than one method with the same name but with different signatures to distinguish them.
  • Overriding a method means replacing the superclass's implementation of a method with one of your own. The signatures must be identicalbut the return type can vary in a particular way, as discussed below.

1. The return type of an overriding method is allowed to vary in a specific way: If the return type is a reference type then the overriding method can declare a return type that is a subtype of that declared by the superclass method.

2. A subclass can change the access of a superclass's methods, but only to provide more access. A method declared protected in the superclass can be redeclared protected (the usual thing to do) or declared public, but it cannot be declared private or have package access. Making a method less accessible than it was in a superclass would violate the contract of the superclass, because an instance of the subclass would not be usable in place of a superclass instance.

3. The overriding method is also allowed to change other method modifiers. The synchronized, native, and strictfp modifiers can be freely varied because they are implementation concerns, as are any annotations.

4. The overriding method can be final but obviously the method it is overriding CAN NOT.

5. An instance method cannot have the same signature as an inherited static method, and vice versa.

6. The overriding method can, however, be made abstract, even though the superclass method was not.

7. A subclass can change whether a parameter in an overriding method is final(adding or removing final); a final modifier for a parameter is not part of the method signatureit is an implementation detail.

8. The throws clause of an overriding method can have fewer types listed than the method in the superclass, or more specific types, or both. The overriding method can even have no tHRows clause, which means that it results in no checked exceptions.

Example 1:

class A{
	public void print(){System.out.println("I'm A!");}
}
class B extends A{
	public void print(){System.out.println("I'm B!");}
}

class SuperClass{
	A getObject(){return new A();}
}

class SubClass extends SuperClass{
	B getObject(){return new B();}
}

public class Overriding {

	public static void main(String[] args) {
		SuperClass sc = new SuperClass();
		SuperClass sb = new SubClass();
		
		A a = sc.getObject();
		A b = sb.getObject(); // note that the return object is of type A, 
							  // but refers to a object of type B
		
		a.print();		
		b.print();	
	}
}

Output 1:

I'm A!
I'm B!

 

Example 2:

class ClassA{
	public void print(){
		System.out.println("I'm A!");
	}
}

class ClassB extends ClassA{
	public final void print(){ // the overriding method can be final,
							   //but obviously the method it is overriding CAN NOT
		System.out.println("I'm B!");
	}
}

public class FinalInOverriding {
	public static void main(String[] args) {
		ClassA a = new ClassA();
		ClassA b = new ClassB();
		a.print();
		b.print();
	}
}

Output 2:

I'm A!
I'm B!

 

Example 3:

class X{
	public void print() {
		System.out.println("I'm X!");
	}
}

abstract class Y extends X { //class Y have to be abstract class to have abstract method
	public abstract void print();
	public void description(){ // abstract class can have concrete method
		System.out.println("I'm Y!");
	}
}

class C extends Y{
	public void print(){
		System.out.println("I'm C!");
	}
}

public class AbstractClass {
	public static void main(String[] args) {
		X a = new C();
		a.print();
	}
}

Output 3:

I'm C!

 

Example 4:

class SuperA extends Exception{}
class SubA extends SuperA{}

class SuperB extends Exception{}
class SubB extends SuperB{}

class SuperC extends Exception{}
class SubC extends SuperC{}

class Parent{
	void test() throws SuperA, SuperB, SuperC{}
}

class Child {
	void test() throws SubA{} // the overriding method could have less throws than the super one,
	                          // and if it has, they should be the subtypes of the overridden one's
}

public class ThrowOverriding {
	
}

6)

When you invoke a method through an object reference, the actual class of the object governs which implementation is used.

When you access a field, the declared type of the reference is used.

Example 1:

class S{
	int a = 100;
	void print(){
		System.out.println("S: " + a);
	}
}

class T extends S{
	int a = 1000;
	void print(){
		System.out.println("T: " + a);
	}
}

public class OverridenField {
	public static void main(String[] args) {
		T b = new T();
		S a = b;
		a.print();
		b.print();
		
		System.out.println("a: " + a.a);
		System.out.println("b: " + b.a);
	}
}

Output 1:

T: 1000
T: 1000
a: 100
b: 1000

 

Note that:

  A method can be overridden only if it is accessible. If the method is not accessible then it is not inherited, and if it is not inherited it can't be overridden. For example, a private method is not accessible outside its own class. If a subclass defines a method that coincidentally has the same signature and return type as the superclass's private method, they are completely unrelatedthe subclass method does not override the superclass's private method.

Example 2:

class P {
	private	void print(){
		System.out.println("I'm P!");
	}
	public void say(){
		this.print();
	}
	public void speak(){
		this.print();
	}
}

class PC extends P {
	private void print() {
		System.out.println("I'm C!");
	}
	public void speak(){
		this.print();
	}
}

public class AccessAndInherit {
	public static void main(String[] args) {
		P a = new PC();
		a.say();
		a.speak();
	}
}

Output 2:

I'm P!
I'm C!

 

  Using super is the only case in which the type of the reference governs selection of the method implementation to be used. An invocation of super.method always uses the implementation of method the superclass defines (or inherits).

Example 3:

class Base {
	String name(){return "Base";}
}

class More extends Base{
	String name(){return "More";}
	void print(){
		Base sref = (Base)this; // convert this(reference) to a Base reference
		System.out.println(this.name());
		System.out.println(sref.name());
		System.out.println(super.name());
	}
}

public class SuperTest {
	public static void main(String[] args) {
		new More().print();		
	}
}

Output 3:

More
More
Base

 

6) You can test the class of an object by using the instanceof operator, which evaluates to true if the expression on its left is a reference type that is assignment compatible with the type name on its right, and false otherwise. Because null is not an instance of any type instanceof for null always returns false.

Example:

public class InstanceOfTest {
	public static void main(String[] args) {
		A a = new A();
		A b = new B();
		B c = new B();
		if(a instanceof A) System.out.println("a is instance of A");
		else System.out.println("a is not instance of A");
		if(a instanceof B) System.out.println("a is instance of B");
		else System.out.println("a is not instance of B");
		if(b instanceof A) System.out.println("b is instance of A");
		else System.out.println("b is not instance of A");
		if(b instanceof B) System.out.println("b is instance of B");
		else System.out.println("b is not instance of B");
		if(c instanceof A) System.out.println("c is instance of A");
		else System.out.println("c is not instance of A");
		if(c instanceof B) System.out.println("c is instance of B");
		else System.out.println("c is not instance of B");
	}

}

Output:

a is instance of A
a is not instance of B
b is instance of A
b is instance of B
c is instance of A
c is instance of B

 

7) The real meaning of protected:

  Beyond being accessible within the class itself and to code within the same package, a protected member can also be accessed from a class through object references that are of at least the same type as the classthat is, references of the class's type or one its subtypes.

Example:

package section5;

public class SuperClass{
	protected int value;
	public SuperClass() {
		value = 0;
	}
	public void print(){
		System.out.println("Value: " + value);
	}
}

package subA;

import section5.SuperClass;

public class SubClassA extends SuperClass {
	public void changeValue(SubClassA subRef) {
		subRef.value = 100;
	}
}

package subB;

import section5.SuperClass;

public class SubClassB extends SuperClass {
	public void changeValue(SuperClass superRef) {
		superRef.value = 1000; // compile error, the field is unaccessible
	}
}

package section5;
import subA.SubClassA;
import subB.SubClassB;

public class Protected {
	public static void main(String[] args) {
		SuperClass a1 = new SubClassA();
		SubClassA  a2 = new SubClassA();
		SuperClass b1 = new SubClassB();
		SubClassB  b2 = new SubClassB();

		a2.changeValue(a2);
		a1.print();
		a2.print();
		
		b2.changeValue(a1);
		b2.changeValue(a2);
		b2.changeValue(b1);
		b2.changeValue(b2);
		a1.print();
		a2.print();
		b1.print();
		b2.print();
	}
}

  The reasoning behind the restriction is this: Each subclass inherits the contract of the superclass and expands that contract in some way. Suppose that one subclass(ClassA), as part of its expanded contract, places constraints on the values of protected members of the superclass. If a different subclass(ClassB) could access the protected members of objects of the first subclass(ClassA) then it could manipulate them in a way that would break the first subclass's(ClassA) contractand this should not be permissible. But if ClassA and ClassB in the same package, then ClassB could access ClassA’s protected members, because they are in the same package!

 

8) Using abstract classes, you can declare classes that define only part of an implementation, leaving extended classes to provide specific implementation of some or all of the methods. Such a class is declared abstract, and each method not implemented in the class is also marked abstract. Any class with any abstract methods must be declared abstract, But abstract class could have no abstract methods.

Example 1:

abstract class A { // abstract class could have no abstract methods
	void print() {
		System.out.println("I'm A!");
	}
}

class B extends A {
	void print() {
		System.out.println("I'm B!");
	}
}

public class AbstrackTest {
	public static void main(String[] args) {
		A a = new B();
		a.print();
	}
}

Output 1:

I'm B!

 

Example 2:

class AClass {
	//The abstract method print in type AClass can only be defined by an abstract class
	abstract void print(); 
}

  Any class can override methods from its superclass to declare them abstract, turning a concrete method into an abstract one at that point in the type tree. You cannot create an object of an abstract class because there would be no valid implementation for some methods that might well be invoked.

Example 3:

class SuperClass {
	void print(){
		System.out.println("I'm A!");
	}
}

abstract class SubClass extends SuperClass {
	abstract void print();
}

class Tmp extends SubClass {
	void print(){
		System.out.println("I'm Tmp!");
	}
}

public class TurnToAbstract {
	public static void main(String[] args) {
		SuperClass a = new Tmp();
		a.print();
	}
}

Output 3:

I'm Tmp!

 

9) The Object class defines a number of methods that are inherited by all objects. These methods fall into two categories: general utility methods and methods that support threads.

public boolean equals(Object obj)

  Compares the receiving object and the object referenced by obj for equality, returning true if they have the same value and false if they don't. If you want to determine whether two references refer to the same object, you can compare them using == and !=. The equals method is concerned with value equality. The default implementation of equals in Object assumes that an object is equal only to itself, by testing if this == obj.

public int hashCode()

  Returns a hash code for this object. Each object has a hash code for use in hashtables. The default implementation returns a value that is usually different for different objects.

public String toString()

  Returns a string representation of the object. The toString method is implicitly invoked whenever an object reference is used within a string concatenation expression as an operand of the + operator. The Object version of toString constructs a string containing the class name of the object, an @ character, and a hexadecimal representation of the instance's hash code.

 

Example:

public class ObjectTest {
	int value;
	ObjectTest(int value){
		this.value = value;
	}
	void toStringTest() {
		System.out.println("toString: " + this.toString());
	}
	
	public boolean equals(ObjectTest obj) { 
		return this.value == obj.value; 
	}
	
	public int hashCode() {
		return this.value;
	}
	
	public static void main(String[] args) {
		ObjectTest a = new ObjectTest(0);		
		ObjectTest b = new ObjectTest(0);
		ObjectTest c = a;
		
		a.toStringTest();
		b.toStringTest();
		
		if(a.equals(b)) System.out.println("Object a equals Object b!");
		else System.out.println("Object a does not equal Object b!");
		
		if(a == c) System.out.println("Object reference a is identical to Object reference c!");
		else System.out.println("Object reference a is not identical to Object reference c!");
	}
}

Output:

toString: section8.ObjectTest@0
toString: section8.ObjectTest@0
Object a equals Object b!
Object reference a is identical to Object reference c!

 

  Both the hashCode and equals methods should be overridden if you want to provide a notion of equality different from the default implementation provided in the Object class. The default is that any two different objects are not equal and their hash codes are usually distinct.

 

10) There are three important factors in writing a clone method:

  • The empty Cloneable interface, which you must implement to provide a clone method that can be used to clone an object.
  • The clone method implemented by the Object class, which performs a simple clone by copying all fields of the original object to the new object. This method works for many classes but may need to be supplemented by an overriding method.
  • The CloneNotSupportedException, which can be used to signal that a class's clone method shouldn't have been invoked.

A given class can have one of four different attitudes toward clone:

  • Support clone. Such a class implements Cloneable and declares its clone method to throw no exceptions.

  • Conditionally support clone. Such a class might be a collection class that can be cloned in principle but cannot successfully be cloned unless its contents can be cloned. This kind of class will implement Cloneable, but will let its clone method pass through any CloneNotSupportedException it may receive from other objects it tries to clone. Or a class may have the ability to be cloned itself but not require that all subclasses also have the ability to be cloned.
  • Allow subclasses to support clone but don't publicly support it. Such a class doesn't implement Cloneable, but if the default implementation of clone isn't correct, the class provides a protected clone implementation that clones its fields correctly.
  • Forbid clone. Such a class does not implement Cloneable and provides a clone method that always throws CloneNotSupportedException.

Example 1:

public class Test implements Cloneable {
	private int value;
	Test(int value) {
		this.value = value;
	}
	
	public Test clone() throws CloneNotSupportedException {
		Test cpy = (Test)super.clone();
		return cpy;
	}
	
	public void print(){
		System.out.println("My value: " + value);
	}
	public static void main(String[] args) {
		Test a = new Test(100);
		Test b = null;
		try{
			b = a.clone();
		} catch(CloneNotSupportedException e) {
			System.out.println(e);
		}
		
		a.print();
		b.print();
	}

}

Output 1:

My value: 100
My value: 100

 

  The clone method in Object has a throwsCloneNotSupportedException declaration. This means a class can declare that it can be cloned, but a subclass can decide that it can't be cloned. Such a subclass would implement the Cloneable interface because it extends a class that does so, but the subclass could not, in fact, be cloned. The extended class would make this known by overriding clone to always throw CloneNotSupportedException and documenting that it does so. Be careful -- this means that you cannot determine whether a class can be cloned by a run time check to see whether the class implements Cloneable. Some classes that can't be cloned will be forced to signal this condition by throwing an exception.

 

Example 2:

class Key implements Cloneable {
	private int key;
	Key(int key) {
		this.key = key;
	}
	public String toString(){return new Integer(key).toString();}
	
	public void setKey(int key){ this.key = key; }
	public int getKey(){return this.key;}
	
	public Key clone() throws CloneNotSupportedException {
		Key cpy = (Key)super.clone();
		cpy.key = this.key;
		return cpy;
	}
}

class Shallow implements Cloneable{
	int value;
	Key key;
	Shallow(int value, Key key) {
		this.value = value;
		this.key = new Key(key.getKey());
	}
	
	public Shallow clone() throws CloneNotSupportedException {
		Shallow cpy = (Shallow)super.clone();
		return cpy;
	}
	
	void setKey(Key key) {
		this.key.setKey(key.getKey());
	}
	
	void print() {
		System.out.println("Shallow: " + value + ", " + key);
	}
}

class Deep implements Cloneable{
	int value;
	Key key;
	Deep(int value, Key key) {
		this.value = value;
		this.key = new Key(key.getKey());
	}
	
	public Deep clone() throws CloneNotSupportedException {
		Deep cpy = (Deep)super.clone();
		cpy.value = value;
		cpy.key = key.clone();
		return cpy;
	}
	
	void setKey(Key key) {
		this.key.setKey(key.getKey());
	}
	
	void print() {
		System.out.println("Deep: " + value + ", " + key);
	}
}

public class Cloning {

	public static void main(String[] args) {
		Shallow a = new Shallow(0, new Key(0));
		Shallow acpy = null;
		try{
			acpy = a.clone();
		} catch(CloneNotSupportedException e) {
			System.out.println(e);
		}
		a.print();
		if(acpy != null) acpy.print();
		
		a.setKey(new Key(1)); // a and acpy shares the key object reference
		a.print();
		if(acpy != null) acpy.print();
		
		Deep b = new Deep(0, new Key(0));
		Deep bcpy = null;
		try{
			bcpy = b.clone();
		} catch(CloneNotSupportedException e) {
			System.out.println(e);
		}
		b.print();
		bcpy.print();
		
		b.setKey(new Key(1));
		b.print();
		if(bcpy != null) bcpy.print();
	}
}

Output 2:

Shallow: 0, 0
Shallow: 0, 0
Shallow: 0, 1
Shallow: 0, 1
Deep: 0, 0
Deep: 0, 0
Deep: 0, 1
Deep: 0, 0

 

  Cloning is an alternative form of construction but is not recognized as construction by the system. This means that you have to be wary of using blank finals that can be set only in constructors. If the value of the final field should be a copy of the value in the object being cloned, then there is no problem since Object.clone will achieve this. If copying the value is not appropriate for the field then it cannot be declared final.

 

  You can declare that all subclasses of a class must support clone properly by overriding your class's clone method with one that drops the declaration of CloneNotSupportedException. Subclasses implementing the clone method cannot throw CloneNotSupportedException, because methods in a subclass cannot add an exception to a method. In the same way, if your class makes clone public, all extended classes must also have public clone methods, because a subclass cannot make a method less visible than it was in its superclass.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章