Pull to refresh

2 «простых» вопроса по джаве

Reading time 3 min
Views 64K

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

Когда-то давно на хабре была пара статей как не надо писать на java с интересными задачками по 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);
    }
}

Что произойдет?
  1. Compile error
  2. Runtime error
  3. Выведет life is good
  4. Выведет life is pain
Кроме этого как, не меняя имен, [исправить ошибку, если ответ 1/2 и] вывести обе строчки?



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());
    }
}

Что произойдет?
  1. Compile error
  2. Runtime error
  3. Выведет 42
  4. Выведет 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 дженерики — не более чем синтаксический сахар, ограничивающий наши возможности позволяющий компилятору выполнять дополнительные проверки типов. Но как только программа запущена — вся информация о классах-параметрах теряется. Увы, это не c++ и даже не c#.

Посмотрим внимательно на код:

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!
}
Ответ:
Скрытый текст
    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 за пример.
Tags:
Hubs:
+25
Comments 52
Comments Comments 52

Articles