
Приветствую, уважаемый читатель.
Java — интересный и красивый язык. Но иногда на нем можно написать такое, что лучше этого не видеть. Но все равно полезно знать, что происходит и в таких, кривых, случаях.
Любителей чистого кода прошу меня извинить.
Когда-то давно на хабре была пара статей
Часть 1, Часть 2
Они очень интересные, но, к сожалению, автор не стал продолжать.
Представляю вашему вниманию еще 2 задачки (на большее не хватило сил. Оказывается, писать статьи не так-то просто.)
В конце статьи, разумеется, будут ответы с разъяснениями, а также дополнительные задания для самых сильных.
Первая задача взята подчистую из замечательной книги Джошуа Блоха Java Puzzlers. Книга не научит вас, как писать код (скорее наоборот, как не надо и почему), но лично мне было дико интересно это читать. Просто для развлечения. Рекомендую.
Итак, приступим.
Условия
1. Казнить нельзя помиловать
public class A {
public static class X {
public static class Y {
public static String Z = "life is good";
}
public static C Y;
}
public static class C {
public static String Z = "life is pain";
}
public static void main(String[] args) {
System.out.println(X.Y.Z);
}
}
Что произойдет?
- Compile error
- Runtime error
- Выведет life is good
- Выведет life is pain
2. Дженерики такие дженерики
public class B {
public static <T> T foo() {
try {
return (T) new Integer(42);
} catch (ClassCastException e) {
return (T) "habr";
}
}
public static void main(String[] args) {
System.out.println(B.<String>foo());
}
}
Что произойдет?
- Compile error
- Runtime error
- Выведет 42
- Выведет habr
А теперь правильные ответы:
1. Казнить нельзя помиловать
public class A {
public static class X {
public static class Y {
public static String Z = "life is good";
}
public static C Y;
}
public static class C {
public static String Z = "life is pain";
}
public static void main(String[] args) {
System.out.println(X.Y.Z);
}
}
Выведет life is painТут все дико, но просто. Да, jls такое позволяет. Приоритет всегда у поля.
Гораздо интереснее — как это обойти и вывести-таки life is good? Мне известны три решения:
- reflection api
- импорт X.Y
- К статике можно обращаться через экземпляр: (new X.Y()).Z
Задание на пятерку
Последний способ хорош, но требует создания объекта, что плохо. Добавим приватный конструктор.А теперь слабо? (без рефлексии и статического импорта)
public static class X {
public static class Y {
private Y() {}
public static String Z = "life is good";
}
public static C Y;
}
Ответ:Скрытый текст
((X.Y)null).Z; // подберите свои челюсти, это Java.
2. Дженерики такие дженерики
public class B {
public static <T> T foo() {
try {
return (T) new Integer(42);
} catch (ClassCastException e) {
return (T) "habr";
}
}
public static void main(String[] args) {
System.out.println(B.<String>foo());
}
}
Runtime error, а если точнее, то ClassCastExceptionИтак, все мы знаем, что в java дженерики — не более чем синтаксический сахар,
Посмотрим внимательно на код:
System.out.println(B.<String>foo());
Компилятор понимает, что тип аргумента — String, а следовательно подставляет наиболее подходящий println:
public void println(String x)
Важно, что определять, какую версию перегруженного метода использовать — задача уровня компиляции, не рантайма.
Идем дальше.
return (T) new Integer(42);
Cast происходит в рантайме, в тот момент, когда дженериков уже «нет».
Что произойдет?
Ничего. Просто вернется Integer.
Ну и приехали — вызвали printf, принимающий String, а передали ему Integer.
Уроки на будущее
- Не надо так делать
- В мире есть не только println, принимающий Object
- Каст к типу дженерика — это костыль
Задание на пятерку
Реализуйте метод, бросающий произвольный Throwable (в том числе, checked exception), не требующий ни thows, ни try-catch:
public static void throwWithoutCheck(Throwable t) {
// Никаких проверок, только хардкор. Хочу throwWithoutCheck(new Exception()) - и никаких throws!
}
Ответ:Скрытый текст
UPD:
Есть еще способы кинуть эксепшн без проверок. Они не подразумевались в качестве ответа, но, скажем так, более «честные»:
Обратите внимание, что stop — @deprecated. Концепция работы с потоками претерпела значительные изменения,трава стала зеленее, а воздух чище.
В качестве аргумента Stop можно передать «причину» остановки. Понятное дело, что на джаве в принципе нельзя заключить это в try catch блок, надеюсь, тут все ясно.
Спасибо orionll за пример.
Или:
Тут все тоже понятно, просто так с Unsafe не поработать и, в данном случае, все совершенно оправданно.
Спасибо mishadoff за пример.
private static <T extends Throwable> void castAndThrow(Throwable t) throws T {
throw (T) t;
}
public static void throwWithoutCheck(Throwable t) {
B.<RuntimeException>castAndThrow(t);
}
UPD:
Есть еще способы кинуть эксепшн без проверок. Они не подразумевались в качестве ответа, но, скажем так, более «честные»:
Thread.currentThread().stop(new IOException());
Обратите внимание, что stop — @deprecated. Концепция работы с потоками претерпела значительные изменения,
В качестве аргумента Stop можно передать «причину» остановки. Понятное дело, что на джаве в принципе нельзя заключить это в try catch блок, надеюсь, тут все ясно.
Спасибо orionll за пример.
Или:
Unsafe.getUnsafe().throwException(new IOException());
Тут все тоже понятно, просто так с Unsafe не поработать и, в данном случае, все совершенно оправданно.
Спасибо mishadoff за пример.