Java 基础 - 枚举类 、注解

465 阅读9分钟

往期推荐

一、枚举类

1.枚举类的说明

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

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

  • 枚举类的实现:

    JDK 5.0以前需要自定义;

    JDK 5.0后新增enum关键字用于定义枚举类。

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

  • 枚举类的属性:

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

2.如何定义枚举类

2.1 自定义枚举类。

步骤:

  1. 私有化构造器,保证不能在类的外部创建其对象;
  2. 在类的内部创建枚举类的示例。声明为:public static final
  3. 对象如果有实例变量,应该声明为private final,并在构造器中初始化;
//自定义枚举类
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 + '\'' +
            '}';
    }
}
public class SeasonTest {
    public static void main(String[] args) {
        Season autumn = Season.AUTUMN;
        System.out.println(autumn); // Season{seasonName='秋天', seasonDesc='秋高气爽'}
    }
}

2.2 JDK 5.0 新增使用enum定义枚举类。

使用说明:

  • 使用enum定义的枚举类默认继承了 java.lang.Enum 类,因此不能再继承其他类。
  • 枚举类的构造器只能使用private权限修饰符。
  • 枚举类的所有实例必须在枚举类中显式列出(, 分隔 ; 结尾),列出的实例系统会自动添加public static final 修饰。
  • 必须在枚举类的第一行声明枚举类对象。
// 使用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.获取枚举类对象的属性

    public String getSeasonName() {
        return SeasonName;
    }

    public String getSeasonDesc() {
        return SeasonDesc;
    }

    @Override
    public String toString() {
        return "Season1{" +
                "SeasonName='" + SeasonName + '\'' +
                ", SeasonDesc='" + SeasonDesc + '\'' +
                '}';
    }
}
public class SeasonTest2 {
    public static void main(String[] args) {
        Season1 season = Season1.AUTUMN;
        System.out.println(season); // Season1{SeasonName='秋天', SeasonDesc='秋高气爽'}
    }
}

Enum类的常用方法:

  • values():返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。

  • valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”.如不是,会有运行时异常 IllegalArgumentException

  • toString():返回当前枚举类对象常量的名称。

public class SeasonTest3 {
    public static void main(String[] args) {
        Season2 season = Season2.AUTUMN;
        System.out.println(season.toString()); //Season2{SeasonName='秋天', SeasonDesc='秋高气爽'}
        season.show(); // 秋天不回来

        // value 返回所有的枚举类对象的构成的数组
        Season2[] values = season.values();
        for(int i = 0 ; i <values.length; i++){
            System.out.println(values[i]);
            values[i].show();
        }
        System.out.println("*************************************************");

        Thread.State[] values1 = Thread.State.values();
        for(int i = 0 ; i < values1.length; i++){
            System.out.println(values1[i]);
        }
        System.out.println("***************************************");
        // valueOf(String objName):返回枚举类中对象名是objName的对象。
        Season2 spring = Season2.valueOf("SPRING");
        System.out.println(spring); // Season2{SeasonName='春天', SeasonDesc='春暖花开'}
    }

用Enum类定义的枚举类对象分别实现接口:

使用说明:

  1. 和普通Java类一样,枚举类可以实现一个或多个接口。
  2. 若每个枚举值在调用实现的接口方法呈现相同的行为方式,则只要统一实现该方法即可。
  3. 若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式,则可以让每个枚举值分别来实现该方法。
interface Info{
    void show();
}

// 使用enum关键字枚举类
enum Season2 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("大约在冬季");
        };
    };
    private final String SeasonName;
    private final String SeasonDesc;

    private Season2(String SeasonName , String SeasonDesc){
        this.SeasonName = SeasonName;
        this.SeasonDesc = SeasonDesc;
    }

    public String getSeasonName() {
        return SeasonName;
    }

    public String getSeasonDesc() {
        return SeasonDesc;
    }

    @Override
    public String toString() {
        return "Season2{" +
                "SeasonName='" + SeasonName + '\'' +
                ", SeasonDesc='" + SeasonDesc + '\'' +
                '}';
    }
}

二、注解

1. 注解的理解

① jdk 5.0 新增的功能

Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation,程序员可以在不改变原逻辑的情况下, 在源文件中嵌入一些补充信息。

Annotation可以像修饰符一样使用,可以用来修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被保存在Annotationname = value 对中。

④ 在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android 中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗 代码和XML配置等。

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

2. Java内置注解

Java 1.5开始自带的标准注解,包括@Override@Deprecated@SuppressWarnings

  • @Override:表示当前的方法定义将覆盖父类中的方法。
  • @Deprecated:表示代码被弃用,如果使用了被@Deprecated注解的代码则编译器将发出警告。
  • @SuppressWarnings:表示关闭编译器警告信息。

内置注解 - @Override

我们先来看一下这个注解类型的定义:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

从它的定义我们可以看到,这个注解可以被用来修饰方法,并且它只在编译时有效,在编译后的class文件中便不再存在。这个注解的作用我们大家都不陌生,那就是告诉编译器被修饰的方法是重写的父类的中的相同签名的方法,编译器会对此做出检查,若发现父类中不存在这个方法或是存在的方法签名不同,则会报错。

内置注解 - @Deprecated

这个注解的定义如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
} 

从它的定义我们可以知道,它会被文档化,能够保留到运行时,能够修饰构造方法、属性、局部变量、方法、包、参数、类型。这个注解的作用是告诉编译器被修饰的程序元素已被“废弃”,不再建议用户使用。

内置注解 - @SuppressWarnings

这个注解我们也比较常用到,先来看下它的定义:

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

它能够修饰的程序元素包括类型、属性、方法、参数、构造器、局部变量,只能存活在源码时,取值为String[]。它的作用是告诉编译器忽略指定的警告信息,它可以取的值如下所示:

参数作用原描述
all抑制所有警告to suppress all warnings
boxing抑制装箱、拆箱操作时候的警告to suppress warnings relative to boxing/unboxing operations
cast抑制映射相关的警告to suppress warnings relative to cast operations
dep-ann抑制启用注释的警告to suppress warnings relative to deprecated annotation
deprecation抑制过期方法警告to suppress warnings relative to deprecation
fallthrough抑制确在switch中缺失breaks的警告to suppress warnings relative to missing breaks in switch statements
finally抑制finally模块没有返回的警告to suppress warnings relative to finally block that don’t return
hiding抑制与隐藏变数的区域变数相关的警告to suppress warnings relative to locals that hide variable()
incomplete-switch忽略没有完整的switch语句to suppress warnings relative to missing entries in a switch statement (enum case)
nls忽略非nls格式的字符to suppress warnings relative to non-nls string literals
null忽略对null的操作to suppress warnings relative to null analysis
rawtype使用generics时忽略没有指定相应的类型to suppress warnings relative to un-specific types when using
restriction抑制与使用不建议或禁止参照相关的警告to suppress warnings relative to usage of discouraged or
serial忽略在serializable类中没有声明serialVersionUID变量to suppress warnings relative to missing serialVersionUID field for a serializable class
static-access抑制不正确的静态访问方式警告to suppress warnings relative to incorrect static access
synthetic-access抑制子类没有按最优方法访问内部类的警告to suppress warnings relative to unoptimized access from inner classes
unchecked抑制没有进行类型检查操作的警告to suppress warnings relative to unchecked operations
unqualified-field-access抑制没有权限访问的域的警告to suppress warnings relative to field access unqualified
unused抑制没被使用过的代码的警告to suppress warnings relative to unused code

3. 注解与反射接口

定义注解后,如何获取注解中的内容呢?反射包java.lang.reflect下的AnnotatedElement接口提供这些方法。这里注意:只有注解被定义为RUNTIME后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。

AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的方法来访问Annotation信息。我们看下具体的先关接口

  • boolean isAnnotationPresent(Class<?extends Annotation> annotationClass)

判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false。注意:此方法会忽略注解对应的注解容器。

  • <T extends Annotation> T getAnnotation(Class<T> annotationClass)

返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。

  • Annotation[] getAnnotations()

返回该程序元素上存在的所有注解,若没有注解,返回长度为0的数组。

  • <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)

返回该程序元素上存在的、指定类型的注解数组。没有注解对应类型的注解时,返回长度为0的数组。该方法的调用者可以随意修改返回的数组,而不会对其他调用者返回的数组产生任何影响。getAnnotationsByType方法与 getAnnotation的区别在于,getAnnotationsByType会检测注解对应的重复注解容器。若程序元素为类,当前类上找不到注解,且该注解为可继承的,则会去父类上检测对应的注解。

  • <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)

返回直接存在于此元素上的所有注解。与此接口中的其他方法不同,该方法将忽略继承的注释。如果没有注释直接存在于此元素上,则返回null

  • <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass)

返回直接存在于此元素上的所有注解。与此接口中的其他方法不同,该方法将忽略继承的注释

  • Annotation[] getDeclaredAnnotations()

返回直接存在于此元素上的所有注解及注解对应的重复注解容器。与此接口中的其他方法不同,该方法将忽略继承的注解。如果没有注释直接存在于此元素上,则返回长度为零的一个数组。该方法的调用者可以随意修改返回的数组,而不会对其他调用者返回的数组产生任何影响。

4. 元注解

上述内置注解的定义中使用了一些元注解(注解类型进行注解的注解类)

在JDK 1.5中提供了4个标准的元注解:@Target@Retention@Documented@Inherited

元注解 - @Target

Target注解的作用是:描述注解的使用范围(即:被修饰的注解可以用在什么地方) 。

Target注解用来说明那些被它所注解的注解类可修饰的对象范围:注解可以用于修饰 packagestypes(类、接口、枚举、注解类)、类成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数),在定义注解类时使用了@Target 能够更加清晰的知道它能够被用来修饰哪些对象,它的取值范围定义在ElementType 枚举中。

public enum ElementType {
 
    TYPE, // 类、接口、枚举类
 
    FIELD, // 成员变量(包括:枚举常量)
 
    METHOD, // 成员方法
 
    PARAMETER, // 方法参数
 
    CONSTRUCTOR, // 构造方法
 
    LOCAL_VARIABLE, // 局部变量
 
    ANNOTATION_TYPE, // 注解类
 
    PACKAGE, // 可用于修饰:包
 
    TYPE_PARAMETER, // 类型参数,JDK 1.8 新增
 
    TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
 
}
  

元注解 - @Retention & @RetentionTarget

Reteniton注解的作用是:描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到何时) 。

Reteniton注解用来限定那些被它所注解的注解类在注解到其他类上以后,可被保留到何时,一共有三种策略,定义在RetentionPolicy枚举中。

public enum RetentionPolicy {
 
    SOURCE,    // 源文件保留
    CLASS,       // 编译期保留,默认值
    RUNTIME   // 运行期保留,可通过反射去获取注解信息
}

为了验证应用了这三种策略的注解类有何区别,分别使用三种策略各定义一个注解类做测试。

@Retention(RetentionPolicy.SOURCE)
public @interface SourcePolicy {
 
}
@Retention(RetentionPolicy.CLASS)
public @interface ClassPolicy {
 
}
@Retention(RetentionPolicy.RUNTIME)
public @interface RuntimePolicy {
 
}
  

用定义好的三个注解类分别去注解一个方法。

public class RetentionTest {
 
	@SourcePolicy
	public void sourcePolicy() {
	}
 
	@ClassPolicy
	public void classPolicy() {
	}
 
	@RuntimePolicy
	public void runtimePolicy() {
	}
}

通过执行 javap -verbose RetentionTest命令获取到的RetentionTest 的 class 字节码内容如下。

{
  public retention.RetentionTest();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public void sourcePolicy();
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 7: 0

  public void classPolicy();
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 11: 0
    RuntimeInvisibleAnnotations:
      0: #11()

  public void runtimePolicy();
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 15: 0
    RuntimeVisibleAnnotations:
      0: #14()
}

从 RetentionTest 的字节码内容我们可以得出以下两点结论:

  • 编译器并没有记录下 sourcePolicy() 方法的注解信息;
  • 编译器分别使用了 RuntimeInvisibleAnnotationsRuntimeVisibleAnnotations 属性去记录了classPolicy()方法 和 runtimePolicy()方法 的注解信息;

元注解 - @Documented

Documented注解的作用是:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。

以下代码在使用Javadoc工具可以生成@TestDocAnnotation注解信息。

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
 
@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
  public @interface TestDocAnnotation {

	public String value() default "default";
}    
@TestDocAnnotation("myMethodDoc")
  public void testDoc() {

}

元注解 - @Inherited

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

我们来测试下这个注解:

  • 定义@Inherited注解:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface TestInheritedAnnotation {
    String [] values();
    int number();
}  
  • 使用这个注解
@TestInheritedAnnotation(values = {"value"}, number = 10)
public class Person {
}

class Student extends Person{
	@Test
    public void test(){
        Class clazz = Student.class;
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation.toString());
        }
    }
}

  • 输出
xxxxxxx.TestInheritedAnnotation(values=[value], number=10)

即使Student类没有显示地被注解@TestInheritedAnnotation,但是它的父类Person被注解,而且@TestInheritedAnnotation@Inherited注解,因此Student类自动有了该注解。

5. 如何自定义注解

参照 @SuppressWarnings 定义

  1. 注解声明为:@interface
  2. 内部定义成员,通常使用value表示
  3. 可以指定成员的默认值,使用default定义
  4. 如果自定义注解没成员,表明是一个标识作用。

说明:

  • 如果注解有成员,在使用注解时,需要指明成员的值。
  • 自定义注解必须配上注解的信息处理流程(使用反射)才意义。
  • 自定义注解通过都会指明两个元注解:@Retention@Target
package com.pdai.java.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyMethodAnnotation {

    public String title() default "";

    public String description() default "";

}

6. JDK 8.0中注解的新特性:

可重复注解、类型注解

6.1 可重复注解

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

② MyAnnotation的Target和Retention等元注解与MyAnnotations相同。

6.2 类型注解

ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明。)

ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。