Вводная
Наверное, 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 |