反射(Reflection
)是 Java 中的一种工具,即运行态的 Java
程序可获取任意一个对象的信息,并且可以操作类或对象的内部属性(类型、属性、方法)。
程序中一般的对象的类型都是在编译期就确定下来的,而Java反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。
反射的核心是JVM
在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。反射是相对常规的通过new
来创建对象方式的反操作的称呼。
实际上,只有当程序需要动态创建某个类的对象时才会考虑使用反射,通常在开发通用性比较广的框架、基础平台时可能会大量使用反射。
概述
JAVA反射机制是在运行状态中,对于任意一个对象,可以获取到该对象的所有属性和方法,并可以执行调用;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要了解一个运行态的对象,必须先要获取到该类的字节码文件对象(Class
),每一个对象都会有一个字节码文件对象,通过Class
对象的方法,可以获取到类对象的方法和属性。
Class对象
获得Class对象
在Java程序中获得Class
对象通常有如下三种方式:
- 使用
Class
类的forName(Sting ClazzName)
静态方法。传入的是类的全限定类名。
Class c1 = Class.forName("Student");
- 调用类的
class
属性来获取该类对应的Class
对象。
Class c2 = Student.class;
- 调用任何一个对象都有的
getClass()
方法。返回所属类对应的Class
对象。
Student st = new Student();
Class c3 = st .getClass();
//c3是运行时类 (e的运行时类是Student)
第一和第二种都是直接根据类来取得该类Class
对象,第二种方式有两种优势:代码更安全(编译阶段即可检查需要访问的Class对象是否存在)、程序性能更好(无须调用方法)
Class
类提供了大量的方法来获取Class对象所对应类的详信息,可以获取该类里包含的成员变量、构造器、方法、内部类、注解等。
Class方法
- **getDeclaredConstructors()**获取对应类的所有构造器,含
private
修饰的构造。
- getConstructors获取对应类的所有公共(
public
)修饰的构造。
反射
反射的作用
- 在运行态时创建类的对象。
- 判断创建的对象所属的类。
- 获取类所具有的属性和方法(暴力反射可获取private类和方法)。
- 可以调用对象的方法。
反射相关的类
- java.lang.Class;
- java.lang.reflect.Constructor;
- java.lang.reflect.Field;
- java.lang.reflect.Method;
- java.lang.reflect.Modifier;
反射中的方法,属性等操作我们可以从这四个类中查询。多查JDK的API
反射的使用
Class对象可以获得该类的方法(由Method对象表示)、构造器(由Constructor对象表示)、成员变量(由Field对象表示)。
创建对象
创建对象有两种方式
- 使用Class对象的
newInstance()
方法来创建该Class对象对应类的实例。
前提是该Class对象有默认的构造器,而执行的newInstance()**方法时实际上是利用默认无参构造器来创建该类的实例。1 2 3 4
| Class stClazz = Class.forName("Student"); //创建此Class 对象所表示的类的一个新实例 //调用了Student的无参数构造方法. Object st = stClazz .newInstance();
|
- 先使用Class对象获取指定的
Constructor
对象,再调用Constructor
对象的newInstance()
方法来创建该Class**对象对应类的实例。可以先择使用指定的构造器来创建实例。1 2 3 4 5 6 7
| //获取class对象 Class<Person> p = Person.class; //获取有参构照方法 Constructor<Person> cstr = p.getConstructor(String.class,int.class); //创建对象并传参 Person person = cstr.newInstance("张三",22); System.out.println(person);
|
第一种方式比较常见,在很多JaveEE框架中都需要根据配置文件信息来创建Java对象,从配置文件读取的只是某个类的字符串类名,程序需要根据字符串业创建对应的实例,这就必须用到反射。Spring
框架的XML
配置文件使用就是这种方式的体现。
调用方法
当获得某个类对应的Class对象后,可通过该Class对象的**getMethods()方法或getMethod()**方法来获取全部方法或指定方法——这两个方法的返回值是Method()数组
,或Method()对象
。
当通过Method的invoke()
方法来执行对应的方法时,如果是private
修改的访问,必须先开启访问权限,使用setAccessible(boolean flag)
方法,将值置为true
,指示Method
在使用时取消访问权限查。
访问类属性
通过Class对象的getFields()
或getField()
方法可以获取该类所包括的全部属性或指定的属性。
Field提供了两组方法来读取或设置成员变量值:
- getXxx(Object obj):获取obj对象的该成员变量的值。如果该成员变量的类型是引用类型,则取消
get
后面的Xxx。
- setXxx(Object obj, Xxx val):将obj对象的该成员变量设置成val值。如果该成员变量的类型是引用类型,则取消
set
后面的Xxx。
反射示例
实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class People { public String nickName; private String realName; private int age; public String getP1(String name, int age) { return "name= " + name + "; age= " + age; } public String getP2(String name, int age) { return "name= " + name + "; age= " + age; } @Override public String toString() { return "People [nickName=" + nickName + ", realName=" + realName + ", age=" + age + "]"; } }
|
创建Class对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package com.demo; public class Test3 { public static void main(String[] args) { Person p1 = new Person(); Class<?> clazz1 = null; try { clazz1 = Class.forName("com.demo.Person"); } catch (ClassNotFoundException e) { e.printStackTrace(); } boolean instance = clazz1.isInstance(p1); System.out.println(instance); Class<Person> clazz2 = Person.class; Person p2 = new Person();
Class<? extends Person> clazz3 = p2.getClass(); System.out.println(clazz1 == clazz2); System.out.println(clazz2 == clazz3);
Field nameField = clazz3.getDeclaredField("name"); nameField.setAccessible(true); nameField.set(p1, "Yeeku.H.Lee"); Field ageField = personClazz.getDeclaredField("age"); ageField.setAccessible(true); ageField.setInt(p1, 30); System.out.println(p1); } }
|
结果如下:
1 2 3 4
| ===========输出内容============================================== true true true
|
配置文件创建对象
分别有父类水果(Fruit);子类苹果(Apple)、香蕉(Banana)、桔子(Orange)、榨汁(squeeze);榨汁机(Juicer)。
配置文件名config.properties
,文件里有一行类名的参数com.heima.reflect.Apple
,读取配置文件里的一行内容,获取类的字节码对象,通过对象调用newInstance()
方法创建一个新的实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| package com.demo; import java.io.BufferedReader; import java.io.FileReader; public class Test4 { public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new FileReader("config.properties")); Class<?> clazz = Class.forName(br.readLine()); Fruit f = (Fruit) clazz.newInstance(); Juicer j = new Juicer(); j.run(f); } } interface Fruit { public void squeeze(); } class Apple implements Fruit { public void squeeze() { System.out.println("榨出一杯苹果汁"); } } class Orange implements Fruit { public void squeeze() { System.out.println("榨出一杯橘子汁"); } } class Juicer {
public void run(Fruit f) { f.squeeze(); } }
|
参构造创建对象
Constructor:Class类的newInstance()
方法是使用该类无参的构造函数创建对象, 如果一个类没有无参的构造函数, 就不能这样创建了,可以调用Class类的getConstructor(String.class,int.class)
方法获取一个指定的构造函数然后再调用Constructor
类的newInstance("张三",20)
方法创建对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| package com.demo; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class Test5 { public static void main(String[] args) throws Exception { Class<?> pClazz = Class.forName("com.demo.Person"); Constructor<?> c = pClazz.getConstructor(String.class, int.class); Person p = (Person) c.newInstance("张三", 23); System.out.println(p); } }
|
暴力反射操作属性
Field:Class.getField(String)
方法可以获取类中的指定字段(可见的), 如果是私有的可以用getDeclaedField("name")
方法获取,通过set(obj, "李四")
方法可以设置指定对象上该字段的值,如果是私有的需要先调用setAccessible(true)
设置访问权限,用获取的指定的字段调用get(obj)
可以获取指定对象中该字段的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Test5 { public static void main(String[] args) throws Exception { Class<?> clazz = Class.forName("com.demo.Person"); Constructor<?> c = clazz.getConstructor(String.class, int.class); Person p = (Person) c.newInstance("李四", 19);
Field f = clazz.getDeclaredField("name"); f.setAccessible(true); f.set(p, "赵五"); System.out.println(f.get(p)); System.out.println(p); } }
|
获取方法并调用
Method对象:Class.getMethod(String, Class...)
和 Class.getDeclaredMethod(String, Class...)
方法可以获取类中的指定方法,调用invoke(Object, Object...)
可以调用该方法。
Class.getMethod(“eat”) invoke(obj) Class.getMethod(“eat”,int.class) invoke(obj,10)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package com.demo; import java.lang.reflect.Constructor; import java.lang.reflect.Method; public class Test5 { public static void main(String[] args) { try { Class<?> clazz = Class.forName("com.demo.Person"); Constructor<?> c = clazz.getConstructor(String.class, int.class); Person p = (Person) c.newInstance("张三", 23); Method m = clazz.getMethod("eat"); m.invoke(p); Method m2 = clazz.getMethod("eat", int.class); m2.invoke(p, 2); } catch (Exception e) { } } }
|
访问类属性和方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| package com.demo; import java.lang.reflect.Field; import java.lang.reflect.Method; public class Test9 { public static void main(String[] args) throws Exception { Class<?> klass = People.class; Object obj = klass.newInstance(); String klassType = klass.getTypeName(); System.out.println("getTypeName()获取klass对象类型:" + klassType); System.out.println("---------------------------------------------------------------"); Field[] fields2 = klass.getFields(); for (Field field : fields2) { System.out.println("getFields()获取公共的成员变量" + field); } System.out.println("---------------------------------------------------------------"); Field[] fields = klass.getDeclaredFields(); for (Field field : fields) { System.out.println("getDeclaredFields()获取所有的成员变量:" + field); } System.out.println("---------------------------------------------------------------"); Method method = klass.getMethod("getP1", String.class, int.class); Method[] methods = klass.getMethods(); for (Method m : methods) { System.out.println("getMethods()获取所有包括继承的方法:" + m); } System.out.println("---------------------------------------------------------------"); Method[] declaredMethods = klass.getDeclaredMethods(); for (Method m : declaredMethods) { System.out.println("getDeclaredMethods()获取所有自有的方法:" + m); } System.out.println("---------------------------------------------------------------"); Method method2 = klass.getMethod("getP1", String.class, int.class); Object obj2 = method2.invoke(obj, "张三", 22); System.out.println(obj2); } }
|
结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| =====================以下为输出内容======================================================= getTypeName()获取klass对象类型:com.demo.People --------------------------------------------------------------- getFields()获取公共的成员变量public java.lang.String com.demo.People.nickName --------------------------------------------------------------- getDeclaredFields()获取所有的成员变量:public java.lang.String com.demo.People.nickName getDeclaredFields()获取所有的成员变量:private java.lang.String com.demo.People.realName getDeclaredFields()获取所有的成员变量:private int com.demo.People.age --------------------------------------------------------------- getMethods()获取所有包括继承的方法:public java.lang.String com.demo.People.toString() getMethods()获取所有包括继承的方法:public java.lang.String com.demo.People.getP1(java.lang.String,int) getMethods()获取所有包括继承的方法:public java.lang.String com.demo.People.getP2(java.lang.String,int) getMethods()获取所有包括继承的方法:public final void java.lang.Object.wait() throws java.lang.InterruptedException getMethods()获取所有包括继承的方法:public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException getMethods()获取所有包括继承的方法:public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException getMethods()获取所有包括继承的方法:public boolean java.lang.Object.equals(java.lang.Object) getMethods()获取所有包括继承的方法:public native int java.lang.Object.hashCode() getMethods()获取所有包括继承的方法:public final native java.lang.Class java.lang.Object.getClass() getMethods()获取所有包括继承的方法:public final native void java.lang.Object.notify() getMethods()获取所有包括继承的方法:public final native void java.lang.Object.notifyAll() --------------------------------------------------------------- getDeclaredMethods()获取所有自有的方法:public java.lang.String com.demo.People.toString() getDeclaredMethods()获取所有自有的方法:public java.lang.String com.demo.People.getP1(java.lang.String,int) getDeclaredMethods()获取所有自有的方法:public java.lang.String com.demo.People.getP2(java.lang.String,int) --------------------------------------------------------------- name= 张三; age= 22 -
|
越过泛型检查
ArrayList的一个对象,在这个集合中添加一个字符串数据,如何实现呢?
泛型只在编译期有效,在运行期会被擦除掉。而反射是运行阶段,立用反射绕过编译阶段的泛型检查。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package com.demo; import java.lang.reflect.Method; import java.util.ArrayList; public class Test5 { public static void main(String[] args) throws Exception { ArrayList<Integer> list = new ArrayList<>(); list.add(111); list.add(222); Class<?> clazz = Class.forName("java.util.ArrayList"); Method m = clazz.getMethod("add", Object.class); m.invoke(list, "abc"); System.out.println(list); } }
|
通用方法设置值
public void setProperty(Object obj, String propertyName, Object value){}
,此方法可将obj对象中名为propertyName
的属性的值设置为value
。
1 2 3 4 5 6 7 8 9 10 11 12 13
| package com.demo; import java.lang.reflect.Field; public class Test6 { public void setProperty(Object obj, String propertyName, Object value) throws Exception { Class clazz = obj.getClass(); Field f = clazz.getDeclaredField(propertyName); f.setAccessible(true); f.set(obj, value); } }
|