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文件,并将所有对象实例存放在堆中,将类型信息存放到方法区中。同时对象实例会保存指向类型信息的指针。也就是说,一个对象包含了完整的类的结构信息。反射就是通过一个类的实例能够知道该类的所有信息。

关于反射机制的相关类就在如下包

java.lang.reflect.*;

获取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();
//通过getClass获取类的Class对象
Class<?> c2 = a.getClass();
System.out.println(c2);
//通过反射获取类的class对象
Class<?> c1;
c1 = Class.forName("com.Reflection.cat");
System.out.println(c1);
//通过类名.class获取类的class对象
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对象(加载)
对m赋值默认初始值为0(链接)
执行加载类中的代码(初始化)
*/
}
}

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();
//getDeclaredFields()获取所有属性,getFields()获取public属性
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);
}
/*
利用getDeclaredMethods方法获取的是类中所有的方法,包括public和非public的方法,但不包括继承的方法:
利用getMethods()方法可以获取到类中所有public的方法,其中包括父类的方法
*/
//获得指定方法
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);//拿到构造器
//其中getDeclaredConstructor()方法返回的是public和非public的构造器,
// 而getConstructor()方法只制定参数类型访问权限是public的构造器
webdog02 a2 = (webdog02) constructor.newInstance("XiLitter");
System.out.println(a2);
//通过反射获取一个方法
Method named = c1.getDeclaredMethod("named",String.class);
named.invoke(a2,"XiLitter");//激活方法
//invoke方法,第一个参数为对象,第二个参数为激活的方法传值
System.out.println("名字是"+a2.names());
//通过反射获取属性
webdog02 a3 = (webdog02) c1.newInstance();
Field name = c1.getDeclaredField("name");
//通过set方法修改属性(只能修改public属性)
//添加代码 name.setAccessible(true);可以修改私有属性
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/