Reflection(反射)

Reflection


  • 什么是反射

在运行区间,动态地去获取类中的信息(类的信息,方法信息,构造器信息,字段信息)

  • 反射中常用的API
//Class:表示所有的类信息
// 获取对应类的Class实例
static Class forName(String class className); // 获取对应类的Class实例
T newInstance(); // 创建对应类的对象(该类中必须有一个公共无参数的构造器)
String getName(); //获取类的权限定名
String getSimpleName(); //获取类的简单名称
Constructor[] getConstructors(); // 获取当前类中所有的公共构造器
COnstructor getConstructor(Class ...parameterTypes); // 获取当前类中指定的构造方法
Method[] getMethods(); // 获取当前类中所有的公共方法(包括从父类中继承过来的方法)
Method getMethod(String name, Class ...parameterTypes); // 获取指定的类方法
Method[] getDeclaredMethods(); //获取当前类中所有方法,和访问权限无关
Method getDeclaredMethod(String name, Class ...parameterTypes);
// 获取指定的类方法,和访问权限无关

Object invoke(Object obj, Object ...args); //调用指定的方法
//参数:obj,该方法所属的对象,如果是静态方法则传入null;args,调用方法所需的实际参数

Class类和Class的实例


  • Class类:

用于描述一切接口枚举是一种注解是一种接口

为了明确区分出Class实例表示的是谁的字节码,Class提供了泛型

Class clzl = Date.class; //clzl表示的是Date的字节码
Class clz2 = String.class; // clz2表示的是String的字节码
  • Class实例:

JVM中一份字节码

  • 获取到Class实例的三种方式
    • 类型.class(就是一份字节码)
    • Class.forName(String className);根据一个类的全限定名来构建Class对象
    • 每一个对象都有getClass()的方法

反射很强大,但是非常消耗性能,主要是为了做工具和框架是用的

// 同一个类在JVM中只有一份字节码;
//第一种方式:数据类型.class
Class clz1 = User.class;
// 第二种方式:Class.forName(String className);
Class clz2 = Class.forName("cn.itsource.User");
// 第三种方式:对象.getClass();得到对象的真实类型
User u = new User();
Class clz3 = u.getClass();
//clz1 == clz2 == clz3; 因为表示都是JVM中共同的一份字节码(User.class)
  • 八大基本数据类型和关键字VoidClass实例

在八大基本数据类型的包装类和Void类中都有一个常量:TYPE

所有数据类型都有class属性,表示对应的Class实例

  Integer.Type-->int.class
  Void.Type-->void.class
  
  Integer.Type == int.class; // true
  Integer.Type == Integer.class; // false
  • 数据的Class实例
    • 特点

所有具有相同元素类型和维数的数组才共享同一份字节码对象(Class对象)

 // 表示数组的Class实例:
String[] sArr = {"A", "B", "C"};
Class clz = String[].class; // 此时clz表示就是一个String类的一位数组类型;
  • 示例代码
public class ArrayClassInstanceDemo {
       String[] arr1 = {};
       String[] arr2 = {"A", "B"};
       Class clz1 = String[].class
       Class clz2 = arr2.getClass();
       Class clz3 = getClass();
       System.out.println(cl2 == c3);
       String[][] arr = {}
       System.out.println(clz1 == String[][].class); //false
       int[] iArr = {};
       System.out.prinln(iArr.getClass == clz1); // false
}
  • 获取类中的构造器

    • 常用方法
// Constructor类:表示父类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器
// 获取某一个类中的所有构造器:
// 1.明确操作的是哪一份字节码对象。
// 2.获取构造器。

// Class类获取构造器方法:
// Constructor类:表示类中构造器的类型,Constructor的实例就是某一个类中中的某一个构造器

public Constructor[] getConstructors(); // 该方法只能获取当前Class所表示的类的public修饰的构造器
public Constructor[] getDeclaredConstructors(); // 获取当前Class所表示类的所有构造器,和访问权限无关
public Constructor getConstructor(Class... parameterTypes); // 获取当前Class所表示类中指定的一个public的构造器
// 参数:parameterTypes 表示:构造器参数的Class类型
// 例如:
public User(String username);
Constructor c = clz.getConstructor(String.class);
public Constructor getDeclaredConstructor(Class.. parameterTypes); // 获取当前Class所表示类中指定的一个构造器
  • 实例代码
public class User {
private String name;
private Integer age;
private User() {}
public User(String name) {}
  public static void main(String[] args) {
    Class clz = User.class;
    Constructor conn = clz.getConstructor(in.class);
    System.out.println(conn);
    // 由于在User类中没有带有int类型参数的构造器,所以会抛出异常
    // NoSuchMethodException
  }
}
  • 创建对象
    • 方式一
// 在反射中,Constructor最主要的作用就是调用构造器,创建构造对象
// 常用方法
public T newInstance(Object... initargs); // 如果调用带参数的构造器,只能使用该方法
// 参数:initargs:表示调用构造器的实际参数
// 返回:返回创建的实例,T表示Class所表示的类的类型
// 如果:一个雷的构造器可以直接访问,同时没有参数,那么可以直接使用Class类中的newInstance方法创建对象.
public Object newInstance(); // 相当于new 类名();
// 注意不能调用私有的构造器(可以先将对象的)
* 方式二
// 调用Class类中的newInstance()方法来创建
Class clz = User.class;
User user = clz.newInstance();
  • APIAccessibleObject的介绍

    AccessibleObject 类是File``MethodConstructor对象的基类。在使用Field、Method或者Constructor对来来设置或者获取访问字段、调用方法或者创建和初始化类的实例的时候,会执行访问检查

    在ACcessibleObject中提供了一个方法setAccessible(boolean flag)方法来设置是否忽略底层的访问检查flag:true表示忽略访问检查,false表示要检查(缺省值)

获取类中的方法


  • 使用反射获取某一个勒种的方法:
    • 找到获取方法所在类的字节码对象
    • 摘到需要被获取的方法
  • Class类中常用方法:
// 获取包括自身和继承过来的所有public方法
public Method[] getMethods();

// 获取自身的所有方法(不包括继承,和访问权限无关)
public Method[] getDeclaredMethods();

// 表示调用指定的一个公共方法(包括继承的)
// 参数:methodName(被调用方法的名字);parameterTypes(被调用方法的参数类型,如:String.class)
public Method getMethod(String methodName, Class... parameterTypes);

// 调用指定的一个本类中的方法(不包括继承的)
// 参数:同上
public Method getDeclaredMethod(String name, Class... parameterTypes);
  • 示例代码
// 获取所有方法
private static void getAllMethod() {
  Class clz = User.class;
  Method[] ms = clz.getMethods(); // 获取所有方法(注意:包括继承的所有公共方法)
  for (Method m : ms) {
    System.out.println(m);
  }
  ms = clz.getDeclaredMethos();// 获取所有的方法(注意:不包括继承的)
}
for (Method m : ms) {
  System.out.println(m);
}

pubic void testMethods() {
  // 获取 public void sayHi();
  Class clz = User.class;
  // 只有通过方法签名才能找到唯一的方法
  // 方法签名 = 方法签名 + 参数列表(参数类型,参数个数,参数顺序)
  Method m = clz.getMethod("sayHi", String.class);
  System.out.println(m);
  
  // 调用private void sayGoodBye(String name, int age);
  m = clz.getDeclaredMethod("sayGoodBye", String.class, int.class);
  System.out.println(m);
}

使用反射调用方法


  • 使用反射调用方法:
    • 找到被调用方法所在的字节码
    • 获取到被调用的方法对象
    • 调用该方法

调用方法

// 调用当前Method所表示的方法
// 参数:obj(被调用方法的对象--一般都是这个类的一个对象);args(传递的参数)
public Object invoke(Object obj,Object.. args);
  • 示例代码
public static void main(String[] args) {
  // 1.获取User类的Class实例
  Class clz = User.class;
  // 2.获取User类中指定的方法:public String doWork3(String name, Integer.class);
  Method method3 = clz.getMethod("doWork3", String.class, Integer.class);
  // 3.调用对象方法,这里需要传入被调用方法所属的对象,和方法所需要的实际参数
  User u = clz.newInstance(); // 保证在User类中有公共无参数的构造器
  // 用变量ret来接收该方法的返回值
  String ret = (String) method3.invoke(u, "neld", 18);
  
  // 获取User类的一个指定的私有方法:private void doWork2(String name)
  Method method2 = clz.getMethod("doWork2", String.class);
  // 将方法设置为可访问的
  method2.setAccessible(true);
  // 调用方法
  method2.invoke(u, "neld");
}

使用放射调用静态方法


  • 方法
public Object invoke(Object obj, Object.. args);
// 如果底层方法是静态的,那么可以忽略指定的obj参数。将obj参数设置为null即可。

使用反射调用可变参数


  • 方法

对于数组的引用类型,底层会自动解包 ,为了解决该问题,我们使用Object的一个一维数组把实际参数包装起来。

无论是基本数据类型还是引用数据类型,或者是可变参数类型,方正就是一切实际参数都包装在 new Object[] {} 中,就没有自动解包的问题了

  • 示例代码
public static main(String[] args) {
  // 使用反射调用public static int show(int... args)
  Class clz = VarArgsMethodInvokeDemo.class; // 获取本类的字节码
  Method m = clz.getMethod("show1", int[].class);
  //m.invoke(null, 1, 2, 3, 4, 5) // 会报错
  m.invoke(null, new int[]{1, 2, 3, 4, 5}); // 通过
  m.invoke(null, new Object[]{new int[] {1, 2, 3, 4, 5}}); // 通过
  m = clz.getMethod("show2", String[].class);
  // m.invoke(null, "A", "B", "C"); // 不通过
  // m.invoke(null, new String[] {"A", "B", "C"})// 不通过
  // 对于数组类型的引用型参数,底层会自动解包,为了解决该问题,我们使用Object的一维数组把实际参数包装起来
  m.invoke(null, new Object[]{new String[]{"A", "B", "C"}});
}

// 可变参数底层就是一个数组
// 基本类型
public static void show1(int... args) {
  System.out.println(Arrays.toString(args));
}
// 引用类型
public static void show2(String.. . args) {
  System.out.println(Arrays.toString(args));
}

使用反射获取字段


  • 思路

    • 找到字段所在类的字节码
    • 获取字段
  • 常用方法

// 获取当前Class所表示类中的所有public的字段,包括继承的字段
public Field[] getFileds();

// 获取类中指定的public字段,包括继承的字段
public Field getField(String fieldName);

// 获取当前类中的所有字段,不包括继承的字段
public Field[] getDeclaredFileds();

// 获取当前Class所表示类中该fieldName名字的字段,不包括继承的字段
public Field[] getDeclaredFileds();

// 获取当前Class中的字段,指定的字段,不包括继承的字段
public Field getDeclaredFiled(String name);

单例设计模式


  • 概念

在项目中,某个类有且只有一个实例,一般的把工具类做成单例的

  • 步骤
    • 把当前类的构造器私有化
    • 在当前类中实现创建好一个私有的静态对象
    • 向外暴露一个公共的静态的方法来返回该对象
  • 写法
    • 饿汉式
    • 懒加载式
    • 枚举
    • 使用缓存机制来实现单例效果
    • 在Spring中(对象工厂),创建的对象默认就是单例的

饿汉式

public class ArrayTool {
  private ArrayTool(){}
  private static ArrayTool instance = new ArrayTool();
  // 获取类的一个实例
  public static ArrayTool getInstance() {
    return instance;
  }
  // 工具方法
  public void sort(int arr) {
    System.out.println("数组排序...");
  }
}

懒加载(可能有线程安全问题,所以要使用同步方法)

public class ArrayTool {
  private static ArrayTool instance = null;
  
  public static ArrayTool getInstace() {
    if (instance == null) {
      synchronized (ArrayTool.class) {
        if (instance == null) {
          instance = new ArrayTool();
        }
      }
    }
  }
}

public void sort(int[] arr) {
  System.out.println("数组排序...");
}

枚举

public enum ArrayTool {
  INSTANCE; // public static final ArrayTool INSTANCE;
  // 工具方法
  private ArrayTool(){}
  public void sort(int[] arr) {
    System.out.println("数组排序...");
  }
}

Eclipse项目下classpath文件分析


source folder目录下的文件会编译到output(默认bin)目录中

  • 加载资源文件
    • 使用相对路径

相对于CLASSPATH的根路径(output, 输出目录) ,需要获取ClassLoader对象

// 获取ClassLoader对象的两种方式
// 方式一:
ClassLoader loader = Thread.currentThread().getContextClassLoader();
// 方式二:
ClassLoader loader2 = 当前类名.class.getClassloader();

// 在调用即可获取到output目录下的资源文件流
public InputStream getResourceAsStream(String fileName);

注意


在使用newInstace()的时候,需要被实例化对象的类,具有无参的公共的构造方法。

你可能感兴趣的:(Reflection(反射))