反射

2019-09-09  ⋅   反射  

基础知识

在Java中的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象都能够调用它的任意一个方法。

在Java程序中许多对象在运行是都会出现两种类型:编译时类型和运行时类型。编译时类型由声明对象时使用的类型来决定,运行时类型由实际赋值给对象的类型决定。如:Person p=new Student(); 其中编译时类型为Person,运行时类型为Student。

使用场景

首先,既然有反射,那么先从正射开始解释。

正射:一般情况下我们使用某个类一定是知道它是什么操作用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。

1
2
Person person=new Person();//直接初始化,相当于正射
person.set();//对象调用方法

反射:而反射则是一开始不知道需要初始化的对象时什么,自然也无法使用new关键字来创建对象了。例如:请求后台接口,根据url里面的信息进行相应的调用方法。

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
public class pro8 {
public static void main(String[] args) {
test tt=factory("testOne");
tt.test();
test rr=factory("testTwo");
rr.test();
}
public static test factory(String name) {
test test1 = null;
try {
//test test1=(test)Class.forName(name).newInstance();
//对于初始化对象,jdk1.9以后变成下面的了。
test1=(test)Class.forName("test."+name).getDeclaredConstructor().newInstance();
}catch(Exception e) {
e.printStackTrace();
}
return test1;
}
}
interface test{
public void test();
}
class testOne implements test{
public void test() {
System.out.println("this is testOne");
}
}
class testTwo implements test{
public void test() {
System.out.println("this is testTwo");
}
}


//输出结果
this is testOne
this is testTwo
概念
  • 反射是框架设计的灵魂
  • 反射机制就是将各个组成部分封装为其他对象。
优势
  • 可以在程序运行中,操作这些对象。
  • 可以解耦,提高程序的可扩展性
Java代码的阶段

Java代码在计算机中经历的三个阶段

  • source(源代码阶段)
    • Java文件被javac编译成class文件,class文件会将成员变量、成员方法和构造方法等编译成不同的模块。
    • class文件通过classlocad类加载器来进入和下一阶段
  • class(类对象阶段)
    • 通过classload类加载器,将class文件加载进内存,成员变量、成员方法和构造方法会被封装为class类中的对象。
  • runtime(运行阶段)
    • 当我们需要访问Java文件的成员时,我们就可以逆向访问,通过class类的classload类加载器来获取该成员的对象。
获取class对象
  • Class.forName(“全类名”):将字节码文件加载进内存,返回class对象。

    • 处于source,并未加载进内存,用于配置文件,将类名定义在配置文件中。读取文件,从而加载类。
  • 类名.class:通过类名的属性class获取。

    • 处于class,已经加载进内存,多用于参数的传递。
  • 对象.getclass():object类中定义的方法。

    • runtime,处于运行时,用于获取对象的字节码文件对象。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    testOne to=new testOne();
    //方法1:对象.getClass()
    Class clazz1=to.getClass();
    //方法2:类名.Class
    Class clazz2=testOne.class;
    //方法3:Class.forName("全类名“),这种方法就是动态加载,需要处理异常
    Class clazz3=Class.forName("test.testOne");

    //在运行期间,一个类只有一个Class对象产生,上面的三个方法只是获取对象,所以下面的都相等为true
    System.out.println(clazz1==clazz2);
    System.out.println(clazz2==clazz3);
创建对象实例

两种方法:

  • class对象调用newInstance()方法进行实例化(针对无参构造函数)
  • 调用Constructor(构造函数)对象的newInstance()方法。(针对有参函数)
1
2
3
4
5
6
7
8
//方法1
Class clazz=Class.forName("test.Person");
Person person=(Person)clazz.newInstance();
//方法2
Class clazz=Class.forName("test.Person");
//获取class对象构造方法
Constructor c=clazz.getDeclaredConstructors(String.class,String.class,int.class);
Person person=(Person)c.newInstance("李四","男",20);//实例化
获取构造方法
  • Constructor[] getConstructors(构造函数参数类型),返回一个 Constructor对象数组,反射 所有的 public构造类对象。
  • Constructor getConstructors(构造函数参数类型),返回一个 Constructor对象,反射 指定的 public构造类对象。
  • Constructor[] getDeclaredConstructor(构造函数参数类型),返回一个 Constructor对象数组,反射 所有的 构造类对象。
  • Constructor getDeclaredConstructor(构造函数参数类型),返回一个 Constructor对象,反射 指定的 构造类对象。
获取成员变量(属性)(导包 java.lang.reflect.Field)
  • Field[] getField(),返回包含一个Field对象数组,反射所有可访问的public字段类对象。
  • Field getField(String name),返回一个Field对象,反射指定的public成员字段类对象。
  • Field[] getDeclaredFields(),返回一个Field对象,反射所有可访问(public,private等)的已声明字段类对象。
  • Field getDeclaredField(String name),返回一个指定可访问(public,private等)已声明字段类对象的一个Field对象数组。
  • 既然有get那么就会有set方法,void set(Object obj,Object value),get(Object obj)
获取成员方法
  • Method[] getMethod(),返回一个Method对象数组,反射指定的public成员方法对象。

  • Method getMethod(String name),返回一个Method对象,反射指定的public成员方法对象。

  • Method[] getDeclaredMethods(),返回一个Method对象数组,反射 所有的成员方法类对象。

  • Method getDeclaredMethods(String name),返回一个Method对象,反射 所有的成员方法类对象。

#####

操作Constructor(构造方法)
  1. 无参构造方法:创建class对象,class对象调用newInstance方法(1.9以后方法有改变,合并为下面步骤2)
  2. 有参构造方法:创建class对象,class对象调用getConstructor方法获得Constructor对象,接着cons对象调用newInstance方法实例化
操作Field(成员变量)

拥有get和set方法,可以在任何时候对类对象进行改变取值。

首先class对象调用getField方法获得Field对象,然后该对象调用set或者get方法

void set(Object obj,Object value) / get(Object obj),取出来后记得值类型转换

setAccessible(true)暴力反射:忽略访问权限的安全检查,也就是说虽然Field对象可以获得所有成员变量但是私有变量其实是不能使用的,所以在获得私有成员变量对象Field后需要:

Field对象.setAccessible(true),接着就可以继续调用set或者get方法了

操作Method(成员方法)

获取Method对象:class对象调用getMethod方法

执行方法:Method对象调用invoke方法 Object invoke(Object object,Object..args)

​ Method md=clazz.getDeclaredMethod(“test”);

​ md.invoke(oo);oo是实例化对象名

小结:为了动态加载类对象,基本上全部需要先获取class对象,然后调用相应模块的方法获得该对象,接着配合实例化对象使用。因为直接的操作全部是class对象没有代入真正的对象。

下面是分界线~

  1. 基础知识
    1. 使用场景
    2. 概念
    3. 优势
  2. Java代码的阶段
  3. 获取class对象
  4. 创建对象实例
  5. 获取构造方法
  6. 获取成员变量(属性)(导包 java.lang.reflect.Field)
  7. 获取成员方法
  8. 操作Constructor(构造方法)
  9. 操作Field(成员变量)
  10. 操作Method(成员方法)
  11. 小结:为了动态加载类对象,基本上全部需要先获取class对象,然后调用相应模块的方法获得该对象,接着配合实例化对象使用。因为直接的操作全部是class对象没有代入真正的对象。