Готовясь к собеседованию, я решил освежить память да и вообще поискать каверзные и малоизвестные нюансы языка Java. Выборку пяти наиболее интересных на мой взгляд моментов я вам и предлагаю.
Вот уже подоспела и вторая часть статьи.
1. Нестатические блоки инициализации.
Всем, я думаю, известно, что в Java существуют статические блоки инициализации (class initializers), код которых выполняется при первой загрузке класса.
Но существуют также и нестатические блоки инициализации (instance initializers). Они позволяют проводить инициализацию объектов вне зависимости от того, какой конструктор был вызван или, например, вести журналирование:
Такой метод инициализации весьма полезен для анонимных внутренних классов, которые конструкторов иметь не могут. Кроме того, вопреки ограничению синтаксиса Java, используя их, мы можем элегантно инициализировать коллекцию:
Очень даже мощное средство, не находите?
Остальные четыре пункта под катом.
2. Вложенные в интерфейсы классы.
Вложенный (nested) в интерфейс класс является открытым (public) и статическим (static) даже без явного указания этих модификаторов. Помещая класс внутрь интерфейса, мы показываем, что он является неотъемлемой частью API этого интерфейса и более нигде не используется.
Поскольку такой класс является статическим, мы можем создать его экземпляр, не ссылаясь на объект объемлющего класса, а лишь указывая тип внешнего интерфейса или реализующего его класса.
Самым, наверное, известным примером этой идиомы является класс Map.Entry<K, V>, содержащий пары ключ-значение ассоциативного словаря.
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 не вводит в заблуждение.
Если вам статья понравилась — продолжение следует...
Вот уже подоспела и вторая часть статьи.
1. Нестатические блоки инициализации.
Всем, я думаю, известно, что в Java существуют статические блоки инициализации (class initializers), код которых выполняется при первой загрузке класса.
class Foo {
static List<Character> abc;
static {
abc = new LinkedList<Character>();
for (char c = 'A'; c <= 'Z'; ++c) {
abc.add( c );
}
}
}
Но существуют также и нестатические блоки инициализации (instance initializers). Они позволяют проводить инициализацию объектов вне зависимости от того, какой конструктор был вызван или, например, вести журналирование:
class Bar {
{
System.out.println("Bar: новый экземпляр");
}
}
Такой метод инициализации весьма полезен для анонимных внутренних классов, которые конструкторов иметь не могут. Кроме того, вопреки ограничению синтаксиса Java, используя их, мы можем элегантно инициализировать коллекцию:
Map<String, String> map = new HashMap<String, String>() {{
put("паук", "арахнид");
put("птица", "архозавр");
put("кит", "зверь");
}};
Очень даже мощное средство, не находите?
JFrame frame = new JFrame() {{
add(new JPanel() {{
add(new JLabel("Хабрахабр?") {{
setBackground(Color.BLACK);
setForeground(Color.WHITE);
}});
add(new JButton("Торт!") {{
addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
System.out.println("Хабрахабр - торт!");
}
});
}});
}});
}};
Остальные четыре пункта под катом.
2. Вложенные в интерфейсы классы.
Вложенный (nested) в интерфейс класс является открытым (public) и статическим (static) даже без явного указания этих модификаторов. Помещая класс внутрь интерфейса, мы показываем, что он является неотъемлемой частью API этого интерфейса и более нигде не используется.
interface Colorable {
public Color getColor();
public static class Color {
private int red, green, blue;
Color(int red, int green, int blue) {
this.red = red;
this.green = green;
this.blue = blue;
}
int getRed() {
return red;
}
int getGreen() {
return green;
}
int getBlue() {
return blue;
}
}
}
class Triangle implements Colorable {
private Color color;
// ...
@Override
public Color getColor() {
return color;
}
}
Поскольку такой класс является статическим, мы можем создать его экземпляр, не ссылаясь на объект объемлющего класса, а лишь указывая тип внешнего интерфейса или реализующего его класса.
Colorable.Color color = new Colorable.Color(0, 0, 0);
color = new Triangle.Color(255, 255, 255);
Самым, наверное, известным примером этой идиомы является класс Map.Entry<K, V>, содержащий пары ключ-значение ассоциативного словаря.
3. Коварианты возвращаемых типов.
Начиная с Java SE 5 типы возвращаемых результатов из методов ковариантны (covariant). Это означает, что мы можем в перекрытом методе (overriden) в качестве типа результата использовать подтип результата перекрываемого метода.
class Covariance implements Cloneable {
@Override
public Covariance clone() {
Object cloned = null;
try {
cloned = super.clone();
} catch (CloneNotSupportedException exc) {
// В данном примере недостижимо.
}
return (Covariance)cloned;
}
}
Метод Object.clone() имеет такую сигнатуру:
protected Object clone()
Заметьте, возвращаемый тип изменён с Object на Covariance. Теперь, к примеру, нет нужды приводить результат работы метода clone() к действительному типу объекта, как это требовалось в ранних версиях JDK. Вместо этого кода:
Covariance foo = new Covariance();
Covariance bar = (Covariance)foo.clone();
Мы можем смело писать код следующий:
Covariance foo = new Covariance();
Covariance bar = foo.clone();
4. Выход из любого блока операторов.
Хотя goto и является зарезервированным ключевым словом Java, использовать его в своих программах нельзя. Пока? На смену ему пришли операторы break и continue, позволяющие прерывать и продолжать (соответственно) не только текущий цикл, но также и любой обрамляющий цикл, обозначенный меткой:
String a = "quadratic", b = "complexity";
boolean hasSame = false;
outer:
for (int i = 0; i < a.length(); ++i) {
for (int j = 0; j < b.length(); ++j) {
if (a.charAt(i) == b.charAt(j)) {
hasSame = true;
break outer;
}
}
}
System.out.println(hasSame);
Но многие даже не догадываются, что в Java мы всё же можем при помощи оператора break не только прервать цикл, но и покинуть совершенно любой блок операторов. Чем не оператор goto, правда, односторонний? Как говорится, вперёд и ни шагу назад.
long factorial(int n) {
long result = 1;
scope: {
if (n == 0) {
break scope;
}
result = n * factorial(n - 1);
}
return result;
}
Практическая ценность от таких прыжков весьма сомнительна и нарушает принципы структурного программирования, но знать о такой возможности, я думаю, стоит.
5. Модификация данных из внутренних классов.
Хотя в Java и предусмотрено ключевое слово final, однако на деле отсутствует возможность задать неизменяемость самого объекта, а не указывающей на него ссылки (не относится к примитивам). Ну, в принципе, можно спроектировать неизменяемый (immutable) класс, предоставив только геттеры и чистые функции, но нельзя, к примеру, создать неизменяемый массив. Это, как мне кажется, существенное упущение в дизайне языка. Тут бы пригодилось зарезервированное, но запрещённое ключевое слово const. Ждём в следующих версиях?
final int[] array = {1, 2, 3, 4, 5};
new Object() {
void twice() {
for (int i = 0; i < array.length; ++i) {
array[i] *= 2;
}
}
}.twice();
Таким образом, мы можем модиицировать хотя и финализированные, но фактически изменямые данные, будь то массивы либо другие неперсистентные объекты даже из контекста внутренних (inner) классов. Со строками и оболочками примитивных типов, к сожалению, такой фокус не пройдёт. Пусть вас ключевое слово final не вводит в заблуждение.
Если вам статья понравилась — продолжение следует...