Java Guru 🤓 – Telegram
Java Guru 🤓
13.1K subscribers
912 photos
15 videos
783 links
Канал с вопросами и задачами с собеседований!

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

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

Мы на бирже: telega.in/channels/javatasks/card?r=lcDuijdm
Download Telegram
Что такое heap pollution?

Как было сказано ранее, массивы в Java ковариантны. А значит, можно обратиться к объекту типа String[] через переменную типа Object[], и положить туда например Integer. Такой код скомпилируется, но в момент записи произойдет ArrayStoreException.

Дженерики защищены инвариантностью. Если попытаться положить List<Object> в List<String>, эта же по сути ошибка произойдет уже на этапе компиляции.

Heap pollution – ситуация, когда эта защита не срабатывает, и переменная параметризованного типа хранит в себе объект, параметризованный другим типом. Простейший пример:

List<String> strings = (List) new ArrayList<Integer>();

Документация гарантирует, что при компиляции всего кода целиком, heap pollution не может возникнуть без варнинга этапа компиляции.
Heap pollution может произойти в двух случаях: при использовании массивов дженериков и при смешивании параметризованных и raw-типов.

Raw types – это параметризованные типы без указания параметра. Пример с raw types, приводящий к heap pollution, уже был описан выше:

List<String> strings = (List) new ArrayList<Integer>();

Использовать raw types не надо вообще, причины подробно изложены в главе 26 Effective Java. Если информация о дженериках не нужна, используется символ wildcard (<?>).

Компилятор не даст создать массив параметризованного типа, это приведет к ошибке generic array creation. Картинка выше иллюстрирует, к чему это могло бы привести.

Параметризованный тип varargs-аргумента метода вызывает ту же проблему, т.к. varargs – не что иное как параметр-массив. Вот почему он так же приводит к предупреждению компилятора «possible heap pollution». Если вы уверены что риска нет, с Java 7 это предупреждение заглушается аннотацией
@SafeVarargs.
👍245🔥3
Еще один классный канал для изучения Java
@Java_per_month
👍4
Каким будет результат компиляции и выполнения следующего кода?
👍15
Каким будет результат компиляции и выполнения следующего кода?
Anonymous Quiz
18%
C5
18%
C
26%
ABC5
29%
ABC
9%
AB5
👍18🤔17🔥2🤯1
Каким будет результат компиляции и выполнения следующего кода?
👍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