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/