Что случилось с неконтролируемым актерским составом?
Я читаю Дж. Блоха эффективную Java, и теперь я нахожусь в разделе массивов и списков. Вот пример непроверенного актера, который он предоставил:
interface Function<T> {
T apply(T arg1, T arg2);
}
public class Main{
public static void main( String[] args ){
Function<String> f = null;
List<String> str = Arrays.asList("asd");
//staff
reduce(str, f, ""); //E deduced to String. Where is type-unsafe?
}
static <E> E reduce(List<E> list, Function<E> f, E initVal) {
E[] snapshot = (E[]) list.toArray(); // Unchecked cast
E result = initVal;
for (E e : snapshot)
result = f.apply(result, e);
return result;
}
}
Он сказал, что метод не безопасен для типа, и мы можем легко получить ClassCastException
. Но я не понимаю, как это сделать. Если тип-небезопасный, переменная типа E
всегда будет выводиться соответствующему типу, поэтому мы не будем беспокоиться о том, что класс-литье-лишение.
Не могли бы вы привести пример с метанием ClassCastException
?
Ответы
Ответ 1
Нет гарантии времени компиляции, что list.toArray()
вернет массив типа E[]
. Более того, он почти всегда возвращает массив типа Object[]
. Таким образом, в зависимости от более позднего использования этого массива вы можете иметь ClassCastException
. Например, рассмотрим следующий код:
public static void main( String[] args ){
List<String> str = Collections.singletonList("asd");
String[] array = test(str);
}
static <E> E[] test(List<E> list) {
E[] snapshot = (E[]) list.toArray(); // Unchecked cast
return snapshot;
}
Здесь вы возвращаете массив E[]
, и получатель ожидает, что массив String[]
будет возвращен. Но на самом деле это массив Object[]
, таким образом, вы получите метод ClassCastException
в main
после того, как возвращаемый общий тип неявно будет передан в String[]
.
В вашем коде вы можете быть уверены, что массив используется безопасным образом. Но компилятор недостаточно умен, чтобы сделать этот анализ, поэтому он просто предупреждает вас.
Ответ 2
Идиома list.toArray
, которую вы здесь используете, не параметризируется массивом вашего параметрированного типа List
, поэтому возвращает Object[]
.
Например, с помощью List<String> str
вы можете вызывать: String[] foo = str.toArray(new String[str.size()]);
без кастования.
Проблема заключается в том, что из-за дизайна дженериков Java вы никогда не сможете инициализировать new E[]
, поэтому вам нужно указать (E[])
.
Я не вижу, как это бросает ClassCastException
когда-либо.
Как отмечали другие, "косметическим" обходным путем является добавление @SuppressWarnings("unchecked")
перед вызовом toArray
, который будет подавлять предупреждение.
Ответ 3
Object [] toArray() Возвращает массив, содержащий все элементы в этот список в правильной последовательности (от первого до последнего элемента).
Мы отбрасываем это на E [] выписываем дженерики, поэтому приведение не проверяется, потому что jvm не знает, какой тип E будет таким предупреждением.
Скажем, например, E является строковым типом (как в коде для вас). И мы пытаемся передать Object [] в String [], что вполне может быть Object [] для Integer [] для другого случая. Эта достоверность не может быть проверена в момент компиляции/запуска jvm, поэтому проблема.
public static void main( String[] args ){
List<String> str = Arrays.asList("asf");
//staff
System.out.println(reduce(str, 2)); //E deduced to String. Where is type-unsafe?
}
static <E, T> E reduce(List<E> list, T initVal) {
Object snapshot = list.size(); // Unchecked cast
return (E) snapshot;
}
Это создаст исключение класса.
Ответ 4
Нет ничего плохого в твоем актере, но с Java Generics:
static <E> E reduce(List<E> list, Function<E> f, E initVal) {
@SuppressWarnings({"unchecked"}) //nevermind
E[] snapshot = (E[]) list.toArray(); //Unchecked cast
E result = initVal;
for (E e : snapshot)
result = f.apply(result, e);
return result;
}