共计 7627 个字符,预计需要花费 20 分钟才能阅读完成。
用最通俗易懂的话来说一说 Java 中的反射机制
思考:在讲反射之前,先思考一个问题,java 中如何创立一个对象,有哪几种形式?
Java 中创建对象大略有这几种形式:
1、应用 new 关键字:这是咱们最常见的也是最简略的创建对象的形式
2、应用 Clone 的办法:无论何时咱们调用一个对象的 clone 办法,JVM 就会创立一个新的对象,将后面的对象的内容全副拷贝进去
3、应用反序列化:当咱们序列化和反序列化一个对象,JVM 会给咱们创立一个独自的对象
上边是 Java 中常见的创建对象的三种形式,其实除了上边的三种还有另外一种形式,就是接下来咱们要探讨的 “反射”
1、反射概述
1.1 什么是反射
反射就是把 Java 类中的各个局部,映射成一个个的 Java 对象,拿到这些对象后能够做一些事件。
既然说反射是反射 Java 类中的各个组成部分,所以说咱们得晓得一个类中有哪儿些局部?
例如,一个类有:成员变量,办法,构造方法,等信息,利用反射技术咱们能够把这些组成部分映射成一个个对象。
1.2、反射能干什么
说完反射的概念后,咱们说一下反射能干什么?
一般来说反射是用来做框架的,或者说能够做一些形象度比拟高的底层代码,反射在日常的开发中用到的不多,然而咱们还必须搞懂它,因为搞懂了反射当前,能够帮忙咱们了解框架的一些原理。所以说有一句很经典的话:反射是框架设计的灵魂。当初说完这个可能还不太能了解,不急,等下说完一个疾速入门的例子后,应该会略微有点感觉
1.3、怎么失去想反射的类
方才曾经说过,反射是对一个类进行解剖,想解剖一个货色,前提是首先你得拿到这个货色,那么怎么失去咱们想解剖的类呢?
首先大家要明确一点,咱们写的代码是存储在后缀名是 .java 的文件里的,然而它会被编译,最终真正去执行的是编译后的 .class 文件。Java 是面向对象的语言,所有皆对象,所以 java 认为 这些编译后的 class 文件,这种事物也是一种对象,它也给形象成了一品种,这个类就是 Class,大家能够去 AIP 里看一下这个类
所以拿到这个类后,就相当于拿到了咱们想解剖的类,那怎么拿到这个类?
看 API 文档后,有一个办法 forName(String className); 而且是一个动态的办法,这样咱们就能够失去想反射的类了
到这里,看 Class clazz = Class.forName(“com.cj.test.Person”); 这个应该有点感觉了吧
Class.forName(“com.cj.test.Person”); 因为这个办法里接管的是个字符串,字符串的话,咱们就能够写在配置文件里,而后利用反射生成咱们须要的对象,这才是咱们想要的。很多框架里都有相似的配置
2、解剖类
咱们晓得一个类里个别有构造函数、办法、成员变量 (字段 / 属性) 这三局部组成
翻阅 API 文档,能够看到
Class 对象提供了如下罕用办法:
public Constructor getConstructor(Class<?>…parameterTypes)
public Method getMethod(String name,Class<?>… parameterTypes)
public Field getField(String name)
public Constructor getDeclaredConstructor(Class<?>…parameterTypes)
public Method getDeclaredMethod(String name,Class<?>… parameterTypes)
public Field getDeclaredField(String name)
这些办法别离用于帮咱们从类中解剖出构造函数、办法和成员变量(属性)。
而后把解剖进去的局部,别离用 Constructor、Method、Field 对象示意。
2.1 反射构造方法
2.1.1 反射无参的构造函数
能够看到 默认的无参构造方法执行了
从上边的例子看出,要想反射,首先第一步就是失去类的字节码
所以简略说一下失去类的字节码的几种形式
(1)、Class.forName(“com.cj.test.Person”); 这就是上边咱们用的形式
(2)、对象.getClass();
(3)、类名.class;
2.1.2 反射“一个参数”的构造函数
2.1.3 反射“多个参数”的构造函数
2.1.4 反射“公有”的构造函数
留神:在反射公有的构造函数时,用一般的 clazz.getConstructor()会报错,因为它是公有的,所以提供了专门反射公有构造函数的办法 clazz.getDeclaredConstructor(int.class);// 读取公有的构造函数,用这个办法读取完还须要设置一下暴力反射才能够
c.setAccessible(true);// 暴力反射
2.1.5 反射失去类中所有的构造函数
2.2 反射类中的办法
package com.cj.test;
import java.util.Date;
public class Person {public Person(){System.out.println("默认的无参构造方法执行了");
}
public Person(String name){System.out.println("姓名:"+name);
}
public Person(String name,int age){System.out.println(name+"="+age);
}
private Person(int age){System.out.println("年龄:"+age);
}
public void m1() {System.out.println("m1");
}
public void m2(String name) {System.out.println(name);
}
public String m3(String name,int age) {System.out.println(name+":"+age);
return "aaa";
}
private void m4(Date d) {System.out.println(d);
}
public static void m5() {System.out.println("m5");
}
public static void m6(String[] strs) {System.out.println(strs.length);
}
public static void main(String[] args) {System.out.println("main");
}
}
&&
package com.cj.test;
import java.lang.reflect.Method;
import java.util.Date;
import org.junit.Test;
public class Demo2 {@Test//public void m1()
public void test1() throws Exception{Class clazz = Class.forName("com.cj.test.Person");
Person p = (Person)clazz.newInstance();
Method m = clazz.getMethod("m1", null);
m.invoke(p, null);
}
@Test//public void m2(String name)
public void test2() throws Exception{
Class clazz = Person.class;
Person p = (Person) clazz.newInstance();
Method m = clazz.getMethod("m2", String.class);
m.invoke(p, "张三");
}
@Test//public String m3(String name,int age)
public void test3() throws Exception{
Class clazz = Person.class;
Person p = (Person) clazz.newInstance();
Method m = clazz.getMethod("m3", String.class,int.class);
String returnValue = (String)m.invoke(p, "张三",23);
System.out.println(returnValue);
}
@Test//private void m4(Date d)
public void test4() throws Exception{
Class clazz = Person.class;
Person p = (Person) clazz.newInstance();
Method m = clazz.getDeclaredMethod("m4", Date.class);
m.setAccessible(true);
m.invoke(p,new Date());
}
@Test//public static void m5()
public void test5() throws Exception{
Class clazz = Person.class;
Method m = clazz.getMethod("m5", null);
m.invoke(null,null);
}
@Test//private static void m6(String[] strs)
public void test6() throws Exception{
Class clazz = Person.class;
Method m = clazz.getDeclaredMethod("m6",String[].class);
m.setAccessible(true);
m.invoke(null,(Object)new String[]{"a","b"});
}
@Test
public void test7() throws Exception{
Class clazz = Person.class;
Method m = clazz.getMethod("main",String[].class);
m.invoke(null,new Object[]{new String[]{"a","b"}});
}
}
留神:看下上边代码里 test6 和 test7 的 invoke 办法里传的参数和其余的有点不一样 *
这是因为 jdk1.4 和 jdk1.5 解决 invoke 办法有区别
1.5:public Object invoke(Object obj,Object…args)
1.4:public Object invoke(Object obj,Object[] args)
因为 JDK1.4 和 1.5 对 invoke 办法的解决有区别,所以在反射相似于 main(String[] args) 这种参数是数组的办法时须要非凡解决
启动 Java 程序的 main 办法的参数是一个字符串数组,即 public static void main(String[] args),通过反射形式来调用这个 main 办法时,如何为 invoke 办法传递参数呢?按 jdk1.5 的语法,整个数组是一个参数,而按 jdk1.4 的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给 invoke 办法时,javac 会到底依照哪种语法进行解决呢?jdk1.5 必定要兼容 jdk1.4 的语法,会按 jdk1.4 的语法进行解决,即把数组打散成为若干个独自的参数。所以,在给 main 办法传递参数时,不能应用代码 mainMethod.invoke(null,new String[]{“xxx”}),javac 只把它当作 jdk1.4 的语法进行了解,而不把它当作 jdk1.5 的语法解释,因而会呈现参数个数不对的问题。
上述问题的解决办法:
(1)mainMethod.invoke(null,new Object[]{new String[]{“xxx”}});
这种形式,因为你传的是一个数组的参数,所以为了向下兼容 1.4 的语法,javac 遇到数组会给你拆开成多个参数,然而因为咱们这个 Object[] 数组里只有一个元素值,所以就算它拆也没关系
(2)mainMethod.invoke(null,(Object)new String[]{“xxx”});
这种形式相当于你传的参数是一个对象,而不是数组,所以就算是依照 1.4 的语法它也不会拆,所以问题搞定
编译器会作非凡解决,编译时不把参数当作数组对待,也就不会数组打散成若干个参数了
对上边的形容进行一下总结:在反射办法时,如果办法的参数是一个数组,思考到向下兼容问题,会依照 JDK1.4 的语法来看待(JVM 会把传递的数组参数拆开,拆开就会报参数的个数不匹配的谬误)
解决办法:避免 JVM 拆开你的数组
形式一:把数组看做是一个 Object 对象
形式二:从新构建一个 Object 数组,那个参数数组作为惟一的元素存在。
2.3 反射类中的属性字段
package com.cj.test;
import java.util.Date;
public class Person {
public String name="李四";
private int age = 18;
public static Date time;
public int getAge() {return age;}
public Person(){System.out.println("默认的无参构造方法执行了");
}
public Person(String name){System.out.println("姓名:"+name);
}
public Person(String name,int age){System.out.println(name+"="+age);
}
private Person(int age){System.out.println("年龄:"+age);
}
public void m1() {System.out.println("m1");
}
public void m2(String name) {System.out.println(name);
}
public String m3(String name,int age) {System.out.println(name+":"+age);
return "aaa";
}
private void m4(Date d) {System.out.println(d);
}
public static void m5() {System.out.println("m5");
}
public static void m6(String[] strs) {System.out.println(strs.length);
}
public static void main(String[] args) {System.out.println("main");
}
}
package com.cj.test;
import java.lang.reflect.Field;
import java.util.Date;
import org.junit.Test;
public class Demo3 {
//public String name="李四";
@Test
public void test1() throws Exception{
Class clazz = Person.class;
Person p = (Person)clazz.newInstance();
Field f = clazz.getField("name");
String s = (String)f.get(p);
System.out.println(s);
// 更改 name 的值
f.set(p, "王六");
System.out.println(p.name);
}
@Test//private int age = 18;
public void test2() throws Exception{
Class clazz = Person.class;
Person p = (Person)clazz.newInstance();
Field f = clazz.getDeclaredField("age");
f.setAccessible(true);
int age = (Integer)f.get(p);
System.out.println(age);
f.set(p, 28);
age = (Integer)f.get(p);
System.out.println(age);
}
@Test//public static Date time;
public void test3() throws Exception{
Class clazz = Person.class;
Field f = clazz.getField("time");
f.set(null, new Date());
System.out.println(Person.time);
}
}
以上就是本人对 Java 中反射的一些学习总结,欢送大家留言一起学习、探讨
看完上边无关反射的货色,对罕用框架里的配置文件是不是有点思路了
上边是 Spring 配置文件里的常见的 bean 配置,这看起来是不是能够用反射很轻易的就能够实现:解析 xml 而后把 xml 里的内容作为参数,利用反射创建对象。
拓展:
1、除了上述的 Spring 配置文件里会用到反射生成 bean 对象,其余常见的 MVC 框架,比方 Struts2、SpringMVC 等等一些框架里还有很多中央都会用到反射。
前端夜页面录入的一些信息通过表单或者其余模式传入后端,后端框架就能够利用反射生成对应的对象,并利用反射操作它的 set、get 办法把前端传来的信息封装到对象里。
感兴趣的话能够看下这篇:利用 Java 反射模仿一个 Struts2 框架 Struts2 次要外围设计 手动实现 Struts2 外围代码,这篇里边蕴含了 XML 解析、反射的货色,模仿了一个 Struts2 的外围代码
2、框架的代码里常常须要利用反射来操作对象的 set、get 办法,来把程序的数据封装到 Java 对象中去。
如果每次都应用反射来操作对象的 set、get 办法进行设置值和取值的话,过于麻烦,所以 JDK 里提供了一套 API,专门用于操作 Java 对象的属性 (set/get 办法),这就是 内省
对于内省相干的内容我也整顿了一篇文章,感兴趣能够点击:Java 反射——内省(Introspector)以及 BeanUtils 内省框架
3、平时用到的框架,除了配置文件的模式,当初很多都应用了注解的模式。
其实注解也和反射非亲非故:应用反射也能轻而易举的拿到类、字段、办法上的注解,而后编写注解解析器对这些注解进行解析,做一些相干的解决
所以说不论是配置文件还是注解的模式,它们都和反射无关。注解和自定义注解的内容,最近也抽时间大略整顿了一下,感兴趣的小可爱能够点击理解:Java 中的注解以及自定义注解
写在最初:反射是框架的灵魂,具备反射常识和思维,是看懂框架的根底。