多态&Class对象&注册工厂&反射&动态代理

类型信息(多态&Class对象&注册工厂&反射&动态代理)

运行时类型信息(RTTI)使得你可以在程序运行时发现和使用类型信息
以下将讨论Java是如何让我们在运行时识别对象的信息的。
主要有两种方式:

  • 一种是传统的RTTI:它假定了我们在编译时已经知道了所有的类型(在本程序中);
  • 另一种是:“反射”机制,它允许我们在运行时发现和使用类的信息。

一、为什么需要RTTI

先来看一个很熟悉的例子。

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

    @Override
    public String toString() {
        return "Circle";
    }
}
class Square extends Shape{
    @Override
    public String toString() {
        return "Square";
    }
}
public class Shapes {
    public static void main(String[] args) {
        List<Shape> shapeList = Arrays.asList(new Circle(), new Square());
        for (Shape shape : shapeList) {
            shape.draw();
        }
    }
}
/*输出
Circle.draw()
Square.draw()
 */

这是一个典型的类层次结构。面向对象编程的基本目的是:让代码只操纵对基类(Shape)的引用。这样,如果要添加一个新类来拓展程序,就不会影响到原来的代码。其中Shape接口中动态绑定了draw(),目的就是让程序员使用泛化的Shape引用来调用draw()。draw()在所有子类中都会重写,并且由于它是动态绑定的,所以即使是通过泛化的Shape来调用,也能产生正确行为。这就是多态。
基类中包含draw(),它通过传递this参数给输出语句,间接的调用toString()打印类标识符(toString()被声明为abstract,以此强制子类重写该方法,并且可以防止对无格式的Shape的实例化)。如果某个对象出现在字符串表达式中(涉及“+”和字符串对象的表达式:详情看字符串‘+’的使用),toString()方法就会自动调用,以生成表示该对象的string。
在这个例子中,当把Shape对象放入List<Shape>的数组时会向上转型。但在转型的过程中也丢失了Shape对象的具体类型。当从数组中取出对象时,这种容器----实际上它将所有的事务都当做Object持有----会自动将结果转型回Shape。这是RTTI最基本的使用形式,因为在Java中,所有的类型转换都是在运行时进行正确性检查的。这也是RTTI的含义:在运行时识别一个对象的类型。
但是假如你碰到了一个特殊的编程问题——如果能够知道某个泛型的确切类型,就可以使用最简单的方式去解决它,那么此时应该怎么办呢?例如:将某一具体的几何图形变为红色,以凸显他们。所以可以使用RTTI获取它的确切类型。

二、Class对象

要理解RTTI在Java中的工作原理,首先必须知道类型信息在运行时是如何表示的。
这项工作是由被称为Class对象 的特殊对象完成的,它包含了与类有关的信息。事实上,Class对象就是用来创建类的所有的“常规”对象的。Java使用Class对象来执行其RTTI,即使你正在执行的是转型这样的操作。Class对象还拥有大量的使用RTTI的其他方式。

类是程序的一部分,每一个类都有一个Class对象,每当编写并编译了一个新类,就会产生一个Class对象保存在同名的.class文件中。为了生成这个类的对象,运行这个程序的Java虚拟机(JVM)将使用被称为“类加载器”的子系统。
类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器。原生类加载器加载的是所谓的可信类(包括Java API类),它们通常都是从本地盘加载的。
所有的类都是在其第一次使用时,动态加载到JVM中的。当程序创建第一个对类的静态成员引用 时,就会加载这个类。这个证明构造器也是类的静态方法,即使构造器之前并没有使用static关键字。因此,使用new操作符创建类的新对象也会被当做对类的静态成员的引用。而其他的部分是在必需时才加载的,这叫做动态加载。
类加载器首先会检查这个类的Class对象是否已经被加载。如果尚未加载,默认的类加载器就会根据类名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保没有损坏且不包含不良代码。然后将它加载到内存中去,此时它就可以用来创建这个类的所有对象。
Class.forName("com.gui.demo.thingInJava.RTTI.Square"); 这个方法是Class类(所有Class对象都属于这个类)的一个static成员。Class对象就和其他对象一样,我们可以获取并操作它的引用(这也是类加载器的工作)。forName()是取得Class对象的引用的一种方法。
无论何时,只要你想在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用。Class.forName()就是实现此功能的便捷途径,因此你不需要为了获得Class引用而持有该类型的对象。但是,如果你已经拥有了一个感兴趣的类型的对象,那就可以通过调用getClass()来获取Class引用了,这个方法属于Object的一部分。

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

class Toy{
    Toy() { }
    Toy(int i) { }
}

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

public class ToyTest {
    static void printInfo(Class cc){
        System.out.println("Class name:" + cc.getName()+" is interface? ["+ cc.isInterface()+ "]");
        //产生不含包名的类名
        System.out.println("Simple name:" + cc.getSimpleName());
        //全限定的类名
        System.out.println("Canonical name:" + cc.getCanonicalName());
    }

    public static void main(String[] args) {
        Class c = null;
        try {
            c = Class.forName("com.gui.demo.thingInJava.RTTI.FancyToy");
        } catch (ClassNotFoundException e) {
            System.out.println("can't find FancyToy!");
            System.exit(1);
        }

        printInfo(c);
		//getInterfaces():返回的是Class对象,表示类对象中包含的接口。
        for (Class face : c.getInterfaces()) {
            printInfo(face);
        }
        Class up = c.getSuperclass();
        Object obj = null;
        try {
        	//构造实例化:相当于调用默认构造器
            obj = up.newInstance();
        } catch (InstantiationException e) {
            System.out.println("can not instantiate");
            System.exit(1);
        } catch (IllegalAccessException e) {
            System.out.println("can not access");
            System.exit(1);
        }

        printInfo(obj.getClass());
    }
}
/*
输出:
Class name:com.gui.demo.thingInJava.RTTI.FancyToy is interface? [false]
Simple name:FancyToy
Canonical name:com.gui.demo.thingInJava.RTTI.FancyToy
Class name:com.gui.demo.thingInJava.RTTI.HasBatteries is interface? [true]
Simple name:HasBatteries
Canonical name:com.gui.demo.thingInJava.RTTI.HasBatteries
Class name:com.gui.demo.thingInJava.RTTI.Waterproof is interface? [true]
Simple name:Waterproof
Canonical name:com.gui.demo.thingInJava.RTTI.Waterproof
Class name:com.gui.demo.thingInJava.RTTI.Shoots is interface? [true]
Simple name:Shoots
Canonical name:com.gui.demo.thingInJava.RTTI.Shoots
Class name:com.gui.demo.thingInJava.RTTI.Toy is interface? [false]
Simple name:Toy
Canonical name:com.gui.demo.thingInJava.RTTI.Toy
 */

FancyToy继承自Toy并实现了三个接口。在main()中,用forName()在适当的try语句块中,创建了一个Class引用,并将其初始化为指向FancyToy Class。
printInfo()使用getName()来产生权限定的类名,并分别使用getSimpleName()和getCanonicalName()来产生不含包名的类名和全限定的类名。isInterface判断这个类是否是接口。
Class的newInstance()方法是实现“虚拟构造器”的一种途径,虚拟构造器允许你声明:“我不知道你的确切类型,但是无论如何要正确的创建你自己。”up仅仅只是一个Class引用,在编译器不具备任何更进一步的信息。当你创建新实例时 ,会得到Object引用,但是引用指向的是Toy对象。此外,使用newInstance()来创建的类,必须带有默认的构造器。后面将会看到如何通过使用Java的反射API,用任意的构造器来动态的创建类的对象。

//如果将Toy中的默认构造器注掉,打印结果将会发生如下变化
Class name:com.gui.demo.thingInJava.RTTI.FancyToy is interface? [false]
Simple name:FancyToy
Canonical name:com.gui.demo.thingInJava.RTTI.FancyToy
Class name:com.gui.demo.thingInJava.RTTI.HasBatteries is interface? [true]
Simple name:HasBatteries
Canonical name:com.gui.demo.thingInJava.RTTI.HasBatteries
Class name:com.gui.demo.thingInJava.RTTI.Waterproof is interface? [true]
Simple name:Waterproof
Canonical name:com.gui.demo.thingInJava.RTTI.Waterproof
Class name:com.gui.demo.thingInJava.RTTI.Shoots is interface? [true]
Simple name:Shoots
Canonical name:com.gui.demo.thingInJava.RTTI.Shoots
can not instantiate//此处它的基类将不能被实例化

再写一个例子来加深印象:
写一个方法:令它接受任意对象作为参数,并能递归打印出该对象所在的继承体系中的所有类。

public class RandomSuper {
    static void printClasses(Class<?> c) {
        if (c == null) return;
        System.out.println(c.getName());
        for (Class<?> k : c.getInterfaces()) {
            System.out.println("Interface: " + k.getName());
            System.out.println(k.getSuperclass());
        }
        printClasses(c.getSuperclass());
    }
    public static void main(String[] args) throws ClassNotFoundException {
        args = new String[]{"com.gui.demo.thingInJava.RTTI.FancyToy","com.gui.demo.thingInJava.RTTI.Square"};
        for (int i = 0; i < args.length; i++) {
            System.out.println("Displaying " + args[i]);
            printClasses(Class.forName(args[i]));

            if (i < args.length - 1) {
                System.out.println("=================================");
            }
        }
    }
}
/*
输出:
Displaying com.gui.demo.thingInJava.RTTI.FancyToy
com.gui.demo.thingInJava.RTTI.FancyToy
Interface: com.gui.demo.thingInJava.RTTI.HasBatteries
null
Interface: com.gui.demo.thingInJava.RTTI.Waterproof
null
Interface: com.gui.demo.thingInJava.RTTI.Shoots
null
Interface: com.gui.demo.thingInJava.RTTI.DNF
null
com.gui.demo.thingInJava.RTTI.Toy
java.lang.Object
=================================
Displaying com.gui.demo.thingInJava.RTTI.Square
com.gui.demo.thingInJava.RTTI.Square
com.gui.demo.thingInJava.RTTI.Shape
java.lang.Object
 */

现在我们可以通过Toy toy1 = up.getDeclaredConstructor(int.class).newInstance();来创建这个对象的引用。

2.1 类字面常量

Java还提供了另一种方法来生成对Class对象的引用,即使用类字面常量:FancyToy.class;
这样做不仅更简单,而且更安全,因为它在编译时就会收到检查(因此不需要置于try语句块中)。并且它根除了对forName()的调用,所以也更高效。
类字面常量不仅可以应用于普通的类,也可以应用于接口、数组以及基本数据类型。
当使用“.class”来创建对Class对象的引用时,不会自动地初始化该对象。为了使用类而做的准备工作实际包含三个步骤:
1.加载。这是由类加载器执行的。该步骤将查找字节码(通常在classpath所指定的路径中查找,但这并非是必需的),并从这些字节码中创建一个Class对象。
2. 链接。在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类创建的对其他类的所有引用。
3. 初始化。如果该类具有超类(基类),则对其初始化,执行静态初始化器和静态初始化块。初始化被延迟到了对静态方法(切记构造器是隐式的静态方法)或者非常数静态域进行首次引用时才执行

class Initable{
    static final int staticFinal = 47;
    static final int StaticFinal2 = ClassInitialization.rand.nextInt(1000);
    static {
        System.out.println("Initializing Initable");
    }
}
class Initable2{
    static int staticNonFinal = 147;
    static {
        System.out.println("Initializing Initable2");
    }
}
public class ClassInitialization {
    public static Random rand = new Random(47);

    public static void main(String[] args) {
        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);
    }
}
/*输出:
After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
 */

可以看出,仅使用.class语法来获得对类的引用并不会引发初始化。但是,Class.forName()立即就进行了初始化。
如果一个static final值是“编译器常量”,就像Initable.staticFinal那样,那么不需要初始化就可以读取到。但是如果只是将一个域设置为static和final的,还不足以确保这种行为,如对Initable.StaticFinal2的访问将强制进行类的初始化,因为它不是一个编译器常量
如果一个static域不是final的,那么在对它访问时,总是要求在它被读取之前必须先进行链接(为对象分配空间)和初始化(初始化该空间)

2.2 泛化的Class引用

通过泛化语法对Class对象的类型可以进行限定:Class<Integer> genericIntClass = int.class;
Class<?>优于平凡的Class,即便它们是等价的,虽然平凡的Class也不会产生编译器警告信息,但是Class<?>的好处就是它表示你不是碰巧使用了一个非具体的引用,而是你就是选择了非具体的版本。
除此之外,为了创建一个Class引用将它限制为某种类型或该类型的任何子类型,你需要将通配符与extends关键字相结合来创建一个范围:Class<? extends Number> bounded = int.class;
向Class引用添加泛型语法的原因仅仅是为了提供编译类型检查,这样如果你操作有误就会立刻发现它。在使用普通Class引用,你得知道运行时才能发现它。

class CountedInteger{
    private static long counter;
    private final long id = counter++;
    public String toString(){
        return Long.toString(id);
    }
}
public class FilledList<T> {
    private Class<T> type;

    public FilledList(Class<T> type) {
        this.type = type;
    }
    
    public List<T> create(int nElements) {
        List<T> results = new ArrayList<>();
        try {
            for (int i = 0; i < nElements; i++) {
            	//newInstance需要默认构造器
                results.add(type.newInstance());
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return results;
    }

    public static void main(String[] args) {
        FilledList<CountedInteger> filledList = new FilledList<>(CountedInteger.class);
        System.out.println(filledList.create(15));
    }
}
/*输出:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
 */

注意:这个类必须假设与它一起工作的任何类型都具有一个默认的构造器;并且如果不符合条件,你将会得到一个运行时异常。当你将泛型应用于Class对象时,会发生一件很有趣的事情:newInstance()将返回该对象的确切类型,而不仅仅是一个基本的Object类型。

三. 类型转换前先做检查

在以上的论述中,我们已知的RTTI形式包括:

  1. 传统的类型转换:由RTTI确保转换 的正确性,如果执行了一个错误的类型转换就会抛出一个ClassCastException。
  2. 代表对象的类型的Class对象:通过查询Class对象可以获得运行时所需的信息。

RTTI在Java中还有第三种形式,就是关键字instanceof。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例。if(x instanceof Dog) ((Dog)x).bark();

//每一个public class为一个类
public abstract class PetCreator {
    private Random random = new Random(47);
    /*
    创建不同类型的宠物:抽象的types方法在导出类中实现,以获取由Class对象构成的list(这是模板方法设计模式的一种变体)
    注意:其中类的类型被指定为“任何从Pet导出的类”。因此newInstance()不需要转型就可以产生Pet。
    当导出PetCreator的子类时,唯一所提供的就是创建宠物类型的List
     */
    public abstract List<Class<? extends Pet>> types();
    //随机获取宠物类型
    public Pet randomPet(){
        int n = random.nextInt(types().size());
        try {
            return types().get(n).newInstance();
        } catch (IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }
        return null;
    }

    //创建宠物数组
    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;
    }
}
/******************************************************************************/
public class ForNameCreator extends PetCreator{
    private static List<Class<? extends Pet>> types = new ArrayList<>();
    //你想随机创建的类型
    private static String[] typeNames = {
            "com.gui.demo.thingInJava.RTTI.Mutt",
            "com.gui.demo.thingInJava.RTTI.Pug",
            "com.gui.demo.thingInJava.RTTI.Cat",
            "com.gui.demo.thingInJava.RTTI.EgyptianMau",
            "com.gui.demo.thingInJava.RTTI.Cymric"
    };
    /*
     * loader()用Class.forName()创建了Class对象的List,为了产生具有实际类型的Class对象的List,必须使用转型。
     * loader()被单独定义,然后被置于一个静态初始化子句中,因为@SuppressWarnings注解不能直接置于静态初始化子句上
     * @author gt
     * @date 2021/4/22 9:52 
     */
    @SuppressWarnings("unchecked")
    private static void loader(){
        try {
            for (String name : typeNames) {
                //将宠物类型添加到types中,供选择
                types.add(
                        (Class < ? extends Pet >) Class.forName(name));
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    @Override
    public List<Class<? extends Pet>> types() {
        return types;
    }
    static {
        loader();
    }
}

/*****************************************************************************************/

public class PetCount 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 petCreator) {
        PetCount count = new PetCount();
        for (Pet pet : petCreator.createArray(20)) {
            System.out.print(pet.getClass().getSimpleName() + " ");
            if (pet instanceof Pet) count.count("Pet");
            if (pet instanceof Dog) count.count("Dog");
            if (pet instanceof Mutt) count.count("Mutt");
        }
        System.out.println();
        System.out.println(count);
    }

    public static void main(String[] args) {
        countPets(new ForNameCreator());
    }
}
/*
输出:
EgyptianMau Mutt EgyptianMau Pug Pug Cymric EgyptianMau Mutt Cat Cat EgyptianMau EgyptianMau Pug Cymric Cymric EgyptianMau EgyptianMau Pug Mutt EgyptianMau 
{Mutt=3, Dog=7, Pet=20}
 */

在CountPets()中,使用PetCreator来随机生成Pet,然后使用instanceof对该数组中的每个Pet进行测试和计数;对instanceof有比较严格的限制:只可以将其与命名类型进行比较,而不能与Class对象作比较。除此之外,instanceof一个个比较很累赘。

3.1 使用类字面常量

public class LiteralPetCountor extends PetCreator {
    @SuppressWarnings("unchecked")
    public static final List<Class<? extends Pet>> allTypes = Collections.unmodifiableList(Arrays.asList(
            Pet.class, Dog.class, Cat.class, Mutt.class, Pug.class, EgyptianMau.class, Cymric.class));

    public static final List<Class<? extends Pet>> types = allTypes.subList(allTypes.indexOf(Mutt.class), allTypes.size());
    public List<Class<? extends Pet>> types(){
        return types;
    }

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

使用类字面常量重新实现PetCount,改写后的结果更加清晰

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

public class PetCount3 {
    static class PetCounter extends LinkedHashMap<Class<? extends Pet>, Integer>{

        public PetCounter(){
            super();
        }

        public void count(Pet pet, Map<Class<? extends Pet>, Integer> map) {

            for (Map.Entry<Class<? extends Pet>, Integer> pair : map.entrySet()) {
                if (pair.getKey().isInstance(pet))
                    map.put(pair.getKey(), pair.getValue() + 1);
            }
        }
        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(", ");
            }
            if (result.length() >= 2)
            result.delete(result.length() - 2, result.length()-1);
            result.append("}");
            return result.toString();
        }
    }

    public static void main(String[] args) {
        PetCounter petCounter = new PetCounter();
        LiteralPetCountor creator = new LiteralPetCountor();
        List<Class<? extends Pet>> list = LiteralPetCountor.types;
        Map<Class<? extends Pet>, Integer> map = new LinkedHashMap<>();
        for (int i = 0; i < list.size(); i++) {
            map.put(list.get(i),0);
        }

        for (Pet pet : creator.arrayList(10)) {
            System.out.print(pet.getClass().getSimpleName() + " ");
            petCounter.count(pet,map);
        }
        System.out.println();
        System.out.println(petCounter);
    }
}

四. 注册工厂

生成Pet继承结构的对象中存在着一个问题,即每次向该继承结构添加新的Pet类型时,必须将其添加为LiteralPetCreator.java中的项。这样就很麻烦。
除非你想编写一个工具,它可以全面搜索和分析源代码,然后创建和编译这个列表。因此,最佳的做法时,将这个列表置于一个位于中心的,位置明显的位置,而我们感兴趣的基类可能就是这个最佳位置。这里使用了工厂方法设计模式:工厂设计模式初识
它们一般都是这样一个模式:public interface Factory<T> { T create();}泛型参数T使得create()可以在每种Factory实现中返回不同的类型。这也充分利用了协变返回类型。
在下面的示例中,基类Part包含一个工厂对象的列表,对于由createRandom()产生的类型,他们的工厂都被添加到了partFactories List中,从而被注册到了基类中。

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

    static List<Factory<? extends Part>> partFactories = new ArrayList<>();
    static {
        partFactories.add(new FuelFilter.Factory());
        partFactories.add(new AirFilter.Factory());
        partFactories.add(new OilFactory.Factory());
        partFactories.add(new FanBelt.Factory());
        partFactories.add(new PowerSteeringBelt.Factory());
    }

    private static Random random = new Random(47);
    public static Part createRandom(){
        int n = random.nextInt(partFactories.size());
        return partFactories.get(n).create();
    }
}


class Filter extends Part{}

class FuelFilter extends Filter{
    //为每一个具体类型创建一个类工厂
     static class Factory implements com.gui.demo.thingInJava.RTTI.Factory<FuelFilter> {
        @Override
        public FuelFilter create() {
            return new FuelFilter();
        }
    }
}

class AirFilter extends Filter{
    public static class Factory implements com.gui.demo.thingInJava.RTTI.Factory<AirFilter> {
        @Override
        public AirFilter create() {
            return new AirFilter();
        }
    }
}
class OilFactory extends Filter{
    public static class Factory implements com.gui.demo.thingInJava.RTTI.Factory<OilFactory> {
        @Override
        public OilFactory create() {
            return new OilFactory();
        }
    }
}


class Belt extends Part{}

class FanBelt extends Belt{
    public static class Factory implements com.gui.demo.thingInJava.RTTI.Factory<FanBelt> {
        @Override
        public FanBelt create() {
            return new FanBelt();
        }
    }
}
class PowerSteeringBelt extends Belt{
    public static class Factory implements com.gui.demo.thingInJava.RTTI.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());
        }
    }
}
/*
输出:
FanBelt
FuelFilter
FanBelt
AirFilter
AirFilter
PowerSteeringBelt
FanBelt
FuelFilter
OilFactory
OilFactory
 */

并非所有在继承结构中的类都应该被实例化,如Filter和Belt这两个只是分类标识,只需创建它们子类的实例。如果某个类应该由createRandom创建,那么它内部就包含一个Factory类。如上所示:重用名字Factory的唯一方式就是限定com.gui.demo.thingInJava.RTTI.Factory。
createRandom方法从partFactories中随机地选取一个工厂对象,然后调用其create方法,从而产生一个新的Part。

构造器就是一种工厂方法。下面是对该实例的优化:不使用显式的工厂,而是将类对象存储到List中,并使用newInstance来创建对象。

class Part{
    public String toString(){
        return getClass().getSimpleName();
    }
    
    static List<Class<? extends Part>> partFactories = Arrays.asList(
            FuelFilter.class, AirFilter.class, OilFactory.class, FanBelt.class, PowerSteeringBelt.class);

    private static Random random = new Random(47);
    public static Part createRandom(){
        int n = random.nextInt(partFactories.size());

        try {
            return partFactories.get(n).newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }
}

/****************************************相同代码省略*********************/
public class RegisteredFactories {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(Part.createRandom());
        }
    }
}

五、 instanceof与Class的等价性

在查询类型信息时,以instanceof的形式(即以instanceof的形式或isInstance()的形式,它们产生相同的结果)与直接比较Class对象有一个很重要的差别。

class Base{}
class Derived extends Base{}

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

    public static void main(String[] args) {
        test(new Base());
        System.out.println("==================================================");
        test(new Derived());
    }
}
/*
输出
Testing x of type Base
x instanceof Base true
x instanceof Derived false
Base.instance(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 Derived
x instanceof Base true
x instanceof Derived true
Base.instance(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对象是否相等。使人放心的是:instanceof和isInstance()生成的结果完全一致,equals和==也一样。但是这两组的结论却不相同。
Instanceof保持了类型的概念,它指的是“你是这个类吗,或者你是这个类的派生类吗?”而如果用==比较比较实际的Class对象,就没有考虑继承——它是这个类确切的类型,或者不是。

六、 反射:运行时的类信息

6.1 Java反射机制

Java Reflection:反射是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。Class c = Class.forName("java.lang.String");
加载玩类之后在堆内存的方法区中就产生了一个Class类型的对象。我们可以通过这个对象看到这个类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以我们称之为反射
反射与正常new产生对象的不同之处在于:
正常方式:引入包名——通过new实例化——取得实例对象
反射:实例化对象——getClass方法——取得完整的包名
如果不知道某个对象的确切类型,RTTI可以告诉你。但是有一个限制:这个类型在编译时必须已知,这样才能使用RTTI识别它,并利用这些信息做一些事情。换句话说:在编译时,编译器必须知道所有要通过RTTI来处理的类。
处理图形化用户界面(GUI)事件的构建必须暴露相关方法的信息,以便于IDE能够帮助程序员覆盖这些处理事件的方法。反射提供了一种机制——用来检查可用的方法,并返回方法名。
在运行时获取类的信息的另一个动机便是希望提供在跨网络的远程平台上创建和运行对象的能力。这被称为远程方法调用,它允许一个Java程序将对象分布到多台机器上。需要这种分布能力是有许多原因的,例如:你可能正在执行一项需要大量计算的任务,为了提高运算速度,将计算分为许多小的计算单元,分布到闲置的机器上运行。又比如,你可能希望将处理特定类型任务的代码置于特定的机器上,于是这台机器就成了描述这些动作的公共场所,可以很容易的通过改动它就影响到所有人。同时,分布式计算也支持适于执行特殊任务的专用硬件,如矩阵转置。
Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field、Method以及Constructor类(每个类都实现了Member接口)。这些类型的对象是在JVM在运行时创建的,用以表示未知类里对应的成员。这样你就可以使用Constructor创建新的对象,用get()和set()方法读取和修改与Field对象关联的字段,用invoke()调用与Method对象关联的方法。另外,还可以调用getFields()、getMethod()和getContructors()等很便利的方法,已返回表示字段、方法以及构造器的数组。这样,匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。
重要的的是:要认识到反射机制并没有什么神奇之处。当通过反射与一个未知类型的对象打交道时,JVM只是简单地检查这个对象,看它属于哪个特定的类(就像RTTI那样)。在用它做其他事情之前必须先加载那个类的Class对象。因此,那个类的.class文件必须是可获取的,要么在本地机器上,要么可以通过网络获得。所以RTTI和反射真正的区别在于,对RTTI来说,编译器在编译时打开和检查.class文件。而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件

6.2 类方法提取器

反射在你需要创建动态的代码时很有用,反射在Java中是用来支持其他特性的,例如序列化对象和JavaBean。能动态的提取某个类的信息有时还是很有用的:类方法提取器实现了这种方式。

/**
 * @Classname ShowMethods
 * @Description 使用反射去显示一个类的所有方法,即使方法是在父类中被定义的
 * @Date 2021/4/27 20:39
 * @Created by gt136
 */
public class ShowMethods {
    private static final Pattern pattern = Pattern.compile("\\w+\\.");

    public static void main(String[] args) {
        if (args.length < 1) {
            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'";
            System.out.println(usage);
            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 method : methods) {
                    System.out.println(pattern.matcher(method.toString()).replaceAll(""));
                }
                for (Constructor<?> constructor : constructors) {
                    System.out.println(pattern.matcher(constructor.toString()).replaceAll(""));
                }
                lines = methods.length + constructors.length;
            }else {
                for (Method ignored : methods) {
                    if (Arrays.toString(constructors).contains(args[1])) {
                        System.out.println(pattern.matcher(Arrays.toString(constructors)).replaceAll(""));
                        lines ++;
                    }
                }
                for (Constructor<?> constructor : constructors) {
                    if (constructor.toString().contains(args[1])) {
                        System.out.println(pattern.matcher(constructor.toString()).replaceAll(""));
                    }
                    lines++;
                }
            }
            System.out.println(lines);
        } catch (ClassNotFoundException e) {
            System.out.println("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()
11
 */

上面的输出是从下面的命令行开始的:java ShowMethods ShowMethods

Class的getMethods()和getConstructors()方法分别返回Method对象的数组和Constructor对象的数组。这两个方法都提供了深层方法,用以解析其对象所代表的方法,并获取其名字、输入参数以及返回值。但也可以像这里一样,只使用toString()生成一个含有完整的方法特征签名的字符串。
代码其他部分用于提取命令行信息,判断某个特定的特征签名是否与我们的目标字符串相符(contains),并使用正则表达式去掉了命名修饰符。

输出中包含一个public的默认构造器(倒数第二行),该自动合成的默认构造器会自动赋予与类一样的访问权限。所以当把ShowMethods改为一个非public的类时就不会打印这个构造器了。

6.3 动态代理

6.3.1 代理

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

interface Interface{
    void doSomeThing();
    void somethingElse(String args);
}
class RealObject implements Interface{

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

    @Override
    public void somethingElse(String args) {
        System.out.println("somethingElse " + args);
    }
}
class SimpleProxyDemo implements Interface{
    private Interface proxied;

    public SimpleProxyDemo(Interface proxied) {
        this.proxied = proxied;
    }

    @Override
    public void doSomeThing() {
        System.out.println("simpleProxyDemo doSomething");
        proxied.doSomeThing();
    }

    @Override
    public void somethingElse(String args) {
        System.out.println("SimpleProxyDemo somethingElse");
        proxied.somethingElse("args");
    }
}
public class SimpleProxy {
    static void consumer(Interface iface) {
        iface.doSomeThing();
        iface.somethingElse("bonobo");
    }

    public static void main(String[] args) {
        consumer(new RealObject());
        consumer(new SimpleProxyDemo(new RealObject()));
    }
}
/*
输出:
doSomeThing.
somethingElse bonobo
simpleProxyDemo doSomething
doSomeThing.
SimpleProxyDemo somethingElse
somethingElse args
 */

因为consumer()接受的Interface,所以它无法确切知道到底获得的是哪个类。但是SimpleProxyDemo已经被插入到了客户端consumer和RealObject之间,因此它会执行操作(就是插入你想做的事情),然后再调用RealObject上相同的方法(回到原路径上去)。
代理的使用场景: 在任何时刻,只要你想要将额外的操作从“实际”对象中分离到不同的地方,特别是当你希望能够很容易的做出修改,从没有使用额外操作转为使用这些操作,或者反过来时,代理就显得很有用(设计模式的关键就是封装修改——因此你需要修改事务以证明这种模式的正确性)。例如,如果你希望跟踪对RealObject中的方法调用,或者希望度量这些调用的开销。

6.3.2 动态代理

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

public 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);
        Interface proxy = (Interface) Proxy.newProxyInstance(
                Interface.class.getClassLoader(),
                new Class[]{Interface.class},
                new DynamicProxyHandler(real));
        consumer(proxy);
    }
}
/*
输出:
doSomeThing.
somethingElse bonobo
*** proxy: class com.gui.demo.thingInJava.RTTI.AnnotationAndReflact.$Proxy0,method: public abstract void com.gui.demo.thingInJava.RTTI.AnnotationAndReflact.Interface.doSomeThing(),args: null
doSomeThing.
*** proxy: class com.gui.demo.thingInJava.RTTI.AnnotationAndReflact.$Proxy0,method: public abstract void com.gui.demo.thingInJava.RTTI.AnnotationAndReflact.Interface.somethingElse(java.lang.String),args: [Ljava.lang.Object;@58372a00
 bonobo
somethingElse bonobo
 */

整个过程通过调用静态方法**Proxy.newProxyInstance()**可以创建动态代理,这个方法需要得到1.一个类加载器(你通常可以从已加载的对象中获取其类加载器classLoader,然后传递给它),2.一个你希望该代理实现的接口列表(不是类或抽象类),3.以及InvocationHandler接口的一个实现,它是在上面自己重写的方法。
这段程序的后半段会让人很迷惑。原因在于动态代理可以将所有调用重定向到 调用处理器,因此通常会向调用处理器的构造器传递一个“实际”对象的引用,从而使得调用处理器在执行任务时可以将请求转发。
Invoke()中传递进来了代理对象,以帮助你区分请求的资源,但是在很多情况下,你不需要关心。
通常,你会执行被代理的操作,然后使用Method.invoke()将请求转发给被代理对象,并传入必须的参数。

总结

面向对象编程的目的是让我们在凡是可以使用的地方都是用多态机制,只有在必须的时候使用RTTI。然而使用多态机制的方法调用,要求我们拥有基类定义的控制权,因为在你拓展程序的时候,可能会发现基类并未包含我们想要的方法。如果基类来自于别人的类,有别人所控制,那么RTTI便是一种解决之道:可以继承一个新类,然后添加你要添加的方法

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值