Вводная


Очень часто, мы используем инструменты строго по назначению, запрещая себе делать шаг влево или вправо. Но что если мы немного 'забудемся'? Что, если мы посмотрим на привычные нам вещи под другим углом? В этой статье собраны подходы использования перечислений и проведен небольшой эксперимент над ними. Сарказм, юмор и немного филосовских вопросов. Кому интересно, добро пожаловать под кат.

Предостережение


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

Не рассказанная история...


Однажды попадает разработчик в место, где решают судьбу. Время спустя, перед ним появляется образ и спрашивает:
— Кто ты?
— Я, разработчик, звать Иван, — а про себя: Во встрял то.
Голос опять:
— Хочешь туда?. Взгляд на дверь, за которой рай.
— Ага, — робко Иван.
— Чего поведаешь мне?, — спрашивает Голос.
Немного подумав, Иван начинает говорить:
— Есть в java Enum-Всемогущий.
— Как так, Всемогущий? — перебивает Голос с возмущением. — Это только перечисление!

public enum JavaLanguage {
    JAVA("Forever"),
    SCALA("Next generation") {},
    KOTLIN("Future") {};
    private final String claim;
    JavaLanguage(String claim) {
        this.claim = claim;
    }
    public String getClaim() {
        return claim;
    }
}

— Ага, — отвечает разраб, Но не только.
— Докажи!
— Enum как гвозди, утильным могёт.

public enum LanguageUtils {
    ;
    /** java-doc */
    LanguageUtils() {
        throw new IllegalStateException("Это не перечисление");
    }
    /** java-doc */
    public static String[] getKeyWords(String languageName) {
        if (languageName != null) {
            // немного логики здесь
            return loadFromResource(languageName + "/keywords.dat");
        }
        throw new IllegalStateException("Необходимо указать язык");
    }
    /** java-doc */
    private static synchronized String[] loadFromResource(String resourceName) {
        String[] items = null;
        // Код загрузки здесь
        return items;
    }
    /** Много статических методов здесь */
}

— Во так чудеса, но… Наследника у него нет!
— А это как посмотреть. А кого считать Наследником? Scala? Kotlin?
— Давай пример, не дожидаясь пока разраб завершит свой вопрос

    // в JavaLanguage.java файле
    public static void main(String[] args) {
        // it's true
        if (JAVA.getClass() == SCALA.getClass().getSuperclass()) {
            System.out.println("Наследник то есть!");
        }
        // it's true
        if (JAVA.getClass() == KOTLIN.getClass().getSuperclass()) {
            System.out.println("Да не один!");
        }
    }

— Да уж, интересные Вы ребята, прогеры — уже улыбаясь, говорит Голос, — Но малова-то будет
Почесав репу, Иван продолжил:
— Enum-то у нас фабрика!
— Не, было уже.
Пришлось, Ивану последний козырь достать:
— Enum-Синглтон, точно!
Выбери свое
Ты за Java?
public enum Highlander {
    JAVA;
    Highlander() {
        if (ordinal() != 0) {
            throw new IllegalStateException("В живых должен остаться только один");
        }
        init();
    }
    private void init() {
        // код здесь!
    }
    /** java-doc */
    public String getOwner(String index) {
        // логика здесь
        return "Джеймс Гослинг, Sun Microsystems";
    }
}

Ты за Scala?
public enum Highlander {
    SCALA {};
    Highlander() {
        if (ordinal() != 0) {
            throw new IllegalStateException("В живых должен остаться только один");
        }
        init();
    }
    private void init() {
        // код здесь!
    }
    /** java-doc */
    public String getOwner(String index) {
        // логика здесь
        return "Мартин Одерски, Федеральная политехническая школа Лозанны";
    }
}

Ты за Kotlin?
public enum Highlander {
    KOTLIN {};
    Highlander() {
        if (ordinal() != 0) {
            throw new IllegalStateException("В живых должен остаться только один");
        }
        init();
    }
    private void init() {
        // код здесь!
    }
    /** java-doc */
    public String getOwner(String index) {
        // логика здесь
        return "Андрей Бреслав, JetBrains";
    }
}


— Джошуа Блох говорит*, что это лучшая реализация Синглтона.
— Ну а ты?
— А что я? Это, это — это синглтон-фабрика, для хранения одного единственного элемента, тить колотить...

Highlander.valueOf("JAVA");
Это точка для доступа к массиву для хранения одного единственного элемента, тить колотить...

Highlander.values()[0];

Это наследник класса ..., — хотел было продолжить Иван, но был приятно удивлен:
— Проходи...

Немного выводов


Итого получается, что enum можно наделить следующими качествами и свойствами, в зависимости от точки обзора:

  • Перечисление и данные
  • Каркас для утилитного класса
  • Каркас для синглтона класса + антипатерн прилагается
  • Каркас для фабрики

Эксперимент


Я решил понять, сколько можно максимально сгенерировать элементов перечислений. Мой собственный ответ и реальность настолько разошлись, что я усомнился в своих знаниях. Прежде чем Вы посмотрите ниже, попытайтесь дать ответ самостоятельно. Упростим, скажите хотя бы порядок? Вот код, который я использовал для генерации класса перечислений (на быструю руку):

Код-генерации enum?
package com.enums;

import java.io.*;

public class EnumGeneratorShort implements Closeable {
    private BufferedWriter writer;
    public EnumGeneratorShort(File outputFile) throws IOException {
        writer = new BufferedWriter(new FileWriter(outputFile));
    }
    void append(String line) throws IOException {
        writer.append(line);
    }
    void appendLine(String line) throws IOException {
        writer.append(line).append("\n");
    }
    @Override
    public void close() throws IOException {
        if (writer != null) {
            writer.close();
        }
    }
    /** Сколько сгенерировать enum*/
    private static final int ENUM_COUNT = XXXX; // где XXXX - размер
    private static final char[] ALPHABET = new char[] {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
            'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
    public static void main(String[] args) throws IOException {
        System.out.println("Start generation");
        File outputFile = new File("src/main/java/com/enums/BigEnumShort.java");
        try (EnumGeneratorShort enumGen = new EnumGeneratorShort(outputFile)) {
            enumGen.appendLine("package com.enums;");
            enumGen.appendLine("");
            enumGen.appendLine("public enum BigEnumShort {");
            enumGen.append("A");
            int index = 1;
            for (; index < ENUM_COUNT; index++) {
                enumGen.appendLine(",");
                String name = getName(index, "");
                if ("if".equals(name) || "do".equals(name)) {
                    name = getName(++index, "");
                }
                enumGen.append(name);
            }
            enumGen.appendLine(";");
            enumGen.appendLine("");
            enumGen.appendLine("    public static void main(String[] args) {");
            enumGen.appendLine("        System.out.println(\"Find enum \" + BigEnumShort.valueOf(\"B\"));");
            enumGen.appendLine("    }");
            enumGen.appendLine("}");
            System.out.println("End generation. Total " + index);
        }
    }
    public static String getName(int index, String before) {
        if (index < ALPHABET.length) {
            return before + ALPHABET[index];
        }
        int tail = index / ALPHABET.length;
        int current = index % ALPHABET.length;
        return getName(tail, before + ALPHABET[current]);
    }
}

Вы уже предположили? Так вот, на семерочке мне удалось сгенерировать всего 2746 элементов перечислений. А дальше вот это:

Ошибка_3
Total 5000
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project bigenum: Compilation failure
[ERROR] /home/XXX/temp/BigEnum/bigenum/src/main/java/com/enums/BigEnumShort.java:[4,1] code too large

Но, так как я раскатал губу в 4 этажа, сначала я получил такую ошибку:

Ошибка_1
Total 134217727
Compiling 1 source file to /home/XXX/temp/BigEnum/bigenum/target/classes
An exception has occurred in the compiler (1.7.0_51). Please file a bug at the Java Developer Connection (http://java.sun.com/webapps/bugreport) after checking the Bug Parade for duplicates. Include your program and the following diagnostic in your report. Thank you.
java.lang.IllegalArgumentException

А потом, немного подвернув ее, такую:

Ошибка_2
Total 8388607
ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project bigenum: Compilation failure: Compilation failure:
[ERROR] /home/XXX/temp/BigEnum/bigenum/src/main/java/com/enums/BigEnumShort.java:[4,1] code too large
[ERROR] /home/XXX/temp/BigEnum/bigenum/src/main/java/com/enums/BigEnumShort.java:[3,8] too many constants

Еще мне было интересно, как такое перечисление смогут переварить известные декомпиляторы. Итого по субъективной оценке ожидания:
Место Декомпилятор Результат
1 fernflower.jar ОК
2 jad OK
3 procyon Дождался
4 cfr_0_115.jar Не дождался

Спасибо вам за внимание.

Источники и вдохновители


Effective Java, 2nd Edition, by Joshua Bloch*
Википедия
Правильный Singleton в Java
Комментарий от apanasevich
Комментарий от Sirikid
Поделиться с друзьями
-->

Комментарии (17)


  1. abbath0767
    06.02.2017 21:52
    +1

    Относительно очевидные вещи, но интересно преподнесенные) спасибо за статью, хоть сейчас и не пятница, но формат близок


    1. reforms
      06.02.2017 21:52

      Благодарю


  1. rPman
    07.02.2017 00:05
    +1

    Я в шоке! 8 миллионов констант в одном файле, и компилятор его съел.
    Как долго вообще шла компиляция? Меняется ли заметно скорость компиляции при использовании такого большого класса?


    1. TheKnight
      07.02.2017 05:06

      Не знаю как у автора, но из моей практики могу сказать следующее — десять больших классов компилируются быстрее чем сотня более мелких, выполняющую ту же роль. Размеры больших классов близки к предельному.


      1. Borz
        07.02.2017 15:08

        потому как к ФС происходит меньше обращений


    1. reforms
      07.02.2017 11:08
      +1

      Комп: 4 ядра, i5-2500 CPU @ 3.30GHz, оперативка 16G
      Исходник: 85 мегабайт
      Время компиляции: Total time: 05:04 min


  1. LekaOleg
    07.02.2017 15:11
    +1

    Простите конечно, но определение Разраб меня как Разработчика обижает(
    PS. Разработчики такие ранимые)


    1. reforms
      08.02.2017 16:09

      Ок. Учту. Слово это конечно с душком..., как известно раз_раб, два_раб… :)


  1. sleeply4cat
    07.02.2017 17:08

    А кто-нибудь может объяснить, почему в методе может быть u4 команд, но таблица исключений покрывает только u2, что, по-видимому, и является ограничением его размера?


  1. apanasevich
    08.02.2017 16:03

    А я еще я в предыдущем вашем посте написал вот такой коммент коммент.

    И судя по ссылке оттуда выходит, что не нужно бросать исключения в конструкторе энума. Это лишний код. На который вам нужно писать лишние тесты (по-хорошему).

    Наглядно написанное ораклом ссылку демонстрирует вот такой пример:

    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    
    public final class Test {
        public static void main(String[] args) {
            try {
                final Constructor<?> qwertyConstructor = Qwerty.class.getDeclaredConstructors()[0];
                qwertyConstructor.setAccessible(true);
                System.out.println("I've got " + qwertyConstructor.newInstance());
    
                final Constructor<?> timeUnitConstructor = java.util.concurrent.TimeUnit.class.getDeclaredConstructors()[0];
                timeUnitConstructor.setAccessible(true);
                timeUnitConstructor.newInstance();
                System.out.println("You'll never see me.");
            } catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException | InstantiationException x) {
                x.printStackTrace();
            }
        }
    }
    
    class Qwerty {
        private Qwerty() {
        }
    }


    I've got ololo.Qwerty@45ee12a7
    
    java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    	at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
    	at ololo.Test.main(Test.java:15)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:498)
    	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
    


    1. reforms
      08.02.2017 16:20

      И судя по ссылке оттуда выходит, что не нужно бросать исключения в конструкторе энума. Это лишний код.

      В этом тоже определенный сарказм, до точки с запятой в LanguageUtils, неожиданно:
          SHAPITO_CIRCUS;
      

      Ясно, что это апофеоз тупости, но все же, конструтор-смотрелка, все сделает как надо :)


      1. apanasevich
        08.02.2017 16:23

        Я не понимаю, о чем вы. Где SHAPITO_CIRCUS?


        1. reforms
          08.02.2017 16:38

          Добавить разумеется в класс LanguageUtils, если отсутствует такой конструктор-старожил — плевое дело, если Вы понимаете о чем я :)


          1. apanasevich
            08.02.2017 16:45

            Удалить такой конструктор тоже плевое дело.


            1. reforms
              08.02.2017 16:48

              Хотите живой пример?


              1. apanasevich
                08.02.2017 16:49

                конечно


                1. reforms
                  08.02.2017 16:56

                  public enum LanguageUtils {
                      SYNCH_OBJECT;
                      /** java-doc */
                      public static void doWork1() {
                          // много код здесь, а потом
                          synchronized (SYNCH_OBJECT) {
                              // doSomeReadWriteOperation1 - внутри изменение/чтение общего объект N1
                          }
                      }
                      /** java-doc */
                      public static String doWork2(String name) {
                          // много код здесь, а потом
                          synchronized (SYNCH_OBJECT) {
                              // doSomeReadWriteOperation2 - внутри изменение/чтение общего объект N1
                          }
                          return "";
                      }
                  }