反射与枚举

简介: 本篇文章主要介绍Java语法中的反射与枚举部分。

1.反射

1.1 定义


Java的反射机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。


1.2 原理


Java程序运行前,首先会通过编译将Java代码生成.class文件;程序运行过程中使用某个类时,如果该类还未被加载到内存中,系统会将该类的.class字节码文件读入内存,同时JVM会产生一个java.lang.Class对象代表该.class字节码文件。

通过Class对象可以得到大量的Method、Constructor、Field等对象,这些对象分别代表该类所包括的方法、构造器和属性等,反射的工作原理就是通过这些对象来执行实际的功能,例如调用方法、创建实例等。


类名 用途
Class类 代表类的实体,在运行的Java应用程序中表示类和接口
Field类 代表类的成员变量/类的属性
Method类 代表类的方法
Constructor类 代表类的构造方法


1.3 反射对象类中的常用方法(方法的使用在1.4中)


常用获得类相关的方法(以下方法返回值与Class相关)


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


常用获得类中属性相关的方法(以下方法返回值为Field相关)


方法 用途
getField(String name) 获得某个公有的属性对象(指定参数)
getFields() 获得所有公有的属性对象
getDeclaredField(String name) 获得某个属性对象(指定参数)
getDeclaredFields() 获得所有属性对象


获得类中构造器相关的方法(以下方法返回值为Constructor相关)


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


获得类中方法相关的方法(以下方法返回值为Method相关)


方法 用途

getMethod(String name, Class…<?> parameterTypes)

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


注意:在方法中声明“Declared”后,就可以获得对象私有的属性和方法,但是必须将该对象的accessible=true,否则抛出IllegalAccessException异常。


1.4 反射示例


首先我们在一个包下写一个类,该类中有私有和公有的各类成员变量和方法,然后我们利用反射来获取该类的方法、构造器或属性等。


package reflect;
public class Person {
    //私有属性name
    private String name = "张三";
    //公有属性age
    public int age;
    //不带参数的构造方法
    public Person() {
        System.out.println("Person()");
    }
    //带参数的私有构造方法
    private Person(String name, int age) {
        this.age = age;
        this.name = name;
        System.out.println("Person(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 "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}


1.4.1 获取反射中的Class对象


想获取类的方法、构造器或属性等,必须先获取该类的Class对象;

且一个类只有一个Class对象,所以代码示例中的打印结果全为true.


 

//1.使用 Class.forName() 静态方法,需要知道类的全路径名
        Class<?> c1=Class.forName("reflect.Person");
        //2.使用.class方法
        Class<?> c2=Person.class;
        //3.使用类对象的getClass()方法
        Person person=new Person();
        Class<?> c3=person.getClass();
        System.out.println(c1==c2);
        System.out.println(c1==c3);
        System.out.println(c2==c3);
   //
   true
   true
   true


1.4.2 通过反射创建类对象


 

Class<?> c=Person.class;
        //1.通过Class对象的newInstance()方法
        Person person1=(Person) c.newInstance();
        //2.通过 Constructor 对象的 newInstance() 方法
        Constructor<?> constructor= c.getConstructor();
        Person person2=(Person) constructor.newInstance();


1.4.3 通过反射获取类属性、方法、构造器

Class<?> c=Person.class;
        //获取name属性
        Field field=c.getDeclaredField("name");
        //获取自身和父类的公有属性(不包括私有的)
        Field[] fields= c.getFields();
        //获取自身的公有和私有属性
        Field[] declaredFields = c.getDeclaredFields();
        //获取function方法,参数为String
        Method method= c.getMethod("function", String.class);
        //获取自身和父类的公有方法(不包括私有的)
        Method[]methods= c.getMethods();
        //获取自身的公有和私有方法
        Method[] declaredMethods = c.getDeclaredMethods();
        //获取无参的构造方法
        Constructor<?> constructor=c.getConstructor();
        //获取参数为String和int的构造方法
        Constructor<?> constructor1=c.getDeclaredConstructor(String.class,int.class);
        //获取自身和父类的构造方法(不包括私有的)
        Constructor<?>[]constructors=c.getConstructors();


1.4.4 通过反射获取属性值及执行方法


public static void main(String[] args) {
        try {
            Class<?> c=Person.class;
            //通过构造方法创建对象
            Constructor<?> constructor= c.getDeclaredConstructor(String.class,int.class);
            constructor.setAccessible(true);
            Person person=(Person) constructor.newInstance("王五",18);
            System.out.println(person);
            System.out.println("=====================");
            //字段属性
            Field field=c.getDeclaredField("name");
            field.setAccessible(true);
            field.set(person,"李四");
            System.out.println(person);
            System.out.println("=====================");
            //方法属性
            Method method= c.getDeclaredMethod("function", String.class);
            method.setAccessible(true);
            method.invoke(person,"我是通过反射给你传参的");//执行方法
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    //
Person(String,name)
Person{name='王五', age=18}
=====================
Person{name='李四', age=18}
=====================
我是通过反射给你传参的


1.5 反射优点和缺点


优点:


  • 反射增加了程序的灵活性。
  • 反射可以让程序有很好的拓展性。
  • 反射可以在不知道会运行哪一个类的情况下,获取到类的信息,创建对象以及操作对象;我们无需将类型硬编码写死,方便程序拓展,降低耦合度。


缺点:


  • 反射更容易出现运行时错误。
  • 使用显式的类和接口,编译器能帮我们做类型检查,但使用反射需要运行时才知道类型,此时编译器也爱莫能助。

  • 反射性能不高。
  • 反射是一种解释操作,在访问字段和调用方法前,需要查找到对应的 Field 和 Method,性能要低一些。
  • 因此反射机制主要应用在对灵活性和拓展性要求较高的系统框架上。

  • 反射会带来安全性问题。
  • 反射可以随意修改私有属性和访问私有方法,破坏了类的封装,可能导致逻辑错误或者存在安全隐患。


2.枚举


2.1 背景及定义


枚举是在JDK1.5以后引入的。主要用途是:将一组常量组织起来


public static int final RED = 1;
public static int final GREEN = 2;
public static int final BLACK = 3;

但是这种常量举例有不好的地方,比如:碰巧有个数字1,但是它可能会误认为是RED,现在我们可以直接用枚举类型来进行组织,这样就有了区分


public enum TestEnum { 
  RED,BLACK,GREEN; 
}


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

这个类。


2.2 枚举的使用


1.switch语句


public enum TestEnum {
    RED,BLACK,GREEN,WHITE;
    public static void main(String[] args) {
        TestEnum testEnum=TestEnum.BLACK;
        switch (testEnum) {
            case RED: System.out.println("red");
                break;
            case BLACK: System.out.println("black");
                break;
            case WHITE: System.out.println("WHITE");
                break;
            case GREEN: System.out.println("black");
                break;
            default:
                break;
        }
    }
}
//
black
Process finished with exit code 0


2.常用方法


Enum类的常用方法


方法名称 描述
values() 以数组形式返回枚举类型的所有成员
ordinal() 获取枚举成员的索引位置
valueOf() 将普通字符串转换为枚举实例
compareTo() 比较两个枚举成员在定义时的顺序


示例:


public enum TestEnum {
    RED, BLACK, GREEN, WHITE;
    public static void main(String[] args) {
        //values()方法
        TestEnum[] testEnums = TestEnum.values();
        for (int i = 0; i < testEnums.length; i++) {
            //ordinal()方法
            System.out.println(testEnums[i] + " 序号: " + testEnums[i].ordinal());
        }
        System.out.println("==============");
        //valueOf()方法
        TestEnum testEnum = TestEnum.valueOf("GREEN");
        System.out.println(testEnum);
        System.out.println("==============");
        TestEnum testEnum1 = TestEnum.BLACK;
        TestEnum testEnum2 = TestEnum.RED;
        //compareTo()方法
        System.out.println(testEnum1.compareTo(testEnum2));
        System.out.println(BLACK.compareTo(RED));
        System.out.println(RED.compareTo(BLACK));
    }
}
//
RED 序号: 0
BLACK 序号: 1
GREEN 序号: 2
WHITE 序号: 3
==============
GREEN
==============
1
1
-1
Process finished with exit code 0


事实上在Java中,枚举就是一个类,所以我们在定义枚举类时,如果枚举对象有参数,需要提供相应的构造函数。

枚举的构造方法默认是私有的


public enum TestEnum {
    RED("red",2), BLACK("black",3), 
    GREEN("green",4), WHITE("white",5);
    public String color;
    public int ordinal;
    TestEnum(String color, int ordinal) {
        this.color = color;
        this.ordinal = ordinal;
    }
}


2.3 枚举和反射


我们了解到,任何一个类,哪怕其属性或者方法是私有的,我们都可以通过反射来获取到该实例对象,枚举的构造方法也是私有的,我们可以尝试是否可以通过反射来获取到。


我们还以2.2中最后的枚举类为例:


public enum TestEnum {
    RED("red", 2), BLACK("black", 3),
    GREEN("green", 4), WHITE("white", 5);
    public String color;
    public int ordinal;
    TestEnum(String color, int ordinal) {
        this.color = color;
        this.ordinal = ordinal;
    }
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> c = TestEnum.class;
        Constructor<?> constructor = c.getDeclaredConstructor(String.class, int.class);
        constructor.setAccessible(true);
        TestEnum testEnum = (TestEnum) constructor.newInstance("海雾蓝", 9);
        System.out.println(testEnum);
    }
}
//
Exception in thread "main" java.lang.NoSuchMethodException: TestEnum.<init>(java.lang.String, int)
  at java.lang.Class.getConstructor0(Class.java:3082)
  at java.lang.Class.getDeclaredConstructor(Class.java:2178)
  at TestEnum.main(TestEnum.java:19)
Process finished with exit code 1


运行后报错 java.lang.NoSuchMethodException: TestEnum.<init>(java.lang.String, int),意思是没有对应的构造方法,但我们所提供的枚举类的构造方法就是两个参数String和int。

这是因为所有的枚举类都默认继承于 java.lang.Enum ,所以在构造子类时,要先帮助父类进行构造,Enum的源码:


protected Enum(String name, int ordinal) { 
  this.name = name; 
  this.ordinal = ordinal; 
}


所以我们需要提供四个参数来完成构造


public enum TestEnum {
    RED("red", 2), BLACK("black", 3),
    GREEN("green", 4), WHITE("white", 5);
    public String color;
    public int ordinal;
    TestEnum(String color, int ordinal) {
        this.color = color;
        this.ordinal = ordinal;
    }
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> c = TestEnum.class;
        Constructor<?> constructor = c.getDeclaredConstructor(String.class, int.class,String.class, int.class);
        constructor.setAccessible(true);
        TestEnum testEnum = (TestEnum) constructor.newInstance("海雾蓝", 9);
        System.out.println(testEnum);
    }
}
//
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
  at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
  at TestEnum.main(TestEnum.java:21)
Process finished with exit code 1


此时仍然报错,但报错原因变为了java.lang.IllegalArgumentException: Cannot reflectively create enum objects,我们从源码中查找原因


微信图片_20230111105527.png

也就是说,枚举在这里被过滤了,不能通过反射获取枚举类的实例。


2.4 总结

枚举本身就是一个类,其构造方法默认为私有的,且都是默认继承与 java.lang.Enum

枚举可以避免反射和序列化问题

枚举优点缺点


优点:

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


缺点:

不可继承,无法扩展

相关文章
|
4月前
|
开发框架 Java 编译器
Java反射,枚举讲解
Java反射,枚举讲解
45 0
|
7天前
枚举和注解
枚举是常量集合,表现为特殊类,包含有限特定对象。适用于只读场景。实现方式有两种:自定义类和使用`enum`关键字。自定义类实现时,常量用`final static`修饰,名称全大写。枚举可含多个属性。示例中展示了自定义枚举类`Season`,包含四个季节实例,构造器私有化且无setter,确保不可修改。
11 1
|
API C#
C#反射与特性(三):反射类型的成员
C#反射与特性(三):反射类型的成员
252 0
|
9月前
|
Java 编译器 C++
常量接口 vs 常量类 vs 枚举区别
把常量定义在接口里与类里都能通过编译,那2者到底有什么区别呢?
44 0
|
9月前
|
设计模式 JSON Java
枚举类——用好枚举真的没有那么简单!
枚举类——用好枚举真的没有那么简单!
69 0
|
9月前
|
Java API
Java反射(通过反射获取构造函数、方法、属性)
1.通过反射获取构造函数,2.通过反射获取方法,3.通过反射调用成员属性
85 0
|
12月前
|
Java 编译器 Spring
|
Java 程序员 API
枚举,注解 ,反射
枚举,注解 ,反射
57 0
|
安全 Java 编译器
枚举使用、转数组、实现接口、枚举单例
枚举使用、转数组、实现接口、枚举单例
96 0