• 反射与枚举


    在这里插入图片描述
    请添加图片描述

    ⭐️前言⭐️

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

    🍉博客主页: 🍁【如风暖阳】🍁
    🍉精品Java专栏【JavaSE】【备战蓝桥】、【JavaEE初阶】【MySQL】【数据结构】
    🍉欢迎点赞 👍 收藏留言评论 📝私信必回哟😁

    🍉本文由 【如风暖阳】 原创,首发于 CSDN🙉

    🍉博主将持续更新学习记录收获,友友们有任何问题可以在评论区留言

    🍉博客中涉及源码及博主日常练习代码均已上传码云(gitee)GitHub


    请添加图片描述

    请添加图片描述

    反射与枚举

    🍅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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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
    • 2
    • 3
    • 4
    • 5
    • 6

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    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
    • 2
    • 3

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

    public enum TestEnum { 
    	RED,BLACK,GREEN; 
    }
    
    • 1
    • 2
    • 3

    本质:是 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    事实上在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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    运行后报错 java.lang.NoSuchMethodException: TestEnum.(java.lang.String, int),意思是没有对应的构造方法,但我们所提供的枚举类的构造方法就是两个参数Stringint
    这是因为所有的枚举类都默认继承于 java.lang.Enum ,所以在构造子类时,要先帮助父类进行构造,Enum的源码:

    protected Enum(String name, int ordinal) { 
    	this.name = name; 
    	this.ordinal = ordinal; 
    }
    
    • 1
    • 2
    • 3
    • 4

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

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    此时仍然报错,但报错原因变为了java.lang.IllegalArgumentException: Cannot reflectively create enum objects,我们从源码中查找原因
    在这里插入图片描述
    也就是说,枚举在这里被过滤了,不能通过反射获取枚举类的实例

    2.4 总结

    • 枚举本身就是一个类,其构造方法默认为私有的,且都是默认继承与 java.lang.Enum
    • 枚举可以避免反射和序列化问题
    • 枚举优点缺点
      优点:
      1.枚举常量更简单安全 。 2. 枚举具有内置方法 ,代码更优雅
      缺点:
      不可继承,无法扩展

    ⭐️最后的话⭐️
    总结不易,希望uu们不要吝啬你们的👍哟(^U^)ノ~YO!!如有问题,欢迎评论区批评指正😁

    请添加图片描述

  • 相关阅读:
    科技成果验收测试需注意哪些方面?
    rviz建图拉点导航
    YOLOv1 沉思录
    【JavaWeb】会话跟踪技术Cookie与Session原始真解
    git标签基础
    地震勘探——相关概念(一)
    SpringCloud - 微服务
    【iMessage苹果家庭群发推】 l NotificationUID:通知ID,可以通过此ID获取详情
    云原生SIEM解决方案
    大规模 Transformer 模型 8 比特矩阵乘简介 - 基于 Hugging Face Transformers、Accelerate 以及 bitsandbytes
  • 原文地址:https://blog.csdn.net/qq_60856948/article/details/126102457