枚举
概述
枚举类型本质上也是一种类,只不过是这个类的对象是有限的、固定的几个,不能让用户随意创建。
在生活中枚举的例子数不胜数:
- 星期:Monday(星期一 )…… Sunday(星期天)
- 性别:Man(男) 、 Woman(女)
- 月份:January(1 月)…… December(12 月)
- 等等
在开发中,如果针对于某个类而言,其实例的个数是确定的,那么这个类就可以声明为枚举类,也推荐声明为枚举类。并且如果这个枚举类的实例就只有一个,就可以把这个枚举类看作是单例实现的。
在 JDK5.0 之前,需要程序员自定义枚举类型。在 JDK5.0 之后, Java 支持 通过 enum 关键字来快速定义枚举类型。
枚举类的使用
定义枚举类
JDK 5.0 之前
在JDK5.0 之前如何声明枚举类呢?
- 私有化类的构造器,保证不能在类的外部创建其对象。
- 在类的内部创建枚举类的实例。声明为:public static final,对外暴露这些常量对象。
- 类中如果有实例变量,应该声明为 private final(建议,不是必须),并在构造器中初始化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
public class Season {
private final String SEASON_NAME; //季节的名称
private final String SEASON_DESC; //季节的描述
private Season(String seasonName, String seasonDesc) {
this.SEASON_NAME = seasonName;
this.SEASON_DESC = seasonDesc;
}
// Season 的实例 都是静态的 可以通过类名获得
// 实例都是 final 的 防止被用户在外部改变 如:Season.SPRING = null;
// 可以写在类中的任意位置
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("冬天", "白雪皑皑");
@Override
public String toString() {
return "Season{" +
"SEASON_NAME='" + SEASON_NAME + '\'' +
", SEASON_DESC='" + SEASON_DESC + '\'' +
'}';
}
}
class SeasonTest {
public static void main(String[] args) {
System.out.println(Season.SPRING);
System.out.println(Season.SUMMER);
System.out.println(Season.AUTUMN);
System.out.println(Season.WINTER);
}
}
|
1
2
3
4
|
Season{SEASON_NAME='春天', SEASON_DESC='春暖花开'}
Season{SEASON_NAME='夏天', SEASON_DESC='夏日炎炎'}
Season{SEASON_NAME='秋天', SEASON_DESC='秋高气爽'}
Season{SEASON_NAME='冬天', SEASON_DESC='白雪皑皑'}
|
JDK 5.0 之后
在 JDK 5.0 之后,定义枚举类就非常方便了
1
2
3
4
5
6
|
// []中的内容表示可选
// <>中的内容表示必选
[public] enum <enum name> {
常量对象列表; (不同对象之间用','连接, 最后以';'结尾)
[对象的实例变量列表];
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// 举例
public enum Season {
// 定义枚举实例必须这么写 不能添加额外的东西 public static final 也不行!
// 必须在枚举类的最上面 既常量列表的上面不能有其他语句
SPRING("春暖花开"),
SUMMER("夏日炎炎"),
AUTUMN("秋高气爽"),
WINTER("白雪皑皑");
private final String SEASON_DESC; //季节的描述
// 这里的 private 可以省略
private Season(String seasonDesc) {
this.SEASON_DESC = seasonDesc;
}
}
|
如果枚举类中没有实例变量,那么定义出来枚举类就可以变成短短的一行
1
2
3
|
public enum Week {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}
|
enum定义的要求和特定:
- 枚举类的常量对象列表必须在枚举类的首行。因为是常量,所以建议大写。
- 列出的实例系统会自动添加 public static final 修饰。不需要也不能手动添加。
- 如果常量对象列表后面没有其他代码,那么 ’;’ 可以省略,否则不可以省略。
- 编译器给枚举类默认提供的是 private 的无参构造器,如果枚举类需要的是无参构造器就不需要声明,写常量对象列表时也不用加括号和参数。
- 如果枚举类需要的是有参构造器就需要手动定义,有参构造器的 private 修饰符可以省略,调用有参构造器的方法就是在常量对象名后面加括号和实参列表就可以。
- 枚举类可以作为枚举类和其他类的内部类。
- 枚举类默认继承的是 java.lang.Enum 类,因此不能再继承其他的类型。但是枚举类可以实现接口。
- JDK5.0 之后 switch 支持枚举类型, case 后面可以写枚举常量名,无需添加枚举类作为限定(当然加了也不会错)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
interface IWeek {
void showWeek();
}
public enum Week implements IWeek {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
@Override
public void showWeek() {
System.out.println("Week::showWeek()");
}
}
class TestWeek {
public static void main(String[] args) {
Week week = Week.MONDAY;
System.out.println(week.getClass());
System.out.println(week.getClass().getSuperclass());
System.out.println(week.getClass().getSuperclass().getSuperclass());
switch (week) {
case Week.MONDAY:
System.out.println("MONDAY");
break;
case TUESDAY:
System.out.println("TUESDAY");
break;
case WEDNESDAY:
System.out.println("WEDNESDAY");
break;
case THURSDAY:
System.out.println("THURSDAY");
break;
case FRIDAY:
System.out.println("FRIDAY");
break;
case SATURDAY:
System.out.println("SATURDAY");
break;
case SUNDAY:
System.out.println("SUNDAY");
break;
default:
break;
}
}
}
|
1
2
3
4
|
class uskg.kotoriforest.enumtest.Week
class java.lang.Enum
java.lang.Enum
MONDAY
|
开发中,当需要定义一组常量时,强烈建议使用枚举类。
enum中的常用方法
既然enum继承自Enum,那么肯定会得到一些方法
1
2
3
4
5
|
String toString(); // 默认返回的是常量名(对象名),可以继续手动重写该方法!
static 枚举类型[] values(); //返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值,是一个静态方法
static 枚举类型 valueOf(String name); // 可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的“名字”(即定义枚举类对象时写下的字面值,如 SPRING 的名字就是 SPRING)。如不是,会有运行时异常:IllegalArgumentException。
String name() // 得到当前枚举常量的名称。建议优先使用toString()。
int ordinal() // 返回当前枚举常量的次序号,默认从0开始
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
import java.util.Scanner;
public enum Week {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
}
class TestEnumMethod {
public static void main(String[] args) {
//values()
Week[] values = Week.values();
for (Week value : values) {
//ordinal()、name()
System.out.println((value.ordinal() + 1) + "->" + value.name());
}
System.out.println("------------------------");
Scanner input = new Scanner(System.in);
System.out.print("请输入星期值:");
int weekValue = input.nextInt();
Week week = values[weekValue - 1];
//toString()
System.out.println(week);
System.out.print("请输入星期名:");
String weekName = input.next();
//valueOf()
week = Week.valueOf(weekName);
System.out.println(week);
input.close();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
1->MONDAY
2->TUESDAY
3->WEDNESDAY
4->THURSDAY
5->FRIDAY
6->SATURDAY
7->SUNDAY
------------------------
请输入星期值:3
WEDNESDAY
请输入星期名:SUNDAY
SUNDAY
|
实现接口的枚举类
- 和普通 Java 类一样,枚举类可以实现一个或多个接口。
- 若每个枚举值在调用实现的接口方法呈现相同的行为方式,则只要在枚举类中统一实现该方法即可。
- 若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式,则可以让每个枚举值分别来实现该方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// 枚举类可以像普通的类一样,实现接口,并且可以多个,但要求必须实现里面所有的抽象方法!
enum A implements 接口1,接口2 {
//抽象方法的实现
}
// 枚举类的实例可以独立重写抽象方法!
enum A implements 接口1,接口2 {
常量名1(参数) {
//抽象方法的实现或重写
},
常量名2(参数) {
//抽象方法的实现或重写
},
//...
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
interface Info {
void show();
}
//使用 enum 关键字定义枚举类
public enum Season implements Info {
// 创建枚举类中的对象,声明在 enum 枚举类的首位
SPRING("春天", "春暖花开") {
public void show() {
System.out.println("春天在哪里?");
}
},
SUMMER("夏天", "夏日炎炎") {
public void show() {
System.out.println("宁静的夏天");
}
},
AUTUMN("秋天", "秋高气爽"),
WINTER("冬天", "白雪皑皑") {
public void show() {
System.out.println("2002 年的第一场雪");
}
};
// 声明每个对象拥有的属性 用 private final 修饰
private final String SEASON_NAME;
private final String SEASON_DESC;
// 私有化类的构造器
private Season(String seasonName, String seasonDesc) {
this.SEASON_NAME = seasonName;
this.SEASON_DESC = seasonDesc;
}
public String getSEASON_NAME() {
return SEASON_NAME;
}
public String getSEASON_DESC() {
return SEASON_DESC;
}
// 为枚举类实现抽象方法
// 如果每个枚举对象都实现了独属于自己的抽象方法 就可以不为抽象类实现 否则就要实现 这里 AUTUMN 没有实现自己的抽象方法
// 所以 AUTUMN 就会调用枚举类实现的方法 即这个方法
@Override
public void show() {
System.out.println("这是一个平凡的季节");
}
}
class SeasonTest {
public static void main(String[] args) {
for (Season season : Season.values()) {
season.show();
}
}
}
|
使用枚举来实现单例模式
1
2
3
4
5
6
7
8
9
10
11
|
// jdk5.0 之前使用枚举类定义单例模式
public class Singleton {
public static final Singleton INSTANCE = new Singleton();
private Singleton() {}
}
// jdk5.0 及之后使用枚举类定义单例模式
public enum Singleton {
instance; // public static final
/* enum 默认的构造函数就是 private 的 所以不需要自己声明 */
}
|
注解
概述
注解(Annotation)是从 JDK5.0 开始引入,以“@注解名”在代码中存在。例如:
- @Override
- @Deprecated
- @SuppressWarnings( value=" " )
Annotation 可以像修饰符一样被使用,可用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明。还可以添加一些参数值,这些信息被保存在 Annotation 的 “key=value” 键值对中。
注解可以在类编译、运行时进行加载,体现不同的功能。
注解与注释的区别
注解也可以看做是一种注释,通过使用 Annotation,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。但是,注解不同于单行注释和多行注释
- 单行注释和多行注释是给程序员看的。
- 注解是可以被编译器或其他程序读取的。程序还可以根据注解的不同,做出相应的处理。
注解的重要性
在 JavaSE 中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。但是在 JavaEE 和 Android 中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替 JavaEE 旧版中所遗留的繁冗代码和 XML 配置等。
未来的开发模式都是基于注解的,JPA是基于注解的,Spring2.5以上都是基于注解的,Hibernate3.x以后也是基于注解的,Struts2有一部分也是基于注解的了。注解是一种趋势,一定程度上可以说:框架 = 注解 + 反射 + 设计模式。
常见注解的作用
生成文档相关的注解
1
2
3
4
5
6
7
|
@author 标明开发该类模块的作者,多个作者之间使用','分割
@version 标明该类模块的版本
@see 参考转向,也就是相关主题
@since 从哪个版本开始增加的
@param 对方法中某参数的说明,如果没有参数就不能写
@return 对方法返回值的说明,如果方法的返回值类型是 void 就不能写
@exception 对方法可能抛出的异常进行说明,如果方法没有用 throws 显式抛出的异常就不能写
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class JavadocTest {
/**
* 程序的主方法,程序的入口
* @param args String[] 命令行参数
*/
public static void main(String[] args) {}
/**
* 求圆面积的方法
* @param radius double 半径值
* @return double 圆的面积
*/
public static double getArea(double radius){
return Math.PI * radius * radius;
}
}
|
在编译时进行格式检查 ( JDK 内置的三个基本注解)
1
2
3
|
@Override: 限定于重写父类方法,该注解只能用于方法
@Deprecated: 用于表示所修饰的元素(类,方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
@SuppressWarnings: 抑制编译器警告
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public class AnnotationTest {
public static void main(String[] args) {
@SuppressWarnings("unused")
int a = 10;
}
@Deprecated(since = "8")
public void print() {
System.out.println("过时的方法");
}
@Override
public String toString() {
return "重写的toString()";
}
}
|
跟踪代码依赖性,实现替代配置文件功能
Servlet3.0 提供了注解(annotation),使得不再需要在 web.xml 文件中进行 Servlet 的部署。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
// 使用注解完成相关功能
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) { }
protected void doPost(HttpServletRequest request, HttpServletResp onse response) {
doGet(request, response);
}
}
// Spring 框架中关于“事务”的管理
@Transactional(propagation=Propagation.REQUIRES_NEW,isolation=Isolation.READ_COMMITTED,readOnly=false,timeout=3)
public void buyBook(String username, String isbn) {
// 查询书的单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
// 更新库存
bookShopDao.updateBookStock(isbn);
// 更新用户的余额
bookShopDao.updateUserAccount(username, price);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 使用配置文件完成相关功能
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
<!-- 配置事务属性 -->
<tx:advice transaction-manager="dataSourceTransactionManager" id="tx Advice">
<tx:attributes>
<!-- 配置每个方法使用的事务属性 -->
<tx:methodname="buyBook"propagation="REQUIRES_NEW"isolation="READ_COMMITTED"
readonly="false"timeout="3" />
</tx:attributes>
</tx:advice>
|
三个最基本的注解
@Override
- 用于检测被标记的方法为有效的重写方法,如果不是,则报编译错误!
- 只能标记在方法上。
- 它会被编译器程序读取。
@Override 主要就是用来检查是否重写了父类或接口的的某个方法。你可能会感到困惑,正常人谁会写错啊,有必要特地写一个注解来检查吗?您还真别说,真不一定写的对。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class SuperClass {
public void showMe1Message() {
System.out.println("SuperClass.showMe1Message");
}
}
public class AnnotationTest extends SuperClass {
// 这里乍看过去好像是重写了父类的方法 但是其实并没有
// 在父类中是 '1' 而在子类中是 'l'
public void showMelMessage() {
System.out.println("AnnotationTest.showMelMessage");
}
}
|
如果你成功发现了这些细微差别,那么恭喜你,你的眼睛比我好多了,因为我真的有时候看快了就没看出来。没看出来也不要紧,因为@Override就是来解决这个问题的。
如果你没有成功重写父类的方法的话,@Override就会报错,我还不信这么刺眼的红线你都看不见。
但说实话,如果你用的是 IDEA ,Alt + Insrrt就能解决这个问题了,IDEA真是太方便了!
@Deprecated
- 用于表示被标记的数据已经过时,不推荐使用。
- 可以用于修饰属性、方法、构造、类、包、局部变量、参数。
- 它会被编译器程序读取。
说实话这个注解就目前来说,我根本就用不到,也就是在 JDK 的官方库中才有机会见到。
注解中的 since
标明从 jdk 的哪个版本开始废弃,forRemoval
标识被注解的函数等会不会在将来被删除,所以如果一个函数、类……被标记为Deprecated
就最好不要在程序中使用了。如果你使用了,编译器可能会给你报错误或者警告,但是不要紧,还是可以正常编译运行的。
@SuppressWarnings
- 抑制编译警告。当我们不希望看到警告信息的时候,可以使用
SuppressWarnings
注解来抑制警告信息。
- 可以用于修饰类、属性、方法、构造、局部变量、参数。
- 它会被编译器程序读取。
- 可以指定的警告类型有(部分):
警告类型 |
说明 |
all |
抑制所有警告 |
unchecked |
抑制与未检查的作业相关的警告 |
unused |
抑制与未用的程式码及停用的程式码相关的警告 |
deprecation |
抑制与淘汰的相关警告 |
nls |
抑制与非 nls 字串文字相关的警告 |
null |
抑制与空值分析相关的警告 |
rawtypes |
抑制与使用 raw 类型相关的警告 |
static access |
抑制与静态存取不正确相关的警告 |
static method |
抑制与可能宣告为 static 的方法相关的警告 |
super |
抑制与置换方法相关但不含 super 呼叫的警告 |
元注解
元注解简而言之来说就是作用于注解的注解。
JDK1.5 在 java.lang.annotation 包定义了4个标准的 meta-annotation 类型,它们被用来提供对其它 annotation 类型作说明。
- @Target:用于描述注解的使用范围
- 可以通过枚举类型 ElementType 的 10 个常量对象来指定
- @Retention:用于描述注解的生命周期
- 可以通过枚举类型 RetentionPolicy 的 3 个常量对象来指定,分别是SOURCE (源代码)、 CLASS (字节码)、 RUNTIME (运行时)
- 唯有 RUNTIME 阶段才能被反射读取到。
- @Documented:表明这个注解应该被 javadoc工具记录。
- @Inherited:允许子类继承父类中的注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
public enum ElementType {
/** Class, interface (including annotation interface), enum, or record
* declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation interface declaration (Formerly known as an annotation type.) */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE,
/**
* Module declaration.
*
* @since 9
*/
MODULE,
/**
* Record component
*
* @jls 8.10.3 Record Members
* @jls 9.7.4 Where Annotations May Appear
*
* @since 16
*/
RECORD_COMPONENT;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// @Deprecated 的定义
package java.lang;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
public @interface Deprecated {
String since() default "";
boolean forRemoval() default false;
}
|
自定义注解的使用
一个完整的注解应该包含三个部分:(1)声明 (2)使用 (3)读取
1
2
3
4
|
[元注解]
[修饰符] @interface <注解名> {
[成员列表]
}
|
自定义注解可以通过四个元注解 @Retention, @Target, @Inherited, @Documented 分别说明它的声明周期,使用位置,是否被继承,是否被生成到 API 文档中。
Annotation 的成员在 Annotation 的定义中以无参数有返回值的抽象方法的形式来声明,我们又称为配置参数。返回值类型只能是八种基本数据类型:String 类型、 Class 类型、 enum 类型、 Annotation 类型、以上所有类型的数组。可以使用 default 关键字为抽象方法指定默认返回值。
如果定义的注解含有抽象方法,那么使用时必须指定返回值,除非它有默认值。格式是 “方法名 = 返回值”
,如果只有一个抽象方法需要赋值,且方法名为 value,则可以省略 “ value= ”,所以如果注解只有一个抽象方法成员,建议使用方法名 value 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
// 定义自定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
// 定义带有默认值的属性
String name() default "DefaultName"; // 如果没有指定,使用默认值
int age() default 18; // 如果没有指定,使用默认值
String[] tags() default {"tag1", "tag2"}; // 数组类型默认值
}
// 使用了默认值
@MyAnnotation
class Example1 {
}
// 这里覆盖了默认值
@MyAnnotation(name = "John", age = 25, tags = {"developer", "java"})
class Example2 {
}
|
自定义注解必须配上注解的信息处理流程才有意义。我们自己定义的注解,只能使用反射的代码读取。所以自定义注解的声明周期必须是RetentionPolicy.RUNTIME。
反射我还没学到,等之后再说,嘿嘿🤭