反射

反射是Java中一种用于动态获取类或对象的信息以及动态调用对象方法的功能机制。在运行状态中,对于任意一个类,使用反射可以获取这个类的所有属性和方法;对于任意一个对象,使用反射能够调用它的任意属性和方法。利用反射可以无视对象的修饰符,调用类里面的内容。利用反射可以跟配置文件结合起来使用,把要创建的对象信息和方法写在配置文件中。

使用反射包含以下步骤:

  1. 获取class字节码文件对象。
  2. 利用反射可以操作类或对象的信息有:
    • 构造方法(获取、创建对象)
    • 成员变量(获取、赋值)
    • 成员方法(获取、运行)

获取字节码文件对象

Java中的每个类都是一个Class对象(字节码文件对象)。获取字节码文件对象共有以下三种方式:

  • 通过class字节码文件获取:Class.forName("类的全类名")

    类的全类名=类的包名.类名。

    使用该方式,是通过.java源代码文件编译后生成的.class字节码文件获取。

    Class clazz = Class.forName("com.linner.repo.Student");
    
  • 通过Class对象的class属性获取:类名.class

    该方式是对象已载入内存后使用的,需要使用import语句将对应的类导入(.class文件加载到内存后产生的Class对象也是唯一的)。

    Class clazz = Student.class;
    
  • 通过对象获取:对象.getClass()

    通过创建对象后的getClass()方法来返回该对象的Class对象。该方法需要创建具体对象后才能使用。

    Student student = new Student();
    Class clazz = student.getClass();
    

以上三种方法获取到的Class对象均为同一个对象。即,使用==分别判断以上三种方法获取到的对象均为true

获取了Class对象后就可以获取到该类的各种信息。

获取构造方法

获取构造方法即为获取Constructor对象。

方法名 说明
Constructor<?>[] getConstructors() 获得所有的公共构造方法(public
Constructor<?>[] getDeclaredConstructors() 获得所有的构造方法(包括private
Constructor<T> getConstructor(Class<?>... parameterTypes) 获取指定的公共构造方法
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 获取指定的构造方法(包括private

Example:

public class Student {
    public Student() {
        System.out.println("创建了一个Student对象");
    }

    private Student(String name) {
        System.out.println("创建了一个Student对象:" + name);
    }

    public Student(String name, int age) {
        System.out.println("创建了一个Student对象:name=" + name + ", age=" + age);
    }
}
public class ReflectDemo {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        // 获取Class对象
        Class clazz = Class.forName("com.linner.repo.Student");

        // 获取所有公共构造方法对象
        Constructor[] constructors1 = clazz.getConstructors();
        for (Constructor constructor : constructors1) {
            System.out.println(constructor);
        }

        System.out.println("=======================");

        // 获取所有构造方法对象(包括private)
        Constructor[] constructors2 = clazz.getDeclaredConstructors();
        for (Constructor constructor : constructors2) {
            System.out.println(constructor);
        }

        System.out.println("=======================");

        // 获取指定的构造方法对象
        // 空参构造方法
        Constructor con1 = clazz.getConstructor();
        System.out.println(con1);
        // 包含参数的构造方法
        Constructor con2 = clazz.getConstructor(String.class, int.class);
        System.out.println(con2);
        // 获取指定的构造方法对象(包括private)
        Constructor con3 = clazz.getDeclaredConstructor(String.class);
        System.out.println(con3);
    }
}

通过反射创建对象

使用Constructor对象的newInstance()方法可以为反射的类创建对象。

Example:

public class ReflectDemo {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        // 获取Class对象
        Class clazz = Class.forName("com.linner.reflect.Student");

        // 获取无参的公共构造方法
        Constructor con1 = clazz.getConstructor();
        // 使用无参的构造方法创建对象
        Student stu1 = (Student) con1.newInstance();
        System.out.println(stu1);

        // 获取带参数的构造方法(包括private)
        Constructor con2 = clazz.getDeclaredConstructor(String.class, int.class);
        // 暴力反射
        // 如果是private修饰的构造方法,需要临时修改构造方法的访问权限(取消检查访问权限)
        con2.setAccessible(true);
        // 使用带参数的构造方法创建对象
        Student stu2 = (Student) con2.newInstance("zhangsan", 23);
        System.out.println(stu2);
    }
}

获取成员变量

获取成员变量即为获取Field对象。

方法名 说明
Field[] getFields() 返回所有公共成员变量对象(public
Field[] getDeclaredFields() 返回所有成员变量对象(包括private
Field getField(String name) 返回指定名称的公共成员变量对象(public
Field getDeclaredField(String name) 返回指定名称的成员变量对象(包括private

Example:

public class Student {
    public String name;
    private int age;
    public String gender;
    private String address;
}
public class ReflectDemo {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        // 获取Class对象
        Class clazz = Class.forName("com.linner.reflect.Student");

        // 获取所有公共成员变量对象
        Field[] fields1 = clazz.getFields();
        for (Field field : fields1) {
            System.out.println(field);
        }

        System.out.println("====================");

        // 获取所有成员变量(包括private)
        Field[] fields2 = clazz.getDeclaredFields();
        for (Field field : fields2) {
            System.out.println(field);
        }

        System.out.println("====================");

        // 获取指定名称的公共成员变量
        Field nameField = clazz.getField("name");
        System.out.println(nameField);

        System.out.println("====================");

        // 获取指定名称的成员变量(包括private)
        Field ageField = clazz.getDeclaredField("age");
        System.out.println(ageField);
    }
}

利用反射获取和修改成员变量的值

可以使用以下Field对象方法对成员变量的值进行修改:

  • 赋值:

    void set(Object obj, Object value)
    

    参数:

    • obj:要修改属性值的具体对象;
    • value:具体要修改的值。
  • 获取值:

    Object get(Object obj)
    

    参数:

    • obj:要修改属性值的具体对象。

Example:

public class Student {
    public String name;
    private int age;
    private String gender;
    private String address;

    public Student(String name, int age, String gender, String address) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.address = address;
    }
}
public class ReflectDemo {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException {
        Student student = new Student("张三", 23, "大三", "广州");

        // 获取Class对象
        Class<?> clazz = Class.forName("com.linner.reflect.Student");

        // 获取并修改name的值
        Field nameField = clazz.getField("name");
        // 获取name的值
        String name = (String) nameField.get(student);
        System.out.println(name);
        // 修改name的值
        nameField.set(student, "李四");
        name = (String) nameField.get(student);
        System.out.println(name);

        System.out.println("===============");

        // 获取并修改私有变量的值(暴力反射)
        Field addressField = clazz.getDeclaredField("address");
        // 临时取消访问权限(获取和修改私有变量的值都需要此操作)
        addressField.setAccessible(true);
        // 获取私有变量address的值
        String address = (String) addressField.get(student);
        System.out.println(address);
        // 修改私有变量address的值
        addressField.set(student, "北京");
        address = (String) addressField.get(student);
        System.out.println(address);
    }
}

获取成员方法

获取成员方法即为获取Method对象。

方法名 说明
Method[] getMethods() 返回所有公共成员方法对象(public
Method[] getDeclaredMethods() 放回所有成员方法对象(包括private
Method getMethod(String name, Class<?>... parameterTypes) 返回指定名称和形参的公共成员方法对象(public
Method getDeclaredMethod(String name, Class<?>... parameterTypes 返回指定名称和形参的成员方法对象(包括private

Example:

package com.linner.reflect;

public class Student {
    private String name;
    private int age;
    private String gender;
    private String address;

    public Student() {

    }

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

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

    public Student(String name, int age, String gender, String address) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.address = address;
    }

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

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

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

    private void sleep() {
        System.out.println(this.name + "正在睡觉");
    }

    private void study() {
        System.out.println(this.name + "正在内卷");
    }

    private void study(int time) {
        System.out.println(this.name + "已经卷了" + time + "分钟还在卷");
    }
}
public class ReflectDemo1 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        // 获取Class对象
        Class<?> clazz = Class.forName("com.linner.reflect.Student");

        // 获取所有公共成员方法
        Method[] methods1 = clazz.getMethods();
        for (Method method : methods1) {
            System.out.println(method);
        }

        System.out.println("==============");

        // 获取所有成员方法(包括private)
        Method[] methods2 = clazz.getDeclaredMethods();
        for (Method method : methods2) {
            System.out.println(method);
        }

        System.out.println("==============");

        // 获取指定的公共成员方法
        // 空参的公共成员方法
        Method toStringMethod = clazz.getMethod("toString");
        System.out.println(toStringMethod);
        // 带参数的公共成员方法
        Method setNameMethod = clazz.getMethod("setName", String.class);
        System.out.println(setNameMethod);

        System.out.println("==============");

        // 获取指定的成员方法(包括private)
        // 空参的成员方法
        Method studyMethod1 = clazz.getDeclaredMethod("study");
        System.out.println(studyMethod1);
        // 带参数的成员方法
        Method studyMethod2 = clazz.getDeclaredMethod("study", int.class);
        System.out.println(studyMethod2);
    }
}

通过反射运行成员方法

使用Method对象的invoke()方法可以运行成员方法。

Object invoke(Object obj, Object... args)
  • obj:具体调用成员方法的对象;
  • args:调用成员方法传递的参数;
  • 返回值:方法的返回值。

Example:

public class ReflectDemo1 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Student student = new Student("张三", 23, "大三", "广州");

        // 获取Class对象
        Class<?> clazz = Class.forName("com.linner.reflect.Student");

        // 调用无参的成员方法
        Method toStringMethod = clazz.getMethod("toString");
        String studentToString = (String) toStringMethod.invoke(student);
        System.out.println(studentToString);

        // 调用带参的私有成员方法
        Method studyMethod = clazz.getDeclaredMethod("study", int.class);
        // 临时取消访问权限
        studyMethod.setAccessible(true);
        studyMethod.invoke(student, 180);
    }
}

注解

Java注解是附加在代码中的一些元信息,用于编译和运行时进行解析和使用,起到说明、配置的功能。

注解不会影响代码的实际逻辑(并不直接影响代码的语义),仅仅起到辅助性的作用(但是注解可以被看做是程序的工具或者类库。会反过来对正在运行的程序语义有所影响)。包含在java.lang.annotation包中。注解使用@interface来定义(类似于接口的定义)。为注解定义一个方法即为注解类型定义了一个元素,方法的声明不允许有参数或throw语句,返回值类型被限定为原始数据类型、StringClassenums、注解类型,或前面这些类型的数组,方法可以有默认值。注解可以从源文件、class文件或者在运行时通过反射机制多种方式被读取。

注解的实现的原理很大的一部分是基于反射实现。

一般来说,注解一般分为三种类型:

  • 元注解
  • 标准注解
  • 自定义注解

元注解

元注解是专职负责注解其他注解,主要是标明该注解的使用范围,生效范围。元注解并不能被修改,只能被用来定义自定义注解。

元注解包括以下四种:

注解 说明
@Target 定义注解的作用目标。即用来定义自定义注解具体作用在类上,方法上,还是变量上。具体值和作用对象如下:
ElementType.TYPE:接口、类、枚举、注解
ElementType.FIELD:字段、枚举的常量
ElementType.METHOD:方法
ElementType.PARAMETER:方法参数
ElementType.CONSTRUCTOR:构造函数
ElementType.LOCAL_VARIABLE:局部变量
ElementType.ANNOTATION_TYPE:注解
ElementType.PACKAGE:包
Retention 定义注解的保留策略。该注解的值如下:
RetentionPolicy.SOURCE:定义注解仅存在于源码中,在class字节码文件中不包含;
RetentionPolicy.CLASS:该注解默认的保留策略,定义注解会在class字节码文件中存在,但运行时无法获得;
RetentionPolicy.RUNTIME:定义注解会在class字节码文件中存在,在运行时可以通过反射获取到。
@Document 说明该注解将被包含在javadoc中。
@Inherited 说明子类可以继承该注解。

标准注解

Java 提供了三个标准注解,定义在java.lang中(这三个注解的作用更多是一种注释)。

  • @Override:表示当前方法覆盖了父类中的对应方法(重写方法)。

  • @Deprecated:标记一个元素为已过期,避免或不推荐使用。

    支持的元素类型为:CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE

  • @SuppressWarnings:不输出对应的编译警告。

自定义注解

注解的定义类似于接口的定义,使用@interface来定义。定义注解类中的一个方法即为注解类型定义了一个元素,方法的声明不允许有参数或throw语句,返回值类型被限定为原始数据类型、字符串String、Class、enums、注解类型,或前面这些类型的数组,方法可以有默认值。

自定义注解一般分为以下三个步骤:

  • 定义注解。

    如:

    @Target(ElementType.Type)   // 表明该注解加载在类上
    @Retention(RetentionPolicy.RUNTIME) // 表明运行时读取该注解
    // 定义注解类
    public @interface MyAnnotationType {
        // 定义注解方法(不能有参数或throw等)
        String value();
    }
    
    @Target(ElementType.FIELD)   // 表明该注解加载在字段上(接口、类、枚举)。
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotationField {
        String name();
        // 使用default定义默认值
        int length() default 0;
    }
    
  • 使用注解。

    如(在其他类上):

    @MyAnnotationType("Annotation")
    // 或(有多个元素时需要指定元素名):@MyAnnotation1(value = "Annotation")
    public class UsingAnnotations {
        @MyAnnotationField(name = "张三", length = 12)
        public int a;
        @MyAnnotationField(name = "李四")   // length将使用默认值
        public String b;
    }
    
  • 读取注解。

    如(定义一个类来读取):

    public class AnnotationValues {
        public static void readValues() throws ClassNotFoundException {
            Class usingAnnotationsClass = Class.forName("UsingAnnotation");
    
            // 读取作用于类上的注解
            MyAnnotationType mat = (MyAnnotationType) usingAnnotationsClass.getAnnotation(MyAnnotationType.class);
            System.out.println(mat.value());
    
            // 读取作用于属性上的注解
            Field a = usingAnnotationsClass.getDeclaredField("a");
            MyAnnotationField maf = (MyAnnotationField) a.getAnnotation(MyAnnotationField.class);
            System.out.println(maf.name());
            System.out.println(maf.length());
        }
    }
    

使用注解模拟 Junit

public class AnnotationDemo {

    // @MyTest
    public void test1() {
        System.out.println("test1...");
    }

    @MyTest
    public void test2() {
        System.out.println("test2...");
    }

    // @MyTest
    public void test3() {
        System.out.println("test3...");
    }

    @MyTest
    public void test4() {
        System.out.println("test4...");
    }

    @MyTest
    public void test5() {
        System.out.println("test5...");
    }

    public static void main(String[] args) throws Exception {
        AnnotationDemo a = new AnnotationDemo();

        // 获取Class对象
        Class<?> clazz = Class.forName("com.linner.annotation.AnnotationDemo");

        // 判断对象的所有方法上是否存在MyTest注解
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(MyTest.class)) {
                method.invoke(a);
            }
        }
    }

}