Think in Java第四版 讀書筆記8第14章 類型信息(RTTI與反射)

Java如何在運行時識別對象和類的信息?
1.RTTI(Run-time type information) 它假定我們在編譯時已經知道了所有類型
2.反射 它允許我們在運行時發現和使用類的信息

14.1 爲什麼需要RTTI

答:爲了知道對象的實際存儲的類型信息(比如父類有n個子類,使用父類引用指向子類對象時,要知道是哪個具體子類對象)
例子

abstract class Shape {
	void draw() {
		System.out.println(this + ".draw()");//此處this其實是this.toString()
	}

	abstract public String toString();//強制子類實現  
}

class Circle extends Shape {
	public String toString() {
		return "Circle";
	}
	
	public void test(){};
}

class Square extends Shape {
	public String toString() {
		return "Square";
	}
	public void test(){};
}

class Triangle extends Shape {
	public String toString() {
		return "Triangle";
	}
	public void test(){};
}

public class Shapes {
	public static void main(String[] args) {
		
		List<Shape> shapeList = Arrays.asList(new Circle(), new Square(),
				new Triangle());//發生向上轉型(父類引用指向子類對象) //丟失了子類特徵 如果子類有父類沒有的特殊方法 無法調用,比如test方法
		//但是具有多態特性 即子類覆蓋了父類方法,使用父類引用調用時 實際會調用子類覆蓋後的方法
		for (Shape shape : shapeList){//取出數組時 只知道元素是Shape類型(編譯時 泛型可以識別) 而不知道具體類型
			shape.draw();
			//shape.test();//compile error,can't find this method
		}
			
		
	}
} /*
 * Output: Circle.draw() Square.draw() Triangle.draw()
 */// :~
//通常 我們希望對象儘可能少的瞭解對象的類型 而只是與對象家族中的一個通用表示打交道(基類),這樣是代碼更易寫易讀易維護
//上面這句話就是父類引用指向子類對象
//但是存在這樣一種情況 比如以上面的這樣例子爲例,我們在數組裏面存了一堆數據,然後想讓數組的元素旋轉 我們知道circle旋轉是無效的,那麼我們如何避開circle 調用其他shape的對象進行旋轉呢?
//這個時候就要了解對象的確切類型 而不是隻知道它是一個shape RTTI就是起到這個作用

14.2 Class對象

//類的類型信息在運行時的表示:
//類的Class對象保存了類型信息

//Java類只有在需要的時候纔會被加載

class Candy {
  static { System.out.println("Loading Candy"); }
}

class Gum {
  static { System.out.println("Loading Gum"); }
}

class Cookie {
  static { System.out.println("Loading Cookie"); }
}

public class SweetShop extends Object{
  public static void main(String[] args) {	
    System.out.println("inside main");
    new Candy();
    System.out.println("After creating Candy");
    try {
      Class.forName("typeinfo.Gum");//可以根據類名獲取相應類的引用(類加載器的作用即是獲取類的引用) 
      //注意和書上有包名的不同 否則報錯 找不到類Gum
      //Class.forName這裏的作用是獲取對應類的引用 他還有一個副作用:如果類沒有被加載,則會先加載對應的類
    } catch(ClassNotFoundException e) {
      System.out.println("Couldn't find Gum");
    }
    System.out.println("After Class.forName(\"typeinfo.Gum\")");
    new Cookie();
    System.out.println("After creating Cookie");
  }
} /* Output:
inside main
Loading Candy
After creating Candy
Loading Gum
After Class.forName("Gum")
Loading Cookie
After creating Cookie
*///:~

Object的getClass方法
    /**
     * Returns the runtime class of this {@code Object}. The returned
     * {@code Class} object is the object that is locked by {@code
     * static synchronized} methods of the represented class.
     *
     * <p><b>The actual result type is {@code Class<? extends |X|>}
     * where {@code |X|} is the erasure of the static type of the
     * expression on which {@code getClass} is called.</b> For
     * example, no cast is required in this code fragment:</p>
     *
     * <p>
     * {@code Number n = 0;                             }<br>
     * {@code Class<? extends Number> c = n.getClass(); }
     * </p>
     *
     * @return The {@code Class} object that represents the runtime
     *         class of this object.
     * @jls 15.8.2 Class Literals
     */
    public final native Class<?> getClass();

這是一個native方法,要想獲得運行時使用的具體類型信息,除了使用Class.forName(),如果有具體的對象,則還可以通過具體對象.getClass來獲取運行時信息(實際類型)

Class的常用方法
class方法1 isInterface:判斷當前class對象是不是接口類型
class方法2 getSimpleName:獲取當前class對象的類名(簡寫)
class方法3 getCanonicalName:獲取當前class對象的正規類名(詳寫)
class方法4 forName:獲取class引用
class方法5 getInterfaces:獲取class實現的所有接口的class信息
class方法6 getSuperclass:獲取當前class的父類class
可以通過這些方法在運行時獲知一個類的完整類結構(父類 實現接口 父類實現的接口 父類的父類等等)

class常用方示例

interface HasBatteries {}
interface Waterproof {}
interface Shoots {}

class Toy {
  //註釋默認構造方法來看調用newInstance時是否發生NoSuchMethodError
  Toy() {}
  Toy(int i) {}
}

class FancyToy extends Toy
implements HasBatteries, Waterproof, Shoots {
  FancyToy() { super(1); }
}

public class ToyTest {
  static void printInfo(Class cc) {
    System.out.println("Class name: " + cc.getName() +
      " is interface? [" + cc.isInterface() + "]");//class方法1 isInterface:判斷當前class對象是不是接口類型
    System.out.println("Simple name: " + cc.getSimpleName());//class方法2 getSimpleName:獲取當前class對象的類名(簡寫)
    System.out.println("Canonical name : " + cc.getCanonicalName());//class方法3 getCanonicalName:獲取當前class對象的正規類名(詳寫)
  }
  public static void main(String[] args) {
    Class c = null;
    try {
      c = Class.forName("typeinfo.toys.FancyToy");//class方法4 forName:獲取class引用
    } catch(ClassNotFoundException e) {
      System.out.println("Can't find FancyToy");//沒有找到FancyToy則退出
      System.exit(1);
    }
    printInfo(c);//打印FancyToy的類型信息	
    for(Class face : c.getInterfaces())//class方法5 getInterfaces:獲取class實現的所有接口的class信息
      printInfo(face);//打印各個接口的類型信息	
    Class up = c.getSuperclass();//class方法6 getSuperclass:獲取當前class的父類
    Object obj = null;
    try {
      // Requires default constructor:
      obj = up.newInstance();//必須有無參構造方法  如果沒有無參構造方法 則會發生InstantiationException
    } catch(InstantiationException e) {
      System.out.println("Cannot instantiate");
      System.exit(1);
    } catch(IllegalAccessException e) {//如果無參構造方法爲private 則會拋出IllegalAccessException
      System.out.println("Cannot access");
      System.exit(1);
    }
    printInfo(obj.getClass());//打印父類的class信息
  }
} /* Output:
Class name: typeinfo.toys.FancyToy is interface? [false]
Simple name: FancyToy
Canonical name : typeinfo.toys.FancyToy
Class name: typeinfo.toys.HasBatteries is interface? [true]
Simple name: HasBatteries
Canonical name : typeinfo.toys.HasBatteries
Class name: typeinfo.toys.Waterproof is interface? [true]
Simple name: Waterproof
Canonical name : typeinfo.toys.Waterproof
Class name: typeinfo.toys.Shoots is interface? [true]
Simple name: Shoots
Canonical name : typeinfo.toys.Shoots
Class name: typeinfo.toys.Toy is interface? [false]
Simple name: Toy
Canonical name : typeinfo.toys.Toy
*///:~

14.2.1 類字面常量

類字面常量意思是類名.class
比如
c = Class.forName(“typeinfo.toys.FancyToy”);
使用類字面常量則寫做
c = FancyToy.class;
這樣寫不僅寫法簡單 不需要使用包名+類名,不用擔心包名拼寫錯誤,還不需要進行ClassNotFoundException的檢測(將運行時異常提前到編譯時檢測)
字面常量除了可以使用在普通類,接口類型,數組類型上,也可以使用在基本數據類型
使用forName和類字面常量的區別在於類字面常量不會進行Class對象 而forName會(forName的副作用)
爲了使用類,需要做一些準備工作:
1.加載 類加載器查找字節碼 根據字節碼創建Class對象(將java源文件編譯爲.class字節碼文件)
2.鏈接 驗證類中的字節碼 爲靜態域分配存儲空間,如果有必要 解析這個類創建的對其它類的所有引用(將Java class文件合併到JVM的運行狀態,包括1驗證:確保 Java 類的二進制表示在結構上是合理的 2準備:創建靜態域並賦值 3 解析:確保當前類引用的其他類被正確地找到,該過程可能會觸發其他類被加載)
3.初始化 執行靜態初始化器和靜態初始化塊(當 Java類第一次被真正使用的時候,JVM 會負責初始化該類。包括:1 執行靜態代碼塊 2 初始化靜態域)
初始化例子

//初始化儘可能是惰性的(初始化時間儘可能延後)
class Initable {
	static final int staticFinal = 47;
	static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
	public Initable() {
		System.out.println("Initable 構造函數");
	}
	static {
		System.out.println("Initializing Initable");
	}
}

class Initable2 {
	static int staticNonFinal = 147;
	static {
		System.out.println("Initializing Initable2");
	}
}

class Initable3 {
	static int staticNonFinal = 74;
	static {
		System.out.println("Initializing Initable3");
	}
}

public class ClassInitialization {
	public static Random rand = new Random(47);

	public static void main(String[] args) throws Exception {
		Class initable = Initable.class;// .class 沒有觸發初始化(靜態代碼塊沒有調用)
		System.out.println("After creating Initable ref");
		System.out.println(Initable.staticFinal);//它是一個編譯期常量,初始化不需要Initable支持,只觸發靜態變量初始化
		System.out.println(Initable.staticFinal2);//它不是一個編譯期常量,初始化該變量需要Initable也初始化,所以觸發靜態代碼塊以及靜態變量初始化
		System.out.println(Initable2.staticNonFinal);//對比staticFinal和staticNonFinal 他們的區別在於一個final,
		//如果static域不是final的,那麼對它訪問時總是被要求在它被讀取之前(之前是重點)需要進行鏈接(分配內存空間)和初始化(初始化存儲空間 賦初值),因此先觸發了靜態代碼塊
		//然後是變量初始化
		Class initable3 = Class.forName("typeinfo.Initable3");//forName會觸發初始化(調用靜態代碼塊)
		System.out.println("After creating Initable3 ref");
		System.out.println(Initable3.staticNonFinal);
	}
} /*
 * Output: 
After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74
 */// :~

14.2.2 泛化的class的引用

public class GenericClassReferences {
  public static void main(String[] args) {
    Class intClass = int.class;//不使用泛型可以賦值任意類型的class
    Class<Integer> genericIntClass = int.class;//使用泛型 只能賦值爲Integer類型的class
    genericIntClass = Integer.class; // Same thing
    intClass = Double.class;
    // genericIntClass = double.class; // Illegal
    
    //int.class和Integer.class還是稍稍有區別的
    Class<Integer> a = int.class;
    Class<Integer> b = Integer.TYPE;
    Class<Integer> c = Integer.class;
    System.out.println(System.identityHashCode(a));
    System.out.println(System.identityHashCode(b));
    System.out.println(System.identityHashCode(c));
    System.out.println(int.class);
    System.out.println(Integer.TYPE);
    System.out.println(Integer.class);
  }
} ///:~
/**
 * 輸出:(數字在不同機器也許不同但是int.class和Integer.TYPE的輸出始終一致)
366712642
366712642
1829164700
int
int
class java.lang.Integer
 */

如上述例子所述
泛型加在Class上可以在編譯期強制要求Class引用指向正確類型的對象,而不加泛型則可以將引用指向任意class類型的對象
要規範class類型 但是也不是嚴格要求是某一個固定類型,如何來做呢?(稍微放鬆的class泛型)

public class WildcardClassReferences {
	public static void main(String[] args) {
		//Class<Number> genericNumberClass = int.class;
		//Class<Number> genericNumberClass1 = double.class;
		//你也許想像上面這麼做來實現放鬆泛型對class的嚴格限制 因爲Integer和Double都繼承自Number
		//但是實際是會報錯的 Integer Class和Double Class都不是Number class的子類(15章討論)
        //報錯爲 Type mismatch: cannot convert from Class<Integer> to Class<Number>
		
		//稍微放鬆泛型的顯示 Class<?>的引用可以指向任意類型的Class對象(?表示任何事物) 雖然和不使用泛型是同樣的效果,但是Class<?>明確表示此處使用類型不定的Class,而直接使用Class則可能是因爲遺漏
		//因此即使作用相同 推薦使用Class<?>
		Class<?> intClass = int.class;
		intClass = double.class;
	}
} ///:~
Class<?>太寬泛了 範圍再縮小一點呢?
public class BoundedClassReferences {
	public static void main(String[] args) {

		//Class<? extends Number>聲明的是任何是Number子類類型的(包括Number)的Class
		Class<? extends Number> bounded = Integer.class;
		bounded = double.class;
		bounded = Number.class;
		// Or anything else derived from Number.
	}
} // /:~

注意對比上一個例子 Class只是聲明一個Number類型的Class Class<? extends Number>纔是聲明一個Number以及繼承了Number的Class.
將泛型引入 僅僅是爲了將類型錯誤發現提前到編譯時。
泛型的Class在newInstance有另外的作用

class CountedInteger {
	private static long counter;
	private final long id = counter++;

	public String toString() {
		return Long.toString(id);
	}
}

public class FilledList<T> {// 定義class時包含了一個泛型T,T
							// 意爲Type,當然也可替換爲S或者L等任意字母。但我們通常還是約定俗成地使用T
	private Class<T> type;

	public FilledList(Class<T> type) {
		this.type = type;
	}

	public List<T> create(int nElements) {
		List<T> result = new ArrayList<T>();
		try {
			for (int i = 0; i < nElements; i++)
				result.add(type.newInstance());//調用輸入類型地無參構造方法來創建輸入類型的實例 如果沒有無參構造方法會報錯。
			//使用泛型的Class newInstance得到的將是具體泛型定義的類型 而不是Object類型
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return result;
	}

	public static void main(String[] args) {
		//泛型T在以下代碼中爲CountedInteger類型
		FilledList<CountedInteger> fl = new FilledList<CountedInteger>(
				CountedInteger.class);
		System.out.println(fl.create(15));//這裏會調用CountedInteger的toString方法 
	}
} /*
 * Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
 */// :~
Class<? extends XXX>的限制,在一些情況,該聲明方式有些含糊不清

 public class GenericToyTest {
	public static void main(String[] args) throws Exception {
		Class<FancyToy> ftClass = FancyToy.class;
		// 產生精確類型:
		FancyToy fancyToy = ftClass.newInstance();
		Class<? super FancyToy> up = ftClass.getSuperclass();// 產生FancyToy父類的Class類型
		// 編譯報錯:
		// Class<Toy> up2 = ftClass.getSuperclass();//不允許直接聲明爲Class<Toy>,雖然Toy是FancyToy的父類,這就是含糊不清的地方
		// 只能產生Object類型:(雖然obj確實是Toy類型的)
		Object obj = up.newInstance();//由於上面的種種原因 newInstance不知道返回的確切類型 只能返回Object類型
		System.out.println(obj instanceof Toy);//實際類型是Toy
	}
} ///:~
/**
 * output:
 * true
 * 
 */

14.2.3 新的轉型語法

class Building {}
class House extends Building {}

public class ClassCasts {
  public static void main(String[] args) {
    Building b = new House();
    Class<House> houseType = House.class;
    House h = houseType.cast(b);//將Building的實例轉化成House類型
    h = (House)b; // 與上面等價,在實際應用中 還沒有使用過cast方法,看上去cast比強制轉換(即使用括弧轉換)更麻煩,暫時不清楚其用途在哪
  }
} ///:~

14.3 類型轉換前先做檢查

已知的RTTI的形式包括
1)使用強制類型轉換 如果無法轉換則會拋出ClassCastException
2)代表對象的類型的Class對象,可以根據Class對象獲取運行時所需的信息
向下轉型和向上轉型
通常我們繪製繼承結構圖時將基類畫在上邊 子類繪製在下邊,因此父類轉換成子類稱爲向下轉型,子類轉換成父類是向上轉型。子類轉換成父類(向上轉型)是安全的,因此可以直接聲明一個父類引用但是實際賦值爲子類對象,只不過此時丟棄了子類特徵。而父類轉換成子類(向下轉型),是不安全的,必須使用強制轉換符(編譯器會檢查向下轉型是否轉型到正確的類型)

class Father {
}
class Child1 extends Father {
}
class Child2 extends Father {
}
public class Test {
	public static void main(String[] args) {
		Father father1 = new Child1();// 向上轉型
		Father father2 = new Child2();// 向上轉型
		Father father = new Father();
		Child1 child1;
		Child2 child2;
		child1 = (Child1) father;//向下轉型 轉換失敗test.Father cannot be cast to test.Child1
		child2 = (Child2) father;//向下轉型 轉換失敗test.Father cannot be cast to test.Child2
		child1 = (Child1) father1;//向下轉型 成功
		child2 = (Child2) father2;//向下轉型 成功
	}
}

3) RTTI在Java中使用的第三種形式是instanceof 該操作符用於判斷某個實例是不是某種類型的實例
比如 x instancof Dog,就是判斷x是不是Dog實例
有了這個操作符 我們在進行強制轉換是可以假設類型檢查 一避免不必要的類型轉換異常

instanceof的使用案例

package typeinfo.pets;

public class Individual implements Comparable<Individual> {//可比較和可排序的Individual //這個類比較複雜 可以到17章再看 目前先忽略
  private static long counter = 0;
  private final long id = counter++;
  private String name;
  public Individual(String name) { this.name = name; }
  // 'name' 是可選的:
  public Individual() {}
  //重寫toString方法 輸出類名+name(如果有name的話)
  @Override
  public String toString() {
    return getClass().getSimpleName() +
      (name == null ? "" : " " + name);
  }
  public long id() { return id; }
  
  //重寫equals方法  判斷對象相等的條件 1都是Individual實例 2兩個實例的id相等
  @Override
  public boolean equals(Object o) {
    return o instanceof Individual &&
      id == ((Individual)o).id;
  }
  
  //重寫hashCode方法
  @Override
  public int hashCode() {
    int result = 17;
    if(name != null)
      result = 37 * result + name.hashCode();//如果有name的case
    result = 37 * result + (int)id;//沒有name的case
    return result;
  }
  
  //Comparable接口的方法
  public int compareTo(Individual arg) {
    // 先比較Class Name:
    String first = getClass().getSimpleName();
    String argFirst = arg.getClass().getSimpleName();
    int firstCompare = first.compareTo(argFirst);
    if(firstCompare != 0)
    return firstCompare;
    //如果Class Name相等 比較name屬性
    if(name != null && arg.name != null) {
      int secondCompare = name.compareTo(arg.name);
      if(secondCompare != 0)
        return secondCompare;
    }
    //如果沒有name 或者name相等 比較id
    return (arg.id < id ? -1 : (arg.id == id ? 0 : 1));
  }
} ///:~
下面是繼承自Individual類的體系
public class Person extends Individual {
  public Person(String name) { super(name); }
} ///:~

public class Pet extends Individual {
  public Pet(String name) { super(name); }
  public Pet() { super(); }
} ///:~

public class Dog extends Pet {//繼承自Pet的Dog
  public Dog(String name) { super(name); }
  public Dog() { super(); }
} ///:~

public class Mutt extends Dog {//混種狗
  public Mutt(String name) { super(name); }
  public Mutt() { super(); }
} ///:~

public class Pug extends Dog {//哈巴狗
  public Pug(String name) { super(name); }
  public Pug() { super(); }
} ///:~

public class Cat extends Pet {//繼承自Pet的Cat
  public Cat(String name) { super(name); }
  public Cat() { super(); }
} ///:~

public class EgyptianMau extends Cat {//埃及貓
  public EgyptianMau(String name) { super(name); }
  public EgyptianMau() { super(); }
} ///:~

public class Manx extends Cat {//馬恩島貓(一種無尾家貓)
  public Manx(String name) { super(name); }
  public Manx() { super(); }
} ///:~

public class Rodent extends Pet {//齧齒動物 繼承自Pet
  public Rodent(String name) { super(name); }
  public Rodent() { super(); }
} ///:~

public class Mouse extends Rodent {//老鼠
  public Mouse(String name) { super(name); }
  public Mouse() { super(); }
} ///:~

public class Hamster extends Rodent {//倉鼠
  public Hamster(String name) { super(name); }
  public Hamster() { super(); }
} ///:~

//創建寵物的類
public abstract class PetCreator {
  private Random rand = new Random(47);
  // types方法返回一個Pet及其子類的list
  public abstract List<Class<? extends Pet>> types();//這是個抽象方法 在子類中實現 這樣就可以返回不同的Pet List
  public Pet randomPet() { // 創建一個隨機類型的Pet
    int n = rand.nextInt(types().size());
    try {
      return types().get(n).newInstance();
    } catch(InstantiationException e) {//實例化出錯 如果創建的是一個接口或者抽象類 則拋出此異常
      throw new RuntimeException(e);
    } catch(IllegalAccessException e) {//訪問受限 如果默認構造器爲private的 則會拋出該異常
      throw new RuntimeException(e);
    }
  }	
  public Pet[] createArray(int size) {//返回一個知道大小的Pet數組 其中包含隨機種類的Pet
    Pet[] result = new Pet[size];
    for(int i = 0; i < size; i++)
      result[i] = randomPet();//使用randomPet生成Pet
    return result;
  }
  public ArrayList<Pet> arrayList(int size) {//返回一個知道大小的Pet ArrayList其中包含隨機種類的Pet
    ArrayList<Pet> result = new ArrayList<Pet>();
    Collections.addAll(result, createArray(size));//複用createArray
    return result;
  }
} ///:~

實現抽象類的一個例子

public class ForNameCreator extends PetCreator {
	private static List<Class<? extends Pet>> types = new ArrayList<Class<? extends Pet>>();
	// Types that you want to be randomly created:
	private static String[] typeNames = {// 注意包名
	"typeinfo.pets.Mutt", "typeinfo.pets.Pug", "typeinfo.pets.EgyptianMau",
			"typeinfo.pets.Manx", "typeinfo.pets.Cymric", "typeinfo.pets.Rat",
			"typeinfo.pets.Mouse", "typeinfo.pets.Hamster" };

	@SuppressWarnings("unchecked")
	private static void loader() {
		try {
			for (String name : typeNames) {
				types.add((Class<? extends Pet>) Class.forName(name));
			}
		} catch (ClassNotFoundException e) {//如果傳入的string無法解析爲一個Class 則拋出異常
			throw new RuntimeException(e);
		}
	}

	static {
		loader();
	}

	public List<Class<? extends Pet>> types() {
		return types;
	}
} ///:~

利用instanceof統計各種Pet出現的次數

// Using instanceof.
package typeinfo;
import typeinfo.pets.*;
import java.util.*;

public class PetCount {
  static class PetCounter extends HashMap<String,Integer> {//PetCounter是一個HashMap
    public void count(String type) {//以寵物name作爲key 出現次數作爲value
      Integer quantity = get(type);
      if(quantity == null)
        put(type, 1);
      else
        put(type, quantity + 1);
    }
  }	
  public static void
  countPets(PetCreator creator) {
    PetCounter counter= new PetCounter();//創建一個HashMap 用於計數Pet出現次數
    for(Pet pet : creator.createArray(20)) {//遍歷創建的隨機20個Pet
      // List each individual pet:
      System.out.print(pet.getClass().getSimpleName() + " ");//輸出每一Pet的類名
      if(pet instanceof Pet)
        counter.count("Pet");
      if(pet instanceof Dog)
        counter.count("Dog");
      if(pet instanceof Mutt)
        counter.count("Mutt");
      if(pet instanceof Pug)
        counter.count("Pug");
      if(pet instanceof Cat)
        counter.count("Cat");
      if(pet instanceof Manx)
        counter.count("EgyptianMau");
      if(pet instanceof Manx)
        counter.count("Manx");
      if(pet instanceof Manx)
        counter.count("Cymric");
      if(pet instanceof Rodent)
        counter.count("Rodent");
      if(pet instanceof Rat)
        counter.count("Rat");
      if(pet instanceof Mouse)
        counter.count("Mouse");
      if(pet instanceof Hamster)
        counter.count("Hamster");
    }
    // Show the counts:
    System.out.println();
    System.out.print(counter);//調用hashmap的toString方法
  }	
  public static void main(String[] args) {
    countPets(new ForNameCreator());
  }
} /* Output:
Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric
{Pug=3, Cat=9, Hamster=1, Cymric=7, Mouse=2, Mutt=3, Rodent=5, Pet=20, Manx=7, EgyptianMau=7, Dog=6, Rat=2}
*///:~ 

例子很長 但是很簡單,很容易理解 Individual可以先不管,刪除裏面的東西都沒有關係,不影響學習instanceof的使用

14.3.1 使用類字面常量

上一個例子 如果使用類字面常量來實現

//在ForNameCreator中我們使用字符串typeinfo.pets.Mutt typeinfo.pets.Pug等來代表各種pet,然後使用Class.forName進行轉換,這樣既容易出錯,一個字符寫錯了就導致找不到類的異常 另外也沒有類字面常量看起來清楚。

public class LiteralPetCreator extends PetCreator {
	// No try block needed.
	// 所有Pet類型的List
	@SuppressWarnings("unchecked")
	public static final List<Class<? extends Pet>> allTypes = Collections
			.unmodifiableList(Arrays.asList(Pet.class, Dog.class, Cat.class,
					Rodent.class, Mutt.class, Pug.class, EgyptianMau.class,
					Manx.class, Cymric.class, Rat.class, Mouse.class,
					Hamster.class));
	// 隨機創建的Pet類型集合:
	private static final List<Class<? extends Pet>> types = allTypes.subList(
			allTypes.indexOf(Mutt.class), allTypes.size());

	//覆蓋父類types方法 因此實際使用的是這裏的types //在父類randomPet方法中會調用該方法
	public List<Class<? extends Pet>> types() {
		return types;
	}

	public static void main(String[] args) {
		System.out.println("LiteralPetCreator main");
		System.out.println(types);
	}
} /*
 * Output: [class typeinfo.pets.Mutt, class typeinfo.pets.Pug, class
 * typeinfo.pets.EgyptianMau, class typeinfo.pets.Manx, class
 * typeinfo.pets.Cymric, class typeinfo.pets.Rat, class typeinfo.pets.Mouse,
 * class typeinfo.pets.Hamster]
 */// :~
 
public class Pets {
	// 使用外觀模式包裹LiteralPetCreator實例 //關於外觀模式
	// 可以參考:https://blog.csdn.net/u011109881/article/details/82344097
	public static final PetCreator creator = new LiteralPetCreator();

	public static Pet randomPet() {
		System.out.println("randomPet");
		return creator.randomPet();
	}

	public static Pet[] createArray(int size) {
		System.out.println("createArray");
		return creator.createArray(size);
	}

	public static ArrayList<Pet> arrayList(int size) {
		System.out.println("arrayList");
		return creator.arrayList(size);
	}
} ///:~

public class PetCount2 {
	public static void main(String[] args) {
		//調用PetCount方法實際參數爲LiteralPetCreator類型
		PetCount.countPets(Pets.creator);
	}
} /* (Execute to see output) */// :~
輸出與PetCount一致
可以看出類字面常量比ForName的形式方便很多

14.3.2 動態的instanceof

在PetCount中 我們使用了
if(pet instanceof Pet)
counter.count(“Pet”);
if(pet instanceof Dog)
counter.count(“Dog”);
if(pet instanceof Mutt)
counter.count(“Mutt”);
if(pet instanceof Pug)
counter.count(“Pug”);
if(pet instanceof Cat)
counter.count(“Cat”);
if(pet instanceof Manx)
counter.count(“EgyptianMau”);
if(pet instanceof Manx)
counter.count(“Manx”);
if(pet instanceof Manx)
counter.count(“Cymric”);
if(pet instanceof Rodent)
counter.count(“Rodent”);
if(pet instanceof Rat)
counter.count(“Rat”);
if(pet instanceof Mouse)
counter.count(“Mouse”);
if(pet instanceof Hamster)
counter.count(“Hamster”);
這一長串的類型判斷,實際可以遍歷hashMap的Key,將他與pet進行比較
即pair.getKey().isInstance(pet)
如果匹配 則計數+1
實際中isInstance使用得相對較少,一般用於不知道要判斷的Class類型的時候

14.3.3 遞歸計數

14.4 註冊工廠(寫的有些亂 沒看懂書裏的意思)

爲什麼需要註冊工廠?
在之前的例子中 我們維護一個列表
LiteralPetCreator的
public static final List<Class<? extends Pet>> allTypes = Collections
.unmodifiableList(Arrays.asList(Pet.class, Dog.class, Cat.class,
Rodent.class, Mutt.class, Pug.class, EgyptianMau.class,
Manx.class, Cymric.class, Rat.class, Mouse.class,
Hamster.class));
這個列表十分死板,加入我們新建了一種Pet 就必須更新該列表 有沒有什麼方法在我們新建了一種類型是 自動更新列表呢?註冊工廠就是爲了這個目的(從後面的例子看似乎不是這樣。。。)
這是一個簡單的工廠方法的例子
public interface Factory { T create(); } ///:~
實現這個接口 可以返回自定義類型
完整的例子

/**
 * 
 * @author hjcai
 * 
 * structure:
 * Part
 *  |
 *  |----------------------------------------------------------
 *  |                                                         |
 * Filter                                                    Belt
 *  |                                                         |
 *  |----------------------------------------------           |-----------------------------
 *  |             |             |              |              |             |              |
 * FuelFilter  AirFilter    CabinAirFilter  AirFilter       FanBelt      GeneratorBelt PowerSteeringBelt
 */

class Part {
	public String toString() {
		return getClass().getSimpleName();
	}

	static List<Factory<? extends Part>> partFactories = new ArrayList<Factory<? extends Part>>();
	static {
		//靜態代碼塊 包含了所有具體子類的工廠接口
		partFactories.add(new FuelFilter.Factory());
		partFactories.add(new AirFilter.Factory());
		partFactories.add(new CabinAirFilter.Factory());
		partFactories.add(new OilFilter.Factory());
		partFactories.add(new FanBelt.Factory());
		partFactories.add(new PowerSteeringBelt.Factory());
		partFactories.add(new GeneratorBelt.Factory());
	}
	private static Random rand = new Random(47);

	// 該方法
	public static Part createRandom() {
		//以partFactories的size爲邊界值  取一個隨機數
		int n = rand.nextInt(partFactories.size());
		//取出List partFactories的第n個Factory調用其create工廠方法創建對象
		return partFactories.get(n).create();
	}
}

abstract class Filter extends Part {
}

class FuelFilter extends Filter {
	// Create a Class Factory for each specific type:
	public static class Factory implements typeinfo.factory.Factory<FuelFilter> {
		public FuelFilter create() {
			return new FuelFilter();
		}
	}
}

class AirFilter extends Filter {
	public static class Factory implements typeinfo.factory.Factory<AirFilter> {
		public AirFilter create() {
			return new AirFilter();
		}
	}
}

class CabinAirFilter extends Filter {
	public static class Factory implements
			typeinfo.factory.Factory<CabinAirFilter> {
		public CabinAirFilter create() {
			return new CabinAirFilter();
		}
	}
}

class OilFilter extends Filter {
	public static class Factory implements typeinfo.factory.Factory<OilFilter> {
		public OilFilter create() {
			return new OilFilter();
		}
	}
}

abstract class Belt extends Part {
}

class FanBelt extends Belt {
	public static class Factory implements typeinfo.factory.Factory<FanBelt> {
		public FanBelt create() {
			return new FanBelt();
		}
	}
}

class GeneratorBelt extends Belt {
	public static class Factory implements
			typeinfo.factory.Factory<GeneratorBelt> {
		public GeneratorBelt create() {
			return new GeneratorBelt();
		}
	}
}

class PowerSteeringBelt extends Belt {
	public static class Factory implements
			typeinfo.factory.Factory<PowerSteeringBelt> {
		public PowerSteeringBelt create() {
			return new PowerSteeringBelt();
		}
	}
}

public class RegisteredFactories {
	public static void main(String[] args) {
		for (int i = 0; i < 10; i++){
			//10次調用Part的靜態方法createRandom
			//System.out.println會調用Part的toString方法
			System.out.println(Part.createRandom());
		}
	}
} /*
 * Output: GeneratorBelt CabinAirFilter GeneratorBelt AirFilter
 * PowerSteeringBelt CabinAirFilter FuelFilter PowerSteeringBelt
 * PowerSteeringBelt FuelFilter
 */// :~

此處還是不明白工廠方法相對於使用類字面常量的優勢。。。。。。工廠方法還是要存在一個靜態代碼塊用於初始化所有子類工廠的List,每次新加一種類型都需在靜態代碼塊添加該類型的工廠對象

14.5 instanceof與Class的等價性

本節討論使用instanceof和使用Class對象直接比較的區別

class Base {
}

class Derived extends Base {
}

public class FamilyVsExactType {
	static void test(Object x) {
		System.out.println("Testing x of type " + x.getClass());
		System.out.println("x instanceof Base " + (x instanceof Base));
		System.out.println("x instanceof Derived " + (x instanceof Derived));
		System.out.println("Base.isInstance(x) " + Base.class.isInstance(x));
		System.out.println("Derived.isInstance(x) "
				+ Derived.class.isInstance(x));
		System.out.println("x.getClass() == Base.class "
				+ (x.getClass() == Base.class));
		System.out.println("x.getClass() == Derived.class "
				+ (x.getClass() == Derived.class));
		System.out.println("x.getClass().equals(Base.class)) "
				+ (x.getClass().equals(Base.class)));
		System.out.println("x.getClass().equals(Derived.class)) "
				+ (x.getClass().equals(Derived.class)));
		System.out.println();
		System.out.println();
	}

	public static void main(String[] args) {
		test(new Base());
		test(new Derived());
	}
} /*
 * Output:
Testing x of type class typeinfo.Base
x instanceof Base true
x instanceof Derived false
Base.isInstance(x) true
Derived.isInstance(x) false
x.getClass() == Base.class true
x.getClass() == Derived.class false
x.getClass().equals(Base.class)) true
x.getClass().equals(Derived.class)) false


Testing x of type class typeinfo.Derived
x instanceof Base true
x instanceof Derived true
Base.isInstance(x) true
Derived.isInstance(x) true
x.getClass() == Base.class false
x.getClass() == Derived.class true
x.getClass().equals(Base.class)) false
x.getClass().equals(Derived.class)) true
 */// :~

結論:
instanceof和isInstance返回結果相同,他們用於判斷一個對象實例是否是某個類型或者某個類型的子類
==與equals的返回結果相同,他們用於判斷一個對象實例是否是確切的某個類型,是子類 父類都不行 ,必須和指定的類型完全相同(此結論只在這裏適用 在String上可不適用)

14.6 反射:運行時的類型信息

反射與RTTI極其類似,他們的目的都是獲取類型信息,RTTI在編譯時就知道類型信息 而反射則發生在運行時(運行時才知道類型信息)
反射的目的之一是爲了遠程方法調用(RMI remote method invoke)
反射的基本組成:Class類+java.lang.reflect類庫 類庫包含Field Method Constructor,有了這些類以及其他類中的方法 結合class類型信息 就能在運行時創建出對象,在編譯期 反射可以什麼都不做
反射和RTTI的關鍵之處在於Class對象,他們的類型信息獲取都是從class對象獲取的。反射在編譯期無法獲取.class文件,只有運行時才能從本地或網絡獲取,RTTI則在編譯期讀取.class文件

14.6.1 類方法提取器(反射的使用案例)

我們通常可以不直接使用反射,反射是爲其他特性服務的,比如對象序列化和Android中跨進程AIDL
但是我們也可以使用反射來實現動態提取某個類的信息
以下例子在eclipse無法運行成功 因爲沒有找到生成.class文件
我是將其copy到其他路徑執行以下命令得到輸出的

javac ShowMethods.java
java ShowMethods ShowMethods

// Using reflection to show all the methods of a class,
// even if the methods are defined in the base class.
// {Args: ShowMethods}
import java.lang.reflect.*;
import java.util.regex.*;

public class ShowMethods {
	private static String usage = "usage:\n"
			+ "ShowMethods qualified.class.name\n"
			+ "To show all methods in class or:\n"
			+ "ShowMethods qualified.class.name word\n"
			+ "To search for methods involving 'word'";

	public static void main(String[] args) {
		if (args.length < 1) {
			System.out.println(usage);
			System.exit(0);
		}
		try {
			Class<?> c = Class.forName(args[0]);
			Method[] methods = c.getMethods();//get Method
			Constructor[] ctors = c.getConstructors();//get Constructor
			if (args.length == 1) {//number of parameter=1
				for (Method method : methods) {
					System.out.println(method.toString());
				}

				for (Constructor ctor : ctors) {
					System.out.println(ctor.toString());
				}

			} else {//number of parameter >1
				for (Method method : methods)
					if (method.toString().indexOf(args[1]) != -1) {
						System.out.println(method.toString());
					}
				for (Constructor ctor : ctors)
					if (ctor.toString().indexOf(args[1]) != -1) {
						System.out.println(ctor.toString());
					}
			}
		} catch (ClassNotFoundException e) {
			System.out.println("No such class: " + e);
		}
	}
} /*
 * Output: 
C:\Users\hjcai\Desktop>java ShowMethods ShowMethods
public static void ShowMethods.main(java.lang.String[])
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
public ShowMethods()
 */// :~

與書中相比 我去掉了正則表達式,如果沒有去掉正則 則輸出結果如下

C:\Users\hjcai\Desktop>java ShowMethods ShowMethods
public static void main(String[])
public final void wait() throws InterruptedException
public final void wait(long,int) throws InterruptedException
public final native void wait(long) throws InterruptedException
public boolean equals(Object)
public String toString()
public native int hashCode()
public final native Class getClass()
public final native void notify()
public final native void notifyAll()
public ShowMethods()

正則表達式去掉了"數個字符和."的字符集合
並且從輸出 我們能看到,方法的打印會打印當前類及從父類繼承的方法。

14.7 動態代理

關於代理模式 可以參考https://blog.csdn.net/u011109881/article/details/82848719
書裏也舉了一個代理模式的例子

interface Interface {
  void doSomething();
  void somethingElse(String arg);
}

class RealObject implements Interface {
  public void doSomething() { System.out.println("doSomething"); }
  public void somethingElse(String arg) {
    System.out.println("somethingElse " + arg);
  }
}	

class SimpleProxy implements Interface {//代理對象
  private Interface proxied;
  public SimpleProxy(Interface proxied) {//創建代理對象時會傳入真實對象 代理對象調用方法時實際執行的還是真實對象
    this.proxied = proxied;
  }
  public void doSomething() {
    System.out.println("SimpleProxy doSomething");
    proxied.doSomething();
  }
  public void somethingElse(String arg) {
    System.out.println("SimpleProxy somethingElse " + arg);
    proxied.somethingElse(arg);
  }
}	

class SimpleProxyDemo {
  public static void consumer(Interface iface) {
    iface.doSomething();
    iface.somethingElse("bonobo");
  }
  public static void main(String[] args) {
    consumer(new RealObject());//直接調用
    System.out.println();
    consumer(new SimpleProxy(new RealObject()));//通過代理SimpleProxy調用
  }
} /* Output:
doSomething
somethingElse bonobo

SimpleProxy doSomething
doSomething
SimpleProxy somethingElse bonobo
somethingElse bonobo
*///:~

代理模式的一個用途是限制客戶端的方法調用,使其無法調用“多餘”的方法
比如代理模式的可以統計調用的開銷 但是你不希望客戶端知道你在統計開銷,就可以使用代理,客戶端使用代理對象,服務端在真實對象的調用處進行統計,客戶端完全不知道。

動態代理的案例

class DynamicProxyHandler implements InvocationHandler {//InvocationHandler 只有一個invoke方法
	private Object proxied;//代理對象

	public DynamicProxyHandler(Object proxied) {//構造方法初始化了代理對象
		this.proxied = proxied;
	}

	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		System.out.println("**** proxy: " + proxy.getClass() + ", method: "
				+ method + ", args: " + args);
		if (args != null){//輸出參數
			for (Object arg : args){
				System.out.println("  " + arg);
			}
		}
		return method.invoke(proxied, args);
	}
}

class SimpleDynamicProxy {
	public static void consumer(Interface iface) {//靜態方法consumer 負責調用Interface的兩個方法
		iface.doSomething();
		iface.somethingElse("bonobo");
	}

	public static void main(String[] args) {
		RealObject real = new RealObject();
		consumer(real);//使用RealObject調用Interface的方法
		System.out.println();
		// 使用代理再調用一遍:
		Interface proxy = (Interface) Proxy.newProxyInstance(
				Interface.class.getClassLoader(),
				new Class[] { Interface.class }, new DynamicProxyHandler(real));//Proxy.newProxyInstance可以創建動態代理
		//Proxy.newProxyInstance 需要的參數
		//1 類加載器 可以從已加載的類獲取類加載器
		//2 希望代理類實現的接口數組(非抽象類或類)
		//3 一個實現了InvocationHandler的對象實例
 		consumer(proxy);//此處對接口的調用轉爲對代理的調用
	}
} /*
 * Output: (95% match) 
doSomething
somethingElse bonobo

**** proxy: class typeinfo.$Proxy0, method: public abstract void typeinfo.Interface.doSomething(), args: null
doSomething
**** proxy: class typeinfo.$Proxy0, method: public abstract void typeinfo.Interface.somethingElse(java.lang.String), args: [Ljava.lang.Object;@6bc7c054
  bonobo
somethingElse bonobo
 */// :~

通過動態代理管理方法的調用

// Looking for particular methods in a dynamic proxy.
import java.lang.reflect.*;

class MethodSelector implements InvocationHandler {
	private Object proxied;

	public MethodSelector(Object proxied) {
		this.proxied = proxied;
	}

	// 通過invoke方法來判斷觀察的調用,甚至可以根據方法名截斷某個方法的調用
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		if (method.getName().equals("interesting")) {// 檢測interesting方法
			System.out.println("Proxy detected the interesting method");
		}
		return method.invoke(proxied, args);
	}
}

interface SomeMethods {
	void boring1();
	void boring2();
	void interesting(String arg);
	void boring3();
}

class Implementation implements SomeMethods {
	public void boring1() {
		System.out.println("boring1");
	}

	public void boring2() {
		System.out.println("boring2");
	}

	public void interesting(String arg) {
		System.out.println("interesting " + arg);
	}

	public void boring3() {
		System.out.println("boring3");
	}
}

class SelectingMethods {
	public static void main(String[] args) {
		SomeMethods proxy = (SomeMethods) Proxy.newProxyInstance(
				SomeMethods.class.getClassLoader(),
				new Class[] { SomeMethods.class }, new MethodSelector(
						new Implementation()));
		// SomeMethods對象是代理 new Implementation()是實際對象
		proxy.boring1();
		proxy.boring2();
		proxy.interesting("bonobo");
		proxy.boring3();
	}
} /*
boring1
boring2
Proxy detected the interesting method
interesting bonobo
boring3
 */// :~

14.8 空對象

例子講解中涉及一個思想:極限編程原則之一:做可以工作的最簡單的事情,再設計草案的初稿中,應該使用最簡單的可以工作的結構,直到程序的某方面的需求要求添加額外的特性,而不是一開始就假設他是必需的。
空對象的創建是爲了不必做過多的判空操作,但是也不能濫用空對象,如果判空不是很多,就不必使用空對象
空對象案例

package typeinfo;
//Null對象的使用 可以是我們不需要再調用方法時都對對象進行判空
//定義Null表示空對象
public interface Null {

}


package typeinfo;
//定義Person類幷包含一個NullPerson的單例表示未初始化的Person
class Person {
	public final String first;
	public final String last;
	public final String address;
	// etc.
	public Person(String first, String last, String address) {
		this.first = first;
		this.last = last;
		this.address = address;
	}
	public String toString() {
		return "Person: " + first + " " + last + " " + address;
	}
	
	public static class NullPerson extends Person implements Null {//創建NullPerson表示沒有初始化的Person
		private NullPerson() {//私有化構造方法
			super("None", "None", "None");
		}
		public String toString() {
			return "NullPerson";
		}
	}

	public static final Person NULL = new NullPerson();//NULL常量表示Person的空對象 單例
	//判斷空對象的方法
	//1 可以利用此單例與實際person對象進行比較 以確定是不是空對象
	//2 可以使用instanceof來判斷是不是NullPerson 而不需要手動添加isNull方法
} ///:~

package typeinfo;
//定義Position類 該類包含一個Person域
class Position {//Position的person域爲空 則表示Position爲空 因此不必爲Position創建專門的空對象
	private String title;
	private Person person;

	public Position(String jobTitle, Person employee) {//構造方法1 兩個參數,如果employee爲空 則person域初始化爲空對象
		title = jobTitle;
		person = employee;
		if (person == null) {
			person = Person.NULL;
		}
	}

	public Position(String jobTitle) {//構造方法2 一個參數,person域初始化爲空對象
		title = jobTitle;
		person = Person.NULL;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String newTitle) {
		title = newTitle;
	}

	public Person getPerson() {
		return person;
	}

	public void setPerson(Person newPerson) {//setPerson方法 如果newPersonperson爲空 person域賦值空對象
		person = newPerson;
		if (person == null) {
			person = Person.NULL;
		}
	}

	public String toString() {
		return "Position: " + title + " " + person;
	}
} ///:~


package typeinfo;

import java.util.*;
//定義Staff類 該類是一個包含一系列Position的ArrayList 
//同時該類也是測試類  目的是測試空對象的使用優勢:不需要額外進行判空處理(在調用toString的地方)
public class Staff extends ArrayList<Position> {// Staff本身是一個List
	public void add(String title, Person person) {
		// Staff新增職位的方法1 默認設置Position的title和person
		add(new Position(title, person));
	}

	public void add(String... titles) {// Staff新增職位的方法2 默認只設置Position的title
		for (String title : titles) {
			add(new Position(title));
		}
	}

	public Staff(String... titles) {// 構造方法 調用Staff新增職位的方法2,只設置Position的title
		add(titles);
	}

	// 職位有效的條件是 1.title與指定title一致 2.職位的Person不是空對象
	public boolean positionAvailable(String title) {
		for (Position position : this) {// 遍歷當前列表 尋找指定title的position
			if (position.getTitle().equals(title)
					&& position.getPerson() == Person.NULL) {
				return true;
			}
		}
		return false;
	}

	// 遍歷position 找到指定title的position,如果該position的Person是空對象,重新賦值
	public void fillPosition(String title, Person hire) {
		for (Position position : this) {
			if (position.getTitle().equals(title)
					&& position.getPerson() == Person.NULL && hire != null) {
				position.setPerson(hire);
				return;
			}
		}
		throw new RuntimeException("Position " + title + " not available");
	}

	public static void main(String[] args) {
		Staff staff = new Staff("President", "CTO", "Marketing Manager",
				"Product Manager", "Project Lead", "Software Engineer",
				"Software Engineer", "Software Engineer", "Software Engineer",
				"Test Engineer", "Technical Writer");
		staff.fillPosition("President", new Person("Me", "Last",
				"The Top, Lonely At"));// 填充指定Position
		staff.fillPosition("Project Lead", new Person("Janet", "Planner",
				"The Burbs"));// 填充指定Position
		if (staff.positionAvailable("Software Engineer")) {
			staff.fillPosition("Software Engineer", new Person("Bob", "Coder",
					"Bright Light City"));// 重新填充職位Software Engineer
		}
		System.out.println(staff);// 調用AbstractCollection的toString方法 無需判空
	}
} /*
 * Output: [Position: President Person: Me Last The Top, Lonely At, Position:
 * CTO NullPerson, Position: Marketing Manager NullPerson, Position: Product
 * Manager NullPerson, Position: Project Lead Person: Janet Planner The Burbs,
 * Position: Software Engineer Person: Bob Coder Bright Light City, Position:
 * Software Engineer NullPerson, Position: Software Engineer NullPerson,
 * Position: Software Engineer NullPerson, Position: Test Engineer NullPerson,
 * Position: Technical Writer NullPerson]
 */// :~

另一個空對象的例子
使用接口(Robot)取代具體類(Person) 就可以使用動態代理自動創建空對象

package typeinfo;

public interface Operation {//操作接口 包含一個命令+一個描述 
  String description();
  void command();
} ///:~


package typeinfo;
import java.util.*;

public interface Robot {//Robot接口包含 name方法 模型方法 一個Operation的數組
  String name();
  String model();
  List<Operation> operations();
  class Test {//嵌套類
    public static void test(Robot r) {//測試方法
      if(r instanceof Null){
    	  System.out.println("[Null Robot]");
      }
      System.out.println("Robot name: " + r.name());
      System.out.println("Robot model: " + r.model());
      for(Operation operation : r.operations()) {//遍歷Robot的operation
        System.out.println(operation.description());
        operation.command();
      }
    }
  }
} ///:~

測試類

package typeinfo;

import java.util.*;

public class SnowRemovalRobot implements Robot {
	private String name;

	public SnowRemovalRobot(String name) {//構造方法
		this.name = name;
	}

	public String name() {//Robot接口方法
		return name;
	}

	public String model() {//Robot接口方法
		return "SnowBot Series 11";
	}

	//SnowRemovalRobot包含一個Operation數組  數組包含4個Operation
	public List<Operation> operations() {
		return Arrays.asList(new Operation() {
			public String description() {
				return name + " can shovel snow";
			}

			public void command() {
				System.out.println(name + " shoveling snow");
			}
		}, new Operation() {
			public String description() {
				return name + " can chip ice";
			}

			public void command() {
				System.out.println(name + " chipping ice");
			}
		}, new Operation() {
			public String description() {
				return name + " can clear the roof";
			}

			public void command() {
				System.out.println(name + " clearing roof");
			}
		});
	}

	public static void main(String[] args) {
		Robot.Test.test(new SnowRemovalRobot("Slusher"));
	}
} /*
 * Output: 
Robot name: Slusher
Robot model: SnowBot Series 11
Slusher can shovel snow
Slusher shoveling snow
Slusher can chip ice
Slusher chipping ice
Slusher can clear the roof
Slusher clearing roof
*/// :~


package typeinfo;

// Using a dynamic proxy to create a Null Object.
import java.lang.reflect.*;
import java.util.*;

class NullRobotProxyHandler implements InvocationHandler {// 
	private String nullName;
	private Robot proxied = new NRobot();

	NullRobotProxyHandler(Class<? extends Robot> type) {
		nullName = type.getSimpleName() + " NullRobot";
	}

	private class NRobot implements Null, Robot {//嵌套類NRobot 代表空對象的Robot
		public String name() {
			return nullName;
		}

		public String model() {
			return nullName;
		}

		public List<Operation> operations() {
			return Collections.emptyList();
		}
	}

	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		return method.invoke(proxied, args);
	}
}

public class NullRobot {//測試類NullRobot
	//獲取空Robot的靜態方法  通過動態代理Proxy.newProxyInstance返回Robot對象
	public static Robot newNullRobot(Class<? extends Robot> type) {
		return (Robot) Proxy.newProxyInstance(NullRobot.class.getClassLoader(),
				new Class[] { Null.class, Robot.class },
				new NullRobotProxyHandler(type));
	}

	public static void main(String[] args) {
		Robot[] bots = { new SnowRemovalRobot("SnowBee"),
				newNullRobot(SnowRemovalRobot.class) };
		for (Robot bot : bots){//數組中存在一個非空對象和一個空對象
			Robot.Test.test(bot);
		}
	}
} /*
 * Output: 
Robot name: SnowBee
Robot model: SnowBot Series 11
SnowBee can shovel snow
SnowBee shoveling snow
SnowBee can chip ice
SnowBee chipping ice
SnowBee can clear the roof
SnowBee clearing roof

[Null Robot]
Robot name: SnowRemovalRobot NullRobot
Robot model: SnowRemovalRobot NullRobot
*/// :~

14.8.1 模擬對象與樁

空對象的邏輯變體是模擬對象與樁
模擬對象屬於更輕量級的東西,如果需要做很多事情,通常創建大量小而簡單的模擬對象 樁通常是重量級的,樁經常在測試間被複用
(樁在單元測試中被廣泛運用)

14.9 接口與類型信息

接口可以降低耦合(原因參見 https://blog.csdn.net/hhhuuu2020/article/details/52440279)
例如
interface Test{}
class Test1 implements Test{}
class Test2 implements Test{}
假設有個方法
void say(Test test){System.out.println("");}
它接受Test對象
我們可以這樣調用
Test t1 = new Test1();
或者t1 = new Test2();
不管哪一種都可以調用say(t1)
我們可以任意添加或刪除Test的實現者,這不會影響現有代碼,或者在代碼調用處更改t1的實際子類對象 比如假設這樣t1 = new Test3();
這種靈活操作就是低耦合,低程度地依賴其他類。

但是書中提及接口並非是對解耦地無懈可擊的保障,原因如下:

package typeinfo.interfacea;

public interface A {
	void f();
} // /:~


package typeinfo;

// 偷偷摸摸繞過接口的例子
import typeinfo.interfacea.*;

class B implements A {// A只有f方法
	public void f() {
	}

	public void g() {
	}
}

public class InterfaceViolation {
	public static void main(String[] args) {
		A a = new B();
		a.f();
		// a.g(); // Compile error
		System.out.println(a.getClass().getName());
		if (a instanceof B) {
			//不用接口而使用實際類型
			//偷偷摸摸繞過接口A 轉換成了classB調用B類的方法
			//這樣做合法 但是接口低耦合的效果就沒了(增加了耦合度)
			B b = (B) a;
			b.g();
		}
	}
} /*
 * Output: 
typeinfo.B
 */// :~

到這裏 可以看到接口是可以被繞過的 通過instanceof和強制類型轉換
有一種方法可以避免這種RTTI的漏洞即控制權限

package typeinfo.packageaccess;

import typeinfo.interfacea.*;
//避免繞過接口,直接使用實際類型的方法是控制權限 使得包外部的的客戶端訪問者看不到實際接口
class C implements A {
	public void f() {
		System.out.println("public C.f()");
	}

	public void g() {
		System.out.println("public C.g()");
	}

	void u() {
		System.out.println("package C.u()");
	}

	protected void v() {
		System.out.println("protected C.v()");
	}

	private void w() {
		System.out.println("private C.w()");
	}
}

public class HiddenC {//返回父類引用 但其實際指向子類對象
	public static A makeA() {
		return new C();
	}
} ///:~


package typeinfo;
// 無法繞過包訪問權限
import typeinfo.interfacea.*;
import typeinfo.packageaccess.*;
import java.lang.reflect.*;

public class HiddenImplementation {
  public static void main(String[] args) throws Exception {
    A a = HiddenC.makeA();//實際引用是C類型的
    a.f();
    System.out.println(a.getClass().getName());//a的實際對象是C類型的
    // 編譯報錯: 找不到符號 'C': 因爲C的訪問權限限制爲包內訪問
    /* if(a instanceof C) {
      C c = (C)a;
      c.g();
    } */
    // 哎呦!反射仍然允許我們調用C的方法 
    callHiddenMethod(a, "g");
    //甚至可以訪問缺少權限的方法
    callHiddenMethod(a, "u");
    callHiddenMethod(a, "v");
    callHiddenMethod(a, "w");
  }
  static void callHiddenMethod(Object a, String methodName)
  throws Exception {
    Method g = a.getClass().getDeclaredMethod(methodName);
    g.setAccessible(true);//可以訪問缺少權限的方法是因爲這句調用
    g.invoke(a);
  }
} /* Output:
public C.f()
typeinfo.packageaccess.C
public C.g()
package C.u()
protected C.v()
private C.w()
*///:~

從上述例子我們看到 訪問權限可以堵住RTTI的漏洞 使程序無法繞開接口,但是 我們同時發現,訪問權限無法限制反射 通過反射,我們仍然可以調用所有方法

實際上,反射是無法避免的,不管是私有接口,私有內部類和匿名內部類 都無法逃脫被反射發現所有方法(包括私有方法)的命運

package typeinfo;
// 私有內部類也無法避免,反射仍然可以調用所有方法 
import typeinfo.interfacea.*;

class InnerA {
  private static class C implements A {
    public void f() { System.out.println("public C.f()"); }
    public void g() { System.out.println("public C.g()"); }
    void u() { System.out.println("package C.u()"); }
    protected void v() { System.out.println("protected C.v()"); }
    private void w() { System.out.println("private C.w()"); }
  }
  public static A makeA() { return new C(); }
}	

public class InnerImplementation {
  public static void main(String[] args) throws Exception {
    A a = InnerA.makeA();
    a.f();
    System.out.println(a.getClass().getName());
    // Reflection still gets into the private class:
    HiddenImplementation.callHiddenMethod(a, "g");
    HiddenImplementation.callHiddenMethod(a, "u");
    HiddenImplementation.callHiddenMethod(a, "v");
    HiddenImplementation.callHiddenMethod(a, "w");
  }
} /* Output:
public C.f()
InnerA$C
public C.g()
package C.u()
protected C.v()
private C.w()
*///:~

package typeinfo;
// 匿名內部類也無法避免,反射仍然可以調用所有方法 
import typeinfo.interfacea.*;

class AnonymousA {
  public static A makeA() {
    return new A() {
      public void f() { System.out.println("public C.f()"); }
      public void g() { System.out.println("public C.g()"); }
      void u() { System.out.println("package C.u()"); }
      protected void v() { System.out.println("protected C.v()"); }
      private void w() { System.out.println("private C.w()"); }
    };
  }
}	

public class AnonymousImplementation {
  public static void main(String[] args) throws Exception {
    A a = AnonymousA.makeA();
    a.f();
    System.out.println(a.getClass().getName());
    // Reflection still gets into the anonymous class:
    HiddenImplementation.callHiddenMethod(a, "g");
    HiddenImplementation.callHiddenMethod(a, "u");
    HiddenImplementation.callHiddenMethod(a, "v");
    HiddenImplementation.callHiddenMethod(a, "w");
  }
} /* Output:
public C.f()
AnonymousA$1
public C.g()
package C.u()
protected C.v()
private C.w()
*///:~

談完了方法 我們再看看域

package typeinfo;

import java.lang.reflect.*;
//private 域也可以被修改,除了final域
class WithPrivateFinalField {
	private int i = 1;
	private final String s = "I'm totally safe";
	private String s2 = "Am I safe?";

	public String toString() {
		return "i = " + i + ", " + s + ", " + s2;
	}
}

public class ModifyingPrivateFields {
	public static void main(String[] args) throws Exception {
		WithPrivateFinalField pf = new WithPrivateFinalField();
		System.out.println(pf);//調用同String
		System.out.println("--------------------");
		
		Field f = pf.getClass().getDeclaredField("i");//獲取WithPrivateFinalField的域i
		f.setAccessible(true);
		System.out.println("f.getInt(pf): " + f.getInt(pf));//打印i的初始值
		f.setInt(pf, 47);//修改i的值
		System.out.println(pf);//確認i的值可以被修改
		System.out.println("---------end test i-----------");
		
		f = pf.getClass().getDeclaredField("s");//獲取WithPrivateFinalField的域s
		f.setAccessible(true);
		System.out.println("f.get(pf): " + f.get(pf));//打印s的初始值
		f.set(pf, "No, you're not!");//嘗試修改s的值
		System.out.println(pf);//final的值無法被修改
		System.out.println("---------end test s-----------");
		
		f = pf.getClass().getDeclaredField("s2");//獲取WithPrivateFinalField的域s2
		f.setAccessible(true);
		System.out.println("f.get(pf): " + f.get(pf));//打印s2的初始值
		f.set(pf, "No, you're not!");//嘗試修改s2的值
		System.out.println(pf);//確認s2的值可以被修改
		System.out.println("----------end test s2----------");
	}
} /*
i = 1, I'm totally safe, Am I safe?
--------------------
f.getInt(pf): 1
i = 47, I'm totally safe, Am I safe?
---------end test i-----------
f.get(pf): I'm totally safe
i = 47, I'm totally safe, Am I safe?
---------end test s-----------
f.get(pf): Am I safe?
i = 47, I'm totally safe, No, you're not!
----------end test s2----------
*/// :~
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章