зачем нужны блоки инициализации java

Собеседование по Java — ООП (вопросы и ответы). Часть 2

Вторая часть ответов и вопросов для собеседования по ООП в Java.

Вопросы. Часть 2

21. Зачем нужен оператор instanceof?
22. Зачем нужны и какие бывают блоки инициализации?
23. Каков порядок вызова конструкторов и блоков инициализации двух классов: потомка и его предка?
24. Где и для чего используется модификатор abstract?
25. Можно ли объявить метод абстрактным и статическим одновременно?
26. Что означает ключевое слово static?
27. К каким конструкциям Java применим модификатор static?
28. Что будет, если в static блоке кода возникнет исключительная ситуация?
29. Можно ли перегрузить static метод?
30. Что такое статический класс, какие особенности его использования?
31. Какие особенности инициализации final static переменных?
32. Как влияет модификатор static на класс/метод/поле?
33. О чем говорит ключевое слово final?
34. Дайте определение понятию “интерфейс”.
35. Какие модификаторы по умолчанию имеют поля и методы интерфейсов?
36. Почему нельзя объявить метод интерфейса с модификатором final или static?
37. Какие типы классов бывают в java (вложенные… и.т.д.)
38. Какие особенности создания вложенных классов: простых и статических.
39. Что вы знаете о вложенных классах, зачем они используются? Классификация, варианты использования, о нарушении инкапсуляции.
40. В чем разница вложенных и внутренних классов?
41. Какие классы называются анонимными?
42. Каким образом из вложенного класса получить доступ к полю внешнего класса?

Ответы. Часть 2

21. Зачем нужен оператор instanceof?

Оператор instanceof возвращает true, если объект является экземпляром класса или его потомком.

Источник

Блоки статической и объектной инициализации

Давайте начнем со следующего примера. Программа SmallSquares (маленькие квадраты) возвращает квадрат маленького целого числа. SmallSquares имеет 2 статические переменные и единственную открытую статическую функцию getSquare().

public class SmallSquares <

private static final int LIMIT = 10 ;
private static final int [] square = new int [ LIMIT ] ;

public SmallSquares () < // не пишите такой код
for ( int i = 0 ; i ) <
square [ i ] = i * i;
>
>
>
public static int getSquare ( int i ) <
// Нет обработки ошибки, предположим, 0 return square [ i ] ;
>

public static void main ( String [] args ) <
new SmallSquares () ;
System.out.println ( «3 squared is » +
getSquare ( 3 )) ;
>
>

Откомпилируйте и запустите SmallSquares, вы должны получить следующий результат:

Как вы наверное догадались из комментария программы, это действительно плохой код. Мы игнорируем недостаток границ проверки аргумента getSquare(). Также игнорируем тот факт, что индексация в очереди чуть ли не дороже, чем простое возведение в квадрат числа. Отложим эти факты в сторону, сконцентрируемся на неэкономном создании объекта, называемом статическим методом.

А ещё лучше использовать статическую инициализацию. За словом статический (static) следует блок кода, окруженного фигурными скобками. Вы можете использовать статический блок для инициализации массива квадратов вот так:

Поставьте этот блок в код программы SmallSquare после объявления квадрата. Из-за статичности блок запрашивается единожды, когда создается класс. Теперь вам не нужен конструктор, и вы можете вызывать статическую функцию getSquare() без предшествующего создания класса. Вот улучшенный код:

public class SmallSquares <

private static final int LIMIT = 10 ;
private static final int [] square = new int [ LIMIT ] ;

static <
for ( int i = 0 ; i ) <
square [ i ] = i * i;
>
>

public static int getSquare ( int i ) <
// Нет обработки ошибки, предположим,0 return square [ i ] ;
>

public static void main ( String [] args ) <
System.out.println ( «3 squared is » + getSquare ( 3 )) ;
>
>

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

public class ConstructorExample <
private final String userName;
private final static int [] square = new int [ 10 ] ;

public ConstructorExample () < // так не следует писать
this ( «Anonymous» ) ;
>

Откомпилируйте и запустите ConstructorExample. Вы должны получить следующий результат:

Пример конструктора можно привести в порядок, переместив поле инициализатора для имени пользователя (userName) и введя следующий блок инициализатора:

Данный блок инициализаторов выглядит как блок статического инициализатора без статического ключевого слова. Он запускается перед тем, как вызвать конструктор. Это значит, что вначале квадрат инициализируется не правильно в зависимости от того, вызывает ли пользователь конструктор без аргумента или использует сигнатуру, требующую строку (String). Отметить, что если у вас есть другой конструктор, который устанавливает размер массива, вы не сможете применять этот метод. Потому что данный инициализатор будет вызван, чтобы инициализировать квадрат, перед тем, как будет прочитан размер массива.

Вы можете разделить конструкторы в примере (ConstructorExample), передвинув следующие строки от конструктора без аргумента:

В следующей программе появляется пустой конструктор, ConstructorExample2 (пример конструктора 2), чтобы продемонстрировать введение данного блока инициализации.

public class ConstructorExample2 <

private final String userName;
private static final int [] square = new int [ 10 ] ;
<
for ( int i = 0 ; i 10 ; i++ ) <
square [ i ] = i * i;
>
>

public ConstructorExample2 () <
userName = «Anonymous» ;
>

public void printSquare ( int i ) <
// Нет обработки ошибки, предположим,0 System.out.println ( «Hello » + userName ) ;
System.out.println ( i + » squared is » + square [ i ]) ;
>

В примере AnonymousExample используется функция createAnonSquare(). Эта функция создает безымянный внутренний класс, который распространяется на AnonymousSquare (безымянный квадрат). Этот пример не более чем инициализация переменных userName и Ed.

Безымянный класс наследует квадрат и функцию print(). Это значит, что вы можете создавать AnonymousSquare (Безымянный квадрат) и вызывать print(). Вы должны получить «Hi Ed, 3 squared is 9.» (Привет Эд, 3 в квадрате будет 9).

static class AnonymousSquare
private static final int [] square = new int [ 10 ] ;

<
for ( int i = 0 ; i 10 ; i++ )
square [ i ] = i * i;
>

String userName;
int i;

void print () <
System.out.println ( «Hi » + userName + «, » + i
+ » squared is » + square [ i ] + ‘.’
) ;
>
>

static AnonymousSquare createAnonSquare () <
return new AnonymousSquare () <
<
userName = «Ed» ;
i = 3 ;
>
> ;
>

Однажды, попробовав работать с блоками инициализации, вы возможно найдёте для них много приложений. Данные инициализаторы лучше всего подходят к безымянным внутренним классам и не часто используются в обычных Java-классах. Вы убедитесь, что статические инициализаторы очень удобные.

Источник

5 скрытых cекретов в Java

Привет, Хабр! Представляю вашему вниманию перевод статьи «5 Hidden Secrets in Java» автора Justin Albano.

Хотите стать джедаем Java? Раскройте древние секреты Java. Мы сосредоточимся на расширении аннотаций, инициализации, на комментариях и интерфейсах enum.

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

Читатель должен понимать, что не все эти функции на самом деле скрыты, просто они часто не используются в повседневном программировании. Некоторые из них могут быть очень полезны в подходящий момент, тогда как использование других – почти всегда плохая идея, и показаны они в этой статье, чтобы заинтересовать читателя (и возможно рассмешить его или ее). Читатель также должен сам принимать решение когда использовать функции, описанные в этой статье: «То, что это можно сделать, не означает, что это нужно делать».

1. Реализация аннотаций

Начиная с Java Development Kit (JDK) 5, аннотации являются неотъемлемой частью многих приложений и сред Java. В подавляющем большинстве случаев аннотации применяются к конструкциям, таким как классы, поля, методы и т.д. Однако их можно использовать и как реализуемые интерфейсы. Например, предположим, у нас есть следующее определение аннотации:

Обычно мы применяем эту аннотацию к методу, как показано ниже:

Затем мы можем обработать эту аннотацию, как это описано в Создание аннотаций в Java. Если бы мы также хотели создать интерфейс, позволяющий создавать тесты как объекты, нам пришлось бы создать новый интерфейс, назвав его чем-то другим, а не Test:

Далее мы можем создать экземпляр объекта TestInstance:

Хотя наша аннотация и интерфейс практически идентичны, с очень заметным дублированием, похоже что нет способа объединить эти две конструкции. К счастью, внешность обманчива, и есть метод для объединения этих двух конструкций: Реализация аннотации:

Обратите внимание, что мы должны реализовать метод annotationType и также возвращать тип аннотации, поскольку это неявная часть интерфейса Annotation. Хотя почти во всех случаях реализация аннотации не являются правильным решением для проектирования (компилятор Java будет показывать предупреждение при реализации интерфейса), это может быть полезно в некоторых случаях, например в annotation-driven framework.

2. Нестатические блоки инициализации.

В Java, как и в большинстве объектно-ориентированных языков программирования, объекты создаются исключительно с использованием конструктора (с некоторыми исключениями, такими как десериализация объектов Java). Даже когда мы создаем статические фабричные методы для создания объектов, мы просто заключаем вызов в конструктор объекта, чтобы создать его экземпляр. Например:

Поэтому, когда мы хотим инициализировать объект, мы объединяем логику инициализации в конструкторе объекта. Например, устанавливаем поле name класса Foo в его параметризованном конструкторе. Хотя может показаться обоснованным предположение, что вся логика инициализации находится в конструкторе или наборе конструкторов для класса, в Java это не так. Вместо этого мы можем использовать нестатические блоки инициализации чтобы выполнить код при создании объекта:

Нестатические блоки инициализации указываются путем добавления логики инициализации в набор фигурных скобок в определение класса. Когда объект создается, сначала вызываются нестатические блоки инициализации, а затем конструкторы объекта. Обратите внимание, что можно указать более одного нестатического блока инициализации, и в этом случае каждый вызывается в том порядке, в котором он указан в определении класса. Помимо нестатических блоков инициализации, мы также можем создавать и статические, которые выполняются когда класс загружается в память. Чтобы создать статический блок инициализации, мы просто добавляем ключевое слово static:

Когда в классе присутствуют все три метода инициализации (конструкторы, нестатические блоки инициализации и статические блоки инициализации), статические всегда выполняются первыми (когда класс загружается в память) в порядке их объявления, затем выполняются нестатические блоки инициализации в порядке, в котором они объявлены, а после них – конструкторы. Когда вводится суперкласс, порядок выполнения немного меняется:

Если мы выполним этот код, то получим следующий вывод:

Обратите внимание на то что статические блоки инициализации были выполнены только один раз, даже если были созданы два объекта Foo. Хотя нестатистические и статические блоки инициализации могут быть полезны, логика инициализации должна быть помещена в конструкторы, а методы (или статические методы) должны использоваться в случаях когда сложная логика требует инициализации состояния объекта.

3. Двойная скобка инициализации

Многие языки программирования включают в себя некоторый синтаксический механизм для быстрого и краткого создания списка или карты (или словаря) без использования подробного шаблонного кода. Например, C ++ включает в себя инициализацию скобок, которая позволяет разработчикам быстро создавать список перечисляемых значений или даже инициализировать целые объекты, если конструктор для объекта поддерживает эту функцию. К сожалению, до JDK 9 такая функция не была реализована (об этом позже). Чтобы просто создать список объектов, мы бы сделали следующее:

Хотя это и выполняет нашу цель по созданию нового списка, инициализированного тремя значениями, это слишком многословно, требуя от разработчика повторения имени переменной списка для каждого добавления. Чтобы сократить этот код, мы можем использовать двойную инициализацию скобок:

Инициализация с двойной скобкой, которая получила свое название от набора двух открытых и закрытых фигурных скобок, на самом деле представляет собой совокупность нескольких синтаксических элементов. Сначала мы создаем анонимный внутренний класс, который расширяет класс ArrayList. Поскольку ArrayList не имеет абстрактных методов, мы можем создать пустое тело для анонимной реализации:

Используя этот код, мы по существу создаем анонимный подкласс, ArrayList – точно такой же, как и оригинальный ArrayList. Одно из основных отличий в том, что наш внутренний класс имеет неявную ссылку на содержащий класс (в форме захваченной this переменной), т.к. мы создаем нестатический внутренний класс. Это позволяет нам написать некоторую интересную, если не запутанную логику. Например, добавление этой переменной к анонимному внутреннему классу, инициализированному двойной скобкой:

Если бы этот внутренний класс был определен как статический, у нас не было бы доступа к Foo.this. Например, следующий код, который создает статический FooArrayList внутренний класс, не имеет доступа к Foo.this ссылке и поэтому он не компилируется:

Возобновляя конструкцию нашей инициализированной двойной скобкой ArrayList, как только был создан нестатический внутренний класс, мы используем нестатические блоки инициализации, как описано выше, чтобы добавить три начальных элемента, при создании экземпляра анонимного внутреннего класса. Когда анонимный внутренний класс создан и когда существует только один объект анонимного внутреннего класса, мы можем сказать что создали нестатический внутренний объект, который добавляет три начальных элемента при его создании. Это будет видно, если мы разделим пару фигурных скобок, где одна фигурная скобка представляет собой определение анонимного внутреннего класса, а другая обозначает начало логики инициализации экземпляра:

Хотя этот трюк может быть полезен, JDK 9 (JEP 269) заменил полезность этого трюка набором статических фабричных методов для List (а также многих других типов коллекций). Например, мы могли бы создать List раньше, используя эти статические фабричные методы, как показано далее:

Эта статическая фабричная техника используется по двум основным причинам: (1) не создается анонимный внутренний класс и (2) для сокращения стандартного кода, необходимого для создания List. Следует помнить что в таком случае полученный результат List является неизменным и не может быть изменен после его создания. Чтобы создать изменяемый List файл с любыми начальными элементами, нам приходится использовать обычный метод или метод с двойной скобкой инициализации.

Обратите внимание что простая инициализация, двойная скобка и статические фабричные методы JDK 9 не просто доступны для List. Они доступны для Set и Map объектов, как показано в следующем фрагменте:

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

4. Исполняемые комментарии

Комментарии являются неотъемлемой частью почти каждой программы, и основное преимущество комментариев заключается в том, что они не выполняются. Это становится еще более очевидным, когда мы закомментируем строку кода в нашей программе: мы хотим сохранить код в нашем приложении, но не хотим, чтобы он выполнялся. Например, следующая программа в результате выводит «5»:

Многие думают что комментарии никогда не выполняются, но это не совсем верно. Например, что выведет следующий фрагмент кода?

Вы могли предположить что это снова 5, но если мы запустим приведенный выше код, то увидим 8 на выходе. Причиной этой «ошибки» является символ Unicode \u000d; этот символ на самом деле является возвратом каретки Unicode, и исходный код Java используется компилятором как текстовые файлы в формате Unicode. Его добавление в код присваивает значению value = 8 в строке, идущей за комментарием, обеспечивая его выполнение. Это означает, что приведенный выше фрагмент кода фактически равен следующему:

Хотя это кажется ошибкой Java, на самом деле это специально добавленная функция в язык. Первоначальная цель состояла в том, чтобы создать независимый от платформы язык (отсюда создание виртуальной машины Java или JVM), и функциональная совместимость исходного кода является ключевым аспектом этой цели. Позволяя исходному коду Java содержать символы Unicode, мы можем использовать нелатинские символы универсальным способом. Это гарантирует, что код, написанный в одном регионе мира (который может содержать нелатинские символы, например в комментариях), может быть выполнен в любом другом. Для получения дополнительной информации см. Раздел 3.3 Спецификации языка Java или JLS.

Если поместить вышеупомянутый код в файл с именем Ugly.java и запустить его, то будет напечатано Hello world на стандартном выходе. Если мы преобразуем эти символы Юникода в символы Американского Стандартного Кода для Обмена Информацией (ASCII), то получим следующую программу:

Итак, символы Unicode могут быть включены в исходный код Java, однако если они не требуется настоятельно не рекомендуется их использовать (например, включать нелатинские символы в комментарии). Если они все же требуются, убедитесь, что они не включают символы, такие как возврат каретки, которые изменяют ожидаемое поведение исходного кода.

5. Реализация интерфейса Enum

Одним из ограничений enums(списка перечислений) по сравнению с другими классами в Java является то, что перечисления не могут расширять другой класс или сами enums. Например, невозможно выполнить следующее:

Однако мы можем заставить наш enums реализовывать интерфейс и обеспечить реализацию для его абстрактных методов следующим образом:

Теперь мы можем использовать экземпляр Person везде, где требуется Speaker объект. Более того, мы также можем обеспечить реализацию абстрактных методов интерфейса на постоянной основе (так называемые методы, специфичные для констант):

В отличие от некоторых других секретов в этой статье, эту технику следует использовать только там, где это необходимо. Например, если enum константа, такая как JOE или JIM, может использоваться вместо интерфейса, такого как Speaker, то enum определяющее константу, должен реализовывать этот тип интерфейса. Для получения дополнительной информации см. Пункт 38 (стр. 176-9) Effective Java, 3rd Edition.

Источник

Блоки инициализации в Java

Что такое инициализация

Например, у нас есть переменная:

Мы получим ошибку «переменная не инициализирована»:

зачем нужны блоки инициализации java. kM9B Thbob0. зачем нужны блоки инициализации java фото. зачем нужны блоки инициализации java-kM9B Thbob0. картинка зачем нужны блоки инициализации java. картинка kM9B Thbob0.

зачем нужны блоки инициализации java. dYHEiBM4OT4. зачем нужны блоки инициализации java фото. зачем нужны блоки инициализации java-dYHEiBM4OT4. картинка зачем нужны блоки инициализации java. картинка dYHEiBM4OT4.

зачем нужны блоки инициализации java. 2HfIISDwMv8. зачем нужны блоки инициализации java фото. зачем нужны блоки инициализации java-2HfIISDwMv8. картинка зачем нужны блоки инициализации java. картинка 2HfIISDwMv8.

Привычные способы инициализации

зачем нужны блоки инициализации java. ZZVrdoQohgU. зачем нужны блоки инициализации java фото. зачем нужны блоки инициализации java-ZZVrdoQohgU. картинка зачем нужны блоки инициализации java. картинка ZZVrdoQohgU.

или, например, в цикле:

зачем нужны блоки инициализации java. Tp0ogCedwYs. зачем нужны блоки инициализации java фото. зачем нужны блоки инициализации java-Tp0ogCedwYs. картинка зачем нужны блоки инициализации java. картинка Tp0ogCedwYs.

Внутри класса, мы можем это делать так:

зачем нужны блоки инициализации java. zfOBm MIfVE. зачем нужны блоки инициализации java фото. зачем нужны блоки инициализации java-zfOBm MIfVE. картинка зачем нужны блоки инициализации java. картинка zfOBm MIfVE.

Инициализация с помощью блоков

Представим, что мы хотим задать базовые значения переменных. Можно сделать это так:

А можно использовать блок инициализации:

зачем нужны блоки инициализации java. . зачем нужны блоки инициализации java фото. зачем нужны блоки инициализации java-. картинка зачем нужны блоки инициализации java. картинка .

Выглядит лучше, правда? 🙂

Давайте посмотрим какие существуют блоки инициализации.

Виды блоков инициализации

Существует всего два типа блоков:

Как можно догадаться по названию:

Зачем используются блоки инициализации

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

Теперь в нашем блоке инициализации есть только System.out.println(«Это нестатический блок инициализации!»). Запустим его со следующим main-ом:

И получим в консоли:

зачем нужны блоки инициализации java. lUJcUPn61g4. зачем нужны блоки инициализации java фото. зачем нужны блоки инициализации java-lUJcUPn61g4. картинка зачем нужны блоки инициализации java. картинка lUJcUPn61g4.

Как видите, работает. Если бы мы просто написали System.out.println, без блока инициализации, программа бы не запустилась:

зачем нужны блоки инициализации java. jFpa4vaasDQ. зачем нужны блоки инициализации java фото. зачем нужны блоки инициализации java-jFpa4vaasDQ. картинка зачем нужны блоки инициализации java. картинка jFpa4vaasDQ.

Как видите, такое можно делать только внутри блока инициализации. Это особенно полезно если нам нужно производить какие-нибудь вычисления перед инициализацией переменных.

Подводим итог:

или для статического:

Источник

Малоизвестные особенности Java

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

1. Нестатические блоки инициализации.

Всем, я думаю, известно, что в Java существуют статические блоки инициализации (class initializers), код которых выполняется при первой загрузке класса.

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

Такой метод инициализации весьма полезен для анонимных внутренних классов, которые конструкторов иметь не могут. Кроме того, вопреки ограничению синтаксиса Java, используя их, мы можем элегантно инициализировать коллекцию:

Очень даже мощное средство, не находите?

Остальные четыре пункта под катом.

2. Вложенные в интерфейсы классы.

Вложенный (nested) в интерфейс класс является открытым (public) и статическим (static) даже без явного указания этих модификаторов. Помещая класс внутрь интерфейса, мы показываем, что он является неотъемлемой частью API этого интерфейса и более нигде не используется.

Поскольку такой класс является статическим, мы можем создать его экземпляр, не ссылаясь на объект объемлющего класса, а лишь указывая тип внешнего интерфейса или реализующего его класса.

3. Коварианты возвращаемых типов.

Начиная с Java SE 5 типы возвращаемых результатов из методов ковариантны (covariant). Это означает, что мы можем в перекрытом методе (overriden) в качестве типа результата использовать подтип результата перекрываемого метода.

Метод Object.clone() имеет такую сигнатуру:

Заметьте, возвращаемый тип изменён с Object на Covariance. Теперь, к примеру, нет нужды приводить результат работы метода clone() к действительному типу объекта, как это требовалось в ранних версиях JDK. Вместо этого кода:

Мы можем смело писать код следующий:

4. Выход из любого блока операторов.

Хотя goto и является зарезервированным ключевым словом Java, использовать его в своих программах нельзя. Пока? На смену ему пришли операторы break и continue, позволяющие прерывать и продолжать (соответственно) не только текущий цикл, но также и любой обрамляющий цикл, обозначенный меткой:

Но многие даже не догадываются, что в Java мы всё же можем при помощи оператора break не только прервать цикл, но и покинуть совершенно любой блок операторов. Чем не оператор goto, правда, односторонний? Как говорится, вперёд и ни шагу назад.

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

5. Модификация данных из внутренних классов.

Хотя в Java и предусмотрено ключевое слово final, однако на деле отсутствует возможность задать неизменяемость самого объекта, а не указывающей на него ссылки (не относится к примитивам). Ну, в принципе, можно спроектировать неизменяемый (immutable) класс, предоставив только геттеры и чистые функции, но нельзя, к примеру, создать неизменяемый массив. Это, как мне кажется, существенное упущение в дизайне языка. Тут бы пригодилось зарезервированное, но запрещённое ключевое слово const. Ждём в следующих версиях?

Таким образом, мы можем модиицировать хотя и финализированные, но фактически изменямые данные, будь то массивы либо другие неперсистентные объекты даже из контекста внутренних (inner) классов. Со строками и оболочками примитивных типов, к сожалению, такой фокус не пройдёт. Пусть вас ключевое слово final не вводит в заблуждение.

Если вам статья понравилась — продолжение следует.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *