JavaSE 第十五章 Java反射机制

15.1 Java反射机制概述

  • Reflection(反射)被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
  • 加载完类之后,在堆内存的方法区中产生了一个Class类的对象(一个类只会有一个Class对象),这个对象就包含了完整的类的结构信息。我们可 以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看 到类的结构,所以,我们形象的称之为:反射。

正常的方式:引入“包类”名称 ——> new关键字实例化 ——> 获得实例化对象
反射的方式:实例化对象 ——> getClass()方法 ——> 得到完整的“包类”名称

15.1.1 动态语言和静态语言

动态语言

是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构

主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。

静态语言

静态语言 与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java、C、 C++。 补充:动态语言 vs 静态语言 Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动 态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。 Java的动态性让编程的时候更加灵活!

15.1.2 反射的功能

  • Java反射机制提供的功能
    • 在运行时判断任意一个对象所属的类
    • 在运行时构造任意一个类的对象
    • 在运行时判断任意一个类所具有的成员变量和方法
    • 在运行时获取泛型信息
    • 在运行时调用任意一个对象的成员变量和方法
    • 在运行时处理注解
    • 生成动态代理

15.1.3 反射相关类

  • 反射相关的主要类
    • java.lang.Class:代表一个类
    • java.lang.reflect.Method:代表类的方法
    • java.lang.reflect.Field:代表类的成员变量
    • java.lang.reflect.Constructor:代表类的构造器

15.2 Class类

15.2.1 概述

Class类是用来描述类的类,如我们创建的或使用的Person类、Student类、Object类、String类等等都属于一类事物,而描述它们就使用到了Class类。

在Object类中定义了以下的方法,此方法 将被所有子类继承:

  • public final Class getClass() :返回值的类型是一个Class类, 此类是Java反射的源头,实际上所谓反射 从程序的运行结果来看也很好理解,即: 可以通过对象反射求出类的名称。

15.2.2 常用方法

  • static Class forName(String name): 返回指定类名 name 的 Class 对象
  • Object newInstance(): 调用缺省构造函数,返回该Class对象的一个实例
  • getName(): 返回此Class对象所表示的实体(类、接口、数组类、基本类型 或void)名称
  • Class getSuperClass(): 返回当前Class对象的父类的Class对象
  • Class [] getInterfaces(): 获取当前Class对象的接口
  • ClassLoader getClassLoader(): 返回该类的类加载器
  • Class getSuperclass(): 返回表示此Class所表示的实体的超类的
  • Class Constructor[] getConstructors(): 返回一个包含某些Constructor对象的数组
  • Field[] getDeclaredFields(): 返回Field对象的一个数组
  • Method getMethod(String name,Class … paramTypes) :返回一个Method对象,此对象的形参类型为paramType
  • 全部的构造器
    • public Constructor[] getConstructors():返回此 Class 对象所表示的类的所有public构造方法。
    • public Constructor[] getDeclaredConstructors():返回此 Class 对象表示的类声明的所有构造方法。
  • Constructor类中:
    • 取得修饰符: public int getModifiers();
    • 取得方法名称: public String getName();
    • 取得参数的类型:public Class[] getParameterTypes();

示例:

package b_reflect.constructor;

public class Person {
    //只写构造方法
    public Person() {
        System.out.println("Person的无参构造执行了,对象产生了~");
    }

    protected Person(int age) {
        System.out.println("Person的带 " + age + " 参构造执行了,对象产生了~");
    }


    Person(String name) {
        System.out.println("Person的带 " + name + " 参构造执行了,对象产生了~");
    }

    private Person(String name, int age) {
        System.out.println("Person的带 " + name + " , " + age + " 参构造执行了,对象产生了~");
    }
}


public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        // 获取Person类的Class对象
        Class<?> clazz = Class.forName("cn.pdsu.edu.reflect.Person");

        // 获取Person类的实例
        Person person = (Person) clazz.newInstance();

        // 获取类中的所有的构造方法
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor declaredConstructor : declaredConstructors) {
            System.out.println("declaredConstructor = " + declaredConstructor);
        }

        System.out.println("=====================");


        // 获取指定的构造方法
        // 无参构造
        Constructor d1 = clazz.getDeclaredConstructor();
        System.out.println("d1 = " + d1);

        System.out.println("====================");

        // 有参构造,一个为int类型的参数
        Constructor d2 = clazz.getDeclaredConstructor(int.class);
        System.out.println("d2 = " + d2);
        System.out.println("=========================");

        // 有参构造,参数为String类型
        Constructor d3 = clazz.getDeclaredConstructor(String.class);
        System.out.println("d3 = " + d3);

        System.out.println("========================");

        // 有参构造,连个参数
        Constructor d4 = clazz.getDeclaredConstructor(String.class, int.class);
        System.out.println("d4 = " + d4);
        System.out.println("========================");

        // 调用对应的构造方法对象创建Person类实例
        // 无参构造创建对象
        Person p1 = (Person) d1.newInstance();
        System.out.println("p1 = " + p1);

        System.out.println("==================");

        // 参数为int类型的构造创建对象
        Person p2 = (Person) d2.newInstance(22);
        System.out.println("p2 = " + p2);
        System.out.println("====================");

        // 参数为String类型的构造创建对象
        Person p3 = (Person) d3.newInstance("张三");
        System.out.println("p3 = " + p3);
        System.out.println("===========================");

        // 参数为String和int的构造方法创建对象
        Person p4 = (Person) d4.newInstance("张三", 22);         // 报错位置
        System.out.println("p4 = " + p4);
        System.out.println("=====================");
    }
}
  • 在Person类中存在一个私有的构造方法,所以,按照上面的代码写的去运行会报错:java.lang.IllegalAccessException 非法访问异常,我们需要在访问私有修饰的构造方法前加上一句代码:d4.setAccessible(true);

  • 全部的Field

    • public Field[] getFields() :返回此Class对象所表示的类或接口的public的Field。
    • public Field[] getDeclaredFields() :返回此Class对象所表示的类或接口的全部Field。
  • Field方法中:

    • public int getModifiers(): 以整数形式返回此Field的修饰符
    • public Class getType(): 得到Field的属性类型
    • public String getName(): 返回Field的名称。

示例:

public class Teacher {
    public String name;
    protected int age;
    String gender;
    private double salary;
}


public class Demo1 {
    public static void main(String[] args) throws Exception{
        // 获取类的字节码文件对象
        Class<?> clazz = Class.forName("cn.pdsu.edu.reflect.field.Teacher");

        // 创建类对象
        Teacher teacher = (Teacher) clazz.newInstance();

        // 获取类中所有的属性
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println("declaredField = " + declaredField);
        }
        System.out.println("========================");

        // 获取指定的属性,方法内参数为属性名
        Field name = clazz.getDeclaredField("name");
        // 为属性设置值,两个参数
        // Object obj 是要为哪个对象的属性赋值
        // Object value 是属性值
        name.set(teacher , "张三");

        // 获取属性值,参数为要获取哪个对象的name属性值
        String name1 = (String) name.get(teacher);
        System.out.println("name1 = " + name1);
        System.out.println("========================");

        // 获取属性age
        Field age = clazz.getDeclaredField("age");
        // 设置属性值
        age.set(teacher , 22);
        // 获取属性值age
        int age1 = (int) age.get(teacher);
        System.out.println("age1 = " + age1);
        System.out.println("========================");

        // 获取属性gender
        Field gender = clazz.getDeclaredField("gender");
        // 设置属性值
        gender.set(teacher , "男");
        // 获取属性值gender
        String gender1 = (String) gender.get(teacher);
        System.out.println("gender1 = " + gender1);
        System.out.println("========================");

        // 获取属性salary
        Field salary = clazz.getDeclaredField("salary");
        // 设置属性值,由于salary属性是私有的,所以需要先设置其为可访问的
        salary.setAccessible(true);
        salary.set(teacher , 12000);
        // 获取属性值salary
        double salary1 = (double) salary.get(teacher);
        System.out.println("salary1 = " + salary1);
    }
}

  • 全部的方法
    • public Method[] getDeclaredMethods():返回此Class对象所表示的类或接口的全部方法
    • public Method[] getMethods() :返回此Class对象所表示的类或接口的public的方法
  • Method类中:
    • public Class getReturnType():取得全部的返回值
    • public Class[] getParameterTypes():取得全部的参数
    • public int getModifiers():取得修饰符
    • public Class[] getExceptionTypes():取得异常信息

示例:

public class Student {
    public void study(){
        System.out.println("学生对象在学习~");
    }

    protected void eat(String food){
        System.out.println("学生对象在吃 " + food);
    }

    String sleep(){
        System.out.println("sleep方法执行了");
        return "学生对象在休息~";
    }

    private String playGame(String name){
        System.out.println("学生对象在玩 " + name);
        return "真好玩~";
    }
}


public class Demo1 {
    public static void main(String[] args) throws Exception{
        // 获取类的字节码文件对象
        Class clazz = Class.forName("cn.pdsu.edu.reflect.method.Student");

        // 创建Student类对象
        Student student = (Student) clazz.newInstance();

        // 获取类中的所有的成员方法
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println("declaredMethod = " + declaredMethod);
        }
        System.out.println("=========================");

        // 获取类中是定的成员方法
        // 连个参数分别为:方法名、参数的类型
        // 无形参方法
        Method study = clazz.getDeclaredMethod("study");
        // 方法对象调用invoke()方法,表明调用了该方法
        // 两个参数,第一个为调用该方法的对象,第二各位参数列表
        study.invoke(student) ;
        System.out.println("=========================");

        // 无返回值,一个参数的方法
        Method eat = clazz.getDeclaredMethod("eat", String.class);
        // 调用方法
        eat.invoke(student , "米饭");
        System.out.println("=========================");

        // 有返回值,无参数
        Method sleep = clazz.getDeclaredMethod("sleep");
        // 调用方法并接收返回值
        Object invoke = sleep.invoke(student);
        System.out.println("invoke = " + invoke);
        System.out.println("=========================");

        // 有返回值,有参数
        Method playGame = clazz.getDeclaredMethod("playGame", String.class);

        // playGame()方法是私有的,先设置权限
        playGame.setAccessible(true);

        Object invoke1 = playGame.invoke(student, "王者荣耀");
        System.out.println("invoke1 = " + invoke1);

        System.out.println("=========================");
    }
}

  • 关于setAccessible()方法的使用
    • Method和Field、Constructor对象都有setAccessible()方法。
    • setAccessible启动和禁用访问安全检查的开关。
    • 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
      • 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被 调用,那么请设置为true。
      • 使得原本无法访问的私有成员也可以访问
    • 参数值为false则指示反射的对象应该实施Java语言访问检查。

15.3 类加载与ClassLoader

15.3.1 类的加载过程

  • 当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过 如下三个步骤来对该类进行初始化。

类的加载(Load):将类的class文件读取内存,并为之创建一 个java.lang.Class对 象。此过程由类加载 器完成
——>
类的链接(Link):将类的二进制数 据合并到JRE中
——>
类的初始化(Initialize):JVM负责对类 进行初始化

  • 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时 数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问 入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的 过程需要类加载器参与。
  • 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
    • 验证:确保加载的类信息符合JVM规范
    • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存 都将在方法区中进行分配。
    • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
  • 初始化:
    • 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信 息的,不是构造该类对象的构造器)。
    • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类 的初始化。

15.3.2 类加载的作用

  • 类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方 法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为 方法区中类数据的访问入口。
  • 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器 中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。

15.3.3 ClassLoader类

类加载器ClassLoader,它的作用是将类装载进内存的,JVM规定了如下类型的加载器:

  • 引导类加载器:用C++编写的,是JVM自带的类 加载器,负责Java平台核心库,用来装载核心类 库。该加载器无法直接获取

  • 扩展类加载器:负责jre/lib/ext目录下的jar包或 – D java.ext.dirs 指定目录下的jar包装入工作库

  • 系统类加载器:负责java –classpath 或 –D java.class.path所指的目录下的类与jar包装入工 作 ,是最常用的加载器

  • 常用方法
    • static ClassLoadergetSystemClassLoader() :返回用于委派的系统类加载器。
    • ClassLoadergetParent() :返回父类加载器进行委派。
    • InputStreamgetResourceAsStream(String name) :返回用于读取指定资源的输入流。

实例:

public class Demo3 {
    public static void main(String[] args) throws ClassNotFoundException, IOException {
        // 获取系统类加载器
        ClassLoader classloader = ClassLoader.getSystemClassLoader();
        System.out.println(classloader);

        // 获取系统类加载器的父类加载器,即扩展类加载器
        ClassLoader classloaderParent = classloader.getParent();
        System.out.println(classloaderParent);

        // 获取扩展类加载器的父类加载器,即引导类加载器
        ClassLoader classloaderParentParent = classloaderParent.getParent();
        System.out.println(classloaderParentParent);    // null

        // 获取当前类由哪个类加载器进行加载
        Class clazz = Class.forName("_classloader.Demo3");
        ClassLoader classLoader = clazz.getClassLoader();
        System.out.println(classLoader);

        // 获取Object类的类加载器
        ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader();
        System.out.println(classLoader1);   // null

        // 获取类路径下指定文件的输入流
        InputStream is = Class.forName("_classloader.Demo3").getClassLoader().getResourceAsStream("_classloader\\file.properties");

        byte[] bytes = new byte[1024] ;
        int len = is.read(bytes);
        System.out.println(new String(bytes , 0 , len));
    }
}

你可能感兴趣的