欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

【Java】 RTTI 与 反射

程序员文章站 2022-07-10 21:29:51
文章目录前言反射反射机制一.基本操作获得对象的类名获得基本属性的类型获得类的类名获得类的父类获取类的构造方法调用构造方法RTTI前言刚开始学习Java的时候,我就只是随便看了一点点,根本没有深入了解,反射这一章也是一点没看,因为我觉得可能反射不是那么重要,现在发现反射真的太重要了。很多优秀的开源框架都是通过反射完成的,反射被称为框架设计的灵魂。反射的重要性不言而喻,但是如果刚开始接触反射,完全不了解反射到底是干嘛的。我们也不要着急,反射入门还是比较简单。看了这篇文章,相信你就会对反射有了一个大概的了解。...

前言

刚开始学习Java的时候,我就只是随便看了一点点,根本没有深入了解,反射这一章也是一点没看,因为我觉得可能反射不是那么重要,现在发现反射真的太重要了。很多优秀的开源框架都是通过反射完成的,反射被称为框架设计的灵魂。反射的重要性不言而喻,但是如果刚开始接触反射,完全不了解反射到底是干嘛的。我们也不要着急,反射入门还是比较简单。看了这篇文章,相信你就会对反射有了一个大概的了解。
记录一下学习笔记,希望可以对大家有所帮助。

在Java中,想要在运行时识别对象和类的信息,主要有两种方式:

  • RTTI   它假定了我们在编译时已经知道了所有的类型
  • 反射    它允许我们在运行时发现和使用类的信息

反射

先看一下网上关于反射的概念:

Java反射就是在运行状态中,
对于任意一个类,都能够知道这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意方法和属性;
并且能改变它的属性。而这也是Java被视为动态语言的一个关键性质。

Java反射的功能是在运行时判断任意一个对象所属的类,
在运行时构造任意一个类的对象,
在运行时判断任意一个类所具有的成员变量和方法,
在运行时调用任意一个对象的方法,生成动态代理。

其实这么说还是比较让人懵逼的。我初学的时候,将反射先简单的理解为:反射就是获取类的信息
学习新技术的时候不要一直纠结在一点上,有的时候遇到这种不好理解的定义先在心中有一个大致的概念,然后继续往前看,有了一定了解之后回头再看就比较容易理解。

一.反射机制

我们每天都在和代码打交道,一份代码从我们在键盘上输入,到运行结束得到结果,中间过程经历了什么,估计大部分人的回答就是:编辑java文件,经过javac编译,然后就直接创建对象了。
这比我还好一点,因为我之前都不知道java文件还要经过javac编译。

我们总是忽略了中间的Class类对象阶段
我们看一下下面这个图
【Java】 RTTI 与 反射
需要了解一下,Source 源代码阶段是在磁盘上进行的,而Class 类对象阶段是在内存中进行的。
将类的组成部分封装为其它对象,这就是反射机制,也就是Class类对象阶段所做的事情,主要有三件事:

  • 将类的成员变量封装成Filed对象
  • 将类的构造方法封装成Constructor对象
  • 将类的成员方法封装成Method对象

反射机制:将类的组成部分封装为其它对象
这么说是不是对反射有了一定的了解了呢?

如果还是迷迷糊糊的状态,我们再来看一下Java反射机制的一个应用,相信你了解了之后,肯定会说:“卧槽,反射这么牛逼?”

我们在编程的时候,几乎一直在接触反射,只是我太菜了一直没有发现。举一个简单的例子
【Java】 RTTI 与 反射
现在大家使用的编译器都会有自动提示的功能,比如我们定义一个字符串s,操作s的时候发现,编译器会将s的操作方法自动显示出来,使用的时候我们不必一个一个找方法了,非常快捷方便,但是大家有没有想过这些方法是如何得到的呢?

我没有学习反射之前肯定会说:String类中有这些方法,s是String的对象,自然可以操作这些方法。没有毛病啊!

但是我们的关注重点不是这个,而是编译器如何知道有这些方法的存在呢? 这就用到了反射的知识。
【Java】 RTTI 与 反射
我们定义了一个字符串之后,系统会将字符串的字节码文件加载进内存,在内存中,有一个Class类对象,Class类对象将String的所有方法都抽取出来作为Method的对象并放入Method[] 数组中,使用时,将Method[] 数组中的成员抽取出来,显示在列表中即可。这就是一个反射的过程。

反射的好处:
     (1) 可以在程序的运行过程中操作这些对象
     (2) 可以解耦(降低程序耦合性),提高程序的可扩展性。

二.获取Class对象的三种方式

经过前面的解释,相信你对反射有了一定的了解,
【Java】 RTTI 与 反射
获取Class 对象的三种方式:

  1. Class.forName(“全类名”) 将字节码文件加载进内存,返回Class对象
    • 多用于配置文件,将类名定义在配置文件中,读取类名,加载类
    • 比如JDBC
  2. 类名.class 类名的属性.class 来获取
    • 多用于参数传递
  3. 对象.getClass() getClass()方法在Object中
    • 多用于对象的获取字节码的方式
      有这样一个Person类:
package com.ahmu.yx.reflect;

public class Person {
    private String name;
    private int age;

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

    public Person(){
        
    }

    @Override
    public String toString() {
        return super.toString();
    }

    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;
    }
}

获取Class对象:

package com.ahmu.yx.reflect;

public class ReflectClass {
    public static void main(String[] args) throws Exception {
        //1.Class.forName("全类名")
        Class c1 = Class.forName("com.ahmu.yx.reflect.Person");
        System.out.println(c1);

        //类名.class
        Class c2 = Person.class;
        System.out.println(c2);

        //对象.getClass()
        Person p = new Person();
        Class c3 = p.getClass();
        System.out.println(c3);

        //比较三个对象
        System.out.println("c1 == c2: " + (c1 == c2));
        System.out.println("c2 == c3: " + (c2 == c3));
        System.out.println("c3 == c1: " + (c3 == c1));
    }
}

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

这是一个静态方法,通过类名直接调用
Class.forName(“全类名”)

三种方式对应文件的三种不同状态

三.使用Class对象

前面说过了如何获取Class 对象,那么我们应该如何使用呢?

获取功能

获取成员变量

有这样一个X类,其中有四个成员变量a,b,c,d,分别有着不同的访问修饰权限

class X {
    public String a;
    protected String b;
    String c;
    private String d;
}

获取其成员变量的方法:

  • Field[] getFields()
    获取所有public修饰的成员变量

    Class xClass = X.class;
    
    //获取所有public修饰的成员变量
    Field[] fields = xClass.getFields();
    for (Field field : fields) {
        System.out.println("使用getFields获得的成员变量:" + field);
    }
    
  • Field getField(String name)
    获取指定名称的 public修饰的成员变量

    //获取名称为a的public修饰的成员变量
    Class xClass = X.class;
    Field a = xClass.getField("a");
    System.out.println("获取名称为a的public修饰的成员变量:" + a);
    
  • Field[] getDeclaredFields()
    获取所有的成员变量,不考虑修饰符

    //获取所有成员变量,不考虑修饰符
    Class xClass = X.class;
    Field[] declaredFields = xClass.getDeclaredFields();
    System.out.println("获取所有成员变量:");
    for (Field field : declaredFields){
        System.out.println(field);
    }
    
  • Field getDeclaredField(String name)
    获取指定名称的 public修饰的成员变量
    (若要访问非public属性需要忽略访问权限修饰符的安全检查)
    d.setAccessible(true);//暴露反射

    //获取单个成员变量,不限制修饰符,(需要访问非public属性需要忽略访问权限修饰符的安全检查)
    Class xClass = X.class;
    Field d = xClass.getDeclaredField("d");
    //访问非public属性需要忽略访问权限修饰符的安全检查
    d.setAccessible(true);//暴露反射
    Object dValue = d.get(x);
    System.out.println(dValue);
    

既然已经获取了成员变量,如何操作(设置)成员变量呢?

  • 设置值 : void set(Object obj, Object value)
  • 获取值 : get(Object obj)
//获取成员变量a 的值
X x = new X();
Object aValue = a.get(x);
System.out.println("成员变量a的值为:" + aValue);//因为字符串默认值是null,所以结果是null


//获取成员变量a 的值
X x = new X();
Object aValue =  a.get(x);
System.out.println("成员变量a的值为:" + aValue);//因为字符串默认值是null,所以结果是null

完整代码:

package com.ahmu.yx.reflect;

import java.lang.reflect.Field;

public class TestClass {
    public static void main(String[] args) throws Exception {
        /**
         1. 获取成员变量
         Field[] getFields() :获取所有public修饰的成员变量
         Field getField(String name)   获取指定名称的 public修饰的成员变量
         Field[] getDeclaredFields()  获取所有的成员变量,不考虑修饰符
         Field getDeclaredField(String name)
         */
        Class xClass = X.class;

        //获取所有public修饰的成员变量
        Field[] fields = xClass.getFields();
        for (Field field : fields) {
            System.out.println("使用getFields获得的成员变量:" + field);
        }

        //获取名称为a的public修饰的成员变量
        Field a = xClass.getField("a");
        System.out.println("获取名称为a的public修饰的成员变量:" + a);

        //获取成员变量a 的值
        X x = new X();
        Object aValue = a.get(x);
        System.out.println("成员变量a的值为:" + aValue);//因为字符串默认值是null,所以结果是null

        //设置成员变量a的值
        a.set(x, "旭哥");
        System.out.println("修改a的值为:" + x.a);

        //获取所有成员变量,不考虑修饰符
        Field[] declaredFields = xClass.getDeclaredFields();
        System.out.println("获取所有成员变量:");
        for (Field field : declaredFields) {
            System.out.println(field);
        }

        //获取单个成员变量,不限制修饰符,(需要访问非public属性需要忽略访问权限修饰符的安全检查)
        Field d = xClass.getDeclaredField("d");
        //访问非public属性需要忽略访问权限修饰符的安全检查
        d.setAccessible(true);//暴露反射
        Object dValue = d.get(x);
        System.out.println(dValue);
    }
}

class X {
    public String a;
    protected String b;
    String c;
    private String d;
}

获取构造方法

有这样一个类,有四种不同访问权限的构造方法。

class Y{
    public Y(String name, int age){}
    protected Y(String  name){}
    Y(int age){}
    private Y(){}
}

获取构造方法:

  • Constructor<?>[] getConstructors()
    获取全部public构造方法
//获取全部public构造方法
Class yClass = Y.class;
Constructor[] constructors = yClass.getConstructors();
System.out.println("获取全部public构造方法:");
for (Constructor constructor : constructors){
    System.out.println(constructor);
}
  • Constructor getConstructor(类<?>… parameterTypes)
    根据参数类型获取构造方法获取public构造方法,不包括private权限
//根据参数类型获取构造方法获取public构造方法,不包括private权限
Class yClass = Y.class;
Constructor constructor = yClass.getConstructor(String.class, int.class);
System.out.println("根据参数类型获取构造方法,不包括private权限");
System.out.println(constructor);

  • Constructor<?>[] getDeclaredConstructors()
    获取全部构造方法,包括private权限
//获取全部构造方法,包括private权限
Class yClass = Y.class;
Constructor[] allCons = yClass.getDeclaredConstructors();
System.out.println("获取全部构造方法,包括private权限");
for (Constructor con :allCons){
    System.out.println(con);
}
  • Constructor getDeclaredConstructor(类<?>… parameterTypes)
    根据参数类型获取构造方法,包括private权限
//根据参数类型获取构造方法,包括private权限
Class yClass = Y.class;
Constructor allCon = yClass.getDeclaredConstructor();
System.out.println("获取private权限构造方法:");
System.out.println(allCon);

上面的代码就是如何获取构造方法

创建对象
我们知道,构造方法是为了用来创建对象的。那么如何通过反射来创建对象的方法呢?
其实很简单,调用newInstance(构造器参数)即可

Class yClass = Y.class;
Constructor constructor = yClass.getConstructor(String.class, int.class);
Object y = constructor.newInstance("旭哥", 18);

如果是使用空参构造方法创建对象,可以进行简化,直接使用Class中的newInstance()方法即可

Object o = yClass.newInstance();
System.out.println(o);

由于只是让我们知道概念,Y类只有四个构造器,没有属性也没有方法,构造器只是参数不同,大家需要使用的时候做出相应改变就可以了。

完整代码:

package com.ahmu.yx.reflect;

import java.lang.reflect.Constructor;

public class GetConstructor {
    public static void main(String[] args) throws Exception {
        /**
         2. 获取构造方法
         Constructor<?>[] getConstructors()
         Constructor<T> getConstructor(类<?>... parameterTypes)
         Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
         Constructor<?>[] getDeclaredConstructors()**
         */
        Class yClass = Y.class;

        //获取全部public构造方法
        Constructor[] constructors = yClass.getConstructors();
        System.out.println("获取全部public构造方法:");
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }

        //根据参数类型获取构造方法获取public构造方法,不包括private权限
        Constructor constructor = yClass.getConstructor(String.class, int.class);
        System.out.println("根据参数类型获取构造方法,不包括private权限");
        System.out.println(constructor);

        //获取全部构造方法,包括private权限
        Constructor[] allCons = yClass.getDeclaredConstructors();
        System.out.println("获取全部构造方法,包括private权限");
        for (Constructor con : allCons) {
            System.out.println(con);
        }

        //根据参数类型获取构造方法,包括private权限
        Constructor allCon = yClass.getDeclaredConstructor();
        System.out.println("获取private权限构造方法:");
        System.out.println(allCon);

        //创建对象
        Object y = constructor.newInstance("旭哥", 18);
        System.out.println(y);
    }
}

class Y {

    public Y(String name, int age) {}

    protected Y(String name) {}

    Y(int age) {}

    private Y() {}
}

获取成员方法:

有了前面的解释,相信对这个也并不陌生了。
但是获取成员方法的目的自然是为了调用这个方法,Method类中有invoke()方法 来执行这个方法

invoke(Object obj, Object... args) 

而如果想要获得方法名称,就使用

getName() 

还有需要注意的是,如果是获取private方法,需要使用

method.setAccessible(true);//支持暴露反射

有这样一个Z类:

class Z {
    String x;
    String y;

    public void a(){
        System.out.println("a()");
    }
    public void a(String x) {
        System.out.println("a(" + x +")");
    }

    protected void b() {
        System.out.println("b()");
    }

    void c() {
        System.out.println("c()");
    }

    private void d(String y) {
        System.out.println("d(" + y + ")");
    }
}
  • Method[] getMethods()
    获取并调用public方法

    //获取并调用public方法
    Method method1 = zClass.getMethod("a");//获取方法,方法名为a
    System.out.println(method1);
    method1.invoke(z);//调用方法
    
    
    Method method2 = zClass.getMethod("a", String.class);//获取方法,方法名为a,参数为String类型
    System.out.println(method2);
    method2.invoke(z, "hello");
    

    【Java】 RTTI 与 反射

  • Method getMethod(String name, 类<?>… parameterTypes)
    获取所有public方法,包括继承的父类方法

    //获取所有方法,包括继承的父类方法
    Class zClass = Z.class;
    Method[] methods = zClass.getMethods();
    for (Method method : methods){
        System.out.println(method);
    }
    

    【Java】 RTTI 与 反射

  • Method[] getDeclaredMethods()
    和之前类似,如果想要获得private方法,需要使用
    method.setAccessible(true);来支持暴露反射

  • Method getDeclaredMethod(String name, 类<?>… parameterTypes)
    和前面的代码类似

    完整代码:

    package com.ahmu.yx.reflect;
    
    import java.lang.reflect.Method;
    
    public class GetMethod {
        public static void main(String[] args) throws Exception {
            /**
             2. 获取构造方法
             Method[] getMethods()获取public方法
             Method getMethod(String name, 类<?>... parameterTypes)获取所有public方法
             Method[] getDeclaredMethods()获取方法,包括private方法
             Method getDeclaredMethod(String name, 类<?>... parameterTypes)获取所有方法
             */
            Class zClass = Z.class;
            Z z = new Z();
    
            //获取并调用public方法
    
            Method method1 = zClass.getMethod("a");//获取方法,方法名为a
            System.out.println(method1);
            method1.invoke(z);//调用方法
    
    
            Method method2 = zClass.getMethod("a", String.class);//获取方法,方法名为a,参数为String类型
            System.out.println(method2);
            method2.invoke(z, "hello");
    
    
            //获取所有方法,包括继承的父类方法
            Method[] methods = zClass.getMethods();
            for (Method method : methods) {
                System.out.println(method);
            }
        }
    }
    
    class Z {
        String x;
        String y;
    
        public void a() {
            System.out.println("a()");
        }
    
        public void a(String x) {
            System.out.println("a(" + x + ")");
        }
    
        protected void b() {
            System.out.println("b()");
        }
    
        void c() {
            System.out.println("c()");
        }
    
        private void d(String y) {
            System.out.println("d(" + y + ")");
        }
    }
    

获取全类名

  • String getName()

下面先看几个小例子帮助理解,当你有一定了解时再回头看就更容易理解。

四.扩展操作

获得基本属性的类型

基本类型都有type属性,可以得到这个基本类型的类型

public class ClassType {
    public static void main(String[] args) {
        Class c1 = Boolean.TYPE;
        Class c2 = Integer.TYPE;
        Class c3 = Float.TYPE;
        Class c4 = Double.TYPE;
        Class c5 = Byte.TYPE;
        Class c6 = Character.TYPE;

        System.out.println("  Boolean.TYPE: " + c1.getName());
        System.out.println("  Integer.TYPE: " + c2.getName());
        System.out.println("    Float.TYPE: " + c3.getName());
        System.out.println("   Double.TYPE: " + c4.getName());
        System.out.println("     Byte.TYPE: " + c5.getName());
        System.out.println("Character.TYPE: " + c6.getName());
    }
}

【Java】 RTTI 与 反射

获得类的父类

getSuperclass()

public class ParentClass {
    public static void main(String[] args) {
        String name = "java.io.BufferedReader";
        Class c = null;
        Class cp = null;
        try {
            c = Class.forName(name);
            cp = c.getSuperclass();
            System.out.println(c.getName());
            System.out.println(cp.getName());

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

【Java】 RTTI 与 反射
用法都是差不多的

五.实际操作

前面说了这么多,你可能还是不是很明白到底有什么用呢?
我们看下面这个案例:
写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
我们可以先在src目录下新建一个配置文件,名叫pro.properties
内容如下:
className是要创建的类的全路径,methodName是要执行的方法名

className=com.ahmu.yx.reflect.a.Student
methodName=sleep

通过读取配置文件的内容,然后通过反射机制来创建类的对象与执行方法。


配置文件创建好了之后直接新建一个类:ReflectTest,代码如下:

package com.ahmu.yx.reflect.a;


import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;

/**
 * 框架类
 */
public class ReflectTest {
    public static void main(String[] args) throws Exception {
        //可以执行任意类的对象,可以执行任意方法
        //前提:不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法

        //1.加载配置文件
        Properties pro = new Properties();//创建Properties对象
        //获得类加载器
        ClassLoader classLoader = ReflectTest.class.getClassLoader();//加载配置文件,转换为一个集合
        InputStream is = classLoader.getResourceAsStream("pro.properties");//读取pro.properties文件并返回文件对应的字节流
        pro.load(is);

        //2.获取配置文件中定义的数据
        String className = pro.getProperty("className");//返回一个字符串的className,等会创建的对象
        String methodName = pro.getProperty("methodName");//返回一个字符串的methodName,等会执行的方法


        //3.加载该类进内存
        Class c = Class.forName(className);

        //4.创建对象
        Object o = c.newInstance();

        //5.获取方法对象
        Method method = c.getMethod(methodName);

        //6.执行方法
        method.invoke(o);
    }
}

现在修改配置文件中的内容就可以执行不同的方法而一点也不需要修改类中的代码,比如配置文件中的Student类:

package com.ahmu.yx.reflect.a;

public class Student {

    public void sleep(){
        System.out.println("sleep...");
    }
}

运行程序,得到的结果为:
【Java】 RTTI 与 反射

RTTI

RTTI: RunTime Type Information,即运行时类型信息
这个我过两天,准备先去看看元注解之后再来补充

推荐几篇文章
Java反射技术详解
为什么要反射?
Java基础 -反射

本文地址:https://blog.csdn.net/weixin_45468845/article/details/107362154

相关标签: Java