携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情
简述
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
反射基础
RTTI(Run-Time Type Indentification) 运行时类型识别,就是在运行时识别使用一个对象/类的信息,不同于编译时已经确定好的类型
反射就是将一个类中的各种属性和方法等解剖为一个个对象
Class类
Class(java.lang)类 Java应用里每一个类或接口都是Class类的实例,每个Java类在jvm都表现为一个Class对象,基本数据类型和void数组也是Class类的对象
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
private static final int ANNOTATION= 0x00002000;
private static final int ENUM = 0x00004000;
private static final int SYNTHETIC = 0x00001000;
private static native void registerNatives();
static {
registerNatives();
}
/*
* Private constructor. Only the Java Virtual Machine creates Class objects.
* This constructor is not used and prevents the default constructor being
* generated.
*/
private Class(ClassLoader loader) {
// Initialize final field for classLoader. The initialization value of non-null
// prevents future JIT optimizations from assuming this final field is null.
classLoader = loader;
}
复制代码
阅读源码可得:
- Class也是类,区别于
class
关键字 - Class类构造方法是
private
,仅由JVM调用创建和加载
类加载
类加载机制图解
- Java文件编译后会生成对应的class文件,里面记录着类一切的信息,jvm创建类都是依据Class对象来创建的
- class文件在加载到内存中后,会生成唯一的Class对象,一个类全局仅存一个对应的Class对象
反射的基本使用
在Java中,Class类和java.lang.reflect类库共同支持反射技术
其中,Constructor
类表示Class对象所表示类的构造方法,Field
类表示成员变量,Method
类表示成员方法等
方法介绍
方法 | 说明 |
---|---|
forName() | (1)获取Class对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类。 |
(2)为了产生Class引用,forName()立即就进行了初始化。 | |
Object-getClass() | 获取Class对象的一个引用,返回表示该对象的实际类型的Class引用。 |
getName() | 取全限定的类名(包括包名),即类的完整名字。 |
getSimpleName() | 获取类名(不包括包名) |
getCanonicalName() | 获取全限定规范 的类名(包括包名,一般在log时使用) |
isXXX() | 判断是否为某一类型,比如isInterface()是否为接口,isEnum()等 |
getXXX() | 获取类的某一个信息,比如getFiled(String name)获取名字为name的属性,getMethod(),getAnnotation()等 |
getXXXs() | 获取某个信息的数组,比如getInterfaces(),getMethods,getFileds() 注意:后两个方法会获取到包括父类所拥有的全部信息 |
getDeclredXXX/s() | 获取已经声明的信息,仅包含本类,不包含父类 ,比如:getDeclaredAnnotations() ,getDeclaredMethod() |
newInstance() | 返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器。 |
Class对象的读取
在类加载的时候,JVM会创建一个class对象 class对象是可以说是反射中最常用的,获取class对象的方式的主要有三种
- 根据类名:类名.class
- 根据对象:对象.getClass()
- 根据全限定类名:Class.forName(全限定类名)
tips:如果是加载内部类,格式为package.ClassName$InnerClass
logger.info("根据类名: \t"+test.class); //根据类名: class zjamss.test
logger.info("根据对象: \t"+this.getClass()); //根据对象: class zjamss.test
logger.info("根据全限定类名: \t"+Class.forName("zjamss.test")); //根据全限定类名: class zjamss.test
Class test = zjamss.test.class;
// 常用的方法
logger.info("获取全限定类名:\t" + test.getName()); //获取全限定类名: zjamss.test
logger.info("获取类名:\t" + test.getSimpleName()); //获取类名: test
logger.info("实例化:\t" + test.newInstance()); //实例化: zjamss.test@17d10166
复制代码
Construct类及使用方法
Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法。
//Constructor使用
@Test
public void t2() throws Exception {
Class<?> clazz = Class.forName("zjamss.reflect.User");
User user = (User) clazz.newInstance(); //调用无参构造函数
System.out.println(user); //User{name='null', age=0}
Constructor cs1 = clazz.getConstructor(int.class);
User user1 = (User) cs1.newInstance(10); //调用一个参数的构造函数
System.out.println(user1); //User{name='null', age=10}
Constructor cs2 = clazz.getDeclaredConstructor(String.class,int.class);
User user2 = (User) cs2.newInstance("12", 12); //调用两个参数的构造函数
System.out.println(user2); //User{name='12', age=12}
Constructor[] constructors = clazz.getDeclaredConstructors(); //获取User类里所有的构造方法
for(Constructor constructor : constructors){
System.out.print(constructor.toGenericString+":\t");
Class[] parameterTypes = constructor.getParameterTypes(); //获取构造方法的所有形参类型
for(Class param : parameterTypes){
System.out.print(param.getCanonicalName()+"\t");
}
System.out.println();
}
}
//public zjamss.reflect.User(int): int
//public zjamss.reflect.User(java.lang.String,int): java.lang.String int
//public zjamss.reflect.User():
复制代码
public class User{
private String name;
public int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public User(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
复制代码
Field类及其用法
Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类静态字段或实例字段。
//Field
@Test
public void t3() throws Exception{
Class clazz = User.class;
//获取指定字段名称且修饰符为public的字段 包括父类
Field age = clazz.getField("age");
System.out.println("age: "+age); //age: public int zjamss.reflect.User.age
//获取所有修饰符为public的字段 包括父类
Field[] fields = clazz.getFields();
for(Field field : fields){
System.out.println(field.getName()+": "+field.getDeclaringClass());
//age: class zjamss.reflect.User
//score: class zjamss.reflect.User
}
//获取当前类所有字段
Field[] declaredFields = clazz.getDeclaredFields();
for(Field field : declaredFields){
System.out.println(field.getName()+": "+field.getDeclaringClass());
//name: class zjamss.reflect.User
//tel: class zjamss.reflect.User
//age: class zjamss.reflect.User
//score: class zjamss.reflect.User
}
//获取当前类指定字段,任意修饰符都可
Field name = clazz.getDeclaredField("name");
System.out.println(name.getName()+": "+name); //name: private java.lang.String zjamss.reflect.User.name
}
复制代码
上述方法需要注意的是,如果我们不期望获取其父类的字段,则需使用Class类的getDeclaredField/getDeclaredFields
方法来获取字段即可,倘若需要连带获取到父类的字段,那么请使用Class类的getField/getFields
,但是也只能获取到public修饰的的字段,无法获取父类的私有字段。
设置字段
Class clazz = User.class;
final Field name = clazz.getDeclaredField("name");
final Constructor constructor = clazz.getConstructor(int.class);
final User user = (User) constructor.newInstance(10);
System.out.println(user); //User{name='null', age=10}
name.setAccessible(true); //解除private
name.set(user,"name");
System.out.println(user); //User{name='name', age=10}
System.out.println(name.get(user)); //name
复制代码
Method类及其用法
Class clazz = User.class;
//获取所有public的方法,包括父类
Method[] methods =clazz.getMethods();
for (Method m:methods){
System.out.println("m::"+m);
}
//m::public int zjamss.reflect.User.getAge()
//m::public java.lang.String zjamss.reflect.User.toString()
//m::public final void java.lang.Object.wait() throws java.lang.InterruptedException
//m::public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
//m::public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
//m::public boolean java.lang.Object.equals(java.lang.Object)
//m::public native int java.lang.Object.hashCode()
//m::public final native java.lang.Class java.lang.Object.getClass()
//m::public final native void java.lang.Object.notify()
//m::public final native void java.lang.Object.notifyAll()
//获取当前类的方法包含private,该方法无法获取继承自父类的method
Method method1 = clazz.getDeclaredMethod("display");
System.out.println("method1::"+method1); //method1::private void zjamss.reflect.User.display()
method1.setAccessible(true); //修改访问权限
method1.invoke(clazz.newInstance()); //调用方法 User{name='null', age=0} 12
Method method2 = clazz.getDeclaredMethod("getAge");
System.out.println("method2:"+method2); //method2:public int zjamss.reflect.User.getAge()
int age = (int) method2.invoke(new User(12)); //获取方法返回值
System.out.println(age);
//获取当前类的所有方法包含private,该方法无法获取继承自父类的method
Method[] methods1=clazz.getDeclaredMethods();
for (Method m:methods1){
System.out.println("m1::"+m);
}
//m1::private void zjamss.reflect.User.display()
//m1::public int zjamss.reflect.User.getAge()
//m1::public java.lang.String zjamss.reflect.User.toString()
复制代码
反射的使用案例
Spring
框架想必大家都耳熟能详了,是一个提供IOC和AOP支持等等的框架,那里面的IOC又是怎么实现的呢?其实就是反射,比如最使用的@Autowired
注解,自动类型注入
简单来说,当spring扫描bean的时候,会获取一个bean的Class对象
,然后获取所有的field
,遍历查找属性上是否有@Autowired
注解,如果有,则先设置Accessile
为true
,接着就将字段赋值为对应的bean对象