JAVA进阶——反射

第二篇博客,是关于JAVA中的反射。文章中的图表、代码均为原创,如需转载,请注明出处。

 

一、JAVA中的反射的基本概念

1.1 什么是反射

JAVA中的反射是指,在运行状态中,对于任意一个实体类,都能够知道这个类的所有属性和方法的一种机制;也就是说,对于任意一个对象,我们都有手段能够调用它的任意方法和属性,无论这个方法和属性是public的还是private的;通常地,我们需要创建一个对象的时候,必定知道它是什么类的对象,知道这个类是用来干啥的。我们可以直接用new关键字对这个类进行实例化。而反射则恰恰相反,一开始我们并不知道我要初始化的类是什么,而是在在运行时才知道要操作的类是什么,并且可以使用 JDK 提供的反射 API 在运行时获取类的几乎所有信息。

1.2 反射的主要作用

其实上一节也大概提到了反射的作用,JAVA反射机制允许我们在运行时才去加载、使用一个编译期间完全未知的类,并且能够获取这个类的几乎所有信息。总结一下,大致有以下几点:

  1. 在运行时构造任意一个类的对象
  2. 在运行时获取任意一个类所具有的所有成员变量和方法
  3. 在运行时调用任意一个对象的方法、属性等信息

 

二、反射的基本用法

在学习反射用法之前,需要明确一些问题。首先,我们都知道,在面向对象的思想里,万物皆为对象,我们在代码中写的每一个类其实也都是一个对象,它们都是java.lang.Class的对象。比如说有一个Person类,一个Animal类,一个Book类,这些都是不同的类,但是也有共性:它们应该都有类名,属性,方法,构造器等等,现在我们可以用一个类来描述这些类,也就是java.lang.Class。

2.1 获取Class对象的几种方式

1.通过类名获取      类名.class    

2.通过对象获取      对象名.getClass()

3.通过全限定名获取    Class.forName(全限定名)

全限定名指的是:包名.类型,如以下代码所示:

package com.example.javalib;

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

    public Person() {
    }

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

    public void sayHello() {
        System.out.println("hello!");
    }
}
 
package com.example.javalib;
public class MainClass {
    public static void main(String[] args) throws ClassNotFoundException {
        Person personOne = new Person();
        Class personClass = Person.class;//1.通过类名获取      类名.class
        Class aClass = personOne.getClass();//2.通过对象获取   对象名.getClass()
        Class aClass2 = Class.forName("com.example.javalib.Person");//3.通过全限定名获取    Class.forName(全限定名)
    }
}

2.2 Class中比较重要的一些方法

除了上面所说的获取Class对象的几种方法之外,还有一些比较重要的用法需要关注。

1、获取Class对象的实例

通常,我们获取一个类的对象,是直接new出来,学习了反射之后,我们也可以使用Class类中的newInstance方法来获取“Class对象的对象”:

package com.example.javalib;
public class MainClass {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Person personOne = new Person();//通过new关键字实例化一个Person对象
        Class aClass = Class.forName("com.example.javalib.Person");
        Person personTwo = (Person)aClass.newInstance();//通过newInstance来实例化Person对象
        //两个对象都可以成功调用sayHello方法
        personOne.sayHello();
        personTwo.sayHello();
    }
}

上面是我们通过无参的newInstance获取到的Class对象的实例。我们可以看到,通过new创建的对象和通过newInstance方法创建的Person对象都可以成功调用Person类中的sayHello方法。

2、获取Class对象所表示的实体名称

我们可以用getName方法来获取Class对象所表示的实体名称:

package com.example.javalib;
public class MainClass {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class aClass = Class.forName("com.example.javalib.Person");
        String className = aClass.getName();
        System.out.println(className);
    }
}

3、 获取当前Class对象的父类的Class对象

我们可以使用getSuperClass方法来获取当前Class对象的父类的Class对象:

package com.example.javalib;
public class MainClass {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class aClass = Class.forName("com.example.javalib.Person");
        String className = aClass.getName();
        Class superclass = aClass.getSuperclass();
        Object o = superclass.newInstance();
    }
}

4、获取当前Class对象实现的接口

我们让Person实现Serializable接口,然后尝试用getInterfaces方法来获取当前Class对象继承的接口:

package com.example.javalib;
public class MainClass {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class aClass = Class.forName("com.example.javalib.Person");
        Class superclass = aClass.getSuperclass();
        Class[] interfaces = aClass.getInterfaces();
        for (Class anInterface : interfaces) {
            System.out.println(anInterface.getName());
        }
    }
}

由上图可以看出,我们通过getInterfaces方法成功获取了Person所实现的接口。

 

5、获取Class对象的构造方法

使用getConstructors可以获得Class对象的的所有构造方法,如果要获取某个特定的构造方法,需要给getConstructors方法的传入对应的参数类型,并且再拿到构造方法后,我们自然也就能够调用用newInstance来创建对象:

package com.example.libjava;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

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

        Class<?> aClass = Class.forName("com.example.libjava.Person");
        //获取Class对象的所有构造方法
        Constructor<?>[] constructors = aClass.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println("Constructor is: "+constructor);
        }
        //只获取特定的构造方法
        Constructor<?> constructor = aClass.getConstructor( String.class,int.class);
        System.out.println("Specific Constructor:"+constructor);
        //使用构造器创建对象
        Person person = (Person) constructor.newInstance("Marry", 22);
    }

}

上面的代码获取了当前Class对象构造方法,不知道大家有没有注意到一个比较有意思的地方,我们在获取指定构造方法的时候,getConstructor的参数传的是getConstructor(String.class,int.class);

有人可能会问,int明明是一种基本数据类型,并不是一个类,为什么这里可以传入int.class而不是Integer.class呢?我们先来看看getConstructor方法的参数列表:

    @CallerSensitive
    public Constructor<T> getConstructor(Class<?>... var1) throws NoSuchMethodException, SecurityException {
        this.checkMemberAccess(0, Reflection.getCallerClass(), true);
        return this.getConstructor0(var1, 0);
    }

我们可以看到,这里需要传入的是Class类型的对象,这也就意味着int.class也是一个Class对象,不过这和我们常用的 类名.class 来获取一个Class的方法有所出入,因为int根本不是一个类,按理说不应该支持int.class这种写法,所以就比较奇怪,我们在Class类的官方文档中似乎找到了答案:

关于标蓝的部分,我个人的理解是,虽然基本数据类型不是类或接口,但依然可以有一个Class对象来代表他们,算是一种特例吧。同时,在经典书籍《JAVA核心技术》第五章 5.7节也指出“一个Class对象实际表示的是一种类型,而这个类型未必一定是一个类。”,以下是内容节选:

6、反射中其他关键API

除了上面所列举出的几个常用方法,反射还可以通过以下方法来获取Class对象的方法、属性等更多信息,用法和前面类似,本文就不再一一赘述,将常见用法整理成下表:

2.3 使用反射来执行方法

上文中说到,我们可以利用反射的一些API来获得构造器、方法、属性等,现在我们尝试使用反射来执行一个方法:

package com.example.libjava;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

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

        Class<?> aClass = Class.forName("com.example.libjava.Person");
        //获取Class对象的指定构造器
        Constructor<?> constructor = aClass.getConstructor(String.class, int.class);
        //实例化一个对象
        Object marry = constructor.newInstance("Marry", 25);
        //获取sayhello方方法
        Method sayHello = aClass.getMethod("sayHello");
        //用实例化的marry对象执行sayhello方法
        sayHello.invoke(marry);
    }

}

上述代码中,我们先用forName获得了Class对象,然后使用getConstructor(String.class, int.class)获得的了指定参数列表的构造方法,接着使用newInstance("Marry", 25),实例化了一个对象,接着使用getMethod("sayHello", null)获取了sayhello这个方法,最后使用invoke对方法进行了执行,invoke方法中的第一个参数需要传入实例化的对象,从第二个参数起,需要传入准备调用方法的参数类型,这个案例中sayhello没有形参,所以没传。需要注意的是,如果这里我们准备调用的sayhello方法是一个private的方法,或者需要访问private的属性,都需要先使用sayHello.setAccessible(true);才可以通过invoke来执行。

三、反射真的就那么无敌好用吗?

从上文中,我们可以感受到反射的功能十分强大,它可以使程序变得十分灵活,避免把代码“写死”的情况。然而,我们知道,凡事有利必有弊,反射也是一把双刃剑,正是由于它过于“灵活”,就像开了挂一样,能够无视原有的访问修饰符限制,去访问类的几乎所有信息,所以也有人认为反射会一定程度上破坏代码的封装性,同时也会有一定的安全隐患。而且,由于反射是在运行时期才临时去获取、使用类的属性方法,导致反射操作的效率要比正常操作效率低很多,我们应该避免滥用反射,特别是在对性能要求很高的程序以及经常被执行的代码中,应当尽量避免使用反射。

总结

上面是我学习JAVA泛型后整理的部分内容,如有错误还请各位大神指出,邮箱hbutys@vip.qq.com


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值