Что случилось с неконтролируемым актерским составом?

Я читаю Дж. Блоха эффективную 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;
}