Статьи

Этот мощный Enum

Сегодня я хочу вернуться к известной функции 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.

[JLS- Enums]

Но если вы все равно хотите попробовать, вы получите:

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!