反射的概念

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在运行期间借助
于 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及
方法。
加载完类之后,在堆内存的方法区中就产生了一个 Class 类型的对象(一个类
只有一个 Class 对象),这个对象就包含了完整的类的结构信息。我们可以通
过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,
所以,我们形象的称之为:反射

反射机制提供的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时判断任意一个类对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解
  • 生成动态代理

反射相关 API

java.lang.Class: 代表一个类

java.lang.reflect.Method: 代表类的方法

java.lang.reflect.FieId: 代表类的成员变量

java.lang.reflect.Constructor: 代表类的构造器

反射的优缺点

优点

 1. 提高 java 程序的灵活性、降低了耦合性,提高自适应能力
 2. 允许程序创建和控制任何类的对象,无需提前硬编码目标类

缺点

  1. 反射性能较差
  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)

  1. 启动类加载器(引导类加载器,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 等开头的类
  • 加载扩展类和应用程序类加载器,并指定为他们的父类加载器
  1. 扩展类加载器(Extension ClassLoader)
  • Java 语言编写,由 sun.misc.Launcher$ExtClassLoader 实现。
  • 继承于 ClassLoader 类
  • 父类加载器为启动类加载器
  • 从 java.ext.dirs 系统属性所指定的目录中加载类库,或从 JDK 的安装目录的jre/lib/ext子目录下加载类库。如果用户创建的 JAR 放在此目录下,也会自动由扩展类加载器加载
  1. 应用程序类加载器(系统类加载器,AppClassLoader)
  • java 语言编写,由 sun.misc.Launcher$AppClassLoader 实现
  • 继承于 ClassLoader 类,父类加载器为扩展类加载器
  • 它负责加载环境变量 classpath 或系统属性 java.class.path 指定路径下的类库
  • 应用程序中的类加载器默认是系统类加载器
  • 它是用户自定义类加载器的默认父加载器
  • 通过 ClassLoader 的 getSystemClassLoader()方法可以获取到该类加载器
  1. 用户自定义类加载器
  • 自定义类加载器通常需要继承于 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);

反射的基本应用

创建运行类的对象

这是反射应用最多的地方

  1. 调用 Class 对象的 newInstance() : 要求类有无参构造,且访问权限满足

    • 获取该类型的 Class 对象

    • 调用 Class 对象的 newInstance()方法创建对象

  2. 获取构造器对象来进行实例化

    • 通过 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[] getConstructors() 该类表示的对象全部的 public 构造器
public Constructor[] getDeclaredConstructors() 该类表示的对象全部的构造器
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 annotationClass) 获取注解。形参:注解类型的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 可以拿到里面的值,再由这个值可以去创建相关类,或调用效果方法等等

参考资料

尚硅谷Java基础