【JavaSE】Java反射与枚举的使用

Java反射与枚举的使用

1. Java反射机制

1.1 反射的定义

  • 反射的概念:反射(reflection)机制是指在运行过程中,对于任意一个类,能够获得这个类的所有内部信息。并且对于任何一个对象,能够访问并修改到他的所有属性和调用方法的功能,反射机制是动态语言的关键。

  • 反射的用途和场景:

    1. 在日常开发过程中,我们经常会碰到一个类的属性或者方法是私有的,或者只对内部系统开放。那么这个时候我们就可以通过反射机制绕过封装,获取到所需要的属性和方法
    2. 在各类通用框架中非常常见,例如说Spring框架内部由Spring容器来管理Bean,例如spring读取XML配置文件,而配置文件中就包含了各个类及其之间的内部关系信息,当spring进行依赖注入时就是通过这些类信息,利用反射机制动态创建出类的实例及其依赖关系。
  • 反射的基本信息:Java中许多对象在运行时会出现两种类型:运行时类型、编译时类型,例如说Person p = new Student(),这句代码中p的编译时类型为Person,运行时类型为Student,而程序在运行过程中需要知道对象和类的真实信息,通过反射机制就可以进行判断。

  • 反射的起源:当Java文件被编译时会生成以.class为后缀的字节码文件,该字节码文件被JVM解析为一个对象,这个对象就是Java.lang.Class,这样一来,每一个Java源程序最终都会变为Java.lang.Class类对象的一个实例,我们就是通过反射机制访问到了这个实例,并且能够访问甚至修改该对象的属性及其方法。

1.2 反射相关类和方法

常用获得类相关的方法

方法用途
getClassLoader()获得类的加载器
getDeclaredClasses()返回一个数组,数组中包含该类中所有的类和对象
forName(String className)根据类名返回该类的对象
newInstance()创建类的实例
getName()获取该类的完整路径名字

常用获得类中属性相关的方法

方法用途
getField(String name)获得某个公有的属性字段
getDeclaredField(String name)获得某个属性字段
getFields()获得所有公有的属性字段
getDeclaredFields()获得所有属性字段

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

方法用途
getConstructor(Class<?>… parameterTypes)获取与参数类型匹配的公有构造方法
getDeclaredConstructor(Class<?>… parameterTypes)获取与参数类型匹配的构造方法
getConstructors()获取所有公有的构造方法
getDeclaredConstructors()获取所有构造方法

获得类中方法相关的方法

方法用途
getMethod(String name, Class<?>… parameterTypes)获取名称与参数类型匹配的公有方法
getDeclaredMethod(String name, Class<?>… parameterTypes)获取名称与参数类型匹配的方法
getMethods()获得该类的所有公有方法
getDeclaredMethods()获得该类的所有方法

1.3 反射的使用

注:所有与反射相关的类都在java.lang.reflect包中

1.3.1 获取Class对象的三种方式

在反射之前,我们首先需要的就是获取需要反射的类的Class对象,然后通过Class对象的核心方法来达到反射的目的。

方式一:如果已知类的全路径名(如果存在包名需要加上包名),那么我们可以通过Class.forName(String className)的方式获取Class对象

方式二:如果在编译前已经确定好需要进行反射的类时,可以通过类.class的方式获取Class对象

方式三:通过类的对象调用getClass()方法获取Class对象

示例:

import org.junit.Test;

class Student {
    public String name = "jack";
    private int age = 18;

    public Student() {
    }

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

    private void eat(String foodName) {
        System.out.println(this.name + "在吃" + foodName);
    }

    public void learn() {
        System.out.println(this.name + "在学习...");
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }    

}
public class TestReflect {
    @Test
    public void test_getClass() throws ClassNotFoundException {
        // 1. 通过全路径名方式获取
        Class<?> clazz1 = Class.forName("Student");
        System.out.println(clazz1);
        // 2. 通过类.class方式获取
        Class<?> clazz2 = Student.class;
        System.out.println(clazz2);
        // 3. 通过类对象.getClass()方式获取
        Class<?> clazz3 = new Student().getClass();
        System.out.println(clazz3);
        // 验证class对象是否相同
        System.out.println(clazz1 == clazz2 && clazz1 == clazz3); // true
    }
}

注:我们经常通过第一种方式即全路径名来获取Class对象,因为更加动态灵活可扩展,例如我们可以将类路径名写在配置文件中,这样我们就避免了硬编码的内伤,做到了灵活可扩展。

1.3.2 创建反射对象

我们可以直接通过Class的newInstance方法创建反射类的对象(不推荐)

语法格式:Object obj = Class.forName(className).newInstance()

示例(以上述的Student对象为例):

@Test
public void test_getInstance() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
    Class<?> studentClazz = Class.forName("Student");
    // 通过newInstance()获取实例
    Student student = (Student) studentClazz.newInstance();
    System.out.println(student); // Student{name='jack', age=18}
}
1.3.3 通过反射获取指定构造器

我们可以通过Class的getDeclaredContructor()方法获取指定的构造器

语法格式:Class.forName(className).getDeclaredConstructor(Class<?>... params)

示例(以上述的Student对象为例):

@Test
public void test_getDeclaredField() throws Exception {
    Class<?> studentClazz = Class.forName("Student");
    // 1. 先构造对象
    Student student = (Student) studentClazz.newInstance();
    // 2. 获取指定字段
    Field field = studentClazz.getDeclaredField("age");
    // 3. 如果是私有字段,需要设置访问权限
    field.setAccessible(true);
    // 4. 调用set方法
    field.set(student, 25);
    System.out.println(student); // Student{name='jack', age=25}
}
1.3.4 通过反射获取指定的字段

我们可以通过Class的getDeclaredField(String name)方法获取指定的字段

语法格式:Class.forName(className).getDeclaredField(String name)

使用步骤:

  1. 使用 Field field = getDeclaredField(String name)方法获取指定字段
  2. 创建该反射类的实例对象obj
  3. 如果该字段修饰符为private,那么需要额外设置访问权限:field.setAccessible(true)
  4. 使用field.set(Object obj, Object value)方法设置指定对象的字段值
@Test
public void test_getDeclaredField() throws Exception {
    Class<?> studentClazz = Class.forName("Student");
    // 1. 先构造对象
    Student student = (Student) studentClazz.newInstance();
    // 2. 获取指定字段
    Field field = studentClazz.getDeclaredField("age");
    // 3. 如果是私有字段,需要设置访问权限
    field.setAccessible(true);
    // 4. 调用set方法
    field.set(student, 25);
    System.out.println(student); // Student{name='jack', age=25}
}
1.3.5 通过反射获取指定的方法

我们可以通过Class的getDeclaredMethod()方法获取指定的方法

语法格式:Class.forName(className).getDeclaredMethod(String name, Class<?>... params)

使用步骤:

  1. 使用 Method method = Class.forName(className).getDeclaredMethod(String name, Class<?>... params)获取指定方法
  2. 创建该反射类的实例对象obj
  3. 如果该方法修饰符为private,那么需要额外设置访问权限:method.setAccessible(true)
  4. 使用method.invoke(Object obj, Object... params)调用方法
@Test
public void test_getDeclaredMethod() throws Exception {
    // 1. 获取Class对象
    Class<?> studentClazz = Class.forName("Student");
    // 2. 获取指定方法
    Method method = studentClazz.getDeclaredMethod("eat", String.class);
    // 3. 如果method为私有,额外设置访问权限
    method.setAccessible(true);
    // 4. 指定调用方法的对象
    Student student = (Student) studentClazz.newInstance();
    // 5. 调用方法
    method.invoke(student, "水果"); // jack在吃水果
}

1.4 反射总结

反射优点:

  1. 对于任意一个类来说,通过反射可以获取它的所有内部信息,对于任意一个对象来说,能够访问甚至修改它的属性以及调用其方法
  2. 反射增加了程序的灵活性和可扩展性,提高自适应能力
  3. 已经被大量运用于框架源码中,如Hibernate、Spring中

反射缺点:

  1. 使用反射会具有效率问题
  2. 使用反射绕过了源代码封装等机制,且实现较为复杂,可读性不高

2. Java枚举的使用

2.1 枚举的背景和定义

枚举:枚举是在JDK1.5之后引入的,其用途是将一组常量组织起来进行管理

本质:枚举的本质就是java.lang.Enum的子类,也就是说如果自己实现了一个枚举类,默认继承自java.lang.Enum

2.2 枚举的使用

  1. 枚举字段配合switch语句使用

    public enum TestEnum {
        RED,
        GREEN,
        BLUE;
        
        public static void main(String[] args) {
            TestEnum color = TestEnum.RED;
            switch (color) {
                case RED:
                    System.out.println("红色");
                    break;
                case GREEN:
                    System.out.println("绿色");
                    break;
                case BLUE:
                    System.out.println("蓝色");
                    break;
                default:
                    System.out.println("其他");
                    break;
            }
        }
    }
    
  2. 枚举的常用方法

    方法描述
    values()以数组的形式返回所有的枚举类型成员
    valueOf(“”)将指定的字符串转化为对应枚举实例
    ordinal()返回枚举成员的索引位置
    compareTo()比较两个枚举成员在定义时的顺序

    示例:

    public enum TestEnum {
        RED,
        GREEN,
        BLUE;
    
        public static void main(String[] args) {
            // 1. 通过values方法返回所有枚举类型数组
            TestEnum[] testEnums = TestEnum.values();
            for (TestEnum testEnum : testEnums) {
                System.out.println(testEnum);
            }
            // 2. 通过valueOf方法返回指定名称的枚举类型
            TestEnum testEnum = TestEnum.valueOf("BLUE");
            System.out.println(testEnum); // BLUE
            // 3. 通过ordinal方法返回定义的索引
            System.out.println(testEnum.ordinal()); // 2
            // 4. 通过compareTo方法比较定义的顺序
            TestEnum red = TestEnum.RED;
            TestEnum green = TestEnum.GREEN;
            if (red.compareTo(green) < 0) {
                System.out.println(red  + "在" + green + "前面"); // RED在GREEN前面
            } else if (red.compareTo(green) > 0) {
                System.out.println(green + "在" + red + "前面");
            }
        }
    }
    
    
  3. 枚举类也可以定义构造方法

    (重要)注:枚举类的构造方法默认是私有的

    public enum TestEnum {
        RED("红色", 0),
        GREEN("绿色", 1),
        BLUE("蓝色", 2);
    
        private String name;
        private int key;
        
        private TestEnum(String name, int key) {
            this.name = name;
            this.key = key;
        }
    
        public static TestEnum getEnumKey(int key) {
            for (TestEnum testEnum : TestEnum.values()) {
                if (testEnum.key == key) {
                    return testEnum;
                }
            }
            return null;
        }
    
        public static void main(String[] args) {
            System.out.println(getEnumKey(2)); // BLUE
        }
    }
    

2.3 枚举和反射

我们刚才介绍了反射机制可以调用类对象的私有方法,现在我们看到枚举类型的构造方法是私有的,我们是否也能够尝试利用反射机制获取对应的实例对象呢?

当我们尝试编写如下代码时却报错了:

public static void main(String[] args) throws Exception {
    // 尝试利用反射机制获取实例
    Class<?> clazz = Class.forName("TestEnum");
    // 获取构造器
    Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class);
    // 设置访问权限
    constructor.setAccessible(true);
    // 创建实例
    TestEnum testEnum = (TestEnum) constructor.newInstance("黑色", 5);
    System.out.println(testEnum);
}

在这里插入图片描述

我们根据异常信息可以推断出NoSuchMethodException异常,我们可以意识到由于编译器会自动完成对其父类的初始化,所以我们至少需要4个参数,其中两个参数用于父类初始化,所以我们将代码更改如下:

public static void main(String[] args) throws Exception {
    // 尝试利用反射机制获取实例
    Class<?> clazz = Class.forName("TestEnum");
    // 获取构造器
    Constructor constructor = clazz.getDeclaredConstructor(String.class, int.class, String.class, int.class);
    // 设置访问权限
    constructor.setAccessible(true);
    // 创建实例
    TestEnum testEnum = (TestEnum) constructor.newInstance("黑色", 5);
    System.out.println(testEnum);
}

但是又抛出了如下异常:

在这里插入图片描述

此时我们可以得出结论:枚举类型是不允许被反射创建的!!!

2.4 枚举总结

  1. 枚举实质上是一个类,其构造方法是私有的,这个类默认继承自java.lang.Enum
  2. 枚举可以避免反射和序列化问题
  3. 枚举是比较安全的(不能被反射实例化对象)——因此可以实现安全的单例模式
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值