反射
反射就是把Java类中的各种成分映射成相应的Java类。简单说,就是通过一个class文件对象来使用该class文件中的构造方法,成员变量,成员方法。
反射为Java提供了动态加载执行代码的能力,大大增强了程序的扩展性。
使用反射的基本步骤:
- 获得字节码文件对象,即获得Class对象。
- 获得类的成员属性、方法或者构造函数。
- 访问属性,调用构造函数创建对象,调用成员方法。
Class 类
类就是一组相关的属性和行为的集合,它是一个抽象的概念。由于多个class文件也会存在相同的内容,所以,我们就可以把多个class文件抽象成一个类来描述,这个类就是Class类。
注意:所有类的对象实际上都是Class类的实例,因此所有的对象都可以转变为java.lang.Class类型表示
获取Class类对象的三种方式:
调用Object类中声明的getClass()方法
Person p = new Person(); Class clz = p.getClass();通过类型的一个静态的class属性
Class c = Person.class;通过Class类的一个静态方法
forName()// 需要传入完整的包名类名 Class c1 = Class.forName("com.company.Person");
程序中所有使用的具体类名在设计时(即开发时)无法确定,只有程序运行时才能确定,这时候就可以使用Class.forName去动态加载该类,这个类名通常可在配置文件中配置。
Class类的常用方法
| 返回值类型 | 方法 | 描述 |
|---|---|---|
static Class<?> |
forName(String className) | 使用给定的包名类名返回与类或接口关联的Class对象。 |
Constructor<?>[] |
getConstructors() | 获得类中的全部构造方法对象 |
Field[] |
getDeclaredFields() | 获取类中单独定义的全部属性 |
Field[] |
getFields() | 获取类中继承而来的全部属性 |
Method[] |
getMethods() | 获取类中所有公开的方法,包括继承而来的 |
Method[] |
getDeclaredMethods() | 获取类中所有声明的方法,不包括继承而来的 |
Method |
getMethod(String name, Class<?>... parameterTypes) | 返回一个Method对象,它反映了这个Class对象所代表的类或接口的公共方法。name指定所需方法的名称。parameterTypes参数是一个Class对象的数组,按照声明的顺序标识方法的形式参数类型 |
Class<?>[] |
getInterfaces() | 获取类中所实现的全部接口 |
String |
getName() | 获取类完整的包名类名 |
Package |
getPackage() | 获取此类的包 |
Class<? super T> |
getSuperclass() | 获取此类的父类 |
T |
newInstance() | 根据Class定义的类,实例化对象 |
Class<?> |
getComponentType() | 返回表示数组类型的Class |
boolean |
isArray() | 该Class对象是否是一个数组类 |
Constructor 类
Constructor提供了一个类的单个构造函数的信息,以及对其的访问。
| 返回值类型 | 方法 | 描述 |
|---|---|---|
AnnotatedType |
getAnnotatedReceiverType() | 返回一个AnnotatedType对象,表示使用一个类型来指定这个Executable对象所代表的方法/构造函数的接收者类型。 |
AnnotatedType |
getAnnotatedReturnType() | 返回一个AnnotatedType对象,表示使用一个类型来指定这个Executable所代表的方法/构造函数的返回类型。 |
<T extends Annotation>T |
getAnnotation(Class |
如果存在指定类型的注解,则返回该元素的注解,否则为空。 |
Annotation[] |
getDeclaredAnnotations() | 返回直接存在于该元素上的注解。 |
Class<T> |
getDeclaringClass() | 返回表示该类或接口的Class对象,该类或接口声明此对象表示的可执行文件。 |
Class<?>[] |
getExceptionTypes() |
返回一个Class对象数组,这些对象表示声明为由此对象表示的基础可执行文件引发的异常的类型。 |
Type[] |
getGenericExceptionTypes() | 返回一个Type对象数组,这些对象表示声明为此可执行对象引发的异常。 |
Type[] |
getGenericParameterTypes() | 返回一个Type对象的数组,Type以声明顺序表示由该对象表示的可执行文件的形式参数类型。 |
int |
getModifiers() | 返回此对象表示的可执行文件的Java语言修饰符。 |
String |
getName() | 以字符串形式返回此构造函数的名称。 |
Annotation[][] |
getParameterAnnotations() | 返回一个Annotations数组,该数组表示此对象表示的Executable的形式参数上的注解(按声明顺序)。 |
int |
getParameterCount() | 返回由此对象表示的可执行文件的形式参数(无论是显式声明还是隐式声明)的数量。 |
Class<?>[] |
getParameterTypes() | 返回此对象表示的可执行文件的形式参数数量(无论是显式声明还是隐式声明,或者都不提供)。 |
TypeVariable<Constructor<T>>[] |
getTypeParameters() | 返回TypeVariable对象的数组,这些对象按声明顺序表示此GenericDeclaration对象表示的泛型声明所声明的类型变量。 |
boolean |
isVarArgs() | 如果这个可执行文件被声明为接受一个可变数量的参数,则返回true;否则返回false。 |
T |
newInstance(Object... initargs) | 使用此Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。 |
String |
toGenericString() | 返回描述此Constructor的字符串,包括类型参数。 |
较常用的几个方法:
getModifiers():获取构造方法的修饰符,返回值是一些整型常量,详见ModifiergetName():获取构造方法名字getParameterTypes():获取构造方法中,参数的类型。newInstance(Object... initargs):向构造方法中传递参数,实例化对象
Field 类
字段(Field)提供了关于类或接口的单个字段的信息,以及对该字段的动态访问。反映的字段可能是一个类(静态)字段或一个实例字段。
| 返回值类型 | 方法 | 描述 |
|---|---|---|
Object |
get(Object obj) | 返回指定对象上该Field所代表的字段的值。 |
void |
set(Object obj, Object value) | 在指定的对象参数上,将此Field对象所代表的字段设置为指定的新值。 |
int |
getModifiers() | 以整数形式返回此Field对象表示的字段的Java语言修饰符。 |
String |
getName() | 返回此Field对象表示的字段的名称。 |
Annotation[] |
getDeclaredAnnotations() | 返回直接存在于此元素上的注解。 |
<T extends Annotation> T |
getAnnotation(Class |
如果存在这样的注解,则返回指定类型的该元素的注解,否则为null。 |
Method 类
Method提供了关于一个类或接口上的单个方法的信息,以及对该方法的访问。反映的方法可以是一个类方法或一个实例方法(包括抽象方法)。
| 返回值类型 | 方法 | 描述 |
|---|---|---|
int |
getModifiers() | 获取此方法的访问修饰符 |
String |
getName() | 获得方法的名称。 |
Class<?>[] |
getParameterTypes() | 得到方法的全部参数类型(按声明顺序) |
Class<?> |
getReturnType() | 获取方法的返回值类型 |
Class<?>[] |
getExceptionTypes() | 获取此方法抛出的全部异常 |
Object |
invoke(Object obj, Object... args) | 在指定的对象上,用指定的参数调用这个对象的方法 |
代码示例
public class Person {
private String name;
private int age;
public Person() {}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String sayHello(String name,int age){
return "你好!我叫"+name+",今年"+age+"了。";
}
private void greet(String name){
System.out.println("Hello,"+name);
}
}
通过反射调用类中的方法
import java.lang.reflect.Method;
public classPersonDemo {
public static void main(String[] args) throws Exception {
//实例化Class对象
Class clz = Person.class;
//通过Class对象获得sayHello()方法对像
Method mt = clz.getMethod("sayHello",String.class,int.class);
// 实例化Person类
Object obj = clz.newInstance()
//调用Person对象的sayHello方法,并输出结果
System.out.println(mt.invoke(obj,"张三",18));
}
}
暴力反射
当我们需要访问类中私有的成员属性或方法时,就需要取消Java 语言访问检查。而AccessibleObject类中提供了一个setAccessible(boolean flag)方法,传入true时可以实现我们的目的。
注意到Method和Field类都是AccessibleObject的子类,因此可以对它们调用此方法。
public static void main(String[] args) {
try {
Class<?> clazz = Person.class;
Person person = (Person) clazz.newInstance();
// 获取私有属性age
Field field = clazz.getDeclaredField("age");
// 将目标属性设置为可以访问
field.setAccessible(true);
// 给私有属性age设置值
field.set(person, 18);
// 获取私有方法greet
Method m = clazz.getDeclaredMethod("greet",String.class);
m.setAccessible(true);
// 调用私有方法
m.invoke(person,"Alice");
} catch (Exception e) {
e.printStackTrace();
}
}
反射数组
Class<?> classType = Class.forName("java.lang.String");
// 通过反射创建一个长度为8的字符串数组
Object array = Array.newInstance(classType, 8);
// 把索引为1的元素设为"hello"
Array.set(array, 1, "hello");
// 获得索引为1的元素
String s = (String) Array.get(array, 1);
System.out.println(s);
反射静态方法
Class<?> threadClazz = Class.forName("java.lang.Math");
Method method = threadClazz.getMethod("abs", long.class);
System.out.println(method.invoke(null, -100l));
调用静态方法,将第一个参数设为null即可。
公众号“编程之路从0到1”