08.枚举类和注解
枚举类的使用
1.枚举类的说明
1.枚举类的理解:类的对象只有有限个,确定的。我们称此类为枚举类
2.当需要定义一组常量时,强烈建议使用枚举类
3.如果枚举类中只有一个对象,则可以作为单例模式的实现方式。
enum 枚举类类名 {
对象1,对象2,对象3;
}
4.特点
-
1.枚举类里的构造器默认被private修饰,不能使用除了private以外的其他权限修饰符,目的是为了避免外部创建对象
-
2.枚举类里构造器允许重载
-
3.对象只能在枚举类的内部创建,而且创建的对象默认被 public static final 修饰(所以不能被继承,不能被修改)
-
4.创建枚举对象的,不是直接调用构造器new对象,而是只写枚举对象名()
如果调用的是无参的枚举构造器,() 可以省略;如果调用的是有参的构造器,()中传入参数
-
5.创建的枚举对象通常会使用全大写,表示是一个常量
-
6.创建枚举对象的代码必须要写在第一行
-
7.所有定义的枚举类都自动继承自 java.lang.Enum 类,所以枚举不能再继承其他类
-
8.枚举可以实现接口
5.java.lang.Enum 自带两个属性:name ordinal ,所有每个枚举都自带这两个属性
2.如何自定义枚举类
1.使用java原生代码实现
//自定义枚举类
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 + '\'' +
'}';
}
}
2.jdk 5.0 新增使用enum定义枚举类
//使用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.其他诉求1:获取枚举类对象的属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
}
3.枚举常见方法
enum 继承于 java.lang.Enum 类,Enum 类中重写了方法
调用方法: 类名.对象名.方法名
方法名 | 作用 |
---|---|
toString | 返回的是常量名(对象名),可以重写 |
name | 返回的是常量名(对象名),不推荐使用,建议使用toString |
values | 返回该枚举类的所有的常量对象,返回类型是当前枚举的数组类型。 |
valueOf | 根据常量名,返回一个枚举对象。 |
ordinal | 返回枚举常量的序数(它在枚举声明中的位置,其中初始常量序数为零)。 |
Season1 summer = Season1.SUMMER;
//toString():返回枚举类对象的名称
System.out.println(summer.toString());
// System.out.println(Season1.class.getSuperclass());
System.out.println("****************");
//values():返回所的枚举类对象构成的数组
Season1[] values = Season1.values();
for(int i = 0;i < values.length;i++){
System.out.println(values[i]);
}
System.out.println("****************");
Thread.State[] values1 = Thread.State.values();
for (int i = 0; i < values1.length; i++) {
System.out.println(values1[i]);
}
//valueOf(String objName):返回枚举类中对象名是objName的对象。
Season1 winter = Season1.valueOf("WINTER");
//如果没objName的枚举类对象,则抛异常:IllegalArgumentException
// Season1 winter = Season1.valueOf("WINTER1");
System.out.println(winter);
4.枚举类中对象分别实现接口
interface Info{
void show();
}
//使用enum关键字枚举类
enum Season1 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("大约在冬季");
}
};
}
5.通过javap反编译理解枚举--单例模式最简单的实现方式
public enum Enumerate {
INSTANCE;
}
Compiled from "Enumerate.java"
public final class Enumerate extends java.lang.Enum {
public static final Enumerate INSTANCE;
private static final Enumerate[] $VALUES;
public static Enumerate[] values();
public static Enumerate valueOf(java.lang.String);
private Enumerate();
static {};
}
上面是 javap 反编译 Enumerate.class 文件,得到 JVM 实际处理我们定义的 Enumerate「枚举」代码,然后我们一起看一下, JVM 对我们定义的「枚举」做了哪些处理,通过这些处理可以看一下「枚举」类型应该具有那些特性。
-
让我们定义的 Enumerate 继承 java.lang.Enum 类, java.lang.Enum 类接收 Enumerate 泛型。使用了 final 修饰了 Enumerate 类。
-
通过这个定义,我们知道「枚举」类型的本质也是一个类。「枚举」类型不允许继承其他类,因为 Java 规定类是单继承,而「 枚举」类型已经继承了 Enum 类。「枚举」类型不能被其他类继承,因为「 枚举」类型是用 final 关键字修饰的。
我们在 Java 代码中定义的 INSTANCE 「枚举」,被定义成了 Enumerate 类的实例对象,并且这个实例对象是用 public static final 修饰的。 -
我们定义的「枚举」常量是当前「枚举」类型的实例对象,所以他可以调用当前「 枚举」类型中的方法和变量,并且这个实例对象是静态不可变的。
-
定义了静态、私有的 $VALUES 数组,用来存储我们定义的「枚举」。
-
定义了静态的 values() 方法,返回当前「枚举」类型中所有的「枚举」常量。
-
定义了静态的 valueOf 方法,返回指定「枚举」类型的「枚举」常量。
-
定义了私有的构造方法。
[枚举] 类型不能被实例化。
-
定义了 static 静态代码块,用于初始化「枚举」常量和「枚举」常量数组。
我们通过反编译发现我们定义的「枚举」类型继承了 Enum 类,那我们一起来看一下 Enum 类的源码(JDK 1.8)
//Enum类所有Java语言枚举类型的公共基类
public abstract class Enum>
implements Comparable, Serializable
//「枚举」常量的名字
private final String name
//「枚举」常量声明时的顺序
private final int ordinal;
// 获取「枚举」常量 名字
public final String name() {
return name;
}
// 获取「枚举」常量 顺序
public final int ordinal() {
return ordinal;
}
// 构造方法,给「枚举」常量名字和顺序初始化
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
// 直接返回「枚举」常量名称,获取常量名称推荐使用这个方法,而不是使用 name 方法获取「枚举」常量名称
public String toString() {
return name;
}
// 直接比较内存地址
public final boolean equals(Object other) {
return this == other;
}
public final int hashCode() {
return super.hashCode();
}
// 不允许克隆,直接抛出异常
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
// 用来比较 「枚举」常量 的顺序
public final int compareTo(E o) {
java.lang.Enum other = (java.lang.Enum) o;
java.lang.Enum self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
// 此方法返回对应于此枚举常量的枚举类型的Class对象
//compareTo方法中调用了此方法,原因:详见帖子
//https://blog.csdn.net/anlian523/article/details/102531371
public final Class getDeclaringClass() {
Class clazz = getClass();
Class zuper = clazz.getSuperclass();
return (zuper == java.lang.Enum.class) ? clazz : zuper;
}
// 根据「枚举」类型和「枚举」常量名称返回对应的「枚举」常量
public static > T valueOf(Class enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
protected final void finalize() {
}
// 不允许反序列化,直接抛出异常
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
}
「枚举」是单例模式最简单的实现方式。
「枚举」类型的构造函数被编译器定义为 private ,通过 Enum 类源码可以看到,clone 和反序列化直接抛出异常,保证了每个实例对象的唯一性。对象实例都是由 JVM 来负责创建,保证了线程的安全。
下面的代码便是使用「枚举」实现了一个单例,相比于用懒汉、饿汉、双重检测、静态内部类等方式实现单例,简直简单太多了,感觉「枚举」就是 JVM 提供给我们用来实现单例模式的一个语法糖。
public enum Enumerate {
INSTANCE;
}
6.实战中的学习
注解的使用
1.注解的理解
① jdk 5.0 新增的功能
② Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation,
? 程序员可以在不改变原逻辑的情况下, 在源文件中嵌入一些补充信息。
③在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android
中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。
框架 = 注解 + 反射机制 + 设计模式
2.注解的使用示例
示例一:生成文档相关的注解
? @author 标明开发该类模块的作者,多个作者之间使用,分割
? @version 标明该类模块的版本
? @see 参考转向,也就是相关主题
? @since 从哪个版本开始增加的
? @param 对方法中某参数的说明,如果没有参数就不能写
? @return 对方法返回值的说明,如果方法的返回值类型是void就不能写
? @exception 对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写
@param @return 和 @exception 这三个标记都是只用于方法的。
@param的格式要求:@param 形参名 形参类型 形参说明
@return 的格式要求:@return 返回值类型 返回值说明
@exception的格式要求:@exception 异常类型 异常说明
@param和@exception可以并列多个
示例二:在编译时进行格式检查(JDK内置的3个基本注解)
? @Override: 限定重写父类方法, 该注解只能用于方法
? @Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的择
? @SuppressWarnings: 抑制编译器警告
示例三:跟踪代码依赖性,实现替代配置文件功能
? Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署
? spring框架中关于“事务”的管理
3.如何自定义注解
1.规则
- 定义新的 Annotation 类型使用 @interface 关键字
- 自定义注解自动继承了java.lang.annotation.Annotation接口
- Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、或以上所有类型的数组。
- 可以在定义 Annotation 的成员变量时为其指定初始值, 指定成员变量的初始值可使用default 关键字
- 如果只有一个参数成员,建议使用参数名为value
- 如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。格式是“参数名 = 参数值”,如果只有一个参数成员,且名称为value,可以省略“value=”
- 没有成员定义的 Annotation 称为标记; 包含成员变量的 Annotation 称为元数据 Annotation
- 注意:自定义注解必须配上注解的信息处理流程才有意义。
2.示例
参照@SuppressWarnings定义
① 注解声明为:@interface
② 内部定义成员,通常使用value表示
③ 可以指定成员的默认值,使用default定义
④ 如果自定义注解没成员,表明是一个标识作用
说明:
? 如果注解有成员,在使用注解时,需要指明成员的值
? 自定义注解必须配上注解的信息处理流程(使用反射)才意义
? 自定义注解通过都会指明两个元注解:Retention、Target
? @Retention 运行时注解还是编译时注解
? @Target注解作用范围
代码举例:
@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {
String value() default "hello";
}
4.元注解
JDK 的元 Annotation 用于修饰其他 Annotation 定义:即对现有的注解进行解释说明的注解
JDK5.0提供了4个标准的meta-annotation类型:@Retention @Target @Documented @Inherited
@Retention
@Target
@Documented
@Inherited
5.如何获取注解信息
通过反射来进行获取、调用 前提:要求此注解的元注解Retention中声明的生命周期状态为:RUNTIME(才会被加载到jvm中)
6.JDK8中注解的新特性:可重复注解、类型注解
1.可重复注解(元注解@Repeatable)
代码示例:
需求:在同一个类上使用2次相同的注解,每次value不同
? ① 在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
? ② MyAnnotation的Target和Retention等元注解与MyAnnotations相同。(不同则编译时报错)
? @Inherited一个有,则另一个也要有,否则编译不报错,运行时会报错
//jdk 8之前的写法:
//@MyAnnotations({@MyAnnotation(value="hi"),@MyAnnotation(value="hello")})只能使用数组的方式实现
@MyAnnotation(value="hi")
@MyAnnotation(value="hello")
class Person{
}
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
public @interface MyAnnotations {
MyAnnotation[] value();
}
@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
public @interface MyAnnotation {
String value() default "hello";
}
2.类型注解(@Target value的补充)
? ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型变量T的声明 然后可以通过反射对其进行其他额外操作)
? ElementType.TYPE_USE 表示该注解能写在使用类型
的任何语句中。(只要是类型,前面就都可以加此注解)
代码示例:
class Generic<@MyAnnotation T>{
public void show() throws @MyAnnotation RuntimeException{ //异常前
ArrayList<@MyAnnotation String> list = new ArrayList<>();//泛型前
int num = (@MyAnnotation int) 10L;//强转类型前
}
}
@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {
String value() default "hello";
}