08.枚举类和注解


枚举类的使用

1.枚举类的说明

1.枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类

2.当需要定义一组常量时,强烈建议使用枚举类

3.如果枚举类中只有一个对象,则可以作为单例模式的实现方式。

enum 枚举类类名 {
	对象1,对象2,对象3;
}

4.特点

  • 1.枚举类里的构造器默认被private修饰,不能使用除了private以外的其他权限修饰符,目的是为了避免外部创建对象

  • 2.枚举类里构造器允许重载

  • 3.对象只能在枚举类的内部创建,而且创建的对象默认被 public static final 修饰(所以不能被继承,不能被修改)

  • 4.创建枚举对象的,不是直接调用构造器new对象,而是只写枚举对象名()

    如果调用的是无参的枚举构造器,() 可以省略;如果调用的是有参的构造器,()中传入参数

  • 5.创建的枚举对象通常会使用全大写,表示是一个常量

  • 6.创建枚举对象的代码必须要写在第一行

  • 7.所有定义的枚举类都自动继承自 java.lang.Enum 类,所以枚举不能再继承其他类

  • 8.枚举可以实现接口

5.java.lang.Enum 自带两个属性:name ordinal ,所有每个枚举都自带这两个属性

2.如何自定义枚举类

1.使用java原生代码实现

//自定义枚举类
class Season{
    //1.声明Season对象的属性:private final修饰
    private final String seasonName;
    private final String seasonDesc;

    //2.私化类的构造器,并给对象属性赋值
    private Season(String seasonName,String seasonDesc){
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }

    //3.提供当前枚举类的多个对象:public static final的
    public static final Season SPRING = new Season("春天","春暖花开");
    public static final Season SUMMER = new Season("夏天","夏日炎炎");
    public static final Season AUTUMN = new Season("秋天","秋高气爽");
    public static final Season WINTER = new Season("冬天","冰天雪地");


    //4.其他诉求1:获取枚举类对象的属性
    public String getSeasonName() {
        return seasonName;
    }

    public String getSeasonDesc() {
        return seasonDesc;
    }
    //4.其他诉求1:提供toString()
    @Override
    public String toString() {
        return "Season{" +
                "seasonName='" + seasonName + '\'' +
                ", seasonDesc='" + seasonDesc + '\'' +
                '}';
    }
}

2.jdk 5.0 新增使用enum定义枚举类

//使用enum关键字枚举类
enum Season1 {
    //1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
    SPRING("春天","春暖花开"),
    SUMMER("夏天","夏日炎炎"),
    AUTUMN("秋天","秋高气爽"),
    WINTER("冬天","冰天雪地");

    //2.声明Season对象的属性:private final修饰
    private final String seasonName;
    private final String seasonDesc;

    //3.私化类的构造器,并给对象属性赋值
    private Season1(String seasonName,String seasonDesc){
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }
    //4.其他诉求1:获取枚举类对象的属性
    public String getSeasonName() {
        return seasonName;
    }
    public String getSeasonDesc() {
        return seasonDesc;
    }
}

3.枚举常见方法

enum 继承于 java.lang.Enum 类,Enum 类中重写了方法

调用方法: 类名.对象名.方法名

方法名 作用
toString 返回的是常量名(对象名),可以重写
name 返回的是常量名(对象名),不推荐使用,建议使用toString
values 返回该枚举类的所有的常量对象,返回类型是当前枚举的数组类型。
valueOf 根据常量名,返回一个枚举对象。
ordinal 返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。
Season1 summer = Season1.SUMMER;
        //toString():返回枚举类对象的名称
        System.out.println(summer.toString());
//         System.out.println(Season1.class.getSuperclass());
        System.out.println("****************");
        //values():返回所的枚举类对象构成的数组
        Season1[] values = Season1.values();
        for(int i = 0;i < values.length;i++){
            System.out.println(values[i]);
        }
        System.out.println("****************");

        Thread.State[] values1 =  Thread.State.values();
        for (int i = 0; i < values1.length; i++) {
            System.out.println(values1[i]);
        }

        //valueOf(String objName):返回枚举类中对象名是objName的对象。
        Season1 winter = Season1.valueOf("WINTER");
        //如果没objName的枚举类对象,则抛异常:IllegalArgumentException
//        Season1 winter =  Season1.valueOf("WINTER1");
        System.out.println(winter);

4.枚举类中对象分别实现接口

interface Info{
    void show();
}
//使用enum关键字枚举类
enum Season1 implements Info{
    //1.提供当前枚举类的对象,多个对象之间用","隔开,末尾对象";"结束
    SPRING("春天","春暖花开"){
        @Override
        public void show() {
            System.out.println("春天在哪里?");
        }
    },
    SUMMER("夏天","夏日炎炎"){
        @Override
        public void show() {
            System.out.println("宁夏");
        }
    },
    AUTUMN("秋天","秋高气爽"){
        @Override
        public void show() {
            System.out.println("秋天不回来");
        }
    },
    WINTER("冬天","冰天雪地"){
        @Override
        public void show() {
            System.out.println("大约在冬季");
        }
    };
}

5.通过javap反编译理解枚举--单例模式最简单的实现方式

public enum Enumerate {
    INSTANCE;
}
Compiled from "Enumerate.java"

public final class Enumerate extends java.lang.Enum {
  public static final Enumerate INSTANCE;
  private static final Enumerate[] $VALUES;
  public static Enumerate[] values();
  public static Enumerate valueOf(java.lang.String);
  private Enumerate();
  static {};
}

上面是 javap 反编译 Enumerate.class 文件,得到 JVM 实际处理我们定义的 Enumerate「枚举」代码,然后我们一起看一下, JVM 对我们定义的「枚举」做了哪些处理,通过这些处理可以看一下「枚举」类型应该具有那些特性。

  • 让我们定义的 Enumerate 继承 java.lang.Enum 类, java.lang.Enum 类接收 Enumerate 泛型。使用了 final 修饰了 Enumerate 类。

  • 通过这个定义,我们知道「枚举」类型的本质也是一个类。「枚举」类型不允许继承其他类,因为 Java 规定类是单继承,而「 枚举」类型已经继承了 Enum 类。「枚举」类型不能被其他类继承,因为「 枚举」类型是用 final 关键字修饰的。
    我们在 Java 代码中定义的 INSTANCE 「枚举」,被定义成了 Enumerate 类的实例对象,并且这个实例对象是用 public static final 修饰的。

  • 我们定义的「枚举」常量是当前「枚举」类型的实例对象,所以他可以调用当前「 枚举」类型中的方法和变量,并且这个实例对象是静态不可变的。

  • 定义了静态、私有的 $VALUES 数组,用来存储我们定义的「枚举」。

  • 定义了静态的 values() 方法,返回当前「枚举」类型中所有的「枚举」常量。

  • 定义了静态的 valueOf 方法,返回指定「枚举」类型的「枚举」常量。

  • 定义了私有的构造方法。

    [枚举] 类型不能被实例化。

  • 定义了 static 静态代码块,用于初始化「枚举」常量和「枚举」常量数组。

我们通过反编译发现我们定义的「枚举」类型继承了 Enum 类,那我们一起来看一下 Enum 类的源码(JDK 1.8)

//Enum类所有Java语言枚举类型的公共基类
public abstract class Enum>
        implements Comparable, Serializable 
    //「枚举」常量的名字
    private final String name
    //「枚举」常量声明时的顺序
    private final int ordinal;
    // 获取「枚举」常量 名字
    public final String name() {
        return name;
    }
    // 获取「枚举」常量 顺序
    public final int ordinal() {
        return ordinal;
    }
    // 构造方法,给「枚举」常量名字和顺序初始化
    protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }
    // 直接返回「枚举」常量名称,获取常量名称推荐使用这个方法,而不是使用 name 方法获取「枚举」常量名称
    public String toString() {
        return name;
    }
    // 直接比较内存地址
    public final boolean equals(Object other) {
        return this == other;
    }
    public final int hashCode() {
        return super.hashCode();
    }
    // 不允许克隆,直接抛出异常
    protected final Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }
    // 用来比较 「枚举」常量 的顺序
    public final int compareTo(E o) {
        java.lang.Enum other = (java.lang.Enum) o;
        java.lang.Enum self = this;
        if (self.getClass() != other.getClass() && // optimization
                self.getDeclaringClass() != other.getDeclaringClass())
            throw new ClassCastException();
        return self.ordinal - other.ordinal;
    }
	// 此方法返回对应于此枚举常量的枚举类型的Class对象
	//compareTo方法中调用了此方法,原因:详见帖子
	//https://blog.csdn.net/anlian523/article/details/102531371
    public final Class getDeclaringClass() {
        Class clazz = getClass();
        Class zuper = clazz.getSuperclass();
        return (zuper == java.lang.Enum.class) ? clazz : zuper;
    }
    // 根据「枚举」类型和「枚举」常量名称返回对应的「枚举」常量
    public static > T valueOf(Class enumType,
                                                          String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
                "No enum constant " + enumType.getCanonicalName() + "." + name);
    }

    protected final void finalize() {
    }
    // 不允许反序列化,直接抛出异常
    private void readObject(ObjectInputStream in) throws IOException,
            ClassNotFoundException {
        throw new InvalidObjectException("can't deserialize enum");
    }
}

「枚举」是单例模式最简单的实现方式。

「枚举」类型的构造函数被编译器定义为 private ,通过 Enum 类源码可以看到,clone 和反序列化直接抛出异常,保证了每个实例对象的唯一性。对象实例都是由 JVM 来负责创建,保证了线程的安全。

下面的代码便是使用「枚举」实现了一个单例,相比于用懒汉、饿汉、双重检测、静态内部类等方式实现单例,简直简单太多了,感觉「枚举」就是 JVM 提供给我们用来实现单例模式的一个语法糖。

public enum Enumerate {
    INSTANCE;
}

6.实战中的学习

注解的使用

1.注解的理解

① jdk 5.0 新增的功能

② Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation,

? 程序员可以在不改变原逻辑的情况下, 在源文件中嵌入一些补充信息。

③在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android

中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。

框架 = 注解 + 反射机制 + 设计模式

2.注解的使用示例

示例一:生成文档相关的注解

? @author 标明开发该类模块的作者,多个作者之间使用,分割

? @version 标明该类模块的版本

? @see 参考转向,也就是相关主题

? @since 从哪个版本开始增加的

? @param 对方法中某参数的说明,如果没有参数就不能写

? @return 对方法返回值的说明,如果方法的返回值类型是void就不能写

? @exception 对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写

@param @return 和 @exception 这三个标记都是只用于方法的。

@param的格式要求:@param 形参名 形参类型 形参说明

@return 的格式要求:@return 返回值类型 返回值说明

@exception的格式要求:@exception 异常类型 异常说明

@param和@exception可以并列多个

示例二:在编译时进行格式检查(JDK内置的3个基本注解)

? @Override: 限定重写父类方法, 该注解只能用于方法

? @Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的择

? @SuppressWarnings: 抑制编译器警告

示例三:跟踪代码依赖性,实现替代配置文件功能

? Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署

image-20220301063933929

image-20220301063825264

? spring框架中关于“事务”的管理

image-20220301064059920

image-20220301064115339

3.如何自定义注解

1.规则

image-20220301065453650

  • 定义新的 Annotation 类型使用 @interface 关键字
  • 自定义注解自动继承了java.lang.annotation.Annotation接口
  • Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、或以上所有类型的数组
  • 可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始值可使用default 关键字
  • 如果只有一个参数成员,建议使用参数名为value
  • 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。格式是“参数名 = 参数值”,如果只有一个参数成员,且名称为value,可以省略“value=”
  • 没有成员定义的 Annotation 称为标记; 包含成员变量的 Annotation 称为元数据 Annotation
    • 注意:自定义注解必须配上注解的信息处理流程才有意义。

2.示例

参照@SuppressWarnings定义

① 注解声明为:@interface

② 内部定义成员,通常使用value表示

③ 可以指定成员的默认值,使用default定义

④ 如果自定义注解没成员,表明是一个标识作用

说明:

? 如果注解有成员,在使用注解时,需要指明成员的值

? 自定义注解必须配上注解的信息处理流程(使用反射)才意义

? 自定义注解通过都会指明两个元注解:Retention、Target

? @Retention 运行时注解还是编译时注解

? @Target注解作用范围

代码举例:

@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME) 
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {
    String value() default "hello";
}

4.元注解

JDK 的元 Annotation 用于修饰其他 Annotation 定义:即对现有的注解进行解释说明的注解

JDK5.0提供了4个标准的meta-annotation类型:@Retention @Target @Documented @Inherited

@Retention

image-20220301070329900

image-20220301070400125

@Target

image-20220301070432553

@Documented

image-20220301070553515

@Inherited

image-20220301070614709

5.如何获取注解信息

通过反射来进行获取、调用 前提:要求此注解的元注解Retention中声明的生命周期状态为:RUNTIME(才会被加载到jvm中)

image-20220301072628001

image-20220301072643230

6.JDK8中注解的新特性:可重复注解、类型注解

1.可重复注解(元注解@Repeatable)

代码示例:

需求:在同一个类上使用2次相同的注解,每次value不同

? ① 在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class

? ② MyAnnotation的Target和Retention等元注解与MyAnnotations相同。(不同则编译时报错)

? @Inherited一个有,则另一个也要有,否则编译不报错,运行时会报错

//jdk 8之前的写法:
//@MyAnnotations({@MyAnnotation(value="hi"),@MyAnnotation(value="hello")})只能使用数组的方式实现
@MyAnnotation(value="hi")
@MyAnnotation(value="hello")
class Person{
}
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
public @interface MyAnnotations {

    MyAnnotation[] value();
}

@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
public @interface MyAnnotation {

    String value() default "hello";
}

2.类型注解(@Target value的补充)

? ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型变量T的声明 然后可以通过反射对其进行其他额外操作)

? ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。(只要是类型,前面就都可以加此注解)

代码示例:

class Generic<@MyAnnotation T>{
    public void show() throws @MyAnnotation RuntimeException{ //异常前
        ArrayList<@MyAnnotation String> list = new ArrayList<>();//泛型前
        int num = (@MyAnnotation int) 10L;//强转类型前
    }
}
@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {

    String value() default "hello";
}

相关