枚举介绍
-
枚举类型都隐式继承了
java.lang.Enum
类,因此不能继承其他类,但可以实现接口;同时枚举类是final的,所以也不能被继承。 -
枚举类的构造方法是私有的(java运行时创建,外部不能进行实例化);
那么什么时候应该使用枚举呢?每当需要一组固定的常量的时候,如一周的天数、一年四季等。或者是在我们编译前就知道其包含的所有值的集合。Java 1.5的枚举能满足绝大部分程序员的要求的,它的简明,易用的特点是很突出的。
枚举产生之前
下面的方法定义十分繁琐,而且容易出错。例如我们定义的int数字出现重复,编译器也不会给出任何的警示。同时,这样的操作是实在太频繁了,最终Java 5中增加了枚举类型。
package com.demo.enumDemo;
public class Season {
public static final int SPRING = 0;
public static final int SUMMER = 0;
public static final int AUTUMN = 1;
public static final int WINTER = 2;
}
枚举
定义的枚举类,默认继承Enum抽象类
,好处是提供了一些函数:
// 返回枚举常量的序数,即它是第几个声明的常量(从 0 开始)
// 该方法获取的是枚举变量在枚举类中声明的顺序,下标从0开始,
// 如日期中的MONDAY在第一个位置,那么MONDAY的ordinal值就是0,
// 如果MONDAY的声明位置发生变化,那么ordinal方法获取到的值也随之变化,
// 注意在大多数情况下我们都不应该首先使用该方法,毕竟它总是变幻莫测的。
public final int ordinal()
// 使枚举常量之间可以比较大小。比较此枚举与指定对象定义时的顺序
public final int compareTo(E o)
// 返回该枚举变量所在的枚举类。
public final Class<E> getDeclaringClass()
// 提供了 values 方法,可以用来遍历所有的枚举常量。
// 提供了 valueOf 方法,可以将字符串转换为枚举常量。
// name()可以获得枚举值的名称
package com.demo.enumDemo;
public enum SeasonEnum {
SPRING,SUMMER,AUTUMN,WINTER;
}
package com.demo.enumDemo;
public class TestEnum {
public static void main(String[] args) {
//名字是:SPRING,对应的索引为:0
//名字是:SUMMER,对应的索引为:1
//名字是:AUTUMN,对应的索引为:2
//名字是:WINTER,对应的索引为:3
System.out.println("名字是:" + SeasonEnum.SPRING.name() + ",对应的索引为:" + SeasonEnum.SPRING.ordinal());
System.out.println("名字是:" + SeasonEnum.SUMMER.name() + ",对应的索引为:" + SeasonEnum.SUMMER.ordinal());
System.out.println("名字是:" + SeasonEnum.AUTUMN.name() + ",对应的索引为:" + SeasonEnum.AUTUMN.ordinal());
System.out.println("名字是:" + SeasonEnum.WINTER.name() + ",对应的索引为:" + SeasonEnum.WINTER.ordinal());
}
}
枚举值自定义构造函数
实际使用中,我们可能想给每个枚举值赋予更多的含义,例如,给每个季节一个中文说明和编码等。
因为最简单的枚举类型调用了默认的构造方法,如果我们要增加新的含义,则需要自己覆盖原来的构造方法。
package com.demo.enumDemo;
public enum SeasonEnum {
SPRING("春天",01),SUMMER("夏天",02),AUTUMN("秋天",03),WINTER("冬天",04);
private String name;
private Integer code;
SeasonEnum(String name, Integer code) {
this.name = name;
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
@Override
public String toString() {
return "SeasonEnum{" +
"name='" + name + '\'' +
", code=" + code +
'}';
}
}
枚举类中定义抽象方法
既然编译器最终将每个枚举值声明为枚举类的实例,那我们能在枚举类中声明抽象方法让枚举值去实现么?
听起来有些不可思议,其实也是可以的。我们在枚举类Season中声明了一个抽象方法sayHello()。然后在创建枚举值时,就必须实现该抽象方法。最终的代码如下:
package com.demo.enumDemo;
public enum SeasonEnum {
SPRING("春天",01){
void sayHello(){
System.out.println("hello 春天");
}
},
SUMMER("夏天",02){
@Override
void sayHello() {
System.out.println("hello 夏天");
}
},
AUTUMN("秋天",03){
@Override
void sayHello() {
System.out.println("hello 秋天");
}
},
WINTER("冬天",04){
@Override
void sayHello() {
System.out.println("hello 冬天");
}
};
private String name;
private Integer code;
SeasonEnum(String name, Integer code) {
this.name = name;
this.code = code;
}
// 抽象方法必须定义在下面,否则会报错,原因未知
abstract void sayHello();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
@Override
public String toString() {
return "SeasonEnum{" +
"name='" + name + '\'' +
", code=" + code +
'}';
}
}
枚举常用方法
枚举常见用法
向枚举中添加新方法
public enum Color {
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
// 普通方法
public static String getName(int index) {
for (Color c : Color.values()) {
if (c.getIndex() == index) {
return c.name;
}
}
return null;
}
// get set 方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}
实现接口
public interface Behaviour {
void print();
String getInfo();
}
public enum Color implements Behaviour{
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
//接口方法
@Override
public String getInfo() {
return this.name;
}
//接口方法
@Override
public void print() {
System.out.println(this.index+":"+this.name);
}
}
使用接口组织枚举
public interface Food {
enum Coffee implements Food{
BLACK_COFFEE,DECAF_COFFEE,LATTE,CAPPUCCINO
}
enum Dessert implements Food{
FRUIT, CAKE, GELATO
}
}
switch
enum Signal {
GREEN, YELLOW, RED
}
public class TrafficLight {
Signal color = Signal.RED;
public void change() {
switch (color) {
case RED:
color = Signal.GREEN;
break;
case YELLOW:
color = Signal.RED;
break;
case GREEN:
color = Signal.YELLOW;
break;
}
}
}
枚举和常量的区别
日常开发中,大家对这俩货的认识,其实都仅仅停留在“可穷尽状态集合”的层面。什么意思呢?就是当你发现某个字段存在有限的若干种状态时,你就下意识地选择使用常量类或者枚举。比如对于订单状态,你可以用枚举表示:
// 用枚举表示
package com.demo.enumDemo.EnumConstantCompare;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum OrderStatusEnum {
UNPAY(1,"待付款"),
PAYED(2,"代发货");
// 省略其他状态
private final Integer num;
private final String desc;
}
// 也可以用常量表示
package com.demo.enumDemo.EnumConstantCompare;
public class OrderStatusConstant {
public static int UNPAY = 1;
public static int PAYED = 2;
}
// 有些人可能更骚一点
package com.demo.enumDemo.EnumConstantCompare;
public final class OrderConstant {
// 订单状态
public static class Status{
public static int UNPAY = 1;
public static int PAYED = 2;
}
// 订单类型
public static class Type{
public static int TB = 1;
public static int JD = 1;
}
}
但实际上,枚举和常量不是一个维度的东西,枚举是对象,常量是字段。常量能做的,枚举都能做,枚举能做的常量不一定能做。
比如,枚举可以实现自定义的方法:
package com.demo.enumDemo.EnumConstantCompare;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.math.BigDecimal;
@Getter
@AllArgsConstructor
public enum MemberEnum {
GOLD_MEMBER(1,"黄金会员"){
@Override
protected BigDecimal calculateFinalPrice(BigDecimal originPrice) {
return originPrice.multiply(new BigDecimal("0.6"));
}
},
SILVER_MEMBER(2,"白银会员"){
@Override
protected BigDecimal calculateFinalPrice(BigDecimal originPrice) {
return originPrice.multiply(new BigDecimal("0.7"));
}
},
BRONZE_MEMBER(3,"青铜会员"){
@Override
protected BigDecimal calculateFinalPrice(BigDecimal originPrice) {
return originPrice.multiply(new BigDecimal("0.8"));
}
};
private final Integer num;
private final String name;
/**
* 定义抽象方法,留给子类实现
* @param originPrice
* @return
*/
protected abstract BigDecimal calculateFinalPrice(BigDecimal originPrice);
}
package com.demo.enumDemo.EnumConstantCompare;
import java.math.BigDecimal;
public class TestEnumConstant {
public static void main(String[] args) {
BigDecimal originPrice = new BigDecimal("100");
BigDecimal goldBigDecimal = MemberEnum.GOLD_MEMBER.calculateFinalPrice(originPrice);
BigDecimal silverBigDecimal = MemberEnum.SILVER_MEMBER.calculateFinalPrice(originPrice);
BigDecimal bronzeBigDecimal = MemberEnum.BRONZE_MEMBER.calculateFinalPrice(originPrice);
// 原价100,黄金会员打折后需要支付:60.0
// 原价100,白银会员打折后需要支付:70.0
// 原价100,青铜会员打折后需要支付:80.0
System.out.println("原价" + originPrice + ",黄金会员打折后需要支付:" + goldBigDecimal);
System.out.println("原价" + originPrice + ",白银会员打折后需要支付:" + silverBigDecimal);
System.out.println("原价" + originPrice + ",青铜会员打折后需要支付:" + bronzeBigDecimal);
}
}
// 如果用常量表示,就是下面这样
package com.demo.enumDemo.EnumConstantCompare;
public class Constants {
// 黄金会员
public static final Integer GOLD_MEMBER = 1;
// 白银会员
public static final Integer SILVER_MEMBER = 2;
// 青铜会员
public static final Integer BRONZE_MEMBER = 3;
}
package com.demo.enumDemo.EnumConstantCompare;
import java.math.BigDecimal;
public class MemberDemo {
public static void main(String[] args) {
BigDecimal bigDecimal = calculateFinalPrice(new BigDecimal("100"), Constants.GOLD_MEMBER);
System.out.println(bigDecimal);
}
public static BigDecimal calculateFinalPrice(BigDecimal originPrice,Integer type){
if (Constants.GOLD_MEMBER.equals(type)){
return originPrice.multiply(new BigDecimal("0.6"));
}else if (Constants.GOLD_MEMBER.equals(type)){
return originPrice.multiply(new BigDecimal("0.7"));
}else if (Constants.GOLD_MEMBER.equals(type)){
return originPrice.multiply(new BigDecimal("0.8"));
}else {
return originPrice;
}
}
}
但上面计算价格的逻辑不能复用,需要计算会员价格时,就要拷贝一份上面的代码。
当然,你可能会抽取到工具类中,但既然如此为什么不直接用枚举呢?枚举状态和计算方法放在一块,统一性更强、更合理。
这里并不是为了突出枚举优点而一味贬低常量类,只是想和大家说,枚举毕竟是对象,对象能玩的它都能玩,而常量类只能提供一个个字段变量,是很单薄的。
入参约束
对于常量,其实很难约束调用者按你的意图传参。比如上面计算会员价格:
你并不能约束我传 666,尽管666不代表任何一种会员类型
package com.demo.enumDemo.EnumConstantCompare;
import java.math.BigDecimal;
public class MemberDemo {
public static void main(String[] args) {
// 我可能压根不知道你定义了Constants.GOLD_MEMBER,随便传666进去
BigDecimal bigDecimal = calculateFinalPrice(new BigDecimal("100"), 666);
System.out.println(bigDecimal);
}
public static BigDecimal calculateFinalPrice(BigDecimal originPrice,Integer type){
if (Constants.GOLD_MEMBER.equals(type)){
return originPrice.multiply(new BigDecimal("0.6"));
}else if (Constants.GOLD_MEMBER.equals(type)){
return originPrice.multiply(new BigDecimal("0.7"));
}else if (Constants.GOLD_MEMBER.equals(type)){
return originPrice.multiply(new BigDecimal("0.8"));
}else {
return originPrice;
}
}
}
但枚举不仅可以约束入参,还能很好的提示调用者:
下面这样写只是一个demo,实际肯定不这么写,
这里想表达的意思是:枚举就可以像类一样,可以限制入参的类型,像调用普通方法那样。
搭配switch,结构更清晰
这个理由其实是网上找的,switch和if else,我觉得半斤八两。
package com.demo.enumDemo.EnumConstantCompare;
import lombok.AllArgsConstructor;
import lombok.Getter;
public class EnumDemo {
public static void main(String[] args) {
// 这是苹果
guessFruit(Fruit.APPLE);
}
public static void guessFruit(Fruit fruit){
switch (fruit){
case APPLE:
System.out.println("这是苹果");
break;
case BANANA:
System.out.println("这是香蕉");
break;
case ORANGE:
System.out.println("这是橘子");
break;
}
}
}
@AllArgsConstructor
@Getter
enum Fruit{
APPLE(20200325,"中国山东",6.6),
BANANA(20210325,"泰国曼谷",7.7),
ORANGE(20220325,"日本北海道",8.8);
private final Integer num;
private final String origin;
private final Double price;
}
什么时候用枚举、什么时候用常量?
最后说一下个人平时对枚举和常量类的取舍:
- 错误码的定义用枚举
- 字段状态用枚举或常量类都可以,个人更倾向于使用枚举,可玩性会比常量类高
- 一些全局变量的定义可以使用常量类,比如USER_INFO_CACHE_KEY、SESSION_KEY
部分内容转载自:
https://zhuanlan.zhihu.com/p/64604609
https://www.jianshu.com/p/174467006572
https://www.zhihu.com/question/33659578
退税可以放弃,但补税不能放弃。
中国新闻网微博