Spec-Zone .ru
спецификации, руководства, описания, API
Содержание документации

Перечисления


В предшествующих выпусках стандартным способом представить перечислимый тип был образец Перечисления int:
// int Enum Pattern - has severe problems!
public static final int SEASON_WINTER = 0;
public static final int SEASON_SPRING = 1;
public static final int SEASON_SUMMER = 2;
public static final int SEASON_FALL   = 3;
У этого образца есть много проблем, таких как: Возможно обойти эти проблемы при использовании Безопасного с точки зрения типов Перечислимого образца (см. Эффективный Элемент Java 21), но у этого образца есть свои собственные проблемы: Это довольно многословно, следовательно подвержено ошибкам, и его перечислимые константы не могут использоваться в switch операторы.

В 5.0, язык программирования Java™ получает лингвистическое обеспечение перечислимых типов. В их самой простой форме, эти перечисления взгляд точно так же как их C, C++, и C# дубликаты:

enum Season { WINTER, SPRING, SUMMER, FALL }

Но появления могут обманывать. Перечисления языка программирования Java намного более мощны чем их дубликаты на других языках, которые являются немного больше чем прославленные целые числа. Новое enum объявление определяет законченный класс (дублировал перечислимый тип). В дополнение к решению всех упомянутых выше проблем это позволяет Вам добавлять произвольные методы и поля к перечислимому типу, реализовывать произвольные интерфейсы, и больше. Перечислимые типы обеспечивают высококачественные реализации всех методов Object. Они - Comparable и Serializable, и последовательная форма разрабатывается, чтобы противостоять произвольным изменениям в перечислимом типе.

Вот пример класса игральной карты, созданного на нескольких простых перечислимых типах. Card класс является неизменным, и только один экземпляр каждого Card создается, таким образом, это не должно переопределить equals или hashCode:

import java.util.*;

public class Card {
    public enum Rank { DEUCE, THREE, FOUR, FIVE, SIX,
        SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING, ACE }

    public enum Suit { CLUBS, DIAMONDS, HEARTS, SPADES }

    private final Rank rank;
    private final Suit suit;
    private Card(Rank rank, Suit suit) {
        this.rank = rank;
        this.suit = suit;
    }

    public Rank rank() { return rank; }
    public Suit suit() { return suit; }
    public String toString() { return rank + " of " + suit; }

    private static final List<Card> protoDeck = new ArrayList<Card>();

    // Initialize prototype deck
    static {
        for (Suit suit : Suit.values())
            for (Rank rank : Rank.values())
                protoDeck.add(new Card(rank, suit));
    }

    public static ArrayList<Card> newDeck() {
        return new ArrayList<Card>(protoDeck); // Return copy of prototype deck
    }
}
toString метод для Card использует в своих интересах toString методы для Rank и Suit. Отметьте что Card класс короток (приблизительно 25 строк кода). Если безопасные с точки зрения типов перечисления (Rank и Suit) был создан вручную, каждый из них будет значительно более длинным чем все Card класс.

(Частный) конструктор Card берет два параметра, a Rank и a Suit. Если Вы случайно вызовете конструктора с инвертированными параметрами, то компилятор вежливо сообщит Вам о Вашей ошибке. Противопоставьте это int перечислимый образец, в котором программа перестала бы работать во время выполнения.

Отметьте, что у каждого перечислимого типа есть помехи values метод, который возвращает массив, содержащий все значения перечисления, вводит порядок, которым они объявляются. Этот метод обычно используется в комбинации с циклом foreach, чтобы выполнить итерации по значениям перечислимого типа.

Следующий пример является простой вызванной программой Deal это тренируется Card. Это читает два числа из командной строки, представляя число рук, чтобы иметь дело и число карт на руку. Затем это создает новую деку карт, переставляет это, и имеет дело и печатает требуемые руки.

import java.util.*;

public class Deal {
    public static void main(String args[]) {
        int numHands = Integer.parseInt(args[0]);
        int cardsPerHand = Integer.parseInt(args[1]);
        List<Card> deck  = Card.newDeck();
        Collections.shuffle(deck);
        for (int i=0; i < numHands; i++)
            System.out.println(deal(deck, cardsPerHand));
    }

    public static ArrayList<Card> deal(List<Card> deck, int n) {
         int deckSize = deck.size();
         List<Card> handView = deck.subList(deckSize-n, deckSize);
         ArrayList<Card> hand = new ArrayList<Card>(handView);
         handView.clear();
         return hand;
     }
}

$ java Deal 4 5
[FOUR of HEARTS, NINE of DIAMONDS, QUEEN of SPADES, ACE of SPADES, NINE of SPADES]
[DEUCE of HEARTS, EIGHT of SPADES, JACK of DIAMONDS, TEN of CLUBS, SEVEN of SPADES]
[FIVE of HEARTS, FOUR of DIAMONDS, SIX of DIAMONDS, NINE of CLUBS, JACK of CLUBS]
[SEVEN of HEARTS, SIX of CLUBS, DEUCE of DIAMONDS, THREE of SPADES, EIGHT of CLUBS]

Предположите, что Вы хотите добавить данные и поведение к перечислению. Например рассмотрите планеты солнечной системы. Каждая планета знает свою массу и радиус, и может вычислить ее поверхностную силу тяжести и вес объекта на планете. Вот то, как это смотрит:

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),
    PLUTO   (1.27e+22,  1.137e6);

    private final double mass;   // in kilograms
    private final double radius; // in meters
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }
    public double mass()   { return mass; }
    public double radius() { return radius; }

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

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

Перечислимый тип Planet содержит конструктора, и каждую перечислимую константу, как объявляют, с параметрами передают конструктору, когда она создается.

Вот пример программы, который берет Ваш вес на земле (в любом модуле) и вычисляет и печатает Ваш вес на всех планетах (в том же самом модуле):

    public static void main(String[] args) {
        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));
    }

$ java Planet 175
Your weight on MERCURY is 66.107583
Your weight on VENUS is 158.374842
Your weight on EARTH is 175.000000
Your weight on MARS is 66.279007
Your weight on JUPITER is 442.847567
Your weight on SATURN is 186.552719
Your weight on URANUS is 158.397260
Your weight on NEPTUNE is 199.207413
Your weight on PLUTO is 11.703031

Идея добавить поведение к перечислимым константам может быть сделана один шаг далее. Можно дать каждой перечислимой константе различное поведение для некоторого метода. Один способ сделать это, включая постоянное перечисление. Вот пример с перечислением, константы которого представляют четыре основных арифметических операции, и чей eval метод выполняет работу:

public enum Operation {
    PLUS, MINUS, TIMES, DIVIDE;

    // Do arithmetic op represented by this constant
    double eval(double x, double y){
        switch(this) {
            case PLUS:   return x + y;
            case MINUS:  return x - y;
            case TIMES:  return x * y;
            case DIVIDE: return x / y;
        }
        throw new AssertionError("Unknown op: " + this);
    }
}
Это хорошо работает, но это не будет компилировать без оператора throw, который не ужасно симпатичен. Хуже, следует не забыть добавлять новый случай к оператору switch каждый раз, когда Вы добавляете новую константу к Operation. Если Вы забываете, метод eval со сбоем, выполняя вышеупомянутый оператор throw

Есть, иначе дают каждой перечислимой константе различное поведение для некоторого метода, который избегает этих проблем. Можно объявить краткий обзор метода в перечислимом типе и переопределить это с конкретным методом в каждой константе. Такие методы известны как постоянно-специфичные методы. Вот предыдущий пример, сделанный заново, используя этот метод:

public enum Operation {
  PLUS   { double eval(double x, double y) { return x + y; } },
  MINUS  { double eval(double x, double y) { return x - y; } },
  TIMES  { double eval(double x, double y) { return x * y; } },
  DIVIDE { double eval(double x, double y) { return x / y; } };

  // Do arithmetic op represented by this constant
  abstract double eval(double x, double y);
}

Вот пример программы, который тренируется Operation класс. Это берет два операнда из командной строки, выполняет итерации по всем операциям, и для каждой работы, выполняет работу и печатает получающееся уравнение:

    public static void main(String args[]) {
        double x = Double.parseDouble(args[0]);
        double y = Double.parseDouble(args[1]);
        for (Operation op : Operation.values())
            System.out.printf("%f %s %f = %f%n", x, op, y, op.eval(x, y));
    }

$ java Operation 4 2
4.000000 PLUS 2.000000 = 6.000000
4.000000 MINUS 2.000000 = 2.000000
4.000000 TIMES 2.000000 = 8.000000
4.000000 DIVIDE 2.000000 = 2.000000
Постоянно-специфичные методы разумно сложны, и много программистов никогда не должны будут использовать их, но хорошо знать, что они там, если Вы нуждаетесь в них.

Два класса были добавлены к java.util в поддержку перечислений: специального назначения Set и Map реализации вызывают EnumSet и EnumMap. EnumSet высокоэффективное Set реализация для перечислений. Все элементы перечислимого набора должны иметь тот же самый перечислимый тип. Внутренне, это представляется битовый вектором, обычно сингл long. Перечисление устанавливает итерацию поддержки по диапазонам перечислимых типов. Например учитывая следующее перечислимое объявление:

    enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }
за будние дни можно выполнить итерации. EnumSet класс обеспечивает статическую фабрику, которая облегчает:
    for (Day d : EnumSet.range(Day.MONDAY, Day.FRIDAY))
        System.out.println(d);
Перечислимые наборы также обеспечивают богатую, безопасную с точки зрения типов замену для традиционных битовых флагов:
    EnumSet.of(Style.BOLD, Style.ITALIC)

Точно так же EnumMap высокоэффективное Map реализация для использования с перечислимыми ключами, внутренне реализованными как массив. Перечислимые карты комбинируют богатство и безопасность Map интерфейс со скоростью, приближающейся к тому из массива. Если Вы хотите отобразить перечисление на значение, следует всегда использовать EnumMap в предпочтении к массиву.

Card класс, выше, содержит статическую фабрику, которая возвращает деку, но нет никакого способа получить отдельную карту от ее разряда и иска. Просто представление конструктора уничтожило бы одноэлементное свойство (что только единственному экземпляру каждой карты позволяют существовать). Вот то, как записать статическую фабрику, которая сохраняет одноэлементное свойство, используя вложенный EnumMap:

private static Map<Suit, Map<Rank, Card>> table =
    new EnumMap<Suit, Map<Rank, Card>>(Suit.class);
static {
    for (Suit suit : Suit.values()) {
        Map<Rank, Card> suitTable = new EnumMap<Rank, Card>(Rank.class);
        for (Rank rank : Rank.values())
            suitTable.put(rank, new Card(rank, suit));
        table.put(suit, suitTable);
    }
}

public static Card valueOf(Rank rank, Suit suit) {
    return table.get(suit).get(rank);
}
EnumMap (table) карты каждый иск к EnumMap это отображает каждый разряд на карту. Поиск, выполняемый valueOf метод внутренне реализуется как два доступа массива, но код является намного более четким и более безопасным. Чтобы сохранить одноэлементное свойство, обязательно что вызов конструктора в прототипной инициализации деки в Card будьте заменены звонком в новую статическую фабрику:
    // Initialize prototype deck
    static {
        for (Suit suit : Suit.values())
            for (Rank rank : Rank.values())
                protoDeck.add(Card.valueOf(rank, suit));
    }
Также обязательно что инициализация table будьте размещены выше инициализации protoDeck, поскольку последний зависит от прежнего.

Так, когда следует использовать перечисления? Любое время Вы нуждаетесь в фиксированном наборе констант. Это включает естественные перечислимые типы (как планеты, дни недели, и подходит в деке карты), так же как другие наборы, где Вы знаете все возможные значения во время компиляции, такие как варианты в меню, округляя режимы, флаги командной строки, и т.п.. Не необходимо, чтобы набор констант в перечислимом типе остался фиксированным навсегда. Функция была специально предназначена, чтобы учесть двоичное совместимое развитие перечислимых типов.


Oracle и/или его филиалы Авторское право © 1993, 2011, Oracle и/или его филиалы. Все права защищены.
Свяжитесь с Нами