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 private
类加载
类加载机制图解
- 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对象