java.lang.reflect
的包中,提供了反射相关的类,常用的为Field:字段
,Method:方法
,Annotation:注解
,Constructor:构造器
,我们平常最常见到的Class
类则在java.lang目录下
。
Field,Method,Constructor都与Class相关,可以通过Class获取到
Class
常用获取方式
class常用的获取方式有3种
-
类型.class
-
对象的getClass方法()
-
Class.forName(className),使用此方法需传入对象的全限定类名
package org.example; public class Test{} // 类型.class Class<Test> clazz = Test.class; // 对象的getClass方法 Test test = new Test(); Class<? extends Test> clazz = test.getClass(); // Class.forName方法,此方法会抛出ClassNotFoundException Class<Test> clazz = (Class<Test>)Class.forName("org.example.Test");
常用方法
-
isPrimitive 判断是否为基本类型
String.class.isPrimitive(); // false int.class.isPrimitive(); // true Integer.class.isPrimitive();// false boolean.class.isPrimitive();// true Boolean.class.isPrimitive();// false short.class.isPrimitive(); // true Short.class.isPrimitive(); // false Void.class.isPrimitive(); // false void.class.isPrimitive(); // true
-
getName 获取class全限定类名
-
getSimpleName 获取类名
-
getSuperClass 获取父类,Object.class调用此方法返回null
public static class A{} public static class B extends A{} Class<? super B> superclass = B.class.getSuperclass(); System.out.println(superclass); // A Class<? super B> superclass1 = superclass.getSuperclass(); System.out.println(superclass1);// java.lang.Object Class<? super B> superclass2 = superclass1.getSuperclass(); System.out.println(superclass2);// null
-
isAssignableFrom(Class clazz) 判断当前类是否为指定类的父类
System.out.println(A.class.isAssignableFrom(B.class)); // true System.out.println(Object.class.isAssignableFrom(B.class)); //true
-
getClassLoader 获取类加载器
-
isEnum 当前类是否是枚举类型
-
isInterface() 当前类是否为接口
-
isArray 是否是数组
-
getAnnotation(Class annotationClass) 获取类上的指定注解
-
newInstance 调用该类的无参构造函数进行对象实例化
如果该类没有无参构造函数,只有含参构造函数,则次方法会抛出异常,此时需要使用
Constructor
构造器进行初始化
场景举例
Spring扫描包后,通过Class.forName
方法获取Class对象
Constructor
当我们写一个类的时候,如果没有声明任何构造函数,java会帮我们生成一个无参构造函数,如果我们自己声明了构造函数,则Java不会帮我们生成无参构造,无论我们自己声明的构造函数是含参的还是无参的,所以在声明了含参构造的函数之后,我们还想使用无参构造函数,则需要手动声明
我们可以通过Class来获取类的构造函数
public class A{
public A(){}
public A(String name){}
private A(Integer age){}
}
// 获取所有构造函数
Constructor<?>[] constructors = A.class.getConstructors();
System.out.println(constructors.length);
// 获取参数为String类型的构造函数
Constructor<A> constructor = A.class.getConstructor(String.class);
// 获取参数为Integer类型的构造函数
Constructor<A> constructor1 = A.class.getConstructor(Integer.class);
执行上述方法,我们可以看到constructors.length=2
,并且会抛出异常Exception in thread "main" java.lang.NoSuchMethodException: A.<init>(java.lang.Integer)
找不到参数为Integer的构造方法,这是因为getConstructors
方法和getConstructor
方法只能拿到访问控制域为public的构造函数
如果需要获取所有访问控制域的构造函数,需要使用getDeclaredConstructors
和getDeclaredConstructor
将上面的代码进行修改
Constructor<?>[] constructors = A.class.getDeclaredConstructors();
System.out.println(constructors.length);
Constructor<A> constructor = A.class.getDeclaredConstructor(String.class);
Constructor<A> constructor1 = A.class.getDeclaredConstructor(Integer.class);
此时constructors.length
打印出来的值为3,并且也没有报错信息,说明此时我们已经获取到了所有的构造函数
接下来就可以使用获取到的构造函数创建实例了
// 无参构造函数
A a = A.class.getDeclaredConstructor().newInstance();
A a1 = A.class.getDeclaredConstructor(String.class).newInstance("1");
A a2 = A.class.getDeclaredConstructor(Integer.class).newInstance(1);
执行代码,此时控制台会抛出异常Exception in thread "main" java.lang.IllegalAccessException: Class TestMain can not access a member of class A with modifiers "private"
告诉我们不能调用private的构造函数,正常情况下,我们是不能调用非本类的私有方法的,但是用过反射,我们只需要关闭访问检查,便可以对这些本无法访问的方法或者属性进行调用或者访问
Constructor<A> declaredConstructor = A.class.getDeclaredConstructor(Integer.class);
declaredConstructor.setAccessible(true);
A a2 = declaredConstructor.newInstance(1);
setAccessible
方法描述为值为true表示反射对象在使用时应禁止Java语言访问检查。 值为false表示反射对象应强制执行Java语言访问检查。
此时再去调用方法,即可创建对应的实例,给A类加上对应的属性,进行测试
@Data
public static class A{
public A(){}
public A(String name){this.name=name;}
private A(Integer age){this.age=age;}
private int age;
private String name;
}
A a = A.class.getDeclaredConstructor().newInstance();
A a1 = A.class.getDeclaredConstructor(String.class).newInstance("1");
Constructor<A> declaredConstructor = A.class.getDeclaredConstructor(Integer.class);
declaredConstructor.setAccessible(true);
A a2 = declaredConstructor.newInstance(1);
System.out.println(a); // A(age=0, name=null)
System.out.println(a1);// A(age=0, name=1)
System.out.println(a2);// A(age=1, name=null)
从结果可以看到,我们通过反射调用构造函数进行对象实例化已经成功了
常用方法
- newInstance 初始化对象
- getAnnotation 获取构造函数上的注解
- getParameterTypes 获取构造函数的参数类型数组
场景举例
Spring初始化对象加载到容器中时,通过Class获取到构造方法,并对其进行初始化
Field
通过Class对象,我们可以获取对象中的字段信息,获取属性的方法有4个
- getField(String name)
- getFields()
- getDeclaredField(String name)
- getDeclaredFields()
和上面的Constructor
一样,不带Declared的方法,只能获取public修饰的字段,如需访问非public字段,应禁止访问权限检查。
获取指定字段
Field nameField = A.class.getField("name");
通过字段名称即可获取Field对象
如果指定的字段名称不存在,会抛出NoSuchFieldException
异常
比如执行A.class.getDeclaredField("addr")
,则会抛出异常Exception in thread "main" java.lang.NoSuchFieldException: addr
获取全部字段
Field[] declaredFields = A.class.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField.getName());
}
这样可以获取到全部的字段,但是这个方法只能获取当前类的字段,也就说,如果A类存在父类,父类的字段是无法获取到的
@Data
public class B {
private String address;
}
@Data
public class A extends B {
private String name;
private Integer age;
}
Field[] declaredFields = A.class.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField.getName());
}
从输出结果来看,只能拿到当前类的字段name和age,父类的字段address并不能获取,如需获取父类的字段,可调用A.class.getSuperClass(),通过父类的Class获取需要的字段
Class<A> clazz = A.class;
List<Field> fields = new LinkedList<>();
Class superClass = null;
do {
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
superClass = clazz.getSuperclass();
}while (superClass!=null);
常用方法
- getType 获取字段类型
- getAnnotation 获取字段上的注解
- getName 获取字段名称
- get 获取字段的值
- set 给字段赋值
获取字段值
调用get(Object obj)
方法即可,Obj为对象实例,即表示获取obj的中该field的值
A a1 = new A();
a1.setName("1");
A a2 = new A();
a2.setName("2");
Field nameField = A.class.getDeclaredField("name");
nameField.setAccessible(true);
System.out.println(nameField.get(a1)); // 1
System.out.println(nameField.get(a2)); // 2
获取静态字段的值
静态字段只类有关,与实例无关,故获取静态字段的值时,可以将obj设置为null
@Data
public class A extends B {
private static String STATIC_FIELD = "static";
private String name;
private Integer age;
}
Field staticField = A.class.getDeclaredField("STATIC_FIELD");
staticField.setAccessible(true);
System.out.println(staticField.get(null)); // static
给字段赋值
调用set(Object obj,Object value)
方法即可
表示给obj实例的field赋值
Field staticField = A.class.getDeclaredField("age");
staticField.setAccessible(true);
A a1 = new A();
A a2 = new A();
staticField.set(a1,10);
staticField.set(a2,10);
System.out.println(a1); // A(name=null, age=10)
System.out.println(a2); // A(name=null, age=20)
如果赋值的value类型与field的type类型不符,会抛出IllegalArgumentException
异常
Field staticField = A.class.getDeclaredField("age");
staticField.setAccessible(true);
A a = new A();
staticField.set(a,"young");
此时会抛出异常Exception in thread "main" java.lang.IllegalArgumentException: Can not set java.lang.Integer field com.example.jsch.A.age to java.lang.String
场景举例
- BeanCopy
- Spring基于字段的依赖注入
Method
通过Class对象,我们可以获取当前类中方法信息
- getMethod
- getMethods
- getDeclaredMethod
- getDeclaredMethods
因为方法重载的原因,获取指定的方法时,除了需要指定获取方法名称外,还要指定方法的参数,如果找不到对应的方法,会抛出NoSuchMethodException
常用方法
- invoke 执行方法
- getAnnotation 获取方法上的注解
- getParameterTypes 获取方法参数类型列表
- getParameterAnnotations 获取方法参数上的列表
- getReturnType 获取返回值类型
invoke
方法有返回值,当返回类型为void
时,invoke返回null
A a = new A();
a.setName("young");
Method getNameMethod = A.class.getDeclaredMethod("getName");
Object value = getNameMethod.invoke(a);
System.out.println(value); // young
System.out.println(a); // A(name=young, age=null)
Method setNameMethod = A.class.getDeclaredMethod("setName", String.class);
System.out.println(setNameMethod.getReturnType()); // void
value = setNameMethod.invoke(a, "hahaha");
System.out.println(value); // null
System.out.println(a); // A(name=hahaha, age=null)
场景举例
- BeanCopy 在某些场景下,set方法或者get方法会对值进行一些处理操作
Annotation
Java内置注解
- @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
- @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
- @SuppressWarnings - 指示编译器去忽略注解中声明的警告。
- @Retention - 标识这个注解作用场景,源码期,编译期,运行期,如果想通过反射获取到该注解,应使使注解的作用场景为运行期。
- @Documented - 标记这些注解是否包含在用户文档中。
- @Target - 标记这个注解应该是哪种 Java 成员。
- @Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)
- @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
- @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
- @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。
注解的作用范围
可通过注解上的@Target
注解的属性查看
- TYPE 类、接口、枚举
- FIELD 字段、枚举常量
- METHOD 方法
- PARAMETER 参数
- CONSTRUCTOR 构造函数
- LOCAL_VARIABLE 局部变量
- ANNOTATION_TYPE 注解
- PACKAGE 包
注解的生效时期
可通过注解上@Retention
注解的属性查看
- SOURCE 编译器
- CLASS 编译之后,记录在class中,不会加载到JVM中
- RUNTIME 运行期,会加载到JVM中,可以通过反射获取到