Java 中的反射和枚举

  • Post author:
  • Post category:java




引言

在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量或成员方法是私有的或只对系统应用开放,这时候就可以利用 Java 的反射机制通过反射来获取所需的私有成员或方法,当然,公开的成员变量和方法显然也能通过反射获取得到。

反射最重要的用途就是开发各种通用框架,比如在 spring 中,我们将所有的类Bean 交给 spring 容器管理,无论是 XML 配置 Bean 还是注解配置,当我们从容器中获取 Bean 来依赖注入时,容器会读取配置,而配置中给的就是类的信息,spring 根据这些信息,需要创建 Bean,spring 动态的这些类。



一、什么是反射

Java 的反射机制表示在运行状态中,对于任意一个类或者说对于任意一个对象,都能够调用它的任意方法和属性(主要解决调用私有方法和私有属性的问题)。这样一来,我们就可以修改部分类型信息,而这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。



二、通过反射获取对象的三种方式

通过反射机制获取对象的三种方式中,方式一用的最多。

创建一个包 demo1,在包中创建一个 java 文件,里面放着两个类,即类 Student 和 类 Test1,如下所示:

那么下面的方式一需要指明类的全路径,如果有包需要加包的路径。

Class<?> c1 = Class.forName("demo1.Student"); //用的情况最多
/**
 * 演示 获取对象的三种方式
 */
class Student{

    //私有属性name
    private String name = "bit";
    //公有属性age
    public int age = 18;
    //不带参数的构造方法
    public Student(){
        System.out.println("Student()");
    }

    private Student(String name,int age) {
        this.name = name;
        this.age = age;
        System.out.println("Student(String,name)");
    }

    private void eat(){
        System.out.println("I am eating");
    }

    public void sleep(){
        System.out.println("I am sleeping");
    }

    private void function(String str) {
        System.out.println(str);
    }

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

public class Test {
    public static void main(String[] args) throws ClassNotFoundException{
        //获得 Class 对象的三种方式
        //不管使用哪种方式来获取 Class 对象,此时对象只有一个

        //方式一
        Class<?> c1 = Class.forName("demo1.Student"); //用的情况最多
        //方式二
        Class<?> c2 = Student.class;
        //方式三
        Student student = new Student();
        Class<?> c3 = student.getClass();

        System.out.println(c1==c2);
        System.out.println(c1==c3);
        System.out.println(c2==c3);

    }
}

输出结果:

out



三、演示反射机制的一些方法

在刚刚的包 demo1 中,我们创建第二个 java 文件,里面创建四个类,用来测试反射机制来拿到 Student 类中的相关信息。

接下来我们测试反射机制常用的四个方法:

① 通过 Class 类的 newInstance( )方法获取学生实例

② 通过 Constructor 类 的 getDeclaredConstructor( )方法,获得私有的构造方法(公开的构造方法也可通过反射获得)

③ 通过 Field 类的 getDeclaredField( )方法获得对象中私有的成员变量(公有的成员变量也可通过反射获得)

④ 通过 Method类的 getDeclaredMethod( )方法获得对象中私有的成员方法(公有的成员变量也可通过反射获得)

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

public class ReflectClass {
    /**
     * 通过 Class 类的 newInstance()方法 获取学生实例
     */
    public static void reflectNewInstance() {

        try {
            //1. 拿到 Class 对象
            Class<?> c1 = Class.forName("demo1.Student");
            Student student = (Student) c1.newInstance();
            //2. 直接打印
            System.out.println(student);

        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 反射私有的构造方法(公开私有皆可)
     */
    public static void reflectPrivateConstructor(){

        try {
            //1. 拿到 Class 对象
            Class<?> c1 = Class.forName("demo1.Student");

            //2. 获得该类中与参数类型匹配的构造方法
            //例如:Student 类的公开和私有的构造方法都可以获取到
            Constructor<?> constructor = c1.getDeclaredConstructor(String.class, int.class);
            constructor.setAccessible(true); // 设置为true后可修改访问权限

            Student student = (Student) constructor.newInstance("linxi", 13);
            System.out.println(student);

        } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException
                 | InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 反射私有的成员变量(公开私有皆可)
     */
    public static void reflectPrivateField() {
        try {
            //1. 拿到 Class 对象
            Class<?> c1 = Class.forName("demo1.Student");
            Student student = (Student) c1.newInstance();

            //2. 获得对象中私有的成员变量并给其赋值(公有也可进行操作)
            Field field = c1.getDeclaredField("name");
            field.setAccessible(true);
            field.set(student,"Jack");//给指定对象私有的成员变量赋值
            System.out.println(student);

        } catch (ClassNotFoundException | InstantiationException
                 | IllegalAccessException | NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 反射私有的成员方法(公开私有皆可)
     */
    public static void reflectPrivateMethod() {
        try {
            //1. 拿到 Class 对象
            Class<?> c1 = Class.forName("demo1.Student");
            Student student = (Student) c1.newInstance();

            //2. 获得对象中私有的成员方法并给其赋值(公有也可进行操作)
            Method method = c1.getDeclaredMethod("function", String.class);
            method.setAccessible(true);
            method.invoke(student,"拿到私有方法");

        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
                 | NoSuchMethodException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }


    public static void main(String[] args) {
        reflectNewInstance();
        reflectPrivateConstructor();
        reflectPrivateField();
        reflectPrivateMethod();

    }
}

输出结果:

out



四、枚举

枚举类的本质:枚举类是 java.lang.Enum 的子类,也就是说,自己写的枚举类,就算没有显示继承 Enum ,但在 Java 中,它都默认继承了Enum 类。

源代码中的 Enum 类是抽象类,其作用就是用来被继承。

out



1. 语法

public enum TestEnum {
	RED,BLACK,GREEN; //列举某个事物的颜色
}



2. 枚举的一些方法

分析



3. 演示1

public enum TestEnum {
    RED,BLACK,GREEN,WHITE;

    public static void main(String[] args) {
        TestEnum[] testEnums = TestEnum.values();
        for (int i = 0; i < testEnums.length; i++) {
            System.out.println(testEnums[i] + " -> " + testEnums[i].ordinal() );
        }

        System.out.println("-----------------");
        System.out.println(BLACK.compareTo(RED)); //依照索引下标比较
        System.out.println(BLACK.compareTo(WHITE));
    }
}

输出结果:

out



4. 演示2

public enum TestEnum2 {
    RED("red",1),BLACK("black",2),
    WHITE("white",3),GREEN;

    public String color;
    public int ordinal;

    //1. 当枚举对象有参数后,需要提供相应的构造函数
    //2. 枚举的构造函数默认是私有的
    TestEnum2 (String color,int ordinal) { //private
        this.color = color;
        this.ordinal = ordinal;
    }

    TestEnum2(){

    }

    public static void main(String[] args) {

    }
}



5. 枚举的优缺点

优点:

  1. 枚举常量更简单安全 。
  2. 枚举具有内置方法 ,代码更优雅

缺点:

不可被继承,无法扩展



6. 枚举与反射


枚举非常安全,不能通过反射来获取到枚举的实例对象



7. 学完线程再回过头看问题

如何实现一个线程安全的单例模式(只能获取一个实例对象)?

通过枚举实现。



版权声明:本文为lfm1010123原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。