反射与枚举

反射与枚举_第1张图片

⭐️前言⭐️

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

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

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

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

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


请添加图片描述

内容导读

  • 反射与枚举
    • 1.反射
      • 1.1 定义
      • 1.2 原理
      • 1.3 反射对象类中的常用方法(方法的使用在1.4中)
      • 1.4 反射示例
        • 1.4.1 获取反射中的Class对象
        • 1.4.2 通过反射创建类对象
        • 1.4.3 通过反射获取类属性、方法、构造器
        • 1.4.4 通过反射获取属性值及执行方法
      • 1.5 反射优点和缺点
    • 2.枚举
      • 2.1 背景及定义
      • 2.2 枚举的使用
      • 2.3 枚举和反射
      • 2.4 总结

反射与枚举

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.(java.lang.String, int),意思是没有对应的构造方法,但我们所提供的枚举类的构造方法就是两个参数Stringint
这是因为所有的枚举类都默认继承于 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,我们从源码中查找原因
反射与枚举_第2张图片
也就是说,枚举在这里被过滤了,不能通过反射获取枚举类的实例

2.4 总结

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

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

请添加图片描述

你可能感兴趣的:(JavaSE,java)