Java 类型信息详解和反射机制介绍
RTTI(RunTimeTypeInformation)运行时类型信息,能够在程序运行时发现和使用类型信息,把我们从只能在编译期知晓类型信息并操作的局限中解脱出来
传统的多态机制正是RTTI的基本使用:假设有一个基类Shape和它的三个子类Circle、Square、Triangle,现在要把Circle、Square、Triangle对象放入List
但这样的类型转换并不彻底,Object只是被转型为Shape,而不是更具体的Circle、Square、Triangle,如果我们希望得到更具体的类型呢?比如说我们现在需要旋转所有图形,但是想跳过圆形(圆形旋转没有意义),这时可以使用RTTI查询某个Shape引用所指向对象的确切类型,然后选择进行合适的处理
众所周知,每当我们编写并编译了一个新类,就会产生一个Class对象,它包含了与类有关的信息。我们可以使用Class对象来实现RTTI,一旦某个类的Class对象被载入内存,它就可以用来创建这个类的所有对象
Class对象都属于Class类型,既然它也是对象,那我们就可以获取和操控它的引用。forName()是Class类的一个静态方法,我们可以使用forName()根据目标类的全限定名(包含包名)得到该类的Class对象。使用forName()会有一个副作用,那就是如果这个类没有被加载就会加载它,而在加载的过程中,Gum类的static初始块会被执行。当Class.forName()找不到要加载的类,就会抛出异常
ClassNotFoundException
ClassgumClass=Class.forName("Gum");
使用Class.forName()你不需要先持有这个类型的对象,但如果你已经拥有了目标类的对象,那就可以通过调用getClass()方法来获取Class引用,这个方法来自根类Object,它将返回表示该对象实际类型的Class对象的引用
Gumgum=newGum();
ClassgumClass=gum.getClass();
另外,你还可以调用getSuperclass()方法来得到父类的class对象,再用父类的Class对象调用该方法,重复多次,你就可以得到一个完整的类继承结构
Class对象的newInstance()方法可以让你在不知道一个的确切类型的时候创建这个类的对象,使用newInstance()来创建的类,必须带有无参数的构造器
Objectobj=gumClass.newInstance();
当然,由于得到的是Object的引用,目前你只能给它发送Object对象能接受的调用。如果你想请求具体对象才有的调用,你就得先获取该对象的更多类型信息,并执行转型
Java还提供了另一种生成类对象的引用:类字面常量,这样做不仅更简单,而且更安全,因为它在编译时就会收到检查(不用放在try语句块中),而且根除了对forName()方法的调用,效率更高
ClassgumClass=Gum.class;
类字面常量不仅可以用于普通类,也可以用于接口、数组以及基本数据类型。对于基本数据类型的包装类,还有一个标准字段Type,Type字段是一个引用,指向对应基本数据类型的Class对象,例如int.class就等价于Integer.TYPE。还有一点值得注意的是:使用.class语法来获得对类对象的引用不会触发初始化
到这里我们都知道了,Class引用总是指向某个Class对象,而Class对象可以用于产生类的实例。不过自从Java引入泛型以后,我们就可以使用泛型对Class引用所指向的Class对象的类型进行限定,让它的类型变得更具体些
ClassintClass=int.class; ClassgenericIntClass=int.class; intClass=genericIntClass; //同一个东西 //genericIntClass=double.class 非法
好了,既然拿到了Class对象,那我们就可以这个类的类型信息,常用的方法如下:
方法 | 用途 |
---|---|
asSubclass(Classclazz) | 把传递的类的对象转换成代表其子类的对象 |
Cast | 把对象转换成代表类或是接口的对象 |
getClassLoader() | 获得类的加载器 |
getClasses() | 返回一个数组,数组中包含该类中所有公共类和接口类的对象 |
getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象 |
forName(StringclassName) | 根据类名返回类的对象 |
getName() | 获得类的完整路径名字 |
newInstance() | 创建类的实例 |
getPackage() | 获得类的包 |
getSimpleName() | 获得类的名字 |
getSuperclass() | 获得当前类继承的父类的名字 |
getInterfaces() | 获得当前类实现的类或是接口 |
到目前为止,我们已知的RTTI类型包括:
- 传统的类型转换,如多态
- 代表对象类型的Class对象
RTTI在Java中还有第三种形式,那就是关键字instanceof,它返回一个布尔值,告诉我们对象是不是某个特定类型的实例,可以用提问的方式使用它
if(xinstanceofDog){((Dog)x).bark();}Java还提供了Class.isInstance()方法动态检测对象类型,例如
0instanceofString//编译报错 String.class.isInstance(0) //可以通过编译
如果你不知道对象的确切类型,RTTI会告诉你,但是有一个限制:必须在编译时知道类型,才能使用RTTI检测它。换句话说,编译器必须知道你使用的所有类
看上去这并不是什么特别大的限制,但假设你引用了一个不在程序空间中的对象,比如你从磁盘文件或网络连接中获得大量的字节,并被告知这些字节代表一个类,那该怎么办呢?
类Class支持反射的概念,java.lang.reflect库中支持类Field、Method、Constructor(每一个都实现了Member接口),这些类型的对象由JVM运行时创建,以表示未知类中的对应成员。通常我们不会直接使用反射,但反射可以用来支持其他Java特性,例如对象序列化等
Field代表类的成员变量(成员变量也称为类的属性),Class类中定义了如下方法用来获取Field对象
方法 | 用途 |
---|---|
getField(Stringname) | 获得某个公有的属性对象 |
getFields() | 获得所有公有的属性对象 |
getDeclaredField(Stringname) | 获得某个属性对象 |
getDeclaredFields() | 获得所有属性对象 |
Field类定义了如下方法设置成员变量的信息
方法 | 用途 |
---|---|
equals(Objectobj) | 属性与obj相等则返回true |
get(Objectobj) | 获得obj中对应的属性值 |
set(Objectobj,Objectvalue) | 设置obj中对应属性值 |
Method代表类的方法,Class类中定义了如下方法用来获取Method对象
方法 | 用途 |
---|---|
getMethod(Stringname,Class...>parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(Stringname,Class...>parameterTypes) | 获得该类某个方法 |
getDeclaredMethods() | 获得该类所有方法 |
Method类定义了如下方法对方法进行调用
方法 | 用途 |
---|---|
invoke(Objectobj,Object...args) | 传递object对象及参数调用该对象对应的方法 |
Constructor代表类的构造器,Class类中定义了如下方法用来获取Constructor对象
方法 | 用途 |
---|---|
getConstructor(Class...>parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class...>parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
Constructor代表类的构造方法
方法 | 用途 |
---|---|
newInstance(Object...initargs) | 根据传递的参数创建类的对象 |
除了成员变量、方法和构造器以外,反射还能获取其他更多的信息,例如注解等,具体可查阅JavaAPI
反射的强大威力大家已经看到了,通过反射我们甚至可以获取到一些“本不应该获取”的信息,例如程序员为了降低耦合,往往会使用接口来隔离组件,但反射却可以轻易破解
publicinterfaceA{ voidf(); } classBimplementsA{ publicvoidf(){} publicvoidg(){} } publicclassInterfaceViolation{ publicstaticvoidmain(String[]args){ Aa=newB(); a.f(); //a.g();//编译错误 if(ainstanceofB){ Bb=(B)a; b.g(); } } }
通过使用RTTI,我们发现a是用B实现的,只要将其转型为B,我们就可以调用不在A中的方法。如果你不希望客户端开发者这样做,那该如何解决呢?一种解决方案是直接声明为实际类型,另一种则是让实现类只具有包访问权限,这样包外部的客户端就看不到实现类了
除了这个以外,通过反射可以获得所有成员信息,包括private的,通常这种违反访问权限的操作并不是十恶不赦的,也许还可以帮助你解决某些特定类型的问题
代理是基本的设计模式之一,一个对象封装真实对象,代替真实对象提供其他不同的操作,这些操作通常涉及到与真实对象的通信,因此代理通常充当中间对象。下面是一个简单的静态代理的示例:
interfaceInterface{ voiddoSomething(); } classRealObjectimplementsInterface{ @Override publicvoiddoSomething(){ System.out.println("doSomething"); } } classSimpleProxyimplementsInterface{ privateInterfaceproxied; SimpleProxy(Interfaceproxied){ this.proxied=proxied; } @Override publicvoiddoSomething(){ System.out.println("SimpleProxydoSomething"); proxied.doSomething(); } } classSimpleProxyDemo{ publicstaticvoidconsumer(Interfaceiface){ iface.doSomething(); } publicstaticvoidmain(String[]args){ consumer(newRealObject()); consumer(newSimpleProxy(newRealObject())); } }
当你希望将额外的操作与真实对象做分离时,代理可能会有所帮助,而Java的动态代理更进一步,不仅动态创建代理对象,而且可以动态地处理对代理方法的调用。在动态代理上进行的所有调用都会重定向到一个调用处理程序,该程序负责发现调用的内容并决定如何处理,下面是一个简单示例:
classDynamicProxyHandlerimplementsInvocationHandler{ privateObjectproxied; DynamicProxyHandler(Objectproxied){ this.proxied=proxied; } @Override publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{ returnmethod.invoke(proxied,args); } } classSimpleDynamicProxy{ publicstaticvoidconsumer(Interfaceiface){ iface.doSomething(); } publicstaticvoidmain(String[]args){ RealObjectreal=newRealObject(); Interfaceproxy=(Interface)Proxy.newProxyInstance( Interface.class.getClassLoader(), newClass[]{Interface.class}, newDynamicProxyHandler(real)); consumer(proxy); } }
通过调用静态方法Proxy.newProxyInstance()来创建动态代理,该方法需要三个参数:类加载器、希望代理实现的接口列表、以及接口InvocationHandler的一个实现。InvocationHandler正是我们所说的调用处理程序,动态代理的所有调用会被重定向到调用处理程序,因此通常为调用处理程序的构造函数提供一个真实对象的引用,以便执行中间操作后可以转发请求
到此这篇关于Java类型信息详解和反射机制介绍的文章就介绍到这了,更多相关Java类型信息与反射机制内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。