Hey, Nice to meet You. 

必有过人之节.人情有所不能忍者,匹夫见辱,拔剑而起,挺身而斗,此不足为勇也,天下有大勇者,猝然临之而不惊,无故加之而不怒.此其所挟持者甚大,而其志甚远也.          ☆☆☆所谓豪杰之士,

夯实Java基础(二十一)----Java反射机制

1、反射的概述

[1]、什么是反射

Java反射机制是指程序在运行状态中,对于任何一个类,我们都能够知道这个类的所有的属性和方法(包括private、protected等)。对于任何一个对象,我们都能够对它的属性和方法进行调用。我们把这种动态获取对象信息和调用对象方法的功能称之为反射机制。

Oracle 官方对反射的解释是:

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.

简而言之,通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。

反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。重点:是运行时而不是编译时

Java 反射主要提供以下功能:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时处理注解
  • 生成动态代理
  • ......

[2]、反射的理解

所谓有反射就有正射,在理解反射这个概念之前,我们先来理解Java中的“正射”。

正射:就是当需要使用到某一个类的时候,必定先会去了解这是一个什么类,是用来做什么的,有怎么样的功能。之后我们才对这个类进行实例化,之后再使用这个类的实例化对象进行操作。

Person person = new Person();
person.getName();

反射:就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

Class<?> personClazz = Class.forName("com.thr.reflect.Person");
Field field = personClazz.getDeclaredField("name");
Constructor<?> constructor = personClazz.getConstructor();
Method method = personClazz.getMethod("doHomework", String.class);
Object instance = personClazz.newInstance();
method.invoke(instance,"English");

[3]、反射的主要用途

在我们日常的生产环境中,很少会直接使用到反射,所以很多人都认为反射在实际的 Java 开发应用中并不广泛,其实不然,反射最重要的用途就是开发各种通用框架,反射是框架设计的灵魂。当我们在使用大部分Java框架的时候,基本上都用到了反射,到后面会学习到框架的知识,其实框架的组成结构可以理解为这个公式:框架=反射+注解+设计模式,其中反射就是框架的灵魂所在。所以反射在Java的学习中非常重要。对与框架开发人员来说,反射虽小但作用非常大,它是各种容器实现的核心。而对于一般的开发者来说,不深入框架开发则用反射用的就会少一点,不过了解一下框架的底层机制有助于丰富自己的编程思想,也是很有益的。


[4]、反射机制的优缺点

  • 优点:可以实现动态创建对象和编译,体现出很大的灵活性。
  • 缺点:对性能有影响。使用反射基本上是一种解释操作,可以告诉 JVM,希望做什么,并且满足要求。这类操作总是慢于,直接执行相同的操作。

[5]、Java反射API中常用的四个类

描述
java.lang.Class 代表一个类
java.lang.reflect.Field 代表类的成员变量(成员变量也称为类的属性)
java.lang.reflect.Method 代表类的方法
java.lang.reflect.Constrctor 代表类的构造方法

2、如何获取Class对象

关于Class的介绍:Class类是用来描述类的类,它是一个十分特殊的类,没有构造方法。Class对象的加载过程如下:当程序运行时,我们编写的每一个类都会被编译生成 类名.class 文件,当我们我们new对象或者类加载器加载的时候,JVM就会加载我们的 类名.class 文件并且加载到内存中,即当一个类加载完成之后,在堆内存的方法区中就生成了一个该类唯一的Class对象(一个类只会对应一个Class对象,绝对不会产生第二个),这个Class对象就包含了完整的类的结构信息,用于表示该类的所有信息。

image

image


既然反射机制一定会用到Class这个类,那么就必须先获取它,先看下哪些对象可以获取Class。注意:Class并不是只有普通类或接口才能获取,其中基本数据类型、数组、枚举、注解、void等都可以获取其Class对象,甚至Class这个类本身也可以获取Class对象

Class<Object> c1 = Object.class;
Class<String> c2 = String.class;
Class<Integer> c3 = int.class;
Class<int[]> c4 = int[].class;
Class<int[][]> c5 = int[][].class;
Class<ElementType> c6 = ElementType.class;
Class<Override> c7 = Override.class;
Class<Class> c8 = Class.class;
Class<Void> c9 = void.class;
 
// 数组
int arr[]=new int[10];
int arr1[]=new int[100];
Class<? extends int[]> c10 = arr.getClass();
Class<? extends int[]> c11 = arr1.getClass();
//只要元素类型和维度相同,就是同一个Class
System.out.println(c10==c11);

TIPS:反射这里所有举例都用Person类来作为演示,所以先创建一个Person类,后面会一直用这个。

public class Person {

    // public公共的成员变量
    public String name;
    // default成员变量
    int age;
    // private私有的成员变量
    private String address;

    // public默认构造方法
    public Person() {
    }

    // private私有有参构造方法
    private Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // public公共全参构造方法
    public Person(String name, int age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    // public公共方法
    public void doHomework(String subject) {
        System.out.println("公共方法..." + subject);
    }

    // private私有方法
    private void sleep() {
        System.out.println("私有方法...");
    }

    //getter,setter,toString省略...
}

获取Class对象四种方式

  • 通过Object类中的getClass方法获取。这种方式要先创建类的对象,这样再使用反射就多此一举了,不推荐。
  • 通过 类名 .class 直接获取。这种方式需要导入相应类的包,依赖性较强,不推荐。
  • 使用Class类中静态方法forName(String className)获取。所以这种方式最常用。
  • 使用类加载器ClassLoader来获取。这种方式了解即可,用的很少,一般用来加载properties文件。

代码演示如下:

/**
 * 获取Class对象的四种方式
 */
public class Reflection1 {
    public static void main(String[] args) throws ClassNotFoundException {
        //1、第一种方式:getClass
        Person person = new Person();
        Class<? extends Person> clazz1 = person.getClass();
        System.out.println("getClass获取:" + clazz1);

        //2、第二种方式:类名.class
        Class<Person> clazz2 = Person.class;
        System.out.println("类名.class获取:" + clazz2);

        //3、第三种方式:Class.forName()
        Class<?> clazz3 = Class.forName("com.thr.reflect.Person");
        System.out.println("forName()方法获取:" + clazz3);

        //4、第四种方式:类加载器ClassLoader,常用来加载properties配置文件
        ClassLoader classLoader = Reflection1.class.getClassLoader();
        Class<?> clazz4 = classLoader.loadClass("com.thr.reflect.Person");
        System.out.println("类加载器ClassLoader获取:" + clazz4);

        //可以发现返回都是true,说明引用的同一个对象
        System.out.println("----------------------");
        System.out.println(clazz1 == clazz2);
        System.out.println(clazz1 == clazz3);
        System.out.println(clazz1 == clazz4);
    }
}

image

这四种方式推荐使用Class类中的静态方法forName(String className)来获取。但无论使用哪种方式获取,结果都是同一个类。

3、Class类中常用的方法

Class代表一个类,在运行的Java应用程序中表示类或接口。在Class类中提供了很多有用的方法,这里简单的列举一些。

1、获得类相关的方法:

  • asSubclass(Class<U> clazz):把传递的类的对象转换成代表其子类的对象
  • Cast:把对象转换成代表类或是接口的对象
  • getClassLoader():获得类的加载器
  • getClasses():返回一个数组,数组中包含该类中所有公共类和接口类的对象
  • getDeclaredClasses():返回一个数组,数组中包含该类中所有类和接口类的对象
  • forName(String className):根据类名返回类的对象
  • getName():获得类的完整路径名字
  • newInstance():创建类的实例,它调用的是此类的默认构造方法(没有默认无参构造器会报错)
  • getPackage():获得类的包
  • getSimpleName():获得类的名字
  • getSuperclass():获得当前类继承的父类的名字
  • getInterfaces():获得当前类实现的类或是接口

2、获得类中属性相关的方法:

  • getField(String name):获得类中公有(public)的属性/字段,私有的属性它无法访问,能访问从其它类继承来的公有属性。
  • getFields():获得所有公有的属性对象。
  • getDeclaredField(String name):能获取类中所有的属性,与public,private,protect无关,不能访问从其它类继承来的属性。
  • getDeclaredFields():获得所有属性对象。

注意:getXxx()与getDeclaredXxx()的区别:getXxx()只能访问类中声明为公有的内容,私有的内容它无法访问。getDeclaredXxx():能获取类中所有的字段,与public,private,protect无关。

3、获得类中注解相关的方法:

  • getAnnotation(Class<A> annotationClass):返回该类中与参数类型匹配的公有注解对象
  • getAnnotations():返回该类所有的公有注解对象
  • getDeclaredAnnotation(Class<A> annotationClass):返回该类中与参数类型匹配的所有注解对象
  • getDeclaredAnnotations():返回该类所有的注解对象

4、获得类中构造器相关的方法:

  • getConstructor(Class...<?> parameterTypes):获得该类中与参数类型匹配的公有构造方法
  • getConstructors():获得该类的所有公有构造方法
  • getDeclaredConstructor(Class...<?> parameterTypes):获得该类中与参数类型匹配的构造方法
  • getDeclaredConstructors():获得该类所有构造方法

5、获得类中方法相关的方法:

  • getMethod(String name, Class...<?> parameterTypes):获得该类某个公有的方法
  • getMethods():获得该类所有公有的方法
  • getDeclaredMethod(String name, Class...<?> parameterTypes):获得该类某个方法
  • getDeclaredMethods():获得该类所有方法

6、类中其他重要的方法:

  • isAnnotation():如果是注解类型则返回true
  • isAnnotationPresent(Class<? extends Annotation> annotationClass):如果是指定类型注解类型则返回true
  • isAnonymousClass():如果是匿名类则返回true
  • isArray():如果是一个数组类则返回true
  • isEnum():如果是枚举类则返回true
  • isInstance(Object obj):如果obj是该类的实例则返回true
  • isInterface():如果是接口类则返回true
  • isLocalClass():如果是局部类则返回true
  • isMemberClass():如果是内部类则返回true

Class类中的方法有很多,这里只列举了部分,需要学习更多的可以自行去查看Class的API文档。

4、获取构造器

获取类中的构造器用到的是Constructor类,构造器的获取比较的简单,获的类中构造器相关的方法如下所示:

  • getConstructor(Class...<?> parameterTypes):获得该类中与参数类型匹配的公共(public)构造方法
  • getConstructors():获得该类的所有公共(public)构造方法
  • getDeclaredConstructor(Class...<?> parameterTypes):获得该类中与参数类型匹配的构造方法
  • getDeclaredConstructors():获得该类所有构造方法

简单演示一下:

package com.thr.reflect;

import java.lang.reflect.Constructor;

/**
 * 获取类中的构造器,并且创建对象的实例
 */
public class Reflection2 {
    public static void main(String[] args) throws Exception {
        //获取Person的Class对象
        Class<?> clazz = Class.forName("com.thr.reflect.Person");

        //1、获取public的构造器
        Constructor<?>[] constructors = clazz.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }
        System.out.println("-----------------------");

        //2、获取所有的构造器
        Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }
        System.out.println("-----------------------");

        //3、使用反射创建实例,默认构造器
        Constructor<?> constructor = clazz.getConstructor();
        System.out.println(constructor.getName());
        Object o = constructor.newInstance();
        Person p = (Person) o;
        System.out.println(p.toString());

        //4、有参构造器创建实例
        Constructor<?> constructor1 = clazz.getConstructor(String.class, int.class, String.class);
        Object o1 = constructor1.newInstance("张三", 20, "China");
        Person p1 = (Person) o1;
        System.out.println(p1.toString());
    }
}

5、通过反射创建对象

通过反射创建类对象主要有两种方式:

  • 第一种:通过 Class 对象的 newInstance() 方法。
  • 第二种:通过 Constructor 对象的 newInstance(Object ... initargs) 方法
package com.thr.reflect;

import java.lang.reflect.Constructor;

/**
 * 通过反射创建类对象
 */
public class Demo {
    public static void main(String[] args) throws Exception {
        //获取Person的Class对象
        Class<Person> clazz = Person.class;

        //1、通过Class对象的newInstance()方法,只能使用默认的无参数构造方法
        Person person = clazz.newInstance();
        System.out.println(person);
        System.out.println("-----------------");

        //2、通过Constructor对象的newInstance(Object...initargs)方法,通过无参构造器创建
        Constructor<Person> constructor = clazz.getConstructor();
        Person person1 = constructor.newInstance();
        System.out.println(person1);
        System.out.println("-----------------");

        //3、通过Constructor对象的newInstance(Object...initargs)方法,通过有参的构造器创建
        Constructor<Person> constructor1 = clazz.getConstructor(String.class, int.class, String.class);
        Person person2 = constructor1.newInstance("张三", 20, "深圳");
        System.out.println(person2);
        System.out.println("-----------------");
    }
}

6、获取成员变量

在我们正常创建对象的实例时,其内部的私有元素是不能访问的,但是使用反射则可以轻易的获取,前面创建的Person类中属性、方法和构造器都有被private关键字所修饰。所以接下来演示一下怎么获取成员变量、方法和构造器。

获取属性使用到的是Field这个类,它内部的方法主要是获取、设置某个属性的方法,例如:getXXX()、setXXX();获得类中成员变量相关的方法如下:

  • getField(String name):获得某个公有的属性对象
  • getFields():获得所有公有的属性对象
  • getDeclaredField(String name):获得某个属性对象
  • getDeclaredFields():获得所有属性对象
package com.thr.reflect;

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

/**
 * 通过反射获取类的成员变量
 */
public class Reflection3 {
    public static void main(String[] args) throws Exception {

        //获取Person的Class对象
        Class<?> clazz = Class.forName("com.thr.reflect.Person");

        //1、通过反射获取所有public的成员变量,private、protected、default是不能获取到的
        Field[] fields1 = clazz.getFields();
        for (Field field : fields1) {
            System.out.println(field.getName());
        }
        System.out.println("------------------------");

        //2、通过反射单个获取public成员变量
        Field name = clazz.getField("name");
        System.out.println(name);
        Field age = clazz.getDeclaredField("age");
        System.out.println(age);
        Field address = clazz.getDeclaredField("address");
        System.out.println(address);
        System.out.println("------------------------");

        //3、通过反射获取所有变量,包括private、protected、default。
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            //4、获取成员变量名字
            System.out.println("成员变量名:"+field.getName());
            //5、获取权限修饰:0是default、1是public、2是private、4是protected。或者使用Modifier.toString(modifiers)输出
            int modifiers = field.getModifiers();
            System.out.println("权限修饰符:"+modifiers);
            //6、获取成员变量数据类型
            Class<?> type = field.getType();
            System.out.println("数据类型:"+type);
            System.out.println("---------------");
        }

        //4、获取成员变量并且给其赋值
        //获取无参构造
        Constructor<?> constructor = clazz.getConstructor();
        //通过constructor对象进行实例化
        Object o = constructor.newInstance();
        //获取Class所对应的类型中某个公共的成员变量
        Field addressField = clazz.getDeclaredField("address");
        //这里必须设置为true,否则会报错java.lang.IllegalAccessException
        //setAccessible(true)的作用是:忽略Class对象所对应的类型中成员的访问权限
        addressField.setAccessible(true);
        addressField.set(o, "中国深圳");
        System.out.println(o.toString());
    }
}

7、获取方法并且调用

获取方法用到的是Method类,内部方法也是一些getXXX()和setXXX的方法,这些方法就不演示了,和上面获取Field大同小异,可以参考上面的例子。

获得类中方法相关的方法:

  • getMethod(String name, Class...<?> parameterTypes):获得该类某个公有的方法
  • getMethods():获得该类所有公有的方法,包括继承父类的方法
  • getDeclaredMethod(String name, Class...<?> parameterTypes):获得该类某个方法
  • getDeclaredMethods():获得该类所有方法,包括私有的,不包括继承父类的方法

需要注意的一点是:如果需要通过反射来调用方法,那么就需要使用到下面这个方法:

  • invoke(Object obj, Object... args):传递object对象及参数然后调用该对象所对应的方法。
/**
 * 通过反射获取类的方法并且调用
 */
public class Reflection5 {
    public static void main(String[] args) throws Exception {
        //获取Person的Class对象
        Class<?> clazz = Class.forName("com.thr.reflect.Person");
        Object obj = clazz.newInstance();
        Method doHomework = clazz.getDeclaredMethod("doHomework", String.class);
        doHomework.invoke(obj, "English");
        Method sleep = clazz.getDeclaredMethod("sleep");
        //setAccessible(true):忽略Class对象所对应的类型中成员的访问权限,这里必须要设置,否则会报错
        sleep.setAccessible(true);
        sleep.invoke(obj);
    }
}

8、获取注解

获得类中注解相关的方法:

  • getAnnotation(Class<A> annotationClass):返回该类中与参数类型匹配的公有注解对象
  • getAnnotations():返回该类所有的公有注解对象
  • getDeclaredAnnotation(Class<A> annotationClass):返回该类中与参数类型匹配的所有注解对象
  • getDeclaredAnnotations():返回该类所有的注解对象
  • isAnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.

为了测试注解这里创建了三个自定义注解,分别是作用在类、属性、方法上面的注解。

定义作用于类上面的MyAnnotation注解 (需要注意的是@Retention必须要设置为RUNTIME类型,否则是获取不到的):

//作用于类上面的注解
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)//这里必须定义为RUNTIME
public @interface MyAnnotation {
    String[] value() default "";
}

定义作用于属性上面的AttributeAnnotation注解:

//作用于属性上的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)//这里必须定义为RUNTIME
public @interface AttributeAnnotation {
    String value();
}

定义作用于方法上面的MethodAnnotation注解:

//作用于方法上的注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)//这里必须定义为RUNTIME
public @interface MethodAnnotation {
    String value();
}

然后将上面创建的三个自定义注解作用到指定的位置:

@MyAnnotation(value = "MyAnnotation")
class AnnotationReflect {

    @AttributeAnnotation(value = "AttributeAnnotation")
    String str;

    @MethodAnnotation(value = "MethodAnnotation_show")
    public void show() {
        System.out.println("MethodAnnotation_show");
    }

    @MethodAnnotation(value = "MethodAnnotation_display")
    public void display() {
        System.out.println("MethodAnnotation_display");
    }
}

最后就是测试了:

public class AnnotationReflectMain {

    public static void main(String[] args) throws NoSuchFieldException {
        // 1、获取类上面的注解
        boolean annotationPresent = AnnotationReflect.class.isAnnotationPresent(MyAnnotation.class);
        System.out.println(annotationPresent);
        if (annotationPresent) {
            MyAnnotation myAnnotation = AnnotationReflect.class.getAnnotation(MyAnnotation.class);
            System.out.println("class-annotation:" + Arrays.toString(myAnnotation.value()));
        }

        // 2、获取单个属性中的注解
        Field str = AnnotationReflect.class.getDeclaredField("str");
        AttributeAnnotation attributeAnnotation = str.getAnnotation(AttributeAnnotation.class);
        if (attributeAnnotation != null) {
            System.out.println("attribute-annotation:" + attributeAnnotation.value());
        }

        // 3、获取多个方法中的注解
        Method[] declaredMethods = AnnotationReflect.class.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            MethodAnnotation methodAnnotation = declaredMethod.getAnnotation(MethodAnnotation.class);
            if (methodAnnotation != null) {
                System.out.println("method-annotation:" + methodAnnotation.value());
            }
        }
    }
}

其实后面还有获取父类、泛型、接口、所在包等等其他的就不多说了,其实都是大同小异,理解了上面这些,后面这些应该也会了。


参考链接:

深入解析Java反射(1) - 基础 | 「浮生若梦」 - sczyh30's blog

posted @ 2019-09-18 22:51  唐浩荣  阅读(503)  评论(0编辑  收藏  举报