Сегодня я хочу вернуться к известной функции Java — мы рассмотрим Enums.
Давайте начнем с определения:
Перечисление — коллекция элементов, представляющая собой полный упорядоченный список всех элементов в этой коллекции.
Звучит просто, не так ли? Даже если Enums в Java позволяет нам многое, и это действительно отличная функция языка, которая может повысить качество нашего кода.
Давайте начнем с основ, и после этого мы проверим, насколько мощны Enums в Java.
типобезопасный
Это означает, что ваше перечисление будет иметь тип, и вы не можете назначать никакие значения, кроме указанных в константах Enum.
Чтобы быть более конкретным, со следующим перечислением:
enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
нет проблем написать что-то вроде:
Season favouriteSeason = Season.SUMMER;
но:
Season favouriteSeason = 1;
закончится с:
java.lang.Error: Unresolved compilation problem:
Type mismatch: cannot convert from int to Season
собственное пространство имен
Это означает, что даже если значения в одном перечислении перекрываются с другим перечислением:
enum TDDStep {
RED, GREEN, REFACTOR
}
enum TrafficLight {
RED, ORANGE, GREEN
}
Оба действительны и могут использоваться одновременно.
константы неявно статические и конечные
Невозможно сделать что-то вроде:
TDDStep.RED = TDDStep.GREEN;
Это просто закончится с:
java.lang.Error: Unresolved compilation problem:
The final field TDDStep.RED cannot be assigned
можно использовать внутри оператора switch.
Так что не бойтесь писать что-то вроде этого:
public void continueWork(TDDStep step) {
switch (step) {
case RED:
makeTestGreen();
break;
case GREEN:
refactoreCode();
break;
case REFACTOR:
goToNextTest()
break;
}
}
Это будет работать ? Как в коде, так и в реальной жизни ?
невозможно создать экземпляр с помощью нового
Как написано в документации :
Конструктор для типа enum должен быть закрытым или закрытым доступом. Он автоматически создает константы, которые определены в начале тела enum. Вы не можете вызвать конструктор enum самостоятельно.
И это (согласно определению), потому что Enum должен быть единственным, кто отвечает за возврат предопределенных экземпляров.
Чтобы предотвратить любые другие изменения:
Последний метод клонирования в Enum гарантирует, что константы enum никогда не могут быть клонированы, а специальная обработка механизмом сериализации гарантирует, что повторяющиеся экземпляры никогда не будут созданы в результате десериализации. Рефлексивная реализация типов перечислений запрещена. Вместе эти четыре вещи гарантируют, что не существует ни одного экземпляра типа enum, кроме тех, которые определены константами enum.
Но если вы все равно хотите попробовать, вы получите:
java.lang.Error: Unresolved compilation problem:
Illegal modifier for the enum constructor; only private is permitted.
что мы получаем без усилий?
Взгляните на простой enum:
public enum TDDStep {
RED, GREEN, REFACTOR;
}
Не так впечатляет, не так ли? Но не волнуйтесь, даже такая простая конструкция дает несколько полезных методов:
@Test
public void stringToEnum() {
assertEquals(TDDStep.RED, TDDStep.valueOf("RED"));
}
@Test
public void enumToString() {
assertEquals("GREEN", TDDStep.GREEN.toString());
}
@Test
public void enumName() {
assertEquals("REFACTOR", TDDStep.REFACTOR.name());
}
@Test
public void enumOrdinal() {
assertEquals(1, TDDStep.GREEN.ordinal());
}
Метод name () и ordinal () — это простые методы получения, которые возвращают значения, заданные в конструкторе, который генерируется компилятором в ответ на объявления типов enum.
Конструктор не может быть вызван программистом, и его объявление выглядит так:
protected Enum(String name, int ordinal)
Метод toString () до тех пор, пока он не будет переопределен, будет возвращать имя константы перечисления, которое более не совпадает с тем, которое возвращает name ().
Метод valueOf () ищет перечисление, имя которого совпадает с заданным значением, и возвращает его.
Есть еще один метод, о котором стоит упомянуть:
@Test
public void valuesOfEnum() {
TDDStep[] steps = {TDDStep.RED, TDDStep.GREEN, TDDStep.REFACTOR};
assertArrayEquals(steps, TDDStep.values());
}
Я считаю, что после просмотра теста не нужно больше объяснений ?
В этом параграфе я описал самый важный метод Enum, который мы получаем из-за использования ключевого слова enum.
Давайте посмотрим, что мы можем сделать с этим больше.
мои собственные методы
Перечисление не должно быть простым списком элементов. Вы можете добавить свой собственный метод в него или / и предоставить свой собственный конструктор.
Стоит отметить, что конструктор должен быть закрытым, и вы не можете запустить конструктор родителя (Enum), это будет сделано без дополнительных усилий.
Категория enum показывает, как это сделать:
package com.smalaca.enumeration.designpatterns;
public enum Category {
CREATIONAL("Deals with object creation mechanisms, trying to create objects in a manner suitable to the situation."),
STRUCTURAL("Eases the design by identifying a simple way to realize relationships between entities."),
BEHAVIORAL("Identifies common communication patterns between objects and realize these patterns.");
private final String description;
private Category(final String description) {
this.description = description;
}
public String describe() {
return description;
}
public String getExample() {
String example = "";
switch(this)
{
case CREATIONAL:
example = "Builder";
break;
case STRUCTURAL:
example = "Adapter";
break;
case BEHAVIORAL:
example = "Strategy";
break;
}
return example;
}
}
Верьте мне или нет, но это действительно работает, и если вы хотите проверить это, просто запустите следующие тесты:
@RunWith(Parameterized.class)
public class CategoryTest {
private Category category;
private String description;
private String example;
public CategoryTest(Category category, String description, String example) {
this.category = category;
this.description = description;
this.example = example;
}
@Parameters
public static Collection<Object[]> dataProvider() {
return Arrays.asList(new Object[][] {
{
Category.CREATIONAL,
"Deals with object creation mechanisms, trying to create objects in a manner suitable to the situation.",
"Builder"
},
{
Category.STRUCTURAL,
"Eases the design by identifying a simple way to realize relationships between entities.",
"Adapter"
},
{
Category.BEHAVIORAL,
"Identifies common communication patterns between objects and realize these patterns.",
"Strategy"
}
});
}
@Test
public void getCategoryExplanation()
{
assertSame(description, category.describe());
}
@Test
public void getExample()
{
assertSame(example, category.getExample());
}
}
Если вы не знакомы с аннотацией @Parameterized в JUnit, посмотрите здесь для быстрого ознакомления.
дай мне немного абстракции
Время от времени полезно создавать абстрактные методы, поэтому это можно сделать и в перечислении:
package com.smalaca.enumeration.designpatterns;
public enum Creational {
ABSTRACT_FACTORY {
@Override
public String describe() {
return "Provide an interface for creating families of related or dependent objects without specifying their concrete classes.";
}
},
BUILDER {
@Override
public String describe() {
return "Separate the construction of a complex object from its representation allowing the same construction process to create various representations.";
}
},
FACTORY_METHOD {
@Override
public String describe() {
return "Define an interface for creating a single object, but let subclasses decide which class to instantiate.";
}
};
abstract public String describe();
}
Теперь запустите:
@RunWith(Parameterized.class)
public class CreationalTest {
private Creational designPattern;
private String description;
public CreationalTest(Creational designPattern, String description) {
this.designPattern = designPattern;
this.description = description;
}
@Parameters
public static Collection<Object[]> dataProvider() {
return Arrays.asList(new Object[][] {
{
Creational.ABSTRACT_FACTORY,
"Provide an interface for creating families of related or dependent objects without specifying their concrete classes.",
},
{
Creational.BUILDER,
"Separate the construction of a complex object from its representation allowing the same construction process to create various representations.",
},
{
Creational.FACTORY_METHOD,
"Define an interface for creating a single object, but let subclasses decide which class to instantiate.",
}
});
}
@Test
public void getPatternExplanation()
{
assertSame(description, designPattern.describe());
}
}
И? Все зеленое, не так ли?
а как насчет интерфейсов?
Мы знаем, насколько важны интерфейсы в хорошо разработанном коде. Вот почему для всех нас это хорошая новость, что Enums позволяет реализовать столько интерфейсов, сколько вы хотите, как в обычном классе.
Давайте предположим, что у нас есть следующий интерфейс:
public interface HasDescription {
public String describe();
}
Теперь мы изменим определение перечислений и добавим часть о реализации вновь добавленного интерфейса:
public enum Creational implements HasDescription { /** code */ )
и
public enum Category implements HasDescription { /** code */ )
Теперь запустите наши тесты еще раз. Все еще в порядке? Должно быть ?
как насчет наследства?
Итак, можно реализовать интерфейсы и создать абстрактные методы, а как насчет наследования?
Я считаю, что в настоящее время нет необходимости в каких-либо объяснениях, потому что я уже упоминал кое-что о parent в одном из абзацев выше, когда писал о методах name () и ordinal (), и вызывал в нем parent, но будь точным …
Как написано в учебнике :
Все перечисления неявно расширяют java.lang.Enum. Поскольку Java не поддерживает множественное наследование, перечисление не может расширять что-либо еще.
пустой список
Из любопытства хочу также написать, что можно создать пустой enum, и у компилятора с этим не возникнет проблем. Это означает, что перечисленное ниже действительно:
public enum KnownAlienRace {}
В качестве примечания хочу сказать, что я никогда не использовал его, поэтому если у вас есть хороший пример, который показывает, что создание перечисления имеет смысл поделиться со мной в комментариях, я буду признателен ?
Это все на сегодня.
Я надеюсь, вам понравится и узнал что-то новое. Или хотя бы освежить память ?
Я жду ваших комментариев.
И … удачи в использовании силы Enums!