Java編程思想之十四 類型信息

第十四章 類型信息

運行時類型信息使得你能夠在程序運行時發現和使用類型信息java

14.1 爲何須要RTTI

面向對象編程中基本的目的是:讓代碼只操做對基類的引用。
多態:程序員

import java.util.*;

abstract class Shape {
  void draw() { System.out.println(this + ".draw()"); }
  abstract public String toString();
}

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

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

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

public class Shapes {
  public static void main(String[] args) {
    List<Shape> shapeList = Arrays.asList(
      new Circle(), new Square(), new Triangle()
    );
    for(Shape shape : shapeList)
      shape.draw();
  }
} /* Output:
Circle.draw()
Square.draw()
Triangle.draw()
*///:~

Rtti基本使用形式:全部類型轉換都是在運行時進行正確性檢查的。在運行時識別一個對象的類型。編程

14.2 Class對象

每當編寫而且編譯一個新類,就會產生一個Class對象。爲了生成這個類的對象,運行這個程序的Java虛擬機(JVM)將使用被稱爲"類加載器"的子系統。
類加載器子系統實際上能夠包含一條加載器鏈,可是隻有一個原生類加載器,它是JVM實現的一部分。原生類加載器加載的所謂的可信類,它們一般是從本地盤加載的。
全部類都在第一個使用時,動態加載到JVM中。
Java程序在它開始運行以前並不是被徹底加載,其各個部分是在必須時才加載的。
類加載器首先檢查這個類的class對象是否已經加載。若是沒有加載,默認的類加載器就會根據類名查找.class文件。設計模式

//: typeinfo/toys/ToyTest.java
// Testing class Class.
package toys;

interface HasBatteries
{
}

interface Waterproof
{
}

interface Shoots
{
}

class Toy
{
    // Comment out the following default constructor
    // to see NoSuchMethodError from (*1*)
    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() + "]");
        System.out.println("Simple name: " + cc.getSimpleName());
        System.out.println("Canonical name : " + cc.getCanonicalName());
    }

    public static void main(String[] args)
    {
        Class c = null;
        try
        {
            c = Class.forName("toys.FancyToy");
        }
        catch (ClassNotFoundException e)
        {
            System.out.println("Can't find FancyToy");
            System.exit(1);
        }
        printInfo(c);
        for (Class face : c.getInterfaces())
            printInfo(face);
        Class up = c.getSuperclass();
        Object obj = null;
        try
        {
            // Requires default constructor:
            obj = up.newInstance();
        }
        catch (InstantiationException e)
        {
            System.out.println("Cannot instantiate");
            System.exit(1);
        }
        catch (IllegalAccessException e)
        {
            System.out.println("Cannot access");
            System.exit(1);
        }
        printInfo(obj.getClass());
    }
} /* 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 類字面常量

Java還提供了一個方法來生成對Class對象的引用,即便用類字面常量
FancyToy.class;
類字面常量不只能夠應用於普通的類,也能夠應用於接口、數組、基本數據類型。
對於基本數據類型的包裝器類,還有一個標準字段TYPE。TYPE字段是一個引用,指向對應的基本數據類型的Class對象。
當使用.class建立對象的引用時,不會自動初始化該class對象。
爲了使用類而作的準備工做實際包含三個步驟:數組

  1. 加載:這是由類加載器執行的。該步驟將查找字節碼,並從這些字節碼中建立一個Class對象。
  2. 連接:在連接階段將驗證類中的字節碼,爲靜態域分配存儲空間,而且若是必須的話,將解析這個類建立的對其餘類的全部引用。
  3. 初始化:若是該類具備超類,則對其初始化,執行靜態初始化器和靜態初始化塊。
//: typeinfo/ClassInitialization.java
import java.util.*;

class Initable {
  static final int staticFinal = 47;//編譯期常量,不須要進行初始化就能夠讀取
  static final int staticFinal2 =
    ClassInitialization.rand.nextInt(1000);
  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;
    System.out.println("After creating Initable ref");
    // Does not trigger initialization:
    System.out.println(Initable.staticFinal);
    // Does trigger initialization:
    System.out.println(Initable.staticFinal2);
    // Does trigger initialization:
    System.out.println(Initable2.staticNonFinal);
    Class initable3 = Class.forName("Initable3");
    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
*///:~

若是一個static域不是final的,那麼在對它訪問時,老是要求在它被讀取前,先進行連接(分配存儲空間)和初始化(初始化存儲空間)網絡

14.2.2 泛化的Class引用

Class引用老是指向某個Class對象,它能夠製造類的實例,幷包含可做用於這些實例的全部方法代碼。它還包含該類的靜態成員,所以,Class引用表達的就是它所指向的對象的確切類型,而該對象即是Class類的一個對象。
經過容許你對Class引用所指向的Class對象的類型進行限定,將它的類型變得更具體一些。less

public class GenericClassReferences
{
    public static void main(String[] args)
    {
        Class intClass = int.class;
        Class<Integer> genericIntClass = int.class;//只能指向具體的類型
        genericIntClass = Integer.class; // Same thing
        intClass = double.class;
        //genericIntClass = double.class; // Illegal
    }
} ///:~

通配符:? 表示任何事物。dom

public class WildcardClassReferences {
  public static void main(String[] args) {
    Class<?> intClass = int.class;
    intClass = double.class;
  }
} ///:~

Class<?>優於平凡得class,即使他們是等價得。Class<?>的好處表達你知道你選擇的是非具體的版本。
將通配符和extends關鍵字結合使用:ide

public class BoundedClassReferences {
  public static void main(String[] args) {
    Class<? extends Number> bounded = int.class;
    bounded = double.class;
    bounded = Number.class;
    // Or anything else derived from Number.
  }
} ///:~

下面的例子,它存儲了一個引用,稍後又產生了一個List,填充這個List對象使用newInstance()方法:工具

import java.util.*;

class CountedInteger {
  private static long counter;
  private final long id = counter++;
  public String toString() { return Long.toString(id); }
}

public class FilledList<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());
    } catch(Exception e) {
      throw new RuntimeException(e);
    }
    return result;
  }
  public static void main(String[] args) {
    FilledList<CountedInteger> fl =
      new FilledList<CountedInteger>(CountedInteger.class);
    System.out.println(fl.create(15));
  }
} /* Output:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
*///:~

newInstance()將返回對象的確切類型:

public class GenericToy
{
    public static void main(String[] args) throws Exception
    {
        Class<B> b = B.class;
        System.out.println("a Name:" + b.getName());
        B b1 = b.newInstance();
        System.out.println(b);
        A a = (A)b.getSuperclass().newInstance();
        System.out.println("getSuperclass.Name:"+a);
        A a1 = b.newInstance();
        System.out.println(a1);

        Object o = b.getSuperclass();
        System.out.println(o);
    }
}
class A{
    static    {
        System.out.println("AAAA");
    }
    public String toString() {
        return "a";
    }
}
class B extends A{
    static    {
        System.out.println("BBBB");
    }
    public String toString()    {
        return "b";
    }
}
14.2.3 新的轉型語法

Class引用的轉型語法cast()方法:接受參入參數對象,轉換爲引用類型

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);
    h = (House)b; // ... or just do this.
  }
} ///:~

Class.asSubclass():容許你將一個類對象轉型爲更加具體的類型。

14.3 類型轉換前先作檢查

咱們已知的RTTI形式包括:

  1. 傳統的類型轉換
  2. 表明對象的類型的CLass對象。查詢Class對象獲取容許時所須要的信息。
  3. 關鍵字instanceof。它返回一個布爾值,告訴咱們對象是否是某個特定類型的實例。

instanceof只可將其與命名空間類型進行比較,而不能與Class對象做比較。

14.4 註冊工廠

使用工廠方法設計模式,將對象的建立工做交給類本身去完成,工廠方法能夠被多態的調用,從而爲你建立恰當類型的對象。
使用工廠建立對象:

//: typeinfo/RegisteredFactories.java
// Registering Class Factories in the base class.
import factory.*;
import java.util.*;

class Part {
  public String toString() {
    return getClass().getSimpleName();
  }
  static List<Factory<? extends Part>> partFactories =
    new ArrayList<Factory<? extends Part>>();   
  static {
    // Collections.addAll() gives an "unchecked generic
    // array creation ... for varargs parameter" warning.
    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() {
    int n = rand.nextInt(partFactories.size());
    return partFactories.get(n).create();
  }
}   

class Filter extends Part {}

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

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

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

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

class Belt extends Part {}

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

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

class PowerSteeringBelt extends Belt {
  public static class Factory
  implements 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++)
      System.out.println(Part.createRandom());
  }
} /* Output:
GeneratorBelt
CabinAirFilter
GeneratorBelt
AirFilter
PowerSteeringBelt
CabinAirFilter
FuelFilter
PowerSteeringBelt
PowerSteeringBelt
FuelFilter
*///:~

14.5 instanceof與Class的等價性

在查詢類型信息時,以instanceof的形式與直接比較Class對象有一個很重要的差異,下面看看這些差異:

//: typeinfo/FamilyVsExactType.java
// The difference between instanceof and 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)));
  }
  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
*///:~

14.6 反射:運行時的類信息

在編譯時,編譯器必須知道所要經過RTTI來處理的類。
反射提供了一種機制——用來檢查可用的方法,並返回方法名。
人們想要在運行時獲取類的信息的另外一個動機,但願在跨網絡的平臺上建立和運行對象的能力。這被稱爲遠程方法調用(RMI),它容許將一個Java程序分佈到多臺機器上。
Class類與java.lang.reflect類庫一塊兒對反射概念進行了支持。這些類型的對象都是又JVM在運行時建立的,用以表示未知類裏對應的成員。
當經過反射與一個未知類型的對象打交道時,JVM只是簡單的檢查這個對象,看它屬於哪一個特定的類。在用它作其餘事情以前必須先加載那個類的Class對象。所以,那個類的.Class文件對於JVM來講必須是可獲取的:要麼在本地機器上,要麼能夠經過網絡獲取。因此RTTI和反射之間真正的區別只在於du:對RTTI來講,編譯器在編譯時打開和檢查.class文件,對於反射來講:.class在編譯時是不可獲取的,全部在運行時打開和檢查.class文件。

14.6.1 類方法提取器

反射機制提供了一種方法,使咱們可以編寫能夠自動展現完整接口的簡單工具:

 

14.7 動態代理

代理是基本的設計模式之一,它是你爲了提供額外的或不一樣的操做,而插入的用來代替實際對象的對象。

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());
    consumer(new SimpleProxy(new RealObject()));
  }
}

Java的動態代理能夠動態建立並動態地處理對所代理方法的調用。在動態代理上所作的全部調用都會被重定向到單一的調用處理器上,它的工做是揭示調用的類型並肯定相應的對策。

import java.lang.reflect.*;

class DynamicProxyHandler implements InvocationHandler {
  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) {
    iface.doSomething();
    iface.somethingElse("bonobo");
  }
  public static void main(String[] args) {
    RealObject real = new RealObject();
    consumer(real);
    // Insert a proxy and call again:
    Interface proxy = (Interface)Proxy.newProxyInstance(
      Interface.class.getClassLoader(),
      new Class[]{ Interface.class },
      new DynamicProxyHandler(real));
    consumer(proxy);
  }
}

動態代理能夠將全部調用重定向到調用處理器,所以一般會向調用處理器的構造器傳遞一個實際對象的引用,從而使得調用處理器在執行其中介任務時,能夠將請求轉發。

14.8 空對象

引入空對象的思想是頗有用的,它能夠接收傳遞給它的所表明的對象的消息,可是將返回表示爲實際上並不存在的任何真實對象的值。經過這種方式,你能夠假設全部的對象都是有效的,而沒必要浪費時間去檢查null。
即便空對象能夠響應實際對象能夠響應的全部消息,任須要測試是否爲空:

public interface Operation {
  String description();
  void command();
}
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 {
    private NullPerson() { super("None", "None", "None"); }
    public String toString() { return "NullPerson"; }
  }
  public static final Person NULL = new NullPerson();
}
class Position {
  private String title;
  private Person person;
  public Position(String jobTitle, Person employee) {
    title = jobTitle;
    person = employee;
    if(person == null)
      person = Person.NULL;
  }
  public Position(String jobTitle) {
    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) {
    person = newPerson;
    if(person == null)
      person = Person.NULL;
  }
  public String toString() {
    return "Position: " + title + " " + person;
  }
}
//: typeinfo/Staff.java
import java.util.*;

public class Staff extends ArrayList<Position> {
  public void add(String title, Person person) {
    add(new Position(title, person));
  }
  public void add(String... titles) {
    for(String title : titles)
      add(new Position(title));
  }
  public Staff(String... titles) { add(titles); }
  public boolean positionAvailable(String title) {
    for(Position position : this)
      if(position.getTitle().equals(title) &&
         position.getPerson() == Person.NULL)
        return true;
    return false;
  } 
  public void fillPosition(String title, Person hire) {
    for(Position position : this)
      if(position.getTitle().equals(title) &&
         position.getPerson() == Person.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"));
    staff.fillPosition("Project Lead",
      new Person("Janet", "Planner", "The Burbs"));
    if(staff.positionAvailable("Software Engineer"))
      staff.fillPosition("Software Engineer",
        new Person("Bob", "Coder", "Bright Light City"));
    System.out.println(staff);
  }
} /* 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]
*///:~

使用命令模式:

//: typeinfo/Robot.java
import java.util.*;
public interface Robot {
  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()) {
        System.out.println(operation.description());
        operation.command();
      }
    }
  }
} ///:~
//: typeinfo/SnowRemovalRobot.java
import java.util.*;

public class SnowRemovalRobot implements Robot {
  private String name;
  public SnowRemovalRobot(String name) {this.name = name;}
  public String name() { return name; }
  public String model() { return "SnowBot Series 11"; }
  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
*///:~
//: typeinfo/NullRobot.java
// 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 {
    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 {
  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 模擬對象與樁

空對象的邏輯變體是模擬對象和樁。模擬對象和樁都只是假扮能夠傳遞實際信息的存活對象,而不是像空對象那樣能夠稱爲null的一種更加智能化的替代品。

14.9 接口與類型信息

interface關鍵字的一種重要目標就是容許程序員隔離構建,進而下降耦合性。經過類型信息,這種耦合性仍是會傳播出去——接口並不是是對解耦的一種無懈可擊的保障。

package interfacea;

public interface A {
  void f();
}
import interfacea.*;

class B implements A {
  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) {
      B b = (B)a;
      b.g();
    }
  }
}

最簡單的方式是對實現使用包訪問權限,這樣在包外部的客戶端就不能看見它了。

package packageaccess;
import 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(); }
}

這裏makeA返回C類型,但在包在並不能使用到C。可是反射卻仍舊能夠調用:

import interfacea.*;
import packageaccess.*;
import java.lang.reflect.*;

public class HiddenImplementation {
  public static void main(String[] args) throws Exception {
    A a = HiddenC.makeA();
    a.f();
    System.out.println(a.getClass().getName());
    // Compile error: cannot find symbol 'C':
    /* if(a instanceof C) {
      C c = (C)a;
      c.g();
    } */
    // Oops! Reflection still allows us to call g():
    callHiddenMethod(a, "g");
    // And even methods that are less accessible!
    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()
*///:~

即便是內部類,反思仍舊能夠調用到:

import 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");
  }
}

匿名類也同樣:

import 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()
*///:~
相關文章
相關標籤/搜索