Java的反射机制

Java的反射机制

五月 25, 2020

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:反射的使用

假设我们现在有一个persion类

1
2
3
4
5
6
package com.gupao.test.decorator.classapi;
public class Person {
public String name; //姓名
public int age; //年龄
public String gender; //性别
}

3.1 获取类对象

获取类对象的方式有3种:

  1. Class.forName()(常用)
  2. Person.class
  3. new Person().getClass()
    在一个JVM中,一种类,只会有一个类对象存在。所以以上三种方式取出来的类对象,都是一样。(此处准确是在ClassLoader下,只有一个类对象)
    示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package pojo;
    public class ObjectTest {

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

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

3.2 利用反射机制创建对象

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

1
2
3
1.获取类对象 Class class = Class.forName("com.gupao.test.decorator.classapi.Person");
2.获取构造器对象 Constructor con = clazz.getConstructor(形参.class);
3 获取对象 Person person =con.newInstance(实参);

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    //---------------构造方法-------------------
    //(默认的构造方法)
    Person(String str){
    System.out.println("(默认)的构造方法 s = " + str);
    }

    //无参构造方法
    public Person(){
    System.out.println("调用了公有、无参构造方法执行了。。。");
    }

    //有一个参数的构造方法
    public Person(char name){
    System.out.println("姓名:" + name);
    }

    //有多个参数的构造方法
    public Person(String name ,int age){
    System.out.println("姓名:"+name+"年龄:"+ age);
    }

    //受保护的构造方法
    protected Person(boolean n){
    System.out.println("受保护的构造方法 n = " + n);
    }

    //私有构造方法
    private Person(int age){
    System.out.println("私有的构造方法 年龄:"+ age);
    }
  2. 通过反射机制获取对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    package com.gupao.test.decorator.classapi;

    import java.lang.reflect.Constructor;

    /**
    * @Author Cxy
    * @Description:
    * @Date 2020-05-25
    */
    public class main {
    /*
    * 通过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("com.gupao.test.decorator.classapi.Person");

    //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(int.class);
    System.out.println(con);
    //调用构造方法
    con.setAccessible(true);//暴力访问(忽略掉访问修饰符)
    obj = con.newInstance(100);
    }

    }

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
**********************所有公有构造方法*********************************
public com.gupao.test.decorator.classapi.Person(java.lang.String,int)
public com.gupao.test.decorator.classapi.Person(char)
public com.gupao.test.decorator.classapi.Person()
************所有的构造方法(包括:私有、受保护、默认、公有)***************
private com.gupao.test.decorator.classapi.Person(int)
protected com.gupao.test.decorator.classapi.Person(boolean)
public com.gupao.test.decorator.classapi.Person(java.lang.String,int)
public com.gupao.test.decorator.classapi.Person(char)
public com.gupao.test.decorator.classapi.Person()
com.gupao.test.decorator.classapi.Person(java.lang.String)
*****************获取公有、无参的构造方法*******************************
con = public com.gupao.test.decorator.classapi.Person()
调用了公有、无参构造方法执行了。。。
******************获取私有构造方法,并调用*******************************
private com.gupao.test.decorator.classapi.Person(int)
私有的构造方法 年龄:100

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

  1. 获取成员变量并使用
    基本步骤
    1
    2
    3
    获取PersonPlus类的对象 new方法/第2章中的方法 h
    获取属性 Field f1 = h.getDeclaredField("属性名")
    修改属性 f1.set(h,实参),注意这里的h是对象,不是类对象

示例:
-. 新增HeroPlus类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.gupao.test.decorator.classapi;

/**
* @Author Cxy
* @Description:
* @Date 2020-05-25
*/
public class PersonPlus {
public String name; //姓名
public int age; //年龄
public String gender; //性别


public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public PersonPlus(){

}
public PersonPlus(String str,int intAge) {
name =str;
age = intAge;
}


@Override
public String toString() {
return "Person [name=" + name + " age="+age+"]";
}

public boolean isOld() {
System.out.println("调用isOld方法,name="+name+" age="+age);
return age>40 ? true : false;
}

public void talkPerson(PersonPlus p2) {
System.out.println(this.name+ " 正在和 " + p2.getName()+" 聊天");
}
}

-. 获取属性并修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class main {

public static void main(String[] args) throws Exception {
PersonPlus h =new PersonPlus();
//使用传统方式修改name的值为garen
h.name = "x";
h.age = 20;

try {
//获取类PersonPlus的名字叫做name的字段
Field f1= h.getClass().getDeclaredField("name");
Field f2= h.getClass().getDeclaredField("age");
//修改这个字段的值
f1.set(h, "y");
f2.set(h, 40);
//打印被修改后的值
System.out.println(h.name);//输出y
System.out.println(h.age);//输出40
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

}

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

  1. 获取成员方法并使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    获取PersonPlus类的对象 p
    获取成员方法:
    public Method getMethod(String name ,Class<?>… parameterTypes):获取"公有方法";(包含了父类的方法也包含Object类)
    public Method getDeclaredMethods(String name ,Class<?>… parameterTypes) :获取成员方法,包括私有的(不包括继承的)
    参数解释:
    name : 方法名;
    Class … : 形参的Class类型对象
    调用方法
    Method --> public Object invoke(Object obj,Object… args):
    参数说明:
    obj : 要调用方法的对象;
    args:调用方式时所传递的实参;

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class main {

public static void main(String[] args) {

PersonPlus h = new PersonPlus();

try {
// 获取这个名字叫做setName,参数类型是String的方法
Method m = h.getClass().getMethod("setName", String.class);
// 对h对象,调用这个方法
m.invoke(h, "测试名字1");
// 使用传统的方式,调用getName方法
System.out.println(h.getName());//输出 测试名字1

} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

}

  1. 获取方法并使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class main {

    public static void main(String[] args) {

    try {
    //1、获取PersonPlus的构造函数
    Class clazz = Class.forName("com.gupao.test.decorator.classapi.PersonPlus");
    Constructor con = clazz.getConstructor(String.class,int.class);
    //调用构造方法
    Object obj = con.newInstance("测试名字1",60);
    //2、获取isOld方法
    Method method= clazz.getMethod("isOld");
    //3、调用isOld方法
    Boolean boo = (boolean)method.invoke(obj);
    System.out.println("调用返回=" + boo.toString());//输出结果调用isOld方法,name=测试名字1 age=60 调用返回=true

    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    }

关于反射的用法举例

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

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

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

    1. 首先准备两个业务类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
        package service;
      public class Service1 {
      public void doService1(){
      System.out.println("业务方法1");
      }
      }

      package service;
      public class Service2 {
      public void doService2(){
      System.out.println("业务方法2");
      }
      }
    2. 当需要从第一个业务方法切换到第二个业务方法的时候,使用非反射方式,必须修改代码,并且重新编译运行,才可以达到效果

      1
      2
      3
      4
      5
      6
      7
      8
      package service;
      public class CommonTest {
      public static void main(String[] args) {
      //new Service1().doService1();
      //必须重新修改代码
      new Service2().doService2();
      }
      }
    3. 使用反射方式则方便很多

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

示例:
spring.txt内容

1
2
class=reflection.Service1
method=doService1

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
}
}

  1. 通过反射越过泛型检查
    泛型是在编译期间起作用的。在编译后的.class文件中是没有泛型的。所有比如T或者E类型啊,本质都是通过Object处理的。所以可以通过使用反射来越过泛型
    示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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);
    }
    }

    }