1.概念
Java反射是指:在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。
这也是Java作为静态语言,却能被视为动态/准动态语言(动态语言是指程序运行时,允许改变程序结构或变量类型。Perl,Python,Ruby是动态语言;C++,Java,C#是静态语言)的一个关键性质。
2.作用
反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括:modifiers(修饰符),fields(属性),methods(方法)等,并可于运行时改变fields内容或调用methods。
运用反射,我们便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度。此外,还有动态代理的实现,等等。
但是需要注意的是:反射使用不当会造成很高的资源消耗。
3.反射的实现
3.1关键概念
Java 提供反射机制,依赖于 Class 类和 java.lang.reflect 类库。
反射API用来生成JVM中的类、接口或则对象的信息。
-
Class类:反射的核心类,可以获取类的属性,方法等信息。
Class 类是 Java 中用来表示运行时类型信息的对应类。在 Java 中,每个类都有一个 Class 对象,每当我们编写并且编译一个新创建的类,就会将相关信息写到 .class 文件里。当我们 new 一个新对象或者引用静态成员变量时,JVM 中的类加载器子系统会将对应 Class 对象加载到 JVM 中,然后 JVM 再根据这个类型信息相关的 Class 对象创建我们需要实例对象或者提供静态变量的引用值。我们可以将 Class 类,称为类类型,一个 Class 对象,称为类类型对象。
Field类:Java.lang.reflec包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。
Method类: Java.lang.reflec包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。
Cons:Java.lang.reflec包中的类,表示类的构造方法。
总结一下:反射就是把java类中的各种成分映射成一个个的Java对象
3.2加载过程
加载的时候:将.class文件读入内存,并为之创建一个Class对象
4.反射的使用
步骤如下:
- 获取想要操作的类的Class对象
- 调用Class类中的方法
- 使用反射API来操作这些信息
4.1获取Class对象
- Object.getClass( ): 返回一个对象的运行时类
- 通过类名.class获取:任何数据类型(包括基本数据类型)都有一个“静态”的class属性
- 通过Class类的静态方法:Class.forName(String className) (常用)
注意:在运行期间,一个类,只有一个Class对象产生。
说明:上述三种方式,常用第三种。
第一种:已经有了对象,直接通过对象去调用即可,再用反射意义不大。
第二种:需要导入类的包,依赖太强,不导包就抛编译错误。
第三种:参数字符串,可以直接传入,也可以写在配置文件中。
代码实现:
@Test
public void test1() throws ClassNotFoundException {
// 方法1:通过Object.getClass( ),返回一个对象的运行时类
Cat cat = new Cat();
Class extends Cat> clazz1 = cat.getClass();
System.out.println("方法1:" + clazz1);
// 方法2:通过类名.class获取
Class clazz2 = Cat.class;
System.out.println("方法2:" + clazz2);
Assert.assertEquals(clazz2, clazz1);
// 方法3:通过Class.forName()获取
Class clazz3 = Class.forName("com.qyl.spring.resttemplate.domain.Cat");
System.out.println("方法3:" + clazz3);
Assert.assertEquals(clazz3, clazz1);
}
4.2获取构造方法
Cat类:
@Data
//@NoArgsConstructor
//@AllArgsConstructor
//@Builder
public class Cat {
public String name;
private Integer age;
private String sound;
// 默认的构造方法
Cat(String str) {
System.out.println("默认的构造方法,s = " + str);
}
// 无参构造方法
public Cat() {
System.out.println("调用了共有的无参构造方法执行");
}
// 有一个参数的构造方法
public Cat(char name) {
System.out.println("姓名:" + name);
}
// 有多个参数的构造方法
public Cat(String name, int age) {
System.out.println("姓名:"+name+"年龄:"+ age);
}
protected Cat(boolean n) {
System.out.println("受保护的构造方法 n = " + n);
}
//私有构造方法
private Cat(int age) {
System.out.println("私有的构造方法,年龄:"+ age);
this.age = age;
}
}
测试:
@Test
public void test2() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class clazz = Class.forName("com.qyl.spring.resttemplate.domain.Cat");
//获取所有共有的构造方法
System.out.println("-----------所有共有构造方法-----------");
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
System.out.println("\n-----------所有的构造方法(包括:私有、受保护、默认、公有)-----------");
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for(Constructor declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
System.out.println("\n-----------获取公有、无参的构造方法-----------");
Constructor constructor = clazz.getConstructor();
System.out.println("constructor = " + constructor);
Object obj = constructor.newInstance();
Cat cat = (Cat)obj;
System.out.println(cat);
System.out.println("\n-----------获取私有构造方法,并调用-----------");
Constructor con = clazz.getDeclaredConstructor(int.class);
System.out.println(con);
//调用构造方法
con.setAccessible(true);//暴力访问(忽略掉访问修饰符)
Object o = con.newInstance(3);
System.out.println(o);
}
执行结果:
-----------所有共有构造方法-----------
public com.qyl.spring.resttemplate.domain.Cat(java.lang.String,int)
public com.qyl.spring.resttemplate.domain.Cat()
public com.qyl.spring.resttemplate.domain.Cat(char)
-----------所有的构造方法(包括:私有、受保护、默认、公有)-----------
private com.qyl.spring.resttemplate.domain.Cat(int)
protected com.qyl.spring.resttemplate.domain.Cat(boolean)
public com.qyl.spring.resttemplate.domain.Cat(java.lang.String,int)
com.qyl.spring.resttemplate.domain.Cat(java.lang.String)
public com.qyl.spring.resttemplate.domain.Cat()
public com.qyl.spring.resttemplate.domain.Cat(char)
-----------获取公有、无参的构造方法-----------
constructor = public com.qyl.spring.resttemplate.domain.Cat()
调用了共有的无参构造方法执行
Cat(name=null, age=null, sound=null)
-----------获取私有构造方法,并调用-----------
private com.qyl.spring.resttemplate.domain.Cat(int)
私有的构造方法,年龄:3
Cat(name=null, age=3, sound=null)
4.3获取属性
@Test
public void test3() throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 获取Class对象
Class clazz = Class.forName("com.qyl.spring.resttemplate.domain.Cat");
// 获取字段
System.out.println("-----------获取所有公有的字段-----------");
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("\n-----------获取所有的字段(包括私有、受保护、默认的)-----------");
Field[] declaredFields = clazz.getDeclaredFields();
for (Field f : declaredFields) {
System.out.println(f);
}
System.out.println("\n-----------获取公有字段并调用-----------");
Field f = clazz.getField("name");
System.out.println(f);
//获取一个对象
Object obj = clazz.getConstructor().newInstance();
f.set(obj, "昭君");
Cat cat = (Cat)obj;
System.out.println("验证姓名:" + cat.getName());
System.out.println("\n-----------获取私有字段并调用-----------");
f = clazz.getDeclaredField("age");
System.out.println(f);
f.setAccessible(true); // 暴力反射,解除私有限定
f.set(cat, 5);
System.out.println("验证电话:" + cat);
}
运行结果:
-----------获取所有公有的字段-----------
public java.lang.String com.qyl.spring.resttemplate.domain.Cat.name
-----------获取所有的字段(包括私有、受保护、默认的)-----------
public java.lang.String com.qyl.spring.resttemplate.domain.Cat.name
private java.lang.Integer com.qyl.spring.resttemplate.domain.Cat.age
private java.lang.String com.qyl.spring.resttemplate.domain.Cat.sound
-----------获取公有字段并调用-----------
public java.lang.String com.qyl.spring.resttemplate.domain.Cat.name
调用了共有的无参构造方法执行
验证姓名:昭君
-----------获取私有字段并调用-----------
private java.lang.Integer com.qyl.spring.resttemplate.domain.Cat.age
验证电话:Cat(name=昭君, age=5, sound=null)
4.4获取成员属性
代码如下:
@Test
public void test4() throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException {
//1.获取Class对象
Class clazz = Class.forName("com.qyl.spring.resttemplate.domain.Cat");
//2.获取所有公有方法
System.out.println("-----------获取所有的公有方法-----------");
Method[] methodArray = clazz.getMethods();
for(Method m : methodArray) {
System.out.println(m);
}
System.out.println("\n-----------获取所有的方法,包括私有的-----------");
methodArray = clazz.getDeclaredMethods();
for(Method m : methodArray) {
System.out.println(m);
}
System.out.println("\n-----------获取公有的show1()方法-----------");
Method m = clazz.getMethod("show1", String.class);
System.out.println(m);
//实例化一个Student对象
Object obj = clazz.getConstructor().newInstance();
m.invoke(obj, "貂蝉");
System.out.println("\n-----------获取私有的show4()方法-----------");
m = clazz.getDeclaredMethod("show4", int.class);
System.out.println(m);
m.setAccessible(true);//解除私有限定
Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
System.out.println("返回值:" + result);
}
运行结果:
-----------获取所有的公有方法-----------
public boolean com.qyl.spring.resttemplate.domain.Cat.equals(java.lang.Object)
public java.lang.String com.qyl.spring.resttemplate.domain.Cat.toString()
public int com.qyl.spring.resttemplate.domain.Cat.hashCode()
public java.lang.String com.qyl.spring.resttemplate.domain.Cat.getName()
public void com.qyl.spring.resttemplate.domain.Cat.setName(java.lang.String)
public void com.qyl.spring.resttemplate.domain.Cat.show1(java.lang.String)
public java.lang.Integer com.qyl.spring.resttemplate.domain.Cat.getAge()
public void com.qyl.spring.resttemplate.domain.Cat.setAge(java.lang.Integer)
public java.lang.String com.qyl.spring.resttemplate.domain.Cat.getSound()
public void com.qyl.spring.resttemplate.domain.Cat.setSound(java.lang.String)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
-----------获取所有的方法,包括私有的-----------
public boolean com.qyl.spring.resttemplate.domain.Cat.equals(java.lang.Object)
public java.lang.String com.qyl.spring.resttemplate.domain.Cat.toString()
public int com.qyl.spring.resttemplate.domain.Cat.hashCode()
public java.lang.String com.qyl.spring.resttemplate.domain.Cat.getName()
public void com.qyl.spring.resttemplate.domain.Cat.setName(java.lang.String)
public void com.qyl.spring.resttemplate.domain.Cat.show1(java.lang.String)
private java.lang.String com.qyl.spring.resttemplate.domain.Cat.show4(int)
protected void com.qyl.spring.resttemplate.domain.Cat.show2()
void com.qyl.spring.resttemplate.domain.Cat.show3()
public java.lang.Integer com.qyl.spring.resttemplate.domain.Cat.getAge()
public void com.qyl.spring.resttemplate.domain.Cat.setAge(java.lang.Integer)
protected boolean com.qyl.spring.resttemplate.domain.Cat.canEqual(java.lang.Object)
public java.lang.String com.qyl.spring.resttemplate.domain.Cat.getSound()
public void com.qyl.spring.resttemplate.domain.Cat.setSound(java.lang.String)
-----------获取公有的show1()方法-----------
public void com.qyl.spring.resttemplate.domain.Cat.show1(java.lang.String)
调用了共有的无参构造方法执行
调用了:公有的,String参数的show1(): s = 貂蝉
-----------获取私有的show4()方法-----------
private java.lang.String com.qyl.spring.resttemplate.domain.Cat.show4(int)
调用了,私有的,并且有返回值的,int参数的show4(): age = 20
返回值:age = 20
4.5读取配置文件信息
@Test
public void test5() throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//通过反射获取Class对象
Class clazz = Class.forName(getValue("className")); // "com.qyl.spring.resttemplate.domain.Book"
//2获取show()方法
Method m = clazz.getMethod(getValue("methodName")); // show
//3.调用show()方法
m.invoke(clazz.getConstructor().newInstance());
}
//此方法接收一个key,在配置文件中获取相应的value
private String getValue(String key) throws IOException {
Properties pro = new Properties();//获取配置文件的对象
FileReader in = new FileReader("src/test.properties");//获取输入流
pro.load(in);//将流加载到配置文件对象中
in.close();
return pro.getProperty(key);//返回根据key获取的value值
}
Book.java
@Data
public class Book {
public void read() {
System.out.println("this is read()");
}
}
配置文件:
className = com.qyl.spring.resttemplate.domain.Book
methodName = read
运行结果:
this is read()
好处:
当我们升级这个系统时,不再需要Book类时,而要新增一个Book_New的类时,这时只需要更改test.properties的文件内容就可以了。
4.6通过反射越过泛型检查
代码:
@Test
public void test6() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
ArrayList strList = new ArrayList<>();
strList.add("qyl");
strList.add("nn");
//获取ArrayList的Class对象,反向的调用add()方法,添加数据
Class listClazz = strList.getClass(); //得到 strList 对象的字节码 对象
//获取add()方法
Method m = listClazz.getMethod("add", Object.class);
//调用add()方法
m.invoke(strList, 100);
//遍历集合
for(Object obj : strList){
System.out.println(obj.getClass() + ":" + obj);
}
}
运行结果:
class java.lang.String:qyl
class java.lang.String:nn
class java.lang.Integer:100