栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JavaSE-Adventure(VI) Enum 枚举

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JavaSE-Adventure(VI) Enum 枚举

JavaSE-Adventure(VI) Enum 枚举

CONTENTS
  • JavaSE-Adventure(VI) Enum 枚举
    • 如何使用枚举
      • 定义枚举
      • 枚举为什么特殊(枚举类型的本质)
      • Enum 类
    • 枚举的特性, 优点
      • 枚举可以添加成员变量, 方法, 构造器
      • 枚举可以实现接口
      • 枚举不可以继承
    • EnumSet和EnumMap
      • EnumSet
      • EnumMap
    • 枚举的应用
      • switch 状态机
      • 组织枚举
      • 策略枚举

Java 5.0引入了枚举,枚举限制变量只能是预先设定好的值。

当类的对象时有限且固定的,如季节类,它只有春夏秋冬4个对象这种实例有限且固定的类,在 Java 中可以定义为枚举类。

public enum Season {
    SPRING, SUMMER, AUTUMN, WINTER
}

枚举是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性、安全性以及便捷性。

如何使用枚举 定义枚举
访问修辞符 enum 枚举名 {
    枚举成员,
    枚举成员,
    ...
};
public enum Color { 
    RED, GREEN, BLUE
}

如果枚举不添加任何方法,枚举值默认为从0开始的有序数值。以 Color 枚举类型举例,它的枚举常量依次为 RED:0,GREEN:1,BLUE:2。

枚举为什么特殊(枚举类型的本质)

尽管 enum看起来像是一种新的数据类型,事实上,enum是一种受限制的类,并且具有自己的方法。

且创建enum时,编译器会为你生成一个相关的类,这个类继承自 java.lang.Enum。

public abstract class Enum>
        implements Comparable, Serializable { 
    ... 
}

接下来更深入一点, 假如我们定义一个星期类:

//定义枚举类型
enum Day {
    MONDAY, 
    TUESDAY, 
    WEDNESDAY,
    THURSDAY, 
    FRIDAY, 
    SATURDAY, 
    SUNDAY
}

通过编译Day.java 后生成的.class 文件并反编译后, 我们查看反编译后的代码:

final class Day extends Enum
{
    //编译器为我们添加的静态的values()方法
    public static Day[] values()
    {
        return (Day[])$VALUES.clone();
    }
    //编译器为我们添加的静态的valueOf()方法,注意间接调用了Enum也类的valueOf方法
    public static Day valueOf(String s)
    {
        return (Day)Enum.valueOf(com/zejian/enumdemo/Day, s);
    }
    //私有构造函数
    private Day(String s, int i)
    {
        super(s, i);
    }
     //前面定义的7种枚举实例
    public static final Day MONDAY;
    public static final Day TUESDAY;
    public static final Day WEDNESDAY;
    public static final Day THURSDAY;
    public static final Day FRIDAY;
    public static final Day SATURDAY;
    public static final Day SUNDAY;
    private static final Day $VALUES[];

    static 
    {    
        //实例化枚举实例
        MONDAY = new Day("MONDAY", 0);
        TUESDAY = new Day("TUESDAY", 1);
        WEDNESDAY = new Day("WEDNESDAY", 2);
        THURSDAY = new Day("THURSDAY", 3);
        FRIDAY = new Day("FRIDAY", 4);
        SATURDAY = new Day("SATURDAY", 5);
        SUNDAY = new Day("SUNDAY", 6);
        $VALUES = (new Day[] {
            MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
        });
    }
}

从反编译的代码可以看出:

  1. 编译器生成了一个Day类
  2. 该类由final修饰,将无法被继承
  3. 该类继承自java.lang.Enum
  4. 该类构造器是私有的, 无法通过new 关键字进行实例化
  5. 生成了7个Day 类型的实例对象, 分别对应枚举中定义的7个日期
  6. 编译器还生成了两个静态方法,分别是values()和 valueOf()

values() 方法返回一个包含枚举中所定义常量的数组。

valueOf(String) 方法根据传入字符串的值, 和枚举中所定义的常量名进行匹配, 并返回匹配到的枚举值。

Enum自带的静态方法valueOf()则需要传递两个方法,从前面反编译后的代码可以看出,编译器生成的valueOf方法最终还是调用了Enum类的valueOf方法

Enum 类
public abstract class Enum>
        implements Constable, Comparable, Serializable {
  private final String name;
  // 返回此枚举对象的名称 (声明时定义的)
  public final String name() {
      return name;
  }

  private final int ordinal;
  // 返回枚举常量的序数 (声明时的次序)
  public final int ordinal() {
      return ordinal;
  }

  protected Enum(String name, int ordinal) {
      this.name = name;
      this.ordinal = ordinal;
  }

  public String toString() {
      return name;
  }

  public final boolean equals(Object other) {
      return this==other;
  }

  public final int hashCode() {
      return super.hashCode();
  }
  // 比较此枚举与指定对象的顺序
  public final int compareTo(E o) {
      Enum other = (Enum)o;
      Enum self = this;
      if (self.getClass() != other.getClass() && // optimization
          self.getDeclaringClass() != other.getDeclaringClass())
          throw new ClassCastException();
      return self.ordinal - other.ordinal;
  }
}
枚举的特性, 优点
  1. 提供编译期的类型检查, 枚举类型预定义的类型, 避免了错误赋值导致的运行时报错

  2. 枚举类型可以直接通过 == 进行比较, 因为枚举值都是默认使用 static, final 进行修饰, 内存中仅存在一个实例

  3. switch 状态机, 使代码可读性更高, 效率更高

  4. 枚举类型可以拥有构造器(可拥有多个), 成员变量, 方法, 实现接口

  5. 枚举类型的构造器永远是私有的, 无法通过new 关键字进行实例化, 只有在定义枚举类时就进行初始化

除了不能继承,基本上可以将 enum看做一个常规的类。

枚举可以添加成员变量, 方法, 构造器

枚举值默认为从0开始的有序数值。那么问题来了:如何为枚举显示的赋值。

枚举可以添加普通方法、静态方法、抽象方法、构造方法

Java 虽然不能直接为实例赋值,但是它有更优秀的解决方案:为 enum 添加方法来间接实现显示赋值。

创建 enum时,可以为其添加多种方法,甚至可以为其添加构造方法。

注意一个细节:如果要为enum定义方法,那么必须在enum的最后一个实例尾部添加一个分号。此外,在enum中,必须先定义实例,不能将字段或方法定义在实例前面。否则,编译器会报错。

public enum Planet {
    MERCURY (3.303e+23, 2.4397e6),
    VENUS   (4.869e+24, 6.0518e6),
    EARTH   (5.976e+24, 6.37814e6),
    MARS    (6.421e+23, 3.3972e6),
    JUPITER (1.9e+27,   7.1492e7),
    SATURN  (5.688e+26, 6.0268e7),
    URANUS  (8.686e+25, 2.5559e7),
    
    NEPTUNE (1.024e+26, 2.4746e7);

    // in kilograms
    private final double mass;   

    // in meters
    private final double radius; 

    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }

    private double mass() { return mass; }

    private double radius() { return radius; }

    // universal gravitational constant  (m3 kg-1 s-2)
    public static final double G = 6.67300E-11;

    double surfaceGravity() {
        return G * mass / (radius * radius);
    }

    double surfaceWeight(double otherMass) {
        return otherMass * surfaceGravity();
    }

    public static void main(String[] args) {
        if (args.length != 1) {
            System.err.println("Usage: java Planet ");
            System.exit(-1);
        }
        double earthWeight = Double.parseDouble(args[0]);
        double mass = earthWeight/EARTH.surfaceGravity();
        for (Planet p : Planet.values())
           System.out.printf("Your weight on %s is %f%n",
                             p, p.surfaceWeight(mass));
    }
}
枚举可以实现接口

enum可以像一般类一样实现接口。

public interface Introduce {
    String getDescription();
}

public enum Planet implements Introduce {
    MERCURY (3.303e+23, 2.4397e6) {
        @Override
        String getDescription() {
            // ...
        }
    },
    VENUS   (4.869e+24, 6.0518e6) {
        @Override
        String getDescription() {
            // ...
        }
    },
    EARTH   (5.976e+24, 6.37814e6) {
        @Override
        String getDescription() {
            // ...
        }
    },
    //...
    ;
    
    // ...
    
    String getDescription();
}
枚举不可以继承

enum 不可以继承另外一个类,当然,也不能继承另一个 enum 。

因为 enum实际上都继承自 java.lang.Enum类,而 Java 不支持多重继承,所以 enum不能再继承其他类,当然也不能继承另一个 enum。

EnumSet和EnumMap EnumSet

EnumSet是枚举类型的高性能 Set实现。它要求放入它的枚举常量必须属于同一枚举类型,其内部使用long值作为比特向量,以最大化Set的性能

EnumSet存在两种实现类:

  1. RegularEnumSet 针对枚举数量小于等于64的EnumSet实现,内部使用long作为存储。
  2. JumboEnumSet 针对枚举数量大于64的EnumSet实现,内部使用long数组进行存储。

EnumSet作为工厂类,提供大量的静态方法,以方便的创建EnumSet:

  1. noneOf()
    创建EnumSet,空Set,没有任何元素
  2. allOf()
    创建EnumSet,满Set,所有元素都在其中
  3. copyOf()
    从已有Set中创建EnumSet
  4. of()
    根据提供的枚举值创建EnumSet
  5. range()
    根据枚举定义的顺序,定义一个区间的EnumSet
EnumMap

EnumMap是专门为枚举类型量身定做的 Map实现。它只能接收同一枚举类型的实例作为键值,并且由于枚举类型实例的数量相对固定并且有限,所以 EnumMap 使用数组来存放与枚举类型对应的值。这使得 EnumMap 的效率非常高。

private transient Object[] vals;
public V get(Object key) {
    return (isValidKey(key) ?
            unmaskNull(vals[((Enum)key).ordinal()]) : null);
}

public V put(K key, V value) {
    typeCheck(key);
    int index = key.ordinal();
    Object oldValue = vals[index];
    vals[index] = maskNull(value);
    if (oldValue == null)
        size++;
    return unmaskNull(oldValue);
}

由上可见,EnumMap内部由数组实现(ordrial),以提高Map的操作速度。enum中的每个实例作为键,总是存在,但是如果没有为这个键调用put方法来存入相应值的话,其对应的值便是null。在使用上,EnumMap与key为枚举的Map并无差异。

枚举的应用 switch 状态机
enum Signal {RED, YELLOW, GREEN}

public static String getTrafficInstruct(Signal signal) {
    String instruct = "信号灯故障";
    switch (signal) {
        case RED:
            instruct = "红灯停";
            break;
        case YELLOW:
            instruct = "黄灯请注意";
            break;
        case GREEN:
            instruct = "绿灯行";
            break;
        default:
            break;
    }
    return instruct;
}
组织枚举

可以将类型相近的枚举通过接口或类组织起来, 但是一般用接口方式进行组织。

原因是:Java接口在编译时会自动为enum类型加上public static修饰符, Java类在编译时会自动为 enum类型加上static修饰符。
就是说,在类中组织 enum,如果你不给它修饰为 public,那么只能在本包中进行访问。

public interface Plant {
    enum Vegetable implements INumberEnum {
        POTATO(0, "土豆"),
        TOMATO(0, "西红柿");

        Vegetable(int number, String description) {
            this.code = number;
            this.description = description;
        }

        private int code;
        private String description;

        @Override
        public int getCode() {
            return 0;
        }

        @Override
        public String getDescription() {
            return null;
        }
    }

    enum Fruit implements INumberEnum {
        APPLE(0, "苹果"),
        ORANGE(0, "桔子"),
        BANANA(0, "香蕉");

        Fruit(int number, String description) {
            this.code = number;
            this.description = description;
        }

        private int code;
        private String description;

        @Override
        public int getCode() {
            return 0;
        }

        @Override
        public String getDescription() {
            return null;
        }
    }
}
策略枚举

EffectiveJava中展示了一种策略枚举。这种枚举通过枚举嵌套枚举的方式,将枚举常量分类处理。

enum PayrollDay {
    MONDAY(PayType.WEEKDAY), TUESDAY(PayType.WEEKDAY), WEDNESDAY(
            PayType.WEEKDAY), THURSDAY(PayType.WEEKDAY), FRIDAY(PayType.WEEKDAY), SATURDAY(
            PayType.WEEKEND), SUNDAY(PayType.WEEKEND);

    private final PayType payType;

    PayrollDay(PayType payType) {
        this.payType = payType;
    }

    double pay(double hoursWorked, double payRate) {
        return payType.pay(hoursWorked, payRate);
    }

    private enum PayType {
        WEEKDAY {
            double overtimePay(double hours, double payRate) {
                return hours <= HOURS_PER_SHIFT ? 0 : (hours - HOURS_PER_SHIFT)
                        * payRate / 2;
            }
        },
        WEEKEND {
            double overtimePay(double hours, double payRate) {
                return hours * payRate / 2;
            }
        };
        private static final int HOURS_PER_SHIFT = 8;

        abstract double overtimePay(double hrs, double payRate);

        double pay(double hoursWorked, double payRate) {
            double basePay = hoursWorked * payRate;
            return basePay + overtimePay(hoursWorked, payRate);
        }
    }
}
System.out.println("时薪100的人在周五工作8小时的收入:" + PayrollDay.FRIDAY.pay(8.0, 100));
System.out.println("时薪100的人在周六工作8小时的收入:" + PayrollDay.SATURDAY.pay(8.0, 100));

// TODO 目前觉得对枚举了解比较浅层, 看到了很多更高级的用法, 日后更加的深入的了解后, 有机会回来补充

参考:
一文搞懂 Java 中的枚举,写得非常好!:https://www.cnblogs.com/javastack/p/12837890.html
Java枚举——高级用法与深入解读:https://blog.csdn.net/CSDN_SAVIOR/article/details/122499089
Java 枚举详解:http://events.jianshu.io/p/4ea3f8661f8a
Understanding Java enums:https://blog.knoldus.com/understanding-java-enums/
When to and When Not to Use Enums in Java:https://betterprogramming.pub/when-to-and-when-not-to-use-enums-in-java-8d6fb17ba978
Advanced Java Enum Features You Need to Know:https://alexpower.me/advanced-java-enum-features-you-need-to-know/
Oracle Java Documentation:https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/996298.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号