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

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

1:反射概述

 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

 实际上,我们创建的每一个类也都是对象,即类本身是java.lang.Class类的实例对象。这个实例对象称之为类对象,也就是Class对象。那么,Class对象又是什么对象呢?

2:Class对象特点

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

086f394a237df6dd2f2572f412a2c08.png从图中可以得出以下几点:

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

3:反射的使用

 假设我们现在有一个Hero类

package pojo;
public class Hero {
  public String name; //姓名
    public float hp; //血量
    public float armor; //护甲
    public int moveSpeed; //移动速度
}

1:获取类对象

获取类对象有3种方式

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

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

示例:

package pojo;
public class ObjectTest {
  public static void main(String[] args) {
        String className = "pogo.Hero";
    try {
          //获取类对象的第一种方式
            Class pClass1 = Class.forName(className);
            //获取类对象的第二种方式
            Class pClass2 = Hero.class;
            //获取类对象的第三种方式
            Class pClass3 = new Hero().getClass();
            System.out.println(pClass1==pClass2);//输出true
            System.out.println(pClass1==pClass3);//输出true
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
 }
}

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

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种构造方法

//---------------构造方法-------------------
  //(默认的构造方法)
  Hero(String str){
    System.out.println("(默认)的构造方法 s = " + str);
  }
  //无参构造方法
  public Hero(){
    System.out.println("调用了公有、无参构造方法执行了。。。");
  }
  //有一个参数的构造方法
  public Hero(char name){
    System.out.println("姓名:" + name);
  }
  //有多个参数的构造方法
  public Hero(String name ,float hp){
    System.out.println("姓名:"+name+"血量:"+ hp);
  }
  //受保护的构造方法
  protected Hero(boolean n){
    System.out.println("受保护的构造方法 n = " + n);
  }
  //私有构造方法
  private Hero(float hp){
    System.out.println("私有的构造方法   血量:"+ hp);
  }

1.通过反射机制获取对象

package test;
public class ConstructorTest {
  /*
   * 通过Class对象可以获取某个类中的:构造方法、成员变量、成员方法;并访问成员;
   * 
   * 1.获取构造方法:
   *    1).批量的方法:
   *      public Constructor[] getConstructors():所有"公有的"构造方法
              public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)
   *    2).获取单个的方法,并调用:
   *      public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:
   *      public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;
   *    
   * 2.创建对象
   *    Constructor对象调用newInstance(Object... initargs)
   */
  public static void main(String[] args) throws Exception {
    //1.加载Class对象
    Class clazz = Class.forName("pojo.Hero");
    //2.获取所有公有构造方法
    System.out.println("**********************所有公有构造方法*********************************");
    Constructor[] conArray = clazz.getConstructors();
    for(Constructor c : conArray){
      System.out.println(c);
    }
    System.out.println("************所有的构造方法(包括:私有、受保护、默认、公有)***************");
    conArray = clazz.getDeclaredConstructors();
    for(Constructor c : conArray){
      System.out.println(c);
    }
    System.out.println("*****************获取公有、无参的构造方法*******************************");
    Constructor con = clazz.getConstructor(null);
    //1>、因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的类型,切记是类型
    //2>、返回的是描述这个无参构造函数的类对象。
    System.out.println("con = " + con);
    //调用构造方法
    Object obj = con.newInstance();
    System.out.println("******************获取私有构造方法,并调用*******************************");
    con = clazz.getDeclaredConstructor(float.class);
    System.out.println(con);
    //调用构造方法
    con.setAccessible(true);//暴力访问(忽略掉访问修饰符)
    obj = con.newInstance(100);
  }
}

输出:

**********************所有公有构造方法*********************************
public pojo.Hero(java.lang.String,float)
public pojo.Hero(char)
public pojo.Hero()
************所有的构造方法(包括:私有、受保护、默认、公有)***************
private pojo.Hero(float)
protected pojo.Hero(boolean)
public pojo.Hero(java.lang.String,float)
public pojo.Hero(char)
public pojo.Hero()
pojo.Hero(java.lang.String)
*****************获取公有、无参的构造方法*******************************
con = public pojo.Hero()
调用了公有、无参构造方法执行了。。。
******************获取私有构造方法,并调用*******************************
private pojo.Hero(float)
私有的构造方法   血量: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

2. 获取属性 Field f1 = h.getDeclaredField("属性名")

3. 修改属性 f1.set(h,实参),注意这里的h是对象,不是类对象

示例:

1.新增HeroPlus类

package pojo;
public class HeroPlus {
  public String name;
    public float hp;
    public int damage;
    public int id;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public HeroPlus(){
    }
    public HeroPlus(String string) {
        name =string;
    }
    @Override
    public String toString() {
        return "Hero [name=" + name + "]";
    }
    public boolean isDead() {
        // TODO Auto-generated method stub
        return false;
    }
    public void attackHero(HeroPlus h2) {
        System.out.println(this.name+ " 正在攻击 " + h2.getName());
    }
}

1.获取属性并修改

package test;
public class ParaTest {
   public static void main(String[] args) {
         HeroPlus h =new HeroPlus();
         //使用传统方式修改name的值为garen
         h.name = "garen";
         try {
             //获取类HeroPlus的名字叫做name的字段
             Field f1= h.getClass().getDeclaredField("name");
             //修改这个字段的值
             f1.set(h, "teemo");
             //打印被修改后的值
             System.out.println(h.name);
         } catch (Exception e) {
             // TODO Auto-generated catch block
             e.printStackTrace();
         }
 }
}

补充:

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:调用方式时所传递的实参;

示例:

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

5: 获取main方法并使用

示例:

  1. HeroPlus 新增main方法
public static void main(String[] args) {
    System.out.println("执行main方法");
  }

1.通过下面步骤获取main方法1

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

4 : 关于反射的用法举例

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

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

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

1.首先准备两个业务类

package service;
public class Service1 {
    public void doService1(){
        System.out.println("业务方法1");
    }
}
package service;
public class Service2 {
  public void doService2(){
        System.out.println("业务方法2");
    }
}

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

package service;
public class CommonTest {
    public static void main(String[] args) {
      //new Service1().doService1();
      //必须重新修改代码
          new Service2().doService2();
      }
}

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

1.首先准备一个配置文件,就叫做spring.txt吧, 放在src目录下。

里面存放的是类的名称,和要调用的方法名。首先准备一个配置文件,就叫做spring.txt吧, 放在src目录下。里面存放的是类的名称,和要调用的方法名。

2.在测试类Test中,首先取出类名称和方法名,然后通过反射去调用这个方法。

3.当需要从调用第一个业务方法,切换到调用第二个业务方法的时候,不需要修改一行代码,也不需要重新编译,只需要修改配置文件spring.txt,再运行即可。

示例:

spring.txt内容

class=reflection.Service1
method=doService1
package service;
public class ReflectTest {
  @SuppressWarnings({ "rawtypes", "unchecked" })
    public static void main(String[] args) throws Exception {
        //从spring.txt中获取类名称和方法名称
        File springConfigFile = new File("H:\\eclpise-workspace\\reflect-demo\\src\\spring.txt");
        Properties springConfig= new Properties();
        springConfig.load(new FileInputStream(springConfigFile));
        String className = (String) springConfig.get("class");
        String methodName = (String) springConfig.get("method");
        //根据类名称获取类对象
        Class clazz = Class.forName(className);
        //根据方法名称,获取方法对象
        Method m = clazz.getMethod(methodName);
        //获取构造器
        Constructor c = clazz.getConstructor();
        //根据构造器,实例化出对象
        Object service = c.newInstance();
        //调用对象的指定方法
        m.invoke(service);
    }
}

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

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

示例:

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

GitHub源码:java反射

Java基础之—反射(非常重要) ↩︎

反射有什么用 ↩︎

深入分析Java方法反射的实现原理

后记:

 本文部分内容引用自csdn的敬业的小码哥和How2jJava,如有侵权,请联系我 ↩︎

目录
相关文章
|
17天前
|
数据采集 人工智能 Java
Java产科专科电子病历系统源码
产科专科电子病历系统,全结构化设计,实现产科专科电子病历与院内HIS、LIS、PACS信息系统、区域妇幼信息平台的三级互联互通,系统由门诊系统、住院系统、数据统计模块三部分组成,它管理了孕妇从怀孕开始到生产结束42天一系列医院保健服务信息。
28 4
|
23天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
61 2
|
2月前
|
Java Apache Maven
Java百项管理之新闻管理系统 熟悉java语法——大学生作业 有源码!!!可运行!!!
文章提供了使用Apache POI库在Java中创建和读取Excel文件的详细代码示例,包括写入数据到Excel和从Excel读取数据的方法。
61 6
Java百项管理之新闻管理系统 熟悉java语法——大学生作业 有源码!!!可运行!!!
|
3月前
|
数据采集 运维 前端开发
【Java】全套云HIS源码包含EMR、LIS (医院信息化建设)
系统技术特点:采用前后端分离架构,前端由Angular、JavaScript开发;后端使用Java语言开发。
109 5
|
27天前
|
人工智能 监控 数据可视化
Java智慧工地信息管理平台源码 智慧工地信息化解决方案SaaS源码 支持二次开发
智慧工地系统是依托物联网、互联网、AI、可视化建立的大数据管理平台,是一种全新的管理模式,能够实现劳务管理、安全施工、绿色施工的智能化和互联网化。围绕施工现场管理的人、机、料、法、环五大维度,以及施工过程管理的进度、质量、安全三大体系为基础应用,实现全面高效的工程管理需求,满足工地多角色、多视角的有效监管,实现工程建设管理的降本增效,为监管平台提供数据支撑。
35 3
|
1月前
|
运维 自然语言处理 供应链
Java云HIS医院管理系统源码 病案管理、医保业务、门诊、住院、电子病历编辑器
通过门诊的申请,或者直接住院登记,通过”护士工作站“分配患者,完成后,进入医生患者列表,医生对应开具”长期医嘱“和”临时医嘱“,并在电子病历中,记录病情。病人出院时,停止长期医嘱,开具出院医嘱。进入出院审核,审核医嘱与住院通过后,病人结清缴费,完成出院。
76 3
|
1月前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
2月前
|
JSON 前端开发 Java
震惊!图文并茂——Java后端如何响应不同格式的数据给前端(带源码)
文章介绍了Java后端如何使用Spring Boot框架响应不同格式的数据给前端,包括返回静态页面、数据、HTML代码片段、JSON对象、设置状态码和响应的Header。
151 1
震惊!图文并茂——Java后端如何响应不同格式的数据给前端(带源码)
|
2月前
|
移动开发 前端开发 JavaScript
java家政系统成品源码的关键特点和技术应用
家政系统成品源码是已开发完成的家政服务管理软件,支持用户注册、登录、管理个人资料,家政人员信息管理,服务项目分类,订单与预约管理,支付集成,评价与反馈,地图定位等功能。适用于各种规模的家政服务公司,采用uniapp、SpringBoot、MySQL等技术栈,确保高效管理和优质用户体验。
|
2月前
|
存储 Java
[Java]反射
本文详细介绍了Java反射机制的基本概念、使用方法及其注意事项。首先解释了反射的定义和类加载过程,接着通过具体示例展示了如何使用反射获取和操作类的构造方法、方法和变量。文章还讨论了反射在类加载、内部类、父类成员访问等方面的特殊行为,并提供了通过反射跳过泛型检查的示例。最后,简要介绍了字面量和符号引用的概念。全文旨在帮助读者深入理解反射机制及其应用场景。
26 0
[Java]反射