本文是我们名为“ 高级Java ”的学院课程的一部分。
本课程旨在帮助您最有效地使用Java。 它讨论了高级主题,包括对象创建,并发,序列化,反射等。 它将指导您完成Java掌握的过程! 在这里查看 !
目录
- 1.简介 2.枚举作为特殊类 3.枚举和实例字段 4.枚举和接口 5.枚举和泛型 6.便捷的枚举方法 7.专门的集合:EnumSet和EnumMap 8.何时使用枚举 9.注释作为特殊接口 10.注释和保留政策 11.注释和元素类型 12.注释和继承 13.可重复的注释 14.注释处理器 15.约定上的注释和配置 16.何时使用注释 17.接下来 18.下载源代码
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() {@Overridepublic boolean isWeekend() {return false;}},TUESDAY() {@Overridepublic boolean isWeekend() {return false;}},WEDNESDAY() {@Overridepublic boolean isWeekend() {return false;}},THURSDAY() {@Overridepublic boolean isWeekend() {return false;}},FRIDAY() {@Overridepublic boolean isWeekend() {return false;}},SATURDAY() {@Overridepublic boolean isWeekend() {return true;}},SUNDAY() {@Overridepublic 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;}@Overridepublic 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 somethingbreak;case TUESDAY:// Do somethingbreak;// 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部分“ 反射和动态语言支持”中的一些变通方法)。 第三,注释只能声明基本类型的属性,即String
或Class< ? >
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