Java反射详解

反射:框架设计的灵魂

  • 框架:半成品软件。可以在框架的基础上进行软件开发,简化编码。
  • 反射:将类的各个组成部分封装为其他对象,这就是反射机制。

好处:

  1. 可以在程序运行过程中,操作这些对象。
  2. 可以解耦,提高程序的可扩展性。

反射机制有什么用?

  1. 通过java语言中的反射机制可以操作字节码文件(class文件)。
  2. 运行时分析类的能力。
  3. 在运行时检查对象,例如,编写一个适用于所有类的 toString 方法。
  4. 实现泛型数组操作代码。
  5. 利用 Method 对象,这个对象很像C++中的函数指针。

反射机制的相关类在哪个包下?
java.lang.reflect.*;
反射机制相关的重要的类有哪些?
java.lang.class:代表整个字节码,代表-一个类型, 代表整个类。
java.lang.reflect.Method:代表字节码中的方法字节码。代表类中的方法。
java.lang.reflect.Constructor:代表字节码中的构造方法字节码。代表类中的构造方法。
java.lang.reflect.Field:代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。

Class类

​ 在程序运行期间,Java运行时系统始终为所有对象维护一个运行时类型标志。这个信息会跟踪每个对象所属的类。虚拟机利用运行时类型信息选择要执行的正确的方法。

​ 可以使用特殊的Java类访问这些信息。保存这些信息的类名为ClassObject类中的 getClass()将会返回一个Class类型的实例。

个人理解:运行时类型标志:每个对象都以各自的(类)模板创建对象,在下图中Class类对象阶段各个类的类对象包含了每个类的成员变量、构造方法、成员方法等信息。虚拟机运行时会利用这些类对象的信息创建实例对象,并根据这些信息结合实际对象执行相应对象的方法。每个实例对象可以通过 实例对象.getClass() 获得其对应的类对象。每个实例对象可能都有一个“指针”指向其对应的类对象。

要操作一个类的字节码,需要首先获取到这个类的字节码,怎么获取java.lang.Class实例?:

  1. Class.forName("className") :将字节码文件加载进内存,返回Class对象
    (多用于配置文件,将类名定义在配置文件中。读取文件,加载类)

    className:如果类在一个包里,包的名字也作为类名的一部分。

    如果className是一个类名或接口名,这个方法可以正常执行。否则,Class.forName("className")方法将抛出一个检查型异常(checked exception)。无论何时使用这个方法,都应该提供一个异常处理器(exception handler)。

    Class.forName() 发生了什么?

    重点:

    如果你只希望一个类的静态代码块执行,其他代码一律不执行,

    可以使用: Class.forName("完整类名");

    这个方法的执行会导致类加载,类加载时,静态代码块执行。

    在启动时,包含main方法的类被加载。它会加载所有需要的类。这些被加载的类又需要加载他们需要的类,以此类推。对于一个大型的应用程序来说,这将花费很长的时间,用户会因此感到不耐烦。可以使用下面这个技巧给用户一种启动速度比较快的假象。不过,要确保包含main方法的类没有显示的引用其他的类。首先,显示一个启动的画面;然后,通过调用Class.forName手工的加载其他类。

  2. T.class:通过类名的属性class获取

    (多用于参数的传递)

    T是任意Java类型(或void关键字),T.class将代表匹配的类对象。

    一个Class对象实际和是哪个表示的是一个类型,可能是类,也可能不是类。(例如,int不是类,但int.class是一个Class类型的对象。)

  3. 实例对象.getClass() : getClass()方法在0bject类中定义着。
    (多用于对象的获取字节码的方式)

Class对象描述了一个特定类的属性:成员变量、构造方法、成员方法。

java语言中任何一种类型,包括基本数据类型,它都有 .class 属性。

java语言中任何一个对象都有一个方法:getClass()。因为Object类中含有getClass()方法,所有类都直接或间接继承自Object类。

Class类实际上是一个泛型类。在大多数问题中,可以忽略类型参数,而使用原始的Class类。

public static void main(String[] args) throws ClassNotFoundException {
    //1.Class. forName("全类名")
    Class cls1 = Class.forName("cn.mycompany.mypackage.Person");
    System.out.println(cls1);// class cn.mycompany.mypackage.Person
    //2.类名.class
    Class cls2 = Person.class;
    System.out.println(cls2);// class cn.mycompany.mypackage.Person
    //3.对象.getClass()
    Person person = new Person() ;
    Class cls3 = person.getClass();
    System.out.println(cls3);// class cn.mycompany.mypackage.Person
    System.out.println(cls1==cls2==cls3)// true  ==判断的是对象的内存地址
}

结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次, 不论通过哪一种方式获取的Class对象都是同一 个。

虚拟机为每一个类型管理一个唯一的Class对象。因此,可以利用 == 运算符实现两个类对象的比较。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OsHGOEzJ-1614328619734)(E:\Typora\images\image-20210224181337077.png)]

利用反射分析类的能力

反射机制最重要的内容——检查类的结构。

Class对象功能
获取功能:

  1. 获取成员变量

    Field[] getFields()//获取所有public修饰的成员变量
    Field getField(String name)//获取指定名称的public修饰的成员变量
    Field[] getDeclaredFields()//获取所有的成员变量,不考虑修饰符
    Field getDeclaredField(String name)//获取指定名称的成员变量,不考虑修饰符
    
  2. 获取构造方法

    Constructor<T>[] getConstructors()//获取所有public修饰的构造方法
    Constructor<T> getConstructor(Class<T>... parameterTypes)//获取指定参数类型的public修饰的构造方法
    Constructor<T>[] getDeclaredConstructors()//获取所有构造方法
    Constructor<T> getDeclaredConstructor(Class<T>... parameterTypes)//获取指定参数类型的构造方法
    
  3. 获取成员方法

    Method[] getMethods()//获取本类及其父类所有被public修饰的方法
    Method getMethod(String name,Class<T>... parameterTypes)//获取指定参数类型的被public修饰的方法
    Method[] getDeclaredMethods()//获取本类及其父类所有方法
    Method getDeclaredMethod(String name,Class<T>...parameterTypes)//获取指定参数类型的方法
    
  4. 获取类名

    String getName()//获取完整类名
    String getSimpleName()//获取简类名
    

    鉴于历史原因,getName方法在应用于数组类型的时候会返回有些奇怪的名字:

    • Double[].class.getName() 返回 “[Ljava.lang.Double;”
    • int[].class.getName() 返回"[I"

Field:成员变量

​ 操作:

  1. 设置值
void set(object obj object value)
  1. 获取值
get(object obj)
  1. 忽略访问权限修饰符的安全检查
setAccessible(true):暴力反射
public static void main(String[] args) throws Exception {
    //获取Person的Class对象
    Class personClass = Person.class; 

    //1. Field[] getFields()获取所有public修饰的成员变量
    Field[] fields = personClass.getFields();
    for (Field field : fields) {
        System.out.println(field);
    }
    //2.Field getField(String name)
    Field fieldVar = personClass.getField("fieldVar");
    //获取成员变量fieldVar的值
    Person person = new Person();
    Object value = fieldVar.get(person);
    System.out.println(value);
    //设置fieldVar的值
   	fieldVar.set(person,"joker");
    System.out.println(person);
    
    //Field[] getDeclaredFields(): 获取所有的成员变量,不考虑修饰符
    Field[] declaredFields = personClass.getDeclaredFields();
    for (Field declaredField : declaredFields) {
        System.out.println(declaredField);
    }
    //Field getDeclaredField(String name)
    Field privateFieldVar = personClass.getDeclaredField("privateFieldVar");
    //忽略访问权限修饰符的安全检查
    privateFieldVar.setAccessible(true);//暴力反射
    value = privateFieldVar.get(person);
    System.out.println(value);
}

Constructor:构造方法
创建对象:

T newInstance(object... initargs)

newInstance方法相当于C++中的虚拟构造器概念。

如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法

public static void main(String[] args) throws Exception {
    //获取Person的Class对象
    Class personClass = Person.class; 
    
    Constructor constructor = personClass.getConstructor(String.class,int.class);
	System.out.println( constructor);
    //创建对象
    Object person = constructor.newInstance(...initargs:... );
    System.out.println(person);
    
    Constructor constructor = personClass.getConstructor() ;
    System.out.println(constructor);
    //创建对象
    person = constructor.newInstance();
    System.out.println(person);
    
    person = personClass.newInstance();
    //底层调用的是该类型的无参数构造方法,如果没异常有无参数构造方法会出现“实例化”异常
    System.out.println(person) ;
}

有一个已经废弃的 Class.toInstance 方法,它也可以用无参数构造器构造一个实例。不过,如果构造器抛出一个检查型异常,这个异常将不做出任何检查重新抛出。这违反了编译时异常检查的原则。与之不同,Constructor.newInstance会把所有的构造器异常包装到一个InvocationTargetException中。

异常分类:

  • 非检查型(unchecked)异常

    很多常见,例如:越界错误或者访问null引用,都属于非检查型异常。编译器并不希望你为这些异常提供处理器。程序员应该集中精力避免这些错误的发生,而不是将经历花在编写异常处理器上。

  • 检查型(checked)异常

    对于检查型异常,编译器将会检查程序员是否知道这个异常并做好准备来处理后果。

Method :方法对象
执行方法:

Object invoke(object obj,object... args)

获取方法名称:

String getName
public static void main(String[] args) throws Exception {
    //获取Person的Class对象
    Class personClass = Person.class; 
    
    //获取指定名称的方法
    Method eat_method = personClass.getMethod("eat");
    Person person = new Person(); 
    //执行方法
    eat_method.invoke(person);
    eat_method = personClass.getMethod( name: "eat",String.class);
    //执行方法
    eat_method.invoke(p,"葡萄");
    //获取所有public修饰的方法
    Method[] methods = personClass.getMethods();//获取该类及其父类所有被public修饰的方法
    for (Method method : methods) {
        System.out.println(method) ;
        String name = method.getName( );
        System.out.println(name);
        //method.setAccessible(true); 暴力反射
    }
    
    //获取类名
	String className = personClass. getName( ) ;
	System.out.println(className);//全类名 cn.mycompany.mypackage.Person
}

​ 在java.lang.reflect包中的三个类 Field、Method、Constructor分别用于描述类的字段、方法、构造器。

这三个类都有一个叫做 getName 的方法,用来返回字段、方法或构造器的名称。

在Field类有一个 getType 方法用来返回描述字段类型的一个对象,这个对象的类型同样是Class。

Method和Constructor有报告参数类型的方法,Method类还有一个报告返回类型的方法。

这三个类都有一个名为getModifiers的方法,它将返回一个整数,用不同的0/1位描述所使用的修饰符,如public和static。可以利用java.lang.reflect包中的Modifier类的静态方法分析getModifiers返回的这个整数,利用Modifier.toString方法返回修饰符的字符串形式。

​ Class类中的 getFieldsgetMethodsgetConstructors 方法将分别返回这个类支持的公共字段、方法、构造器数组,其中包裹超类的公共成员。Class类的getDeclaredFieldsgetDeclaredMethodsgetDeclaredConstructors方法将分别返回类中声明的全部字段、方法和构造器的数组,其中包括私有成员、包成员和受保护成员,但不包括超类的成员。

案例
需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法。
实现:

  1. 配置文件
  2. 反射

步骤:

  1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
  2. 在程序中加载读取配置文件
  3. 使用反射技术来加载类文件进内存
  4. 创建对象
  5. 执行方法
className=cn.mycompany.mypackage.Person
methodName=eat
public class ReflectDemo{//不改变该类的任何代码可以创建任意类的对象,执行任意方法。
    public static void main(String[] args){
        //1.加载配置文件
        //1.1创建Properties对象
        Properties properties = new Properties();
        //1.2加载配置文件,转换为一个Map集合
        //1.2.1获取class目录下的配置文件
        ClassLoader classLoader = ReflectDemo.class.getClassLoader();
        InputStream inputStream = classLoader.getResourceAsStream("reflectDemo.properties");
        properties.load(inputStream);
        //2.获取配置文件中定义的数据
        String className = properties.getProperty("className");
        String methodName = properties.getProperty("methodName");
        //3.加载该类进内存
        Class cls = Class.forName(className);
        //4.创建对象
        Object obj = cls.newInstance();
        //5.获取方法对象
        Method method = cls.getMethod(methodName);
        //6.执行方法
        method.invoke (obj);
    }
}

验证了反射机制的灵活性。
java代码写一遍,再不改变java源代码的基础之上,可以做到不同对象的实例化。非常灵活。( 符合OCP开闭原则:对扩展开放,对修改关闭。)
后期学习高级框架,而工作过程中也都是使用高级框架,
包括: ssm ssh
Spring SpringMVC MyBatis
Spring Struts Hibernate
这些高级框架底层实现原理:都采用了反射机制,所以反射机制还是重要的。
学会了反射机制有利于你理解剖析框架底层的源代码。

类加载器

关于JDK中自带的类加载器:
什么是类加载器?
ClassLoader,专门负责加载类的命令/工具。
JDK中自带了3个类加载器:

  • 启动类加载器 rt. jar
  • 扩展类加载器 ext/*. jar
  • 应用类加载器 classpath

假设有这样一段代码:String s = "flow" ;
代码在开始执行之前,会将所需要类全部加载到JVM当中。通过类加载器加载,看到以上代码类加载器会找String.class文件,找到就加载,那么是怎么进行加载的呢?
  首先通过“启动类加载器”加载。
  注意:启动类加载器专门加载:C:\Program Files\Java\jdk1 .8.0_101\jre\lib\rt.jar
  rt.jar中都是JDK最核心的类库。
  如果通过“启动类加载器”加载不到的时候,会通过"扩展类加载器"加载。
  注意:扩展类加载器专门加载: C: \Program Files\Java\jdk1.8.0_101\jre\lib\ext.jar
  如果"扩展类加载器"没有加载到,那么会通过“应用类加载器"加载。
  注意:应用类加载器专门加载:classpath中的类。

java中为了保证类加载的安全,使用了双亲委派机制
优先从启动类加载器中加载,这个称为“父"。”父"无法加载到,再从扩展类加载器中加载,这个称为“母"。双亲委派。如果都加载不到,才会考虑从应用类加载器中加载。直到加载到为止。

Java反射技术详解

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值