十二、 枚举类与注解
12.1 枚举类的使用
其实就是项目三当中的员工状态,如FREE、BUSY、VACATION等状态类的对象。总的来说,当一个类只有有限个数的常量对象时,如季节、星期、性别、订单状态等,我们就优先考虑使用枚举类。
如果枚举类中只有一个对象,可以作为单例模式的实现方式。
12.1.1 自定义枚举类
JDK 5.0 之前自定义枚举类。
1.使用步骤
- ① 声明枚举类对象的属性:private final;
- ② 私有化枚举类的构造器;
- ③ 提供枚举对象;
- ④ 提供get()属性的方法;
- ⑤ 重写toString方法。
2.例子:自定义枚举类Season
//自定义枚举类Season
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 Season SPRING = new Season("SPRING", "春天花开");//春
public static final Season SUMMER = new Season("SUMMER", "夏日炎炎");//夏
public static final Season AUTUMN = new Season("AUTUMN", "秋高气爽");//秋
public static final Season WINTER = new Season("WINTER", "冰天雪地");//冬
//4.提供get()属性的方法
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
//5.重写toString方法
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
测试:
public class SeasonTest {
public static void main(String[] args) {
Season spring = Season.SPRING;
System.out.println(spring);
}
}
输出:
Season{seasonName='SPRING', seasonDesc='春天花开'}
12.1.2 关键字enum定义枚举类
JDK 5.0 之后使用enum关键字来定义枚举类。
1.使用步骤
① 把 class 改成 enum ;
② 把 "提供枚举对象"移到第一步;
③ 把 “相同的部分” 删掉,多个枚举对象用逗号分隔;
④ toString() 方法一般不重写。
2.例子:关键字enum定义枚举类Season1
//关键字enum定义枚举类Season1
enum Season1 {
//1.提供枚举类的对象,多个对象之间用逗号隔开,末尾分号结束
SPRING("SPRING", "春天花开"),
SUMMER("SUMMER", "夏日炎炎"),
AUTUMN("AUTUMN", "秋高气爽"),
WINTER("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.提供get()属性的方法
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}
测试:
public class SeasonTest1 {
public static void main(String[] args) {
Season1 summer = Season1.SUMMER;
System.out.println(summer);
//查看Season1的父类
System.out.println(Season1.class.getSuperclass());
}
}
输出:
SUMMER
class java.lang.Enum
可以看到,就算不重写 toString() 方法,也能正确输出枚举类。枚举类的父类是 java.lang.Enum。
12.1.3 Enum类常用方法
Enum类主要关注3个方法:
方法 | 作用 |
---|---|
values() | 返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。 |
valueOf (String str ) | 可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”。如果不是,会有运行时异常:IllegalArgumentException 。 |
toString() | 返回当前枚举类对象常量的名称 |
public class SeasonTest1 {
public static void main(String[] args) {
//values()方法的使用
Season1[] season1s = Season1.values();//返回枚举类的对象数组
System.out.println(Arrays.toString(season1s));
//valueOf(String str)的使用
String str = "WINTER";
Season1 s = Season1.valueOf(str);
System.out.println(s);
//toString()方法的使用
System.out.println(Season1.AUTUMN.toString());
}
}
输出:
[SPRING, SUMMER, AUTUMN, WINTER]
WINTER
AUTUMN
12.1.4 实现接口的枚举类
情况1:实现接口,在enum类中实现抽象方法
//接口
interface info {
void show();
}
//关键字enum定义枚举类Season1
enum Season1 implements info{
//1.提供枚举类的对象,多个对象之间用逗号隔开,末尾分号结束
SPRING("SPRING", "春天花开"),
SUMMER("SUMMER", "夏日炎炎"),
AUTUMN("AUTUMN", "秋高气爽"),
WINTER("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.提供get()属性的方法
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
//实现接口方法
@Override
public void show() {
System.out.println("这是季节");
}
}
测试:
public class SeasonTest1 {
public static void main(String[] args) {
Season1 summer = Season1.SUMMER;
System.out.println(summer);
//情况1:实现接口,在enum类中实现抽象方法
summer.show();
}
}
输出:
SUMMER
这是季节
情况2:让枚举类的对象分别实现接口中的抽象方法
//接口
interface info {
void show();
}
//关键字enum定义枚举类Season1
enum Season1 implements info {
//1.提供枚举类的对象,多个对象之间用逗号隔开,末尾分号结束
SPRING("SPRING", "春天花开") {
@Override
public void show() {
System.out.println("春天在哪里");
}
},
SUMMER("SUMMER", "夏日炎炎") {
@Override
public void show() {
System.out.println("夏天的风我永远记得");
}
},
AUTUMN("AUTUMN", "秋高气爽") {
@Override
public void show() {
System.out.println("秋天不回来");
}
},
WINTER("WINTER", "冰天雪地") {
@Override
public void show() {
System.out.println("大约在冬季");
}
};
//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.提供get()属性的方法
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}
测试:
public class SeasonTest1 {
public static void main(String[] args) {
//values()方法的使用
Season1[] season1s = Season1.values();//返回枚举类的对象数组
System.out.println(Arrays.toString(season1s));
//情况2:让枚举类的对象分别实现接口中的抽象方法
for (int i = 0; i < season1s.length; i++) {
season1s[i].show();
}
}
}
输出:
[SPRING, SUMMER, AUTUMN, WINTER]
春天在哪里
夏天的风我永远记得
秋天不回来
大约在冬季
12.2 注解的使用
12.2.1 注解(Annotation)介绍
- 注解(Annotation) 是 JDK 5.0增加的新特性。
- Annotation 其实就是代码里的特殊标记,这些标记可以在编译 、类加载、运行时被读取,并执行相应的处理。通过使用Annotation,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。
- Annotation 可以像修饰符一样被使用 , 可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明 , 这些信息被保存在 Annotation的 “name=value” 对中。
- 在 JavaSE 中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在 JavaEE/Android 中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替 JavaEE 旧版中所遗留的繁冗代码和 XML 配置等。
- 未来的开发模式都是基于注解的,JPA 是基于注解的, Spring2.5 以上都是基于注解的,Hibernate3.x 以后也是基于注解的,现在的 Struts2 有一部分也是基于注解的了,注解是一种趋势 ,一定程度上可以说: 框架 = 注解 + 反射 + 设计模式。
12.2.2 常见的Annotation示例
1.文档注解
如下面所示,“@” 号后面所跟的就是文档注解。一般加在类或者方法前面,起到解释说明的作用。
/**
* @Author: Sihang Xie
* @Description:
* @Date: 2022/4/1 17:16
* @param
* @return {{@link String}}
*/
2.JDK中内置的三个基本注解
注解 | 描述 |
---|---|
@Override | 限定重写父类方法,只能用于方法 |
@Deprecated | 表示所修饰的类、方法等已过时。通常是因为所修饰的结构危险或存在更好的选择 |
@SuppressWarnings | 抑制编译器警告,抑制变量未使用等警告 |
3.跟踪代码依赖性,实现替代配置文件功能
只需要在注解上声明,就不需要写冗长的XML文件了。
12.2.3 自定义Annotation
1.自定义的步骤
① 新建 Java Class的时候选择底部的 Annotation,如下图所示:
② 声明 @interface 。注意,这个修饰符和接口 Interface 没有任何关系。
public @interface MyAnnotation {
}
③ 声明成员变量 String value。
public @interface MyAnnotation {
String value();
}
- 上面的
String value()
看起来像是方法,但其实是属性,只不过是以无参数方法的形式来声明。其方法名和返回值定义了该属性的名字和类型。我们称为配置参数。类型只能是八种基本数据类型:String类型、Class类型、enum类型、Annotation类型以及以上所有类型的数组。 - 如果只有一个参数成员,建议使用参数名为value。
- 没有成员定义的 Annotation 称为标记,如 @Override。有些接口,如
Serializable
,其中没有定义任何抽象方法,我们称为标记接口。
2.自定义注解中的赋值问题
@MyAnnotation(value = "Hello")
public void test(){
System.out.println("test Annotation");
}
如上,@MyAnnotation() 中要赋值 “Hello” 才不会报错。若不想赋值也不报错,可以在自定义注解 @MyAnnotation 中添加 default 默认值,如下代码所示:
public @interface MyAnnotation {
String value() default "Hello";
}
@MyAnnotation
public void test(){
System.out.println("test Annotation");
}
但也可以不用默认值,可以自行更改。如下代码所示:
@MyAnnotation(value = "Hi")
public void test(){
System.out.println("test Annotation");
}
上面自定义注解中的赋值其实没有任何意义。现在还不能很好理解自定义注解的作用没关系,这是要等学完反射和框架才能深入地理解到注解的应用场景。
12.2.4 JDK中的元注解
元注解是对现有的注解进行解释说明的注释。JDK 中一共有 4 个元注解。分别是:Retention、Target、Documented、Inherited。下面分别介绍这 4 个元注解。
以下是 @SuppressWarnings 的源代码,可以看到其上面添加了 2 个元注解 @Target 和 @Retention。
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
1.Retention
声明了注解的 3 种生命周期:SOURCE、CLASS(默认)、RUNTIME。
状态 | 描述 |
---|---|
SOURCE | 编译的时候就被丢弃了 |
CLASS | 会被编译器保留在class文件当中,但执行的时候不会被加载到内存当中 |
RUNTIME | 会被编译器保留在class文件当中,而且执行的时候会被加载到内存当中 |
2.Target
用来声明自定义注解能够修饰哪些结构。以 @SuppressWarnings 为例:
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
Target类型 | 描述 |
---|---|
TYPE | 能修饰Class、interface、enum |
FIELD | 属性 |
METHOD | 方法 |
PARAMETER | 参数 |
CONSTRUCTOR | 构造器 |
LOCAL_VARIABLE | 局部变量 |
ANNOTATION_TYPE | 注解类型 |
PACKAGE | 包 |
其中 @SuppressWarnings 只能修饰 Class、interface、enum、属性、方法、参数、构造器和局部变量。
- 如果自定义注解,一般都会声明 Rentention 和 Target 两个元注解。其他两个则一般很少使用。
3.Documented
表示所修饰的注解在被 javadoc 解析时会被保留下来。
举个例子,就是 @Deprecated 会在形成 Java 文档时显示出来,如下图所示:
4.Inherited
被它修饰的 Annotation 将具有继承性。如果某个类使用了被 @Inherited 修饰的 Annotation,则其子类将自动具有该注解。
- 在实际发开中,应用较少。
12.2.5 利用反射获取注解信息(在反射部分涉及)
略
12.2.6 JDK 8中注解的新特性
JDK 8中注解有 2 个新特性:可重复注解和类型注解。
1.可重复注解
JDK 8 之前只能使用 Annotation[] 数组来写多个数组。如下代码所示:
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
public @interface MyAnnotation {
String value() default "Hello";
}
public @interface MyAnnotations {
MyAnnotation[] value();//Annotation[] 数组
}
public class AnnotationTest {
@MyAnnotations({@MyAnnotation(value = "Hello"), @MyAnnotation(value = "Hi")})//可重复注解
public void test(){
System.out.println("test Annotation");
}
}
JDK 8 之后,只需声明 @Repeatable(MyAnnotations.class) 即可。注意:使用 @Repeatable 之后,@MyAnnotation 与 @MyAnnotations 的 @Retention 、 @Target 和 @Inherited等元注解信息要保持一致。否则就会报错。
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
public @interface MyAnnotation {
String value() default "Hello";
}
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
public @interface MyAnnotations {
MyAnnotation[] value();
}
public class AnnotationTest {
@MyAnnotation(value = "Hello")
@MyAnnotation(value = "Hi")
public void test(){
System.out.println("test Annotation");
}
}
2.类型注解
JDK 8 新增了 2 种类型注解:ElementType.TYPE_PARAMETER、ElementType.TYPE_USE。总的来说,可以让 Java 中可以添加注解的地方变得更多,让注解更加强大。
类型注解 | 描述 |
---|---|
ElementType.TYPE_PARAMETER | 表示该注解能写在类型变量的声明语句中(如:泛型声明) |
ElementType.TYPE_USE | 表示该注解能写在使用类型的任何语句中 |
例子:
泛型,也是可以通过反射获取其注解。
① 自定义注解 @MyAnnotation 添加了TYPE_PARAMETER:
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, TYPE_PARAMETER})//末尾添加了TYPE_PARAMETER
public @interface MyAnnotation {
String value() default "Hello";
}
② 测试:可以在泛型前添加自定义注解了
class Generic<@MyAnnotation T> {
}
自定义注解 @MyAnnotation 添加了TYPE_USE:
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, TYPE_PARAMETER, TYPE_USE})末尾添加了TYPE_USE
public @interface MyAnnotation {
String value() default "Hello";
}
测试:可以在好多你想不到的地方前添加注解。
public void show() throws @MyAnnotation RuntimeMBeanException {
ArrayList<@MyAnnotation String> list = new ArrayList<>();
int num = (@MyAnnotation int) 10L;
}