7.1 枚举(Enumeration)
7.1.1 定义
- 枚举是一种常量的集合,可以理解为枚举属于一种特殊的类,里面只包含一组有限的特定的对象。
7.1.2 自定义类实现枚举
- 构造器私有化。
- 去掉setXX方法,防止属性被修改。
- 在类内部,直接创建固定的对象。
- 对枚举对象/属性可以使用static + final修饰,实现底层优化。
-
public class Enumeration02 { public static void main(String[] args) { System.out.println(Season.AUTUMN); // Season{name='秋天', desc='凉爽'} System.out.println(Season.SPRING); // Season{name='春天', desc='温暖'} } } //演示自定义枚举实现 class Season {//类 private String name; private String desc;//描述 //定义了四个对象, 固定. public static final Season SPRING = new Season("春天", "温暖"); public static final Season WINTER = new Season("冬天", "寒冷"); public static final Season AUTUMN = new Season("秋天", "凉爽"); public static final Season SUMMER = new Season("夏天", "炎热"); //1. 将构造器私有化,目的防止 直接 new //2. 去掉setXxx方法, 防止属性被修改 //3. 在Season 内部,直接创建固定的对象 //4. 优化,可以加入 final 修饰符 private Season() {} 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 + '\'' + '}'; } }
7.1.3 enum关键字实现枚举
-
使用关键字enum替代class。
-
构造器私有化。
-
去掉setXX方法,防止属性被修改。
-
使用简化版的方法来创建固定的对象。
-
public class Enumeration03 { public static void main(String[] args) { System.out.println(Season2.AUTUMN); // Season{name='秋天', desc='凉爽'} System.out.println(Season2.SUMMER); // Season{name='夏天', desc='炎热'} } } //演示使用enum关键字来实现枚举类 enum Season2 {//类 //1. 使用关键字 enum 替代 class //2. public static final Season SPRING = new Season("春天", "温暖") 直接使用 // SPRING("春天", "温暖") 解读 常量名(实参列表) //3. 如果有多个常量(对象), 使用 ,号间隔即可 //4. 如果使用enum 来实现枚举,要求将定义常量对象,写在前面 //5. 如果我们使用的是无参构造器,创建常量对象,则可以省略 () SPRING("春天", "温暖"), SUMMER("夏天", "炎热"), AUTUMN("秋天", "凉爽"), WINTER("冬天", "寒冷") /*, What()*/; private String name; private String desc;//描述 private Season2() {//无参构造器 } private Season2(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 + '\'' + '}'; } }
7.1.3.1 注意事项
- 当我们使用 enum 关键字开发一个枚举类时,默认会继承 Enum 类, 而且是一个 final 类。
- 传统的 public static final Season2 SPRING = new Season2(“春天”, “温暖”); 简化成 SPRING(“春天”, “温暖”), 这里必须知道,它调用的是哪个构造器。
- 如果使用无参构造器 创建 枚举对象,则实参列表和小括号都可以省略。
- 当有多个枚举对象时,使用,间隔,最后有一个分号结尾。
- 枚举对象必须放在枚举类的行首。
7.1.3.2 常用方法
-
Season2 spring = Season2.SPRING; Season2 summer = Season2.SUMMER; Season2 autumn = Season2.AUTUMN; Season2 winter = Season2.WINTER;
-
toString:Enum 类已经重写过了,返回的是当前对象名,子类可以重写该方法,用于返回对象的属性信息。
System.out.println(spring.toString()); // Season{name='春天', desc='温暖'} System.out.println(summer.toString()); // Season{name='夏天', desc='炎热'}
-
name:返回当前对象名(常量名),子类中不能重写。
System.out.println(spring.name()); // SPRING System.out.println(summer.name()); // SUMMER
-
ordinal:返回当前对象的位置号,默认从 0 开始。
System.out.println(autumn.ordinal()); // 2 System.out.println(winter.ordinal()); // 3
-
values:返回当前枚举类中所有的常量。
Season2[] values = Season2.values(); for (Season2 value : values) { System.out.println(value); } /* Season{name='春天', desc='温暖'} Season{name='夏天', desc='炎热'} Season{name='秋天', desc='凉爽'} Season{name='冬天', desc='寒冷'} */
-
valueOf:将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常!
Season2 autumn1 = Season2.valueOf("AUTUMN"); System.out.println("autumn1=" + autumn1); // autumn1=Season{name='秋天', desc='凉爽'} System.out.println(autumn == autumn1); // true Season2 autumn2 = Season2.valueOf("AUTUMNx"); // 报错
-
compareTo:比较两个枚举常量,比较的就是编号!
System.out.println(spring.compareTo(summer)); // -1 System.out.println(summer.compareTo(spring)); // 1
7.1.3.3 使用细节
-
使用 enum 关键字后,就不能再继承其它类了,因为 enum 会隐式继承 Enum,而 Java 是单继承机制。
-
枚举类和普通类一样,可以实现接口,如下形式。
enum 类名 implements 接口 1,接口 2{}
7.2 注解(Annotation)
7.2.1 定义
-
定义:从Java 5版本之后可以在源代码中嵌入一些补充信息,这种补充信息称为注解(Annotation),是 Java 平台中非常重要的一部分。
-
作用范围:注解(Annotation)也被称为元数据(Metadata),用于修饰解释
包
、类
、方法
、属性
、构造器
、局部变量
等数据信息。 -
类型:无论是哪一种注解,本质上都一种数据类型,是一种接口类型。到 Java 8 为止 Java SE 提供了 11 个内置注解。其中有 5 个是基本注解,它们来自于 java.lang 包。有 6 个是元注解,它们来自于 java.lang.annotation 包,自定义注解会用到元注解。
-
注解和注释的区别:和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息。
7.2.2 作用
- 在
JavaSE
中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE
中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替Java EE
旧版中所遗留的繁冗代码和 XML 配置等。 - 生成帮助文档,这是最常见的,也是 Java 最早提供的注解。常用的有
@see
、@param
和@return
等。 - 在编译时进行格式检查,如把 @Override 注解放在方法前,如果这个方法并不是重写了父类方法,则编译时就能检查出。
7.2.3 基本注解
7.2.3.1 Override(重写)
-
定义:@Override表示指定重写父类的方法(从编译层面验证),如果父类没有定义该方法,则会报错。
-
如果不写@Override注解,而父类仍有该方法,依然构成重写。
-
限定某个方法,是重写父类方法,该注解只能用于方法,不能修饰其他类、包、属性等。
public class Person { private String name = ""; private int age; ... @Override public String toString() { //toString() return "Person [name=" + name + ", age=" + age + "]"; } }
7.2.3.2 Deprecated(弃用)
-
定义:用于表示某个程序元素(包、类、方法、字段、参数等)已过时,当其他程序使用已过时的元素时,编译器将会给出警告。
-
@Deprecated的作用可以做到新旧版本的兼容和过渡,Java 9 为 @Deprecated 注解增加了以下两个属性:
- forRemoval:该 boolean 类型的属性指定该 API 在将来是否会被删除。
- since:该 String 类型的属性指定该 API 从哪个版本被标记为过时。
@Deprecated(since = "9", forRemoval = true) public class Person { @Deprecated protected String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Deprecated public void setNameAndAge(String name, int age) { this.name = name; this.age = age; } }
7.2.3.3 SuppressWarnings(抑制警告)
-
定义:注解指示被该注解修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告,且会一直作用于该程序元素的所有子元素。
-
注解的使用有以下三种:
- 抑制单类型的警告:@SuppressWarnings(“unchecked”)
- 抑制多类型的警告:@SuppressWarnings(“unchecked”,“rawtypes”)
- 抑制所有类型的警告:@SuppressWarnings(“unchecked”)
-
在{“}中,可以写入你希望抑制的警告信息。
@SuppressWarnings({"rawtypes", "unchecked", "unused"}) public class HelloWorld { @SuppressWarnings({ "deprecation" }) public static void main(String[] args) { Person p = new Person(); p.setNameAndAge("C语言中文网", 20); p.name = "Java教程"; } }
-
抑制警告的关键字如下表所示:
关键字 用途 all 抑制所有警告 boxing 抑制装箱、拆箱操作时候的警告 cast 抑制映射相关的警告 dep-ann 抑制启用注释的警告 deprecation 抑制过期方法警告 fallthrough 抑制在 switch 中缺失 breaks 的警告 finally 抑制 finally 模块没有返回的警告 hiding 抑制相对于隐藏变量的局部变量的警告 incomplete-switch 忽略不完整的 switch 语句 nls 忽略非 nls 格式的字符 null 忽略对 null 的操作 rawtypes 使用 generics 时忽略没有指定相应的类型 restriction 抑制禁止使用劝阻或禁止引用的警告 serial 忽略在 serializable 类中没有声明 serialVersionUID 变量 static-access 抑制不正确的静态访问方式警告 synthetic-access 抑制子类没有按最优方法访问内部类的警告 unchecked 抑制没有进行类型检查操作的警告 unqualified-field-access 抑制没有权限访问的域的警告 unused 抑制没被使用过的代码的警告
7.2.3.4 FunctionInterface(函数式接口)
-
学习 Lambda 表达式时,我们提到如果接口中只有一个抽象方法(可以包含多个默认方法或多个 static 方法),那么该接口就是函数式接口。@FunctionalInterface 就是用来指定某个接口必须是函数式接口,所以 @FunInterface 只能修饰接口,不能修饰其它程序元素。
-
@FunctionalInterface public interface FunInterface { static void print() { System.out.println("你好"); } default void show() { System.out.println("你干嘛啊哎哟"); } void test(); // 只定义一个抽象方法 }
7.2.4 元注解
- 元注解是负责对其它注解进行说明的注解,自定义注解时可以使用元注解。Java 5 定义了 4 个注解,分别是
@Documented
、@Target
、@Retention
和@Inherited
。Java 8 又增加了@Repeatable
和@Native
两个注解。这些注解都可以在 java.lang.annotation 包中找到。下面主要介绍每个元注解的作用及使用。
7.2.4.1 Documented
-
@Documented 是一个标记注解,没有成员变量。用 @Documented 注解修饰的注解类会被 JavaDoc 工具提取成文档。默认情况下,JavaDoc 是不包括注解的,但如果声明注解时指定了 @Documented,就会被 JavaDoc 之类的工具处理,所以注解类型信息就会被包括在生成的帮助文档中。
-
案例:
@Documented @Target({ ElementType.TYPE, ElementType.METHOD }) public @interface MyDocumented { public String value() default "这是@Documented注解"; }
@MyDocumented public class DocumentedTest { /** * 测试document */ @MyDocumented public String Test() { return "C语言中文网Java教程"; } }
打开 Java 文件所在的目录,分别输入如下两条命令行:运行成功后,打开生成的帮助文档,可以看到在类和方法上都保留了MyDocument 的注解信息。
javac MyDocumented.java DocumentedTest.java javadoc -d doc MyDocumented.java DocumentedTest.java
7.2.4.2 Target
-
@Target 注解用来指定一个注解的使用范围,即被 @Target 修饰的注解可以用在什么地方。@Target 注解有一个成员变量(value)用来设置适用目标,value 是 java.lang.annotation.ElementType 枚举类型的数组,下表为 ElementType 常用的枚举常量。
名称 说明 CONSTRUCTOR 用于构造方法 FIELD 用于成员变量(包括枚举常量) LOCAL_VARIABLE 用于局部变量 METHOD 用于方法 PACKAGE 用于包 PARAMETER 用于类型参数(JDK 1.8新增) TYPE 用于类、接口(包括注解类型)或 enum 声明 -
比如下述代码中的自定义注解的作用范围为方法,当在变量上添加该注解时会出现编译性错误。
@Target({ ElementType.METHOD }) public @interface MyTarget { } class Test { @MyTarget String name; }
7.2.4.3 Retention
-
@Retention 用于描述注解的生命周期,也就是该注解被保留的时间长短。@Retention 注解中的成员变量(value)用来设置保留策略,value 是 java.lang.annotation.RetentionPolicy 枚举类型,RetentionPolicy 有 3 个枚举常量,如下所示。
-
SOURCE:在源文件中有效(即源文件保留)
-
CLASS:在 class 文件中有效(即 class 保留)
-
RUNTIME:在运行时有效(即运行时保留)
生命周期大小排序为 SOURCE < CLASS < RUNTIME,前者能使用的地方后者一定也能使用。如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS 注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。
-
7.2.4.4 Inherited
-
@Inherited 是一个标记注解,用来指定该注解可以被继承。使用 @Inherited 注解的 Class 类,表示这个注解可以被用于该 Class 类的子类。就是说如果某个类使用了被 @Inherited 修饰的注解,则其子类将自动具有该注解。
-
案例:创建一个自定义注解,代码如下所示:
@Target({ ElementType.TYPE }) @Inherited @Retention(RetentionPolicy.RUNTIME) public @interface MyInherited { }
测试代码:
@MyInherited public class TestA { public static void main(String[] args) { System.out.println(TestA.class.getAnnotation(MyInherited.class)); System.out.println(TestB.class.getAnnotation(MyInherited.class)); System.out.println(TestC.class.getAnnotation(MyInherited.class)); } } class TestB extends TestA { } class TestC extends TestB { }
运行结果:
@MyInherited() @MyInherited() @MyInherited()
7.2.4.5 Repeatable
-
@Repeatable 注解是 Java 8 新增加的,它允许在相同的程序元素中重复注解,在需要对同一种注解多次使用时,往往需要借助 @Repeatable 注解。Java 8 版本以前,同一个程序元素前最多只能有一个相同类型的注解,如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”。
-
Java 8之前的做法:
public @interface Roles { Role[] roles(); } public @interface Roles { Role[] value(); } public class RoleTest { @Roles(roles = {@Role(roleName = "role1"), @Role(roleName = "role2")}) public String doString(){ return "这是C语言中国网Java教程"; } }
-
Java 8 之后增加了重复注解,使用方式如下:
public @interface Roles { Role[] value(); } @Repeatable(Roles.class) public @interface Role { String roleName(); } public class RoleTest { @Role(roleName = "role1") @Role(roleName = "role2") public String doString(){ return "这是C语言中文网Java教程"; } }
不同的地方是,创建重复注解 Role 时加上了 @Repeatable 注解,指向存储注解 Roles,这样在使用时就可以直接重复使用 Role 注解。从上面例子看出,使用 @Repeatable 注解更符合常规思维,可读性强一点。
两种方法获得的效果相同。重复注解只是一种简化写法,这种简化写法是一种假象,多个重复注解其实会被作为“容器”注解的 value 成员的数组元素处理。
7.2.4.6 Native
- 使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。对于 @Native 注解不常使用,了解即可。
7.2.5 自定义注解
-
当基本注解和元注解,如果这两种注解不能满足你的需求,可以自定义注解。下面介绍如何自定义注解。
-
声明自定义注解使用 @interface 关键字(interface 关键字前加 @ 符号)实现。定义注解与定义接口非常像,如下代码可定义一个简单形式的注解类型。此外注解中的成员变量也可使用 default 关键字来定义默认值。
public @interface Test { String name() default "张三"; int age(); }
-
根据注解是否包含成员变量,可以分为如下两类。
- 标记注解:没有定义成员变量的注解类型被称为标记注解。这种注解仅利用自身的存在与否来提供信息,如前面介绍的 @Override、@Test 等都是标记注解。
- 元数据注解:包含成员变量的注解,因为它们可以接受更多的元数据,所以也被称为元数据注解。
7.3 多态练习
-
public class Homework06 { public static void main(String[] args) { person tang = new person("唐僧", new Horse()); tang.common(); tang.PassRiver(); tang.fly(); } } interface Vehicles { public void work(); } class Horse implements Vehicles { @Override public void work() { System.out.println("一般情况下,使用马儿前进"); } } class Boat implements Vehicles { @Override public void work() { System.out.println("过河的时候,使用小船前进"); } } class Plane implements Vehicles { @Override public void work() { System.out.println("过火焰山的时候,使用飞机前进"); } } class VehicleFactory { // 创建交通工具工厂类,有两个方法分别获得交通工具Horse和Boat private static Horse horse = new Horse(); // 创建始终为一的白龙马 public static Horse getHorse() { // return new Horse(); return horse; } public static Boat getBoat() { return new Boat(); } public static Plane getPlane() { return new Plane(); } } class person { private String name; private Vehicles vehicles; public person() { } public person(String name, Vehicles vehicles) { this.name = name; this.vehicles = vehicles; } // 过河 public void PassRiver() { if (!(vehicles instanceof Boat)) { // 需要船时 vehicles = VehicleFactory.getBoat(); // // 从工厂获得船 } // Boat boat = VehicleFactory.getBoat(); // 从工厂获得船 vehicles.work(); // 用船过河 } // 一般情况 public void common() { if (!(vehicles instanceof Horse)) { // 需要白龙马时 vehicles = VehicleFactory.getHorse(); // 从工厂获得马 } // Horse horse = VehicleFactory.getHorse(); // 从工厂获得马 vehicles.work(); // 用马走路 } // 一般情况 public void fly() { if (!(vehicles instanceof Plane)) { // 需要飞机时 vehicles = VehicleFactory.getPlane(); // 从工厂获得飞机 } // Plane plane = VehicleFactory.getPlane(); // 从工厂获得马 vehicles.work(); // 用飞机飞 } }
7.4 枚举练习
-
public class Homework08 { public static void main(String[] args) { Color blue = Color.BLUE; blue.show(); switch (blue) { case RED: System.out.println("成功匹配红色"); break; case BLUE: System.out.println("成功匹配蓝色"); break; default: System.out.println("匹配颜色失败"); } } } /* 属性值为0,0,255 成功匹配蓝色 */ interface ColorInterface { void show(); } enum Color implements ColorInterface { 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() { } 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); } }
部分内容参考: