枚举类型和注解

·1·
第 8 章  枚举类型和注解

在 Java 中,通常使用一种特殊的枚举类型来定义和使用一系列相关的常量。从 JDK 1.5 开始,Java
新增加了“注解”,使用注解可以向程序添加额外的信息,但并不影响程序的执行。
8.1  枚举类型
枚举类型是指其字段由一系列固定的常量组成的数据类型。在生活中,常见这种类型的数据,例如,
表示方向的值,只能是东、西、南、北,表示一周中的天数,只能是星期一、星期二、星期三、星期四、
星期五、星期六、星期日。因为是常量,所以枚举类型的字段要用大写字母表示。
在 Java 语言中,使用关键字 enum 定义一个枚举类型。例如,可以使用枚举类型来指定一周中的天
数,如下所示。
public enum Day {
星期日,  星期一,  星期二,  星期三,  星期四,  星期五,  星期六 
}
在任何时候,如果需要代表一系列固定的常量,就应该使用枚举类型。有两种情况下,尽可能使用
枚举类型,一种是自然的枚举类型,例如,表示太阳系中的行星或者员工的性别,这是一组固定的值,
所以在程序中应该使用枚举类型来表示。另一种是程序在编译的时候,已经知道某个数据所有可能的值,
例如菜单、命令行标记等,也要尽可能使用枚举类型。
例如,在扑克牌游戏中,花色也是固定的,只有 4 种颜色:方块、梅花、红心和黑桃。所以在一个
扑克游戏的程序中,如果要定义表示扑克牌花色的类型,就可以使用枚举,如下所示。
public enum Suit { 
DIAMONDS,            //方块
CLUBS,              //梅花
HEARTS,              //红心
SPADES              //黑桃
}
【例】枚举示例程序。演示如何使用上面所定义的名为 Day 的枚举类型。
public class EnumExample{
Day day;              //声明 Day 这种枚举类型的变量 day
public EnumExample (Day day) {    //构造器,对字段 day 进行初始化
this.day = day;
}
2
public void tellFeeling() {        //匹配每一天,并输出感受
switch (day) {
case  星期一: System.out.println("星期一都不令人喜欢.");
break;   
case  星期五: System.out.println("星期五比较好,有盼头.");
break;     
case  星期六:
case  星期日: System.out.println("周末真令人愉快.");
break;     
default:    System.out.println("一周的中间一般般,不好也不坏.");
break;
}
}
public static void main(String[] args) {
EnumExample firstDay = new EnumExample (Day.星期一);  //创建第 1 个对象实例
firstDay. tellFeeling ();                //调用其 tellFeeling 方法
EnumExample thirdDay = new EnumExample (Day.星期二);  //创建第 2 个对象实例
thirdDay. tellFeeling ();                //同样调用其 tellFeeling 方法
EnumExample fifthDay = new EnumExample (Day.星期五);  //创建第 3 个对象实例
fifthDay. tellFeeling ();
EnumExample sixthDay = new EnumExample (Day.星期六);  //创建第 4 个对象实例
sixthDay. tellFeeling ();
EnumExample seventhDay = new EnumExample (Day.星期日);//创建第 5 个对象实例
seventhDay. tellFeeling ();
}
}
使用如下命令依次编译 Day.java 和 EnumExample.java。
javac Day.java
javac EnumExample.java
程序运行结果如下。
星期一都不令人喜欢.
一周的中间一般般,不好也不坏.
星期五比较好,有盼头.
周末真令人愉快.
周末真令人愉快.
Java 语言中的枚举类型要比其他语言中的枚举类型要强大得多。在 Java 中,enum 声明定义了一个
类(称为“枚举类型”)。枚举类的类体中也可以包括方法和其它字段。当编译器创建一个枚举时,它
会自动地添加一些专门的方法。例如,它会添加一个静态的 values()方法,该方法会按声明的顺序返回
一个包含枚举中所有值的数组。
values()方法通常与 for-each 结构一起使用,以迭代一个枚举类型的值。例如,要迭代一星期中的每
一天,使用如下代码。
for(Day day : Day.values()){
System.out.println(“今天是”  + day);
}
所有的枚举都隐含地继承自 java.lang.Enum。因为 Java 不支持多重继承,所以一个枚举类型不能再
从任何其它类继承。
下面是使用 Java 枚举类型的一个小示例程序。在这个程序中,定义一个枚举类型 UserStatus.java,
·3·
来代表用户的几种状态:待定、活跃、不活跃和已删除。
UserStatus.java
package com.citymango;
public enum UserStatus {
待定("P"), 
活跃("A"), 
不活跃("I"), 
已删除("D");
private String statusCode;
private UserStatus(String s) {
statusCode = s;
}
public String getStatusCode() {
return statusCode;
}
}
接下来编写代码获取用户的状态。
Test.java
package com.citymango;
public class Test {
public static void main(String[] args) {
System.out.println(UserStatus.活跃.getStatusCode());
}
}
编译并运行程序,输出结果如下:
A
在下面的示例中,定义了一个 Planet 枚举类型,代表太阳系中的行星。Planet 中使用了代表质量和
半径的常量属性。
【例】枚举示例程序。在程序中,输入你在地球上的体重,并计算和输出在各个行星上时的体重。
/*
*  测试枚举类型
*/
enum Planet {
水星(3.303e+23, 2.4397e6),       //水星
金星(4.869e+24, 6.0518e6),       //金星 
4
地球(5.976e+24, 6.37814e6),      //地球
火星(6.421e+23, 3.3972e6),       //火星
木星(1.9e+27, 7.1492e7),         //木星
土星(5.688e+26, 6.0268e7),       //土星
天王星(8.686e+25, 2.5559e7),     //天王星
海王星(1.024e+26, 2.4746e7);     //海王星,注意这里要以分号结尾
private final double mass;        //代表质量,单位为千克(公斤)
private final double radius;       //代表半径,单位为米
Planet(double mass, double radius) {//构造器
this.mass = mass;
this.radius = radius;
}
//  通用的万有引力常量
public static final double G = 6.67300E-11;
//获得地心引力(重力)
double surfaceGravity() {       
return G * mass / (radius * radius);
}
//获得地表重量
double surfaceWeight(double otherMass) { 
return otherMass * surfaceGravity();
}    
}
public class Main {
public static void main(String[] args) {
double earthWeight = 78;    //假定在地球上的体重为 78kg
double mass = earthWeight / Planet.地球.surfaceGravity(); //获取质量大小
for (Planet p : Planet.values()){                          //迭代输出在每个行星上的体重
System.out.printf("你在%s 上重%f 公斤。%n", p, p.surfaceWeight(mass));
}
}
}
使用如下命令编译并运行 Planet.java 程序:
javac Planet.java
java Planet
程序输出结果如下:
你在水星上重 29.465094 公斤。
你在金星上重 70.589930 公斤。
你在地球上重 78.000000 公斤。
你在火星上重 29.541500 公斤。
你在木星上重 197.383487 公斤。
你在土星上重 83.149212 公斤。
你在天王星上重 70.599922 公斤。
你在海王星上重 88.789590 公斤。 
·5·
正如上面的示例代码所示,每一个枚举常量使用表示质量和半径的参数值来声明。当常量被创建时,
这些值被传递给构造器。Java 语言要求枚举常量要首先定义,优先于任何其他的字段和方法。另外,如
果有其他的字段和方法,枚举常量的列表必须以分号  “;”结束。
一个枚举类型的构造器必须为包级私有的或私有的(private)。它将自动地创建在枚举体一开始定
义的常量。不能人为地调用枚举的构造器。除了属性和构造器外,Planet 还定义了一些方法,用来计算
每个行星的地心引力和在行星上对象的重量。
8.2  注解
注解 (Annotations)就是代码里的标记,它提供与程序有关的数据,但是注解本身不是程序一部分。
注解不对其所标注的代码的操作有直接的影响。通过使用注解,程序开发人员可以在不改变原有逻辑的
情况下,在源文件中嵌入一些补充的信息。代码分析工具、开发工具和部署工具可以通过这些补充信息
进行验证或者进行部署。
8.2.1   注解的用法
简单地说,注解就是代码里的标记,这些标记在类加载、运行时或是编译的时候可以被解释,但是
不对程序的运行产生直接的影响。
注解有很多用法,包括:
  为编译器提供信息。编译器可以使用注解来检测错误或禁止警告。
  在编译和部署时处理。软件开发工具可以处理注解信息以生成代码、XML 文件等。
  在运行时处理。有些注解在运行时可以被检查并使用。
从某些方面看,注解就像修饰符一样被使用,并应用于包、类型、构造方法、方法、成员变量、参
数、本地变量的声明中。注解可以应用到程序的类的声明、字段的声明、方法的声明以及其他程序元素
的声明中。按惯例注解要出现在它自己所在行的第一行,并且可以包括带有名称或未命名值的元素,如
下所示。
@Author(
name = "辛立伟",
date = "2008-12-12"
)
class MyClass() { }
或者
@SuppressWarnings(value = "unchecked")
void myMethod() { }
如果只有一个为值的元素,可以省略名称,如下所示。
@SuppressWarnings("unchecked")
void myMethod() { }
此外,注解也可以没有元素,这时可以省略圆括号,如下所示。
@Override
void myMethod() { } 
6
8.2.2   文档注解
许多注解可以被用来代替应该出现在代码中的注释。假设一个软件以传统的方式开始构造类,那么
通常会使用大量的注释来对类进行解释说明,如下所示。
public class B extends A{
//作者:辛立伟
//日期:2013-2-12
//当前版本:V1.0
//最后修改日期:2013-2-28
//参与者:张三,李四,赵小六
//类体代码…
}
如果要使用注解来添加同样的元数据信息,必须首先定义“注解类型”。定义注解类型的语法如下
所示。
@interface AuthorInfo {
String 作者();
String 日期();
int 当前版本() default 1;
String 最后修改日期() default "N/A";
String[ ]  参与者();          //注意,这里使用数组
}
注解类型的定义有点类似于接口的定义,使用'@'字符作为关键字 interface 的前导字符。 '@' (发“at”
音)代表注解类型。实际上,注解类型是接口的一种。
上面定义的注解的标注体内容包括“注解类型元素”声明,看上去与方法的声明类似。注意他们可
以定义默认值。一旦定义了注解类型,就可以使用该类型的注解了,像下面这样填充值进去。
@ AuthorInfo (
作者  = "辛立伟",
日期  = "2008-12-12",
当前版本  = 6,
最后修改日期  = "2008-12-28",
参与者  = {"张三", "李四", "赵小六"}        //注意使用数组符号
)
public class B extends A {
//类体的代码…
}
如果要使在“@ AuthorInfo”中的信息出现在 Javadoc 生成的文档中,必须使用“@Documented”   注
解注释“@AuthorInfo”的定义,如下所示。
import java.lang.annotation.*;          //导入这个包才能使用@Documented
@Documented
@interface AuthorInfo {
String 作者();
String 日期();
int 当前版本() default 1;
String 最后修改日期() default "N/A";
String[ ]  参与者();            //注意,这里使用数组

·7·
8.2.3   预定义注解
Java 语言规范本身在 java.lang 包中预定义有三种标准的注解类型,分别是:@Deprecated、
@SuppressWarnings 和@Override。下面逐一介绍这三个标准注解类型。
1.  @Deprecated 不提倡注解
当一个类型或者类型成员使用@Deprecated 修饰的话,编译器将不提倡使用这个被标注的程序元素。
而且这种修饰具有一定的“延续性”:如果在代码中通过继承或者覆盖的方式使用了这个过时的类型或
者成员,虽然继承或者覆盖后的类型或者成员并不是被声明为“@Deprecated”,但编译器仍然要报警。
当一个程序使用带有“@Deprecated”注解的方法、类或字段时,编译器会生成一个警告信息。当
一个元素是不提倡使用时,就应该使用 Javadoc 的“@deprecated”标签将其收入文档中,如下面代码所
示。
//Javadoc 注释
/**
* @deprecated
*  解释为什么反对它
*/
@Deprecated
static void deprecatedMethod() { }      //不提倡使用的方法声明
在 Javadoc 注释中使用“@”符号和在注解中使用“@”符号其含义是不一致的,它们仅是概念上
的相关。另外要注意,Javadoc 标签以一个小写字母“d”开头,而注解以一个大写的“D”开头。例如
下面的代码:
public interface House { 
/**
* @deprecated 
*  不提倡使用 open 方法,首选使用 openFrontDoor 或 openBackDoor 方法.
*/
@Deprecated
public void open(); 
public void openFrontDoor();
public void openBackDoor();
}
在上面的接口中,提倡使用更明确的 openFrontDoor()或 openBackDoor()方法,而不提倡使用 open()
方法。如果在程序中使用了 open()方法,那么根据标注,编译器就会生成一个警告信息。例如,再定义
一个实现了上述 House 接口的类 MyHouse,并保存为 MyHouse.java 文件。定义如下。
public class MyHouse implements House {
public void open() {}
public void openFrontDoor() {}
public void openBackDoor() {}
}
这时使用如下命令编译 MyHouse 类
javac MyHouse.java
会出现如下警告信息:
注意:MyHouse.java  使用或覆盖了已过时的  API。
注意:要了解详细信息,请使用  -Xlint:deprecation  重新编译。
如果不想编译时出现这样的警告提示信息,可以使用如下方法: 
8
public class MyHouse2 implements House {
//从 house 接口中继承文档注释标签
@Deprecated
public void open() {} 
public void openFrontDoor() {}
public void openBackDoor() {}
}
也就是说,用“@Deprecated”标注 open()方法的实现,那么在编译 MyHouse2 类时,就不会出现警
告提示信息了。
2.  @Override 重载注解
使用“@Override”注解,用来“告诉”编译器,下面的元素要重载在父类中声明的一个元素。它
说明了被标注的方法重载了父类的方法,起到了断言的作用。例如:
//  标注要被覆盖的超类的方法
@Override 
int overriddenMethod() { }
虽然重载一个方法的时候并不要求使用这个注解,但是使用它有助于防止错误。如果使用
“@Override”标注的方法不能正确地覆盖其超类中的方法,那么编译器就会生成一个错误。例如下面
的代码所示。
interface Closable {
void close();
}
class File implements Closable {
@Override
public void close() {
//...  关闭文件...
}
}
在编译类 File 时,编译器会生成一个错误,提示 File.close()方法并没有覆盖任何其超类的方法。因
为它并没有覆盖 Closable.close 方法,而是实现了接口中的 close()方法。
“@Override”   注解常常用在防止在子类中不小心写错重写父类方法的方法名。请看下面这个例子:
class A{
public void sayHello(){
System.out.println("hello");
}
}
public class AnnotationOverrideExample extends A{
public void sayHelo(){
System.out.println("hello world!");
}
}
在上面的代码中,本意是想要在子类 AnnotationOverrideExample 中重写父类 A 中的方法 sayHello(),
但是一不小心,将方法名错打成了 sayHelo()。这时编译器并不知道我们的本意是要进行方法重写,它会
认为这是一个在子类中定义的新的方法,因此程序编译时并不会报错,只有在运行时,才会发现执行结
·9·
果与预期不符,这时再查找错误产生的原因,将是非常困难的。
我们可以将上述程序代码稍加改动,在子类中重写父类的方法上加上@Override 注解,那么编译器
就会知道我们的本意是要覆盖父类中的同名方法。这时如果再进行编译,编译器就报错。在编译时发现
的错误是很容易查找原因并解决的。 如果是使用集成开发环境 IDE 进行开发的话(如 NetBeans), 不必
进行编译,IDE 就会根据注解自动识别出此类错误。
下面是添加了注解后的程序代码:
class A{
public void sayHello(){
System.out.println("hello");
}
}
public class AnnotationOverrideExample extends A{
@Override
public void sayHelo(){
System.out.println("hello world!");
}
}
对上述程序进行编译,编译器会提示如下错误:
AnnotationOverrideExample.java:9:  错误:  方法不会覆盖或实现超类型的方法
@Override
^
1  个错误
根据编译时的错误信息,我们很容易发现错误产生的原因并加以修正。
3.  @SuppressWarnings 禁止警告注解
在编写 Java 代码时,有时会因为某些原因,编译器给出一些警告信息,比如使用一个泛型集合类时
没有指定类型,编译器就会提示“unchecked warning”警告。通常当这种情况发生时,就需要查找引起
警告的代码。如果它真的表示错误,就需要纠正它。
但有时无法避免这种警告,例如,使用必须和非泛型的旧代码交互的泛型集合类时,无法避免这个
“unchecked warning”。这时可以在调用的方法前增加“@SuppressWarnings”注解修饰,告诉编译器停
止对此方法的警告。
“@SuppressWarnings”注解被用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警
告。因此,当不想出现某些警告时,使用“@SuppressWarnings”注解来“告诉”编译器,阻止生成某
些指定的警告。如下面代码所示。
//使用了一个不提倡使用的方法,并告诉编译器不要生成警告
@SuppressWarnings("deprecation")
void useDeprecatedMethod() {
objectOne.deprecatedMethod();    //不提倡使用的信息被阻止
}
如果使用了一个不提倡使用的方法,正常情况下编译器应该生成一个警告。但在上面的代码示例中,
标注所引起的警告被阻止了。
例如,在前面声明的 House 接口中,使用“@Deprecated”标注了不提倡使用的 open()方法,那么
在编译 House 接口的实现类 MyHouse 时,就会出现警告提示信息。如果要阻止编译时出现这样的警告
提 示 信 息 , 除 了 可 以 同 样 使 用 “ @Deprecated ” 标 注 open() 方 法 的 实 现 之 外 , 也 可 以 使 用
“@SuppressWarnings("deprecation")”标注禁止出现“不提倡”的警告提示信息,如下代码所示。 
10
public class MyHouse3 implements House { 
@SuppressWarnings("deprecation")
public void open() {} 
public void openFrontDoor() {}
public void openBackDoor() {}
}
每一个编译器的警告信息都属于一种类型,所以在使用@suppressWarnings 注解时,后面必须指明
禁止出现哪种类型的警告信息。在 Java 语言规范中列出了两种类型: unchecked (未检查的) 和 deprecation
(不提倡使用的) 。上面使用的是禁止 deprecation 类型的编译器警告。而当连接泛型出现以前编写的代
码时,会出现“unchecked”警告。要阻止超过一种类型的警告,使用如下的语法:
@SuppressWarnings({“unchecked”,"deprecation"})
8.2.4   注解处理
对于注解更高级的使用是自己编写一个“注解处理器”。 注解处理器可以处理 Java 程序并根据其注
解采取相应的动作。例如,生成辅助代码、帮助程序员按照预定义的模式创建模板代码等。在 JDK 中
包括有一个标注处理工具,名为“apt”。在 JDK6.0 中,apt 的功能已经是 Java 编译器的标准组成部分
了。
要使注解信息在运行时可用,其本身必须有一个@Retention(RetentionPolicy.RUNTIME)标注,如下
所示。
import java.lang.annotation.*; 
@Retention(RetentionPolicy.RUNTIME)
@interface AnnotationForRuntime {
//为运行时处理提供信息的元素
...
}
8.3  小结
枚举类型是指其字段由一系列固定的常量组成的数据类型。枚举类型的字段要用大写字母表示。在
Java 语言中,使用关键字 enum 定义一个枚举类型。有两种情况下,尽可能使用枚举类型,一种是自然
的枚举类型,另一种是程序在编译的时候,已经知道某个数据所有可能的值。
枚举类的类体中也可以包括方法和其它字段。所有的枚举都隐含地继承自 java.lang.Enum。
注解就是代码里的标记,它提供与程序有关的数据,但是注解本身不是程序一部分,它们在类加载、
运行时或是编译的时候可以被解释,但是不对程序的运行产生直接的影响。通过使用注解,程序开发人
员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息。代码分析工具、开发工具和部署
工具可以通过这些补充信息进行验证或者进行部署。
注解可以应用到程序的类的声明、字段的声明、方法的声明以及其他程序元素的声明中。Java 语言
规范本身在 java.lang 包中预定义有三种标准的注解类型,分别是:@Deprecated、@SuppressWarnings
和@Override。 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值