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
-
由此看出 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,则其子类将自动具有该注解
- @Retention:指定注解的作用范围,三种 SOURCE, CLASS, RUNTIME ( 源码,类,运行时 )
-
章节练习
-
计算机接口具有 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); } }
-