java反射
java反射
Jin反射的概念
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在运行期间借助
于 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及
方法。
加载完类之后,在堆内存的方法区中就产生了一个 Class 类型的对象(一个类
只有一个 Class 对象),这个对象就包含了完整的类的结构信息。我们可以通
过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,
所以,我们形象的称之为:反射
反射机制提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时判断任意一个类对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
反射相关 API
java.lang.Class
: 代表一个类
java.lang.reflect.Method
: 代表类的方法
java.lang.reflect.FieId
: 代表类的成员变量
java.lang.reflect.Constructor
: 代表类的构造器
反射的优缺点
优点:
1. 提高 java 程序的灵活性、降低了耦合性,提高自适应能力
2. 允许程序创建和控制任何类的对象,无需提前硬编码目标类
缺点:
- 反射性能较差
- 反射会模糊程序的内部逻辑,可读性较差
Class类
Class 对象是反射的根源
解刨一个类,先获取该类的 Class 对象
而剖析一个类或用反射解决具体的问题就是使用相关 API:
java.lang.Class
java.lang.reflect.*
理解 Class
在 Object 类 定义了 public final Class getClass()
方法,而且被所有子类继承
可以通过对象反射求出类的名称
对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实
现了哪些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象
一个 Class 对象包含了特定某个结构如下:class
: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类interface
: 接口enum
:枚举annotation
:注解primitive type
:基本数据类型void
:空[]
:数组
获取 Class 类实例
方法一:已知具体类,通过类的.class获取
Class clazz = String.class;
方法二:已知某个类的实例,调用该实例的 getClass()方法获取 Class 对象
Class clazz = "www.example.com".getClass();
方法三::已知一个类的全类名,且该类在类路径下,可通过 Class 类的静态方法
forName()获取,可能抛出 ClassNotFoundException
Class clazz = Class.forName("java.lang.String");
方法四:可以用系统类加载对象或自定义加载器对象加载指定路径下的类型
Classloader cl = this.getClass().getClassLoader();
Class clazz = cl.loadClass("类的全类名");
Class 类常用方法
方法名 | 说明 |
---|---|
static Class forName(String name) | 返回 类名name的 Class 对象 |
Object newInstance() | 调用缺省构造函数,返回该 Class 对象的一个实例 |
Class getSuperClass() | 返回当前 Class 对象的父类的 Class 对象 |
Class [] getInterfaces() | 获取当前 Class 对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Class getSuperclass() | 返回表示此 Class 所表示的实体的超类的Class |
Constructor[] getConstructors() | 返回一个包含某些 Constructor 对象的数组 |
Field[] getDeclaredFields() | 返回 Field 对象的一个数组 |
Method getMethod(String name,Class …paramTypes) | 返回一个 Method 对象,此对象的形参类型为 paramType |
举例
@Test
public void testClass(){
// com.example.model 包下的 User 类
String str = "com.example.model.User";
// 获取 Class 实例
Class clazz = Class.forName(str);
// 调用 上面获取的类 构造函数 创建对象
Object obj = clazz.newInstance();
// 获取 对象的属性 username
Field field = clazz.getField("username");
// 设置属性 username 为 fantasy
field.set(obj, "fantasy");
// 获取上面设置的属性
Object name = field.get(obj);
System.out.println(name);
}
类的加载
类在内存中完整的生命周期:加载–>使用–>卸载。其中加载过程又分为:装
载、链接、初始化三个阶段
当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载、
链接、初始化三个步骤来对该类进行初始化。如果没有意外,JVM 将会连续完
成这三个步骤,所以有时也把这三个步骤统称为类加载
类加载器的作类加载器的作用
将 class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行
时数据结构,然后在堆中生成一个代表这个类的 java.lang.Class 对象,作为方
法区中类数据的访问入口
类缓存:标准的 JavaSE 类加载器可以按要求查找类,但一旦某个类被加载到类
加载器中,它将维持加载(缓存)一段时间。不过 JVM 垃圾回收机制可以回收
这些 Class 对象
类加载器的分类(JDK8 为例)
JVM 支持两种类型的类加载器,分别为引导类加载器(Bootstrap
ClassLoader)和自定义类加载器(User-Defined ClassLoader)
- 启动类加载器(引导类加载器,Bootstrap ClassLoader)
- 这个类加载使用 C/C++语言实现的,嵌套在 JVM 内部,获取会返回 null
- 它用来加载 Java 的核心库(JAVA_HOME/jre/lib/rt.jar 或 sun.boot.class.path 路径下的内容)用于提供 JVM 自身需要的类
- 并不继承自 java.lang.ClassLoader,没有父加载器
- 出于安全考虑,Bootstrap 启动类加载器只加载包名为 java、javax、sun 等开头的类
- 加载扩展类和应用程序类加载器,并指定为他们的父类加载器
- 扩展类加载器(Extension ClassLoader)
- Java 语言编写,由 sun.misc.Launcher$ExtClassLoader 实现。
- 继承于 ClassLoader 类
- 父类加载器为启动类加载器
- 从 java.ext.dirs 系统属性所指定的目录中加载类库,或从 JDK 的安装目录的jre/lib/ext子目录下加载类库。如果用户创建的 JAR 放在此目录下,也会自动由扩展类加载器加载
- 应用程序类加载器(系统类加载器,AppClassLoader)
- java 语言编写,由 sun.misc.Launcher$AppClassLoader 实现
- 继承于 ClassLoader 类,父类加载器为扩展类加载器
- 它负责加载环境变量 classpath 或系统属性 java.class.path 指定路径下的类库
- 应用程序中的类加载器默认是系统类加载器
- 它是用户自定义类加载器的默认父加载器
- 通过 ClassLoader 的 getSystemClassLoader()方法可以获取到该类加载器
- 用户自定义类加载器
- 自定义类加载器通常需要继承于 ClassLoader
- 自定义类加载器,来定制类的加载方式
举例代码
// 获取默认系统的类加载器
ClassLoader classloader = ClassLoader.getSystemClassLoader();
// 查看一个类是哪一个类加载器加载的
ClassLoader classloader = ClassLoader.forName("com.example.User").getClassLoader();
// 获取某个类加载器的父加载器
ClassLoader parentCl = classloader.getParent();
使用 ClassLoader 获取流
getResourceAsStream(String str)
:获取类路径下的指定文件的输入流
代码
InputStream in = null;
in = this.getClass().getClassLoader().getResourceAsStream("test.properties");
System.out.printIn(in);
反射的基本应用
创建运行类的对象
这是反射应用最多的地方
调用 Class 对象的 newInstance() : 要求类有无参构造,且访问权限满足
获取该类型的 Class 对象
调用 Class 对象的 newInstance()方法创建对象
获取构造器对象来进行实例化
通过 Class 类的 getDeclaredConstructor(Class … parameterTypes) 取得本类的指定形参类型的构造器
向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数
通过 Constructor 实例化对象
构造器权限不足可调用
setAccessible(true)
代码示例
// 1. 调用 Class 对象的 newInstance()
//clazz 代表 com.example.model.User 类型
//clazz.newInstance()创建的就是 User 的对象
Class<?> clazz = Class.forName("com.example.model.User");
Object obj = clazz.newInstance();
System.out.println(obj);
// 2. 获取构造器对象来进行实例化
// 获取 class 对象
Class<?> clazz = Class.forName("com.example.model.User");
// 获取构造器对象
/** 获取 User 里的有参构造
例如 User(String name, int age)
在获取构造器 getDeclaredConstructor方法里传入对于的 Class
即 String.class int.calss
**/
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
Object obj = constructor.newInstance("fantasy",123);
System.out.println(obj);
获取运行时类的完整结构
完整对象包括 :
包、修饰符、类型名、父类(泛型父类)、父接口(泛型父接口)、成员(属性,构造器,方法)、注解(类、方法、属性上的)
相关api
方法名 | 说明 |
---|---|
public Class<?>[] getInterfaces() | 该类实现的全部接口 |
public Class<? Super T> getSuperclass() | 此 Class 所表示的实体(类、接口、基本类型)的父类的 Class |
public Constructor |
该类表示的对象全部的 public 构造器 |
public Constructor |
该类表示的对象全部的构造器 |
Constructor 类中的方法 ⬇ | |
public int getModifiers() | 获取修饰符 |
public String getName(); | 获取方法名 |
public Class<?>[] getParameterTypes(); | 获取参数类型 |
public Method[] getDeclaredMethods() | 获取全部方法 |
public Method[] getMethods() | 获取 public 方法 |
Method 类中的方法 ⬇ | |
public Class<?> getReturnType() | 获取全部返回值 |
public Class<?>[] getParameterTypes() | 获取参数类型 |
public int getModifiers() | 获取修饰符 |
public Class<?>[] getExceptionTypes() | 获取异常信息 |
public Field[] getFields() | 获取全部 public 的 Field(属性) |
public Field[] getDeclaredFields() | 获取全部 Field(属性) |
Field 方法中 ⬇ | |
public int getModifiers() | 获取属性的修饰符 |
public Class<?> getType() | 获取属性的类型 |
public String getName() | 获取名字 |
Annotation 相关 | |
public Annotation getAnnotation(Class |
获取注解。形参:注解类型的Class对象 |
public Annotation[] getDeclaredAnnotations() | 获取全部注解 |
泛型相关 | |
Type getGenericSuperclass() | 获取父类泛型 |
Type[] getActualTypeArguments() | 获取实际的泛型类型参数数组 |
Package getPackage() | 获取类所在的包 |
代码举例
@Test
public void test5() throws ClassNotFoundException {
// 获取所有属性
Class<?> clazz = Class.forName("com.example.model.User");
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
System.out.println(f);
}
}
获取类型,修饰符
@Test
public void test6() throws ClassNotFoundException {
Class<?> clazz = Class.forName("com.jin.usercenter.model.User");
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
// 获取权限修饰符
int modifiers = f.getModifiers();
System.out.print(Modifier.toString(modifiers)+"\t");
// 获取数据类型
Class<?> type = f.getType();
System.out.print(type.getName()+"\t");
// 变量名
String fName = f.getName();
System.out.println(fName);
}
}
权限修饰符返回值说明:
十六进制、十进制、二进制
- PUBLIC : 0x00000001; 1; 1
- PRIVATE : 0x00000002; 2; 10
- PROTECTED : 0x00000004; 4; 100
- STATIC : 0x00000008; 8; 1000
- FINAL: 0x00000010; 16; 10000
设计的理念,就是用二进制的某一位是 1,来代表一种修饰符,整个二
进制中只有一位是 1,其余都是 0
mod = 17 0x00000011
if ((mod & PUBLIC) != 0) 说明修饰符中有 public
if ((mod & FINAL) != 0) 说明修饰符中有 fina
&
按位与运算 只有对应的两个二进位均为1时,结果位才为1 ,否则为0参与运算的数以补码方式出现
获取 方法里的:注解、修饰符、返回值类型、形参、异常
@Test
public void test7() throws ClassNotFoundException {
Class<?> clazz = Class.forName("com.example.model.User");
// 获取所有方法
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method method : declaredMethods) {
// 方法名
System.out.println("方法名: "+method.getName());
// 获取方法的注解
Annotation[] annotation = method.getAnnotations();
for (Annotation at : annotation) {
System.out.print("注解: "+ at);
}
// 方法的权限修饰符
int modifiers = method.getModifiers();
System.out.print("权限修饰符: "+Modifier.toString(modifiers)+"\t");
// 返回值类型
Class<?> returnType = method.getReturnType();
System.out.println("返回值类型: "+returnType.getName());
// 形参列表
System.out.print("形参列表: ");
Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.print(parameterType.getName()+"\t");
}
System.out.println();
// 抛出的异常
System.out.print("抛出的异常: ");
Class<?>[] exceptionTypes = method.getExceptionTypes();
for (Class<?> exceptionType : exceptionTypes) {
System.out.println(exceptionType);
}
System.out.println();
}
}
获取 构造器,父类,接口
@Test
public void test9() throws ClassNotFoundException {
Class<?> clazz = Class.forName("com.example.model.User");
// 获取所有构造器
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
// 获取运行时类的父类
Class<?> superclass = clazz.getSuperclass();
System.out.println(superclass);
//获取运行时类的所在的包
Package aPackage = clazz.getPackage();
System.out.println(aPackage);
// 获取运行时类的注解
Annotation[] annotation = clazz.getAnnotations();
for (Annotation at : annotation) {
System.out.println(at);
}
// 获取运行时类所实现的接口
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println(anInterface);
}
}
调用运行时类的指定结构
调用指定属性
通过 Field 类 get 、set 方法操作类的属性
代码:
@Test
public void test10() throws Exception {
Class<?> clazz = Class.forName("com.jin.usercenter.model.User");
Field username = clazz.getDeclaredField("username");
// 设置属性可访问
username.setAccessible(true);
// 创建实例对象
Object obj = clazz.newInstance();
// 设置
username.set(obj,"张三");
// 获取
Object value = username.get(obj);
System.out.println(value);
}
调用指定方法
代码示例
@Test
public void test11() throws Exception{
// 1. 获取该类型的 Class 对象
Class<?> clazz = Class.forName("com.jin.usercenter.model.User");
// 2.获取方法对象
/* User 类下的 add 方法
public int add(int a ,int b){
return a+b;
}
*/
Method add = clazz.getDeclaredMethod("add", int.class, int.class);
//3.创建实例对象
Object obj = clazz.newInstance();
// 4.调用方法
Object invoke = add.invoke(obj, 1, 2);
System.out.println(invoke); // 3
}
读取注解信息
代码
创建注解
// Table注解
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String value();
}
// Column 注解
@Inherited
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
String columnName();
String columnType();
}
使用注解
package com.example.annntation;
@Table("t_user")
public class User {
@Column(columnName = "sid",columnType = "int")
private int id;
@Column(columnName = "name",columnType = "varchar(20)")
private String name;
// 省略 get/set 方法
}
通过反射获取注解的内容
我们自己定义的注解,只能使用反射的代码读取。
所以上面自定义注解的声明周期必须是 RetentionPolicy.RUNTIME
public static void main(String[] args) {
// 获取 Class
Class<User> userClass = User.class;
// 获取注解 @Table
Table table = userClass.getAnnotation(Table.class);
// 拿到注解的值 value
String tableValue = table.value();
System.out.println(tableValue); // t_user
Field[] userFields = userClass.getDeclaredFields();
for (Field userField : userFields) {
// 获取注解 @Column
Column annotation = userField.getAnnotation(Column.class);
// 拿到注解的值 columnName,columnType
String columnName = annotation.columnName();
String columnType = annotation.columnType();
System.out.println(columnName); // sid
System.out.println(columnType); // int
}
}
反射的动态性
动态的创建给定字符串对应的类的对象
由用户选择创建什么类,而不是写死具体 new 哪一个类
代码
public class reflection {
public <T> T getInstance(String name) throws Exception {
Class<?> clazz = Class.forName(name);
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
return (T) clazz.cast(constructor.newInstance());
}
@Test
public void test() throws Exception {
// 创建 User类
String className = "com.example.User";
User user = getInstance(className);
System.out.println(user);
}
}
调用指定的方法
用户可动态决定调用哪一个类里的哪一个方法
public Object invoke(String className, String methodName) throws Exception {
// 创建对象
Class<?> clazz = Class.forName(className);
Constructor<?> constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Object obj = constructor.newInstance();
// 调用方法
Method method = clazz.getDeclaredMethod(methodName);
method.setAccessible(true);
Object invoke = method.invoke(obj);
return invoke;
}
@Test
public void test1() throws Exception {
String className = "com.example.annntation.User";
String methodName = "getId";
// 调用 User 类的 getId 方法
Object invoke = invoke(className, methodName);
System.out.println(invoke);
}
加载配置文件获取指定的值
@Test
public void test3() throws IOException {
// 加载"config.properties"的配置文件,获取"name"的属性的值
Properties properties = new Properties();
InputStream is = ClassLoader.getSystemResourceAsStream("config.properties");
properties.load(is);
String name = properties.getProperty("name");
System.out.println(name);
}
通过加载配置文件 config.properties 可以拿到里面的值,再由这个值可以去创建相关类,或调用效果方法等等
参考资料