Подтвердить что ты не робот

Как правильно использовать массивы типичных типов?

У меня есть класс, который отображает входящие сообщения для соответствия читателям на основе класса сообщений. Все типы сообщений реализуют сообщение интерфейса. Читатель регистрируется в классе mapper, указывая, какие типы сообщений он сможет обрабатывать. Эта информация должна быть сохранена в устройстве чтения сообщений в некотором роде, и мой подход состоял в том, чтобы установить массив private final из конструктора.

Теперь, кажется, у меня есть недоразумение о дженериках и/или массивах, которые я не могу понять, см. код ниже. Что это?

public class HttpGetMessageReader implements IMessageReader {
    // gives a warning because the type parameter is missing
    // also, I actually want to be more restrictive than that
    // 
    // private final Class[] _rgAccepted;

    // works here, but see below
    private final Class<? extends IMessage>[] _rgAccepted;

    public HttpGetMessageReader()
    {
        // works here, but see above
        // this._rgAccepted = new Class[1];

        // gives the error "Can't create a generic array of Class<? extends IMessage>"
        this._rgAccepted = new Class<? extends IMessage>[1];

        this._rgAccepted[0] = HttpGetMessage.class;
    }
}

ETA: Как ясно указал cletus, самый простой googling показывает, что Java не разрешает общие массивы. Я определенно понимаю это для приведенных примеров (например, E[] arr = new E[8], где E - это параметр типа окружающего класса). Но почему разрешено new Class[n]? И каков тогда "правильный" (или, по крайней мере, общий) способ сделать это?

4b9b3361

Ответ 1

Java не разрешает общие массивы. Дополнительная информация в Часто задаваемые вопросы Java Generics.

Чтобы ответить на ваш вопрос, просто используйте List (возможно ArrayList) вместо массива.

Еще несколько объяснений можно найти в Теория и практика Java: Generics gotchas:

Дженерики не ковариантны

Хотя вам может показаться полезным думать о коллекциях как о абстракции массивов, у них есть специальные свойства, которые делают коллекции не. Массивы на языке Java являются ковариант - что означает, что если Целое число расширяет число (которое оно), то не только Integerтакже Число, но Integer[]также a Number[], и вы свободны передать или присвоить Integer[], где a Number[]. (Больше формально, если Number является супертипом Integer, то Number[] является супертип Integer[].) Вы можете думаю, что то же самое относится к родовым типы также - что List<Number>является супертипом List<Integer>, и что вы можете передать List<Integer>где a List<Number>. К сожалению, это не работает путь.

Оказалось, что это хорошая причина не работает таким образом: он сломается Предполагалось, что дженерики безопасности типа предоставлять. Представьте, что вы можете назначить List<Integer> до List<Number>. Тогда следующий код позволит вы ставите что-то, что не было Integer в List<Integer>:

List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415));

Поскольку ln является List<Number>, добавление a Float для него кажется совершенно законным. Но если бы вы были наняты ли, тогда это сломает обещание типа безопасности неявный в определении li - что это список целых чисел, который почему общие типы не могут быть ковариантны.

Ответ 2

Правильно, что сказал клетус. Существует общее несоответствие между обычно принудительной инвариантностью параметров типового типа против ковариации массивов Java.

(Некоторая предыстория: Variance указывает, как типы относятся друг к другу относительно подтипирования. I. из-за того, что типичные параметры типа являются инвариантными Collection <: Коллекция не выполняется. Итак, что касается системы типа Java, коллекция String не является коллекцией CharSequence. Массивы, являющиеся ковариантными, означают, что для любых типов T и U с T <: U, T [] <: U []. Таким образом, вы можете сохранить переменную типа T [] в переменной типа U []. Поскольку существует естественная потребность в других вариантах дисперсии, Java по крайней мере допускает использование подстановочных знаков для этих целей.)

Решение (хак, на самом деле), которое я часто использую, объявляет вспомогательный метод, который генерирует массив:

public static <T> T[] array(T...els){
    return els;
}
void test(){
    // warning here: Type safety : A generic array of Class is created for a varargs parameter
    Class<? extends CharSequence>[] classes = array(String.class,CharSequence.class);
}

Из-за стирания сгенерированные массивы всегда будут иметь тип Object [].

Ответ 3

А какой тогда "правильный" (или, по крайней мере, общий) способ сделать это?

@SuppressWarnings(value="unchecked")
public <T> T[] of(Class<T> componentType, int size) {
    return (T[]) Array.newInstance(componentType, size);
}

public demo() {
    Integer[] a = of(Integer.class, 10);
    System.out.println(Arrays.toString(a));
}

Ответ 4

Массивы всегда имеют определенный тип, в отличие от того, как раньше использовались Коллекции перед Generics.

Вместо

Class<? extends IMessage>[] _rgAccepted;

Вы должны просто написать

IMessage[] _rgAccepted;

Дженерики не входят в него.

Ответ 5

ИМХО,

this._rgAccepted = (Class<? extends IMessage>[])new Class[1];

- это подходящий способ справиться с этим. Типы типов массива должны быть типами reified, а Class - ближайшим типом reified до Class<whatever>. Он будет работать так же, как вы ожидали бы Class<whatever>[].

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