Reflection(反射)

Reflection


  • 什么是反射

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

  • 反射中常用的API
//Class:表示所有的类信息
// 获取对应类的Class实例
static Class<?> forName(String class className); // 获取对应类的Class实例
T newInstance(); // 创建对应类的对象(该类中必须有一个公共无参数的构造器)
String getName(); //获取类的权限定名
String getSimpleName(); //获取类的简单名称
Constructor<?>[] getConstructors(); // 获取当前类中所有的公共构造器
COnstructor<T> 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<Date> clzl = Date.class; //clzl表示的是Date的字节码
Class<String> clz2 = String.class; // clz2表示的是String的字节码
  • Class实例:

JVM中一份字节码

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

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

// 同一个类在JVM中只有一份字节码;
//第一种方式:数据类型.class
Class<User> 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<T>类:表示父类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器
// 获取某一个类中的所有构造器:
// 1.明确操作的是哪一份字节码对象。
// 2.获取构造器。

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

public Constructor<?>[] getConstructors(); // 该方法只能获取当前Class所表示的类的public修饰的构造器
public Constructor<?>[] getDeclaredConstructors(); // 获取当前Class所表示类的所有构造器,和访问权限无关
public Constructor<T> getConstructor(Class<?>... parameterTypes); // 获取当前Class所表示类中指定的一个public的构造器
// 参数:parameterTypes 表示:构造器参数的Class类型
// 例如:
public User(String username);
Constructor c = clz.getConstructor(String.class);
public Constructor<T> 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<User> clz = User.class;
    Constructor<User> 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<User> 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()的时候,需要被实例化对象的类,具有无参的公共的构造方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容

  • Java反射机制可以让我们在编译期(Compile Time)之外的运行期(Runtime)获得任何一个类的字节码...
    badcyc阅读 572评论 0 0
  • Java反射机制可以让我们在编译期(Compile Time)之外的运行期(Runtime)获得任何一个类的字节码...
    总是擦破皮阅读 32,556评论 6 28
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,582评论 18 399
  • 初秋的空洞 是某种美学上的留白 又像是似是而非的困境 紧锁那些甜美的雨夜 房顶的月亮 在雨天的晚上碎成 九百九十几...
    租了五颗星阅读 579评论 6 12