枚举

枚举是指包含指定个数特定类型的实例类,所有枚举类默认都继承 java.lang.Enum ,枚举类是可序列化、可比较的

1
2
3
4
5
6
7
8
public enum Person {
    MAN,
    WOMAN;
}

public static void main(String[] args) {
    System.out.println(Person.MAN);
}
1
MAN

枚举构造器

  1. 枚举中自动生成的无参构造器默认是private的
  2. 显示定义的构造器只能是私有或无访问符的,即枚举不可以从外部进行实例化

无参构造器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public enum Person {
    MAN(),
    WOMAN();

    Person() {}
}

public static void main(String[] args) {
    for(Person person : Person.values()) {
        System.out.println(person);
    }
}
1
2
MAN
WOMAN

带参构造器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public enum Person {
    MAN(1),
    WOMAN(2),
    UNKNOWN,
    ;

    Person() {}

    Person(int status) {
        this.status = status;
    }

    public int status;
}

public static void main(String[] args) {
    for(Person person : Person.values()) {
        System.out.println(person.status);
    }
}
1
2
3
1
2
0

枚举类初始化

枚举类的初始化与一般类的初始化过程(静态代码块->构造器代码块->构造函数)不同,它的执行顺序为:实例构造器代码块->构造函数->静态常量代码块,以下代码进行演示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public enum Person {
    MAN, WOMAN;

    Person() {
        System.out.println("person constructor run!");
    }
    {
        System.out.println("constructor code block run");
    }

    static {
        System.out.println("static code block run");
    }
}

public static void main(String[] args) {
    for(Person person: Person.values()) {
        System.out.println(person);
    }

}
1
2
3
4
5
6
7
constructor code block run
person constructor run!
constructor code block run
person constructor run!
static code block run
MAN
WOMAN

从结果中可以看出构造代码块先执行了,为什么这样?因为枚举中的常量默认都是 public static final 修饰的,当想执行对静态变量的访问时,需要先有变量才行(这也是枚举类要求枚举值要定义在最上面的原因),这时就需要先把常量实例创建出来,就要先调用构造方法进行创建

枚举提供的方法

name()

获取当前枚举常量的名称

ordinal()

获取枚举常量的编号,默认从0开始

toString()

values()

获取当前枚举所有的常量值,返回为一个数组

valueOf(String name)

根据名称获取枚举常量

方法示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public enum Person {
    MAN, WOMAN
}

public static void main(String[] args) {

    System.out.println(Person.MAN.name());
    System.out.println(Person.MAN.ordinal());
    System.out.println(Person.MAN.toString());
    System.out.println("person lengthis " + Person.values().length);
    System.out.println(Person.valueOf("WOMAN"));
    // System.out.println(Person.valueOf("Person.WOMAN")); //error, only need constant name
}
1
2
3
4
5
MAN
0
MAN
person lengthis 2
WOMAN

equals、hashcode、compareTo

这些方法在 java.lang.Enum 都被定义成了final方法,即不可以被子类覆写

枚举中定义方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public enum Person {
    MAN {
        @Override
            public void absMethod() {
            System.out.println("man absMethod");
        }
    },
    WOMAN {
        @Override
            public void absMethod() {
            System.out.println("woman absMethod");
        }
    };

    public void normal() {
        System.out.println("normal method");
    }

    public final void say() {
        System.out.println("final method");
    }

    public abstract void absMethod();
}

public static void main(String[] args) {
    for(Person person : Person.values()) {
        person.normal();
        person.say();
        person.absMethod();
    }
}
1
2
3
4
5
6
normal method
final method
man absMethod
normal method
final method
woman absMethod

上面的例子显示可以在枚举中定义 普通方法final方法抽象方法 ,定义的抽象方法必须由具体的实例进行实现,final方法可以防止具体的实例进行覆写

枚举实现接口

枚举是可以实现接口的,这是真的!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public enum Person implements Life {
    MAN {
        @Override
            public void eat() {
            System.out.println("man eat");
        }

        @Override
            public void sleep() {
            System.out.println("man sleep");
        }

        @Override
            public void drink() {
            System.out.println("man drink");
        }
    },
    WOMAN {
    };

    @Override
    public void eat() {
        System.out.println("person eat");
    }

    @Override
    public void sleep() {
        System.out.println("person sleep");
    }

    @Override
    public void drink() {
        System.out.println("person drink");
    }
}

interface Life {
    void eat();
    void sleep();
    void drink();
}

public static void main(String[] args) {
    for(Person person : Person.values()) {
        System.out.println(person);
        person.eat();
        person.sleep();
        person.drink();
    }
}
1
2
3
4
5
6
7
8
MAN
man eat
man sleep
man drink
WOMAN
person eat
person sleep
person drink

上面的枚举类Person实现了Life接口,默认实现了Life中的3个方法 eat, sleep和 drink,而MAN枚举常量又对Person的实现进行了覆写,实现了自己的逻辑。这样一看,枚举类还是很强大的

枚举相关的集合

与枚举相关的集合有 EnumSetEnumMap 一个表示存储枚举的Set,一个表示存储Key为枚举的Map

EnumSet

存储枚举的Set,确定存储类型后按bit位进行存储

1
2
3
4
5
6
7
8
9
public enum Person {
    MAN, WOMAN;
}

public static void main(String[] args) {
    Set<Person> persons = EnumSet.allOf(Person.class);
    System.out.println(persons.size());
    System.out.println(persons.contains(Person.MAN));
}
1
2
2
true

EnumMap

存储Key为枚举的Map,确定存储类型后按数组进行存储

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public enum Person {
    MAN, WOMAN;
}

public static void main(String[] args) {
    Map<Person, Integer> map = new EnumMap<>(Person.class);
    map.put(Person.MAN, 11);
    map.put(Person.WOMAN, 12);
    System.out.println(map.size());
    System.out.println(map.containsKey(Person.MAN));
}
1
2
2
true

总结

  1. 枚举类不可以被 abstractfinal 修饰
  2. 枚举的构造器默认是 private
  3. 不可以显式的实例化枚举类
  4. 枚举类中可以声明final方法
  5. 枚举类中可以声明abstract方法
  6. 枚举可以实现接口
  7. JDK中提供了针对枚举的集合 EnumSet 和 EnumMap

Reference

https://docs.oracle.com/javase/specs/jls/se11/html/jls-8.html#jls-8.9

https://blogs.oracle.com/javamagazine/post/quiz-yourself-initializing-enums-in-java-code