Java基础(八)——反射机制

版本说明发布日期
1.0发布文章第一版2021-01-12

前言

  • 这篇文章是我个人的学习笔记,可能无法做到面面俱到,也可能会有各种纰漏。如果任何疑惑的地方,欢迎一起讨论~
  • 如果想完整阅读这个系列的文章,欢迎关注我的专栏《Java基础系列文章》~

啥子是反射机制?

  • 对于反射机制的理解,可能很多小伙伴们都是这样的:
    • 同事A:晓不晓得啥子是反射机制?
    • 同事B:大概还是晓得嘛。
    • 同事A:那你来描述一哈喃?
    • 同事B:呃…
    • 同事A:就是一种可以在运行时再确定代码怎么执行的机制。
    • 同事B:喔~~~~~~就是嘛,就这个意思!
  • 通常情况下,我们编写的代码都是固定的,无论运行多少次,代码的执行逻辑都是一样的。
  • 但在某些特殊场合中,我们在编写代码时并不确定要创建什么对象、调用什么方法。而是在运行时通过参数来决定。实现该需求的机制叫做反射机制,也叫动态编程技术。
  • 目前主流的框架底层都是采用反射机制实现的,例如Spring的IOC。

Class类

基本概念

  • 位于java.lang.Class。该类用于描述Java中的类和接口。
  • 该类没有公共构造方法,该类的实例均由JVM和类加载器(ClassLoader)自动构造。每一个被加载到内存中的类,都会被自动构造一个对应的Class实例。
    • 注意了哦,类加载器会把类的信息加载到方法区中。但是Class对象是存放在堆区中的哈。

获取Class对象的五种方式

  1. 数据类型.class。这种方式很常用,要脸熟他哦!
    • 举个栗子:
        public class ClassTest {
            public static void main(String[] args) {
                Class c = String.class;
                System.out.println(c);
                c = int.class;
                System.out.println(c);
                c = void.class;
            System.out.println(c);
            }
        }
    
    • 运行结果如下。可以发现哈,Class的toString方法比较独特。
      • 此外,小伙伴们肯定也注意到了,基本数据类型和void也是有对应的Class实例的。为什么要这样设计呢?看了下文就会明白了,因为反射机制中,很多地方是要对基本数据类型和void返回值进行操作的!
    class java.lang.String
    int
    void
    
  2. 引用/对象.getClass()。这个没啥好说的。但是注意,因为基本数据类型不是对象,所以这个方式无法获取基本数据类型的Class对象。
  3. 包装类.TYPE获取对应基本数据类型的Class对象。注意和包装类.class进行区分哦。.Type获取到的是基本数据类型的Class对象,而.class获取到的是包装类自身的Class对象。
  4. Class.forName()。这种方式也很常用,请继续脸熟~
    • 举个栗子:
    public class ClassTest {
        public static void main(String[] args) throws ClassNotFoundException {
            System.out.println(Class.forName("java.lang.String"));
        }
    }
    
    • 运行结果如下。注意喽,forName的参数必须是完整类名(也就是带上包路径的类名)。同时,这种方式也不能获取基本数据类型的Class对象。
    class java.lang.String
    
  5. 类加载器ClassLoader的loadClass方法。
    • 这个是什么意思呢?我们来看一下:
    public class ClassTest {
        public static void main(String[] args) throws ClassNotFoundException {
            ClassLoader loader = ClassTest.class.getClassLoader();
            System.out.println(loader.loadClass("java.lang.String"));
        }
    }
    
    • 执行结果还是一样。我们可以通过任何类的CLass实例来获取类加载器对象。然后用其loadClass方法,可以获取任何指定类的Class对象(也是需要提供完成类名)。除此之外,类加载器还有很多别的方法。

newInstance方法(过时)

  • newInstance方法用于实例化Class对象对应的类。但是目前该方法已经过时了,现在推荐使用Constructor来实例化(下文会讲)。
  • 但这newInstance作为传统艺能,还是需要眼熟一下滴!
//先搞一个Person类
public class Person {
    String name;

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

    public Person(){
        this.name = "莫得名字";
    }

    public void introduce() {
        System.out.println("俺是一个人,俺叫:" + this.name);
    }
}

//然后利用反射创建对象
public class ClassTest {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Scanner sc = new Scanner(System.in);
        String className = sc.next();
        Class<?> c = Class.forName(className);
        System.out.println("创建对象:" + c.newInstance());
        sc.close();
    }
}
  • 控制台内容如下。当我们输入了Person的完整类名之后,就成功调用了Person的无参构造创建了对象。从这里,小伙伴也能够感受到什么叫“动态创建对象”吧。
com.Reflection.Person
创建对象:com.Reflection.Person@5d5eef3d

Class类的其他方法

  • Class类中还有很多其他的方法,这里再列举一些稍微比较常用的。但是这里也不举例子来讲了,这些东西讲起来就稍微有点多喽(偷个小懒)。
方法声明功能
Package getPackage()获取所在的包信息。Package为描述包的类。
Class<? super T> getSuperclass()获取父类的信息。
Type getGenericSuperclass()获取带泛型的父类的信息。Type为描述带泛型的类的类。
Class<?>[] getInterfaces()获取所有接口信息。
Type[] getGenericInterfaces()获取带泛型的接口信息。
Annotation[] getAnnotations()获取所有注解信息。Annotation为描述注解的类。

Constructor类

  • 位于java.lang.reflect.Constructor。用于描述构造方法的反射。
  • Class类有几个方法可以用于获取Constructor对象:
方法声明功能
Constructor getConstructor(Class<?>… parameterTypes)获取此Class对应类中,参数所对应的公共构造方法。
Constructor getDeclaredConstructor(Class<?>… parameterTypes)获取此Class对应类中,参数所对应的构造方法。
Constructor<?>[] getConstructors()获取此Class对应类中,所有的公共构造方法。
Constructor<?>[] getDeclaredConstructors()获取此Class对应类中,所有的构造方法。

常用方法

方法声明功能
T newInstance(Object… initargs)使用对应构造方法来构造对应对象。
int getModifiers()获取方法的访问修饰符。常见的值:无修饰:0、public:1、protected:4、private:2、public final:17。此外还有很多,就不一一列举啦。
String getName()获取方法的名称。
Class<?>[] getParameterTypes()获取方法所有参数的类型。

来个栗子

//先整一个毫无创意的Person类
public class Person {
    private String name;
    private int age;

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

    public Person() {
        this.name = "莫得名字";
    }

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

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

    public void introduce() {
        System.out.println("俺是一个人,俺叫:" + this.name + "。今年" + this.age + "岁了");
    }

    private void growUp(int period) {
        age += period;
    }
}

//然后整一个毫无创新的测试类
public class ClassTest {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class<Person> perClass = Person.class;
        Constructor<Person> noParaCon = perClass.getConstructor();//无参构造
        Constructor<Person> oneParaCon = perClass.getConstructor(String.class);//有参构造

        //通过构造器构造对象
        Person per = noParaCon.newInstance();
        System.out.println("无参构造的结果:" + per);
        per = oneParaCon.newInstance("angel");
        System.out.println("有参构造的结果:" + per);
    }
}
  • 执行结果如下。
    • Class类的getConstructor方法中指定对应构造方法的参数,即可获取到对应的Constructor实例。注意了,getConstructor中的参数都是Class对象。所以就如上文提到的,基本数据类型的Class对象就可能在这里派上用场。
    • Constructor对象的newInstance方法中,需要根据对应构造方法的参数列表来传入参数。此时传入的参数就是正常的参数值了。
无参构造的结果:Person{name='莫得名字'}
有参构造的结果:Person{name='angel'}

我要全部的构造方法!

  • 很多时候,我们并不能清楚地知道将要获取哪一个构造方法。这个时候我们可能需要先把所有构造方法都拿到,然后再来慢慢挑选。
  • 此时我们就用getDeclaredConstructors来实现我们的小小心愿。这个例子还是用上面那个毫无创意的Person。
public class ClassTest {
    public static void main(String[] args) {
        Class<Person> perClass = Person.class;

        //获取所有的构造器
        Class<?>[] paraTypes;
        Constructor<?>[] cons = perClass.getDeclaredConstructors();
        for (Constructor<?> con : cons) {
            System.out.println("构造方法名称是:" + con.getName());
            System.out.println("访问权限是:" + con.getModifiers());
            paraTypes = con.getParameterTypes();
            System.out.print("参数列表是:");
            for (Class<?> paraType : paraTypes) {
                System.out.print(paraType.getName() + ", ");
            }
            System.out.println("");
            System.out.println("-------------");
        }
    }
}
  • 运行结果如下。因为用的declared获取,所以获取到了所有的构造方法。
构造方法名称是:com.Reflection.Person
访问权限是:2
参数列表是:java.lang.String, int, 
-------------
构造方法名称是:com.Reflection.Person
访问权限是:1
参数列表是:
-------------
构造方法名称是:com.Reflection.Person
访问权限是:1
参数列表是:java.lang.String, 
-------------

Field类

  • 位于java.lang.reflect.Field。用于描述单个成员变量的反射。
  • 和Constructor类似,Class类中有也有对应的获取Field实例的方法。同理,带declared获取所有的,不带declared就是只获取公共的。
方法声明功能
Field getDeclaredField(String name)获取此Class对应类中,参数指定的单个成员变量
Field[] getDeclaredFields()获取此Class对应类中,所有成员变量

常用方法

方法声明功能介绍
Object get(Object obj)从obj中获取此Field对象对应的成员变量的数值。
void set(Object obj, Object value)将obj中此Field对象对应的成员变量的数值修改为value。
void setAccessible(boolean flag)设置是否对访问权限进行检查。true代表不检查,false代表检查。
int getModifiers()获取成员变量的访问修饰符。值与Constructor类的相同。
Class<?> getType()获取成员变量的数据类型。
String getName()获取成员变量的名称。

又来个栗子

//Person还是那个Person,这里就省略了。直接整测试类。
public class FieldTest {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Class<?> c = Person.class;
        Field field = c.getDeclaredField("name");

        //为了直接步入主题,我就不用反射获取Class对象了
        Person per = new Person();

        //因为name是私有变量,所以我们需要关闭访问控制校验。否则无法get或者set
        field.setAccessible(true);

        //这里又涉及到多态了,运行时将会调用String的toString方法,而不是Object类的
        System.out.println(field.getName() + "的值为:" + field.get(per)+"。该变量的类型是:" + field.getType().getName());

        //修改一下变量值
        field.set(per, "大帅哥");
        System.out.println(field.getName() + "的值为:" + field.get(per)+"。该变量的类型是:" + field.getType().getName());
    }
}
  • 运行结果如下。我很想习惯性地说点什么,但是这个例子太简单了,实在是不知道说啥。获取所有的Field我就不测啦,一回事儿!
name的值为:莫得名字。该变量的类型是:java.lang.String
name的值为:大帅哥。该变量的类型是:java.lang.String

Method类

  • 位于java.lang.reflect.Method。用于描述单个成员方法的反射。
  • Class类中对应的获取方法如下。
方法声明功能
Method getMethod(String name, Class<?>… parameterTypes)获取该Class对应类中,名字为name,参数为parameterTypes的公共成员方法。
Method[] getMethods()获取该Class对应类中,所有公共成员方法。包括从父类继承的方法。
Method[] getDeclaredMethods()获取该Class对应类中,所有成员方法。不包括从父类继承的方法。

常用方法

方法声明功能
Object invoke(Object obj, Object… args)调用对象obj中对应的方法,args为实参列表。
int getModifiers()获取方法的访问修饰符。值与Constructor类的相同。
void setAccessible(boolean flag)设置是否对访问权限进行检查。true代表不检查,false代表检查。
Class<?> getReturnType()获取方法的返回值类型。
String getName()获取方法的名称。
Class<?>[] getParameterTypes()获取方法所有参数的类型。
Class<?>[] getExceptionTypes()获取方法的异常抛出类型。

双来个栗子

//老规矩,Person省略
public class MethodTest {
    public static void main(String[] args) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        //和Field的例子不同的是,我这里用反射来获取Person对象吧
        Class<?> c = Person.class;
        Constructor<?> constructor = c.getDeclaredConstructor(String.class, int.class);
        //因为这个构造方法是私有的,需要取消访问检查
        constructor.setAccessible(true);
        Object object = constructor.newInstance("小美女", 24);

        //获取introduce方法
        Method m1 = c.getDeclaredMethod("introduce");

        //调用方法
        m1.invoke(object);
    }
}
  • 运行结果如下。到这里,小伙伴们都能发现了,无论是Constructor、Filed还是Method。反射机制的这几个家伙,几乎都是大同小异。所以,不会吧?不会吧?不会真有人还不能触类旁通吧?
俺是一个人,俺叫:小美女。今年24岁了

获取子类方法时,有无declared的区别

  • 按规矩来说,这一节应该讲获取所有方法的例子,但是光讲这个就太没意思了。所以我决定整一个子类,然后获取其所有方法,看看方法数组中有哪些父类方法:
//整一个子类,类体是空的,直接继承Person。Person还是和上面的一样。
public class Woman extends Person {
}

//测试类
public class MethodTest {
    public static void main(String[] args) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        MethodTest.multi();
    }

    private static void multi() {
        Class<?> c = Woman.class;

        //获取方法
        Method[] methods = c.getMethods();

        //遍历获取到的所有方法
        for (Method method : methods) {
            System.out.println("方法名称为:" + method.getName() + "。访问控制符为:" + method.getModifiers() + "。返回值类型为:" + method.getReturnType().getName());
            System.out.println();
        }
    }
}
  • 执行结果如下。
    • 可以看到,除了获取到了Person类的方法,还获取到了很多Object类的。
    • getModifiers还出现了几个新值:273:public final native、257:public native。
方法名称为:toString。访问控制符为:1。返回值类型为:java.lang.String
方法名称为:introduce。访问控制符为:17。返回值类型为:void
方法名称为:wait。访问控制符为:17。返回值类型为:void
方法名称为:wait。访问控制符为:17。返回值类型为:void
方法名称为:wait。访问控制符为:273。返回值类型为:void
方法名称为:equals。访问控制符为:1。返回值类型为:boolean
方法名称为:hashCode。访问控制符为:257。返回值类型为:int
方法名称为:getClass。访问控制符为:273。返回值类型为:java.lang.Class
方法名称为:notify。访问控制符为:273。返回值类型为:void
方法名称为:notifyAll。访问控制符为:273。返回值类型为:void
  • 如果把Method[] methods = c.getMethods();改为Method[] methods = c.getDeclaredMethods();,则执行结果会变为如下。对,就是一个方法都没有哈哈哈!这一点在上面的方法表格中也说到了,因为Woman类中确实一个方法都没有声明。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值