Class’ные Class’ы

в 10:20, , рубрики: class, java, класс, Локальный класс, метки:

Вводная

Наверное, 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 классов.
Спасибо за внимание и приятного Вам времени препровождения.

Автор: reforms

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js