概述
- Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。
- 反射机制的相关类:java.lang.reflect.*;
- java.lang.Class 代表整个字节码。代表一个类型,代表整个类。
- java.lang.reflect.Method 代表字节码中的方法字节码。代表类中的方法。
- java.lang.reflect.Constructor 代表字节码中的构造方法字节码。代表类中的构造方法。
- java.lang.reflect.Field代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)
- 尽管反射机制带来了极大的灵活性及方便性,但反射也有缺点:
- 性能问题,Java反射机制中包含了一些动态类型,所以Java虚拟机不能够对这些动态代码进行优化。因此,反射操作的效率要比正常操作效率低很多。我们应该避免在对性能要求很高的程序或经常被执行的代码中使用反射。
- 安全限制,使用反射通常需要程序的运行没有安全方面的限制。如果一个程序对安全性提出要求,则最好不要使用反射。
- 程序健壮性,反射允许代码执行一些通常不被允许的操作,所以使用反射有可能会导致意想不到的后果。反射代码破坏了Java程序结构的抽象性,所以当程序运行的平台发生变化的时候,由于抽象的逻辑结构不能被识别,代码产生的效果与之前会产生差异。
使用方法
- 对于一个字节码文件.class,Java将.class字节码文件载入时,JVM将产生一个java.lang.Class对象代表该.class字节码文件,从该Class对象中可以获得类的许多基本信息,这就是反射机制。
Class对象
- 获取一个类的 Class 对象:
- Class.forName(“完整类名带包名”);
- 对象.getClass() ;
- 任何类型.class;
- 获取一个类的 Class 对象: Class myObjectClass = MyObject.class;
- Class.forName():Class class = Class.forName(className); 你必须提供一个类的全名,这个全名包括类所在的包的名字
类名
* 通过 getName() 方法返回类的全限定类名(包含包名):
Class aClass = ... //获取Class对象,具体方式可见Class对象小节
String className = aClass.getName();
* 如果你仅仅只是想获取类的名字(不包含包名),那么你可以使用 getSimpleName()方法:
Class aClass = ... //获取Class对象,具体方式可见Class对象小节
String simpleClassName = aClass.getSimpleName();
修饰符
* 可以通过 Class 对象来访问一个类的修饰符, 即public,private,static 等等的关键字
Class aClass = ... //获取Class对象,具体方式可见Class对象小节
int modifiers = aClass.getModifiers();
* 修饰符都被包装成一个int类型的数字,这样每个修饰符都是一个位标识(flag bit),这个位标识可以设置和清除修饰符的类型。 可以使用 java.lang.reflect.Modifier 类中的方法来检查修饰符的类型:
* Modifier.isAbstract(int modifiers);
包信息
Class aClass = ... //获取Class对象,具体方式可见Class对象小节
Package package = aClass.getPackage();
父类
Class superclass = aClass.getSuperclass();
实现的接口
Class aClass = ... //获取Class对象,具体方式可见Class对象小节
Class[] interfaces = aClass.getInterfaces();
构造器
Constructor[] constructors = aClass.getConstructors();
方法
Method[] method = aClass.getMethods();
变量
你可以通过如下方式访问一个类的成员变量:
Field[] method = aClass.getFields();
注解
Annotation[] annotations = aClass.getAnnotations();
isPrimitive(判断是否是基本类型的字节码)
cls1.isPrimitive()
Java 构造器
- 1 获取Constructor对象
Class aClass = ...//获取Class对象
Constructor[] constructors = aClass.getConstructors();
返回的 Constructor 数组包含每一个声明为公有的(Public)构造方法。 如果你知道你要访问的构造方法的方法参数类型,你可以用下面的方法获取指定的构造方法,这例子返回的构造方法的方法参数为 String 类型:
Class aClass = ...//获取Class对象
Constructor constructor = aClass.getConstructor(new Class[]{String.class});
- 2 构造方法参数
Constructor constructor = ... //获取Constructor对象
Class[] parameterTypes = constructor.getParameterTypes();
- 3 利用 Constructor 对象实例化一个类
Constructor constructor = MyObject.class.getConstructor(String.class);
MyObject myObject = (MyObject) constructor.newInstance("constructor-arg1");
- EG:通过反射机制调用构造方法实例化java对象。
//使用反射机制创建对象
// 调用有参数的构造方法怎么办?
// 第一步:先获取到这个有参数的构造方法
Constructor c1 = vipClass.getDeclaredConstructor(int.class, String.class, String.class, boolean.class);
// 第二步:调用构造方法new对象
Object obj2 = c1.newInstance(321, "lsi", "1999-10-11", true);//Constructor类的newInstance方法
System.out.println(obj2);
// 获取无参数构造方法
Constructor c2 = vipClass.getDeclaredConstructor();
Object obj3 = c2.newInstance();
System.out.println(obj3);
- EG:获取这个类的父类
// String举例
Class vipClass = Class.forName("java.lang.String");
// 获取String的父类
Class superclass = vipClass.getSuperclass();
// 获取String类实现的所有接口(一个类可以实现多个接口。)
Class[] interfaces = vipClass.getInterfaces();
System.out.println(superclass.getName());
for (Class i : interfaces) {
System.out.println(i.getName());
}
Java 变量
- 1 获取Field对象
Class aClass = ...//获取Class对象
Field[] methods = aClass.getFields();
返回的 Field 对象数组包含了指定类中声明为公有的(public)的所有变量集合。
如果你知道你要访问的变量名称,你可以通过如下的方式获取指定的变量:
Class aClass = MyObject.class
Field field = aClass.getField("someField");
- 2 变量名称
String fieldName = field.getName();
- 3 变量类型
Object fieldType = field.getType();
- 4 获取或设置(get/set)变量值, 通过调用 Field.get()或 Field.set()方法,获取或者设置变量的值,如下例:
Class aClass = MyObject.class
Field field = aClass.getField("someField");
MyObject objectInstance = new MyObject();
Object value = field.get(objectInstance);
field.set(objetInstance, value);
- eg:通过反射机制访问一个java对象的属性
给属性赋值:1 获取对象 2 获取属性Fileld 3 设值
//使用反射机制给属性赋值
Class studentClass = Class.forName("javase.reflectBean.Student");
Object obj = studentClass.newInstance();// obj就是Student对象。(底层调用无参数构造方法)
// 获取no属性(根据属性的名称来获取Field)
Field noField = studentClass.getDeclaredField("no");
// 给obj对象(Student对象)的no属性赋值
noField.set(obj, 22222);
// 读取属性的值
// 两个要素:获取obj对象的no属性的值。
System.out.println(noField.get(obj));
Java 方法
- 1 获取 Method 对象
Class aClass = ...//获取Class对象
Method[] methods = aClass.getMethods();
如果你知道你要调用方法的具体参数类型,你就可以直接通过参数类型来获取指定的方法,
方法对象名称是“doSomething”,他的方法参数是 String 类型:
Class aClass = ...//获取Class对象
Method method = aClass.getMethod("doSomething", new Class[]{String.class});
- 2 方法参数以及返回类型
Class[] parameterTypes = method.getParameterTypes();
Class returnType = method.getReturnType();
- 3 通过 Method 对象调用方法
//获取一个方法名为doSomesthing,参数类型为String的方法
Method method = MyObject.class.getMethod("doSomething", String.class);
Object returnValue = method.invoke(null, "parameter-value1");
传入的null参数是你要调用方法的对象,如果是一个静态方法调用的话则可以用 null 代替指定对象作为 invoke()的参数,
在上面这个例子中,如果 doSomething 不是静态方法的话,你就要传入有效的 MyObject 实例而不是 null。 Method.invoke(Object target, Object … parameters)方法的第二个参数是一个可变参数列表,但是你必须要传入与你要调用方法的形参一一对应的实参。
- EG:通过反射机制调用一个对象的方法
//使用反射机制调用方法
Class userServiceClass = Class.forName("javase.reflectBean.UserService");
// 创建对象
Object obj = userServiceClass.newInstance();
// 获取Method
Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
// 调用
Object resValues = loginMethod.invoke(obj, "admin", "123");//注:方法返回值是void 结果是null
System.out.println(resValues);
Java 访问器
- 使用 Java 反射你可以在运行期检查一个方法的信息以及在运行期调用这个方法,使用这个功能同样可以获取指定类的 getters 和 setters,你不能直接寻找 getters 和 setters,你需要检查一个类所有的方法来判断哪个方法是 getters 和 setters。
public static void printGettersSetters(Class aClass){
Method[] methods = aClass.getMethods();
for(Method method : methods){
if(isGetter(method)) System.out.println("getter: " + method);
if(isSetter(method)) System.out.println("setter: " + method);
}
}
public static boolean isGetter(Method method){
if(!method.getName().startsWith("get")) return false;
if(method.getParameterTypes().length != 0) return false;
if(void.class.equals(method.getReturnType()) return false;
return true;
}
public static boolean isSetter(Method method){
if(!method.getName().startsWith("set")) return false;
if(method.getParameterTypes().length != 1) return false;
return true;
}
Java 私有变量和私有方法
- 1 访问私有变量
PrivateObject privateObject = new PrivateObject("The Private Value");
Field privateStringField = PrivateObject.class.
getDeclaredField("privateString");
privateStringField.setAccessible(true);
String fieldValue = (String) privateStringField.get(privateObject);
System.out.println("fieldValue = " + fieldValue);
// 可以访问私有的属性吗?
Field nameField = studentClass.getDeclaredField("name");
// 打破封装
// 这样设置完之后,在外部也是可以访问private的。
nameField.setAccessible(true);
// 给name属性赋值
nameField.set(obj, "xiaowu");
// 获取name属性的值
System.out.println(nameField.get(obj));
- 2 访问私有方法
PrivateObject privateObject = new PrivateObject("The Private Value");
Method privateStringMethod = PrivateObject.class.
getDeclaredMethod("getPrivateString", null);
privateStringMethod.setAccessible(true);
String returnValue = (String)
privateStringMethod.invoke(privateObject, null);
System.out.println("returnValue = " + returnValue);
Java 注解
- 2 类注解
Class aClass = TheClass.class;
Annotation[] annotations = aClass.getAnnotations();
for(Annotation annotation : annotations){
if(annotation instanceof MyAnnotation){
MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());
}
}
你还可以像下面这样指定访问一个类的注解:
Class aClass = TheClass.class;
Annotation annotation = aClass.getAnnotation(MyAnnotation.class);
- 3 方法注解
public class TheClass {
@MyAnnotation(name="someName", value = "Hello World")
public void doSomething(){}
}
你可以像这样访问方法注解:
Method method = ... //获取方法对象
Annotation[] annotations = method.getDeclaredAnnotations();
- 4 参数注解
public class TheClass {
public static void doSomethingElse(
@MyAnnotation(name="aName", value="aValue") String parameter){
}
}
你可以通过 Method对象来访问方法参数注解:
Method method = ... //获取方法对象
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Class[] parameterTypes = method.getParameterTypes();
- 5 变量注解
public class TheClass {
@MyAnnotation(name="someName", value = "Hello World")
public String myField = null;
}
你可以像这样来访问变量的注解:
Field field = ... //获取方法对象</pre>
<pre>Annotation[] annotations = field.getDeclaredAnnotations();
Java 泛型
- 1 运用泛型反射的经验法则
- 1、声明一个需要被参数化(parameterizable)的类/接口。 2、使用一个参数化类。
- 2 泛型方法返回类型
- 如果你获得了 java.lang.reflect.Method 对象,那么你就可以获取到这个方法的泛型返回类型信息。如果方法是在一个被参数化类型之中(译者注:如 T fun())那么你无法获取他的具体类型,但是如果方法返回一个泛型类(译者注:如 List fun())那么你就可以获得这个泛型类的具体参数化类型。
- 3 泛型方法参数类型
public class MyClass {
protected List<String> stringList = ...;
public void setStringList(List<String> list){
this.stringList = list;
}
}
你可以像这样来获取方法的泛型参数:
method = Myclass.class.getMethod("setStringList", List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for(Type genericParameterType : genericParameterTypes){
if(genericParameterType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericParameterType;
Type[] parameterArgTypes = aType.getActualTypeArguments();
for(Type parameterArgType : parameterArgTypes){
Class parameterArgClass = (Class) parameterArgType;
System.out.println("parameterArgClass = " + parameterArgClass);
}
}
}
这段代码会打印出”parameterArgType = java.lang.String”。
Type[]数组 parameterArgTypes 只有一个结果 – 一个代表 java.lang.String 的 Class 类的实例。Class 类实现了Type接口。
- 4 泛型变量类型
- 同样可以通过反射来访问公有(Public)变量的泛型类型,无论这个变量是一个类的静态成员变量或是实例成员变量。你可以在“Java Reflection: Fields”中阅读到有关如何获取 Field 对象的相关内容。这是之前的一个例子,一个定义了一个名为 stringList 的成员变量的类。
public class MyClass {
public List<String> stringList = ...;
}
Field field = MyClass.class.getField("stringList");
Type genericFieldType = field.getGenericType();
if(genericFieldType instanceof ParameterizedType){
ParameterizedType aType = (ParameterizedType) genericFieldType;
Type[] fieldArgTypes = aType.getActualTypeArguments();
for(Type fieldArgType : fieldArgTypes){
Class fieldArgClass = (Class) fieldArgType;
System.out.println("fieldArgClass = " + fieldArgClass);
}
}
这段代码会打印出”fieldArgClass = java.lang.String”。Type[]数组 fieldArgClass 只有一个结果 – 一个代表 java.lang.String 的 Class 类的实例。Class 类实现了 Type 接口。
Java 数组
- 1 java.lang.reflect.Array
- 2 创建一个数组
int[] intArray = (int[]) Array.newInstance(int.class, 3);
- 3 访问一个数组
- 通过 Java 反射机制同样可以访问数组中的元素。具体可以使用Array.get(…)和Array.set(…)方法来访问。下面是一个例子:
int[] intArray = (int[]) Array.newInstance(int.class, 3);
Array.set(intArray, 0, 123);
Array.set(intArray, 1, 456);
Array.set(intArray, 2, 789);
System.out.println("intArray[0] = " + Array.get(intArray, 0));
System.out.println("intArray[1] = " + Array.get(intArray, 1));
System.out.println("intArray[2] = " + Array.get(intArray, 2));
- 4 获取数组的 Class 对象
Class intArray = Class.forName("[I");
在 JVM 中字母 I 代表 int 类型,左边的‘[’代表我想要的是一个int类型的数组,这个规则同样适用于其他的原生数据类型。
对于普通对象类型的数组有一点细微的不同:
Class stringArrayClass = Class.forName("[Ljava.lang.String;");
注意‘[L’的右边是类名,类名的右边是一个‘;’符号。这个的含义是一个指定类型的数组。
需要注意的是,你不能通过 Class.forName()方法获取一个原生数据类型的 Class 对象。
下面这两个例子都会报 ClassNotFoundException:
Class intClass1 = Class.forName("I");
Class intClass2 = Class.forName("int");
通常会用下面这个方法来获取普通对象以及原生对象的 Class 对象:
public Class getClass(String className){
if("int" .equals(className)) return int .class;
if("long".equals(className)) return long.class;
...
return Class.forName(className);
}
一旦你获取了类型的 Class 对象,你就有办法轻松的获取到它的数组的 Class 对象,你可以通过指定的类型创建一个空的数组,然后通过这个空的数组来获取数组的 Class 对象。这样做有点讨巧,不过很有效。
如下例:
Class theClass = getClass(theClassName);
Class stringArrayClass = Array.newInstance(theClass, 0).getClass();
这是一个特别的方式来获取指定类型的指定数组的Class对象。无需使用类名或其他方式来获取这个 Class 对象。 为了确保 Class 对象是不是代表一个数组,你可以使用 Class.isArray()方法来进行校验:
Class stringArrayClass = Array.newInstance(String.class, 0).getClass();
System.out.println("is array: " + stringArrayClass.isArray());
- 5 获取数组的成员类型
String[] strings = new String[3];
Class stringArrayClass = strings.getClass();
Class stringArrayComponentType = stringArrayClass.getComponentType();
System.out.println(stringArrayComponentType);
Java 动态代理
- 1创建代理
你可以通过使用 Proxy.newProxyInstance()方法创建动态代理。
newProxyInstance()方法有三个参数:
1、类加载器(ClassLoader)用来加载动态代理类。
2、一个要实现的接口的数组。
3、一个 InvocationHandler 把所有方法的调用都转到代理上。 如下例:
InvocationHandler handler = new MyInvocationHandler();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
MyInterface.class.getClassLoader(),
new Class[] { MyInterface.class },
handler);
- 2 InvocationHandler 接口
你必须要传入一个 InvocationHandler 接口的实现。所有对动态代理对象的方法调用都会被转向到 InvocationHandler 接口的实现上,
下面是 InvocationHandler 接口的定义:
public interface InvocationHandler{
Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
下面是它的实现类的定义:
public class MyInvocationHandler implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//do something "dynamic"
}
}
传入 invoke()方法中的 proxy 参数是实现要代理接口的动态代理对象。通常你是不需要他的。
invoke()方法中的 Method 对象参数代表了被动态代理的接口中要调用的方法,从这个 method 对象中你可以获取到这个方法名字,方法的参数,参数类型等等信息。关于这部分内容可以查阅之前有关 Method 的文章。
参考文档
https://mp.weixin.qq.com/s/QbacsQwTyvBJi12LYPNKJw