Java Guru 🤓
13.4K subscribers
933 photos
15 videos
788 links
Канал с вопросами и задачами с собеседований!

По сотрудничеству и рекламе: @NadikaKir

Канал в перечне РКН: https://vk.cc/cJrSQZ

Мы на бирже: telega.in/channels/javatasks/card?r=lcDuijdm
Download Telegram
Каким будет результат компиляции и выполнения следующего кода?
👍9
Каким будет результат компиляции и выполнения следующего кода?
Anonymous Quiz
27%
true
60%
false
12%
Ошибка при исполнении.
👍15🤔2
Как работает вывод типов?

Для
начала разберемся, что такое вывод типов. Type inference – это способность компилятора догадаться, какой тип нужно подставить, и сделать это за вас. На обычном интервью никто не спросит детали алгоритма вывода типов, достаточно будет сказать, что вывод происходит статически, только на основании типов аргументов и ожидаемого типа результата. По сути, вопрос заключается не в «как работает?», а «что это и когда возникает?».

Первое, что многим приходит в голову при фразе «вывод типов» – diamond operator <>. Он появился в Java с версии 7. Его применяют к конструкторам дженерик классов, чтобы отличать требование автоматического вывода типа от raw type.

С Java 9 diamond operator заработал и для анонимных классов.

Для дженерик методов можно указывать параметр явно, но diamond синтаксически недопустим – вывод и так сработает по умолчанию.

В Java 10 для вывода типа локальной переменной добавлено ключевое слово var. Работает это так же, как в большинстве современных языков – ключевое слово ставится вместо типа при объявлении.

Типы выводимых параметров лямбда-выражения также можно не указывать. С Java 11 вместо типа указывается ключевое слово var. Такой синтаксис дает возможность добавлять параметру модификаторы и аннотации.
👍15🔥3
Каким будет результат компиляции и выполнения следующего кода?
👍131
Каким будет результат компиляции и выполнения следующего кода?
Anonymous Quiz
52%
BABB
21%
ABBB
28%
Ошибка - StackOferflowError
👍143
Как инстанцировать экземпляр generic типа?

Внутри класса class Foo<T> на generic параметре T невозможно выполнить никакой оператор: нельзя взять его .class, нельзя применить его в instanceof. Также и вызов на нем оператора new приведет к ошибке.

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

Стандартный простой способ действия здесь – кроме значения типа T передавать еще и объект-дескриптор для этого типа, экземпляр класса Class<T>. Объект может быть создан из дескриптора рефлекшеном.

Но существует один хак, способный справиться со стиранием типов. Тип-параметр все-таки остается в одном месте в рантайме. Метод метакласса наследника определившего конкретный тип getGenericSuperclass() возвращает класс, которым параметризован родитель.
👍27🔥5❤‍🔥1
Каким будет результат компиляции и выполнения следующего кода?
👍9🌚5🔥1
👍10🥴5
Что означает ArrayStoreException?

Это исключение значит, что программа попыталась сохранить в массив значение неправильного типа. Такая попытка становится возможно из-за ковариантности массивов.

Ковариантность позволяет работать с массивом по типу массива родителей. Например, через приведение к Object[] можно попытаться положить любой объект в любой массив:

 Object x[] = new String[3];
x[0] = new Integer(0);

Компилятор гарантирует, что когда вы берете элемент из массива, он будет представителем типа элементов самого этого массива. Не важно какого типа переменная его хранит. Именно для обеспечения этой гарантии работает проверка типа времени выполнения, которая и выбрасывает ArrayStoreException.

Ситуация похожа на проблему heap pollution в случае дженериков. Только для этого случая такая проблема возникает реже, потому что работает проверка этапа компиляции:

// Ошибка компиляции – дженерики инвариантны!
List<Object> x = new ArrayList<String>();
👍21
Каким будет результат компиляции и выполнения следующего кода?
👍7
Можно ли выбрасывать исключение generic-типа?

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

Чтобы объявить, что метод выбрасывает исключение обобщенного типа T, этот тип T должен быть объявлен расширяющим Throwable. Именно в Throwable в таком случае сотрется T при компиляции. Также в качестве типа-верхней границы можно использовать любого наследника Throwable:

class MyClass<T extends IOException> {
void foo() throws T {
// ...
}
}
👍17
Дженерики в исключениях – что можно, а что нельзя?

1. Можно выбрасывать исключение generic-типа.
Тип-параметр T может использоваться в throws, переменная типа T может использоваться в throw. Недавно мы уже говорили об этом.

2. Нельзя использовать дженерик в catch.
Множественные блоки catch должны идти без повторений, в определенном порядке – от специфичного класса к более базовому. Стирание типов-параметров в связи с этими правилами добавило бы путаницу, не неся особой пользы.

3. Нельзя параметризовать класс-исключение типами.
Если вы попытаетесь скомпилировать конструкцию вида class MyException<T> extends Throwable {}, то увидете ошибку generic class may not extend java.lang.Throwable.

4. Можно реализовывать исключением generic-интерфейс.
Исключение вполне может быть например Comparable или Iterable. Механизм обработки исключений работает на классах, никак не затрагивая интерфейсы.
👍15
Что выведет код?
👍15
👍23🌚5👌3
Как ограничить upcasting типа-параметра?

Задача: запретить этому методу принимать параметры разных типов:

<T> void pair(T a, T b) {}

То есть, нужно разрешить вызывать pair(Foo, Foo), но запретить pair(Foo, Bar).

Upcasting – приведение к типу-родителю. String → Object, Integer → Number.

Дело в том, что у любых двух классов есть общий предок: как минимум Object. Если вызвать этот метод с параметрами String и Boolean – согласно правилам вычисления типа-границы, параметр T будет стерт в Object.

Использовать super тоже не поможет: для этого нужно знать заранее, какой именно тип будет передаваться.

Фокус в том, что на этапе компиляции это невозможно. Объект любого типа всегда является объектом типа-родителя (отношение is a). Это фундаментальное правило ООП, которое невозможно нарушить. К тому же, подобный метод нарушал бы принцип подстановки Лисков.

Единственная возможность добиться желаемого поведения – с помощью getClass() сравнивать классы объектов в рантайме.
👍26🥰1
Каким будет результат компиляции и выполнения следующего кода?
👍12
Каким будет результат компиляции и выполнения следующего кода?
Anonymous Quiz
42%
Вывод в консоль - 1
5%
Вывод в консоль - 2
43%
Ошибка во время компиляции
10%
Ошибка во время исполнения
👍22🌭3
Когда нужно использовать raw types?

Сначала вспомним, что такое raw type. В Java так называют generic-типы без указания типа-параметра. Такая языковая конструкция валидна, но в большинстве случаев приводит к предупреждению компилятора.

Предупреждение связано с риском получения проблемы heap pollution. Ей мы уже посвящали публикации
ранее. Использование raw types никогда не оправдано – спецификация языка явно говорит: их поддержка остается только для обратной совместимости.

Есть всего три случая, когда использовать обобщенный тип без параметра правильно:
• Целевая версия Java < 5.0 (2002 год и ранее – вряд ли это ваш случай);
• В литерале класса. List<String>.class не сработает, нужно писать List.class;
• В операторе instanceof. Вместо instanceof Set<Integer> должно быть instanceof Set.
👍16🔥6