Pull to refresh

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

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

Вот уже подоспела и вторая часть статьи.


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 не вводит в заблуждение.

Если вам статья понравилась — продолжение следует...
Tags: javatipstricks
Hubs: Java
Total votes 163: ↑150 and ↓13 +137
Comments 75
Comments Comments 75

Popular right now