Java 反射相关的知识

学前问题:

  1. 什么是反射?
  2. 反射出现的背景?
  3. 反射的具体实现原理?
  4. 反射的应用场景?

基本就是 What --- Why --- How + 场景

1. 什么是反射

反射的两层理解:

  • 1、反转创建对象的角色
  • 2、将 类结构 映射为对应的 Java 对象。

反射 Reflection:将 Java 类中的各种结构(方法、属性。构造器、类名映射为对应的 Java 对象,利用反射技术可以对一个类进行解剖,反射是框架技术的灵魂。

Oracle 官网对 Java Reflection 反射技术的一个定义:

①:Reflection is a feature in the Java programming language. It allows an executing Java program to examine or "introspect" upon itself, and manipulate internal properties of the program. For example, it's possible for a Java class to obtain the names of all its members and display them.

②:One tangible use of reflection is in JavaBeans, where software components can be manipulated visually via a builder tool. The tool uses reflection to obtain the properties of Java components (classes) as they are dynamically loaded.

①: 反射技术允许 Java 程序在运行期间对自身进行自省(introspect up it self),并操作程序内部的属性。例如 Java 类可以获取其所有的成员名称并显示他们。

反射是可以获取类的所有信息:包括字段方法构造函数等。

②:反射的实际用途是在 JavaBean中,使用反射功能动态加载类的属性。

小结:反射技术在运行期间可以获取到类的所有信息,与方法,并且支持动态地创建类的对象。

1.1 Class 对象

其实要介绍什么是反射,首先要介绍一下 Java 的核心思想 —— 面向对象编程

Java 的核心是对象,对象通过类创建,而 Class 类代表了对象的模板,也就是每个类都有一个对应的 Class 对象,通过这个 Class 来生成具体的实例。

那么对象是怎样来的?

这里的 对象 ,狭义的来说就是类的实例,而 Java 是由许多个类组成的。

类的定义很简单,一个 .java 源文件被编译成字节码文件后, JVM经过一系列过程例如验证,链接 之后 加载 Class 文件,生成 Class 模板对象,然后当我们使用 new 关键字时,生成类的实例对象。

image-20201026001105122

对象创建的方式:

1、虚拟机 JVM 加载类 Class 文件,代码中使用 new 关键字创建对象

image-20201010165508672

2、使用反射创建对象:在代码运行期对象的使用者准备对象的创建模板 --- Class 类,角色反转

这里可以将 Class 对象看作是 Java 对象的模板,并且这些 Class 对象是由 JVM 进行加载的,保证了安全性,Java 编写了对应的 Class 类。

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
                                ...
                              }

1.2 Class 对象的获取方法

使用反射时,创建类或调用类中方法的前提是获取该类对应的 Class 对象 , Java 中有三种获取方法:

  • 第一种:使用Class.forName 方法 获得传入参数的类,并对其进行初始化。
// 可以看到这个方法的入参是类名,这里是包括包名的完整类路径 例如 study01.server.basic.Iphone
// 这个方法与调用 Class.forName(className,true,currentLoader) 等效,CurrentLoader 指的是当前类的类加载器,通过这个类加载器对类进行装载
@CallerSensitive
public static Class<?> forName(String className)
  throws ClassNotFoundException {
  Class<?> caller = Reflection.getCallerClass();
  return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
  • 第二种: 使用字面量方式 .class 获取 Class
    • 例如:Class clz = String.classclz 获取了 String 对应的 Class 对象

这种方式只适用于编译器就知道的类

  • 第三种:使用类对象的实例的 getClass() 方法

那么这个前提也很明显了,就是已经创建了类对象的实例,反过来获取创建实例的模板。

2. 使用反射创建对象

反射可以获取类的属性,与方法,也包括了构造方法,那么自然也可以创建对象,而不是使用 new 关键字来创建对象。

2.1 newInstance 方法(已在 jdk9 之后被标记为过期)

获取 Class 对象之后,调用 Class.newInstance() 就可以获取对应类的实例,具体演示如下:

首先定义一个类:

class Iphone {
    public Iphone() {
    }

    private String name = "爱疯12 create by Class.newInstance";

    public Iphone(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return new StringJoiner(", ", Iphone.class.getSimpleName() + "[", "]")
                .add("name='" + name + "'")
                .toString();
    }
}

使用 Class.newInstance() 创建 Iphone 对象

public static void main(String[] args)
            throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        // 1、使用 newInstance 的方式创建对象
        Iphone ip = new Iphone();
        Class iPClazz = ip.getClass();
        Iphone ip1 = (Iphone) iPClazz.newInstance();
        System.out.println(ip.toString());
    }
}

我的项目 JDK 设置的是 11,可以看到 newInstance 已经被标记为 Deprecated 过期了

从 JDK9 这个版本开始,不推荐使用这个方法了。

image-20201026005242975

使用 Class.getConstructor().newInstance 创建 Iphone 对象(JDK11 之后推荐的方法)

    public static void main(String[] args)
            throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        // 1、对象.getClass()
        // 3. Class.forName("包名.类名") 完整类路径,不需要类存在,耦合度最低,动态性最高,配合多态使用,更加灵活
        Class iPClazz = Class.forName("study01.server.basic.Iphone");
        Iphone o1 = (Iphone) iPClazz.getConstructor().newInstance();
        System.out.println(o1);
        // 这里如果需要通过传入 name 调用带参构造创建对象可以向下面这样
    }
}

调用无参构造函数通过反射创建对象:

image-20201026005712946

调用带参构造函数通过反射创建对象:

image-20201026005834000

到这一部分为止,为了学习自己写一个 Java HTTP 服务器的反射相关知识已经够了,所以就先不继续深入了。

学习之前的问题基本上回答了什么是反射和反射的应用场景,至于具体反射实现类的源码与底层技术留到之后深入学习。

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

最是人间留不住,曾是惊鸿照影来。