Java編程思想__類型信息

  • 運行時類型信息使得你可以在程序運行時發現和使用類型信息。

 

什麼是RTTI

  • 運行時類型識別(RTTI, Run_Time Type Identification) 是Java中非常有用的機制,在Java運行時,RTTI 維護類的相關信息。

 

爲什麼需要RTTI

  • 最通用的類型(泛型)是基類Shape , 而派生出具體類有 Circle , Square 和 Triangle 。如下圖

 

                                                              

  1.  這是一個典型的類層次結構圖,基類位於頂部,派生類向下拓展。面向對象編程中基本的目的是: 讓代碼只操縱對基類(這裏是Shape)的引用。
  2. 這樣,如果要添加一個新類(比如從Shape派生的Rhomboid)來拓展程序, 就不會影響到原來的代碼。
  3. 在這個例子的Shape 接口動態綁定了 draw()方法,目的就是讓客戶端程序員使用泛化的Shape 引用來調用 draw()。draw() 在所有派生類裏都會被覆蓋,並且由於它是被動態綁定的,所以即使是通過泛化的Shape引用來調用,也能產生正確的行爲。這就是多態
abstract class Shape {
    void draw(){
        System.out.println(this+".draw()");
    }

    @Override
    abstract public String toString();
}

class Circle extends Shape{

    @Override
    public String toString() {
        return "Circle";
    }
}

class Square extends Shape{

    @Override
    public String toString() {
        return "Square";
    }
}

class Triangle extends Shape{

    @Override
    public String toString() {
        return "Triangle";
    }
}

class Shapes{
    public static void main(String[] args) {
        List<Shape> shapes= Arrays.asList(
                new Circle(),new Square(),new Triangle()
        );
        for (Shape each:shapes) {
            each.draw();
        }
    }
}


//運行結果爲

Circle.draw()
Square.draw()
Triangle.draw()
  1. 基類中包含 draw() 方法,它通過傳遞 this 參數給 System.out.println(), 間接地使用 toString() 打印類表示符 (注意, toString() 被聲明爲 abstract,以此強制繼承者覆寫該方法,並可以防止對無格式的 Shape的實例化)。
  2. 如果某個對象出現在字符串表達式中(涉及"+" 和 字符串對象的表達式), toString() 方法就會被自動調用,以生成表示該對象的String。
  3. 每個派生類都要覆蓋(從 Object 繼承來的)toString() 方法,這樣draw() 在不同情況下就打印出不同的消息(多態)。

 

 

Class對象

  • 要理解RTTI在Java中的工作原理,首先必須要知道類型信息在運行時是如何表示的。這項工作是由稱爲 Class對象的特殊對象完成的,它包含了與類有關的信息。

  • 事實上,Class對象就是用來創建類的所有的常規對象的。Java使用Class對象來執行其RTTI,即使你正在執行的是類似轉型這樣的操作。Class 類還擁有大量的使用RTTI的其他方式。

  • 類是程序的一部分,每個類都有一個Class 對象。換言之,每當編寫並且編譯了一個新類,就會產生一個Class對象(更恰當地說,是被保存在一個同名的.class文件中)。爲了生成這個類的對象,運行這個程序的Java虛擬機(JVM) 將使用被稱爲 類加載器的子系統。

  • 所有的類都是在對其第一次使用時,動態加載到JVM中的。當程序第一個對類的靜態成員的引用時,就會加載這個類。這個證明構造器也是類的靜態方法,即使在構造器之前並沒有使用 static關鍵字。因此,使用new 操作符創建類的新對象也會被當做對類的靜態成員的引用。

  • Java 程序在它開始運行之前並非被完全加載,其各個部分是在必須時才加載的。這一點與許多傳統語言都不同。動態加載使能的行爲,在諸如C++這樣的靜態加載語言中是很難或者根本不可能複製的。

  • 類加載器首先檢查這個類的Class對象是否已經加載。如果尚未加載,默認的類加載器就會根據類名查找.class文件(例如,某個附加類加載器可能會在數據庫中查找字節碼)。在這個類的字節碼被加載時,它們會接受驗證,以確保其沒有被破壞,並且不包含不良Java代碼(這是Java中用於安全防範目的的措施之一)。

  • 一旦某個類的Class 對象被載入內存,它就被用來創建這個類的所有對象。如下程序

package classtype;

 class Candy {

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

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

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

class SweetShop{
    public static void main(String[] args) {
        System.out.println("inside main");
        new Candy();
        System.out.println("After creating Candy");

        new Gum();
        try {
            Class.forName("classtype.Gum");
        }catch (ClassNotFoundException e){
            System.out.println("Couldn't find Gum");
        }

        System.out.println("After Class.forName(\"Gum\")");
        new Cookie();
        System.out.println("After creating Cookie");
    }
}

//運行結果爲

inside main
Candy
After creating Candy
Gum
After Class.forName("Gum")
Cookie
After creating Cookie
  1. main() 方法在執行try catch 時,一直運行的就是 catch 內容而Class.forName() 無法訪問到類可以在另一篇文章進行查找問題所在
  2. 從輸出結果可以看出,Class對象僅在需要的時候才被加載,static初始化是在類加載時進行的。
  3. Class.forName()取得Class對象的引用的一種方法。它是用一個包含目標類的文本名(注意拼寫和大小寫)的String作輸入參數,返回的是一個Class對象的引用,上面代碼忽略了返回值。
  4. Class 類方法除了 forName("") 來獲取類的實例, 可以使用 getName() 來產生全限定的類名 , 並分別使用 getSimpleName() 和 getCanonicalName() 來產生 不含包名的類名和權限的類名。
  5. Class 的 newInstance() 方法是實現 虛擬構造器 的一種途徑。使用newInstance() 來創建的類,必須帶有默認的構造器。你將會看到如何通過使用 Java的反射API ,用任意的構造器來動態創建了類的對象。

 

 

類字面常量

  • Java 還提供了另一種方法來生成對Class對象的引用,即 類字面常量。 對於上述程序來說,就像這樣: FancyToy.class
  • 這樣做不僅更簡單, 而且更安全,因爲它在編譯時就會受到檢查(因此不需要置於try語句塊中)。並且它根除了對 forName() 方法的調用,所以也更高效。
  • 類字面常量不僅可以應用於普通類,也可以應用於接口,數組以及基本數據類型。
...等價於...
boolean.class Boolean.TYPE
char.class Character.TYPE
byte.class Byte.TYPE
short.class Short.TYPE
int.class Integer.TYPE
long.class Long.TYPE
float.class Float.TYPE
double.class Double.TYPE
void.class Void.TYPE
  1. 我建議使用 .class 的形式,以保持與普通類的一致性。
  2. 當使用 .class 來創建對Class 對象的引用時,不會自動地初始化該 Class對象。爲了使類而做的準備工作實際包含了三個步驟:
  • 加載,這是由類加載器執行的。該步驟將查找字節碼(通常在classpath 所指定的路徑中查找,但這並非是必須的),並從這些字節碼中創建一個Class對象。
  • 鏈接。在鏈接階段將驗證類中的字節碼,爲靜態域分配存儲空間,並且如果必須的話將解析這個類創建的對其他類的所有引用。
  • 初始化。如果該類具有超類,則對其初始化,執行靜態初始化器和靜態初始化塊。
public class Initable {
    static final int staticFinal = 47;
    static final int staticFinal2 = new Random().nextInt(1000);

    static {
        System.out.println("Initable init Constructor");
    }
}

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

class Initable3{
    static int staticNonFinal = 74;
    static {
        System.out.println("Initable3 init constructor");
    }
}
class ClassInitable{
    public static void main(String[] args) throws ClassNotFoundException {
        Class initable = Initable.class;
        System.out.println("after creating initable ref");
        //does not trigger initialization  不觸發初始化
        System.out.println(Initable.staticFinal);
        //does trigger initalization  確實觸發初始化
        System.out.println(Initable.staticFinal2);

        //does trigger initalization  確實觸發初始化
        System.out.println(Initable2.staticNonFinal);
        
        
        Class initable3=Class.forName("classtype.Initable3");
        System.out.println("after creating initable3 ref");
        System.out.println(Initable3.staticNonFinal);
    }
}

//運行結果爲

after creating initable ref
47
Initable init Constructor
634
Initable2 init Constructor
147
Initable3 init constructor
after creating initable3 ref
74
  1. 初始化有效地實現了儘可能的 "惰性"。 從對 initable 引用的創建中可以看到,僅適用 .class 語法來獲得對類的引用不會引發初始化。但是,爲了產生Class 引用,Class.forName()立即就進入了初始化,就像在堆 initable3引用的創建中所看到的的。
  2. 如果是一個 static final 值時編譯期常量,就像 Initable.staticFinal 那樣,那麼這個值不需要對Inable類進行初始化就可以被讀取。但是,如果只是將一個域設置爲 static 和 final 的,還不足以確保這種行爲,例如, Initable.staticFinal2 的訪問將強制進行類的初始化,因爲它不是一個編譯期常量。
  3. 如果一個 static 域不是 final 的,那麼在對它訪問時,總是要求在它被讀取之前,要先進行鏈接(爲這個域分配存儲空間)和初始化(初始化該存儲空間),就像在對 Initable2.staticNonFinal 的訪問中所看到的那樣。

 

泛化的Class引用

  • Class 引用總是指向某個Class 對象,它可以製造類的實例, 幷包含可作用於這些實例的所有方法代碼。
  • 它還包含該類的靜態成員,因此,Class 引用表示的就是它所指向的對象的確切類型,而該對象便是Class 類的一個對象。
class GenericClassReferences{
    public static void main(String[] args) {
        Class intClass = int.class;
        Class<Integer> integerClass=int.class;
        //same thing 等同於
        integerClass =Integer.class;
        //Illegal  非法 
        intClass = double.class;
    }
}
  1. 普通的類不會產生警告信息,你可以看到,儘管泛型類引用只能賦值爲指向其聲明的類型, 但是普通的類引用可以被重新賦值爲指向任何其他的Class 對象。通過使用泛型語法,可以讓編譯器強制執行額外的類型檢查。
  • 如果你希望稍微訪問放鬆一些這種限制,應該怎麼辦呢?乍一看,好像你應該能夠執行類似下面這樣的操作:
Class<Number> numebers=int.class;
  1. 這看起來似乎是起作用的,因爲 Interger 繼承自 Number 。
  2. 但是它無法工作,因爲 Integer Class 對象不是Number Class對象的子類(這種差異看起來有些詫異,我們將在後面進行討論他)。
  3. 爲了在泛化的Class引用時放鬆限制,我使用了通配符,它是Java 泛型的一部分。 通配符就是 ? 表示任何事物。
class wildcardClassReferences{
    public static void main(String [] args){
        Class<?> intClass=int.class;
        intClass=double.class;
    }
}
  1. Class<?> 優先於平凡的 Class, 即便它們是等價的,並且平凡的Class如你所見,不會產生編譯器警告信息。
  2. Class<?> 的好處是它表示你並非是碰巧或者由於疏漏,而使用了一個非具體的類引用,你就是選擇了非具體的版本。
    public static void main(String[] args) {
        Class<? extends Number> numbers=int.class;
        numbers=double.class;
        numbers=Number.class;
        //or anything else derived from number  或其他從數字得出的東西
        
    }
  1. 向Class 引用添加 泛型語法的原因僅僅是 爲了提供編譯期類型檢查,因此如果你操作有誤,稍後立即就會發現這一點。
  2. 使用普通Class 引用,你不會誤入歧途,但是如果你確實犯了錯誤,那麼直到運行時你才能發現它,而這顯得很不方便。
class  CountedInteger{

    private static long counter;
    private final long id = counter++;

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


public class FilledList<T> {
    private Class<T> type;

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

    /**
     *
     * @param nElements 傳入元素的長度
     * @return List<T>
     */
    public List<T> created(int nElements){
          List<T> list=new ArrayList<>();
          try {
              for (int i = 0; i < nElements; i++) {
                  list.add(type.newInstance());
              }
          }catch (Exception e){
              throw new RuntimeException(e);
          }
          return list;
    }

    public static void main(String[] args) {
        FilledList<CountedInteger> filledList=new FilledList<>(CountedInteger.class);
        List<CountedInteger> created = filledList.created(15);
        System.out.println(created);
    }
}
  1. 注意,這個類必須假設它一同工作餓的任何類型都具有一個默認的構造器(無參構造器)。
  2. 並且,如果不符合該條件,你將會得到一個異常。編譯器對改程序不會產生任何警告信息。
class GenericToyTest{
    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        Class<FancyToy> fancyToyClass=FancyToy.class;
        FancyToy fancyToy = fancyToyClass.newInstance();
        Class<? super FancyToy> superclass = fancyToyClass.getSuperclass();
        //this won't compile  這不會編譯
        //Class<Toy> toyClass=fancyToyClass.getSuperclass();
        //only produces object  只會產生object對象
        Object object = superclass.newInstance();
    }
}
  1. 如果你手頭的是超類,那麼編譯器將只允許你聲明超類引用時 某個類,它是FancyToy 超類, 就像在表達式 Class<? Super FancyToy> 中所看到的,而不會接受 Class<Toy> 這樣的聲明。
  2. 這看上去顯得有些怪, 因爲 getSuperClass() 方法返回的是基類(不是接口), 並且編譯器在編譯期就知道它是什麼類型了--在本例中就是 Toy.class -- 而不僅僅只是 某個類,它是 FancyToy超類。
  3. 不管怎麼樣,正是由於這種含糊性, superclass.newInstance()  的返回值不是 精確類型,而是 Object。

 

新的轉型語法

  • Java SE5 還添加了用於 Class 引用的轉型語法 ,即 cast() 方法。
class ClassCasts{
    public static void main(String[] args) {
        Building building=new House();
        Class<House> houseClass=House.class;
        House cast = houseClass.cast(building);
        //or just do this 或者這樣做
        cast=(House)building;

        //新特性
        Class<? extends Building> asSubclass = houseClass.asSubclass(Building.class);
    }
}
  1. cast() 方法接受參數對象,並將其轉型爲 Class 引用類型。
  2. 當然,如果你觀察上面的代碼,則會發現,與實現了相同功能的 main() 中最後一行相比,這種轉型好像做了許多額外的工作。
  3. 新的轉型語法對於無法使用普通轉型的情況顯得非常有用,在你編寫泛型代碼時,如果你存儲了Class引用,並希望以後通過這個引用來執行轉型,這種情況就會時有發生。
  4. 在Java SE5中另一個沒有任何用處的新特性就是 Class.asSubclass() ,該方法允許你將一個類對象轉型爲 更加具體的類型。

 

類型轉換前先做檢查

  • 迄今爲止,我們已知的RTTI 形式包括:
  1. 傳統的類型轉換,如 Shape ,由RTTI 確保類型轉換的正確性,如果執行一個錯誤的類型轉換,就會拋出一個 ClassCastException 異常。
  2. 代表對象的類型的Class對象。通過查詢Class對象可以獲取運行時所需的信息。 在C++ 中,經典的類型轉換( Shape ) 畢不使用 RTTI 。 它只是簡單地告訴編譯器將這個對象作爲新的類型對待。 而Java要執行類型檢查,通常這被稱爲 類型安全的向下轉型。之所以叫 向下轉型, 是由於類層次結構圖從來就是這麼排列的。 如果將 Circle 類型轉換爲 Shape 類型被稱作 向上轉型。那麼將 Shape 轉型爲 Circle 類型 就被稱爲 向下轉型
  3. RTTI 在Java中還有第三種形式,就是關鍵詞 instanceof 。 他返回一個 布爾值,告訴我們對象是不是某個特定類型的實例
if(x instanceof Dog){
    ((Dog)x).back();
}
  1. 在將 x 轉型成一個Dog前,上面的 if 語句會檢查對象x是否從屬於Dog類。進行向下轉型前,如果沒有其他信息可以告訴你這個對象是什麼類型,那麼使用 instanceof 是非常重要的,否則會得到一個 ClassCastException。
package classtype;

/**
 * @author Administrator
 * @version 1.0
 * @date 2020/5/19 9:52
 */

class Individual{
    private String name;


    public Individual() {
    }

    public Individual(String name) {
        this.name = name;
    }
}


public class Person extends Individual {

    public Person(String name) {
        super(name);
    }
}


class Pet extends Individual{
    public Pet() {
    }

    public Pet(String name) {
        super(name);
    }
}


class Dog extends Pet{
    public Dog() {
    }

    public Dog(String name) {
        super(name);
    }
}

class Mutt extends Dog{
    public Mutt() {
    }

    public Mutt(String name) {
        super(name);
    }
}

class Pug extends Dog{
    public Pug() {
    }

    public Pug(String name) {
        super(name);
    }
}

class Cat extends Pet{
    public Cat() {
    }

    public Cat(String name) {
        super(name);
    }
}

class EgyptianMau extends Cat{
    public EgyptianMau() {
    }

    public EgyptianMau(String name) {
        super(name);
    }
}

class Manx extends Cat{
    public Manx() {
    }

    public Manx(String name) {
        super(name);
    }
}

class Cymric extends Manx{
    public Cymric() {
    }

    public Cymric(String name) {
        super(name);
    }
}


class Rodent extends Pet{
    public Rodent() {
    }

    public Rodent(String name) {
        super(name);
    }
}

class Rat extends Rodent{
    public Rat() {
    }

    public Rat(String name) {
        super(name);
    }
}

class Mouse extends Rodent{
    public Mouse() {
    }

    public Mouse(String name) {
        super(name);
    }
}

class Hamster extends Rodent{
    public Hamster() {
    }

    public Hamster(String name) {
        super(name);
    }
}

接下來,我們還需要一種方法,通過它可以隨機地創建不同類型的寵物,並且方便起見,還可以創建寵物數組和list。

public abstract class PetCreator {

    private Random random = new Random(47);

    //the list of the different types of pet to create  要創建的不同類型寵物的列表
    public abstract List<Class<? extends Pet>> types();

    //create one random pet  隨機制造一隻寵物
    public Pet randomPet() throws IllegalAccessException, InstantiationException {
        int n = random.nextInt(types().size());
        return types().get(n).newInstance();
    }

    public Pet[] createArray(int size) throws InstantiationException, IllegalAccessException {
        Pet[] result = new Pet[size];
        for (int i = 0; i < size; i++) {
            result[i] = randomPet();
        }
        return result;
    }

    public List<Pet> arrayList(int size) throws IllegalAccessException, InstantiationException {
        List<Pet> result = new ArrayList<>();
        Collections.addAll(result, createArray(size));
        return result;
    }
}
  1.  抽象的 getTypes() 方法在導出類中實現,以獲取由Class 對象構成的List(這是 模板方法設計模式的一種變體)。
  2. 注意, 其中類的類型被指定爲 任何從Pet導出的類,因此 newInstance() 不需要轉型就可以產生 Pet。
  3. randomPet() 隨機地產生List 中的索引,並使用被選取的 Class對象,通過 Class.newInstance() 來生成該類的新實例。
  4. createArray() 方法使用 randomPet() 來填充數組,而arrayList() 方法使用戶的則是 createArray()。
public class ForNameCreator extends PetCreator {

    private static List<Class<? extends Pet>> types = new ArrayList<>();

    static {
        loader();
    }

    //types that you want to be randomly created  您想要隨機創建的類型
    private static String[] typeNames = {
            "classtype.Mutt",
            "classtype.Pug",
            "classtype.EgyptianMau",
            "classtype.Manx",
            "classtype.Cymric",
            "classtype.Rat",
            "classtype.Mouse",
            "classtype.Hamster",
    };

    @SuppressWarnings("unchecked")
    private static void loader(){
        try {
            for (String name : typeNames) {
                types.add((Class<? extends Pet>) Class.forName(name));
            }
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
    
    @Override
    public List<Class<? extends Pet>> types() {
        return types;
    }
}
  1. loader()  方法使用 Class.forName() 創建 Class對象的List,這可能會產生 ClassNotFoundException 異常,這麼做是有意義的, 因爲你傳遞給它的是一個在編譯期無法驗證的 String。
  2. 爲了產生具有實際類型的 Class對象的List,必須使用轉型,這會產生編譯器警告。loader() 方法被單獨定義,然後置於一個靜態初始化子句中,因爲 @SuppressWarning 註解不能直接置於靜態初始化子句之上。
  3. 爲了對 Pet 進行計數,我們需要一個能夠跟蹤各種不同類型的Pet 的數量的工具。
public class PetCountNumber {

    static class PetCounter extends HashMap<String, Integer> {
        public void count(String type) {
            Integer quantity = get(type);
            if (quantity == null) put(type, 1);
            else put(type, quantity + 1);
        }
    }

    static void countPets(PetCreator petCreator) throws IllegalAccessException, InstantiationException {
        PetCounter petCounter = new PetCounter();
        for (Pet pet : petCreator.createArray(20)) {
            System.out.println(pet.getClass().getSimpleName()+" ");
            if (pet instanceof Pet)
                petCounter.count("Pet");
            if (pet instanceof Dog)
                petCounter.count("Dog");
            if (pet instanceof Mutt)
                petCounter.count("Mutt");
            if (pet instanceof Pug)
                petCounter.count("Pug");
            if (pet instanceof Cat)
                petCounter.count("Cat");
            if (pet instanceof Manx)
                petCounter.count("Manx");
            if (pet instanceof EgyptianMau)
                petCounter.count("EgyptianMau");
            if (pet instanceof Cymric)
                petCounter.count("Cymric");
            if (pet instanceof Rodent)
                petCounter.count("Rodent");
            if (pet instanceof Rat)
                petCounter.count("Rat");
            if (pet instanceof Mouse)
                petCounter.count("Mouse");
            if (pet instanceof Hamster)
                petCounter.count("Hamster");
        }
        //show the counts 顯示計數
        System.out.println(petCounter);
    }

    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        countPets(new ForNameCreator());
    }
}

//運行結果爲

Rat 
Manx 
Cymric 
Mutt 
Pug 
Cymric 
Pug 
Manx 
Cymric 
Rat 
EgyptianMau 
Hamster 
EgyptianMau 
Mutt 
Mutt 
Cymric 
Mouse 
Pug 
Mouse 
Cymric 
{EgyptianMau=2, Pug=3, Rat=2, Cymric=5, Mouse=2, Cat=9, Manx=7, Rodent=5, Mutt=3, Dog=6, Pet=20, Hamster=1}
  1. 在countPets() 中,是使用 PetCreator 來隨機向數組中填充 Pet的。然後使用 Instanceof 對改數組中的每個Pet 進行測試和技術。
  2. 對於 instanceof 有比較嚴格的限制: 只可將其與命名類型進行比較,而不能與Class作比較。有時候,你可能要寫一堆 instanceof 表達式是很乏味的,的確如此。但是也沒有更好的辦法讓 instanceof 聰明起來,讓它能夠自動地創建Class 對象的數組,然後將目標對象與這個數組中的對象進行逐一比較。其實這並不是一種如你想象中那般好的限制,因爲漸漸地讀者就會理解,如果程序寫了許多的 instanceof 表達式,就說明你的設計可能存在瑕疵

 

使用類字面常量

  • 我們使用類字面常量重新設計 PetCounter,那麼改寫後的結果在許多方面都會顯得更加清晰。
public class LiteralPetCreator extends PetCreator {

    //no try block needed  無需try塊
    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
            )
    );

    //subList 返回List<E>中一部分對象的集合  從 fromIndex開始 到toIndex結束
    private static final List<Class<? extends Pet>> types=
            allTypes.subList(
                    allTypes.indexOf(Mutt.class),
                    allTypes.size()
            );



    @Override
    public List<Class<? extends Pet>> types() {
        return types;
    }

    public static void main(String[] args) {
        System.out.println(types);
    }
}

//運行結果爲

[class classtype.Mutt, class classtype.Pug, class classtype.EgyptianMau, 
class classtype.Manx, class classtype.Cymric, class classtype.Rat,
class classtype.Mouse, class classtype.Hamster]
  1. 我們需要先用所有的 Pet類型來預加載一個 Map(而僅僅只是那些將要隨機生成的類型) ,因此 allTypes List是必須的。types列表是allTypes 的一部分(通過使用 List.subList()創建的),它包含了 確切的寵物類型,因此它被用於就隨機Pet生成。
  2. 這一次,生成types的代碼不需要放在 try 塊內,因爲它會在編譯時得到檢查,因此,它不會拋出任何異常,這與Class.forName()不一樣。
  3. 我們現在創建新類 Pets 作爲默認實現,使用了 LiteralPetCreator的外觀。
public class Pets {
    static final PetCreator petCreator =
            new LiteralPetCreator();


    static Pet randomPet(){
        try {
            return petCreator.randomPet();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return null;
    }

    static Pet[] createArray(int size){
        try {
            return petCreator.createArray(size);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }

    static List<Pet> arrayList(int size){
        try {
            return  petCreator.arrayList(size);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return null;
    }
}
  1. 這個類還提供了對 randomPet() , createArray() 和 arrayList()的簡介調用。
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        PetCountNumber.countPets(Pets.petCreator);
    }

//運行結果

與上述 PetCountNumber類 運行結果一致

 

遞歸計數

  • 與預加載不同的是,我們可以使用 Class.isAssignableFrom() ,並創建一個不侷限於對Pet計數的通用工具。
public class TypeCounter extends HashMap<Class<?>, Integer> {

    private Class<?> baseType;

    public TypeCounter(Class<?> baseType) {
        this.baseType = baseType;
    }

    void count(Object object) {
        Class<?> objectType = object.getClass();
        if (!baseType.isAssignableFrom(objectType))
            throw new RuntimeException(object + " 類型錯誤 " + objectType + " 應該是的類型或子類型 " + baseType);
        countClass(objectType);
    }

    void countClass(Class<?> type) {
        Integer quantity = get(type);
        put(type, null == quantity ? 1 : quantity + 1);
        Class<?> superclass = type.getSuperclass();
        if (null != superclass && baseType.isAssignableFrom(superclass))
            countClass(superclass);
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder("{");
        for (Map.Entry<Class<?>,Integer> each: entrySet()) {
            builder.append(each.getKey().getSimpleName())
                    .append("=")
                    .append(each.getValue())
                    .append(",");
        }

        builder.delete(builder.length()-2,builder.length())
                .append("}");
        return builder.toString();
    }
}
  1.  count() 方法獲取其參數的 Class ,然後使用 is.AssignableFrom() 來執行檢查,以效驗你傳遞的對象確實屬於我們感興趣的繼承結構。
  2. countClass() 首先對該類的確切類型計數,然後,如果其超類可以複製給 baseType ,countClass() 將其超類上遞歸計數。
class PetCount4{
    public static void main(String[] args) {
        TypeCounter counter = new TypeCounter(Pet.class);
        for (Pet each :
                Pets.createArray(20)) {
            System.out.println(each.getClass().getSimpleName()+" ");
            counter.count(each);
        }
        System.out.println(counter);
    }
}

//運行結果爲

Rat 
Manx 
Cymric 
Mutt 
Pug 
Cymric 
Pug 
Manx 
Cymric 
Rat 
EgyptianMau 
Hamster 
EgyptianMau 
Mutt 
Mutt 
Cymric 
Mouse 
Pug 
Mouse 
Cymric 
{Mutt=3,Manx=7,Dog=6,Hamster=1,Cymric=5,Cat=9,Pet=20,Pug=3,EgyptianMau=2,Mouse=2,Rat=2,Rodent=}

 

註冊工廠

  • 生成 Pet 繼承結構中的對象存在着一個問題,即每次向該繼承添加新的 Pet類型時,必須將其添加爲 LiteralPetCreator.java中的項。
  • 如果在系統中已經存在了繼承結構的常規基礎, 然後在其上要添加更多的類,那麼久有可能出現問題。
  • 這裏我們需要做的其他修改就是使用工廠方法設計模式, 將對象的創建工作交給類自己去完成。工廠方法可以被多態地調用,從而爲你創建恰當類型的對象。
public interface Factory<T> {
   T create();
}
  1. 泛型參數 T 使得create() 可以在每種Factory 實現中返回不同的類型。這也充分利用了協變返回類型。
  2. 基類 PartFactory 包含一個工廠對象的列表。對應這個由 createRandom() 方法產生的類型,它們的工廠都被添加到了 partFactorys List集合中,從而被註冊到了基類中。
public class PartFactory {

    static List<Factory<? extends  PartFactory>> partFactorys=
            new ArrayList<>();

    private static Random random =new Random(47);


    public void init(){
        //array createion ... for varargs parameter warning 數組創建...用於可變參數警告
        partFactorys.add(new FueFilter().new Factory());
        partFactorys.add(new AirFilter().new Factory());
        partFactorys.add(new CabinAirFilter().new Factory());
        partFactorys.add(new OilFilter().new Factory());
        partFactorys.add(new FanBelt().new Factory());
        partFactorys.add(new GeneratorBelt().new Factory());
        partFactorys.add(new PowerSteeringBelt().new Factory());

    }



    public static PartFactory createRandom(){
         int n=random.nextInt(partFactorys.size());
         return partFactorys.get(n).create();
    }


    class Filter extends PartFactory{}

     class FueFilter extends Filter{
        //create a class factory for each specfic tyep  爲每種特定類型創建一個類工廠
        public  class Factory implements classtype.Factory<FueFilter>{
            @Override
            public FueFilter create() {
                return new FueFilter();
            }
        }
    }

    class AirFilter extends Filter{
        class Factory implements classtype.Factory<AirFilter>{

            @Override
            public AirFilter create() {
                return new AirFilter();
            }
        }
    }

    class CabinAirFilter extends Filter{
        class Factory implements classtype.Factory<CabinAirFilter>{

            @Override
            public CabinAirFilter create() {
                return new CabinAirFilter();
            }
        }
    }

    class OilFilter extends Filter{
        class Factory implements classtype.Factory<OilFilter>{

            @Override
            public OilFilter create() {
                return new OilFilter();
            }
        }
    }

    class Belt extends PartFactory{}

    class FanBelt extends Belt{
        class Factory implements classtype.Factory<FanBelt>{

            @Override
            public FanBelt create() {
                return new FanBelt();
            }
        }
    }

    class GeneratorBelt extends Belt{
        class Factory implements classtype.Factory<GeneratorBelt>{

            @Override
            public GeneratorBelt create() {
                return new GeneratorBelt();
            }
        }
    }

    class PowerSteeringBelt extends Belt{
        class Factory implements classtype.Factory<PowerSteeringBelt>{

            @Override
            public PowerSteeringBelt create() {
                return new PowerSteeringBelt();
            }
        }
    }

    static class RegisteredFactories{
        public static void main(String[] args) {
            //初始化 數組
            new PartFactory().init();
            for (int i = 0; i < 10; i++) {
                System.out.println(PartFactory.createRandom());
            }
        }
    }

    @Override
    public String toString() {
        return getClass().getSimpleName();
    }
}

//運行結果爲

PowerSteeringBelt
CabinAirFilter
PowerSteeringBelt
AirFilter
GeneratorBelt
CabinAirFilter
FueFilter
GeneratorBelt
GeneratorBelt
FueFilter
  1. 並非所有在繼承結構中的類都應該被實例化,在本例中,Filter 和 Belt 只是分類標識,因此你不應該創建它的實例,而只是創建它們的子類實例。
  2. createRandom() 方法從 partFactories 中隨機選取一個工廠對象,然後調用  create() 方法,從而產生一個新的Part。

 

instanceof 與 Class 的等價性

  • 在查詢類型信息時,以 instanceof 的形式(即以 instanceof 的形式或 isInstance()的形式,它們產生相同的結果), 與直接Class對象有一個很重要的差別。
class Base {}

class Derived extends Base{}

class FamilyVsExacType {

    static void test(Object object){
        System.out.println("Testing object of type "+object.getClass());
        System.out.println("object instanceof Base "+ (object instanceof Base));
        System.out.println("object instanceof Derived "+(object instanceof Derived));
        System.out.println("Base isInstance(object)"+Base.class.isInstance(object));
        System.out.println("Derived isInstance(object)"+Derived.class.isInstance(object));

        System.out.println("object.getClass() == Base.class "+(object.getClass() == Base.class));
        System.out.println("object.getClass() == Derived.class "+(object.getClass() == Derived.class));
        System.out.println("object.getClass().equals(Base.class) "+(object.getClass().equals(Base.class)) );
        System.out.println("object.getClass().equals(Derived.class) "+(object.getClass().equals(Derived.class)) );

    }

    public static void main(String[] args) {
        test(new Base());
        System.out.println("---------------");
        test(new Derived());
    }
}

//運行結果爲

Testing object of type class classtype.Base
object instanceof Base true
object instanceof Derived false
Base isInstance(object)true
Derived isInstance(object)false
object.getClass() == Base.class true
object.getClass() == Derived.class false
object.getClass().equals(Base.class) true
object.getClass().equals(Derived.class) false
---------------
Testing object of type class classtype.Derived
object instanceof Base true
object instanceof Derived true
Base isInstance(object)true
Derived isInstance(object)true
object.getClass() == Base.class false
object.getClass() == Derived.class true
object.getClass().equals(Base.class) false
object.getClass().equals(Derived.class) true
  1. test() 方法使用使用了兩種形式的 instanceof 作爲參數來執行類型檢查。然後獲取 Class 引用,並用 == 和 equals()  來檢查Class 對象是否相等。
  2. instanceof 和 isInstance() 生成的結果完全一樣,equals() 和 == 也是一樣。但是這兩組測試結果卻不相同。
  3. instanceof 保持了類型的概念,它指的是 你是這個類?或者你是這個類的派生類? 
  4. 而如果用 == 比較實際的Class 對象,就沒有考慮繼承--它或者是這個確切的類型,或者不是。

 

反射:運行時的類信息

  • 如果不知道某個對象的確切類型,RTTI可以告訴你。但是有一個限制: 這個類型在編譯時,必須已知,這樣才能使用RTTI識別它,並利用這些信息做一些有用的事。即 在編譯時,編譯器必須知道所有要通過RTTI來處理的類
  • 反射提供了一種機制--用來檢查可用的方法,並返回方法名。
  • RTTI 和反射之間真正區別只在於:
  • 對RTTI 來說,編譯器在編譯時打開和檢查 .class文件(換句話說,我們可以用普通方式調用對象的所有方法)。
  • 對反射來說 .class文件在編譯時是不可獲取的,所以在運行時打開和檢查.class文件。

 

類方法提取器

  • 通常你不需要直接使用反射工具,但是它們在你需要更加動態的代碼時會很有用。反射在Java中是用來支持其他特性的,例如對象序列化和JavaBean。
public class ShowMethods {
    private static String useage =
            "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";
    private static Pattern pattern = Pattern.compile("\\w+\\.");


    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println(useage);
            System.exit(0);
        }
        int lines = 0;
        try {
            Class<?> c = Class.forName(args[0]);
            Method[] methods = c.getMethods();
            Constructor<?>[] constructors = c.getConstructors();
            if (args.length == 1) {
                for (Method each : methods) {
                    System.out.println(
                            pattern.matcher(
                                    each.toString()
                            ).replaceAll("")
                    );
                }
                for (Constructor each:constructors) {
                    System.out.println(
                            pattern.matcher(
                                    each.toGenericString()
                            ).replaceAll("")
                    );
                }
                lines =methods.length+constructors.length;
            }else {
                for (Method each:methods) {
                    if (each.toString().indexOf(args[1]) != -1){
                        System.out.println(
                                pattern.matcher(
                                        each.toString()
                                ).replaceAll("")
                        );
                        lines++;
                    }
                }
                for (Constructor each:constructors) {
                    if (each.toString().indexOf(args[1]) != -1){
                        System.out.println(
                                pattern.matcher(
                                        each.toString()
                                ).replaceAll("")
                        );
                        lines++;
                    }

                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

//運行結果爲

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()
  1. 注意,我使用的idea在運行的時候需要進行配置如下圖

  1. Class的 getMethdos() 和 getConstructors() 方法分別返回 Method 對象的數組和 Constructor 對象的數組。
  2. Class.forName() 生成的結果是不可知的,因此所有的方法特徵簽名信息都是在執行時被提取出來的。反射機制提供了足夠的支持,使得能夠創建一個在編譯時完全未知的對象,並調用此對象的方法

 

 

動態代理

  • 代理是基本的設計模式之一,它是你爲了提供額外的或不同的操作,而插入的用來代替實際對象的對象。
  • 這些操作通常涉及與實際對象的通信,因此代理通常充當着中間人的角色。
public interface Interface {

    void doSomething();
    void somethingElse(String arg);
}

class RealObject implements Interface{

    @Override
    public void doSomething() {
        System.out.println("RealObject doSomething");
    }

    @Override
    public void somethingElse(String arg) {
        System.out.println("RealObject somethingElse "+arg);
    }
}

class SimpleProxy implements Interface{

    private Interface anInterface;

    public SimpleProxy(Interface anInterface) {
        this.anInterface = anInterface;
    }

    @Override
    public void doSomething() {
        System.out.println("SimpleProxy doSomething");
        anInterface.doSomething();
    }

    @Override
    public void somethingElse(String arg) {
        System.out.println("SimpleProxy somethingElse "+arg);
        anInterface.somethingElse(arg);
    }
}

class SimpleProxyDemo{
    static void consumer(Interface inface){
        inface.doSomething();
        inface.somethingElse("bonobo");
    }

    public static void main(String[] args) {
        consumer(new RealObject());
        System.out.println("---");
        consumer(new SimpleProxy(new RealObject()));
    }
}

//運行結果爲

RealObject doSomething
RealObject somethingElse bonobo
---
SimpleProxy doSomething
RealObject doSomething
SimpleProxy somethingElse bonobo
RealObject somethingElse bonobo
  1. 因爲 consumer() 接受的 Interface, 所以它無法知道只在獲得的到底是 RealObject 還是 SimpleProxy ,因爲這二者都實現了 Interface 。
  2. Java的動態代理比代理的思想更向前邁進一步, 因爲它可以動態地創建代理並動態地處理對所代理方法的調用。
  3. 在動態代理上所做的所有調用都會被重定向到單一的調用處理器上,它的工作是揭示調用類型並確定相應的對策。
public class DynamicProxyHandler implements InvocationHandler {


    private Object proxied;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("*** proxy:" +proxy.getClass() +", method: "+method +",args: "+args);
        if (null != args)
            for (Object each:args)
                System.out.println(" "+each);
        return method.invoke(proxied,args);
    }


}
class SimpleDynamicProxy{
    static void consumer(Interface iface){
        iface.doSomething();
        iface.somethingElse("bonobo");
    }

    public static void main(String[] args) {
        RealObject realObject=new RealObject();
        consumer(realObject);
        System.out.println("----");
        Interface proxy=(Interface) Proxy.newProxyInstance(
                Interface.class.getClassLoader(),
                new Class[]{Interface.class},
                new DynamicProxyHandler(realObject)
        );
        consumer(proxy);
    }
}

//運行結果爲

RealObject doSomething
RealObject somethingElse bonobo
----
*** proxy:class com.sun.proxy.$Proxy0, method: public abstract void classtype.Interface.doSomething(),args: null
RealObject doSomething
*** proxy:class com.sun.proxy.$Proxy0, method: public abstract void classtype.Interface.somethingElse(java.lang.String),args: [Ljava.lang.Object;@dbc5d3
 bonobo
RealObject somethingElse bonobo
  1. 通過靜態方法 Proxy.newProxyInstance() 可以創建動態代理,這個方法需要得到一個類加載器(你通常可以從已經被加載的對象中獲取其類加載器,然後傳遞給它),一個你希望該代理實現的接口列表(不是類或抽象類),以及 InvocationHandler 接口的一個實現。
  2. 動態代理可以將所有調用重定向到調用處理器,因此通常會向調用處理器的構造器傳遞給一個實際對象的引用,從而使得調用處理器在執行其中介任務時,可以將請求轉發。
  3. invoke() 方法中傳遞進來了代理對象,以防你需要區分請求的來源,在許多情況下,你並不關心這一點。執行 Method.invoke() 將請求轉發給被代理對象,並傳入必須的參數。
public class MethdoSelector implements InvocationHandler {

    private Object proxied;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("interesting"))
            System.out.println("Proxy detected the interesting method");
        return method.invoke(proxied,args);
    }
}
interface SomeMethods{
    void boring1();
    void boring2();
    void interesting(String args);
    void boring3();
}

class Implementaion implements SomeMethods{

    @Override
    public void boring1() {
        System.out.println("boring1");
    }

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

    @Override
    public void interesting(String args) {
        System.out.println("interesting :"+ args);
    }

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

class SelectingMethods{
    public static void main(String[] args) {
        SomeMethods someMethods= (SomeMethods) Proxy.newProxyInstance(
                SomeMethods.class.getClassLoader(),
                new Class[]{SomeMethods.class},
                new MethdoSelector(new Implementaion())
        );
        someMethods.boring1();
        someMethods.boring2();
        someMethods.interesting("bobodo");
        someMethods.boring3();
    }
}

運行結果爲

boring1
boring2
Proxy detected the interesting method
interesting :bobodo
boring3
  1. 這裏,我們只查看了方法名,但是你還可以查看方法簽名的其他方面,甚至可以搜索特定的參數值。
  2. 動態代理並非你日常使用的工具,但是它可以非常好解決某些類型的問題。

 

空對象

  • 當你使用內置 null 表示缺少對象時,在每次使用引用時都必須測試其是否爲null,這顯得枯燥,而且勢必產生相當乏味的代碼。

  • 問題在於 null 除了在你試圖用它執行任何操作來產生 NullPointerException 之外,它自己沒有其他任何行爲。

  • 有時引入 空對象 的思想將會很有用,它可以接受傳遞給它的所代表的對象的信息,但是將返回表示爲實際上並不存在任何 真實 對象的值。通過這種方式, 你可以假設所有的對象都是有效的,而不必浪費編程精力去檢查 null 。

  • 我們可以使用空對象。但是即使空對象可以響應實際對象可以響應所有消息,你仍需某種方式去測試其是否爲空。

package classtype;

/**
 * @author Administrator
 * @version 1.0
 * @date 2020/5/21 11:40
 */
public interface Null {}
  1. 這使得 instanceof 可以探測空對象,更重要的是,這並不要求你在所有的類中都添加 isNull()方法。
public class MyPerson {

    public final String first;
    public final String last;
    public final String address;

    public MyPerson(String first, String last, String address) {
        this.first = first;
        this.last = last;
        this.address = address;
    }


  static   class NullPersoin extends MyPerson implements Null{

      public NullPersoin() {
          super("None", "None", "None");
      }

      @Override
        public String toString() {
            return "NullPersoin";
        }
    }

    public static final MyPerson NULL = new NullPersoin();

    @Override
    public String toString() {
        return "MyPerson{" +
                "first='" + first + '\'' +
                ", last='" + last + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
  1. 通常  空對象 都是單例,因此這裏將其作爲靜態 final 實例創建。 這可以正常工作的,因爲 MyPerson 是不可變的 --你只能在構造器中設置它的值,然後讀取這些值,但是你不能修改它們(因爲 String 自身具備內在不可變性)。
  2. 如果你想要修改一個 NullPerson ,那麼只能用一個新的 MyPerson 對象來替換它。
  3. 你可以選擇是喲昂 instanceof 來探測泛化 的Null 還是更具體的 NullPerson,但是由於使用了單例方式,所以你還可以只使用 equals() 甚至 == 來與 MyPerson.NULL比較。
public class Postion {
    private String titile;
    private MyPerson myPerson;

    public Postion(String titile, MyPerson myPerson) {
        this.titile = titile;
        this.myPerson = myPerson;
        if (null == myPerson)
            myPerson = MyPerson.NULL;
    }


    public Postion(String titile) {
        this.titile = titile;
        myPerson = MyPerson.NULL;
    }

    public String getTitile() {
        return titile;
    }

    public void setTitile(String titile) {
        this.titile = titile;
    }


    public MyPerson getMyPerson() {
        return myPerson;
    }

    public void setMyPerson(MyPerson myPerson) {
        this.myPerson = myPerson;
        if (null == myPerson)
            myPerson = MyPerson.NULL;
    }

    @Override
    public String toString() {
        return "Postion{" +
                "titile='" + titile + '\'' +
                ", myPerson=" + myPerson +
                '}';
    }
}
  1. 有了 Position , 你就不需要創建空對象了, 因爲 MyPerson.NULL 的存在就表示這是一個空 Perition 
public class Staff extends ArrayList<Postion> {

    public Staff(String... title) {
        add(title);
    }

    public void add(String titile, MyPerson myPerson) {
        add(new Postion(titile, myPerson));
    }

    public void add(String... titles) {
        Arrays.asList(titles).forEach(each -> {
            add(new Postion(each));
        });
    }

    boolean positionAvailable(String title) {
        for (Postion each : this) {
            if (each.getTitile().equals(title) && each.getMyPerson() == MyPerson.NULL)
                return true;
        }
        return false;
    }

    void fillPosition(String title,MyPerson myPerson){
        for (Postion each:this){
            if (each.getTitile().equals(title) && each.getMyPerson() == MyPerson.NULL){
                each.setMyPerson(myPerson);
                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 MyPerson("Me","Last","The Top,Lonely At"));
        staff.fillPosition("Project Lead",new MyPerson("Jannt","Planner","The Burbs"));
        if (staff.positionAvailable("Software Engineer")){
            staff.fillPosition("Software Egineer",new MyPerson("Bob","Coder","Bright Light City"));
        }
        System.out.println(staff);
    }
}

//運行結果爲

[
Postion{titile='President', myPerson=MyPerson{first='Me', last='Last', address='The Top,Lonely At'}}, 
Postion{titile='CTO', myPerson=NullPersoin}, 
Postion{titile='Marketing Manager', myPerson=NullPersoin}, 
Postion{titile='Product Manager', myPerson=NullPersoin},
Postion{titile='Project Lead', myPerson=MyPerson{first='Jannt', last='Planner', address='The Burbs'}}, 
Postion{titile='Software Engineer', myPerson=NullPersoin}, Postion{titile='Software Engineer', myPerson=NullPersoin}, Postion{titile='Software Engineer', myPerson=NullPersoin}, Postion{titile='Software Engineer', myPerson=NullPersoin}, 
Postion{titile='Test Engineer', myPerson=NullPersoin},
Postion{titile='Technical Writer', myPerson=NullPersoin}
]
  1. 在某些地方仍必須測試空對象,這與檢查是否爲null 沒有差異,但是在其他地方你就不必額外的測試,而可以直接假設所有的對象都是有效的。
public interface Operation {

    String description();
    void command();
}
  1. 你可以通過調用 operations() 來訪問 Robot的服務。
interface Robot{
    String name();
    String model();
    List<Operation> operations();

    class Test{
       static void test(Robot robot){
           if (robot instanceof Null)
               System.out.println("[Null Robot]");
           System.out.println("Robot name"+robot.name());
           System.out.println("Robot model"+robot.model());
           for (Operation each:robot.operations()) {
               System.out.println(each.description());
               each.command();
           }
       }
    }
}

class SnowRemovealRobot implements Robot{

    private String name;

    public SnowRemovealRobot(String name) {
        this.name = name;
    }

    @Override
    public String name() {
        return name;
    }

    @Override
    public String model() {
        return "SnowBot Series li";
    }


    @Override
    public List<Operation> operations() {
        return Arrays.asList(
                new Operation() {
                    @Override
                    public String description() {
                        return name + "can shovel snow";
                    }

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

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

                    @Override
                    public void command() {
                        System.out.println(name+"can clear the roof");
                    }
                }
        );
    }

    public static void main(String[] args) {
        Robot.Test.test(new SnowRemovealRobot("Slusher"));
    }
}

//運行結果爲

Robot nameSlusher
Robot modelSnowBot Series li
Slushercan shovel snow
Slushershoveling snow
Slushercan chip ice
Slushercan chip ice
Slushercan clear the roof
Slushercan clear the roof
  1. 假設存在許多不同類型的 Robot,我們想對每一種Robot 類型都創建一個空對象,去執行某些特殊操作--在本例中,即提供空對象所代表的 Robot 確切類型的信息。這些信息是通過董濤代理捕獲的。
public class NullRobotProxyHandler implements InvocationHandler {

    private String nullName;
    private Robot proxied = new NullRobot();

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

    class NullRobot implements Null,Robot{

        @Override
        public String name() {
            return nullName;
        }

        @Override
        public String model() {
            return nullName;
        }

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




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


class NullRobot{
    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 [] robots={
                new SnowRemovealRobot("SnowBee"),
                newNullRobot(SnowRemovealRobot.class)
        };

        Arrays.asList(robots).forEach(each->{
            Robot.Test.test(each);
        });

    }
}

//運行結果爲

Robot name: SnowBee
Robot model: SnowBot Series li
SnowBee can shovel snow
SnowBee shoveling snow
SnowBee can chip ice
SnowBee can chip ice
SnowBee can clear the roof
SnowBee can clear the roof
[Null Robot]
Robot name: SnowRemovealRobot NullRobot
Robot model: SnowRemovealRobot NullRobot
  1. 無論何時,如果你需要一個空 Robot對象,只需要調用newNullRobot() ,並傳遞需要代理的Robot類型。代理會滿足Robot 和Null 接口的需求,並提供它所代理的類型的確切名字。

 

模擬對象與樁

  • 空對象的邏輯變體是模擬 對象 。 與空對象一樣,它們都表示在最終的程序中所使用的實際對象。
  • 但是模擬對象和 樁 都只是假扮可以傳遞實際信息的存活對象,而不是像空對象那樣可以成爲 null 的一種更加智能化的替代物。
  • 模擬對象和樁之間的差異在於程度不同。模擬對象往往是輕量級和自測試的,通常很多模擬對象被創建出來是爲了處理各種不同的測試情況。
  • 樁只是返回樁數據,它通常是重量級的,並且經常在測試之間被複用。樁可以根據它們被調用的方式,通過配置進行修改,因此樁是一種複雜對象,它要做很多事。然而對於模擬對象,如果你需要做很多事情,通常會創建大量小而簡單的模擬對象。

 

接口與類型信息

  • interface 關鍵字的一個重要的目標就是允許程序員隔離構件,進而減低耦合性。
public interface A {
    void f();
}
  1. 然後實現這個接口,你可以看到其他代碼是圍繞着實際類型潛行的:
class B implements A{

    void g(){

    }
    @Override
    public void f() {

    }
}

class InterfaceViolation{
    public static void main(String[] args) {
        A a=new B();
        a.f();
        //編譯失敗
        //a.g();
        System.out.println(a.getClass().getName());
        if (a instanceof B){
            B b=(B)a;
            b.g();
        }
    }
}

//運行結果爲

classtype.B
  1. 通過使用RTTI ,我們發現 a 是被當做B 實現的。通過將其轉型爲 B ,我們就可以調用 不在A 中的方法。
  2. 這個完全合法和可接受的,但是你也許並不想讓客戶端程序員這麼做,因爲這給了他們一個機會,使得他們的代碼與你的代碼的耦合程度超過你的期望。
  3. 也就是說,你可能認爲 interface 關鍵字正在保護着你,但是它並沒有,在本例中 使用 B 來實現 A 這一事實是公開有案可查的。
  4. 使用包訪問權限
 class C implements A {

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

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

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

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

    @Override
    public void f() {
        System.out.println("C f()");
    }
}
  1. 這個包中唯一public 是HiddenC,在被調用時將產生A接口類型的對象。這裏有趣之處在於: 即使你從 makeA() 返回C類型,你在包的外部仍舊不能使用A 之外的任何方法,因此爲你不能再包的外部命名爲C。
  2. 如果你試圖將其向下轉型爲C ,則將被禁止,因爲在包的外部沒有任何C類型可用。
public class HiddentImplemention {

    static void callHiddenMethod(Object object,String methodName) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method method = object.getClass().getDeclaredMethod(methodName);
        method.setAccessible(true);
        method.invoke(object);
    }


    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        A makeA = HiddenC.makeA();
        makeA.f();
        System.out.println(makeA.getClass().getName());
        //編譯錯誤找不到符號c
       /* if (makeA instanceof C){
            C c=(C)makeA;
            c.g();
        }*/
        callHiddenMethod(makeA,"g");
        callHiddenMethod(makeA,"v");
        callHiddenMethod(makeA,"w");
    }
}

//運行結果爲

C f()
classtype.C
C g()
C v()
C w()
  1. 通過反射,仍舊可以到達並調用所有方法,甚至是private方法,如果知道方法名,你就可以在 Method對象上調用 setAccessible(true),就像在 caLLhiddenMethod()中看到的那樣。
  2. 你可能會認爲,可以通過只發布編譯後的代碼來阻止這種情況,但是這並不解決問題。因爲只需要運行javap,一個隨JDK發佈的反編譯即可突破這一限制。
//使用下面這個命令 
javap -private classtype.C

-private 標誌表示所有的成員都應該顯示,甚至包括私有成員,輸出如下

Compiled from "C.java"
class classtype.C implements classtype.A {
  classtype.C();
  public void g();
  void u();
  protected void w();
  protected void v();
  public void f();
}
  1. 因此任何人都可以獲取你最私有的方法的名字和簽名,然後調用它們。
  2. 如果你將接口實現爲一個私有內部類,又會怎麼樣呢?
public class InnerA {
    private static class C implements A{
        @Override
        public void f() {
            System.out.println("public C.f()");
        }

        public void g(){
            System.out.println("public C g()");
        }
        void u(){
            System.out.println("默認 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();
    }
}


class obtainPrivateMethod{

    public static void invoke(Object proxy, String method) throws Throwable {
        Method method1 = proxy.getClass().getDeclaredMethod(method);
        method1.setAccessible(true);
        method1.invoke(proxy);
    }
}


/**
 * 注意  InnerImplementation 不能和 InnerA 放在同一包中 package
 */
class InnerImplementation{
    public static void main(String[] args) throws Throwable {
        A makeA = InnerA.makeA();
        makeA.f();
        System.out.println(makeA.getClass().getName());
        
        
        obtainPrivateMethod.invoke(makeA,"g");
        obtainPrivateMethod.invoke(makeA,"u");
        obtainPrivateMethod.invoke(makeA,"v");
        obtainPrivateMethod.invoke(makeA,"w");
    }
}

//運行結果爲

public C.f()
classtype.InnerA$C
public C g()
默認 C u()
protected C v()
private C w()
  1. 看起來沒有任何方式可以組織反射到達並調用那些非公共訪問權限的方法。對於域來說,的確如此,即便是private域。
public class WithPrivateFinalField {
    private int i = 1;
    private final String s="I'm totally safe";
    private String s2="Am I safe";

    @Override
    public String toString() {
        return "WithPrivateFinalField{" +
                "i=" + i +
                ", s='" + s + '\'' +
                ", s2='" + s2 + '\'' +
                '}';
    }
}


class MpodifyingPrivateFields{

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        WithPrivateFinalField privateFinalField = new WithPrivateFinalField();
        System.out.println(privateFinalField);
        System.out.println("----");
        //獲取類屬性 i
        Field field = privateFinalField.getClass().getDeclaredField("i");
        field.setAccessible(true);
        System.out.println("field.getInt(privateFinalField)"+field.getInt(privateFinalField));
        //改變 i 的屬性值
        field.setInt(privateFinalField,110);
        System.out.println(privateFinalField);
        System.out.println("----");
        //獲取 類的字符串屬性值
        Field field1 = privateFinalField.getClass().getDeclaredField("s");
        field1.setAccessible(true);
        String s = field1.get(privateFinalField).toString();
        System.out.println("field1.get(privateFinalField) "+s);

        //改變 s的值
        field1.set(privateFinalField,"hello word");
        System.out.println(privateFinalField);

        System.out.println("----");
        Field field2 = privateFinalField.getClass().getDeclaredField("s2");
        field2.setAccessible(true);
        String s2 = field1.get(privateFinalField).toString();
        System.out.println("field1.get(privateFinalField) "+s2);

        //改變 s的值
        field2.set(privateFinalField,"hello word");
        System.out.println(privateFinalField);

    }
}

//運行結果爲

WithPrivateFinalField{i=1, s='I'm totally safe', s2='Am I safe'}
----
field.getInt(privateFinalField)1
WithPrivateFinalField{i=110, s='I'm totally safe', s2='Am I safe'}
----
field1.get(privateFinalField) I'm totally safe
WithPrivateFinalField{i=110, s='I'm totally safe', s2='Am I safe'}
----
field1.get(privateFinalField) hello word
WithPrivateFinalField{i=110, s='I'm totally safe', s2='hello word'}
  1. 但是 final 域實際上在遭遇修改時是安全的。 運行時系統會在不拋出異常的情況下接受任何修改嘗試,但是實際上不會發生任何修改。
  2. 通常,這些違反權限的操作並非世上最糟糕之事。如果有人使用這樣的技術去調用標識爲 private 或包訪問權限的方法(很明顯這些訪問權限表示這些人不應該調用它們),那麼對他們來說,如果你修改了這些方法,他們不應該抱怨。
  3. 另一方面,總是在類中留下後門的這一事實,也許可以使得你能夠解決某些類型的問題,但是如果不這樣做,這些問題將難以或者不可能解決,通常反射帶有的好處不可否認。

 

總結

  • RTTI 允許通過匿名基類的引用來發現類型信息。面向對象編程語言的目的是讓我們在凡是可以使用的地方都使用多臺機制,只在必需的時候使用RTTI。
  • 最後一點,RTTI 有時能解決效率問題。也許你的程序漂亮地運用了 多態,但其中某個對象是以極端缺乏效率的方式達到這個目的的。你可以挑出這個類,使用RTTI ,併爲其編寫一段特別的代碼以提高效率。
  • 但必須注意,不要太早地關注程序的效率問題,這是個誘人的陷阱。最好首先讓程序運作起來然後再考慮它的速度,如果要解決效率問題可以使用 profiler。
  • 對於有些人來說,反射的動態特性是一種煩擾,對於已經習慣與靜態類型檢查的安全性的人來說,你可以執行一些只能在運行時進行的檢查,並用異常來報告檢查結果的行爲,這本身就是一種錯誤的方法。有些人走的更遠,他們聲稱引入運行時異常本身就是一種指示,說明應該避免這種代碼。

 

 

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