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()方法不一样了。
通过这个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 *///:~
代理可以帮你把一些额外的操作放在别的地方。
一般都会在代理类中放一个被代理类也就是真实对象的引用,方便通信和操作。
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