学过框架的必看—Java反射

反射作为 Java 的高级特性,很多框架中都用到了反射的知识,如 Spring,Hibernate等,通过配置就可以动态干预程序的运行,那么什么是反射呢?

要想理解 Java 反射,首先要弄清类的加载过程,比如这行代码 Person p = new Person();,我们想要创建一个 Person 对象,并用 p 作为对象的引用。在 Java 虚拟机会先执行类的加载,然后才生成对象(分配内存空间)。在类的加载过程中,类加载器负责把类编译好的 class (字节码)文件加入到内存中,并创建一个 Class 对象,这个对象是类 Class 的实例。也就是说,上面的一行的代码看似只是创建了一个 Person 对象,但是如果是第一次使用该类,也即类加载器还未把该类的 class 文件加载到内存中时,还会创建一个 Class 对象。

在 Java 中,一切都是对象。类是对一类对象的抽象,类是一个概念,而类本身也是一种对象,在 Java 中,它们是 Class 类的对象,当然方法、属性、注解也分别是 Method、Field、Annotation 的对象。这些类都是和反射相关的类,在 java.lang.reflect 包中可以找到。

我们怎么能干预程序运行期做的事情呢?比如创建一个在编译期不能确定的类。

我们用反射就可以做到了,反射是在运行期获取自身的信息,比如某个类的信息,从而可以动态的创建某个类的对象。上面的问题其实就是一个类在编译期是未知的,在运行期才能知道它究竟是什么类。JDBC 操作数据库第一步加载数据库驱动, Class.forName("com.mysql.jdbc.Driver");,这里是 MySQL 数据库,假如某一天我们想换成 Oracle 数据库,你可能会修改 forName() 方法中的参数为 Oracle 数据库驱动名。但其实我们还可以在不修改代码的情况下实现修改,就像大多数框架那样,我们可以使用个写个配置文件,forName() 方法中的参数在配置文件中动态读取,编译好的代码完全不用动,这里其实就是反射的一个应用。另外在我们写代码时,在对象后面敲一个 . ,IDE(如 Eclipse) 就会自动帮我们列出该对象有的方法,这里其实就是IDE使用了反射,通过对象找到该类对应的 Class 对象,从而就可以找到类中的属性和方法。

1、获取 Class 对象的三种方法

从上面的介绍中,可以看到这个 Class 对象是我们使用反射的关键,而得到这个对象有下面三种方式。

1、调用 Class 类的 forName() 静态方法 public static Class forName(String className)

Class.forName("Person")

上面 JDBC 那里就是使用的这种方法。

2、调用类的隐藏类属性 class。

Person.class

3、使用对象来获取,调用祖先类 Object 中的方法,public final native Class getClass()

Person p = new Person();
p.getClass();

无论是否显式声明继承,所有类都直接或间接继承自 Object。

推荐使用第二种方式来获取 Class 对象,因为在编译期就会检查该类是否存在,更加安全,并且因为没有方法调用,使用的是属性,所以性能也更高。

2、Class 对象中的方法

可以说我们得到了 Class 对象,就得到了这个类的所有信息了。想一想我们编写的类中有什么信息?如下面这个类

如果我们得到了 Class 对象,我们就得到了这个对象的一切信息,包括构造方法、属性、方法、注解。

1、获取属性的方法

public Field getField(String name)
public Field[] getFields()
public Field getDeclaredField(String name)
public Field[] getDeclaredFields()

name 为属性的名称

2、获取构造函数的方法

public Constructor getConstructor(Class... parameterTypes)
public Constructor[] getConstructors()
public Constructor getDeclaredConstructor(Class... parameterTypes)
public Constructor[] getDeclaredConstructors()

构造器在 Java 中也有对应的类 Constructor,parameterTypes 为参数的 Class 对象,这是一个可变参数。

3、获取普通函数的方法

public Method getMethod(String name, Class... parameterTypes)
public Method[] getMethods()
public Method getDeclaredMethod(String name, Class... parameterTypes)
public Method[] getDeclaredMethods()

name 为方法的名称,parameterTypes 为参数的 Class 对象,这是一个可变参数。

4、获取注解的方法

public boolean isAnnotation()
public  A getAnnotation(Class annotationClass)
public Annotation[] getAnnotations()
public  A getDeclaredAnnotation(Class annotationClass)
public Annotation[] getDeclaredAnnotations()
public  A[] getAnnotationsByType(Class annotationClass)
public  A[] getDeclaredAnnotationsByType(Class annotationClass)

annotationClass 为对应的注解类。

5、其他常用方法

public String getName() // 返回 Class 对象表示的类型(类、接口、数组或基本类型)的完整路径名字符串
public T newInstance() // 此方法是 Java 语言 instanceof 操作的动态等价方法
public ClassLoader getClassLoader() // 获取该类的类加载器
public Class getSuperclass() // 返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class
public boolean isArray() // 如果 Class 对象表示一个数组则返回 true, 否则返回 false
public boolean isInterface() // 判定指定的 Class 对象是否表示一个接口类型
public boolean isPrimitive() // 判定指定的 Class 对象是否表示一个 Java 的基本类型

下面是测试代码:

Animal 类

public class Animal {
    public String name;
    protected String sex;
    private int age;

    public Animal() {

    }

    public void run() {
        System.out.println("动物在跑");
    }
}

Dog 类

public class Dog extends Animal{
    public String color;
    protected String heart;
    private String unknown;

    public Dog() {

    }

    private Dog(String color) {
        this.color = color;
    }

    @Override
    public void run() {
        super.run();
        System.out.println("狗在跑");
    }

    void test() {
        System.out.println("test方法");
    }

    private void fanzhi() {
        System.out.println("生小狗");
    }
}

AnimalTest 类

import org.junit.Test;

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

public class AnimalTest {
    @Test
    // 构造方法
    public void constructorTest() throws NoSuchMethodException {
        Class dog = Dog.class;
        System.out.println("获取指定的public构造方法");
        System.out.println(dog.getConstructor(String.class));
        System.out.println("获取全部的public构造方法");
        Constructor[] constructors1 = dog.getConstructors();
        for(Constructor constructor : constructors1) {
            System.out.println(constructor);
        }
        System.out.println("获取指定的构造方法,不受访问修饰符的限制");
        System.out.println(dog.getDeclaredConstructor(String.class));
        System.out.println("获取全部的构造方法,不受访问修饰符限制");
        Constructor[] constructors2 = dog.getDeclaredConstructors();
        for(Constructor constructor : constructors2) {
            System.out.println(constructor);
        }
    }

    @Test
    // 属性
    public void filedTest() throws NoSuchFieldException {
        Class dog = Dog.class;
        System.out.println("获取指定的public属性");
        System.out.println(dog.getField("color"));
        System.out.println("获取全部的public属性");
        Field[] fields1 = dog.getFields();
        for(Field field : fields1) {
            System.out.println(field);
        }
        System.out.println("获取指定的属性,不受访问修饰符的限制");
        System.out.println(dog.getDeclaredField("unknown"));
        System.out.println("获取全部的属性,不受访问修饰符限制");
        Constructor[] constructors2 = dog.getDeclaredConstructors();
        Field[] fields2 = dog.getDeclaredFields();
        for(Field field : fields2) {
            System.out.println(field);
        }
    }

    @Test
    // 方法
    public void mothodTest() throws NoSuchMethodException {
        Class dog = Dog.class;
        System.out.println("获取指定的public方法");
        System.out.println(dog.getMethod("run"));
        System.out.println("获取全部的public方法");
        Method[] methods1 = dog.getMethods();
        for(Method method: methods1) {
            System.out.println(method);
        }
        System.out.println("获取指定的方法,不受访问修饰符的限制");
        System.out.println(dog.getDeclaredMethod("fanzhi"));
        System.out.println("获取全部的属性,不受访问修饰符限制");
        Method[] methods2 = dog.getDeclaredMethods();
        for(Method method: methods2) {
            System.out.println(method);
        }
    }

    @Test
    // 其他测试
    public void otherTest() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        Class dogClass = Dog.class;
        Dog dog = (Dog) dogClass.newInstance();
        // 调用私有方法
        Method fanzhi = dogClass.getDeclaredMethod("fanzhi");
        System.out.println(fanzhi.isAccessible()); // 是否取消了权限检查
        fanzhi.setAccessible(true); //  取消权限检查
        System.out.println(fanzhi.getModifiers()); // 权限符对应的数字 default:0,public:1,private:2,protected:4,
        fanzhi.invoke(dog); // 执行方法
    }
}

从上面的代码中可以看到,使用反射可以取消权限检查,使平时用一般方式不能访问的方法,可以访问并执行,但这也破坏了类的封装性。

总结:反射可以使我们的代码更具灵活性,但是反射也会消耗更多的系统资源,所以如果不需要动态创建一个对象,那么就不需要用到反射。

编程心路

你可能感兴趣的:(学过框架的必看—Java反射)