Java基础回归之反射Reflection
反射是什么鬼
反射其实就是允许我们获取目标类的方法、成员变量等信息,以及可以调用、改变某些方法和成员变量的值。( JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的以及动态调用对象的方法的功能称为Java的反射机制。)--莎士比亚
详细见某百科反射百科,这个不是重点,下面开始反射之旅。
反射的使用
先上一张反射相关知识点的脑图,如果对脑图上东西一窍不通或者有些模糊,那么请往下看。
反射Reflection.png-
反射机制相关的类
-
java.lang.Class; //类
-
java.lang.reflect.Constructor;//构造方法
-
java.lang.reflect.Field; //类的成员变量
-
java.lang.reflect.Method;//类的方法
-
java.lang.reflect.Modifier;//访问权限
-
java.lang.reflect.Parameter;//方法参数
-
Class和class的区别
-
class:小写字母c开头的class是声明一个类的关键字。
-
Class:在Java中,反射的源头就是Class,每个class都有一个相应的Class对象。也就是说,当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个Class对象,它封装了这个类的信息,包括上面说的反射机制相关的类等信息。
-
Class的获取
上面说了,Class类用于封装类的各种信息,它是反射的源头,获取一个类的Class通常有以下三种方法: -
Class cls = A.class;
-
A a = new A();
Class cls = a.getClass(); -
Class cls = Class.forName("com.xx.A");
-
通过获取到的Class生成一个实例对象
反射获取Car实例.png
A a = (A) cls.newInstance(); //获取A的实例对象
假设我们有一个Car类,里面有一个start()方法,我们可以以下方法得到Car类的实例并调用start()方法,注意,需要无参构造函数:
-
类信息的获取
获取类的方法API | 说明 |
---|---|
Method[] methods = cls.getDeclaredMethods() | 获取当前类声明的所有方法列表(无视权限修饰符) |
Method[] methods = cls.getMethods() | 获取当前类及父类所有public方法列表 |
Method method = cls.getDeclaredMethod(name, parameterTypes) | 获取当前类声明的某个特定方法 |
Method method = cls.getMethod(name, parameterTypes) | 获取当前类或父类某个特定public方法 |
获取类的成员变量API | 说明 |
---|---|
Field[] fields = cls.getDeclaredFields() | 获取当前类声明的所有成员变量列表(无视权限修饰符) |
Field[] fields = cls.getFields() | 获取当前类及父类所有public成员变量列表 |
Field field = cls.getDeclaredField(name) | 获取当前类声明的某个特定成员变量 |
Field field = cls.getField(name) | 获取当前类或父类某个特定public成员变量 |
获取类的构造函数API | 说明 |
---|---|
Constructor[] constructors = cls.getDeclaredConstructors() | 获取当前类声明的所有成员变量列表(无视权限修饰符) |
Constructor[] constructors = cls.getConstructors() | 获取当前类及父类所有public构造函数列表 |
Constructor constructor = cls.getDeclaredConstructor(parameterTypes) | 获取当前类声明的某个特定构造函数 |
Constructor constructor = cls.getConstructor(parameterTypes) | 获取当前类或父类某个特定public构造函数 |
获取类或方法的修饰符API | 说明 |
---|---|
int modifiers = cls.getModifiers() | 获取类的修饰符,注意返回值是int类型,可通过Modifier.toString(modifiers)获取到相对应的String类型 |
int modifiers = method.getModifiers() | 获取方法的修饰符,同上 |
修饰符返回值int类型对应的修饰符如下图:
访问修饰符.png获取方法的参数API | 说明 |
---|---|
Class[] parameterTypes =method.getParameterTypes() | 获取方法参数的类类型列表(即.class,如int.class) |
Parameter[] parameters = method.getParameters() | 获取方法参数列表(即.class,如int.class) |
获取编程元素上的注解API | 说明 |
---|---|
Annotation[] annotations = cls(/method/field).getAnnotations() | 获取类/方法/成员变量上标注的所有注解 |
Annotation[] declaredAnnotations = cls(/method/field).getDeclaredAnnotations() | 获取类/方法/成员变量上标注的所有注解 |
Override annotation = cls(/method/field).getAnnotation(Override.class) | 获取类/方法/成员变量上某个特定注解 |
Annotation[][] parameterAnnotations = m.getParameterAnnotations() | 获取方法参数上的注解 |
boolean b = cls(/method/field).isAnnotationPresent(Bind.class) | 该变成元素上是否有Bind.class注解 |
getDeclaredAnnotation(s):返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。
getAnnotation(s):返回此元素上存在的所有注释。(如果此元素没有注释,则返回长度为零的数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。
getDeclaredAnnotations得到的是当前成员所有的注释,不包括继承的。而getAnnotations得到的是包括继承的所有注释。
关键在于继承的问题上,getDeclaredAnnotations和getAnnotations是否相同,就在于父类的注解是否可继承,这可以用sun.reflect.annotation.AnnotationType antype3=AnnotationType.getInstance(Class.forName(annotationtype_class(example:"javax.ejb.Stateful")).isInherited())来判定,如果为true,说明可以被继承则存在与getAnnotations之中而不在getDeclaredAnnotations之中,否则,也不存在与getannnotations中,因为不能被继承。
- 通过反射获取类中某个方法并调起
//我们有这个Car类
public class Car {
@Override
public void start() {
System.out.println("Car start!");
}
}
Car car = new Car();
Class cls = car.getClass();//获取类类型
Method method = cls.getDeclaredMethod("start", new Class[]{});//获取start方法,第一个参数为方法名字,
//第二个参数是一个可变参数,接收的是方法的参数的类类型列表,此处Car里面有一个方法是无参的start(),则传入new Class[]{};同理,假设Car类还有一个重载的start(int speed)方法,则我们应该通过cls.getDeclaredMethod("start", new Class[]{int.class});
//method.setAccessible(true);//注意如果目标方法是private修饰的,则需要先调用setAccessible(true)破封装,此处Car类里的start方法是public,所以不需要调用
Object obj = method.invoke(car, new Class[]{});//传入的和上面的获取方法参数同理,此处不说。
//需要说明的是如果方法没有返回值,则返回是null,即我们这里接收到的obj是null;如果对应方法有返回值则返回具体的返回值。
- 通过反射获取类中某个成员变量并修改它的值
//1.获取intField成员变量
Field declaredField = cls.getDeclaredField("intField");
//2.如果该成员变量是private,此时我们应该先设置为可操作
declaredField.setAccessible(true);
//3.赋值
declaredField.set(ca, 5);```
## 举个栗子
下面通过一个栗子把上面所说反射的使用全部串联起来。需求:打印类中所有信息,并修改某个成员变量。具体看注释,并且跟着手打一遍印象深刻点,这里直接上代码。
首先我们声明一个父类:BaseActivity
```java
public class BaseActivity {
public int basePublicFiled;//父类公有成员变量
private String basePrivateFiled;//父类私有成员变量
private BaseActivity(){
//父类私有构造函数
}
public BaseActivity(int i){
//父类公有构造函数
}
public void basePublicMethod(String str){
//父类公有方法
}
private void basePrivateMethod(String str){
//父类私有方法
}
}
然后声明一个ChildActivity继承它:
public class ChildActivity extends BaseActivity{
public ChildActivity(int i) {
super(i);
}
public int childPublicFiled;
private String childPrivateFiled;
public void childPublicMethod(String str){
//no-op
}
private void childPrivateMethod(String str){
//no-op
}
private int intField = 0;//注意,这里值是0,待会反射修改这个成员变量的值
public void plus(int i){
//待会反射调用此方法,注意看原始值和反射后打印出来的结果
System.out.println(String.valueOf(i) +" + "+ String.valueOf(intField) + " = " + (i + intField));
}
}
编写核心类,取名ClassMessageGetter,用于获取反射操作
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import entity.ChildActivity;
public class ClassMessageGetter {
/**
* 获取某个类类信息
* @param obj
*/
public static void getMethodMessage(Object obj){
//获取方法
//1.getMethods()获取该类所有public,包括父类的public方法
//2.getDeclaredMethods()获取当前类(不含父类)的所有方法(与方法访问权限无关)
System.out.println("1.getMethods()获取该类所有public,包括父类的public方法\n");
Class cls = obj.getClass();
Method[] methods = cls.getMethods();
Method[] declaredMethods = cls.getDeclaredMethods();
for(Method method : methods){
//通过method.getReturnType().getName()获取返回值名称
//通过method.getName()获取方法名称
int modifiers = cls.getModifiers();
System.out.print(Modifier.toString(method.getModifiers())+" "+method.getReturnType().getName() + " " + method.getName()+"(");
//通过method.getParameterTypes();获取方法参数的类类型
Class[] parameterTypes = method.getParameterTypes();
//通过method.getParameters()获取方法参数
Parameter[] parameters = method.getParameters();
for(int i = 0 ; i < parameterTypes.length ; i ++){
System.out.print(parameterTypes[i].getSimpleName() + " " +parameters[i].getName()+ (i!=parameterTypes.length-1?",":""));
}
System.out.println(")");
}
System.out.println("\n-----------------------------------------------------------\n");
System.out.println("2.getDeclaredMethods()获取当前类(不含父类)的所有方法(与方法访问权限无关)\n");
for(Method method : declaredMethods){
System.out.println(method.getName());
}
}
/**
* 获取类成员变量信息
* @param obj
*/
public static void getFieldMessage(Object obj){
Class cls = obj.getClass();
//1.getFields()获取该类所有public,包括父类的public成员变量
Field[] fields = cls.getFields();
//2.getDeclaredFields()获取当前类(不含父类)的所有成员变量(与变量访问权限无关)
Field[] declaredFields = cls.getDeclaredFields();
System.out.println("cls.getFields() 获取该类所有public,包括父类的public成员变量");
for (Field field : fields) {
System.out.println(field.getType().getSimpleName() + " "+field.getName());
}
System.out.println("\ngetDeclaredFields()获取当前类(不含父类)的所有成员变量(与变量访问权限无关)");
for (Field field : declaredFields) {
System.out.println(field.getType().getSimpleName() + " "+field.getName());
}
}
/**
* 获取构造函数信息
* @param obj
*/
public static void getConstructorMessage(Object obj){
Class cls = obj.getClass();
Constructor[] constructors = cls.getConstructors();
Constructor[] declaredConstructors = cls.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor.getName());
}
System.out.println("------------------");
for (Constructor constructor : declaredConstructors) {
System.out.println(constructor.getName());
}
}
/**
* 通过反射操作方法和成员变量
*/
public static void reflectionProcess(){
ChildActivity ca = new ChildActivity(0);
Class cls = ca.getClass();
try {
//修改filed值
//1.获取intField成员变量
Field declaredField = cls.getDeclaredField("intField");
//2.因为该成员变量是private,此时我们应该先设置为可操作
declaredField.setAccessible(true);
//3.赋值
declaredField.set(ca, 5);
//反射调用方法
//1.获取目标方法plus(int i)
Method method = cls.getDeclaredMethod("plus", int.class);
//因为该方法是public的,因此不需要调用 method.setAccessible(true);
//2.调用method.invoke(对象,参数列表)
method.invoke(ca, 6);
} catch (Exception e) {
e.printStackTrace();
}
}
}
最后写一个测试类Demain进行测试:
public class Domain {
public static void main(String[] args) {
System.out.println("===================方法信息===================");
ClassMessageGetter.getMethodMessage(new ChildActivity(0));
System.out.println("===================成员信息===================");
ClassMessageGetter.getFieldMessage(new ChildActivity(0));
System.out.println("===================构造函数信息===================");
ClassMessageGetter.getConstructorMessage(new ChildActivity(0));
System.out.println("===================修改成员变量及调用方法信息===================");
ClassMessageGetter.reflectionProcess();
}
}
打印结果如下:
===================方法信息===================
1.getMethods()获取该类所有public,包括父类的public方法
public void plus(int arg0)
public void childPublicMethod(String arg0)
public void basePublicMethod(String arg0)
public final void wait()
public final void wait(long arg0,int arg1)
public final native void wait(long arg0)
public boolean equals(Object arg0)
public java.lang.String toString()
public native int hashCode()
public final native java.lang.Class getClass()
public final native void notify()
public final native void notifyAll()
-----------------------------------------------------------
2.getDeclaredMethods()获取当前类(不含父类)的所有方法(与方法访问权限无关)
plus
childPublicMethod
childPrivateMethod
===================成员信息===================
cls.getFields() 获取该类所有public,包括父类的public成员变量
int childPublicFiled
int basePublicFiled
getDeclaredFields()获取当前类(不含父类)的所有成员变量(与变量访问权限无关)
int childPublicFiled
String childPrivateFiled
int intField
===================构造函数信息===================
entity.ChildActivity
------------------
entity.ChildActivity
===================修改成员变量及调用方法信息===================
6 + 5 = 11
到此,Java反射基本就完结。最后说一个与反射相关的东西,叫“类型擦除”,它和泛型有关。
泛型是1.5中引入的一个新的概念,由于不用进行强制转换类型了,所以具有较高的安全性和易用性。因为泛型其实只是在编译器中实现的而虚拟机并不认识泛型类项,所以要在虚拟机中将泛型类型进行擦除。也就是说,在编译阶段使用泛型,运行阶段取消泛型,即擦除。
就是说泛型机制其实是在编译期进行“检查”,因此,只要我们能够绕过编译期,就可以为所欲为。先看下面代码:
泛型检查.png可以看到,当我们add进去的类型不是String时,编译器就会报错,那么有什么办法可以add进去int类型的值吗?上面说了,泛型机制是在编译期进行的,所以我们可以通过反射,add进去其他类型的值。
先看下面代码:
list类类型比较.png
从上面的代码我们可以知道,两个list的Class其实是同一份,从侧面印证了泛型机制是在编译期进行的。接下来即将发生神奇的事情: 类型擦除.png
最终结果size打印的是3,说明我们成功地往猫碗(List<String>)里面撒了狗粮(int)。
The End
转载请注明出处。