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。
  • 对于有些人来说,反射的动态特性是一种烦扰,对于已经习惯与静态类型检查的安全性的人来说,你可以执行一些只能在运行时进行的检查,并用异常来报告检查结果的行为,这本身就是一种错误的方法。有些人走的更远,他们声称引入运行时异常本身就是一种指示,说明应该避免这种代码。

 

 

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