枚举类与注解

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2022-06-25

枚举类

当类的对象是有限个、确定的,需要定义一组常量时,则需要使用枚举(enumeration)类。如果枚举类只有一个对象,则可以作为一种单例模式的实现方式。

枚举类的属性:

  • 枚举类对象的属性不应允许被改动,所以应使用private final修饰
  • 枚举类使用private final修饰的属性应该在构造器中为其赋值
  • 若枚举类显示的定义了带参的构造器,则在列出枚举值时也必须对应传入参数。

定义枚举类

/**
 * 方式一、jak5.0之前,自定义枚举类
 * 方式二、jak5.0后,使用enum关键字定义枚举类
 */
public class SeasonTest {
  public static void main(String[] args) {
    Season spring = Season.SPRING;
    System.out.println(spring); // Season{seasonName='春天', seasonDesc='春暖花开'}
    System.out.println(spring.getSeasonName()); // 春天

    Season1 summer = Season1.SUMMER;
    System.out.println(summer); // SUMMER
    // 定义的枚举类默认继承于java.lang.Enum类
    System.out.println(Season1.class.getSuperclass()); // class java.lang.Enum
  }
}

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

  // 2、声明类对象的属性,需要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;
  }
}


// 自定义枚举类
class Season {
  // 1、声明类对象的属性,需要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 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;
  }

  // 5、其他诉求2:提供toString()方法
  @Override
  public String toString() {
    return "Season{" +
            "seasonName='" + seasonName + '\'' +
            ", seasonDesc='" + seasonDesc + '\'' +
            '}';
  }
}

Enum类的常用方法

public class EnumTest {
  public static void main(String[] args) {
    // values()方法
    Season2[] values = Season2.values();
    for (int i = 0; i < values.length; i++) {
      System.out.println(values[i]); // SPRING SUMMER AUTUMN WINTER 不是字符串,是枚举类
    }

    TestEnum[] values1 = TestEnum.values();
    for (int i = 0; i < values1.length; i++) {
      System.out.println(values1[i]); // LOADING SUCCESS FAILURE 不是字符串,是枚举类
    }

    // valueOf(String objName):返回枚举类中对象名是objName的对象
    Season2 winter = Season2.valueOf("WINTER");
    System.out.println(winter); // WINTER 不是字符串,是枚举类

    TestEnum loading = TestEnum.valueOf("LOADING");
    System.out.println(loading); // LOADING 不是字符串,是枚举类

    // Error
    // TestEnum loading1 = TestEnum.valueOf("LOADING1");
    // System.out.println(loading1);

    // toString()
    TestEnum success = TestEnum.SUCCESS;
    System.out.println(success.toString()); // "SUCCESS" 是字符串
  }
}

enum TestEnum {
  LOADING,
  SUCCESS,
  FAILURE;
}


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

  // 2、声明类对象的属性,需要private final修饰。
  private final String seasonName;
  private final String seasonDesc;

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

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

  public String getSeasonDesc() {
    return seasonDesc;
  }
}

实现接口

public class EnumTest2 {
  public static void main(String[] args) {
    TestEnum2 loading = TestEnum2.LOADING;
    loading.show(); // "这是一个 测试枚举类"
    loading.say(); // "加载中"

    TestEnum2[] values = TestEnum2.values();
    for (int i = 0; i < values.length; i++) {
      values[i].say(); // 加载中 成功了 失败了
    }

    if (EnumTest2.getEnum("LOADING") == TestEnum2.LOADING) {
      System.out.println("true");
    }
  }

  public static TestEnum2 getEnum(String str) {
    return TestEnum2.valueOf(str);
  }
}

interface Info {
  void show();
  void say();
}

enum TestEnum2 implements Info {
  // 在枚举类的对象中分别实现接口中的抽象方法
  LOADING {
    @Override
    public void say() {
      System.out.println("加载中");
    }
  },
  SUCCESS {
    @Override
    public void say() {
      System.out.println("成功了");
    }
  },
  FAILURE {
    @Override
    public void say() {
      System.out.println("失败了");
    }
  };

  @Override
  public void show() {
    System.out.println("这是一个测试枚举类");
  }
}

注解

从JDK5.0开始,Java增加对元数据(MetaData)的支持,也就是注解(Annotation)。

注解其实就是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用注解,可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充工具。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或进行部署。

注解可以向修饰符一样被使用,可以修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被保存在注解的键值对中。

自定义注解

1、定义新的Annotation类使用 @interface 关键字

2、自定义注解自动继承了java.lang.annotation.Annotation接口

3、注解的成员变量在定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型,我们称为配置参数,类型只能是八种基本数据类型,以及StringClassEnumAnnotation类型。

4、可以在定义注解的成员变量时指定初始值,指定成员变量的初始值可使用default关键字。

5、如果只有一个参数成员,建议使用参数名为value

6、如果定义的注解含有配置参数,那么使用时必须指定参数值,除非有默认值。格式是参数名=参数值,如果只有一个参数成员,则名称为value,可以省略value=

7、没有成员变量的注解称为标记,包含成员变量的注解称为元数据。

8、自定义注解必须配上注解的信息处理流程才有意义。

定义:

// MyAnnotation.java
/**
 * 自定义注解
 */
public @interface MyAnnotation {
  String value();
  String name() default "anno";
}

使用:

package com.cz.java;

public class AnnotationTest {
}

// @MyAnnotation("hello") // 将hello传给value,name有默认值可以不穿
@MyAnnotation(name = "jack", value = "hello") // 显示指定
class Person {
  private String name;
  private int age;

  public Person() {
  }

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

上面示例,仅仅只是展示注解的定义和使用,没有任何信息处理流程,而这一部分需要使用反射,将会在后面提到。

JDK中的元注解

理解与元数据类似,即对现有数据进行修饰的数据,元注解即对现有注解进行解释说明的注解。以下为java中的四种元注解:

1、Retention

用于指定该注解的声明周期,它包含了一个RetentionPolicy类型的成员变量:

  • RetentionPolicy.SOURCE: 在源文件中有效,编译器直接丢弃这种策略的注释
  • RetentionPolicy.CLASS: 在class文件中有效,当运行java程序时,JVM不会保留注释,是该成员变量的默认值
  • RetentionPolicy.RUNTIME: 在运行时有效,当运行java程序时,JVM会保留注释,程序可以通过反射获取该注释

2、Target

用于指定被修饰的注解能用于修饰哪些程序元素,也包含一个名为value的成员变量。

  • CONSTUCTOR: 用于描述构造器
  • FIELD: 用于描述域
  • LOCAL_VARIABLE: 用于描述局部变量
  • METHOD: 用于描述方法
  • PACKAGE: 用于描述包
  • PARAMETER: 用于描述参数
  • TYPE: 用于描述类、接口或enum声明

3、Documented

用于指定该注解修饰的类将被javadoc工具提取成文档,默认情况下,javadoc是不包括注解的。定义Documented注解必须设置Retention值为 RUNTIME

4、Inherited

被它修饰的注解将具有继承性,如果某个类使用了被 @Inherited修饰的注解,则其子类将自动具有该注解。

如果把标有@Inherited注解的自定义的注释标注在类级别上,子类可以继承父类类级别的注解,实际应用中,很少使用。

自定义注解通常都会指明两个元注解,RetentionTarget,另外两个使用频率较低。

可重复注解

在jdk8.0之前使用重复注解的话,需要扩展一个注解容器。这个注解容器与可重复注解的@Retention@Target要一致。

// MyAnnotations.java
// 注解容器
public @interface MyAnnotations {
  MyAnnotation[] value();
}

对应注解:

public @interface MyAnnotation {
  String value();
  String name() default "anno";
}

使用:

@MyAnnotations({ @MyAnnotation("hello"), @MyAnnotation(name="jack", value = "hello")})
class Person {
  private String name;
  private int age;

  public Person() {
  }

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

在jdk8.0之后,提供了 @Repeatable() 注解来简化重复注解,只需要在想要重复注解的注解上加上该元注解并传入注解容器即可。

// MyAnnotation.class
// 注意参数是MyAnnotations注解容器
@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
  String value();
  String name() default "anno";
}

使用:

@MyAnnotation("hello")
@MyAnnotation(name="jack", value = "hello")
class Person {
  private String name;
  private int age;

  public Person() {
  }

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

类型注解

jdk8之后,关于元注解@Target的参数类型ElementType枚举值多了两个,之前注解只能在声明的地方使用,jdk8之后注解可以应用在任何定地方,就是新增的这两个来体现的。

  • ElementType.TYPE_PARAMETER:表明该注解能写在类型变量的声明语句中,如泛型声明。
  • ElementType.TYPE_USE:表明该注解能写在使用类型的任何语句中

示例1:

public class TestTypeDefine<@TypeDefine() U> {
  private U u;
  public <@TypeDefine() T> void test(T t) {}
}

// 声明之后,注解就能修饰泛型了,如上
@Target({ElementType.TYPE_PARAMETER})
@interface TypeDefine {

}

示例2:

// 注解没有成员变量可不写()
// 修饰泛型
class Generic<@TypeDefine T> {

  // 修饰异常
  public void show() throws @TypeDefine RuntimeException {
    // 修饰泛型
    ArrayList<@TypeDefine String> list = new ArrayList<>();

    // 修饰强转的类型
    int num = (@TypeDefine  int) 10L;
  }
}

// 声明之后,所有声明类型的地方都可以使用了
@Target({ElementType.TYPE_USE})
@interface TypeDefine {

}