Spec-Zone .ru
спецификации, руководства, описания, API
|
// 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;У этого образца есть много проблем, таких как:
int
можно передать в любом другом значении int, где сезон требуется, или добавьте два сезона вместе (который не имеет никакого смысла).SEASON_
) избегать коллизий с другими международными перечислимыми типами.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, поскольку последний зависит от прежнего. Так, когда следует использовать перечисления? Любое время Вы нуждаетесь в фиксированном наборе констант. Это включает естественные перечислимые типы (как планеты, дни недели, и подходит в деке карты), так же как другие наборы, где Вы знаете все возможные значения во время компиляции, такие как варианты в меню, округляя режимы, флаги командной строки, и т.п.. Не необходимо, чтобы набор констант в перечислимом типе остался фиксированным навсегда. Функция была специально предназначена, чтобы учесть двоичное совместимое развитие перечислимых типов.