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