java反射机制

xiaoxiao2021-02-28  8

RTTI,即Run-Time Type Identification,运行时类型识别。RTTI能在运行时就能够自动识别每个编译时已知的类型。 很多时候需要进行向上转型,比如Base类派生出Derived类,但是现有的方法只需要将Base对象作为参数,实际传入的则是其派生类的引用。那么RTTI就在此时起到了作用,比如通过RTTI能识别出Derive类是Base的派生类,这样就能够向上转型为Derived。类似的,在用接口作为参数时,向上转型更为常用,RTTI此时能够判断是否可以进行向上转型。

而这些类型信息是通过Class对象(java.lang.Class)的特殊对象完成的,它包含跟类相关的信息。每当编写并编译一个类时就会产生一个.class文件,保存着Class对象,运行这个程序的Java虚拟机(JVM)将使用被称为类加载器(Class Loader)的子系统。而类加载器并非在程序运行之前就加载所有的Class对象,如果尚未加载,默认的类加载器就会根据类名查找.class文件(例如,某个附加类加载器可能会在数据库中查找字节码),在这个类的字节码被加载时接受验证,以确保没有被破坏并且不包含不良Java代码。这也是Java中的类型安全机制之一。一旦某个类的Class对象被载入内存,就可以创建该类的所有对象。

package typeinfo; class Base { static { System.out.println("加载Base类"); } } class Derived extends Base { static { System.out.println("加载Derived类");} } public class Test { static void printerInfo(Class c) { System.out.println("类名: " + c.getName() + "是否接口? [" + c.isInterface() + "]"); } public static void main(String[] args) { Class c = null; try { c = Class.forName("typeinfo.Derived"); } catch (ClassNotFoundException e) { System.out.println("找不到Base类"); System.exit(1); } printerInfo(c); Class up = c.getSuperclass(); // 取得c对象的基类 Object obj = null; try { obj = up.newInstance(); } catch (InstantiationException e) { System.out.println("不能实例化"); System.exit(1); } catch (IllegalAccessException e) { System.out.println("不能访问"); System.exit(1); } printerInfo(obj.getClass()); } /* 输出: 加载Base类 加载Derived类 类名: typeinfo.Derived是否接口? [false] 类名: typeinfo.Base是否接口? [false] */ }上述代码中,forName方法是静态方法,参数是类名,用来查找是否存在该类,如果找到则返回一个Class引用,否则会抛出ClassNotFoundException异常。 如果类不是在默认文件夹下,而是在某个包下,前面的包名需要带上,比如这里的typeinfo.Derived。 可以通过getSuperclass方法来返回基类对应的Class对象。使用newInstance方法可以按默认构造创建一个实例对象,在不能实例化和不能访问时分别抛出。会抛出InstantiationException和IllegalAccessException异常。 Java还提供了一种方法来生成对Class对象的引用,即类字面常量。对上述程序来说,up等价于Base.class。 对于基本数据类型的包装类来说,char.class等价于Character.TYPE,int.class等价于Integer.TYPE。其余的ab.class等价于Ab.TYPE。(比如void.class等价于Void.TYP)。另外,Java SE5开始int.class和Integer.class也是一回事。

泛化的Class引用,见下面代码:

Class intClass = int.class; Class<Integer> genericIntClass = int.class; genericIntClass = Integer.class; // 等价 intClass = double.class; // ok // genericIntClass = double.class; // Illegal!

Class<Integer>对象的引用指定了Integer对象,所以不能将引用指向double.class。为了放松限制可以使用通配符?,即Class<?>,效果跟Class是一样的,但是代码更为优雅,使用Class<?>表示你并非是碰巧或疏忽才使用一个非具体的类引用。同时,可以限制继承的类,示例如下:

class Base {} class Derived extends Base {} class Base2 {} public class Test { public static void main(String[] args) { Class<? extends Base> cc = Derived.class; // ok // cc = Base2.class; // Illegal } }向Class引用添加泛型语法的原因仅仅是为了提供编译期类型检查,以便在编译时就能发现类型错误。 总结下来,我们已知的RTTI形式包括: 1、传统的类型转换,由RTTI保证类型转换的正确性,如果执行一个错误的类型转换,就会抛出ClassCastException异常; 2、代表对象的类型的Class对象,通过查询Class对象(即调用Class类的方法)可以获取运行时所需的信息。

在C++中经典的类型转换并不使用RTTI,这点具体见C++的RTTI部分。(说句题外话,以前学C++时看到RTTI这章只是随便扫了眼,现在才记起来dynamic_cast什么的都是为了类型安全而特地添加的,C++在安全方面可以提供选择性,就像Java的StringBuilder和StringBuffer,安全和效率不可兼得?而Java在类型安全上则更为强制,就像表达式x = 1不能被隐式转型为boolean类型)。

RTTI可以识别程序空间的所有类,但是有时候需要从磁盘文件或网络文件中读取一串字节码,并且被告知这些字节代表一个类,就需要用到反射机制。比如在IDE中创建图形化程序时会使用到一些控件,只需要从本地的控件对应class文件中读取即可,然后再主动修改这些控件的属性。(题外话:大概.net组件就是这样的?学C#时总听到反射,但总没感觉用过,前几天做.net项目的同学也跟我说他从来都没用过委托和事件……)Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含Field、Method和Constructor类(每个类都实现了Member接口),这些类型的对象都是JVM在运行时创建的,用以表示未知类里对应成员。这样就可以用Constructor创建未知对象,用get()和set()方法读取和修改与Field对象关联的字段,用invoke方法调用与Method对象关联的字段,等等。

// 使用反射展示类的所有方法, 即使方法是在基类中定义的 package typeinfo; // Print类的print方法等价于System.Out.Println,方便减少代码量 import static xyz.util.Print.*; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.regex.Pattern; // {Args: typeinfo.ShowMethods} public class ShowMethods { private static 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'"; // 去掉类名前面的包名 private static Pattern p = Pattern.compile("\\w+\\."); public static void main(String[] args) { if (args.length < 1) { print(usage); System.exit(0); } int lines = 0; try { Class<?> c = Class.forName(args[0]); // 反射获得对象c所属类的方法 Method[] methods = c.getMethods(); // 反射获得对象c所属类的构造 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("")); } } catch (ClassNotFoundException e) { print("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() */ 简单来说,反射机制就是识别未知类型的对象。反射常用于动态代理中。举例如下: import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; class DynamicProxyHandler implements InvocationHandler { private Object proxied; // 代理对象 public DynamicProxyHandler(Object proxied) { // TODO Auto-generated constructor stub this.proxied = proxied; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub System.out.println("代理类: " + proxy.getClass() + "\n" + "代理方法: " + method + "\n" + "参数: " + args); if (args != null) for (Object arg : args) System.out.println(" " + arg); return method.invoke(proxied, args); } } interface Interface { void doSomething(); } class RealObject implements Interface { @Override public void doSomething() { // TODO Auto-generated method stub System.out.println("doSomething"); } } public class DynamicProxyDemo { public static void consumer(Interface iface) { iface.doSomething(); } public static void main(String[] args) { RealObject realObject = new RealObject(); // 使用动态代理 Interface proxy = (Interface)Proxy.newProxyInstance( Interface.class.getClassLoader(), new Class[] { Interface.class }, new DynamicProxyHandler(realObject)); consumer(proxy); } /* 输出: 代理类: class $Proxy0 代理方法: public abstract void Interface.doSomething() 参数: null doSomething */ }代理是基本的设计模式之一,即用代理类为被代理类提供额外的或不同的操作。而动态代理则需要一个类加载器,就像Java实现RTTI时需要类加载器加载类的信息,这样就可以知道类的相关信息。 关键方法是: Object java.lang.reflect.Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException. 传入三个参数:代理接口的加载器(通过Class对象的getClassLoader方法获取),代理的方法接口,代理对象。 前两个参数很好理解,就是要代理的方法所属的接口对应的Class对象(主语)的加载器和Class对象本身。 主要是参数3,要设计一个实现InvocationHandler接口的类,作为代理对象,一般命名以Handler结尾,Handler翻译为处理者,很形象,就是代替原对象进行处理的处理者(即代理),在程序设计中经常被翻译成“句柄”。 这个类通过传入代理对象来构造,比如这里传入的是Object对象。然后必须覆盖invoke方法。 通过最后输出和invoke方法的具体实现可以发现,return method.invoke(proxied, args);是相当于原对象调用该方法(类似C++的回调函数?) 由于有类加载器,所以代理对象可以知道原对象的具体类名、方法、参数,本示例在调用方法前就输出了这些。 实际应用中可能会针对类名而有所选择。比如接口中有好多个类,你可以选择性的对特定的类、方法、参数进行处理。

比如 if(proxied instanceof RealObject) {} 或者 if(method.getName.equals("doSomething")) {}

关于反射的另一种作用:通过反射来获取私有的属性,方法。

先来看下我们的javaBean:

public class Test { private String name; private int age; private Test(int age){ this.age = age; } private void speak(String name){ System.out.println("我的名字是:"+name); } public Test(String name) { this.name = name; } } 首先,我们要了解三个反射包中的类:            Constructor:代表类的单个构造方法,通过Constructor我们可执行一个类的某个构造方法(有参或者无参)来创建对象时。            Method:代表类中的单个方法,可以用于执行类的某个普通方法,有参或无参,并可以接收返回值。            Field:代表类中的单个属性,用于set或get属性            AccessibleObject:以上三个类的父类,提供了构造方法,普通方法,和属性的访问控制的能力。        使用Class类中的方法可以获得该类中的所有Constructor对象,Method对象,和Field对象。但是任然无法访问私有化的构造方法,普通方法,和私有属性,此时我们可以使用他们继承父类(AccessibleObject)中的setAccessible()方法,来设置或取消访问检查,以达到访问私有对象的目的。

Step1:获取私有属性

public static void main(String[] args) throws NoSuchFieldException { // TODO Auto-generated method stub /* * 使用反射来创建构造方法私有化的对象 * */ //1:获取类的无参构造方法 Test test = new Test("张三"); Method[] methods = Test.class.getMethods(); Field[] fields = Test.class.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { fields[i].setAccessible(true); try { System.out.println(fields[i].get(test)); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(fields[i].getName()); } }然后,我们再来看一下输出:         张三         name         0

        age

Step2:获取私有方法

Test test = new Test("张三"); Method[] methods = Test.class.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { methods[i].setAccessible(true); try { methods[i].invoke(test,"成功调用"); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(methods[i].getName()); }这样,我们就获得了私有属性的值啦~~当然,凡事有利就有弊,然后我们再来说一下 java反射的优缺点;         优点:                1:能够运行时动态获取类的实例,大大提高了系统的灵活性和扩展性;                2:与java动态编译相结合,可以实现无比强大的功能。         缺点:                1:使用反射的性能较低;                2:使用反射来说相对不安全;                3:破坏了类的封装性,可以通过反射来获取这个类的属性,和私有方法。 (参考:https://blog.csdn.net/guim_007/article/details/65953818 , https://www.cnblogs.com/Harley-Quinn/p/5263802.html)
转载请注明原文地址: https://www.6miu.com/read-2300382.html

最新回复(0)