Java-Day-14( 枚举 + 注解 + 自设头文件 )

发布时间 2023-04-24 10:18:22作者: 朱呀朱~

Java-Day-14

枚举

( enumeration, enum )

  • 若是创建春夏秋冬四季的信息,如果按传统方法创建,无法固定信息,可以随时调改,所以要用枚举,做到只读且不能改

  • 枚举

    • 一组常量的集合 —— 属于一种特殊的类,里面只包含一组有限的特定的对象
  • 实现方式

    • 自定义类实现枚举

      • 构造器私有化

      • 本类内创建一组对象

      • 对外暴露对象 ( 通常为对象添加 public final static 修饰符 )

      • 可以提供 get 方法,但是不能提供 set

        public class test {
            public static void main(String[] args){
                System.out.println(Season.SPRING);
        //        如果Season里不加 toString 的话,就是输出对象地址
                System.out.println(Season.SUMMER);
                System.out.println(Season.AUTUMN);
                System.out.println(Season.WINTER);
            }
        }
        
        class Season{
            private String name;
            private String desc;
        
        //    定义了四个对象
            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. 构造器私有化,防止被直接new
        //    2. 去除set方法,以防被修改,使其只读
        //    3. 在Season内部,直接创建固定的对象,对象名全大写规范
        //    4. 底层优化,每个对象除了static外再加一个final修饰符
            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 ... 简化成 SPRING ("春", "暖"),要通过实参列表明白调用的是哪个构造器

      • 如果使用无参构造器创建枚举对象的话,则实参列表和小括号都可以选择省略

        如:SPRING("春", "暖"),BOY,GIRL

        其中 BOY,GIRL 就是调用了无参构造器 ( 无参存在的情况下 )

      • 当有多个枚举对象时,使用逗号隔开,最后有一个分号结尾

      • 枚举对象必须放在枚举类的行首

        public class test {
            public static void main(String[] args){
                System.out.println(Season1.SPRING);
                System.out.println(Season1.SUMMER);
                System.out.println(Season1.AUTUMN);
                System.out.println(Season1.WINTER);
            }
        }
        
        enum Season1{
        
        //    如果使用了enum来实现枚举类:
            SPRING("春", "暖"), SUMMER("夏", "热"), AUTUMN("秋", "凉"), WINTER("冬", "冷");
            private String name;
            private String desc;
        
        //    1. 关键字enum替代枚举类
        //    2. public static final Season SPRING = new Season("春","暖"); ——> SPRING("春", "暖");
        //       即简化变成 常量名(实参列表) 对应构造器
        //    3. 如果有多个常量(对象),用逗号间隔开就可
        //    4. 如果使用enum来实现枚举,就要求将定义常量对象写在前面开头位置
            private Season1(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 "Season1{" +
                        "name='" + name + '\'' +
                        ", desc='" + desc + '\'' +
                        '}';
            }
        }
        
  • Enum 常用方法

    • name

      • 输出常量的名称

        Season autumn = Season.AUTUMN;
        System.out.println(autumn.name());
        // 输出 AUTUMN
        
    • ordinal

      • 输出的是该枚举对象的次序 / 编号,从零开始

        System.out.println(autumn.ordinal());
        // 输出 3 (SPRING:1, SUMMER:2, WINTER:4)
        
    • values

      • 源码隐藏起来看不到,但是反编译的时候能看到

      • 返回的是一个数组:Season[],含有定义的所有枚举对象

        public class test {
            public static void main(String[] args){
                
                Season1[] values = Season1.values();
                for (Season1 season: values){  // 增强for循环
        //            values取到一个值就返回给了season,然后System,再取一值给season,重复操作直到取出所有值
                    System.out.println(season);
                }
            }
        }
        
    • valueOf

      • 将字符串转换成枚举对象

      • 要求字符串必须为已有的常量名,否则找不到就会报错

        Season1 spring1 = Season1.SPRING;
        Season1 spring2 = Season1.valueOf("SPRING");
        
        System.out.println(spring1 == spring2); // true
        
    • compareTo

      • 比较两个枚举常量,比较的就是编号,前减后

        Season1 spring1 = Season1.SPRING;
        Season1 autumn1 = Season1.AUTUMN;
        System.out.println("编号相减:2 - 0 =" + autumn1.compareTo(spring1));
        //  或者直接:
        System.out.println(Season1.AUTUMN.compareTo(Season1.SPRING));
        
  • enum 接口使用细节

    • 使用了 enum 关键字后,就不能再继承其它类了,因为 enum 会隐式继承 Enum,而 Java 是单继承机制

    • 但枚举类和普通类一样,还是可以实现接口的

      public class test {
          public static void main(String[] args){
              Music.CLASSMUSIC.playing();
          }
      }
      
      interface IPlay {
          public void playing();
      }
      enum Music implements IPlay {
      //      反编码可知为:public static final com.hspJava.Music CLASSMUSIC;
          CLASSMUSIC;
          @Override
          public void playing() {
              System.out.println("播放音乐");
          }
      }
      
  • 小练习

    • 得出以下输出什么

      enum Gender{
          BOY,GIRL;
      }
      
      Gender boy = Gender.BOY;
      Gender boy2 = Gender.BOY;
      System.out.println(boy); 
      // 本质就是调用了Gender的父类Enum的toString(),ctrl+B查看源码得知(Returns: the name of this enum constant)
      // 则此处输出就是 BOY
      System.out.println(boy2 == boy);
      // true,因为BOY是静态对象
      
      • 注意,查看源码是 Enum,而不是 enum
  • 使用 enum 关键字会默认继承 Enum 类查证

    • javap 反编译 .class 文件

      • maven 项目是:项目名 — target — classes — com — 自定义文件名 — .class文件

      • java 项目是:项目名 — out — production — ... — com — 自定义文件名 — .class文件

      • idea 在目录右键文件夹 Open In Explorer

      • 文件夹目录 cmd 打开输入命令 javap Season1.class

      • image-20230421163244409

      • 由此看出 extends 继承了 Enum,并且 SPRING 等四个季节皆为:public static final

注解

( Annotation )

  • 也被称为元数据 ( Metadata ),用于修饰解释包、类、方法、属性、构造器、局部变量等数据信息

  • 和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息

  • 在 JavaSE 中,注解的使用目的比较简单,例如标记过的功能,忽略警告等。在 Java EE 中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替 Java EE 旧版中所遗留的繁冗代码和 XML 配置等

  • 使用 Annotation 时要在其前面增加 @ 符合,并把该 Annotation 当成一个修饰符使用,用于修饰它支持的程序元素

  • @Override:限定某个方法,是重写父类方法,该注解只能用于方法

    • 如果写了 @Override 注解,编译器就会去检查该方法是否真的重写了父类 ( 或超类等 ) 的方法,如果的确重写了,则编译通过,如果没有构成重写,则编译错误

    • 就算去掉了 @Override 注解,而父类仍有重名方法,仍然构成重写

    • 源码解读

      @Target(ElementType.METHOD)
      @Retention(RetentionPolicy.SOURCE)
      public @interface Override {
      }
      
      • 见到了 @interface 就表示是一个注解类 ( interface 才是接口,和 @ 的两者不同 )
      • @Target(ElementType.METHOD) 表示只能用于方法上 ( method )
      • @Target 是修饰注解的注解,称之为元注解
  • @Deprecated:用于表示某个程序元素 ( 类、方法等 ) 已过时

    • 表示不推荐使用,而非不能使用

    • 给元素加上此注解后,被修饰的元素在代码中就会被画上删除线 ( 删除 )

    • 可以做版本升级过渡使用

    • 源码解读

      @Documented
      @Retention(RetentionPolicy.RUNTIME)
      @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
      public @interface Deprecated {
      }
      
      • 可见也是被 @interface 修饰了
      • value =
  • @SuppressWarnings:抑制编译器警告

    • 如,下图代码如果不加注释于 main 方法前,List、list、ArrayList 在 idea中就都是黄色的 ( 即 idea 右上角的黄色感叹号标志 )

      @SuppressWarnings({"all"})
      // {""}里写all的话就是把所有的黄色标记都除去
      public static void main(String[] args){
              List list = new ArrayList();
              list.add("aa");
              list.add("bb");
              list.add("cc");
      }
      
    • 其作用范围是和被放置的位置相关,如上述代码的抑制范围就是在 main 里

      • 如果想的话,可以把鼠标放在代码右侧是黄色横杠上会弹出提示,根据提示选择双引号里要写的内容,就可以在每一句前都加一个,也可以直接全放在类前
    • 源码解读

      @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
      @Retention(RetentionPolicy.SOURCE)
      public @interface SuppressWarnings {    String[] value();
      }
      
      • String[] value() :注解括号内要写入的格式,如 ({"rawtypes", "unchecked", "unused"})
  • JDK 的元 Annotation ( 元注解,了解 )

    • @Retention:指定注解的作用范围,三种 SOURCE, CLASS, RUNTIME ( 源码,类,运行时 )
      • 只能用于修饰一个 Annotation 定义,用于指定该 Annotation 可以保留多长时间,其内包含一个 RetentionPolicy 类型的成员变量
      • java 源码 (1) ——> class 文件 (2) ——> JVM加载运行 (3)
        • RetentionPolicy.SOURCE:编译器使用后,直接丢弃这种策略的注释 (1) —— 就表示只在编译器编译时生效,不会写入到 .class 文件,也不会在 runtime 运行时生效
        • RetentionPolicy.CLASS:编译器把注释记录在 class 文件中,当运行 Java 程序时,JVM 不会保留注解。这是默认值 (2)
        • RetentionPolicy.RUNTIME:编译器把注释记录在 class 文件中,当运行 Java 程序时,JVM 会保留注解,程序可以通过反射获取该注解 (3)
    • @Target:指定注解可以在哪些地方使用
      • 用于修饰一个 Annotation 定义,用于指定该 Annotation 能用于修饰哪些程序元素
    • @Documented:指定该注解是否会在 javadoc 体现
      • 用于指定被该元 Annotation 修饰的 Annotation 类将被 javadoc 工具提取成文档,即在生成文档时,可以看到该注解 ( 如:在帮助文档中,已过时方法仍会在其中看到记录 )
    • @Inherited:子类会继承父类注解
      • 被其修饰的 Annotation 将具有继承性,如果某个类使用了被此注解修饰的 Annotation,则其子类将自动具有该注解
  • 章节练习

    • 计算机接口具有 work 方法,功能为运算,有一个手机类Cellphone,定义方法 testWork 测试计算功能,调用计算接口的 work 方法;要求调用 CellPhone 对象的 testWork 方法,使用上匿名内部类

      public class test {
          public static void main(String[] args){
              Cellphone cellphone = new Cellphone();
      //        在new ICalulate()往后到传入的n1,n2具体数值为止,都属于匿名内部类
      //        运行类型:编译类型:ICalulate,运行类型:匿名内部类
              cellphone.testWork(new ICalulate() {
                  @Override
                  public double work(double n1, double n2) {
                      return n1 * n2;
                  }
              }, 2, 3);
      
      //        动态改变内部的计算方法
              cellphone.testWork(new ICalulate() {
                  @Override
                  public double work(double n1, double n2) {
                      return n1 + n2;
                  }
              }, 2, 3);
          }
      }
      
      interface ICalulate {
      //    计算方法内容的具体要求交给匿名内部类
          public double work(double n1, double n2);
      }
      
      class  Cellphone {
      //    当调用testWork方法时,直接传入一个实现了ICalulate接口的匿名内部类即可
      //    该匿名内部类可以灵活实现work,完成不同的计算任务
          public void testWork(ICalulate iCalulate, double n1, double n2) {
              double result = iCalulate.work(n1, n2); // 动态绑定
              System.out.println("计算结果为" + result);
          }
      }
      
    • 唐僧取经路上平常都骑马,只有在过河时才租船过河

      // 设交通工具为接口
      interface Vehicles {
          public void work();
      }
      // Horse.java
      class Horse implements Vehicles{
          @Override
          public void work() {
              System.out.println("骑马赶路");
          }
      }
      // Boat.java
      class Boat implements Vehicles{
          @Override
          public void work() {
              System.out.println("坐船行驶");
          }
      }
      
      // VehiclesPlant.java
      //    工厂工具类,用static修饰比较好,定位于工具,便于调用
      class VehiclesPlant {
      //    先完成再优化
      //    因为每次换马时都new浪费且不真实
      //    所以让马始终为同一匹,船不能搬运,所以船都是new的
          private static Horse horse = new Horse(); // 饿汉式,静态只创建此一次
          private VehiclesPlant() {} // 防创建则私有化,迎合饿汉式
          public static Horse getHorse(){
      //        return new Horse();
              return horse;
          }
          public static Boat getBoat(){
              return new Boat();
          }
      }
      
      // Person.java
      class Person {
          private String name;
      //    注意:此处的Vehicles已经是一个接口了,一种类型
      //    就不要再自以为的定义: private Sting Vehicles,否则无法和先前代码联系起来
      //    而是:
          private Vehicles vehicles;
          
      //    创建此人时,要分配交通工具
          public Person(String name, Vehicles vehicles) {
              this.name = name;
              this.vehicles = vehicles;
          }
      
      //    实例化Person对象,要求在一般情况下,用Horse作为交通工具,遇到河时,用船
      //    此处涉及一个编程思路,就算可以把具体要求封装成方法
          public void passRiver() {
      // 		  F1. 传统方式
      //        Boat boat = VehiclesPlant.getBoat();
      //        boat.work();
      //    在构建对象时,为了使传入的交通工具对象不浪费,在已有马的时候就无需再get,可以直接拿来用(main里默认分配的)
              
      // 		  F2. 工具为空时才get
      //        if (vehicles == null){
      //        get接收时不用Horse horse而是vehicles:向上转型,用接口而非对象 —— 接口的解耦特性
      //        使用了多态
      //            vehicles = VehiclesPlant.getBoat();
      //        }
      //        vehicles.work();
      //        如果是马的话,动用此方法仍不为null,使输出为horse而非boat,所以要用判断是否为某一类型的instanceof
              
      //        F3. 不是船,就获取船
              if (!(vehicles instanceof Boat)){
                  vehicles = VehiclesPlant.getBoat();
              }
              vehicles.work();
          }
      
          public void common() {
              if (!(vehicles instanceof Horse)){
                  vehicles = VehiclesPlant.getHorse();
              }
              vehicles.work();
          }
      }
      
      // main
      public class test {
          public static void main(String[] args){
              Person pt = new Person("唐僧", new Horse()); // 初始化给匹马
              pt.common(); 
              pt.passRiver(); // 过河
          }
      }
      
      // 这种编程方式的话,再增添交通工具的时候会很便利
      
    • 类与成员内部类

      • 有一个 Car 类,有属性 temperature,车内有 Air ( 空调 ) 类,有 flow 吹风功能
      • Air 监视车内的温度,如果温度超过 20 度则吹冷气,温度低于 0 度则吹暖气,在这之间就关空调
      • 实例化具有不同温度的 Car 对象,调用空调 flow 方法
      public class test {
          public static void main(String[] args){
              Car car = new Car(22);
              // get返回的对象的flow方法
              car.getAir().flow();
          }
      }
      
      class Car {
          private double temperature;
      
          public Car(double temperature) {
              this.temperature = temperature;
          }
      
          class Air {
              public void flow() {
                  if (temperature > 20) {
                      System.out.println("温度大于20,吹冷气");
                  } else if (temperature < 0) {
                      System.out.println("温度小于0,吹暖气");
                  } else {
                      System.out.println("温度正常,关闭空调");
                  }
              }
          }
          public Air getAir() {
              return new Air();
          }
      }
      
    • 枚举类

      • 创建一个 Color 枚举类
      • 有 RED,BLUE,BLACK,YELLOW,GREEN 五个枚举值 / 对象
      • Color 有三个属性 redValue,greenValue,blueValue
      • 创建构造方法,参数包含上三属性
      • 枚举值赋值:red — 255,0,0;blue — 0,0,255 ......
      • 定义接口,有方法 show,要求方法显示值
      • 枚举对象在 switch 语句中匹配使用
      //
      public class test {
          public static void main(String[] args){
              Color color = Color.YELLOW;
              color.show();
      //        演示枚举值switch使用
      //        在每个case后,直接写上在枚举类中定义了的枚举对象即可
              switch (color) {
                  case YELLOW:
                      System.out.println("匹配到黄色");
                      break;
                  case BLACK:
                      System.out.println("匹配到黑色");
                      break;
                  default:
                      System.out.println("没得匹配...");
              }
          }
      }
      
      //
      interface IMyInterface {
          public void show();
      }
      
      //
      enum Color implements IMyInterface{
          RED(255,0,0),BLUE(0,0,255),BLACK(0,0,0),
          YELLOW(255,255,0),GREEN(0,255,0);
          private int redValue;
          private int greenValue;
          private int blueValue;
      
          Color(int redValue, int greenValue, int blueValue) {
              this.redValue = redValue;
              this.greenValue = greenValue;
              this.blueValue = blueValue;
          }
      
          @Override
          public void show() {
              System.out.println("属性值为" + redValue + "," + greenValue + "," + blueValue);
          }
      }
      

拓展:idea 自设文件头

image-20230421104809960