Java 反射 -超详细讲解(附源码)

小鱼儿 2022-09-14 15:27 312阅读 0赞

文章目录

  • 1:反射概述
  • 2:Class对象特点
  • 3:反射的使用
  • 1:获取类对象
  • 2 利用反射机制创建对象
  • 3: 获取成员变量并使用
  • 4: 获取成员方法并使用
  • 5: 获取main方法并使用
  • 4 : 关于反射的用法举例
  • 1:通过反射运行配置文件内容
  • 2:通过反射越过泛型检查

1:反射概述

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
实际上,我们创建的每一个类也都是对象,即类本身是java.lang.Class类的实例对象。这个实例对象称之为类对象,也就是Class对象。那么,Class对象又是什么对象呢?


2:Class对象特点

下图是Class类的api(图片来自于Java基础之—反射(非常重要))

在这里插入图片描述
从图中可以得出以下几点:

  1. Class 类的实例对象表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有很多的实例,每个类都有唯一的Class对象。
  2. Class 类没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机自动构造的。也就是说我们不需要创建,JVM已经帮我们创建了。
  3. Class 对象用于提供类本身的信息,比如有几种构造方法, 有多少属性,有哪些普通方法

3:反射的使用

假设我们现在有一个Hero类

  1. package pojo;
  2. public class Hero {
  3. public String name; //姓名
  4. public float hp; //血量
  5. public float armor; //护甲
  6. public int moveSpeed; //移动速度
  7. }

1:获取类对象

获取类对象有3种方式

  1. Class.forName()常用
  2. Hero.class
  3. new Hero().getClass()

在一个JVM中,一种类,只会有一个类对象存在。所以以上三种方式取出来的类对象,都是一样。(此处准确是在ClassLoader下,只有一个类对象)

示例:

  1. package pojo;
  2. public class ObjectTest {
  3. public static void main(String[] args) {
  4. String className = "pogo.Hero";
  5. try {
  6. //获取类对象的第一种方式
  7. Class pClass1 = Class.forName(className);
  8. //获取类对象的第二种方式
  9. Class pClass2 = Hero.class;
  10. //获取类对象的第三种方式
  11. Class pClass3 = new Hero().getClass();
  12. System.out.println(pClass1==pClass2);//输出true
  13. System.out.println(pClass1==pClass3);//输出true
  14. } catch (ClassNotFoundException e) {
  15. // TODO Auto-generated catch block
  16. e.printStackTrace();
  17. }
  18. }
  19. }

三种方式中,常用第一种,第二种需要导入类的包,依赖太强,不导包就抛编译错误。第三种对象都有了还要反射干什么。一般都第一种,一个字符串可以传入也可写在配置文件中等多种方法。


2 利用反射机制创建对象

基本步骤
与传统的通过new 来获取对象的方式不同反射机制,反射会先拿到Hero的“类对象”,然后通过类对象获取“构造器对象”再通过构造器对象创建一个对象,具体步骤:

1.获取类对象 Class class = Class.forName("pojo.Hero");
2.获取构造器对象 Constructor con = clazz.getConstructor(形参.class);
3 获取对象 Hero hero =con.newInstance(实参);

上面是最简单的获取方法,当Hero的构造方法不是无参构造方法时,获取构造器对象略有不同,见下面测试:

构造方法不同时,获取构造器对象的方法

示例:

  1. Hero类添加6种构造方法

    //———————-构造方法—————————-

    1. //(默认的构造方法)
    2. Hero(String str){
    3. System.out.println("(默认)的构造方法 s = " + str);
    4. }
    5. //无参构造方法
    6. public Hero(){
    7. System.out.println("调用了公有、无参构造方法执行了。。。");
    8. }
    9. //有一个参数的构造方法
    10. public Hero(char name){
    11. System.out.println("姓名:" + name);
    12. }
    13. //有多个参数的构造方法
    14. public Hero(String name ,float hp){
    15. System.out.println("姓名:"+name+"血量:"+ hp);
    16. }
    17. //受保护的构造方法
    18. protected Hero(boolean n){
    19. System.out.println("受保护的构造方法 n = " + n);
    20. }
    21. //私有构造方法
    22. private Hero(float hp){
    23. System.out.println("私有的构造方法 血量:"+ hp);
    24. }
  1. 通过反射机制获取对象

    package test;
    public class ConstructorTest {

  1. /*
  2. * 通过Class对象可以获取某个类中的:构造方法、成员变量、成员方法;并访问成员;
  3. *
  4. * 1.获取构造方法:
  5. * 1).批量的方法:
  6. * public Constructor[] getConstructors():所有"公有的"构造方法
  7. public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)
  8. * 2).获取单个的方法,并调用:
  9. * public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:
  10. * public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;
  11. *
  12. * 2.创建对象
  13. * Constructor对象调用newInstance(Object... initargs)
  14. */
  15. public static void main(String[] args) throws Exception {
  16. //1.加载Class对象
  17. Class clazz = Class.forName("pojo.Hero");
  18. //2.获取所有公有构造方法
  19. System.out.println("**********************所有公有构造方法*********************************");
  20. Constructor[] conArray = clazz.getConstructors();
  21. for(Constructor c : conArray){
  22. System.out.println(c);
  23. }
  24. System.out.println("************所有的构造方法(包括:私有、受保护、默认、公有)***************");
  25. conArray = clazz.getDeclaredConstructors();
  26. for(Constructor c : conArray){
  27. System.out.println(c);
  28. }
  29. System.out.println("*****************获取公有、无参的构造方法*******************************");
  30. Constructor con = clazz.getConstructor(null);
  31. //1>、因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的类型,切记是类型
  32. //2>、返回的是描述这个无参构造函数的类对象。
  33. System.out.println("con = " + con);
  34. //调用构造方法
  35. Object obj = con.newInstance();
  36. System.out.println("******************获取私有构造方法,并调用*******************************");
  37. con = clazz.getDeclaredConstructor(float.class);
  38. System.out.println(con);
  39. //调用构造方法
  40. con.setAccessible(true);//暴力访问(忽略掉访问修饰符)
  41. obj = con.newInstance(100);
  42. }
  43. }

输出:

  1. **********************所有公有构造方法*********************************
  2. public pojo.Hero(java.lang.String,float)
  3. public pojo.Hero(char)
  4. public pojo.Hero()
  5. ************所有的构造方法(包括:私有、受保护、默认、公有)***************
  6. private pojo.Hero(float)
  7. protected pojo.Hero(boolean)
  8. public pojo.Hero(java.lang.String,float)
  9. public pojo.Hero(char)
  10. public pojo.Hero()
  11. pojo.Hero(java.lang.String)
  12. *****************获取公有、无参的构造方法*******************************
  13. con = public pojo.Hero()
  14. 调用了公有、无参构造方法执行了。。。
  15. ******************获取私有构造方法,并调用*******************************
  16. private pojo.Hero(float)
  17. 私有的构造方法 血量:100.0

总结:

1.获取构造器对象方法:
1).批量的方法:
public Constructor[] getConstructors():所有”公有的”构造方法
public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)
2).获取单个的方法:
public Constructor getConstructor(Class… parameterTypes): 获取单个的”公有的”构造方法
public Constructor getDeclaredConstructor(Class…parameterTypes):获取”某个构造方法”可以是私有的,或受保护、默认、公有;


3: 获取成员变量并使用

基本步骤

1.获取HeroPlus类的对象 new方法/第2章中的方法 h

  1. 获取属性 Field f1 = h.getDeclaredField("属性名")
  2. 修改属性 f1.set(h,实参),注意这里的h是对象,不是类对象

示例:

  1. 新增HeroPlus类

    package pojo;
    public class HeroPlus {

    1. public String name;
    2. public float hp;
    3. public int damage;
    4. public int id;
    5. public String getName() {
    6. return name;
    7. }
    8. public void setName(String name) {
    9. this.name = name;
    10. }
    11. public HeroPlus(){
    12. }
    13. public HeroPlus(String string) {
    14. name =string;
    15. }
    16. @Override
    17. public String toString() {
    18. return "Hero [name=" + name + "]";
    19. }
    20. public boolean isDead() {
    21. // TODO Auto-generated method stub
    22. return false;
    23. }
    24. public void attackHero(HeroPlus h2) {
    25. System.out.println(this.name+ " 正在攻击 " + h2.getName());
    26. }

    }

  2. 获取属性并修改

    package test;
    public class ParaTest {

    1. public static void main(String[] args) {
    2. HeroPlus h =new HeroPlus();
    3. //使用传统方式修改name的值为garen
    4. h.name = "garen";
    5. try {
    6. //获取类HeroPlus的名字叫做name的字段
    7. Field f1= h.getClass().getDeclaredField("name");
    8. //修改这个字段的值
    9. f1.set(h, "teemo");
    10. //打印被修改后的值
    11. System.out.println(h.name);
    12. } catch (Exception e) {
    13. // TODO Auto-generated catch block
    14. e.printStackTrace();
    15. }

    }
    }

补充
getField和getDeclaredField的区别
getField 只能获取public的,包括从父类继承来的字段。
getDeclaredField 可以获取本类所有的字段,包括private的,但是 不能获取继承来的字段。 (注: 这里只能获取到private的字段,但并不能访问该private字段的值,除非加上setAccessible(true))


4: 获取成员方法并使用

  1. 获取HeroPlus类的对象 h
  2. 获取成员方法:
    public Method getMethod(String name ,Class<?>… parameterTypes):获取”公有方法”;(包含了父类的方法也包含Object类)
    public Method getDeclaredMethods(String name ,Class<?>… parameterTypes) :获取成员方法,包括私有的(不包括继承的)
    参数解释:
    name : 方法名;
    Class … : 形参的Class类型对象
  3. 调用方法
    Method —> public Object invoke(Object obj,Object… args):
    参数说明:
    obj : 要调用方法的对象;
    args:调用方式时所传递的实参;

示例:

  1. package test;
  2. public class MethodTest {
  3. public static void main(String[] args) {
  4. HeroPlus h = new HeroPlus();
  5. try {
  6. // 获取这个名字叫做setName,参数类型是String的方法
  7. Method m = h.getClass().getMethod("setName", String.class);
  8. // 对h对象,调用这个方法
  9. m.invoke(h, "盖伦");
  10. // 使用传统的方式,调用getName方法
  11. System.out.println(h.getName());
  12. } catch (Exception e) {
  13. // TODO Auto-generated catch block
  14. e.printStackTrace();
  15. }
  16. }
  17. }

5: 获取main方法并使用

示例:

  1. HeroPlus 新增main方法

    public static void main(String[] args) {

    1. System.out.println("执行main方法");
    2. }
  2. 通过下面步骤获取main方法1

    package test;
    public class MainTest {

    1. public static void main(String[] args) {
    2. try {
    3. //1、获取HeroPlus对象的字节码
    4. Class clazz = Class.forName("pojo.HeroPlus");
    5. //2、获取main方法,第一个参数:方法名称,第二个参数:方法形参的类型,
    6. Method methodMain = clazz.getMethod("main", String[].class);
    7. //3、调用main方法
    8. // methodMain.invoke(null, new String[]{"a","b","c"});
    9. //第一个参数,对象类型,因为方法是static静态的,所以为null可以,第二个参数是String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数
    10. //这里拆的时候将 new String[]{"a","b","c"} 拆成3个对象。所以需要将它强转。
    11. methodMain.invoke(null, (Object)new String[]{"a","b","c"});//方式一
    12. // methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});//方式二
    13. } catch (Exception e) {
    14. e.printStackTrace();
    15. }
  1. }
  2. }

4 : 关于反射的用法举例

反射非常强大,但是从上面的记录来看,反而觉得还不如直接调用方法来的直接和方便2。

通常来说,需要在学习了Spring 的依赖注入,反转控制之后,才会对反射有更好的理解,所以先这里举两个例子,来演示一下反射的一种实际运用3。

1:通过反射运行配置文件内容

  1. 首先准备两个业务类

    package service;
    public class Service1 {

    1. public void doService1(){
    2. System.out.println("业务方法1");
    3. }

    }

    package service;
    public class Service2 {

    1. public void doService2(){
    2. System.out.println("业务方法2");
    3. }

    }

  2. 当需要从第一个业务方法切换到第二个业务方法的时候,使用非反射方式,必须修改代码,并且重新编译运行,才可以达到效果

    package service;
    public class CommonTest {

    1. public static void main(String[] args) {
    2. //new Service1().doService1();
    3. //必须重新修改代码
    4. new Service2().doService2();
    5. }

    }

  3. 使用反射方式则方便很多

  1. 首先准备一个配置文件,就叫做spring.txt吧, 放在src目录下。
    里面存放的是类的名称,和要调用的方法名。首先准备一个配置文件,就叫做spring.txt吧, 放在src目录下。里面存放的是类的名称,和要调用的方法名。
  2. 在测试类Test中,首先取出类名称和方法名,然后通过反射去调用这个方法。
  3. 当需要从调用第一个业务方法,切换到调用第二个业务方法的时候,不需要修改一行代码,也不需要重新编译,只需要修改配置文件spring.txt,再运行即可。

示例:
spring.txt内容

  1. class=reflection.Service1
  2. method=doService1

测试类

  1. package service;
  2. public class ReflectTest {
  3. @SuppressWarnings({ "rawtypes", "unchecked" })
  4. public static void main(String[] args) throws Exception {
  5. //从spring.txt中获取类名称和方法名称
  6. File springConfigFile = new File("H:\\eclpise-workspace\\reflect-demo\\src\\spring.txt");
  7. Properties springConfig= new Properties();
  8. springConfig.load(new FileInputStream(springConfigFile));
  9. String className = (String) springConfig.get("class");
  10. String methodName = (String) springConfig.get("method");
  11. //根据类名称获取类对象
  12. Class clazz = Class.forName(className);
  13. //根据方法名称,获取方法对象
  14. Method m = clazz.getMethod(methodName);
  15. //获取构造器
  16. Constructor c = clazz.getConstructor();
  17. //根据构造器,实例化出对象
  18. Object service = c.newInstance();
  19. //调用对象的指定方法
  20. m.invoke(service);
  21. }
  22. }

2:通过反射越过泛型检查

泛型是在编译期间起作用的。在编译后的.class文件中是没有泛型的。所有比如T或者E类型啊,本质都是通过Object处理的。所以可以通过使用反射来越过泛型。

示例:

  1. package test;
  2. public class GenericityTest {
  3. public static void main(String[] args) throws Exception{
  4. ArrayList<String> list = new ArrayList<>();
  5. list.add("this");
  6. list.add("is");
  7. // strList.add(5);报错
  8. /********** 越过泛型检查 **************/
  9. //获取ArrayList的Class对象,反向的调用add()方法,添加数据
  10. Class listClass = list.getClass();
  11. //获取add()方法
  12. Method m = listClass.getMethod("add", Object.class);
  13. //调用add()方法
  14. m.invoke(list, 5);
  15. //遍历集合
  16. for(Object obj : list){
  17. System.out.println(obj);
  18. }
  19. }
  20. }


GitHub源码:java反射


  1. Java基础之—反射(非常重要) ↩︎
  2. 反射有什么用 ↩︎
  3. 深入分析Java方法反射的实现原理
    后记:
    本文部分内容引用自csdn的敬业的小码哥和How2jJava,如有侵权,请联系我 ↩︎

发表评论

表情:
评论列表 (有 0 条评论,312人围观)

还没有评论,来说两句吧...

相关阅读