Вводная


Наверное, java-классы — это самая известная ее часть. Мы их используем каждый день, пишем их, правим их. Но есть много нюансов, о которых мы даже не догадываемся. И я люблю за это 'нашу' java — она всегда сможет оставаться загадочной, таинственной. Сегодня часть ее секретов падет к Вашим ногам. Здесь вы найдете необычные примеры кода, смешную историю и интересную статистику. Кому интересно, добро пожаловать под кат.

Немного деталей


Если Вы java-эксперт, примеры кода для Вас будут скучноватыми, а в остальном, как всегда. В свое время мне было очень интересно, что в java 7 снаружи и под капотом, как устроен формат файла class и прочее. Мне пришлось познакомиться вот с этими документами. От туда я подчеркнул почти все идеи для этой статьи. Тем не менее, я заранее прошу прощения за неточности в терминологии у фундаментальных теоретиков и опытных экспертов. На некторые вопросы я не буду давать ответы из-за их очевидности или легкого поиска ответа.

И так первый вопрос: 'А какие бывают типы и виды классов в java 7?' Большинство ответит правильно, но некоторые — неполностью. Очень часто забывают упомянуть про локальные классы.

Локальный класс


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

Пример локального класса
public class LocalClassExample {
    {
        // локальный класс в блоке инициализации
        class MyFirstLocalClass {
            int someField;
        };
    }
    // в методе
    public void someMethod() {
        // еще один
        class MySecondLocalClass {
        };
    }
    // в статическом методе
    public static void someStaticMethod() {
        class MyThirdLocalClass {
        };
    }
    // и даже так
    public void someBlock() {
        try {
        } catch (Exception e) {
            class MyFourthLocalClass {};
        }
    }
}

Я просмотрел очень много кода за свою жизнь, но ни разу не встретил явных именованных деклараций класса внутри метода. Может просто не повезло. А Вы встречали? Но когда я решил собрать статистику по типам и видам классов, то обнаружил, что в rt.jar локальные классы присутствуют и более того, используются в таком небезызвестном классе как java.lang.Package. Век живи — век учись. Еще есть интересное утверждение: 'Анонимный класс — это локальный класс без имени'. Для экспертов вопрос: 'Так ли это?'

Класс аннотаций


Уже не осталось людей, которые бы ни разу не писали классы типа аннотации. И сразу маленький пример.

@Target(ElementType.LOCAL_VARIABLE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SmileAnn {
    String name() default "";
}

Но тем не менее, здесь есть чему удивиться. Как вы думаете, ниже представлен валидный код?

Странный код аннотации
@Target(ElementType.LOCAL_VARIABLE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DontSmileAnn {
    String name() default "";
    /** Что это? */
    static final String WHAT1 = "WHAT1";
    /** А это? */
    final String WHAT2 = "WHAT2";
    /** Кто разрешил здесь класс объявить? */
    static class What3 {
    };
}

На самом деле ничего сложного нет. Но продолжим, а как Вам такой пример?

Наследник
public class ExtendsFromAnn implements DontSmileAnn {
    @Override
    public Class<ExtendsFromAnn> annotationType() {
        return ExtendsFromAnn.class;
    }
    @Override
    public String name() {
        return "ExtendsFromAnn";
    }
}

Ответы здесь все простые — это рабочие примеры кода, так как по факту, под капотом interface = interface, с небольшими оговорками, поэтому все, что можно писать в interface можно и в аннотациях (опять же с оговорками). Наследование в коде от класса типа аннотация я встречал в тестах. Про аннотации у меня все, но есть маленький пример аннотированного типа массива строк и формы его объявления:

Необычная форма
    public static void main(String[] args) {
        // Да так можно, но все равно странно.
        @SmileAnn String []simpleArray[] = {{}};
    }

Надеюсь я Вас не утомил. Но если это не так, тогда следующий абзац специально для Вас.

Обычный класс


Очень сложно удивить кого-либо информацией об обычном классе (исключая примеры с дженериками). Как я ни старался, найти что-то значимое не смог. Но есть у меня одна история-анекдот.

Однажды разработчику потребовалось написать утилитный класс для решения поставленной задачи. Вроде все сделал правильно, написал java-doc, тесты. Отправил патч на ревью.

/**java-doc*/
public class Utils {
    /**несколько методов, для экономии места не привожу*/
}

Начальник-Михалыч посмотрел патч и сказал — 'Все ок, но давай сделаем защиту от дурака — добавь приватный конструктор'. Как это водится, разработчику патч переделывать не хочется, а поверх нельзя, поэтому превозмогая себя и, переходя определенную грань субординации, разраб спросил: 'А что у нас в компании, Михалыч, дураки работают или ты кого-то конкретно имеешь в виду?'. Но делать нечего, нужно переделывать, причем все просто — добавить приватный конструктор:

/**java-doc*/
public class Utils {
    /** Добрый комментарий от доброго разработчика */
    private Utils() {}
    /**несколько методов, для экономии места не привожу*/
}

'Готово' — крикнул разраб, 'Молодец' — ответил Михалыч. Хотел он было нажать submit, как раздался звонок. В этот самый момент, начальник департамента, освободившись от важных дел решил тряхнуть стариной и ткнул в первый попавшийся патч для ревью. 'О-о-о!' — заверещал он. 'Михалыч, Вы что код разучились писать? А где защита от деб*ла?'. Начальник департамента человек серьезный, поэтому Михалыч про себя: 'Что у нас в компании, деб*лы работают или ты кого-то конкретно имеешь в виду?'. Угрюмый Михалыч заворачивает патч с пометкой добавить abstract к классу. Нижняя губа разраба затряслась.Шо опять?

/**java-doc и очень милый комментарий от милого разработчика */
public abstract class Utils {
    /** Добрый комментарий от доброго разработчика */
    private Utils() {}
    /**несколько методов, для экономии места не привожу*/
}

По иронии судьбы в этот день в отдел пришел стажер и, получив свое первое задание, кинулся в бой. Его взгляд остановился на Utils, и на лице появились и восхищение и недоумение. Набравшись смелости, он громко задал свой первый искрометный вопрос: 'Парни, а как можно наследоваться от класса, с приватным конструктором?'

Класс перечислений


Кого сейчас ими удивишь? Вот если бы лет 10 назад. Поэтому здесь немного вопросов по пониманию кода. Как вы думаете, есть ли разница в декларации следующих элементов перечисления и если есть, то почему?

public class EnumExample {
    public enum E1 {
        SIMPLE
    }
    public enum E2 {
        SIMPLE()
    }
    public enum E3 {
        SIMPLE {
        }
    }
    public enum E4 {
        SIMPLE() {
        }
    }
}

Если вы знаете правильный ответ то следующий вопрос Вам покажется легким: 'Что будет на консоле?'

public class EnumExample {
    public enum E1 {
        SIMPLE
    }
    public enum E2 {
        SIMPLE()
    }
    public enum E3 {
        SIMPLE {
        }
    }
    public enum E4 {
        SIMPLE() {
        }
    }
    public static void main(String[] args) {
        System.out.println(E1.SIMPLE.getClass().isEnum());
        System.out.println(E2.SIMPLE.getClass().isEnum());
        System.out.println(E3.SIMPLE.getClass().isEnum());
        System.out.println(E4.SIMPLE.getClass().isEnum());
    }
}

Конечно, здесь все на поверхности — E3.SIMPLE и E4.SIMPLE это экземпляры анонимного класса этих енумов. Поэтому последние 2 вызова дадут false результат. Будьте внимательны, когда используете проверку на enum класс через isEnum().

Внутренний класс


Про внутренние классы информации очень много, как, что и с чем их едят. Но многие, кого я собеседовал не могли ответить на 2 вопроса. Прежде обратимся к примеру:

Внутренний внутренний класс
// Файл InnerClassExample.java
public class InnerClassExample {
    private int myField;
    public class InnerClass {
        private int myField;
        public class InnerInnerClass {
            private int myField;
            public InnerInnerClass() {
            }
        }
    }
}
// Файл InnerClassCreate.java
public class InnerClassCreate {
    public static void main(String[] args) {
    }
}

И первый вопрос: 'Как получить доступ к полю myField класса InnerClassExample в конструкторе класса InnerInnerClass и возможно ли это?' Второй вопрос: 'Как создать экземпляр класса InnerInnerClass в методе main класса InnerClassCreate?

Ответ
public class InnerClassExample {
    private int myField;
    public class InnerClass {
        private int myField;
        public class InnerInnerClass {
            private int myField;
            public InnerInnerClass() {
                int mf1 = InnerClassExample.this.myField; // Ответ: да.
                // Еще интересные примеры:
                int mf2_0 = InnerClass.this.myField;
                int mf2_1 = InnerClassExample.InnerClass.this.myField; // лапша? Но необычно.
                int mf3_0 = InnerInnerClass.this.myField;
                int mf4_1 = InnerClassExample.InnerClass.InnerInnerClass.this.myField; // лапша? Но необычно.
            }
        }
    }
}
public class InnerClassCreate {
    public static void main(String[] args) {
        // 1. Заметили, что анализатор кода на хабре, второй и третий new не подсветил
        InnerInnerClass one = new InnerClassExample().new InnerClass().new InnerInnerClass();
        // 2
        InnerClass innerClass = new InnerClassExample().new InnerClass();
        InnerInnerClass two = innerClass.new InnerInnerClass();
        // 3
        InnerInnerClass three = getInnerClass().new InnerInnerClass();
    }

    private static final InnerClass getInnerClass() {
        return new InnerClassExample().new InnerClass();
    }
}

С примерами кода пожалуй и все.

Статистика по классам


Я собрал статистику по некоторым типам и видам классов в rt.jar из jdk1.7.0_60 под Mac Os. И данные такие
Описание класса Количество
Локальные 21
Аннотации 137
Перечисления 278
Внутренние (не статические) 1482
Абстрактные 1560
Анонимные 2230
Интерфейсы 2352
Вложенные статические 3222
Обычные 12943
Всего у меня под анализ попало 19898 классов. Спасибо за внимание и приятного Вам времени препровождения.
Поделиться с друзьями
-->

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


  1. Ch4r1k
    02.02.2017 14:44
    +1

    Позвольте спросить, а на кого ориентирована эта статья?


    1. breakoffbrain
      02.02.2017 16:10
      +3

      Люди почему-то перед тем, как что-то написать, не думают: а надо ли?


      1. breakoffbrain
        02.02.2017 16:27
        -2

        теперь тут минусуют за то, что людям непонятно для чего эта статья?


      1. Ch4r1k
        03.02.2017 07:07
        +2

        Вы не поверите, но, если мне было бы не интересно, я бы и не спрашивал, серьезно.


    1. zip_zero
      02.02.2017 16:15

      (deleted)


      1. msts2017
        02.02.2017 17:01
        -1

        ага испугался!


    1. vladimir_dolzhenko
      02.02.2017 19:03
      +1

      Статью прочитал, но согласен с автором предыдущего комментария


  1. remal
    02.02.2017 18:36
    +1

    Java 7? в 2017 году?


    1. Vogr
      02.02.2017 18:42
      +4

      "В свое время...", т.е было давно.


  1. vladimir_dolzhenko
    02.02.2017 19:01
    +5

    Делать класс абстрактным с private конструктором — малость странно — как по мне так более удачная практика, так это делать подобный класс final

    см. java.util.Objects или великий и могучий sun.misc.Unsafe


    1. reforms
      02.02.2017 21:30
      +1

      Поддерживаю. +1

      История хоть и вымышленная, но родилась не на пустом месте. Лет nth назад я повстречал человека, который утверждал, что final+private constructor не тоже самое, что abstract+private constructor. И его основной аргумент был в том, что через рефлекш, так просто создать объект класса с модификатором abstract не получится, в отличии от final, хотя при этом признавал не эстетичность конструкции. Но вот сколько лет я уже мучаюсь вопросом: 'А зачем?'. Может есть у кого хоть какие-то гипотезы?


      1. mayorovp
        02.02.2017 21:36
        +2

        Создание через рефлексию фиксится конструкцией throw в конструкторе.


        А организовать такое можно и случайно, настраивая какой-нибудь IoC-контейнер.


        1. reforms
          02.02.2017 22:24

          Создание через рефлексию фиксится конструкцией throw в конструкторе.

          Я так понимаю, у парней, которые сражаются за свой стиль abstract VS final есть 2 ключевых позиции: краткость и защищенность от.... Во вторую позицию Ваше предложение отлично вписывается, но вот в первую.


          1. vladimir_dolzhenko
            02.02.2017 23:04
            +1

            да ну фигня какая — если очень уж хочется — то можно и отнаследоваться от final класса, и сделать abstract не abstract — но вопрос — таки зачем?

            мне как-то ближе традиционные практики как в том же Objects — и final, и private конструктор, и исключение в нём.


            1. apanasevich
              03.02.2017 15:43

              мне как-то ближе традиционные практики как в том же Objects — и final, и private конструктор, и исключение в нём.


              Простите за рекламу, но посмотрите мой комент ниже ) Он гораздо компактнее и джава гарантирует, что скажет рефлекшену «Давай, досвиданья».

              http://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.9

              An enum type has no instances other than those defined by its enum constants. It is a compile-time error to attempt to explicitly instantiate an enum type (§15.9.1).

              In addition to the compile-time error, three further mechanisms ensure that no instances of an enum type exist beyond those defined by its enum constants:

              The final clone method in Enum ensures that enum constants can never be cloned.

              Reflective instantiation of enum types is prohibited.

              Special treatment by the serialization mechanism ensures that duplicate instances are never created as a result of deserialization.



              1. vladimir_dolzhenko
                03.02.2017 16:28

                конечно же знаю этот приём, и читал ваш комментарий — но мне он не по нраву, не знаю почему, но не по нраву — наверно всё же потому, что привык utility-методы держать в классе, а не в enum-е, хотя это конечно же тоже класс.

                вопрос привычки.


          1. mayorovp
            03.02.2017 16:45
            -1

            Нужна краткость — переходите на C#. Нам давным-давно статические классы завезли! (static class в C# = abstract final class в Java)


  1. apanasevich
    02.02.2017 21:31
    +3

    public enum Utils {
        ;
    
        /**несколько методов, для экономии места не привожу*/
    }
    


    Потому что можем.


  1. vyatsek
    03.02.2017 11:11

    Чем собирали аналитику?


  1. reforms
    03.02.2017 11:16

    Свое решение.