Java编程思想笔记——类型信息

运行时类型信息使得你可以在程序运行时发现和使用类型信息。
运行时识别对象和类的信息:一种是传统的RTTI,另一种是反射机制

为什么需要RTTI

多态的类层次结构,最通用的类型泛型是基类Shape,而派生类有Circle,Square和Triangle。
Shape接口动态绑定draw方法,目的是让客户端程序员使用泛化的Shape引用来调用draw。draw在所有派生类里都会被覆盖,并且由于动态绑定,所以即使通过泛化的Shape引用来调用,也能产生正确行为,这就是多态。

public abstract class Shape {
    void draw() {
        System.out.println(this + ".draw()");
    }
    @Override
    abstract  public String toString();
}
public class Circle extends Shape {
    @Override
    public String toString() {
        return "Circle";
    }
}
public class Square extends Shape {
    @Override
    public String toString() {
        return "Square";
    }
}
public class Triangle extends Shape {
    @Override
    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();
        }
    }
}/*
Circle.draw()
Square.draw()
Triangle.draw()
*/

当把Shape对象放入List的数组时会向上转型。但在向上转型为Shape的时候也丢失了Shape对象的具体类型。对于数组而言,他们只是Shape类的对象。
当从数组中取出元素时,这种容器——实际上它将所有的事物都当作Object持有——会自动将结果转型回Shape。这就是RTTI最基本的形式,因为在Java中,所有的类型转换都是在运行时进行正确性检查的。这也是RTTI名字的含义:在运行时,识别一个对象的类型。
例子中,RTTI类型转换并不彻底:Object被转型成了Shape,而不是转型为Circle等。这是因为目前我们只知道List保存的是Shape。在编译时,将由容器和Java泛型系统来强制确保这一点;而在运行时,由类型转换操作来确保这一点。
接下来就是多态机制的事了,Shape对象执行什么样的代码,是由引用所指向的具体对象而决定。

Class对象

类型信息在运行时由称为Class对象的特殊对象完成的,它包含了与类有关的信息。
类是程序的一部分,每个类都有一个Class对象。换言之,每当编写并编译一个新类,就会产生一个Class对象。为了生成这个类的对象,运行这个程序的Java虚拟机将使用被成为类加载器的子系统。
类加载器子系统实际上可以包含一条类加载器链,但只有一个原生类加载器(可信类包含Java API类,通常从本地盘加载的)。所有类都是在对其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员的引用时,就会加载这个类。这个证明构造器也是类的静态方法,即使在构造器之前并没有使用static关键字。因此,使用new操作符创建类的新对象也会被当作对类的静态成员的引用。
因此,Java程序在运行之前并非被完全加载,其各个部分是在必要时才加载。
类加载器首先检查这个类的Class对象是否已经加载。如果未加载,默认的类加载器就会根据类名查找.class文件。在这个类的字节码被加载时,它会接受验证,以确保其没有被破坏,并且不包含不良Java代码。
一旦某个类的Class对象被载入内存,他就被用来创建这个类的所有对象:

public class Candy {
    static {
        System.out.println("Loading Candy");
    }
}
public class Gum {
    static {
        System.out.println("Loading Gum");
    }
}
public class Cookie {
    static {
        System.out.println("Loading Cookie");
    }
}
public class SweetShop {
    public static void main(String[] args) {
        System.out.println("inside main");
        new Candy();
        System.out.println("After creating Candy");
        try {
            // 必须使用全限定名(包括包名)
            Class.forName("thinking.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
Loading Candy
After creating Candy
Loading Gum
After Class.forName("Gum")
Loading Cookie
After creating Cookie
*/

从输出中可以看到,Class对象仅在需要时候才被加载,static初始化是在类加载时进行的。
Class.forName(“Gum”);这个方法是Class类(所有Class对象都属于这个类)的一个static成员。forName是取得Class对象的引用的一种方法,返回一个Class对象的引用,找不到要加载的类,会抛出ClassNotFoundException异常。
Class.forName是获得Class对象的引用的便捷途径,因为不需要为了获得Class引用而持有该类型的对象。但如果已经有了一个对象,那可以调用getClass方法来回去Class引用,这个方法属于Object的一部分,它返回表示该对象的实际类型的Class引用:

public interface HasBatteries {
}
public interface Waterproof {
}
public interface Shoots {
}
public class Toy {
    Toy() {}
    Toy(int i) {}
}
public 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("thinking7.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 {
            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());
    }
}/*
Class Name: thinking7.FancyToy is interface? [false]
Simple name: FancyToy
Canonical name: thinking7.FancyToy
Class Name: thinking7.HasBatteries is interface? [true]
Simple name: HasBatteries
Canonical name: thinking7.HasBatteries
Class Name: thinking7.Waterproof is interface? [true]
Simple name: Waterproof
Canonical name: thinking7.Waterproof
Class Name: thinking7.Shoots is interface? [true]
Simple name: Shoots
Canonical name: thinking7.Shoots
Class Name: thinking7.Toy is interface? [false]
Simple name: Toy
Canonical name: thinking7.Toy
*/

getName和getCanonicalName产生全限定类名,getSimpleName产生不包含包名的类名,isInterface判断Class对象是否表示某个接口。Class.getInterfaces返回Class对象,表示Class对象中所包含的接口。
getSuperclass查询其直接基类,返回一个Class对象。Class的newInstance方法是实现虚拟构造器(不知道确切类型但无论如何都要正确的创建自己)的一种途径。up仅仅是Class应用,在编译期不具备进一步的类型信息。当创建新实例时,会得到Obejct引用,但是这个引用指向的是Toy对象。newInstance创建的类必须有默认的构造器。

类字面常量

FancyToy.class,不仅简单而且安全因为他在编译时就会受到检查,并且根除了对forName的调用,所以也更高效。
基本数据类型:
这里写图片描述

.class创建对Class对象的引用时,包含三个步骤:
加载,类加载器执行查找字节码,并从字节码中创建Class对象
链接,验证类中的字节码,为静态域分配存储空间
初始化,初始化,执行静态初始化器和静态初始化块

public class Initable {
    static final int staticFinal = 47;
    static final int staticFinal2 = ClassInstrumentation.rand.nextInt(1000);
    static {
        System.out.println("Initializing Initable");
    }
}
public class Initable2 {
    static int staticNonFinal = 147;
    static {
        System.out.println("Initializing Initable2");
    }
}
public class Initable3 {
    static int staticNonFinal = 74;
    static {
        System.out.println("Initializing Initable3");
    }
}
public class ClassInstrumentation {
    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");
        System.out.println(Initable.staticFinal);
        System.out.println(Initable.staticFinal2);
        System.out.println(Initable2.staticNonFinal);
        Class initable3 = Class.forName("thinking7.Initable3");
        System.out.println("After creating Initable2 ref");
        System.out.println(Initable3.staticNonFinal);
    }
}/*
After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable2 ref
74
*/

使用.class不会引起初始化。
如果一个static final值是编译期常量,就像Initable.staticFinal,那么这个值不需要Initable类进行初始化就可以被读取。但是,如果只是将一个域设置为static和final的,还不足以确保这种行为。Initable.staticFinal2不是编译期常量。
如果一个static域不是final的对他访问时总要在他被读取之前,先进行链接(分配存储空间)和初始化(初始化该存储空间)。就像Initable2.staticNonFinal。

泛化的Class引用

Class引用总是指向某个Class对象,它可以制造类的实例,并包含可作用于这些实例的所有方法代码。它还包含该类的静态成员。因此,Class引用表示的就是它所指向的对象的确切类型,而该对象便是Class类的一个对象。
Jave SE5允许对Class引用指向的Class对象的类型进行限定实现的,这里用到了泛型语法:

public class GenericClassRefernces {
    public static void main(String[] args) {
        Class intClass = int.class;
        Class<Integer> genericIntClass = int.class;
        genericIntClass = Integer.class;
        intClass = double.class;
        //genericIntClass = double.class; //Illegal
    }
}

普通的类引用可以被重新赋值为指向任何其他的Class对象。通过使用泛型语法,可以让编译器强制执行额外的类型检查。

Class<Number> genericIntClass = int.class;

这看起来起作用,因为Integer继承自Number,但是它无法工作,因为Integer Class对象不是Number Class对象的子类。

使用通配符?:

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

Java SE5中,Class

public class BoundedClassRefernces {
    public static void main(String[] args) {
        Class<? extends Number> bounded = int.class;
        bounded = double.class;
        bounded = Number.class;
    }
}

向Class引用添加泛型语法仅仅是为了提供编译器类型检查。
泛型类语法:存储一个类引用,稍候又产生一个list,使用newInstance填充这个List对象,通过该引用生成的:

public 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;
    }
    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));
    }
}/*
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
*/

这个类必须假设与同一工作的任何类型都具有一个默认的构造器,并且如果不符合该条件,将得到一个异常。编译器不会对该程序不会产生任何警告信息。
newInstance()将返回该对象的确切类型,而不仅仅是ToyTest中基本的Object:

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();
        // Class<Toy> up2 = ftClass.getSuperclass();
        Object obj = up.newInstance();
    }
}

如果是超类,编译器将只允许声明超类引用就像Class

新的转型语法
public class ClassCasts {
    public static void main(String[] args) {
        Building b = new House();
        Class<House> houseClass = House.class;
        House h = houseClass.cast(b);
        h = (House) b;
    }
}

cast方法接受参数对象,并将其转型为Class对象的引用。
Class.asSubclass允许将一个对象转型为更加具体的类型。

类型转换前先做检查

RTTI形式:
1.传统的类型转换,ClassCastException
2.代表对象的类型的Class对象,通过查询Class对象获取运行时所需信息

RTTI在java中的第三种形式就是关键字instanceof,他返回一个布尔值,告诉我们对象是不是某个特定类型的实例:

if(x instanceof Dog){
    ((Dog)x).bark();
}

继承自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 {
    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 {
    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 Cymric extends Manx {
    public Cymric(String name) {
        super(name);
    }
    public Cymric() {

    }
}
public class Rodent extends Pet {
    public Rodent(String name) {
        super(name);
    }
    public Rodent() {
        super();
    }
}
public class Rat extends Rodent {
    public Rat(String name) {
        super(name);
    }
    public Rat() {
        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();
    }
}

我们需要一种方法,通过它可以随机地创建不同类型的宠物,并且为方便起见,还可以创建宠物数组和List。为了使该工具适应多种不同的实现,我们将其定义为抽象类:

public abstract class PetCreator {
    private Random rand = new Random(47);
    public abstract List<Class<? extends Pet>> types();
    public Pet randomPet() {
        int n = rand.nextInt(types().size());
        try {
            return types().get(n).newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
    public Pet[] createArray(int size) {
        Pet[] result = new Pet[size];
        for(int i = 0; i < size; i++){
            result[i] = randomPet();
        }
        return result;
    }
    public ArrayList<Pet> arrayList(int size) {
        ArrayList<Pet> result = new ArrayList<>();
        Collections.addAll(result, createArray(size));
        return result;
    }
}

抽象的getTypes方法在导出类中出现,以获取由Class对象构成的List。randomPet随机地产生List中的索引,并使用被选取的Class对象,通过Class.newInstance来生成该类的新实例。createArray方法使用randomPet来填充数组,而arrayList方法使用的则是createArray。
在调用newInstance时,可能会得到两种异常,在几根try语句块后面的catch子句可以看到它们的处理。异常的名字在一次为了一种错误类型相对比较有用的解释(IllegalAccessException 违反了Java安全机制)。

getTypes方法通常只返回对一个静态List的引用,下面是使用forName的一个具体实现:

public class ForNameCreator extends PetCreator {
    private static List<Class<? extends Pet>> types = new ArrayList<>();
    private static String[] typeNames = {
            "thinking3.Mutt",
            "thinking3.Pug",
            "thinking3.EgyptianMau",
            "thinking3.Manx",
            "thinking3.Cymric",
            "thinking3.Rat",
            "thinking3.Mouse",
            "thinking3.Hamster"   
    };
    @SuppressWarnings("unchecked")
    private static void loader() {
        try {
            for(String name : typeNames) {
                types.add((Class<? extends Pet>)Class.forName(name));
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
    static {
        loader();
    }
    @Override
    public List<Class<? extends Pet>> types() {
        return types;
    }
}

为了产生具有实际类型的Class对象的List,必须使用转型,这会产生编译器警告。loader方法被单独定义,然后置于一个静态初始化子句中,因为@SuppressWarnings(“unchecked”)注解不能直接置于初始化子句之上。
使用instanceof来对Pet进行计数:

public class PetCount {
    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);
            }
        }
    }
    public static  void countPets(PetCreator creator) {
        PetCounter counter = new PetCounter();
        for(Pet pet : creator.createArray(20)) {
            System.out.print(pet.getClass().getSimpleName() + " ");
            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");
            }
        }
        System.out.println();
        System.out.println(counter);
    }

    public static void main(String[] args) {
        countPets(new ForNameCreator());
    }
}/*
Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric 
{EgyptianMau=7, Pug=3, Rat=2, Cymric=7, Mouse=2, Cat=9, Manx=7, Rodent=5, Mutt=3, Dog=6, Pet=20, Hamster=1}
*/

在CountPets中,是使用PetCreator来随机地向数组中填充Pet的。然后使用instanceof对该数组中的每个Pet进行测试和计数。
instanceof只可将其与命名类型进行比较,而不能与Class对象作比较。可能觉得一大堆的instanceof表达式是很乏味的,但也没有更好的办法,如果你的程序中存在过多的instanceof表达式,说明你的设计可能存在瑕疵。

使用类字面常量
public class LiteralPetCreator extends PetCreator {
  @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));
  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 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]
*/

这一次,生成types的代码不需要放在try块内,因为它会在编译时得到检查,因此,它不会抛出任何异常,这与Class.forName不一样。
现在类库中有了两种PetCreator的实现,为了将第二种做为默认实现,创建一个使用LiteralPetCreator 的外观:

public class Pets {
    public static final PetCreator creator =
    new LiteralPetCreator();
    public static Pet randomPet() {
        return creator.randomPet();
    }
    public static Pet[] createArray(int size) {
        return creator.createArray(size);
    }
    public static ArrayList<Pet> arrayList(int size) {
        return creator.arrayList(size);
    }
}

这个类还提供了对randomPet、createArray和arrayList的间接调用。
因为PetCount.countPets接受的是一个PetCreator参数:

public class PetCount2 {
    public static void main(String[] args) {
        PetCount.countPets(Pets.creator);
    }
}/*
Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric 
{EgyptianMau=7, Pug=3, Rat=2, Cymric=7, Mouse=2, Cat=9, Manx=7, Rodent=5, Mutt=3, Dog=6, Pet=20, Hamster=1}
*/
动态的instanceof

Class.isInstance方法提供了一种动态地测试对象的途径。

public class PetCount3 {
  static class PetCounter
  extends LinkedHashMap<Class<? extends Pet>,Integer> {
    public PetCounter() {
      super(MapData.map(LiteralPetCreator.allTypes, 0));
    }
    public void count(Pet pet) {
      // Class.isInstance() eliminates instanceofs:
      for(Map.Entry<Class<? extends Pet>,Integer> pair : entrySet()){
        if(pair.getKey().isInstance(pet)) {
          put(pair.getKey(), pair.getValue() + 1);
        }
      }

    }   
    @Override
    public String toString() {
      StringBuilder result = new StringBuilder("{");
      for(Map.Entry<Class<? extends Pet>,Integer> pair
          : entrySet()) {
        result.append(pair.getKey().getSimpleName());
        result.append("=");
        result.append(pair.getValue());
        result.append(", ");
      }
      result.delete(result.length()-2, result.length());
      result.append("}");
      return result.toString();
    }
  } 
  public static void main(String[] args) {
    PetCounter petCount = new PetCounter();
    for(Pet pet : Pets.createArray(20)) {
      printnb(pet.getClass().getSimpleName() + " ");
      petCount.count(pet);
    }
    print();
    print(petCount);
  }
}/*
Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric 
{EgyptianMau=7, Pug=3, Rat=2, Cymric=7, Mouse=2, Cat=9, Manx=7, Rodent=5, Mutt=3, Dog=6, Pet=20, Hamster=1}
*/

为了对所有不同类型的Pet进行计数,PetCounter Map预加载了LiteralPetCreator.allTypes中的类型。这使用了MapData类,这个类接受一个Iteralbe(allTypes List)和一个常数值,然后用allTypes中元素作为键,用0作为值,来填充Map。
可见isInstance方法使我们不需要instanceof表达式。

递归计数

PetCount3 的PetCounter的Map预加载了所有不同的Pet类。与预加载映射表不同的是,可以使用Class.isAssignableFrom,并创建一个不局限于对Pet计数的通用工具:

public class TypeCounter extends HashMap<Class<?>, Integer> {
    private Class<?> baseType;

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

    public void count(Object obj) {
        Class type = obj.getClass();
        if(!this.baseType.isAssignableFrom(type)) {
            throw new RuntimeException(obj + " incorrect type: " + type + ", should be type or subtype of " + this.baseType);
        } else {
            this.countClass(type);
        }
    }

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

    }

    public String toString() {
        StringBuilder result = new StringBuilder("{");
        Iterator var3 = this.entrySet().iterator();

        while(var3.hasNext()) {
            Entry pair = (Entry)var3.next();
            result.append(((Class)pair.getKey()).getSimpleName());
            result.append("=");
            result.append(pair.getValue());
            result.append(", ");
        }

        result.delete(result.length() - 2, result.length());
        result.append("}");
        return result.toString();
    }
}

count方法获取其参数的Class,然后使用isAssignableFrom来执行运行时的检查,以校验你传递的对象确实属于感兴趣的继承结构。countClass首先对该类的确切类型计数,然后,如果其超类可以赋值给baseType,countClass将其超类上递归计数。

public class PetCount4 {
  public static void main(String[] args) {
    TypeCounter counter = new TypeCounter(Pet.class);
    for(Pet pet : Pets.createArray(20)) {
      printnb(pet.getClass().getSimpleName() + " ");
      counter.count(pet);
    }
    print();
    print(counter);
  }
}/*
Rat Manx Cymric Mutt Pug Cymric Pug Manx Cymric Rat EgyptianMau Hamster EgyptianMau Mutt Mutt Cymric Mouse Pug Mouse Cymric 
{Mutt=3, Mouse=2, Pet=20, Dog=6, EgyptianMau=2, Rodent=5, Cat=9, Hamster=1, Cymric=5, Rat=2, Manx=7, Pug=3}
*/

注册工厂

生成Pet继承结构中的对象存在着一个问题,即每次向该继承结构添加新的Pet类型时,必须将其添加为LiteralPetCreator.java中的项。
使用工厂方法设计模式,将对象的创建工作交给类自己去完成。工厂方法可以被多态地调用,从而为你创建恰当类型的对象。工厂方法就是Factory接口中的create方法:

public interface Factory<T> { T create(); } 

泛型T是的create可以在每种Factory实现中返回不同的类型,这也充分利用了协变返回类型。
下面基类Part包含了一个工厂对象的列表。对于应这个由createRandom方法产生的类型,他们的工厂都被添加到了partFactories List中,从而被注册到了基类中:

class Part {
  @Override
  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 typeinfo.factory.Factory<FuelFilter> {
    @Override
    public FuelFilter create() { return new FuelFilter(); }
  }
}

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

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

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

class Belt extends Part {}

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

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

class PowerSteeringBelt extends Belt {
  public static class Factory
  implements typeinfo.factory.Factory<PowerSteeringBelt> {
    @Override
    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());
    }
  }
} /* 
GeneratorBelt
CabinAirFilter
GeneratorBelt
AirFilter
PowerSteeringBelt
CabinAirFilter
FuelFilter
PowerSteeringBelt
PowerSteeringBelt
FuelFilter
*/

并非所有在继承结构中的类都应该被实例化,在本例中,Filter和Belt只是分类标识,因此不应该创建实例。如果某个类应该由createRandom方法创建,那么它就包括一个内部Factory类。createRandom方法从partFactories中随机地选取了一个工厂对象,然后调用其create方法,从而产生一个新的Part。

instanceof与Class的等价性

在查询类型信息时,以instanceof的形式与直接比较Class对象有一个很重要的差别:

public class FamilyVsExactType {
  static void test(Object x) {
    print("Testing x of type " + x.getClass());
    print("x instanceof Base " + (x instanceof Base));
    print("x instanceof Derived "+ (x instanceof Derived));
    print("Base.isInstance(x) "+ Base.class.isInstance(x));
    print("Derived.isInstance(x) " +
      Derived.class.isInstance(x));
    print("x.getClass() == Base.class " +
      (x.getClass() == Base.class));
    print("x.getClass() == Derived.class " +
      (x.getClass() == Derived.class));
    print("x.getClass().equals(Base.class)) "+
      (x.getClass().equals(Base.class)));
    print("x.getClass().equals(Derived.class)) " +
      (x.getClass().equals(Derived.class)));
  }
  public static void main(String[] args) {
    test(new Base());
    test(new Derived());
  } 
}/*
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
*/

test方法使用了两种形式的instanceof作为参数来执行类型检查,然后获取Class引用,并用==和equals来检查Class对象是否相等。二者结果相同,equals和==也一样。但结论不同,instanceof保持了类型的概念,它指的是你是这个类吗,或者你是这个类的派生类吗?而如果用==比较实际的Class对象,就没有考虑继承——它或者是这个确切类型,或者不是。

反射:运行时的类信息

RTTI有一个限制:这个类型在编译时必须已知,也就是说编译器必须知道所有要通过RTTI来处理的类。人们想要在运行时获取类的信息的另一个动机,便是希望提供在跨网络的远程平台上创建和运行对象的能力,被称为远程方法调用(RMI),它允许一个Java程序将对象分布到多台机器上。
Class类与java.lang.reflect类库一起对反射概念进行了支持。该类库包含了Field、Method以及Constructor类。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。这样就可以使用Constructor创建新的对象,用get和set方法读取和修改与Field对象关联的字段,用invoke方法调用与Method对象关联的方法。另外,还可以调用getFields、getMethods和getConstructors等方法,以返回表示字段、方法以及构造器的对象的数组。
当通过反射与一个未知类型的对象打交道时,JVM只是简单地检查这个对象,看它属于哪个特定类。在用它做其他事情之前必须先加载那个类的Class对象。因此,那个类的.class文件对于JVM来说必须是可获取的:要么在本地机器上,要么可以通过网络取得。所以RTTI和反射之间真正的区别在于,对RTTI来说,编译器在编译时打开和检查.class文件,对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。

类方法提取器

反射在Java中用来支持其他特性,例如对象序列化和JavaBean。但是,如果能动态地提取某个类的信息有的时候还是有用的,反射机制提供了一种方法,使我们能够编写可以自动展示完整接口的简单工具:

// {Args: typeinfo.ShowMethods}
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'";
  private static Pattern p = Pattern.compile("\\w+\\.");
  public static void main(String[] args) {
    int lines = 0;
    try {
      Class<?> c = Class.forName(args[0]);
      Method[] methods = c.getMethods();
      Constructor[] ctors = c.getConstructors();
      if(args.length == 1) {
        for(Method method : methods) {
          System.out.println(p.matcher(method.toString()).replaceAll(""));
        }
        for(Constructor ctor : ctors){
          System.out.println(p.matcher(ctor.toString()).replaceAll(""));
        }
        lines = methods.length + ctors.length;
      } else {
        for(Method method : methods) {
          if (method.toString().indexOf(args[1]) != -1) {
            System.out.println(p.matcher(method.toString()).replaceAll(""));
            lines++;
          }
        }
        for(Constructor ctor : ctors) {
          if (ctor.toString().indexOf(args[1]) != -1) {
            System.out.println(p.matcher(ctor.toString()).replaceAll(""));
            lines++;
          }
        }
      }
    } catch(ClassNotFoundException e) {
      print("No such class: " + e);
    }
  }
}/*
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()
*/

Class的getMethods和getConstructors方法分别返回(public的)Method对象的数组和Constructor对象的数组。这两个类都提供了深层方法,用以解析其对象所代表的方法,并获取其名字、输入参数以及返回值,toString生成一个含有完整的方法特征签名的字符串。
反射机制提供了足够的支持,使得能够创建一个在编译时完全未知的对象,并调用此对象的方法。
输出中有一个public的默认构造器,这个构造器是编译器自动合成的。如果ShowMethods做为一个非public类,输出中就不会在显示这个构造器了。该自动合成的默认构造器会自动被赋予与类一样的访问权限。
在编程时,特别是如果不记得一个类是否有某个方法,或者不知道一个类究竟能做些什么,例如Color对象,而又不想通过索引或类的层次结构去查找JDK文档,这时这个工具确实能节省很多时间。

动态代理

代理是最基本的设计模式之一,它是为了提供额外的或者不同的操作,而插入用来替换实际对象的对象。这些操作通常涉及与实际对象的通信,因此代理通常充当着中间人的角色。

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

class RealObject implements Interface {
  public void doSomething() { print("doSomething"); }
  public void somethingElse(String arg) {
    print("somethingElse " + arg);
  }
}   

class SimpleProxy implements Interface {
  private Interface proxied;
  public SimpleProxy(Interface proxied) {
    this.proxied = proxied;
  }
  public void doSomething() {
    print("SimpleProxy doSomething");
    proxied.doSomething();
  }
  public void somethingElse(String arg) {
    print("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()));
  }
}/*
doSomething
somethingElse bonobo
SimpleProxy doSomething
doSomething
SimpleProxy somethingElse bonobo
somethingElse bonobo
*/

SimpleProxy已经被插入到了客户端和RealObject之间,因此他会执行操作,然后调用RealObject上相同的方法。
在任何时刻,只要想将额外操作从实际对象中分离到不同的地方,特别是当希望能够很容易地做出修改,从没有使用额外操作额外操作转为使用这些操作,或者反过来时,代理就显得很有用。
Java的动态代理比代理的思想更前一步。因为它可以动态地创建代理并动态地处理对所代理方法的调用。在动态代理上所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策:

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(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);
  }
}/*
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;@63947c6b
  bonobo
somethingElse bonobo
*/

通过Proxy.newProxyInstance可以创建动态代理,这个方法需要一个类加载器,一个希望该代理实现的接口列表,以及InvocationHandler 接口的一个实现。动态代理可以将所有调用重定向到调用处理器,因此通常会向调用处理器的构造器传递实际对象的引用,从而使得调用处理器在执行其中介任务时,可以将请求转发。
invoke内部在代理上调用方法时需要格外当心,因为对接口的调用将重定向为对代理的调用。

执行被代理的操作,然后使用Method.invoke将请求转发给代理对象,并传入必须的参数,看起来可能有些受限,但通过传递其他参数,来过滤某些方法调用:

class MethodSelector implements InvocationHandler {
  private Object proxied;
  public MethodSelector(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 arg);
  void boring3();
}

class Implementation implements SomeMethods {
  public void boring1() { print("boring1"); }
  public void boring2() { print("boring2"); }
  public void interesting(String arg) {
    print("interesting " + arg);
  }
  public void boring3() { print("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()));
    proxy.boring1();
    proxy.boring2();
    proxy.interesting("bonobo");
    proxy.boring3();
  }
}/*
boring1
boring2
Proxy detected the interesting method
interesting bonobo
boring3
*/

空对象

null除了在试图用它执行任何操作来产生NullPointerException之外,它自己没有其他任何行为。有时引入空对象的思想将会很有用,它可以接受传递给它的所代表的对象的消息,但是将返回表示为实际上并不存在任何真实对象的值。通过这种方式,可以假设所有的对象都是有效的,而不必浪费编程精力去检查null。
空对象最有用之处在于他更靠近数据,因为对象表示的是问题空间内的实体。很多系统都有一个Person类,而在代码中,有很多情况没有一个实际的人,因此,通常会用一个null引用并测试它。与此不同的是,我们可以使用空对象。但是即使空对象可以响应实际对象可以响应的所有信息,仍需某种方式来测试其是否为空,要达到此目的,创建一个标记接口:

public interface Null {
}

这使得instanceof可以探测空对象,并不要求在所有类中都添加isnull方法:

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

通常空对象都是单例,因此这里将其作为静态final实例创建。这可以正常工作,因为Person的不可变的,你只能在构造器中设置他的值,然后读取这些值,但是不能让修改他们(String自身具备内在的不可变型)。如果你想要修改一个NullPerson,那只能用一个新的Person对象来替换它。注意,可以选择使用instanceof来测试泛化的Null还是更具体的NullPerson,但是由于使用了单例方式,所以还可以只使用equals甚至==来与Person.Null比较。
将Person空对象放在每个Position上:

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;
  }
}

有了Position,就不需要创建空对象了,因为Person.Null的存在就表示这是一个空Position。稍后可能会发现需要增加一个显式的用于Position的空对象,但是YAGNI(You Aren’t Going to Need It,你永不需要它)。
Staff类现在可以填充职位时查询空对象:

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);
  }
}/*
[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]
*/

用接口取代具体类,那么就可以使用DynamicProxy来自动地创建空对象。假设有一个Robot接口,它定义了一个名字、一个模型和一个描述Robot行为能力的List。Operation包含一个描述和一个命令:

public interface Operation {
  String description();
  void command();
} 
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();
      }
    }
  }
}

创建一个扫雪机器人:

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

假设存在许多不同类型的Robot,相对每一种Robot类型都创建一个空对象,去执行某特殊操作——即提供空对象所代表的Robot确切类型信息,这些信息是通过动态代理捕获的:

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();
    }
  } 
  @Override
  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);
    }
  }
}/*
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
*/

无论如何,如果需要一个空Robot对象,只需调用newNullRobot,并传递需要代理的Robot类型。代理会满足Robot和NUll接口的需求,并提供它所代理的类型的确切名字。

模型对象与桩

空对象的逻辑变体是模拟对象与桩。与空对象一样,都表示在最终的程序中所使用的实际对象。但是,模拟对象和桩都只是假扮传递实际信息的存活对象,而不是像空对象那样可以成为null的一种更智能化的替代物。
模拟对象和桩之间存在差异在于程度不同。模拟对象往往是轻量级和自测试的,通常很多模拟对象被创建出来是为了处理各种不同的测试情况。桩只是返回桩数据,在测试之间被复用。可以根据他们被调用的方式,通过配置进行修改。

接口与类型信息

interface关键字一个重要目标就是允许程序员隔离构件,进行降低耦合性。接口并非是对解耦的一种无懈可击的保障:

public interface A {
  void f();
}

实现这个接口,可以看到其代码是如何围绕着实际的实现类型潜行的:

class B implements A {
  @Override
  public void f() {}
  public void g() {}
}

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

通过使用RTTI,发现a是被当作B实现的。通过将其转型为B,可以调用不在A中的方法。
这是完全合法和可接受的,但是并不想让客户端程序员这么做,因为这使得他们的代码与你的代码的耦合程度超过你的期望。
一种解决方案是直接声明,如果程序员决定使用实际的类而不是接口,他们需要自己负责。最简单的方式是对实现使用包访问权限:

//typeinfo/packageaccess/HiddenC.java
class C implements A {
  public void f() { print("public C.f()"); }
  public void g() { print("public C.g()"); }
  void u() { print("package C.u()"); }
  protected void v() { print("protected C.v()"); }
  private void w() { print("private C.w()"); }
}

public class HiddenC {
  public static A makeA() { return new C(); }
}

这个包中唯一public部分是HiddenC,在被调用时将产生A接口类型的对象,这里有趣之处在于:即使你从makeA返回了是C类型,在包的外部仍旧不能使用A之外的任何方法,因为不能在包的外部命名C。
现在如果试图将其向下转型为C则被禁止,因为在包的外部没有任何C类型可用:

public class HiddenImplementation {
  public static void main(String[] args) throws Exception {
    A a = HiddenC.makeA();
    a.f();
    System.out.println(a.getClass().getName());
    // 找不到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);
  }
}/*
public C.f()
typeinfo.packageaccess.C
public C.g()
package C.u()
protected C.v()
private C.w()
*/

通过使用反射,可以到达并调用所有方法,甚至是private方法!如果知道方法名,可以在其Method对象上调用setAccessible(true),就像在callHiddenMethod中看到的那样。
可以通过只发布编译后的代码来阻止,但并不能解决问题。因为只需运行javap,一个随JDK发布的反编译器即可突破这一限制,命令行:
javap -private C
-private标志表示所有的成员都应该显示甚至私有成员,输出:

class typeinfo.packageaccess.C implements typeinfo.interfacea.A {
  typeinfo.packageaccess.C();
  public void f();
  public void g();
  void u();
  protected void v();
  private void w();
}

如果将接口实现为一个私有内部类:

class InnerA {
  private static class C implements A {
    public void f() { print("public C.f()"); }
    public void g() { print("public C.g()"); }
    void u() { print("package C.u()"); }
    protected void v() { print("protected C.v()"); }
    private void w() { print("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()
*///:~

对反射仍旧没有任何隐藏。
那么匿名类呢?

class AnonymousA {
  public static A makeA() {
    return new A() {
      public void f() { print("public C.f()"); }
      public void g() { print("public C.g()"); }
      void u() { print("package C.u()"); }
      protected void v() { print("protected C.v()"); }
      private void w() { print("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()
*///:~

看来没有任何方式可以阻止反射到达并调用那些非公共访问权限的方法。对于域来说,确实如此,即便是private域:

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);
    Field f = pf.getClass().getDeclaredField("i");
    f.setAccessible(true);
    System.out.println("f.getInt(pf): " + f.getInt(pf));
    f.setInt(pf, 47);
    System.out.println(pf);
    f = pf.getClass().getDeclaredField("s");
    f.setAccessible(true);
    System.out.println("f.get(pf): " + f.get(pf));
    f.set(pf, "No, you're not!");
    System.out.println(pf);
    f = pf.getClass().getDeclaredField("s2");
    f.setAccessible(true);
    System.out.println("f.get(pf): " + f.get(pf));
    f.set(pf, "No, you're not!");
    System.out.println(pf);
  }
} /* Output:
i = 1, I'm totally safe, Am I safe?
f.getInt(pf): 1
i = 47, I'm totally safe, Am I safe?
f.get(pf): I'm totally safe
i = 47, I'm totally safe, Am I safe?
f.get(pf): Am I safe?
i = 47, I'm totally safe, No, you're not!
*///:~

但是final实际上在遭遇修改时是安全的,运行时系统会在不抛出异常的情况下接受任何修改尝试,但是实际上不会发生任何修改。
如果有人使用这样的技术调用了private或包访问权限的方法,那么对他们来说,如果你修改了这些方法,他们不应该抱怨。另一方面,总是在类中留下后门这一事实,也许可以使得你能够解决某些特定类型的问题,但如果不这样做,这些问题或将难以解决,通常反射带来的好处是不可否认的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值