枚举

枚举介绍

先看一个需求:要求创建季节对象,请设计并完成(按照这种设计方式,不能体现季节是固定的四个对象。因此,这样的设计不好)

image-20250101074811392

枚举类:枚:一个一个,举:列举,即,把一个一个的具体对象列举出来。
创建Season对象有如下特点:

  • 季节的值是有限的几个值(spring,summer,autumn,winter)
  • 只读,不需要修改

解决方案——枚举:

  • 枚举对应英文:enumeration,简写enum
  • 枚举是一组常量的集合
  • 可以这样理解:枚举属于一种特殊的类,里面只包含一组有限的特定的对象

枚举的两种实现方式:

  • 自定义类实现枚举、

  • 使用enum关键字实现枚举

自定义枚举

自定义类实现枚举:

  1. 不需要提供setXxx方法,因为枚举对象值通常为只读
  2. 对枚举对象/属性使用final+static共同修饰,实现底层优化
  3. 枚举对象名通常使用全部大写,这是常量的命名规范
  4. 枚举对象根据需要,也可以有多个属性
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
package com.study.enum_;

public class Enumeration02 {
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);
}
}
//演示自定义枚举类
class Season{
private String name;
private String desc;
//3.在Season类内部直接创建固定的对象
//4.优化,可以加入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("冬天", "寒冷");

//1.把构造器做成私有的
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
//2.不能有set方法,防止属性被修改
public String getName() {
return name;
}

public String getDesc() {
return desc;
}

@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}

总结:自定义类实现枚举,有如下特点

  1. 构造器私有化
  2. 本类内部创建一组对象【四个,春夏秋冬】
  3. 对外暴露对象(通过为对象添加public static final修饰符)
  4. 可以提供get方法,但是不能提供set方法

enum关键字实现枚举

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
package com.study.enum_;

public class Enumeration03 {
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);
}
}
//演示使用enum关键字定义枚举类
//1.使用enum关键字来替代class
//2.public static final Season SPRING = new Season("春天", "温暖");直接使用SPRING("春天", "温暖");来替代
//3.如果有多个对象,使用逗号间隔即可SPRING("春天","温暖"),SUMMER("夏天", "炎热");
//4.如果使用enum关键字来实现枚举,要求将常量对象写在最前面
enum Season{
SPRING("春天","温暖"),
SUMMER("夏天", "炎热"),
AUTUMN("秋天", "凉爽"),
WINTER("冬天", "寒冷");
private String name;
private String desc;

private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}

public String getName() {
return name;
}

public String getDesc() {
return desc;
}

@Override
public String toString() {
return "Season{" +
"name='" + name + '\'' +
", desc='" + desc + '\'' +
'}';
}
}

注意:

  • 当我们使用enum关键字开发一个枚举类时,默认会继承Enum类
  • 传统的public static final Season SPRING = new Season(“春天”, “温暖”);简化成SPRING(“春天”, “温暖”),这里必须知道,它调用的是哪个构造器
  • 如果使用无参构造器创建枚举对象,则实参列表和小括号都可以省略
  • 当有多个枚举对象时,使用逗号间隔,最后由一个分号结尾
  • 枚举对象必须放在枚举类的行首

注解

注解概述

注解概述

  • 注解(annotation),是一种代码级别的说明,和类 接口平级关系.

    • 注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,以后,javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无标记,看你的程序有什么标记,就去干相应的事

    • 我们之前使用过的注解:

      1)@Override:子类重写方法时——编译时起作用

      2)@FunctionalInterface:函数式接口——编译时起作用

      3)@Test: 的测试注解——运行时起作用

注解的作用

  • 生成帮助文档@author和@version

  • 执行编译期的检查 例如:@Override

  • 框架的配置(框架=代码+配置)

    • 具体使用请关注框架课程的内容的学习。

小结

  1. 注解用在“源码中”,作为一个“标记”。给“注解解析器”看的,告诉“注解解析器”怎样编译、运行下面的代码。
  2. 开发中,我们一般都是使用注解

JDK提供的三个基本的注解

@Override:描述方法的重写.

@SuppressWarnings:压制\忽略警告.

@Deprecated:标记过时

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
@SuppressWarnings("all")
class Fu{
public void show(){

}
}
class Zi extends Fu{
@Override
public void show(){

}
}
public class Demo {
public static void main(String[] args) {
/*
JDK提供的三个基本的注解:
@Override:描述方法的重写.
@SuppressWarnings:压制\忽略警告.
@Deprecated:标记过时
*/
@SuppressWarnings("all")
int num;
}

@Deprecated
public static void method1(){

}

public static void method2(){

}
}

自定义注解

自定义注解语法

1
2
3
public @interface 注解名{
属性
}
  • 示例代码
1
2
3
4
5
6
7
/**
* 定义了注解
*
*/
public @interface Annotation01 {

}

注解属性

格式

  • 数据类型 属性名();

属性类型

​ 1.基本类型

​ 2.String

​ 3.Class类型

​ 4.注解类型

​ 5. 枚举类型

​ 6.以上类型的一维数组类型

  • 示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public @interface Annotation01 {
// 1.基本数据类型(4类8种)
int a();
double b();

// 2.String类型
String c();

// 3.Class类型
Class d();

// 4.注解类型
Annotation02 f();

// 5.枚举类型
Sex e();
// 6.以上类型的一维数组类型
int[] g();
double[] h();
String[] i();
Sex[] j();
Annotation02[] k();
}

使用注解并给注解属性赋值

1
2
3
4
5
 使用注解:
如果一个注解中有属性,那么使用注解的时候一定要给注解属性赋值
如果一个注解没用属性,那么就不需要给注解属性赋值,直接使用即可
如何给注解属性赋值:
@注解名(属性名=值,属性名2=值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
public @interface MyAnnotation1 {
// 不带属性的注解
}



public @interface MyAnnotation2 {
// 带属性的注解
String name();
int age();
String[] arr();
}



@MyAnnotation1
@MyAnnotation2(name="张三",age=18,arr={"itheima","itcast"})
public class Test1 {
@MyAnnotation1
String str;

@MyAnnotation1
@MyAnnotation2(name="张三",age=18,arr={"itheima","itcast"})
public static void main(String[] args) {
/*
注解使用:
不带属性的注解:@注解名
带属性的注解: @注解名(属性名=属性值,属性名=属性值,...)

注意:带有属性的注解在使用的时候一定要给属性赋值,并且所有属性都要赋值
*/
@MyAnnotation1
@MyAnnotation2(name="张三",age=18,arr={"nbchen","zuoer"})
int num = 10;
}
}


给注解属性赋值的注意事项

  • 一旦注解有属性了,使用注解的时候,属性必须有值
  • 若属性类型是一维数组的时候,当数组的值只有一个的时候可以省略{}
  • 如果注解中只有一个属性,并且属性名为value,那么使用注解给注解属性赋值的时候,注解属性名value可以省略
  • 注解属性可以有默认值 格式:属性类型 属性名() defaul t 默认值;
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public @interface MyAnnotation1 {
int a();
}

public @interface MyAnnotation2 {
int[] arr();
}

public @interface MyAnnotation3 {
int value();
}

public @interface MyAnnotation33 {
String[] value();
}

public @interface MyAnnotation4 {
int a() default 10;
}

public class Test {
public static void main(String[] args) {
/*
给注解属性赋值的注意事项:
- 一旦注解有属性了,使用注解的时候,属性必须有值
- 若属性类型是一维数组的时候,当数组的值只有一个的时候可以省略{}
- 如果注解中只有一个属性,并且属性名为value,那么使用注解给注解属性赋值的时候,注解属性名value可以省略
- 注解属性可以有默认值 格式:属性类型 属性名() defaul t 默认值;

*/
}

// 注解属性可以有默认值 格式:属性类型 属性名() defaul t 默认值;
//@MyAnnotation4
//@MyAnnotation4()
@MyAnnotation4(a = 100)
public static void method4(){

}

// 若属性类型是一维数组的时候,当数组的值只有一个的时候可以省略{}
//如果注解中只有一个属性,并且属性名为value,那么使用注解给注解属性赋值的时候,注解属性名value可以省略
//@MyAnnotation33(value={"nbchen","zuoer"})
//@MyAnnotation33(value={"nbchen"})
//@MyAnnotation33(value="nbchen")
@MyAnnotation33("itheima")
public static void method33(){

}

// 如果注解中只有一个属性,并且属性名为value,那么使用注解给注解属性赋值的时候,注解属性名value可以省略
//@MyAnnotation3(value=10)
@MyAnnotation3(10)
public static void method3(){

}

// 若属性类型是一维数组的时候,当数组的值只有一个的时候可以省略{}
// @MyAnnotation2(arr={10,20,30})
// @MyAnnotation2(arr={10})
@MyAnnotation2(arr=10)
public static void method2(){

}

// 一旦注解有属性了,使用注解的时候,属性必须有值
@MyAnnotation1(a = 10)
public static void method1(){

}

}

元注解

什么是元注解

​ 定义在注解上的注解

常见的元注解

@Target:表示该注解作用在什么上面(位置),默认注解可以在任何位置. 值为:ElementType的枚举值

  • METHOD:方法
  • TYPE:类 接口
  • FIELD:字段
  • CONSTRUCTOR:构造方法声明

@Retention:定义该注解保留到那个代码阶段, 值为:RetentionPolicy类型,默认只在源码阶段保留

  • SOURCE:只在源码上保留(默认)
  • CLASS:在源码和字节码上保留
  • RUNTIME:在所有的阶段都保留

.java (源码阶段) —-编译—> .class(字节码阶段) —-加载内存–> 运行(RUNTIME)

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {
// 限制该注解只能在方法上和类上使用
// 设置注解保留到运行阶段
}


@MyAnnotation1
public class Test {
//@MyAnnotation1 // 编译报错
int num;

@MyAnnotation1
public static void main(String[] args) {
//@MyAnnotation1 // 编译报错
String str;
}
}

注解解析

java.lang.reflect.AnnotatedElement接口: Class、Method、Field、Constructor等实现了AnnotatedElement

  • T getAnnotation(Class<T>annotationType):得到指定类型的注解引用。没有返回null。

  • boolean isAnnotationPresent(Class<?extends Annotation> annotationType):判断指定的注解有没有。

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
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {
String name();

int age();

}


public class Test {

@MyAnnotation1(name="张三",age=18)
public void show1(){
System.out.println("show1方法执行了....");
}

public void show2(){
System.out.println("show2方法执行了....");
}

public static void main(String[] args) throws Exception{
/*
java.lang.reflect.AnnotatedElement接口: Class、Method、Field、Constructor等实现了AnnotatedElement
- T getAnnotation(Class<T> annotationType):得到指定类型的注解引用。没有返回null。
- boolean isAnnotationPresent(Class<?extends Annotation> annotationType):判断指定的注解有没有。

*/
// 需求:1.获取show1方法上面的注解对象
// 1.1 得到Test类的Class对象
Class<?> c = Class.forName("com.itheima.demo12_注解解析.Test");

// 1.2 获得show1方法的Method对象
Method show1M = c.getDeclaredMethod("show1");

// 1.3 根据Method对象调用getAnnotation()方法得到注解对象
MyAnnotation1 a1 = show1M.getAnnotation(MyAnnotation1.class);
System.out.println(a1.name());
System.out.println(a1.age());

System.out.println("======================");

// 2.需求: 判断某个方法上是否有MyAnnotation1注解
// 判断show1方法上是否有MyAnnotation1注解
boolean res1 = show1M.isAnnotationPresent(MyAnnotation1.class);
System.out.println(res1);// true

// 判断show2方法上是否有MyAnnotation1注解
Method show2M = c.getDeclaredMethod("show2");
boolean res2 = show2M.isAnnotationPresent(MyAnnotation1.class);
System.out.println(res2);// false

}
}

完成注解的MyTest案例

需求

​ 在一个类(测试类,TestDemo)中有三个方法,其中两个方法上有@MyTest,另一个没有.还有一个主测试类(MainTest)中有一个main方法. 在main方法中,让TestDemo类中含有@MyTest方法执行. 自定义@MyTest, 模拟单元测试.

思路分析

  1. 定义两个类和一个注解

  2. 在MainTest的main()方法里面:

1
2
3
4
//1.获得TestDemo字节码对象
//2.反射获得TestDemo里面的所有的方法
//3.遍历方法对象的数组. 判断是否有@MyTest(isAnnotationPresent)
//4.有就执行(method.invoke())

代码实现

  • MyTest.java
1
2
3
4
5
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {

}
  • TestDemo.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class TestDemo {
@MyTest
public void show1(){
System.out.println("show1方法执行了...");
}

@MyTest
public void show2(){
System.out.println("show2方法执行了...");
}

public void show3(){
System.out.println("show3方法执行了...");
}

}

  • MainTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MainTest {

public static void main(String[] args) throws Exception {
// 让第一个类中含有@MyTest注解的方法执行
// 1.获取TestDemo类的字节码对象
Class<TestDemo> clazz = TestDemo.class;

// 2.使用字节码对象获取该类中所有方法对象
Method[] methods = clazz.getDeclaredMethods();

// 3.循环遍历所有方法对象
for (Method method : methods) {
// 4.在循环中,判断遍历出来的方法对象是否含有@MyTest注解
boolean res = method.isAnnotationPresent(MyTest.class);
if (res) {
// 5.如果有,就调用该方法执行
method.invoke(clazz.newInstance());
}
}

}

}