第一章:枚举

1.1 概述

  • 类的对象只有有限个,确定的。例如:
    • 星期:Monday (星期一)、……、Sunday (星期天)。
    • 性别:Man (男)、Woman (女)。
    • 季节:Spring (春节)……Winter (冬天)。
    • 支付方式:Cash(现金)、WeChatPay(微信)、Alipay (支付宝)、BankCard (银 行卡)、CreditCard (信用卡)。
    • 就职状态:Busy、Free、Vocation、Dimission。
    • 订单状态:Nonpayment(未付款)、Paid(已付款)、Delivered(已发货)、 Return(退货)、Checked(已确认)Fulfilled(已配货)。
    • 线程状态:创建、就绪、运行、阻塞、死亡。
  • 枚举类型本质也是也是一种类,只不多是这个类的对象是固定的几个,而不能随意让用户创建。
  • 在JDK 5 之前,需要程序员自定义枚举类。
  • 在JDK 5 之后,Java 支持通过 enum 关键字来快速的定义枚举类型。

1.2 JDK 5 之前

  • 在JDK 5 之前如何声明枚举类?

    • ① 私有化构造器,保证在类的外部不能创建其对象。
    • ② 在类的内部创建枚举类的实例,通过 public static final 修饰。
    • ③ 对象如果有实例变量,通过 private final 修饰,并在构造器中进行初始化。
  • 示例:

  1. package com.github.demo18;
  2. /**
  3. * @author 许大仙
  4. * @version 1.0
  5. * @since 2021-09-10 16:58
  6. */
  7. public class Gender {
  8. // 在类的内部创建枚举类的实例,通过`public static final`修饰。
  9. public static final Gender MAN = new Gender("男");
  10. public static final Gender WOMAN = new Gender("女");
  11. // 对象如果有实例变量,通过`private final`修饰,并在构造器中进行初始化。
  12. private final String gender;
  13. // 私有化构造器,保证在类的外部不能创建其对象。
  14. private Gender(String gender) {
  15. this.gender = gender;
  16. }
  17. @Override
  18. public String toString() {
  19. return "Gender{" + "gender='" + this.gender + '\'' + '}';
  20. }
  21. }
package com.github.demo18;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-10 17:03
 */
public class Person {

    private Gender gender;
    private String name;
    private int age;

    public Person(Gender gender, String name, int age) {
        this.gender = gender;
        this.name = name;
        this.age = age;
    }

    public Gender getGender() {
        return this.gender;
    }

    public void setGender(Gender gender) {
        this.gender = gender;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" + "gender=" + this.gender + ", name='" + this.name + '\'' + ", age=" + this.age + '}';
    }
}
package com.github.demo18;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-10 17:09
 */
public class Test {
    public static void main(String[] args) {
        Person person = new Person(Gender.MAN, "张三", 18);
        System.out.println(person);
    }
}

1.3 JDK 5 之后

  • 语法:
权限修饰符 enmu 枚举类名 {
    // 常量对象列表
}
权限修饰符 enmu 枚举类名 {
    // 常量对象列表

    // 其他成员列表
}
  • 解释:
    • ① 枚举类的常量列表必须在枚举类的首行,因为是常量,所以建议大写。
    • ② 如果常量列表后面没有其他代码,那么 ; 可以省略,否则 ; 不可以省略。
    • ③ 常量对象之间必须用 , 隔开 ; 结尾。
    • ④ 编译器给枚举类默认提供的是 private 修饰的无参构造器,如果枚举类需要的是无参构造器,则不需要声明,写常量对象列表的时候也不需要加参数。
    • ⑤ 如果枚举类需要的是有参构造器,需要手动定义 private 的有参构造,调用有参构造的方法就是在常量对象名后面加 (实参列表) 即可。
    • ⑥ 枚举类默认继承的是 java.lang.Enum 类,所以不能再继承其他的类。

枚举类的类图.png

  • ⑦ JDK 5 之后的 switch 支持枚举,即 case 后面可以写枚举常量对象名。
  • ⑧ 枚举类如果有其他属性,建议也声明为 final 的,因为常量对象在逻辑上应该不可变。
  • 示例:
package com.github.demo19;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-13 09:05
 */
public enum Color {
    RED, GREEN, BLUE
}
  • 示例:
package com.github.demo19;

/**
 * 性别
 *
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-13 08:56
 */
public enum Gender {

    MAN("男"), WOMAN("女");

    private final String name;

    Gender(String name) {
        this.name = name;
    }
}

1.4 枚举常用方法

  • 返回的是常量名(对象名),可以继续手动重写该方法!
public String toString()
  • 返回枚举类型的对象数组。
public static T[] values()
  • 将字符串转换为对应的枚举对象,要求字符串必须是枚举类对象名,否则将报异常。
public Enum valueOf(String str)
  • 返回的是常量名(对象名),很少用
public final String name()
  • 返回的是常量的次序号,默认从 0 开始
public final int ordinal()
  • 示例:
package com.github.demo19;

/**
 * 性别
 *
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-13 08:56
 */
public enum Gender {

    MAN("男"), WOMAN("女");

    private final String name;

    Gender(String name) {
        this.name = name;
    }
}
package com.github.demo19;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-13 09:28
 */
public class Test {
    public static void main(String[] args) {
        Gender[] values = Gender.values();
        for (Gender value : values) {
            System.out.println(value);
        }

        Gender man = Gender.valueOf("MAN");
        System.out.println(man);
    }
}
  • 示例:
package com.github.demo20;

/**
 * 天气
 * 
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-13 08:58
 */
public enum Season {

    SPRING("春天", "春暖花开"), SUMMER("夏天", "夏日炎炎"), AUTUMN("秋天", "秋高气爽"), WINTER("冬天", "白雪皑皑");

    // 名称
    private final String name;
    // 描述
    private final String desc;

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

    public String getName() {
        return name;
    }

    public String getDesc() {
        return desc;
    }
}
package com.github.demo20;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-13 09:40
 */
public class Test {
    public static void main(String[] args) {
        Season[] values = Season.values();
        for (Season value : values) {
            switch (value) {
                case SPRING:
                    System.out.println(Season.SPRING.getName());
                    break;
                case SUMMER:
                    System.out.println(Season.SUMMER.getName());
                    break;
                case AUTUMN:
                    System.out.println(Season.AUTUMN.getName());
                    break;
                case WINTER:
                    System.out.println(Season.WINTER.getName());
                    break;
            }
        }
    }
}

1.5 枚举实现接口(了解)

  • 和普通 Java 类一样,枚举类可以实现一个或多个接口。
  • 如果每个枚举值在调用实现的接口方法呈现的是相同的行为方法,则只需要统一实现该方法即可。
  • 如果需要每个枚举值在调用实现的接口方法呈现不同的行为方式,则可以让每个枚举值分别来实现该方法。

  • 示例:

package com.github.demo21;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-13 09:50
 */
public interface Run {

    void run();
}
package com.github.demo21;

/**
 * 
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-13 08:56
 */
public enum Gender implements Run {

    MAN, WOMAN;

    @Override
    public void run() {
        System.out.println("走路啊");
    }
}
package com.github.demo21;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-13 09:50
 */
public class Test {
    public static void main(String[] args) {
        Gender man = Gender.MAN;
        man.run(); // 走路啊

        Gender woman = Gender.WOMAN;
        woman.run(); // 走路啊

    }
}
  • 示例:
package com.github.demo22;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-13 09:50
 */
public interface Run {

    void run();
}
package com.github.demo22;

/**
 * 
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-13 08:56
 */
public enum Gender implements Run {

    MAN {
        @Override
        public void run() {
            System.out.println("男生大步流星的走路");
        }
    },
    WOMAN {
        @Override
        public void run() {
            System.out.println("女生婀娜多姿的走路");
        }
    }

}
package com.github.demo22;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-09-13 09:56
 */
public class Test {
    public static void main(String[] args) {
        Gender man = Gender.MAN;
        man.run(); // 男生大步流星的走路

        Gender woman = Gender.WOMAN;
        woman.run(); // 女生婀娜多姿的走路
    }
}

第二章:注解

2.1 概述

2.1.1 什么是注解?

  • 从JDK 5 开始,Java 增加了对元数据(MetaData)的支持,也就是 Annotation(注解)。
  • 注解其实就是代码里的 特殊标记 ,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用注解,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证和部署。
  • 注解可以像修饰符一样被使用,可以用于 修饰包、类、构造器、方法、成员变量、参数、局部变量 的声明,这些信息被保存在注解的 name=value中。

2.1.2 注解的作用

  • 执行编译器的检查,例如:@Override 注解。
  • 分析代码:主要的用途,用来替代配置文件。
  • 用在框架里面,注解开发。

2.2 JDK 内置的三种基本注解

  • @Override :限定重写父类方法,该注解只能用于方法。
  • @Deprecated :用于表示所修饰的元素(类、方法等)已经过时。通常是因为所修饰的结构危险或存在更好的选择。
  • @SuppressWarnings :抑制编译器警告。

2.3 自定义注解

2.3.1 注解的定义

  • 自定义注解必须使用 @interface 关键字。
  • 自定义注解自动继承了 java.lang.annotation.Annotation 接口,换言之,注解是一种特殊的接口。
  • 语法:
修饰符 @interface 注解名 {
    属性类型 属性名() default 默认值;
}
  • 注解的属性类型只能是:
    • ① String。
    • ② 8 种基本数据类型。
    • ③ 枚举类型。
    • ④ 注解类型。
    • ⑤ Class类型。
    • ⑥ 以上类型的一维数组类型。
  • 可以在定义注解的属性的时候使用 default 关键字指定初始化值。
  • 如果注解只有一个属性,建议属性的名称为 value 。
  • 如果自定义的注解含有属性,那么使用的时候必须给注解的属性赋值,除非它有默认值。格式是 属性=值 ,如果只有一个属性,且名称是 value ,怎可以省略 value=
  • 没有属性的注解称为 标记 ,有属性的注解称为 元数据注解

  • 示例:

package com.github.demo11;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-10-07 17:47
 */
public enum Color {
    RED, GREEN, BLUE
}
package com.github.demo11;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-10-07 17:46
 */
public @interface MyAnnotation {
    // 基本数据类型
    int num();

    // String类型
    String value();

    // 枚举类型
    Color color();

    // Class类型
    Class clazz();

    // 注解类型
    Override override();

    // 以上类型的数组类型
    String[] values();
}

2.3.2 注解的使用

  • 语法:
@注解名(属性名=值,属性名2=值,...)
  • 特殊情况:

    • ① 注解属性有默认值。

      属性类型 属性名() default 默认值;
      
    • ② 如果注解的属性类型是一维数组,当数组的值只有一个的时候,可以省略 {}

      @MyAnnotation(ss={"aa","bb"})
      
      @MyAnnotation(ss="aa")
      
    • ③ 如果只有一个属性要赋值的时候,且属性名为 value ,赋值的时候可以省略 value=

  • 示例:

package com.github.demo11;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-10-07 17:47
 */
public enum Color {
    RED, GREEN, BLUE
}
package com.github.demo11;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-10-07 17:46
 */
public @interface MyAnnotation {
    // 基本数据类型
    int num();

    // String类型
    String value();

    // 枚举类型
    Color color();

    // Class类型
    Class clazz();

    // 注解类型
    Override override();

    // 以上类型的数组类型
    String[] values();
}
package com.github.demo11;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-10-07 18:03
 */
public class Person {
    @MyAnnotation(num = 0, value = "你好,许大仙", color = Color.RED, clazz = Person.class, override = @Override, values = {"aa","bb"})
    private String name;
}

2.4 元注解

2.4.1 概述

  • 元注解是用于修饰自定义注解的。

2.4.2 常用的元注解

  • JDK 5 提供了四个标准的元注解,分别是:
    • @Retention 注解:
      • 定义该注解可以保留在哪个代码阶段,值为 RetentionPolicy 类型。
      • RetentionPolicy.SOURCE :只在源码阶段保留。
      • RetentionPolicy.CLASS :在源码和字节码上保留,默认。
      • RetentionPolicy.RUNTIME :在源码、字节码和运行阶段保留。
    • @Target 注解:
      • 定义该注解可以作用在什么范围,默认注解可以在任何位置,值为 ElementType 的枚举值。
      • ElementType.TYPE :作用在类、接口(包括注解类型)或 enum 上。
      • ElementType.FIELD :作用在属性上。
      • ElementType.METHOD :作用在方法上。
      • ElementType.PARAMETER :作用在参数上。
      • ElementType.CONSTRUCTOR :作用在构造器上。
      • ElementType.LOCAL_VARIABLE :作用在局部变量上。
      • ElementType.ANNOTATION_TYPE :作用在注解上。
      • ElementType.PACKAGE :作用在包上。
      • ElementType.TYPE_PARAMETER :作用在类型声明上,如泛型声明(JDK 8 新增)。
      • ElementType.TYPE_USE :作用在使用类型的任何语句中(JDK 8 新增)。
    • @Document 注解:
      • 定义该注解可以被 javadoc 工具提取成文档。默认情况下,javadoc 是不包括注解的。
      • 定义为 @Document 注解,必须设置 @Retention 注解的值为 RetentionPolicy.RUNTIME。
    • @Inherit 注解:
      • 定义该注解具有继承性。如果某个类使用被 @Inherit 注解修饰的注解,则该子类将自动具有该注解。
  • JDK 8 提供了新的注解,@Repeatable 注解,可重复注解,值是 Class<? extends Annotation> 类型。

  • 示例:

package com.github.demo11;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-10-07 17:47
 */
public enum Color {
    RED, GREEN, BLUE
}
package com.github.demo11;

import java.lang.annotation.*;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-10-07 17:46
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
public @interface MyAnnotation {
    // 基本数据类型
    int num();

    // String类型
    String value();

    // 枚举类型
    Color color();

    // Class类型
    Class clazz();

    // 注解类型
    Override override();

    // 以上类型的数组类型
    String[] values();
}
package com.github.demo11;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-10-07 18:03
 */
public class Person {
    @MyAnnotation(num = 0, value = "你好,许大仙", color = Color.RED, clazz = Person.class, override = @Override,
        values = {"aa", "bb"})
    private String name;
}

2.4.3 反射获取注解信息

  • 注解解析的目的:
    • ① 获取类、成员变量、成员方法、方法参数、构造函数等上面的注解对象。
    • ② 获取注解对象的属性。
    • ③ 判断某个类、成员变量、成员方法等上面是否有某个注解。
  • JDK 5 在 java.lang.reflect 包下新增了 AnnotatedElement 接口,该接口代表程序中可以接受注解的程序元素。
  • 当一个注解被定义为运行时注解后,该注解才是运行时可见,当 class 文件被加载时,保存在 class 文件中的注解才会被虚拟机读取。
  • Class 、Method 、Package 、Field 、Constructor 、Package 、Parameter 等实现了 AnnotatedElement 接口。

AnnotatedElement接口的类图.png

  • 程序员可以调用 AnnotatedElement 对象的方法获取注解信息。
  • 判断指定的注解是否存在:
default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass){}
  • 获取指定类型的注解,如果没有,返回 null :
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
  • 获取自己的所有注解:
Annotation[] getDeclaredAnnotations();
  • 获取所有的注解,包括从父类继承下来的注解:
Annotation[] getAnnotations();
  • 根据指定的类型获取所有的注解,包括从父类继承下来的注解:
default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {}
  • 获取指定类型的注解,如果没有,返回 null :
default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {}
  • 根据指定的类型获取自己的所有注解:
default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) {}
  • 示例:
package com.github.demo12;

import java.lang.annotation.*;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-10-07 20:20
 */
@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {}
package com.github.demo12;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-10-07 20:21
 */
@MyAnnotation
public class Demo {

    @MyAnnotation
    public void run01() {

    }

    public void run02() {

    }
}
package com.github.demo12;

import java.lang.reflect.Method;

/**
 * @author 许大仙
 * @version 1.0
 * @since 2021-10-07 20:21
 */
public class Test {
    public static void main(String[] args) throws NoSuchMethodException {
        Class<?> clazz = Demo.class;

        boolean annotationPresent = clazz.isAnnotationPresent(MyAnnotation.class);

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

        Method run01 = clazz.getDeclaredMethod("run01");

        boolean annotationPresent1 = run01.isAnnotationPresent(MyAnnotation.class);
        System.out.println("annotationPresent1 = " + annotationPresent1);
    }
}