Thinking In Java——类型信息读书笔记及摘录

运行时类型信息使得你可以在程序运行时发现和使用类型信息。

在Java中主要有两种方法让我们在运行时识别对象和类的信息:第一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型;另一种是“反射”机制,它允许我们在运行时发现和使用类的信息。

为什么需要RTTI?

面向对象编程中基本的目的是:让代码只操纵对基类的引用。这样,如果添加一个新类来扩展程序,就不会影响到原来的代码。

示例代码:

abstract class Shape {

    void draw(){
        System.out.println(this + ".draw()");
    }

    abstract public String toString();

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

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

}
/*
out:
Circle.draw()
Square.draw()
Triangle.draw()
 */

当从数组中取出元素时,这种容器——实际上它将所有的事物都当作Object持有——会自动将结果转型回数组指定的类型。这就是RTTI最基本的使用形式,因为在Java中,所有的类型转换都是在运行时进行正确性检查的。这也就是RTTI名字的含义:在运行时,识别一个对象的类型。

Class对象

Class对象就是用来创建类的所有“常规”对象的。Java使用CLass对象来执行其RTTI,即使你正在执行的是类似转型这样的操作。Class类还拥有大量的使用RTTI的其他方式。

类是程序的一部分,每一个类都有一个Class对象。换言之,每当编写并且编译了一个新类,就会产生一个Class对象(更恰当地说,是被保存在一个同名的.class文件中)。为了生成这个类的对象,运行这个程序的JVM将使用被称为“类加载器”的子系统。

类加载器实际上可以包含一条类加载器链,但是只有一个原生类加载器,它是JVM实现的一部分。原生类加载器加的是所谓的可信类,包括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()要填写全路径,不然找不到
            Class.forName("TypeInfo.ClassObject.Gum");
        } catch (ClassNotFoundException e) {
            System.out.println("cannt find Gum");
        }

        System.out.println("after class.forName(Gum)");
        new Cookie();
        System.out.println("after creating Cookie");
    }

}
/*
out:
inside main
Loading Candy...
after creating Candy
Loading Gum...
after class.forName(Gum)
Loading Cookie...
after creating Cookie
 */

无论何时,只要你想在运行时使用类型信息,就必须首先获得恰当的Class对象的引用。Class.forName()就是实现此功能的便捷途径,因为你不需要为了获得Class引用而持有该类型的对象。但是,如果你已经拥有了一个感兴趣的类型的对象,那就可以通过调用getClass()方法来获取Class引用了,这个方法属于根类Object的一部分,它将返回表示该对象的实际类型的Class引用。

示例代码:

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("TypeInfo.ClassObject.FancyToy");
        } catch (ClassNotFoundException e) {
            System.out.println("cannt 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();
            //用这个
            obj = up.getDeclaredConstructor().newInstance();
        } catch (InstantiationException e) {
            System.exit(1);
        } catch (InvocationTargetException e) {
            System.exit(1);
        } catch (NoSuchMethodException e) {
            System.exit(1);
        } catch (IllegalAccessException e) {
            System.exit(1);
        }
        printInfo(up.getClass());
    }

}
/*
out:
Class Name:TypeInfo.ClassObject.FancyToy,is interface?[false]
Simple Name:FancyToy
Canonical Name:TypeInfo.ClassObject.FancyToy
Class Name:TypeInfo.ClassObject.HasBatteries,is interface?[true]
Simple Name:HasBatteries
Canonical Name:TypeInfo.ClassObject.HasBatteries
Class Name:TypeInfo.ClassObject.Shoots,is interface?[true]
Simple Name:Shoots
Canonical NameTypeInfo.ClassObject.Shoots
Class Name:TypeInfo.ClassObject.Waterproof,is interface?[true]
Simple Name:Waterproof
Canonical Name:TypeInfo.ClassObject.Waterproof
Class Name:java.lang.Class,is interface?[false]
Simple Name:Class
Canonical Name:java.lang.Class
 */

类字面常量

Java还提供了另一种方法来生成对Class对象的引用,即使用类字面常量。

形如,XXX.class。

这样做不仅仅更简单,而且更安全,因为它在编译时就会受到检查(因此不需要至于try语句块中)。并且根除了forName()方法的调用,所以更加高效。

类字面常量不仅可以应用于普通的类,也可以用于接口、数组以及基本类型数据。另外,对于基本类型的包装器类,还有一个标准字段Type。Type字段是一个引用,指向对应的基本数据类型的Class对象。

对应表:

类字面常量 标准字段TYPE
boolean.class Boolean.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
char.class Character.TYPE

为了使用类而做的准备工作实际包含三个步骤:

  1. 加载,这是由类加载器执行的。该步骤将查找字节码(通常在classpath所指定的路径中查找,但这并非是必须的),并从这些字节码中创建一个Class对象;

  2. 链接。在链接阶段将验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类创建的对其他类的所有引用;

  3. 初始化。如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。

    初始化被延迟到了对静态方法(构造器隐式地是静态的)或者非常数静态域进行首次引用时才执行;

示例代码:

public class Initable {

    static final int staticFinal = 47;
    static final int staticFinal2 = ClassInit.rand.nextInt(1000);

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

}

public class Initable2 {

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

}

public class Initable3 {

    static int staticNonFinal = 74;
    static {
        System.out.println("Init Initable3...");
    }

}

public class ClassInit {

    public static Random rand = new Random(47);

    public static void main(String[] args) throws ClassNotFoundException {
        //Class对象不会触发初始化
        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);
        //forName会触发初始化
        Class initable3 = Class.forName("TypeInfo.Classliterals.Initable3");
        System.out.println("After creating Initable3 ref");
        System.out.println(Initable3.staticNonFinal);
    }

}
/*
out:
After creating Initable ref
47
Init Initable...
258
Init Initable2...
147
Init Initable3...
After creating Initable3 ref
74
 */

在输出中可以看到,在获取Initable的Class引用时,通过.class的方式并没有触发类的初始化,而相对的Class.forName()则会触发类的初始化;

如果一个static域不是final的,那么在对它访问时,总是要求在它被读取之前,要先进行链接(为这个域分配存储空间)和初始化(初始化该存储空间)。

泛化的Class引用

普通的类引用不会产生警告信息,尽管泛型类引用只能赋值为指向其声明的类型,但是普通的类引用可以被重新赋值为指向任何其他的Class对象。通过使用泛型语法,可以让编译器强制执行额外的类型检查。

示例代码:

public class GenericClassReferences {

    public static void main(String[] args) {
        Class intClass = int.class;

        Class<Integer> genericIntClass = int.class;
        genericIntClass = Integer.class;
        intClass = double.class;
        //报错
        //genericIntClass = double.class;
    }

}

为了在使用泛化的Class引用时放松限制,可以使用通配符,它是Java泛型的一部分。通配符就是“?”,表示“任何事物”。

示例代码:

public class WildcardClassREferences {

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

为了创建一个Class引用,它被限定为某种类型,或该类型的任何子类,需要将通配符与extends关键字相结合,创建一个范围。

示例代码:

public class BoundedClassReference {

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

向Class引用添加泛型语法的原因仅仅是为了提供编译期类型检查,因此如果操作有误,会马上发现错误。在使用普通的Class引用,不会误入歧途,但是如果确实犯了错,那么有可能到运行时才会发现错误,这就显得非常不方便。

示例代码:

public class CountedInteger {

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

    @Override
    public String toString() {
        return "CountedInteger{" +
                "id=" + id +
                '}';
    }
}

public class FilledList<T> {

    private Class<T> type;

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

    public List<T> craete(int nElements){
        List<T> result = new ArrayList<>();
        try {
            for (int i = 0; i < nElements; i++) {
                result.add(type.getDeclaredConstructor().newInstance());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    public static void main(String[] args) {
        FilledList<CountedInteger> fl = new FilledList<>(CountedInteger.class);
        System.out.println(fl.craete(5));
    }
}
/*
out:
[CountedInteger{id=0},
CountedInteger{id=1},
CountedInteger{id=2},
CountedInteger{id=3},
CountedInteger{id=4}]
 */

注意,这个类必须假设与它一同工作的任何类型都具有一个默认的构造器(无参构造器),并且如果不符合该条件,你将得到一个异常。

类型转换前先做检查

几个常见的RTTI形式包括:

  1. 传统的类型转换,由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换就会抛出一个ClassCastException;
  2. 代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息;
  3. 关键字instanceof。它返回一个布尔值,告诉我们对象是不是某个特定类型的实例;

对instanceof有比较严格的限制:只可将其与命名类型进行比较,而不能与Class对象作比较。

动态的instanceof

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

注册工厂

为方便调用者创建自己想要获取的对象,此时可以使用工厂模式,对象创建者只需要将创建某些对象的方法准备好,保存至注册工厂中,而使用者只需要调用即可。

示例代码:

public class Part {

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

    public static List<Factory<? extends Part>> partFactories = new ArrayList<>();

    static {
        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 random = new Random(47);

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

public class Filter extends Part {
}

public class FuelFilter extends Filter {

    public static class Factory implements TypeInfo.RegisterFactory.Factory<FuelFilter>{

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

}

public class OilFilter extends Filter {

    public static class Factory implements TypeInfo.RegisterFactory.Factory<OilFilter>{

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

public class AirFilter extends Filter {

    public static class Factory implements TypeInfo.RegisterFactory.Factory<AirFilter>{

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

}

public class CabinAirFilter extends Filter {

    public static class Factory implements TypeInfo.RegisterFactory.Factory<CabinAirFilter>{

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

}

public class Belt extends Part {
}

public class FanBelt extends Belt {

    public static class Factory implements TypeInfo.RegisterFactory.Factory<FanBelt>{

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

public class GeneratorBelt extends Belt {

    public static class Factory implements TypeInfo.RegisterFactory.Factory<GeneratorBelt>{

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

public class PowerSteeringBelt extends Belt {

    public static class Factory implements TypeInfo.RegisterFactory.Factory<PowerSteeringBelt>{

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

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

public class RegisterFactories {

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(Part.createRandom());
        }
        //使用者不需要知道怎么创建一个对象,只需要调用即可
        Part part = Part.partFactories.get(2).create();
    }

}

instanceof与Class的等价性

instanceof和isInstance()产生的结果完全一样,equals()和==也一样。instanceof保持了类型的概念,它指的是“你是这个类吗,或者你是这个类的派生类吗?”,而如果使用==比较时间的Class对象,就没有考虑继承——它是这个确切的类型或者不是。

示例代码:

public class FamilyVsExactType {

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

    public static void main(String[] args) {
        test(new Base());
        System.out.println("*****************************");
        test(new Derived());
    }
}
/*
out:
Testing x of type class TypeInfo.InstanceofAndClass.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.InstanceofAndClass.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
 */

反射:运行时的类信息

如果不知道某个对象的确切类型,RTTI可以告诉你。但是有个限制:这个类型在编译时必须已知,这样才能使用RTTI识别它,并利用这些信息做一些有用的事,换句话说,在编译时,编译器必须知道所有要通过RTTI来处理的类。实际上,在编译时程序根本无法获知这个对象所属的类。

Class类和java.lang.reflect类库一起对反射的概念进行了支持,该类库包括了Field、Method以及Constructor类(每一个类都实现了Member接口)。这些类型的对象都是由JVM在运行时创建的,用以表示未知类里对应的成员。这样就可以使用Constructor创建新的对象,用get()和set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。另外还可以调用getField()、getMethods()和getConstructors()等很便利的方法,以返回表示字段、方法以及构造器的对象的数组。这样,匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。

值的注意的是,反射机制并没有什么神奇之处。当通过反射与一个位置类型的对象打交道时,JVM只是简单地检查这个对象没,看它属于哪个特定的类。在用它做其他事情之前必须先加载那个类的Class对象。因此,那个类的.class文件对于JVM来说必须是可获取的:要么在本地,要么可以通过网络获取。所以RTTI和反射之间真正的区别只在于,对于RTTI来说,编译器在编译时打开和检查.class文件。而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。(反射相对RTTI来说显得相对灵活)

类方法提取器

这里给出获取某个对象中getter方法并调用的例子(自己编写非TIJ示例):

public class Frame implements Comparable<Frame>{

    Integer allFrameNum;

    Integer currFrameNum;

    byte[] data;

    Integer prmCode;

    Integer fnCode;

    MessageDirection dir;

    Date tp;

    String tpStr;

    Integer clientId;

    String firAndFin;

    Integer pfc;

    Integer Afn;

    Integer secretData;

    public Integer isSecretData() {
        return secretData;
    }

    public void setSecretData(Integer secretData) {
        this.secretData = secretData;
    }

    public Integer getAfn() {
        return Afn;
    }

    public void setAfn(Integer afn) {
        Afn = afn;
    }

    public Integer getAllFrameNum() {
        return allFrameNum;
    }

    public void setAllFrameNum(Integer allFrameNum) {
        this.allFrameNum = allFrameNum;
    }

    public Integer getCurrFrameNum() {
        return currFrameNum;
    }

    public void setCurrFrameNum(Integer currFrameNum) {
        this.currFrameNum = currFrameNum;
    }

    public byte[] getData() {
        return data;
    }

    public void setData(byte[] data) {
        this.data = data;
    }

    public Integer getPrmCode() {
        return prmCode;
    }

    public void setPrmCode(Integer prmCode) {
        this.prmCode = prmCode;
    }

    public Integer getFnCode() {
        return fnCode;
    }

    public void setFnCode(Integer fnCode) {
        this.fnCode = fnCode;
    }

    public MessageDirection getDir() {
        return dir;
    }

    public void setDir(MessageDirection dir) {
        this.dir = dir;
    }

    public Date getTp() {
        return tp;
    }

    public void setTp(Date tp) {
        this.tp = tp;
    }

    public String getTpStr() {
        return tpStr;
    }

    public void setTpStr(String tpStr) {
        this.tpStr = tpStr;
    }

    public Integer getClientId() {
        return clientId;
    }

    public void setClientId(Integer clientId) {
        this.clientId = clientId;
    }

    public String getFirAndFin() {
        return firAndFin;
    }

    public void setFirAndFin(String firAndFin) {
        this.firAndFin = firAndFin;
    }

    public Integer getPfc() {
        return pfc;
    }

    public void setPfc(Integer pfc) {
        this.pfc = pfc;
    }

    @Override
    public String toString() {
        return "Frame{" +
                "allFrameNum=" + allFrameNum +
                ", currFrameNum=" + currFrameNum +
                ", data=" + Arrays.toString(data) +
                ", prmCode=" + prmCode +
                ", fnCode=" + fnCode +
                ", dir=" + dir +
                ", tp=" + tp +
                ", tpStr='" + tpStr + '\'' +
                ", clientId=" + clientId +
                ", firAndFin='" + firAndFin + '\'' +
                ", pfc=" + pfc +
                ", Afn=" + Afn +
                ", secretData=" + secretData +
                '}';
    }

    @Override
    public int compareTo(Frame frame){
        return Long.compare(this.getCurrFrameNum(),frame.getCurrFrameNum());
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null){
            return false;
        }
        //自己判断自己
        if (this == obj){
            return true;
        }
        //判断类型
        if (obj instanceof Frame){
            Frame other = (Frame)obj;
            Method[] methods = Frame.class.getMethods();
            for (Method cursor : methods) {
                //只获取get方法
                if (!cursor.getName().contains("get")
                        ||cursor.getName().contains("set")){
                    continue;
                }
                //对get方法获取的结果进行比较
                try {
                    Method otherMothod = Frame.class.getMethod(cursor.getName());
                    //如果双方该字段都为null则一定相同
                    if (cursor.invoke(this) == null
                            && otherMothod.invoke(other) == null){
                        continue;
                    }
                    //如果双方该字段一个有一个没有则一定不同
                    if (cursor.invoke(this) == null
                            && otherMothod.invoke(other) != null){
                        return false;
                    }
                    if (cursor.invoke(this) != null
                            && otherMothod.invoke(other) == null){
                        return false;
                    }
                    //如果该字段双方均有则需要判断
                    if (cursor.getReturnType().getCanonicalName().contains("[]")){
                        //如果是数组
                        byte[] myData = (byte[]) cursor.invoke(this);
                        byte[] otherData = (byte[]) cursor.invoke(other);
                        if (!Arrays.equals(myData, otherData)){
                            return false;
                        }
                    }else if (!cursor.invoke(this).equals(otherMothod.invoke(other))){
                        //如果不是集合
                        return false;
                    }
                } catch (Exception e) {
                    System.out.println("error method:" + cursor.getName());
                    e.printStackTrace();
                    return false;
                }
            }
        }else {
            return false;
        }
        return true;
    }

}

示例是比较两个自定的对象,如果挨个调用getter,一是显得代码十分臃肿,二是如果以后扩展这里需要挨个修改容易出错不易扩展。

动态代理

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

示例代码:

public interface Interface {
    void doSomething();
    void doSomethingElse(String arg);
}

public class RealObject implements Interface {
    @Override
    public void doSomething() {
        System.out.println(
                this.getClass().getSimpleName() + ":" +
                new Exception().getStackTrace()[0].getMethodName()
        );
    }

    @Override
    public void doSomethingElse(String arg) {
        System.out.println(
                this.getClass().getSimpleName() + ":" +
                new Exception().getStackTrace()[0].getMethodName() +
                ":" + arg
        );
    }
}

public class SimpleProxy implements Interface {

    private Interface proxied;
    public SimpleProxy(Interface proxied){
        this.proxied = proxied;
    }

    @Override
    public void doSomething() {
        System.out.println(
                this.getClass().getSimpleName() + ":" +
                        new Exception().getStackTrace()[0].getMethodName()
        );
        proxied.doSomething();
    }

    @Override
    public void doSomethingElse(String arg) {
        System.out.println(
                this.getClass().getSimpleName() + ":" +
                        new Exception().getStackTrace()[0].getMethodName() +
                        ":" + arg
        );
        proxied.doSomethingElse(arg);
    }
}

public class SPDemo {

    public static void consumer(Interface iface){
        iface.doSomething();
        iface.doSomethingElse("ABCDEFG");
    }

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

}
/*
out:
RealObject:doSomething
RealObject:doSomethingElse:ABCDEFG
*********************
SimpleProxy:doSomething
RealObject:doSomething
SimpleProxy:doSomethingElse:ABCDEFG
RealObject:doSomethingElse:ABCDEFG
 */

关于Exception.getStackTrace()

JDK中的注释:

/**
     * Provides programmatic access to the stack trace information printed by
     * {@link #printStackTrace()}.  Returns an array of stack trace elements,
     * each representing one stack frame.  The zeroth element of the array
     * (assuming the array's length is non-zero) represents the top of the
     * stack, which is the last method invocation in the sequence.  Typically,
     * this is the point at which this throwable was created and thrown.
     * The last element of the array (assuming the array's length is non-zero)
     * represents the bottom of the stack, which is the first method invocation
     * in the sequence.
     *
     * <p>Some virtual machines may, under some circumstances, omit one
     * or more stack frames from the stack trace.  In the extreme case,
     * a virtual machine that has no stack trace information concerning
     * this throwable is permitted to return a zero-length array from this
     * method.  Generally speaking, the array returned by this method will
     * contain one element for every frame that would be printed by
     * {@code printStackTrace}.  Writes to the returned array do not
     * affect future calls to this method.
     *
     * @return an array of stack trace elements representing the stack trace
     *         pertaining to this throwable.
     * @since  1.4
     */

机翻:

/**

*提供对由打印的堆栈跟踪信息的编程访问
*{@link#printStackTrace()}。返回堆栈跟踪元素数组,
*每个代表一个堆栈帧。数组的第0个元素
*(假设数组的长度为非零)表示
*堆栈,这是序列中最后一个方法调用。通常情况下,
*这就是这个投掷物被创造和投掷的点。
*数组的最后一个元素(假设数组的长度为非零)
*表示堆栈的底部,这是第一个方法调用
*按顺序。
*
*<p>在某些情况下,某些虚拟机可能会忽略一个
*或堆栈跟踪中的更多堆栈帧。在极端情况下,
*没有堆栈跟踪信息的虚拟机
*允许此throwable从中返回零长度数组
*方法。一般来说,此方法返回的数组将
*为每个要打印的帧包含一个元素
*{@code printStackTrace}。对返回数组的写入不
*影响将来对此方法的调用。
*
*@返回表示堆栈跟踪的堆栈跟踪元素数组
*关于这个投掷物的。
*@从1.4开始
*/

在任何时候,只要想将额外的操作从“实际”对象中分离到不同的地方,特别是希望能够很容易地做出修改,从没有使用额外操作的地方转为使用这些操作,或者反过来时,代理就显得十分有用(设计模式的关键就是对封装修改——因此需要修改事务以证明这种模式的正确性)。

Java的动态代理比代理的思想更向前迈进了一些,因为它可以动态地创建代理并动态地处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的对策。

示例代码:

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 + ",arg:" + args);
        if (args != null){
            for (Object arg :
                    args) {
                System.out.print("  " + arg);
            }
        }
        return method.invoke(proxied, args);
    }
}

public class SimpleDynamicProxyDemo {

    public static void consumer(Interface iface){
        iface.doSomething();
        iface.doSomethingElse("ABCDEFG");
    }

    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);
    }
}
/*
out:
RealObject:doSomething
RealObject:doSomethingElse:ABCDEFG
**** Proxy :class com.sun.proxy.$Proxy0.method:public abstract void TypeInfo.ProxyDemo.SimpleProxyDemo.Interface.doSomething(),arg:null
RealObject:doSomething
**** Proxy :class com.sun.proxy.$Proxy0.method:public abstract void TypeInfo.ProxyDemo.SimpleProxyDemo.Interface.doSomethingElse(java.lang.String),arg:[Ljava.lang.Object;@433c675d
  ABCDEFGRealObject:doSomethingElse:ABCDEFG
 */

通过调用静态方法Proxy.newProxyInstance()可以创建动态代理,这个方法需要得到一个加载器,一个你希望该代理实现的接口列表(不是类或抽象类),以及InvocationHandler接口的实现。动态代理可以将所有调用重定向到调用处理器,因此通常会调用处理器的构造器传递给一个“实际”对象的引用,从而使得调用处理器在执行其中介任务时,可以将请求转发。

invoke()方法中传递进来了代理对象,以防你需要区分请求的来源,但是在许多情况下,并不关心这一点。然而,在invoke()内部,在代理上调用方法时需要格外小心,因为接口的调用将被重定向为对代理的调用。

通常,你会执行被代理的操作,然后使用Method.invoke()将请求转发给被代理对象,并传入必须参数。这样虽然看起来有些受限,就像只能执行泛化操作一样。但是,可以通过传递其它的参数,来过滤某些方法的调用。

示例代码:

public interface SomeMethods {

    void boringOne();
    void interesting(String arg);
    void boringTwo();
    void boringThree();

}

public class Implementation implements SomeMethods {
    @Override
    public void boringOne() {
        System.out.println("boringOne");
    }

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

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

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

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

public 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.boringOne();
        proxy.boringTwo();
        proxy.boringThree();
        proxy.interesting("ABCEDFG");
    }
}

关于代理模式

为其他对象提供一种代理,以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用。

发布了24 篇原创文章 · 获赞 8 · 访问量 1857

猜你喜欢

转载自blog.csdn.net/qq_40462579/article/details/104360359