Reflect反射学习
为什么需要学习反射方法?
Object obj="李小龙“;
obj.length(); //这里会报错,因为编译器中不存在Object.length()这个方法。;
所以我们要获取“李小龙”这个字段的长度的时候,就要把它的Object类型强转成String类型,因为系统中存在String.length()类型的方法。
String str=(String) obj;
str.length();
而很多时候,我们作为调用者使用他人所写的代码的时候,不知道Object类型变量底层对面是什么类型的存在,没办法强转,就没有办法调用方法,故我们要通过反射查看其底层是什么类型的变量(照妖镜看衣冠楚楚的外表背后是什么妖怪)。 这里通过一些简单的demo,学习一下反射的基本使用方法.
定义: JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性; 这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
通过这种技术我们可以在编写代码的时候忽略对象的编译类型,直接去操作对象上真实存在的属性和方法。
通过反射,我们可以知道123的底层,它是Integer的对象,而Integer又是Class的对象,
而我们编写的代码,是存在于字节码文件中的,字节码文件在JVM加载后经过封装成为了Class类的对象,Class类会描述每一个类的自有特征。而这些特征,可以通过反射体现出来。
1:字节码加载到JVM后是以字节码对象的形式存在,即Class类的对象
2:字节码对象中用于创建对象的构造器,封装成构造器对象,即Constructor类的对象
3:字节码对象中的字段,封装成Filed对象
4:字节码对象中的方法,封装成Method对象
(```)
public Class User{
public User(){
} //Constructor
private String userName;//Filed
public void play(){...} //Method
} (```)
什么意思呢,就是你用反射(照妖镜)去看你所写或者调用的构造器,它就会告诉你它真实面目其实是Constructor类,只不过是以一个对象(化身)的形式呈现在你面前。
反射的终极目的:为了编写通用的程序代码和拓展现有程序的功能。 反射是框架的灵魂,框架提供所有的通用的功能,我们只需要在拓展一点代码就能拥有强大的功能. 反射也有弊端:照妖镜需要消耗法力,反射也会消耗性能,但它十分便利。
PS:要注意字段的对象——Field很容易与Filed记混。
获取字节码对象
获取字节码对象的3种方式:
1.Class<类名> 字节码对象名 = 类名.class
最简单的一种方式,如果需要反射的类型是基本数据类型只能采用这种方式。
2.Class <?> 字节码对象名 = 对象名. getClass()
可以直接获得对象的真实类型。
3.Class <?> 字节码对象名 = Class. forName()
在Class类型中有一个静态的方法——forName();
在forName()括号中输入类的全限定名,只能写 确实存在该文件的类。
Class<User>clz=User.class //第一种方法获取User类的字节码对象
Object obj =new User();
Class<?>clz =obj.getClass(); //第二种方法获取字节码对象
Clazz<?>clz =Class.forName("cn.luwei.model.User"); //第三种方式
反射创建对象
反射最终就是要创建对象,调用方法。
传统做法中,创建对象的做法是 Person p=new Person();
其中也是需要用构造器进行 p 的创造对象,故我们用反射创造对象,也需要用到构造器。
故我们需要通过反射,获取到所需构造器的对象(镜像);
我们一般可以通过getConstructor获取对象构造器:User u=new User();//无参构造器也需要字节码对象u进行获取
u=new User("tom",19)//有参构造器也需要字节码对象u进行获取
所以我们反射创造对象构造器的思路是:
1.首先获得Peron类的字节码对象(先拿来Person p)。
2.再用字节码对象创建构造器对象。
而拿到了构造器,我们就可以用来创造相应的对象了。
Constructor类中有方法:
T newInstance(Object... initargs) : 调用该构造器创建对象 。
参数是构造器中的实参,例如("gril”,18)。
反射调用方法
反射在不知道对象的真实类型的情况下,调用对象上真实存在的方法。
思路:
1.获取字节码对象
2.在字节码对象中获取方法对象
3.调用方法对象,调用方法中的代码Object obj=clz.newInstance();
Method PayMethod=clz.getMethod("pay",int.class);
Object retrunValue=payMethod.invoke(obj,100);
System.out.println(returnValue);
为什么需要invoke()进行方法调用,payMethod(obj)?
payMethod只是pay()方法的一个镜像,并不是作为一个真实的方法存在的,为了区分你是真人还是镜子里面的人,需要一个invoke去提醒计算机。
调用静态方法
在Java中,静态的方法不需要对象就能调用
在反射中,调用静态方法传入的对象没有意义,一般写null
因为静态方法不需要对象调用someMethod.invoke(null);
暴力反射
暴力反射(Violent Reflect)
跳过Java的访问权限检查,直接使用类中的成员
这种机制严重破坏Java的封装,普通开发是禁止使用的
获取到非公共的成员调用带有Declared(声明)的方法 调用setAccessible方法把当前成员设置成可以访问
Method destoryMethod =clz.getDeclaredMethod("destory");
destoryMethod.setAccessible(true);//设置当前成员是可以访问的
destoryMethod.invoke(null);
而bean规范中字段是私有化的,那如何调用字段?
在Java为了数据的安全字段都是私有起来的,外部不能访问,反射操作字段套路:
字节码对象 -> 声明的字段 -> 设置可以访问 -> 访问
Properties
Class<?> clz=Class.forName("java.lang.Math");
Method random =clz.getMethod("random");
Object val=random.invoke(null);
上述代码调用的时候存在写死的String数据字段,不实用,如何才能实现灵活调用任何对象中的任何方法?
可以通过将类的全限定名和调用方法写入Properties中
data.properties:
ClassName=java.util.Date
methodName=toLocaleString
调用:
Propertis p =new Properties();
ClassLoader loader=PropertiesTest.class.getClassLoader();
InputStream in = loader.getResourceAsStream("data.properties");
p.load(in);
String cn=p.getProperty("className);
String mn=p.getProperty("methodName);
Class<?>clz=Class.getForName(cn);
Object obj=clz.newInstance();
Method random=clz.getMethod(mn);
Object val =random.invoke(obj);
我们可以通过在属性文件中书写需要调用的方法的路径与名称,而不改变已经写好并封装的代码块,达到方法的调用效果,更加符合封装的要求,代码的可重用性被发挥到最大。
属性文件是以properties作为后缀名的,而不是class,换句话说,它不是字节码文件,所以它就和我们以前的调用方法不一样,需要通过类加载器将属性文件模拟成一个类,再通过输入流将属性文件中的信息读进来Java文件中。输入流需要和类加载器配合使用,通过类加载器中的load方法。