介绍
反射是一种技术,使用这种技术理论上可以解刨任意类。Java的反射机制主要涉及到两个类Class和Member。
Class对象是整个类信息的访问入口,每个类被加载之后,系统就会为该类生成一个对应的Class对象,当获取到Class对象后就可以使用Class类提供的方法获取类的对象以及类的信息了。
Member就代表类的成员,包括构造函数Constructor、方法Method、字段Field。因为这三个类都实现了Member接口,所以统称Member吧。通过Class类提供的方法就可以获取到Constructor、Method、Field的实例了,有了这些实例我们就可创建对象,执行方法,修改字段了~
Class类
1、Class对象的常见获取方式
// 方式1
Class clazz = Class.forName("类的全限定名") //包名+类名
// 方式2
Class clazz = 类名.class
// 方式3
Class clazz = 类对象 .getClass()
2、Class对象常用的方法
/**
1、获取构造函数对象。
parameterTypes:构造函数中参数所属的Class类型。由于构造函数中的参数可能有多个,这里的parameterTypes是一个可变参数。
*/
public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
//暴力反射时使用
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
//获取所有的构造函数对象
public Constructor<?>[] getConstructors() throws SecurityException
/**
2、获取方法对象
name:方法名
parameterTypes:方法中参数所属的Class类型。由于方法中的参数可能有多个,这里的parameterTypes是一个可变参数。
*/
public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
//暴力反射使用
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException
//获取所有的方法对象
public Method[] getMethods() throws SecurityException
/**
3、获取字段对象
name:字段名
*/
public Field getField(String name) throws NoSuchFieldException, SecurityException
//暴力反射使用
public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException
// 获取所有的字段
public Field[] getFields() throws SecurityException
/**
4、其他
*/
public String getName() //获取类名如java.lang.String
public String getCanonicalName()//获取类名java.lang.String。不过数组、匿名类无CanonicalName,会返回null。
public String getSimpleName()//获取类名,如String.
public native int getModifiers()// 获取类的修饰符
public TypeVariable<Class<T>>[] getTypeParameters()//获取类上的泛型信息
public Type[] getGenericInterfaces()//获取类实现的接口信息
public native Class<? super T> getSuperclass()//获取所有父类信息
public Annotation[] getAnnotations()//获取类上的注解信息
3、Class类上信息的获取
@Deprecated
@CustomAnnotation
public final class Student<T, E> extends superClass1 implements IInterface1, IInterface2 {
public String name = "SunnyDay";
// test
public static void main(String[] args) {
Student<String, Integer> student = new Student<>();
Class clazz = student.getClass();
System.out.println("类名字:" + clazz.getSimpleName());
System.out.println("类所有的修饰符名字:" + getModifiersByCode(clazz.getModifiers()));
System.out.println("泛型参数名字:" + getTypeParametersName(clazz));
System.out.println("所有实现接口的名字:" + getInterfaceName(clazz));
System.out.println("所有父类的名字:" + getSuperClassName(clazz));
System.out.println("所有注解名字:" + getAnnotationName(clazz));
/**
*log:
* 类名字:Student
*
* 类所有的修饰符名字:public final
*
* 泛型参数名字:T E
*
* 所有实现接口的名字:IInterface1 IInterface2
*
* 所有父类的名字:superClass1 superClass2 Object
*
* 所有注解名字:@java.lang.Deprecated() @CustomAnnotation()
*
*/
}
/**
* 获取类的修饰符
*/
private static String getModifiersByCode(int code) {
return Modifier.toString(code);
}
/**
* 获取类的泛型参数名
*/
private static String getTypeParametersName(Class clazz) {
if (clazz == null) return "";
TypeVariable[] arr = clazz.getTypeParameters();
if (arr.length <= 0) return "";
StringBuilder sb = new StringBuilder();
for (TypeVariable typeVariable : arr) {
sb.append(typeVariable.getName()).append(" ");
}
return sb.toString();
}
/**
* 获取类所有实现接口的名字
*/
private static String getInterfaceName(Class clazz) {
if (clazz == null) return "";
Type[] arr = clazz.getGenericInterfaces();
if (arr.length <= 0) return "";
StringBuilder sb = new StringBuilder();
for (Type type : arr) {
sb.append(type.getTypeName()).append(" ");
}
return sb.toString();
}
/**
* 获取类所有父类的名字
*/
private static String getSuperClassName(Class clazz) {
if (clazz == null) return "";
Class superClass = clazz.getSuperclass();
StringBuilder sb = new StringBuilder();
while (superClass != null) {
sb.append(superClass.getSimpleName()).append(" ");
superClass = superClass.getSuperclass();
}
return sb.toString();
}
/**
* 获取类所有注解的名字
*/
private static String getAnnotationName(Class clazz) {
if (clazz == null) return "";
Annotation[] arr = clazz.getAnnotations();
if (arr.length <= 0) return "";
StringBuilder sb = new StringBuilder();
for (Annotation annotation : arr) {
sb.append(annotation.toString()).append(" ");
}
return sb.toString();
}
}
interface IInterface1 {
}
interface IInterface2 {
}
class superClass1 extends superClass2 {
}
class superClass2 {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface CustomAnnotation {
}
Member
构造方法、方法、字段都是类的Member,如下首先准备一个类供测试用,然后分别解剖下类的Member。
/**
* Create by SunnyDay on 2018/11/19
*/
public class Person {
//字段
public String name;
public int age;
private String pwd;
static int num;
//构造方法
public Person() {}
public Person(String name) {this.name=name;}
public Person(String name, int age) {
this.name=name;
this.age=age;
}
private Person(List list) {}
// 普通方法
public void test(){
System.out.println("无参数此方法!!!");
}
public void test(String s,int a){
System.out.println("两个参数的方法参数:"+s+" "+ a);
}
private String[] test(String s){
System.out.println("有返回值的私有方法");
return new String[]{"123","456"};
}
public static void hello(){
System.out.println("静态的方法");
}
}
1、 反射构造方法
/**
* 1、反射类的无参数构造函数
*/
public static void reflectConstructorNone() throws Exception {
Class cl = Class.forName("reflect.Person");
Constructor constructor = cl.getConstructor(null);
// 通过无参数的构造函数对象创建一个Person类的对象
Person person1 = (Person) constructor .newInstance(null);
System.out.println(person1.name);// 打印null,由于我们未进行赋值 字符串默认为null
}
/**
* 2、 反射类的1个参数构造函数
*/
private static void reflectConstructorOne() throws Exception {
Class cl = Class.forName("reflect.Person");
Constructor constructor = cl.getConstructor(String.class);
//newInstance参数:构造函数需要的参数值
Person person = (Person) constructor .newInstance("Kate");
System.out.println(person.name);// Kate
}
/**
* 3、反射类的2个参数构造函数
*/
private static void reflectConstructorTwo() throws Exception {
Class cl = Class.forName("reflect.Person");// ps:
Constructor constructor = cl.getConstructor(String.class, int.class);
Person person = (Person) constructor .newInstance("Kate", 20);
System.out.println(person.name + " " + person.age);// Kate 20
}
/** 4、反射私有构造
* 反射私有的Member要采用暴力反射的方式,也即getDeclaredXXX+setAccessible。否则得不到反射信息。
* */
private static void violenceReflect() throws Exception {
Class cl = Class.forName("reflect.Person");
Constructor constructor =cl.getDeclaredConstructor(List.class);
constructor.setAccessible(true);// 设置暴力反射权利 否则java.lang.IllegalAccessException
Person p = (Person) constructor.newInstance(new ArrayList());
System.out.println(p.name);
}
创建对象有两种方法,优先使用第一种方法,第二种方法已经被废弃了。
1、Constructor.newInstance()
2、Class.newInstance()
上述二者还是有点区别的:
- 方法一可以调用任意参数的构造方法,而方法二只能调用无参的构造方法
- 方法一会将原方法抛出的异常都包装成 InvocationTaregtException 抛出,而方法二会将原方法中的异常不做处理原样抛出
- 方法一不需要方法权限,方法二只能调用 public 的构造方法
2、反射方法
/**
* 1、反射无参数的方法
*/
private static void ReflectMethodNone() throws Exception {
Class cl = Class.forName("reflect.Person");
/* 反射方法
* 参数一 : String类型方法名
* 参数二: 方法的参数所属的类型
* */
Method method = cl.getMethod("test", null);
/*执行方法
* 参数1:类的对象,说明是由那个类的对象执行的。
* 参数2:方法的参数值。
* */
method.invoke(new Person(), null);
}
/**
* 2、反射两个参数的方法
*/
private static void ReflectMethodTwo() throws Exception {
Class cl = Class.forName("reflect.Person");
Method method = cl.getMethod("test", String.class, int.class);
method.invoke(new Person(), "xxxxx", 20);
}
/**
* 3、反射有返回值私有方法(必须暴力解决啦)
*/
private static void ReflectMethodPrivate() throws Exception {
Class cl = Class.forName("reflect.Person");
Method method = cl.getDeclaredMethod("test", String.class);
method.setAccessible(true);
String[] strings = (String[]) method.invoke(new Person(), "xxxxx");
System.out.println(strings);
}
/**
* 4、反射静态方法方法
*/
private static void ReflectStaticiMethod() throws Exception {
Class cl = Class.forName("reflect.Person");
Method method = cl.getMethod("hello", null);
// 注意 静态的方法执行不需要对象 故此处设为null
method.invoke(null, null);
}
/**
* 5、获取方法的返回值类型.
* <p>
* getSimpleName:获取方法的返回值类型。
* getGenericReturnType:获取方法的返回值类型。
* <p>
* 方法为普通方法时二者返回值相同。但是针对泛型有区别如:
* <p>
* 1、public T test() getReturnType返回Object,getGenericReturnType返回 T
* <p>
* 2、public Class<String> test() getReturnType返回Class,getGenericReturnType返回Class<String>
*/
public static void getMethodReturnType() {
try {
Class clazz = Person.class;
Method method = clazz.getMethod("test", null);
Class returnClass = method.getReturnType();
System.out.println("方法返回值类型:" + returnClass.getSimpleName());
System.out.println("方法返回值类型:" + method.getGenericReturnType().getTypeName());
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
/**
* 获取方法参数信息。
* */
public static void getMethodParameterInfos() {
try {
Class clazz = Person.class;
Method method = clazz.getMethod("test", String.class, int.class);
Parameter[] arr = method.getParameters();
StringBuilder sb = new StringBuilder();
for (Parameter parameter : arr) {
sb.append("参数名:")
.append(parameter.getName())
.append(" ")
.append("参数类型:")
.append(parameter.getType().getName())
.append("\n");
}
System.out.println(sb.toString());
/**
* log:
*
* 参数名:arg0 参数类型:java.lang.String
* 参数名:arg1 参数类型:int
*
* 注意: .class 文件中默认不存储方法参数名称,如果想要获取方法参数名称,需要在编译的时候加上 -parameters 参数。
* 如果编译未加上 -parameters 参数,返回的参数名则形如 ”argX“,X 代表参数的位置。
* */
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
/**
*
* 其他:
*
* 1、获取方法参数类型
* getParameterTypes() 获取目标方法各参数类型对应的 Class 对象
* getGenericParameterTypes() 获取目标方法各参数类型对应的 Type 对象
*
* 2、获取方法抛出的异常的类型
* getExceptionTypes() 获取目标方法抛出的异常类型对应的 Class 对象
* getGenericExceptionTypes() 获取目标方法抛出的异常类型对应的 Type 对象
*
* */
3、反射类的字段
/**
* 1、反射公有的字段
*/
private static void ReflectFieldPublic() throws Exception {
Class cl = Class.forName("reflect.Person");
// 获得字段对象 参数:字段名
Field field = cl.getField("name");
Person person = new Person();
// 给指定对象赋值
field.set(person,"xxxxx");
String name = (String) field.get(person);
System.out.println("获取到字段的值:"+name);
/**
* 注意我们可能不知道字段的类型
* 这时我们使用api获得(安全起见)
* */
Object value = field.get(new Person());// 返回obj类型
Class type = field.getType();// 获得字段类型
if (type.equals(String.class)){
String s =(String) value;
//TODO 数据处理
}
}
/**
* 2、反射私有的字段
*/
private static void ReflectFieldPrivate() throws Exception {
Class cl = Class.forName("reflect.Person");
// 私有的要暴力反射
Field field = cl.getDeclaredField("pwd");
field.setAccessible(true);
Person person = new Person();
field.set(person,"tom");
System.out.println(field.get(person));
}
/**
* 3、反射静态的字段
*/
private static void ReflectStaticField()throws Exception {
Class cl = Class.forName("reflect.Person");
Field field = cl.getField("num");
field.set(null,20);
System.out.println(field.get(null));
}
/**
*
* 其他:
*
* 1、getName 获取字段名
* 2、getType 获取字段类型
* 3、getModifiers 获取字段修饰符
* 4、getDeclaredAnnotations 获取字段上的注解
*
* */
数组枚举的反射
数组和枚举是特殊的对象,Java 反射为数组和枚举提供了一些特定的 API 接口。
1、数组的反射
数组本质上也是一个对象,它也有自己的Class类型。如 int[] array,class类型为 class [I。
数组类型中的 [ 个数表示数组的维度,其后面的字母代表数组元素类型,如int的为I,一般为类型的首字母大小。 long 类型例外使用J,因为 L 被引用对象类型占用了。
数组相关api:
/**
获取数组对象,返回值是Object。
componentType:数组元素对应的class类型。如int.class。
length:数组的长度。
*/
public static Object newInstance(Class<?> componentType, int length)
public static Object newInstance(Class<?> componentType, int... dimensions)
/**
给数组对象赋值
array:数组对象
index:数组index
value:数组index对应的value值
*/
public static native void set(Object array, int index, Object value)
/**
获取数组对象的值
array:数组对象
index:数组index
*/
public static native Object get(Object array, int index)
举个栗子:
/**
* 反射一维数组栗子
*/
private static void ref1DimensionArray() {
int[] arr = new int[]{1, 1, 1};
//由于数组元素为int类型,数组长度为3 所以传递int.class, 3
Object object = Array.newInstance(int.class, 3);
for (int i = 0; i < arr.length; i++) {
Array.set(object, i, 2);
}
for (int i = 0; i < arr.length; i++) {
System.out.println("index:" + i + "index-value:" + Array.get(object, i));
}
/**
*
* log:
* index:0index-value:2
* index:1index-value:2
* index:2index-value:2
*
* */
}
/**
* 反射多维数组(这里以二维数组举个栗子)
* ps:Java 反射没有提供能够直接访问多维数组元素的 API,可以把多维数组当成数组的数组处理。
*/
private static void ref2DimensionArray() {
int[][] arr = new int[][]{{1, 1}, {1, 1}};
Object object = Array.newInstance(int.class, 2, 2);
//每一行都当做一维数组的对象。
Object row1 = Array.get(object, 0);
Object row2 = Array.get(object, 1);
// 设置元素
Array.set(row1, 0, 2);
Array.set(row1, 1, 2);
Array.set(row2, 0, 2);
Array.set(row2, 1, 2);
System.out.println("[0,0]->" + Array.get(row1, 0));
System.out.println("[0,1]->" + Array.get(row1, 1));
System.out.println("[1,0]->" + Array.get(row2, 0));
System.out.println("[1,1]->" + Array.get(row2, 1));
/**
* [0,0]->2
* [0,1]->2
* [1,0]->2
* [1,1]->2
*
* */
}
2、枚举的反射
枚举隐式继承自 java.lang.Enum,Enum 继承自 Object。普通类所能使用的反射方法,枚举都能使用,另外,Java 反射额外提供了几个方法。
Class#isEnum()
Class#getEnumConstants()
Field#isEnumConstant()
反射应用
- 处理配置文件提供的信息
- 注解的运行期处理
- jdk动态代理的实现
反射优缺点
1、优点
- 灵活,可以动态改变类行为。
2、缺点
-
性能开销:反射涉及类型动态解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率低下,尽量避免在高性能要求下使用反射。
-
安全限制:使用反射要求程序必须在一个没有安全限制的环境中运行。
-
内部曝光:由于反射允许代码执行一些在正常情况下不被允许的操作,所以使用反射可能会导致意料之外的副作用。
The end
参考:Java反射完全解析