Java注解与枚举类
枚举类
简介
枚举类是一种特殊的类,类对象只有有限个,确定的,我们称此类为枚举类我们在需要定义一组常量时用枚举类。
枚举类只有 一个对象时,则可以作为单例模式的实现方式
jdk5.0之后可以使用Enum关键字定义,也可以自定义类作为枚举类
下面我们先通过class构建枚举类来理解枚举类
自定义枚举类
class Season{
//声明对象属性,final,private修饰,这也说明了枚举类的属性都是私有final的
private final String seasonName;
private final String seasonDesc;
//私有化构造器并给对象赋值,说明了枚举类构造方法私有
private Season(String seasonName, String seasonDesc) {
this.seasonName = seasonName;
this.seasonDesc = seasonDesc;
}
//提供当前枚举类的多个对象,说明了枚举类对象都是静态不可修改的
public static final Season spring=new Season("春天","1");
public static final Season summer=new Season("夏天","2");
public static final Season autumn=new Season("秋天","3");
public static final Season winter=new Season("冬天","4");
//获取对象属性
public String getSeasonName() {
return seasonName;
}
public String getSeasonDesc() {
return seasonDesc;
}
@Override
public String toString() {
return "Season{" +
"seasonName='" + seasonName + '\'' +
", seasonDesc='" + seasonDesc + '\'' +
'}';
}
}
从代码中可以得知枚举类的特性
枚举类的属性都是私有final的
枚举类构造方法私有
枚举类对象都是静态不可修改的
使用Enum自定义枚举类
使用Enum写枚举类时可省略private,final等修饰词
public enum timeT {
Morning("早","早上"),
Afternoon("午","中午"),
Night("晚","晚上");
//有明确属性可以加属性类型,没有也可以直接写属性名
String time;
String desc;
// 也可以这么写:String time,
// desc;
timeT(String time, String desc) {
this.time = time;
this.desc = desc;
}
public String getTime() {
return time;
}
public String getDesc() {
return desc;
}
}
枚举类中上面的三个是类对象,每个对象两个String类型的属性,并包含了构造方法和get方法
当枚举类属性类型不确定时可以不写,比如 Thread.State枚举类:
枚举类常用方法
枚举类的常用方法有以下三个:
toString()返回变量名的String类型的变量名
values()返回枚举类所有对象数组
valueOf()输入String类型枚举类对象名字,返回枚举类对象,没有就抛参数异常
public class test {
public static void main(String[] args) {
System.out.println(timeT.Afternoon);
//toString()返回变量名的String类型的变量名
System.out.println(timeT.Afternoon.toString());
System.out.println(timeT.Night);
System.out.println(timeT.Night.time);
//values()返回枚举类所有对象数组
timeT[] values = timeT.values();
for (timeT value : values) {
System.out.println(value);
}
//valueOf()输入String类型枚举类对象名字,返回枚举类对象,没有就抛参数异常
timeT morning = timeT.valueOf("Morning");
System.out.println(morning.toString());
}
}
枚举类实现接口
Enum实现接口,和类一样 implements接口,重写方法即可,重写的方法可以被每个枚举类的对象调用,如果需要让每个对象调用的方法不一样,可以在对象后面加{}里面实现接口方法。
这里使用开发中常用的ErrorCode来举例
public interface IErrorCode {
/**
* 返回码
*/
long getCode();
/**
* 返回信息
*/
String getMessage();
}
public enum ResultCode implements IErrorCode {
SUCCESS(200, "操作成功"),
FAILED(500, "操作失败"),
VALIDATE_FAILED(404, "参数检验失败"),
UNAUTHORIZED(401, "暂未登录或token已经过期"),
FORBIDDEN(403, "没有相关权限");
private long code;
private String message;
private ResultCode(long code, String message) {
this.code = code;
this.message = message;
}
public long getCode() {
return code;
}
public String getMessage() {
return message;
}
}
需要对特定对象自定义接口方法是只需在该对象构造的下面加{}即可
SUCCESS(200, "操作成功"),
FAILED(500, "操作失败")
{
@Override
public long getCode()
{
return 6666;
}
},
VALIDATE_FAILED(404, "参数检验失败"),
UNAUTHORIZED(401, "暂未登录或token已经过期"),
FORBIDDEN(403, "没有相关权限");
注解
Annotation 其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用Annotation,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。
框架=注解+反射+设计模式
Annotation可以像修饰符一样被使用,可用于修饰包,类,构造器,方法,成员变量,参数,局部变量的声明,这些信息被保存在Annotation的“name=value”对中。
JDK内置3个基本注解
@Override:限定重写父类方法,该注解只能用于方法
@Deprecated:用于表示所修饰的元素(类,方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
@SuppressWarnings:抑制编译器警告
自定义注解
定义Annotation注解用@interface 关键字
自定义注解自动继承了java.lang.annotation.Annotation接口,Annotation的成员变量在Annotation 定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。
类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组。
定义Annotation 的成员变量时使用default关键字设定初始值
只有一个参数成员,建议用参数名为value如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。格式是“参数名=参数值”,如果只有一个参数成员,且名称为value,可以省略“value=”
没有成员定义的Annotation称为标记;包含成员变量的Annotation称为元数据Annotation
注意:自定义注解必须配上注解的信息处理流程(反射)才有意义。
自定义注解在Java中可以通过反射机制来获取注解信息,并且在运行时可以根据注解的信息进行相应的处理。
元注解
元注解:修饰其他注解的注解
@Retention:指明所修饰注解的声明周期
RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释
RetentionPolicy.CLASS:在class文件中有效(即class保留),当运行Java程序时,JVM不会保留注解。这是默认值
RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行Java程序时,JVM会保留注释。程序可以通过反射获取该注释。
@Target 指定修饰的对象,方法,类,属性,形参,构造器,局部变量,可写多个
以上是自定义注解常用的
@Documented:标识所修饰的注解在Javadoc解析时保留
@Inherited:可继承性,注解修饰类时,修饰父类,子类也会自动被该注解修饰
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE,METHOD,FIELD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE,TYPE_PARAMETER})
@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
//内部定义成员变量,default指定默认值,无默认值则必须指定,只有一个成员变量时可以不写value="",直接写""
String value() default "hello";
//如果自定义注解没有成员,表明是一个标识作用(有的接口没有抽象方法,也可以作为标识接口)
//注解依赖于反射,添加注解后,利用反射读注解,再确定注解作用
}
jdk8注解新特性
1.可重复注解
可重复注解:在一个类上修饰两个同一注解,新建一个注解,属性为老注解的数组,注解上修饰@Repeatable(MyAnnotation.class)
其余元注解配置一致即可
比如:
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE,METHOD,FIELD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE,TYPE_PARAMETER})
@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
//内部定义成员变量,default指定默认值,无默认值则必须指定,只有一个成员变量时可以不写value="",直接写""
String value() default "hello";
//如果自定义注解没有成员,表明是一个标识作用(有的接口没有抽象方法,也可以作为标识接口)
//注解依赖于反射,添加注解后,利用反射读注解,再确定注解作用
}
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE,METHOD,FIELD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE})
public @interface MyAnnotations {
MyAnnotation[] value();
}
2.注解类型
注解类型:在注解修饰@Target中新增注解TYPE_PARAMETER可修饰泛型
@Target({TYPE,METHOD,FIELD,PARAMETER,CONSTRUCTOR,LOCAL_VARIABLE,TYPE_PARAMETER})
自定义注解,校验器实现注解
自定义注解还可以配合SpringAOP或者自定义校验器实现特定功能
业务需求:设定多个时间线,时间线的开始时间必须早于结束时间
自定义注解
@Documented
@Constraint(validatedBy = EndTimeAfterStartTimeValidator.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EndTimeAfterStartTime {
String message() default "结束时间必须晚于开始时间";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@Constraint(validatedBy = EndTimeAfterStartTimeValidator.class)
注解是一个约束注解,并且指定了该注解的验证器,意味着在运行时,当使用了 @EndTimeAfterStartTime 注解时,会调用 EndTimeAfterStartTimeValidator 类中定义的逻辑来进行验证。
自定义验证器逻辑
public class EndTimeAfterStartTimeValidator implements ConstraintValidator<EndTimeAfterStartTime, AddTimeLineCmd> {
@Override
public boolean isValid(AddTimeLineCmd addTimeLineCmd, ConstraintValidatorContext constraintValidatorContext) {
if(addTimeLineCmd.getProposalStart() != null && addTimeLineCmd.getProposalEnd() != null && addTimeLineCmd.getProposalEnd().before(addTimeLineCmd.getProposalStart())) {
return false;
}
if(addTimeLineCmd.getApprovalStart() != null && addTimeLineCmd.getApprovalEnd() != null && addTimeLineCmd.getApprovalEnd().before(addTimeLineCmd.getApprovalStart())) {
return false;
}
if(addTimeLineCmd.getInitialReviewStart() != null && addTimeLineCmd.getInitialReviewEnd() != null && addTimeLineCmd.getInitialReviewEnd().before(addTimeLineCmd.getInitialReviewStart())){
return false;
}
if(addTimeLineCmd.getProvisionalCaseStart() != null && addTimeLineCmd.getProvisionalCaseEnd() != null && addTimeLineCmd.getProvisionalCaseEnd().before(addTimeLineCmd.getProvisionalCaseStart())) {
return false;
}
if(addTimeLineCmd.getFormalCaseStart() != null && addTimeLineCmd.getFormalCaseEnd() != null && addTimeLineCmd.getFormalCaseEnd().before(addTimeLineCmd.getFormalCaseStart())){
return false;
}
if(addTimeLineCmd.getUndertakeStart() != null && addTimeLineCmd.getUndertakeEnd() != null && addTimeLineCmd.getUndertakeEnd().before(addTimeLineCmd.getUndertakeStart())) {
return false;
}
if(addTimeLineCmd.getFeedbackStart() != null && addTimeLineCmd.getFeedbackEnd() != null && addTimeLineCmd.getFeedbackEnd().before(addTimeLineCmd.getFeedbackStart())){
return false;
}
return true;
}
}
自定义验证器实现了ConstraintValidator接口,接口泛型(第一个是要验证的注解,第二个是要验证的参数类型)
当使用了 @EndTimeAfterStartTime 注解的地方,比如在某个类或者方法上,并传递了 AddTimeLineCmd 对象作为参数时,验证框架会将这个对象传递给自定义验证器。
验证器注入
开发环境为SpringBoot,所以需要将验证器注入IOC容器作为Bean
@Configuration
public class ValidatorConfig {
@Bean
public EndTimeAfterStartTimeValidator endTimeAfterStartTimeValidator(){
return new EndTimeAfterStartTimeValidator();
}
}
验证器返回true或False后的逻辑
在SpringBoot的全局异常处理器中配置该异常。当验证器返回 false 结果时,会抛出 ConstraintViolationException 异常。
package com.neu.dto.cmd;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.neu.annotation.EndTimeAfterStartTime;
import lombok.Data;
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.Date;
/**
* @Author lwylwyll
* @Date 2023/10/24
* @Description:
*/
@Validated
@EndTimeAfterStartTime(message = "")
@Data
public class AddTimeLineCmd {
@NotBlank
private String session;
@NotBlank
private String degree;
@NotNull
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date proposalStart;
@NotNull
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date proposalEnd;
@NotNull
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date approvalStart;
@NotNull
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date approvalEnd;
@NotNull
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date initialReviewStart;
@NotNull
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date initialReviewEnd;
@NotNull
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date provisionalCaseStart;
@NotNull
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date provisionalCaseEnd;
@NotNull
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date formalCaseStart;
@NotNull
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date formalCaseEnd;
@NotNull
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date undertakeStart;
@NotNull
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date undertakeEnd;
@NotNull
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date feedbackStart;
@NotNull
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date feedbackEnd;
}