类型信息小笔记

RTTI

是Runtime type information的缩写,可以让你在程序运行的时候,发现和使用类的类型信息。

在有了泛型的容器中拿元素出来,就是一个RTTI最基本的体现。因为需要把容器中存的Object对象,转换成你泛型写的那个对象,这个转换的检查是发生在运行时的,所以是RTTI。

(Shape)强制转型也是RTTI的一个体现

 

Class Object

要知道RTTI在Java中是怎么工作的,你总得在运行时知道类型信息是怎么展示的吧。

这个类型信息在运行时的获取,就是通过这个Class Object 来获取的,这个对象存储着类的信息。

在你的程序中的每个类,都有一个对应的Class 对象,这个Class Object其实就和普通的对象一样,类名是Class而已。每次你写完并编译一个类,这个类的Class Object也同时被创建,并存储在.class文件中。为了创建这个Class对象,JVM用了个叫类加载器的子系统。

类加载器子系统包含一条类加载器链。
但只有一个原生类加载器,这个也是JVM的实现的一部分。
这个原声类加载器加载的都是可信类,包括Java的API,一般都是直接从本地硬盘上加载的。

这个链中,一般不需要添加额外的类加载器,除非你有些特殊的需求。

所以类都是在第一次被使用的时候,被动态加载到JVM中去的。

类加载器首先检查类对象是否被加载。
没的话,默认的类加载器根据类名找.class文件;
附加的类加载器可能会去数据库找字节码。类的字节码在加载的时候,她们会接受验证,保证没有被破坏和不包含坏的Java代码。static的初始化也是在类加载的时候进行的。

获得Class Object对象引用的方法:

1. 用Class类的static方法——Class.forName()

传一个类名String进去,然后可以获得这个类的Class对象的引用。

在调用这个forName()方法的时候,如果这个类还没被加载,就会加载它,所以这里static初始化语句被执行了。这是forName()方法很重要也很有用的一个功能。

2.Object类的getClass()方法

如果你已经有了这个类的对象,那么你可以通过Object类的一个方法—— getClass()来获取这个类的Class对象引用。

3.类字面常量——.class

类名.class的形式咯就。

对于基本数据类型的封装类,有个标准eld叫做TYPE,这个field提供了基本数据类型的Class Object。   比如Integer.TYPE就可以获得基本数据类型int的Class Object或者说是int.class

意,.class有个和forName方法不一样的地方:.class会返回一个Class Object的引用,但不会自动进行初始化操作。这里的初始化指的是这个类的对象的类加载和一些static的初始化。或者说是类初始化。加载一个类需要三步:

而第三步初始化类,被延迟到你第一次执行该类的static方法才会进行。这个就和Class.forName()方法不一样了。

补充:

Classloader.loadClass(String name)方法得到的class是不进行link也就是链接操作的,所以当然后面的初始化也不会执行到,也就是不会初始化static field和static块。

通过这个Class Object你可以获得很多类型信息。下面这个例子展示了一些方法:

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

class Toy {
    // Comment out the following default constructor
    // to see NoSuchMethodError from (*1*)
    Toy() {}
    Toy(int i) {}
}

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

public class ToyTest {
    static void printInfo(Class cc) {
        print("Class name: " + cc.getName() + " is interface? [" + cc.isInterface() + "]");
        print("Simple name: " + cc.getSimpleName());
        print("Canonical name : " + cc.getCanonicalName());
    }
    public static void main(String[] args) {
        Class c = null;
        try {
            c = Class.forName("typeinfo.toys.FancyToy");
        } catch(ClassNotFoundException e) {
            print("Can’t find FancyToy");
            System.exit(1);
        }
        printInfo(c);
        for(Class face : c.getInterfaces())
            printInfo(face);
        Class up = c.getSuperclass();
        Object obj = null;
        try {
            // Requires default constructor:
            obj = up.newInstance();
        } catch(InstantiationException e) {
            print("Cannot instantiate");
            System.exit(1);
        } catch(IllegalAccessException e) {
            print("Cannot access");
            System.exit(1);
        }
        printInfo(obj.getClass());
    }
} 
/* Output:
Class name: typeinfo.toys.FancyToy is interface? [false]
Simple name: FancyToy
Canonical name : typeinfo.toys.FancyToy
Class name: typeinfo.toys.HasBatteries is interface? [true]
Simple name: HasBatteries
Canonical name : typeinfo.toys.HasBatteries
Class name: typeinfo.toys.Waterproof is interface? [true]
Simple name: Waterproof
Canonical name : typeinfo.toys.Waterproof
Class name: typeinfo.toys.Shoots is interface? [true]
Simple name: Shoots
Canonical name : typeinfo.toys.Shoots
Class name: typeinfo.toys.Toy is interface? [false]
Simple name: Toy
Canonical name : typeinfo.toys.Toy
*///:~

几点要注意的:

1.Class.forName()一定要填完整的类名。

2.介个newInstance()方法是Class中一个实现虚拟构造器的方法。例子中,up是一个Class Object的引用,但在编译期不具备任何进一步的类型信息。然后你利用这个newInstance()方法获得了一个Object对象的引用。

但用这个方法,你这个类要有默认的构造器,想想也知道,这个方法是无参数的,所以你必须要一个无参的构造器呢。

加上泛型的Class引用

一个Class引用可以指向一个Class对象,看下面的代码:

Class intClass = int.class;
Class<Integer> genericIntClass = int.class;
genericIntClass = Integer.class; // Same thing
intClass = double.class;
// genericIntClass = double.class; // Illegal

看这个例子就知道要讲什么东西了,本来你可以用一个Class Object的引用去指向任意一个类的Class Object。
但加上泛型后,像genericIntClass,这个Class Object的引用就只能指向正确的类的Class Object了。

使用泛型语法,可以让编译器进行额外的类型检查

哦对,如果你的Class引用有泛型的话,那么执行newInstance()就不再返回Object对象了,会帮你正确转型。

新的转型语法

直接看例子:

//: typeinfo/ClassCasts.java
class Building {}
class House extends Building {}
public class ClassCasts {
    public static void main(String[] args) {
        Building b = new House();
        Class<House> houseType = House.class;
        House h = houseType.cast(b);
        h = (House)b; // ... or just do this.
    }
} ///:

说,这个cast方法似乎看起来直接用(House)b这样的强制转型就可以了,但有时候你不是很方便用这种普通的转型,比如在写泛型代码的时候,你有一个Class Object的引用,然后想转型的时候,这个cast就有用了。

RTTI的第三种形式——instanceof

前面两种是(Shape)强制 转换还有Class Object。

instanceof方法有个局限哦,只能比较类的名称,不能是一个Class Object。

还有个动态的instanceof:

利用Class Object中的isInstance()后更方便,消除了之前傻逼的instanceof语句,然后而且现在代码设计也更好了。

instanceof和isInstance()生成的结果完全一样;如果获得Class引用,用equals()和==来检查Class对象是否相等,这两个方法的结果也一样。

但这两组方法的含义却不同,instanceof系列保持了类型的概念,它指的是”你是这个类吗,或者是你是这个类的派生类吗?“而用equals()或者==比较实际的Class对象,没有考虑继承——它或者是确切的类型,或者不是。

还有个判断子类的方法:

又一个判断是不是的方法,
superClass.isAssignableFrom(childClass) 属于 Class.java。它的对象和参数都是类,意思是“父类(或接口类)判断给定类是否是它本身或其子类”
子接口也可以判断

反射

我们的这个RTTI呢,有个限制,就是这个类型必须是编译时期已知的,换种话说,在编译时,编译器必须知道所有要通过RTTI处理的类。

但有时候捏,你会获得一个指向并不在你程序空间中的对象的引用,这个时候的类是在编译后过了很久才出现的,所以用RTTI无法知道它的类型信息。

这个时候就要用反射的机制了。

Class类还有java.lang.reflect类库一起对反射进行了支持,这个reflect库中有Field、Method、Constructor类,每个类都实现了Member接口。这些几个类都的JVM在运行时才创建的,用以表示未知类的对应成员,其实就把未知类的成员都抽象成类。这样你就可以用Constructor创建新的对象;用get和set方法读取和修改Field对象相关联的字段;用invoke方法调用与Method对象相关联的方法,还有一些很方便的方法等等会介绍。

这样未知类的信息就可以在运行时知道了,在编译期什么都不用知道。

当通过反射来和一个未知类型的对象打交道的时候,JVM只是简单地检查这个对象,看它属于哪个类,在进行任何操作之前,必须加载这个类的Class Object;所以这个类的.class文件对于JVM来说必须是可获取的,要么在本地机器上,要么通过网络。

所以关于RTTI和反射真正的区别是:对于RTTI而言,编译器在编译时打开和检查.class文件;对于反射而言,.class文件在编译时是不可获取的,所以在运行时打开和检查.class文件。

看个简单的用法,就查看类的相关方法信息的:

Class<?> c = Class.forName(args[0]);
Method[] methods = c.getMethods();
Constructor[] ctors = c.getConstructors();
if(args.length == 1) {
    for(Method method : methods)
        print(p.matcher(method.toString()).replaceAll(""));
    for(Constructor ctor : ctors)
        print(p.matcher(ctor.toString()).replaceAll(""));
    lines = methods.length + ctors.length;
} else {
    for(Method method : methods)
        if(method.toString().indexOf(args[1]) != -1) {
            print(p.matcher(method.toString()).replaceAll(""));
            lines++;
        }
    for(Constructor ctor : ctors)
        if(ctor.toString().indexOf(args[1]) != -1) {
            print(p.matcher(ctor.toString()).replaceAll(""));
            lines++;
    }
}

代码是不完整的哈哈,理解下就是。

动态代理

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

代理的一个很简单的实现的方式就是,和真实对象实现同一个接口,然后就可以充当真实对象了,看个例子:

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

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

class SimpleProxy implements Interface {
    private Interface proxied;

    public SimpleProxy(Interface proxied) {
        this.proxied = proxied;
    }
    public void doSomething() {
        print("SimpleProxy doSomething");
        proxied.doSomething();
    }
    public void somethingElse(String arg) {
        print("SimpleProxy somethingElse " + arg);
        proxied.somethingElse(arg);
    }
}

class SimpleProxyDemo {
    public static void consumer(Interface iface) {
        iface.doSomething();
        iface.somethingElse("bonobo");
    }
    public static void main(String[] args) {
        consumer(new RealObject());
        consumer(new SimpleProxy(new RealObject()));
    }
} 
/* Output:
doSomething
somethingElse bonobo
SimpleProxy doSomething
doSomething
SimpleProxy somethingElse bonobo
somethingElse bonobo
*///:~
View Code

代理可以帮你把一些额外的操作放在别的地方。

一般都会在代理类中放一个被代理类也就是真实对象的引用,方便通信和操作。

Java的动态代理就肯定更厉害了,可以动态地创建代理并且动态地处理对代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,Java有一个专门作为这个处理器的接口InvocationHandler;这个处理器的工作就是揭示调用的类型并确定相应的对策。

下面是用Java动态代理重写的一个例子:

import java.lang.reflect.*;
class DynamicProxyHandler implements InvocationHandler {
    private Object proxied; //被代理对象,真实。
    public DynamicProxyHandler(Object proxied) {
        this.proxied = proxied;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("**** proxy: " + proxy.getClass() + ", method: " + method + ", args: " + args);
        if(args != null)
            for(Object arg : args)
                System.out.println(" " + arg);
        return method.invoke(proxied, args);
    }
}

class SimpleDynamicProxy {
    public static void consumer(Interface iface) {
        iface.doSomething();
        iface.somethingElse("bonobo");
    }
    public static void main(String[] args) {
        RealObject real = new RealObject();
        consumer(real);
        // Insert a proxy and call again:
        Interface proxy = (Interface)Proxy.newProxyInstance(
            Interface.class.getClassLoader(),
            new Class[]{ Interface.class },
            new DynamicProxyHandler(real));
        consumer(proxy);
    }
} 
/* Output: (95% match)
doSomething
somethingElse bonobo
**** proxy: class $Proxy0, method: public abstract void
Interface.doSomething(), args: null
doSomething
**** proxy: class $Proxy0, method: public abstract void
Interface.somethingElse(java.lang.String), args:
[Ljava.lang.Object;@42e816
bonobo
somethingElse bonobo
*///:~

首先介绍这个调用处理器的接口——InvocationHandler.

实现这个接口要重写Object invoke()方法。这个方法有三个参数:

参数1:
  代理的对象,代理人,或者说中间人。就是动态生成的代理类的实例,这个是Java会自动传进来的,这个参数你可以1. 可以使用反射获取代理对象的信息(也就是proxy.getClass().getName());

2.可以将代理对象返回以进行连续调用,这就是proxy存在的目的。因为this并不是代理对象。所以好像说,在invoke方法内部调用这个proxy代理对象的方法要很小心,因为会被重定向为对代理的调用。
参数2:
  调用的方法,被执行的方法。这个参数是个Method类的对象,而这个对象有个方法Method.invoke(),通过这个方法你可以把请求转发给被代理的那个对象,并传入需要的参数。
参数3:
  执行该方法所需要的参数

理解就是,你用Java帮你动态生成的代理类执行一个方法后,这个方法的执行立刻就会被重定向到这个指定的InvocationHandler的实现类中,然后映射到这个invoke方法中进行处理。

然后创建动态代理,是通过Proxy.newProxyInstance()方法。这个方法一般也需要三个参数:

参数1:
  类加载器,可以从已经加载的对象那里拿一个喔。(似乎传的都是要实现的接口的类加载器)。

  理解应该是:指明生成代理对象使用哪个类装载器。

  thinking in Java说,一般可以从已经加载的对象中获取其类加载器然后传递给它喔。
参数2:
  你想让动态代理类实现的接口。

  这里似乎传的是接口的Class Object列表。
参数3:
  InvocationHandler接口的实现类。动态代理会把所有调用重定向到这个invocation handler上,所以一般这个invocation handler的实现类的构造器会传一个真正对象的引用。

通过反射可以使用private的方法,还可以访问和修改private的field

一个用反射来调用方法的例子:

public interface A {
    void f();
} ///:

class HiddenImplementation {
    static void callHiddenMethod(Object a, String methodName) throws Exception {
        Method g = a.getClass().getDeclaredMethod(methodName);
        g.setAccessible(true);
        g.invoke(a);
    }
}

class InnerA {
    private static class C implements A {
        public void f() { print("public C.f()"); }
        public void g() { print("public C.g()"); }
        void u() { print("package C.u()"); }
        protected void v() { print("protected C.v()"); }
        private void w() { print("private C.w()"); }
    }
    public static A makeA() { return new C(); }
}

public class InnerImplementation {
    public static void main(String[] args) throws Exception {
        A a = InnerA.makeA();
        a.f();
        System.out.println(a.getClass().getName());
        // Reflection still gets into the private class:
        HiddenImplementation.callHiddenMethod(a, "g");
        HiddenImplementation.callHiddenMethod(a, "u");
        HiddenImplementation.callHiddenMethod(a, "v");
        HiddenImplementation.callHiddenMethod(a, "w");
    }
} 
/* Output:
public C.f()
InnerA$C
public C.g()
package C.u()
protected C.v()
private C.w()
*///:~

callHidenMethod方法就是对某个对象中的方法的调用,什么方法都可以,利用反射。

还有访问和修改private filed的例子:

class WithPrivateFinalField {
    private int i = 1;
    private final String s = "I’m totally safe";
    private String s2 = "Am I safe?";
    public String toString() {
        return "i = " + i + ", " + s + ", " + s2;
    }
}

public class ModifyingPrivateFields {
    public static void main(String[] args) throws Exception {
        WithPrivateFinalField pf = new WithPrivateFinalField();
        System.out.println(pf);
        Field f = pf.getClass().getDeclaredField("i");
        f.setAccessible(true);
        System.out.println("f.getInt(pf): " + f.getInt(pf));

        f.setInt(pf, 47);
        System.out.println(pf);
        f = pf.getClass().getDeclaredField("s");
        f.setAccessible(true);
        System.out.println("f.get(pf): " + f.get(pf));

        f.set(pf, "No, you’re not!");
        System.out.println(pf);
        f = pf.getClass().getDeclaredField("s2");
        f.setAccessible(true);
        System.out.println("f.get(pf): " + f.get(pf));

        f.set(pf, "No, you’re not!");
        System.out.println(pf);
    }
} 
/* Output:
i = 1, I’m totally safe, Am I safe?
f.getInt(pf): 1
i = 47, I’m totally safe, Am I safe?
f.get(pf): I’m totally safe
i = 47, I’m totally safe, Am I safe?
f.get(pf): Am I safe?
i = 47, I’m totally safe, No, you’re not!
*///:~

用反射可以去访问和修改private的field,实验说明只有final的field改不了。

所以可以说,反射确实可以无视权限… ,有好有坏咯。
利大于弊这样。

原文地址:https://www.cnblogs.com/wangshen31/p/10351311.html