枚举对象注释如何以及何时使用枚举和注释

2022-08-10 11:17:12

枚举对象注释

本文是我们名为“高级Java ”的学院课程的一部分。

本课程旨在帮助您最有效地使用Java。 它讨论了高级主题,包括对象创建,并发,序列化,反射等。 它将指导您完成Java掌握的旅程!在这里查看

1.简介

在本教程的这一部分中,我们将介绍Java 5版本中引入到语言中的另外两个重要功能以及泛型:枚举(或枚举)和注释。 枚举可以被视为一种特殊的类,而注解可以被视为一种特殊的接口。

枚举的概念很简单,但是非常方便:它表示一组固定的,恒定的值。 实际上,这意味着通常使用枚举来设计具有恒定可能状态集的概念。 例如,星期几就是枚举的一个很好的例子:它们仅限于星期一,星期二,星期三,星期四,星期五,星期六和星期日。

另一方面,注释是一种特殊的元数据,可以与Java语言的不同元素和构造相关联。 有趣的是,注释对消除Java生态系统中大多数地方使用的样板XML描述符起了很大的作用。 他们介绍了一种新的,类型安全且健壮的配置和自定义技术方法。

2.枚举作为特殊类

在将枚举引入Java语言之前,在Java中建模固定值集的常规方法是声明一些常量。 例如:

public class DaysOfTheWeekConstants {
    public static final int MONDAY = 0;
    public static final int TUESDAY = 1;
    public static final int WEDNESDAY = 2;
    public static final int THURSDAY = 3;
    public static final int FRIDAY = 4;
    public static final int SATURDAY = 5;
    public static final int SUNDAY = 6;
}

尽管这种方法行之有效,但远非理想的解决方案。 主要是因为常量本身只是int类型的值,并且代码中应期望这些常量的每个位置(而不是任意的int值)都应始终明确记录并声明。 从语义上讲,它不是该概念的类型安全表示形式,如以下方法所示。

public boolean isWeekend( int day ) {
    return( day == SATURDAY || day == SUNDAY );
}

从逻辑角度来看,day参数应具有在DaysOfTheWeekConstants类中声明的值之一。 但是,如果不编写其他文档(然后由其他人阅读),则无法猜测。 对于Java编译器,像isWeekend(100)这样的调用看起来是绝对正确的,不会引起任何问题。

枚举在这里进行了救援。 枚举允许用类型化的值替换常量,并在各处使用这些类型。 让我们使用枚举重写上面的解决方案。

public enum DaysOfTheWeek {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
}

更改的是class成为enum ,并且可能的值在枚举定义中列出。 但是,区别在于每个值都是在其声明的枚举类的实例(在我们的示例中为DaysOfTheWeek )。 这样,无论何时使用枚举,Java编译器都可以进行类型检查。 例如:

public boolean isWeekend( DaysOfTheWeek day ) {
    return( day == SATURDAY || day == SUNDAY );
}

请注意,枚举中大写命名方案的使用只是一个约定,没有什么真正阻止您不这样做的。

3.枚举和实例字段

枚举是专门的类,因此是可扩展的。 这意味着它们可以具有实例字段,构造函数和方法(尽管唯一的限制是不能声明默认的no-args构造函数,并且所有构造函数都必须是private )。 让我们使用实例字段和构造函数将属性isWeekend添加到一周的每一天。

public enum DaysOfTheWeekFields {
    MONDAY( false ),
    TUESDAY( false ),
    WEDNESDAY( false ),
    THURSDAY( false ),
    FRIDAY( false ),
    SATURDAY( true ),
    SUNDAY( true );

    private final boolean isWeekend;

    private DaysOfTheWeekFields( final boolean isWeekend ) {
        this.isWeekend = isWeekend;
    }

    public boolean isWeekend() {
        return isWeekend;
    }
}

正如我们所看到的,枚举的值只是构造函数调用,简化了不需要new关键字的情况。isWeekend()属性可用于检测该值代表工作日还是工作日结束。 例如:

public boolean isWeekend( DaysOfTheWeek day ) {
    return day.isWeekend();
}

实例字段是Java枚举中极为有用的功能。 它们经常用于使用常规的类声明规则将一些其他详细信息与每个值相关联。

4.枚举和接口

另一个有趣的功能(又一次证实了枚举只是专门的类)是它们可以实现接口(但是,由于后面的枚举和泛型部分中说明的原因,枚举不能扩展任何其他类)。 例如,让我们介绍接口DayOfWeek

interface DayOfWeek {
    boolean isWeekend();
}

并使用接口实现而不是常规实例字段重写上一部分中的示例。

public enum DaysOfTheWeekInterfaces implements DayOfWeek {
    MONDAY() {
        @Override
        public boolean isWeekend() {
            return false;
        }
    },
    TUESDAY() {
        @Override
        public boolean isWeekend() {
            return false;
        }
    },
    WEDNESDAY() {
        @Override
        public boolean isWeekend() {
            return false;
        }
    },
    THURSDAY() {
        @Override
        public boolean isWeekend() {
            return false;
        }
    },
    FRIDAY() {
        @Override
        public boolean isWeekend() {
            return false;
        }
    },
    SATURDAY() {
        @Override
        public boolean isWeekend() {
            return true;
        }
    },
    SUNDAY() {
        @Override
        public boolean isWeekend() {
            return true;
        }
    };
}

我们实现接口的方式有些冗长,但是可以通过将实例字段和接口组合在一起来使其更好。 例如:

public enum DaysOfTheWeekFieldsInterfaces implements DayOfWeek {
    MONDAY( false ),
    TUESDAY( false ),
    WEDNESDAY( false ),
    THURSDAY( false ),
    FRIDAY( false ),
    SATURDAY( true ),
    SUNDAY( true );

    private final boolean isWeekend;

    private DaysOfTheWeekFieldsInterfaces( final boolean isWeekend ) {
        this.isWeekend = isWeekend;
    }

    @Override
    public boolean isWeekend() {
        return isWeekend;
    }
}

通过支持实例字段和接口,枚举可以以更加面向对象的方式使用,从而带来某种程度的抽象依赖。

5.枚举和泛型

尽管乍看之下看不到它,但是Java中的枚举和泛型之间存在联系。 Java中的每个枚举都是自动从通用Enum< T >类继承的,其中T是枚举类型本身。 Java编译器在编译时代表开发人员进行此转换,将枚举声明public enum DaysOfTheWeek为如下所示:

public class DaysOfTheWeek extends Enum< DaysOfTheWeek > {
    // Other declarations here
}

它还说明了为什么枚举可以实现接口但不能扩展其他类的原因:它们隐式扩展了Enum< T >并且如本教程第2部分所知,使用所有对象通用的方法 ,Java不支持多重继承。

每个枚举都扩展Enum< T >的事实允许定义将枚举类型的实例作为参数或类型参数的通用类,接口和方法。 例如:

public< T extends Enum < ? > > void performAction( final T instance ) {
    // Perform some action here
}

在上面的方法声明中,类型T被约束为任何枚举的实例,Java编译器将对此进行验证。

6.便捷的枚举方法

Enum< T >类提供了两个有用的方法,每个枚举实例都会自动继承这些方法。

方法描述
String name() 返回此枚举常量的名称,该名称与在其枚举声明中声明的完全相同。
int ordinal() 返回此枚举常量的序数(其在枚举声明中的位置,其中初始常量的序数为零)。

表格1

此外,Java编译器会为它遇到的每个枚举类型自动生成两个更有用的static方法(让我们将特定的枚举类型称为T )。

方法描述
T[] values() 返回枚举T的所有声明的枚举常量。
T valueOf(String name) 返回具有指定名称的枚举常量T

表2

由于这些方法的存在和艰苦的编译器工作,在代码中使用枚举还有另一个好处:它们可以在switch/case语句中使用。 例如:

public void performAction( DaysOfTheWeek instance ) {
    switch( instance ) {
        case MONDAY:
            // Do something
            break;

        case TUESDAY:
            // Do something
            break;

        // Other enum constants here
    }
}

7.专门的集合:EnumSet和EnumMap

与所有其他类一样,枚举的实例可以与标准Java集合库一起使用。 但是,某些收集类型已经专门针对枚举进行了优化,并且在大多数情况下建议使用它们来代替通用的对应类型。

我们将研究两种专门的集合类型:EnumSet< T >EnumMap< T, ? >EnumMap< T, ? > 。 两者都很容易使用,我们将从EnumSet< T >

EnumSet< T >是优化的常规集,可以有效地存储枚举。 有趣的是,EnumSet< T >无法使用构造函数实例化,而是提供了许多有用的工厂方法(我们在本教程的第1部分“如何创建和销毁对象”中介绍了工厂模式)。

例如,allOf工厂方法创建EnumSet< T >的实例,该实例包含所涉及的枚举类型的所有枚举常量:

final Set< DaysOfTheWeek > enumSetAll = EnumSet.allOf( DaysOfTheWeek.class );

因此,noneOf工厂方法为有问题的枚举类型创建一个空的EnumSet< T >的实例:

final Set< DaysOfTheWeek > enumSetNone = EnumSet.noneOf( DaysOfTheWeek.class );

另外,也可以指定哪些枚举所讨论的枚举类型的常量应当纳入EnumSet< T >使用of工厂方法:

final Set< DaysOfTheWeek > enumSetSome = EnumSet.of(
    DaysOfTheWeek.SUNDAY,
    DaysOfTheWeek.SATURDAY
);

EnumMap< T, ? >EnumMap< T, ? >非常接近于常规映射,不同之处在于其键可以是所讨论枚举类型的枚举常量。 例如:

final Map< DaysOfTheWeek, String > enumMap = new EnumMap<>( DaysOfTheWeek.class );
enumMap.put( DaysOfTheWeek.MONDAY, "Lundi" );
enumMap.put( DaysOfTheWeek.TUESDAY, "Mardi" );

请注意,作为大多数集合实现,EnumSet< T >EnumMap< T, ? >EnumMap< T, ? >不是线程安全的,并且不能在多线程环境中按原样使用(我们将在本教程的第9部分“并发最佳实践”中讨论线程安全和同步)。

8.何时使用枚举

由于Java 5发布枚举是使用固定的常量集表示和拨号的唯一首选和推荐方法。 它们不仅是强类型的,而且是可扩展的,并得到任何现代库或框架的支持。

9.注释作为特殊接口

如前所述,注释是用于将元数据与Java语言的不同元素相关联的语法糖。

注释本身对所注释的元素没有任何直接影响。 但是,根据注释及其定义方式的不同,Java编译器可能会使用它们(一个很好的例子是@Override注释,我们在本教程的第3部分“如何设计类和接口 ),注释处理器(更多详细信息将在“注释处理器”部分中)以及运行时使用反射和其他自省技术的代码(有关本教程的第11部分中的更多内容,反射和动态语言支持 )。

让我们看一下最简单的注释声明:

public @interface SimpleAnnotation {
}

The @interface keyword introduces new annotation type. That is why annotations could be treated as specialized interfaces. Annotations may declare the attributes with or without default values, for example:
public @interface SimpleAnnotationWithAttributes {
    String name();
    int order() default 0;
}

如果注释声明的属性没有默认值,则应在应用注释的所有位置提供该属性。 例如:

@SimpleAnnotationWithAttributes( name = "new annotation" )

按照惯例,如果注释具有带有名称value的属性,并且它是唯一需要指定的属性,则可以省略属性的名称,例如:

public @interface SimpleAnnotationWithValue {
    String value();
}

It could be used like this:

@SimpleAnnotationWithValue( "new annotation" )

在某些用例中,有两个限制使使用注释不太方便。 首先,注释不支持任何继承:一个注释不能扩展另一个注释。 其次,不可能使用new运算符以编程方式创建注释的实例(我们将参考本教程的第11部分“反射和动态语言支持”中的一些变通方法)。 第三,注释只能声明基本类型的属性,即StringClass< ? >Class< ? >这些的类型和数组。 注释中不允许声明任何方法或构造函数。

10.注释和保留政策

每个注释都有一个非常重要的特性,称为保留策略 ,这是一个枚举(类型为RetentionPolicy ),其中包含有关如何保留注释的策略集。 可以将其设置为以下值之一。

政策描述
CLASS 批注由编译器记录在类文件中,但在运行时VM无需保留批注
RUNTIME 注释由编译器记录在类文件中,并在运行时由VM保留,因此可以通过反射方式读取它们。
SOURCE 批注将被编译器丢弃。

表3

保留策略对注释何时可用于处理至关重要。 可以使用@Retention批注设置保留策略。 例如:

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

@Retention( RetentionPolicy.RUNTIME )
public @interface AnnotationWithRetention {
}

将注释保留策略设置为RUNTIME将保证它在编译过程和正在运行的应用程序中的存在。

11.注释和元素类型

每个注释必须具有的另一个特征是可以应用的元素类型。 与保留策略类似,它被定义为带有可能元素类型集的枚举(ElementType )。

元素类型描述
ANNOTATION_TYPE 注释类型声明
CONSTRUCTOR 构造函数声明
FIELD 字段声明(包括枚举常量)
LOCAL_VARIABLE 局部变量声明
METHOD 方法声明
PACKAGE 包裹申报
PARAMETER 参数声明
TYPE 类,接口(包括注释类型)或枚举声明

表4

除了上述描述的元素外,Java 8还引入了两种可以应用注释的新元素类型。

元素类型描述
TYPE_PARAMETER 类型参数声明
TYPE_USE 使用类型

表5

与保留策略相反,注释可以使用@Target注释声明可以与之关联的多种元素类型。 例如:

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

@Target( { ElementType.FIELD, ElementType.METHOD } )
public @interface AnnotationWithTarget {
}

通常,要创建的所有注释都应同时指定保留策略和元素类型,以便使用。

12.注释和继承

在Java中,声明注释和继承之间存在重要的关系。 默认情况下,子类不继承在父类上声明的注释。 但是,有一种方法可以使用@Inherited批注在类层次结构中传播特定批注。 例如:

@Target( { ElementType.TYPE } )
@Retention( RetentionPolicy.RUNTIME )
@Inherited
@interface InheritableAnnotation {
}

@InheritableAnnotation
public class Parent {
}

public class Child extends Parent {
}

在此示例中,在Parent类上声明的@InheritableAnnotation注释也将由Child类继承。

13.可重复的注释

在Java 8之前的时代,与注释相关的另一个限制尚未讨论:同一注释只能在同一位置出现一次,不能重复多次。 Java 8通过提供对可重复注释的支持来减轻这种限制。 例如:

@Target( ElementType.METHOD )
@Retention( RetentionPolicy.RUNTIME )
public @interface RepeatableAnnotations {
    RepeatableAnnotation[] value();
}

@Target( ElementType.METHOD )
@Retention( RetentionPolicy.RUNTIME )
@Repeatable( RepeatableAnnotations.class )
public @interface RepeatableAnnotation {
    String value();
};
@RepeatableAnnotation( "repeatition 1" )
@RepeatableAnnotation( "repeatition 2" )
public void performAction() {
    // Some code here
}

尽管在Java 8中,可重复注释功能需要做一些工作才能使注释可重复(使用@Repeatable ),但最终结果还是值得的:更干净,更紧凑的带注释的代码。

14.注释处理器

Java编译器支持一种称为注解处理器的特殊类型的插件(使用–processor命令行参数),可以在编译阶段处理注解。 注释处理器可以分析注释的用法(执行静态代码分析),创建其他Java源文件或资源(依次可以对其进行编译和处理)或对注释的代码进行更改。

保留策略 (请参阅注释和保留策略 )通过指示编译器哪些注释应可用于注释处理器进行处理而发挥了关键作用。

注解处理器被广泛使用,但是要编写注解处理器,它需要Java编译器如何工作以及编译过程本身的一些知识。

15.约定上的注释和配置

约定优于配置是一种软件设计范例,旨在简化开发人员遵循一组简单规则(或约定)的开发过程。 例如,某些MVC (模型-视图-控制器)框架遵循约定将控制器放置在“ controller”文件夹(或程序包)中。 另一个示例是ORM (对象关系映射器)框架,该框架通常遵循约定以在“模型”文件夹(或程序包)中查找类,并从相应的类派生关系表名称。

另一方面,注释为基于显式配置的不同设计范例打开了道路。 考虑以上示例,@Controller Controller批注可以将任何类显式标记为controller,而@Entity可以引用关系数据库表。 好处还来自以下事实:注释是可扩展的,可能具有其他属性,并且仅限于特定的元素类型。 Java编译器强加了对注释的不正确使用,并在早期(在编译阶段)揭示了配置错误的问题。

16.何时使用注释

注释几乎无处不在:Java标准库有很多注释,大多数每个Java规范也都包含注释。 每当您需要将其他元数据与代码相关联时,批注便是直接简单的方法。

有趣的是,Java社区正在不断努力开发通用的语义概念,并使几种Java技术之间的注释标准化(有关更多信息,请参阅JSR-250规范 )。 目前,标准Java库包含以下注释。

注解描述
@Deprecated 指示已标记的元素已弃用,不应再使用。 每当程序使用带有此批注的方法,类或字段时,编译器都会生成警告。
@Override 提示编译器该元素旨在替代超类中声明的元素。
@SuppressWarnings 指示编译器禁止以其他方式生成的特定警告。
@SafeVarargs 当应用于方法或构造函数时,断言该代码不会对其varargs参数执行潜在的不安全操作。 使用此批注类型时,将禁止使用与varargs有关的未经检查的警告(有关varargs的更多详细信息将在本教程的第6部分“如何有效地编写方法”中进行介绍 )。
@Retention 指定如何保留标记的注释。
@Target 指定可以将标记的注释应用于哪种Java元素。
@Documented 指示无论何时使用指定的注释,都应使用Javadoc工具记录这些元素(默认情况下,Javadoc中不包含注释)。
@Inherited 指示可以从超类继承注释类型(有关更多详细信息,请参阅注释和继承部分)。

表6

Java 8发行版还添加了一些新的注释。

注解描述
@FunctionalInterface 指示类型声明旨在用作Java语言规范所定义的功能接口(有关功能接口的更多详细信息,将在本教程的第3部分“如何设计类和接口”中进行介绍 )。
@Repeatable 指示标记的注释可以多次应用于同一声明或类型使用(有关更多详细信息,请参阅“可重复注释”部分)。

表7

17.下一步是什么

在本节中,我们介绍了用于表示固定常量集的枚举(或枚举),以及使用元数据装饰Java代码元素的注释。 尽管没有什么联系,但这两个概念在Java中已被广泛使用。 尽管在本教程的下一部分中,我们将继续研究如何有效地编写方法,但注释通常会成为大多数讨论的一部分。

18.下载源代码

这是关于如何设计类和接口的课程。 您可以在此处下载源代码:advanced-java-part-5

翻译自:https://www.javacodegeeks.com/2015/09/how-and-when-to-use-enums-and-annotations.html

枚举对象注释

  • 作者:dnc8371
  • 原文链接:https://blog.csdn.net/dnc8371/article/details/107263532
    更新时间:2022-08-10 11:17:12