java反射
java反射机制在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
java是一门静态语言,反射这一特性使其java有了动态性。这么说可能体会不到,那么就与javascript动态语言做个对比。看这么一小段javascript代码:
function f(){ var name = "var a =2;var b = 3;alert(a+b)"; eval(name); }
|
在代码运行过程中,eval函数能够访问到name的值,使其将字符串变成可执行的代码并执行,变量的自身结构发生变化,这就是语言的动态性。在加载完类之后,会生成class源文件,JVM启动后,会动态加载class文件,并将所有对象实例存放在堆中,将类型信息存放到方法区中。同时对象实例会保存指向类型信息的指针。也就是说,一个对象包含了完整的类的结构信息。反射就是通过一个类的实例能够知道该类的所有信息。
关于反射机制的相关类就在如下包
获取class类型的方式
方式 |
备注 |
Class.forName(“完整类名带包名”) |
静态方法 |
对象.getClass() |
|
任何类型.class |
|
写个例子
package com.Reflection; import java.util.concurrent.atomic.AtomicReference;
public class dog { public static void main(String[] args) throws ClassNotFoundException { cat a = new cat(); Class<?> c2 = a.getClass(); System.out.println(c2); Class<?> c1; c1 = Class.forName("com.Reflection.cat"); System.out.println(c1); System.out.println(cat.class); } } class cat{ }
|
看运行结果他们获得的都是同一个class对象。
注意类对象应该指类的Class对象,也就是字节码对象,每个类都有属于它自己的类对象。
类加载内存分析
加载:代码运行生成的class文件字节码内容加载到内存中,并将这些数据转换成方法区中的运行时数据(静态变量、静态 代码块、常量池等),在堆中生成一个Class类对象代表这个类。
链接:将java类的二进制代码合并到jvm中。确保加载的类信息符合JVM规范,没有安全方面的问题–验证。正式为类变量 (static变量)分配内存并设置类变量初始值–准备。虚拟机常量池内的符号引用替换为直接引用–解析
初始化:初始化阶段是执行类构造器()方法的过程。类构造器()方法是由编译器自动收集类中的所有 类变量的赋值动作和**静态语句块(static块)**中的语句合并产生的。当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先初始化其父类。虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
写个例子
package com.Reflection;
public class testcat { public static void main(String[] args){ A a = new A(); System.out.println(A.m);
} }
class A{ static { m = 300; System.out.println("静态代码块初始化"); } static int m = 100; public A(){ System.out.println("无参构造初始化"); } }
|
最后打印结果:
贴上在网上找到一张图加深理解
类加载器
类加载器本质上也是一个类,将class字节码文件加载到内存中,它把类的静态数据都转换为存放在方法区里的数据结构,并且在堆中生成一个代表这个类的java.lang.Class对象。
反射就是通过类加载器访问方法区中的类数据。在java中有四种类加载器
跟着写个代码获取类加载器。
package com.Reflection;
public class test01 { public static void main(String[] args){ ClassLoader a = ClassLoader.getSystemClassLoader(); System.out.println("系统类加载器为:"+a); ClassLoader b = a.getParent(); System.out.println("扩展类加载器为:"+b); ClassLoader c = b.getParent(); System.out.println("根加载器为:"+c);
} }
|
得到的运行结果为
由于根加载器是由c++编写的,所以java读取不到,返回null。从这段代码可以看出jvm的类加载器是有继承关系的,当一个类要被加载时,不考虑自定义加载器,首先会在系统类加载器检索是否已经加载过(因为有类缓存),如果有就无需加载,反之会拿到父加载器,继续检索,依次往上递归,直到根加载器,如果根加载器也没有加载过,根加载器就会考虑加载类,如果根加载器无法加载,就会往下递归,直到底层加载器加载,如果加载不了,就会抛出异常错误,这就是java的双亲委派机制。这么做是为了防止加载类与jdk中的同名冲突,如果有人想恶意替换系统类,然而这些系统类都被根加载器加载过了,防止恶意代码的植入。
通过反射获得类信息
写个代码很快理解:
package com.Reflection; import java.lang.reflect.Field; import java.lang.reflect.Method; public class test02 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, NoSuchFieldException { Class c1 = Class.forName("com.Reflection.webdog01"); System.out.println(c1.getName()); Field[] fields = c1.getDeclaredFields(); System.out.println("-------获取类属性-------"); for(Field field : fields){ System.out.println(field); } System.out.println("-------获得指定属性------"); Field name = c1.getField("name"); System.out.println(name); System.out.println("-------获得类的方法---------"); Method[] method = c1.getMethods(); for(Method method1 : method){ System.out.println(method1); }
System.out.println("-------获得指定方法--------"); Method a1 = c1.getMethod("setLang", String.class); System.out.println(a1); } }
class webdog01{ public String name; public int age; private String lang; public webdog01(){ System.out.println("初始化"); } public String getLang(){ return this.lang; } public void setLang(String lang){ this.lang = lang; } }
|
看看执行结果:
动态创建对象
利用class对象的一个newInstance()函数,但是这种方法只能调用类中的无参构造方法。还有一种方法是通过构造器创建对象,使用Constructor对象的newInstance()方法可以调用有参和无参的构造方法创建对象。写个例子看看反射能干些什么事。
package com.Reflection;
import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
public class test01 { public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException { Class c1 = Class.forName("com.Reflection.webdog02"); webdog02 a1 = (webdog02) c1.newInstance(); System.out.println(a1); Constructor constructor = c1.getDeclaredConstructor(String.class); webdog02 a2 = (webdog02) constructor.newInstance("XiLitter"); System.out.println(a2); Method named = c1.getDeclaredMethod("named",String.class); named.invoke(a2,"XiLitter"); System.out.println("名字是"+a2.names()); webdog02 a3 = (webdog02) c1.newInstance(); Field name = c1.getDeclaredField("name"); name.set(a3,"dog"); System.out.println("名字是"+a3.names()); } } class webdog02{ public String name; public webdog02(){ System.out.println("获得了无参构造方法"); } public webdog02(String name){ this.name = name; System.out.println("调用了有参构造"); } public void sample(){ System.out.println("调用了普通方法"); } public void named(String name){ this.name = name; } public String names(){ return this.name; } }
|
看看运行结果:
java反射增加了java语言的动态性,不用担心写死代码了,更加灵活方便。但是同时它也会带来一些问题,反射是一种解释操作,它要向java虚拟机解释应该怎么做,所以它比直接执行代码要慢得多。另外我们上面例子也能看出,我们可以直接通过反射来修改莫个类的私有属性,这打破了java的封装特性。使用反射也会带来一些安全问题(不了解)
相关链接:
https://www.bilibili.com/video/BV1p4411P7V3/?p=14
http://blog.m1kael.cn/index.php/archives/421/