Java高级特性——反射机制内容详解并总结以及类的加载过程

本文深入探讨了Java的反射机制,解释了其在动态语言特性中的作用,以及如何通过反射创建对象、操作属性和方法。同时,文章介绍了获取Class对象的多种方式,并展示了如何访问内部类和私有成员。最后,讨论了类加载过程和类加载器的工作原理。
摘要由CSDN通过智能技术生成

来回顾总结一下,温故而知新

什么是反射机制?

解释反射机制之前首先了解一下什么是动态语言和静态语言:

静态语言:运行结构不可变的语言为静态语言,大多数为编译语言(不绝对);如Java、C、C++

动态语言:与静态语言相对,是在运行时可以改变其结构的语言,如:可创建新的函数、对象、甚至代码会被引进,已有的函数可以被删除或者是其他结构上的变化;如:Object-C、C#、JavaScript、PHP、Python、Erlang

而Java提供了一种机制,也就是反射机制,能提供类似于动态语言这一特性的相同的能力。

Java Reflection

  • Reflection是实现结构动态的关键,允许程序在执行期间借助于相关API获取任何类的内部信息,并且可以操作任何对象内部属性和方法。
  • 每一个类加载完成后,会在堆内存中方法区内产生一个与之对应的Class类型的对象,这个对象就包含了这个类完整的信息。
  • 创建对象的方式:
    • 正常方式:通过 package+class 通过关键字 new 来实例化对象
    • 反射方式:通过实例化对象.getClass()得到完整的类信息

通过Java反射机制可以完成:

  • 在运行时判断一个对象所属的类
  • 在运行时构造任何一个类的对象
  • 在运行时获取任何一个类所有成员变量和方法信息
  • 在运行时获取泛型的信息
  • 在运行时调用任何一个对象的成员变量和方法
  • 在运行时处理注解
  • 动态代理的实现

反射机制使用过程中主要相关的类:

  • java.lang.Class
  • java.lang.reflect.Method
  • java.lang.reflect.Field
  • java.lang.reflect.Constructor
  • ......

反射机制使用示例

Person(测试反射用类):

package com.leolee.reflectTest;

/**
 * @ClassName Person
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/11/23
 * @Version V1.0
 **/
public class Person {

    public String name;
    private int age;

    private Person() {
        this.name = "李四";
        this.age = 30;
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void hi() {
        System.out.println("Hi,I'm " + name);
        System.out.println(age(age));
    }

    private String age(int age) {
        return "I'm " + age + " years old";
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

Test:

package com.leolee.reflectTest;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @ClassName ReflectionTest
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/11/23
 * @Version V1.0
 **/
public class ReflectionTest {

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {

        //====================demo1====================
        Class person1Class = Person.class;

        //通过反射创建对象
        System.out.println("通过反射创建对象:");
        Constructor person1Constructor = person1Class.getConstructor(String.class, int.class);
        Object leoLee = person1Constructor.newInstance("LeoLee", 25);
        System.out.println(leoLee.toString());
        Person leoleePerson = (Person) leoLee;
        leoleePerson.hi();

        //通过反射操作属性
        System.out.println("通过反射操作属性:");
        Field name = person1Class.getDeclaredField("name");
        name.set(leoleePerson, "张三");
        System.out.println(leoLee.toString());

        //通过反射操作方法
        System.out.println("通过反射操作方法:");
        Method[] person1Methods = person1Class.getDeclaredMethods();
        System.out.println("获取所有方法名称:");
        for (int i = 0; i < person1Methods.length; i++) {
            System.out.println(person1Methods[i].getName());
        }
        System.out.println("通过反射调用方法:");
        Method hi = person1Class.getDeclaredMethod("hi");
        hi.invoke(leoleePerson);

        //通过反射调用类私有构造器
        System.out.println("通过反调用私有构造器:");
        Constructor nonArgsPrivateConstructor = person1Class.getDeclaredConstructor();//获取无参的私有构造器
        nonArgsPrivateConstructor.setAccessible(true);//禁用java的访问范围检查
        Object leolee2 = nonArgsPrivateConstructor.newInstance();
        Person leoleePerson2 = (Person) leolee2;
        System.out.println(leoleePerson2.toString());

        //通过反射调用私有的属性
        System.out.println("通过反操作私有属性:");
        Field age = person1Class.getDeclaredField("age");
        age.setAccessible(true);//禁用java的访问范围检查
        age.set(leoleePerson2, 60);
        System.out.println(leoleePerson2.toString());

        //通过反射调用私有的方法
        System.out.println("通过反调用私有方法:");
        Method agePrivateMethod = person1Class.getDeclaredMethod("age", int.class);
        agePrivateMethod.setAccessible(true);//禁用java的访问范围检查
        Object resultValue = agePrivateMethod.invoke(leoleePerson2, 70);
        System.out.println(String.valueOf(resultValue));
    }
}

运行结果:

通过反射创建对象:
Person{name='LeoLee', age=25}
Hi,I'm LeoLee
I'm 25 years old
通过反射操作属性:
Person{name='张三', age=25}
通过反射操作方法:
获取所有方法名称:
toString
getName
setName
hi
age
getAge
setAge
通过反射调用方法:
Hi,I'm 张三
I'm 25 years old
通过反调用私有构造器:
Person{name='李四', age=30}
通过反操作私有属性:
Person{name='李四', age=60}
通过反调用私有方法:
I'm 70 years old

Process finished with exit code 0

反射机制的思考

Java中强调封装,为什么又存在反射机制?

  1. 其实不矛盾,封装其实是在传达一个开发者的指引,告诉你哪些属性和方法建议你怎样使用(public、protect、default修饰),哪些不建议你使用,你要是非用不可,ok,你用反射机制
  2. 为了动态创建对象,因为有时候的业务代码场景中,无法确定到底要创建哪个对象,所以在编码期无法确定的对象创建,使用反射机制在运行期根据某些条件来确定创建和使用哪个对象

java.lang.Class

类加载过程简单如下:

  1. 程序经过javac xxx.java命令编译之后,会生成字节码文件(xxx.class)
  2. java xxx.class对编译文件进行解析运行,相当于把某个类的字节码文件加载到内存中

加载到内存中的类就是运行时类,就是java.lang.Class的一个实例,即每一个Class的实例对应一个运行时类对象

获取Class对象的方法:

//获取class的方式1:调用运行时类的属性 .class
Class<Person> personClass1 = Person.class;
System.out.println(personClass1);

//获取class的方式2:通过运行时类的getClass()方法
Person person = new Person("LeoLee", 25);
Class personClass2 = person.getClass();
System.out.println(personClass2);

//获取class的方式3:通过调用Class的forName()方法
Class personClass3 = Class.forName("com.leolee.reflectTest.Person");
System.out.println(personClass3);

//获取class的方式4:使用类加载器ClassLoader
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class personClass4 = classLoader.loadClass("com.leolee.reflectTest.Person");
System.out.println(personClass4);

System.out.println(personClass1 == personClass2);
System.out.println(personClass1 == personClass3);
System.out.println(personClass1 == personClass4);

测试结果:

class com.leolee.reflectTest.Person
class com.leolee.reflectTest.Person
class com.leolee.reflectTest.Person
class com.leolee.reflectTest.Person
true
true
true

Process finished with exit code 0

这三种获取Class对象的方式,获取的Class对象都是一样的地址,说明这三种方式都是获取了内存当中Person的运行时实例

这四种方式中,方式3和方式4更能体现反射机制的动态加载特征

Class对象包含的范围

  1. 一般类、内部类(包括静态内部类)、局部内部类、匿名内部类
  2. 接口
  3. 数组
  4. 枚举
  5. 注解
  6. 基本数据类型
  7. void(在某些方法的返回值定义为void的时候,其实void也看作是一种类型)
  8. Class本身

重点写一下反射对于内部类的操作

修改Person类,增加内部类:

package com.leolee.reflectTest;

/**
 * @ClassName Person
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/11/23
 * @Version V1.0
 **/
public class Person {

    public String name;
    private int age;

    private Person() {
        this.name = "李四";
        this.age = 30;
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void hi() {
        System.out.println("Hi,I'm " + name);
        System.out.println(age(age));
    }

    private String age(int age) {
        return "I'm " + age + " years old";
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }


    //内部类

    private class InnerA {

        private String dec;

        public InnerA(String dec) {
            this.dec = dec;
            System.out.println(dec);
        }

        private void innerAMethod(String arg) {
            System.out.println("innerAMethod execute:" + arg);
        }
    }

    public static class InnerB {
        private String dec;

        public InnerB(String dec) {
            this.dec = dec;
            System.out.println(dec);
        }

        private void innerBMethod(String arg) {
            System.out.println("innerBMethod execute:" + arg);
        }
    }

    private Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("匿名内部类执行");
        }
    };
}

测试方法:

    //内部类测试
    public static void innerClassTest() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {

        Class outterClass = Person.class;
        Constructor declaredConstructor = outterClass.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        Person person = (Person) declaredConstructor.newInstance();
        person.hi();

        Class[] declaredClasses = outterClass.getDeclaredClasses();
        for (Class c: declaredClasses) {
            //获取修饰符
            int modifier = c.getModifiers();
            String modifierString = Modifier.toString(modifier);
            System.out.println("modifierString:" + modifierString);
            if (modifierString.contains("static")) {
                //静态内部类不持有外部类的引用
                Constructor staticInnerBConstructor = c.getDeclaredConstructor(String.class);
                if (modifierString.contains("private")) {
                    staticInnerBConstructor.setAccessible(true);
                }
                Object object = staticInnerBConstructor.newInstance( "静态内部类B初始化");

                Field field = c.getDeclaredField("dec");
                if (Modifier.toString(field.getModifiers()).contains("private")) {
                    field.setAccessible(true);
                }
                System.out.println(field.get(object));

                Method innerBMethod = c.getDeclaredMethod("innerBMethod", String.class);
                if (Modifier.toString(innerBMethod.getModifiers()).contains("private")) {
                    innerBMethod.setAccessible(true);
                }
                innerBMethod.invoke(object, "hahahah");
            } else {
                // If this Class object represents an inner class declared in a non-static context, the formal parameter types include the explicit enclosing instance as the first parameter.
                Constructor innerAConstructor = c.getDeclaredConstructor(outterClass, String.class);
                if (modifierString.contains("private")) {
                    innerAConstructor.setAccessible(true);
                }
                //If the constructor's declaring class is an inner class in a non-static context, the first argument to the constructor needs to be the enclosing instance; see section 15.9.3 of The Java™ Language Specification.
                Object object = innerAConstructor.newInstance( person, "私有内部类A初始化");

                Field field = c.getDeclaredField("dec");
                if (Modifier.toString(field.getModifiers()).contains("private")) {
                    field.setAccessible(true);
                }
                System.out.println(field.get(object));

                Method innerAMethod = c.getDeclaredMethod("innerAMethod", String.class);
                if (Modifier.toString(innerAMethod.getModifiers()).contains("private")) {
                    innerAMethod.setAccessible(true);
                }
                innerAMethod.invoke(object, "hahahah");
            }
        }

        // 获取匿名内部类实例
        Field field = outterClass.getDeclaredField("runnable");
        if (Modifier.toString(field.getModifiers()).contains("private")) {
            field.setAccessible(true);
        }
        Runnable r = (Runnable) field.get(person);
        r.run();
    }

测试输出:

Hi,I'm 李四
I'm 30 years old
modifierString:public static
静态内部类B初始化
静态内部类B初始化
innerBMethod execute:hahahah
modifierString:private
私有内部类A初始化
私有内部类A初始化
innerAMethod execute:hahahah
匿名内部类执行

Process finished with exit code 0

1、由于成员内部类对象的创建依赖于外部类对象,持有指向外部类对象的引用。所以在反射构造成员内部类的时候一定要通过获取构造器再调用构造器的newInstance方法,其中必须要传入外部类的Class和实例。对于私有构造器,需要使用getDeclaredConstructor方法获取并使用setAccessible(true)来设置为可以获取的。

2、静态内部类不持有外部类的引用,所以当其提供了无参显式的构造器的时候,可以直接在调用其class的newInstance()方法获得实例。如果构造器为private的则处理同1中。如果没有提供显式的的无参构造器,只提供了有参构造器,处理也同1中。

3、如果内部类没有提供显式的构造器,则通过上面提到的方法构造内部类对象会抛出java.lang.IllegalAccessException错误。即要通过上面提到的方法使用反射机制创建内部类对象,内部类一定要提供显式的构造函数!

参考:https://blog.csdn.net/ldstartnow/article/details/52782420

类的加载过程

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

  1. 类的加载(load):将编译后的字节码文件class文件读入内存中,并为之创建一个java.lang.Class对象
    1. 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中书的访问入口(即引用地址)。
    2. 所有需要访问的和使用类数据只能通过这个class对象来获取。这个加载的过程需要类的加载器参与。
  2. 类的连接(link):将类的二进制数据合并到JRE中
    1. 验证,确保加载的类信息符合JVM的规范
    2. 准备,正式为类变量(static)分配内存并设置类变量默认初始化值,这些内存都将在方法区中进行分配
    3. 解析,虚拟机常量池内的符号引用(常量名)替换为直接引用地址
  3. 类的初始化(initialize):JVM负责对类进行初始化
    1. 执行类构造器<clinit()>方法的过程,类构造器<clinit()>方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(需要注意的是这个类构造器是构造类的信息集合,并不是类中的构造方法也不是类对象的构造器
    2. 当初始化一个类的时候,如果发现其父类还没有初始化,则需要先初始化其父类
    3. 虚拟机会保证一个类<clinit()>方法在多线程环境中被正确加锁和同步

类的加载器

类加载器的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。

类缓存:标准的JavaSE类加载器可以按要求查找类,一旦某个类被加载到类加载器中后,它将维持一段时间。JVM的垃圾回收机制可以回收这些class对象。

类加载器自上而下分为:

  • Bootstrap Classloader(引导类加载器):用C++编写,是JVM自带的类加载器,负责java平台核心库(如String类的加载),用来装载核心类库,该加载器无法直接获取
  • Extension Classloader(拓展类加载器):负责jre&lib&ext目录下的jar包或 -D java.ext.dirs 指定目录下的jar包装入工作
  • System Classloader(系统类加载器):负责java-classpath或者-D java.class.path所指的目录下的类与jar包装入工作(就是开发者自己写的类),是最常用的类加载器
  • 自定义加载器:开发者自行提供的加载器。

加载器的运行满足两点:

  • 按照 自定义加载器->System Classloader->Extension Classloader->Bootstrap Classloader的顺序检查类是否已经装载
  • 按照Bootstrap Classloader->Extension Classloader->System Classloader->自定义加载器的顺序加载类

示例:

    //类加载器测试
    public static void classloaderTest() {

        //对于开发者编写的类,实用系统类加载器
        ClassLoader classLoader = Person.class.getClassLoader();
        System.out.println("System classloader:" + classLoader);

        //获取拓展类加载器
        ClassLoader parent = classLoader.getParent();
        System.out.println("Extension classloader:" + parent);

        //无法获取引导类加载器
        //引导类加载器主要负责加载java的核心类库,无法被获取,也就无法被开发者使用
        ClassLoader parent1 = parent.getParent();
        System.out.println("Bootstrap classloader:" + parent1);
        ClassLoader classLoader1 = String.class.getClassLoader();
        System.out.println(classLoader1);
    }

运行结果:

System classloader:sun.misc.Launcher$AppClassLoader@18b4aac2
Extension classloader:sun.misc.Launcher$ExtClassLoader@1540e19d
Bootstrap classloader:null
null

充分说明Bootstrap Classloader(引导类加载器)无法获取

 

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值