Запись в параметризованный массив Java

180
16 апреля 2019, 05:50

Имеется класс-обертка для параметризованного массива:

public class CatContainer <T> {
    T[] names;
    public CatContainer(){
        names=(T[]) new Object[10];
    }
//  void set(int index, T value){
//      names[index]=value;
//  }
}

Насколько я понимаю, если создать экземпляр параметризованного класса следующем образом: CatContainer<String> cats=new CatContainer<>() то ссылка cats будет указывать на объект со следующей структурой:

public class CatContainer {
    Object[] names;
    public CatContainer(){
        names= new Object[10]; // массив не может изменить 
//свой тип, приведение типов отбрасывается
    }
//  void set(int index, Object value){
//      names[index]=value;
//  }
}

Но почему же тогда при попытке обратиться к массиву с типом Object[] и записать туда элемент типа String следующим образом cats.names[0]="Murzik"; возникает ошибка:

java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;

P.S. Если обратиться к массиву через метод void set(int index, T value) следующим образом: cats.set(0, "Murzik") то ошибки приведения типов не возникает

P.P.S Вот код класса - клиента:

public class GenericsArray {
    public static void main(String[] args){
        CatContainer<String> cats=new CatContainer<>();
        cats.names[0]="Murzik"; //ClassCastException
        cats.set(0, "Murzik");  // OK
        Object[] arr=new Object[10];
        arr[0]="Hello"; //OK
    }
}
Answer 1

Дело не в присвоении, сообщение об ошибке говорит о том, что массив типа Object ([Ljava.lang.Object) не может быть преобразован в массив типа String ( [Ljava.lang.String).

Проблему можно воспроизвести и без присвоения, просто обратившись к элементу массива.

CatContainer<String> cats=new CatContainer<>();
System.out.println(cats.names[0]);

либо выполнив безобидную, казалось бы, операцию:

System.out.println(((String[])cats.names));

Ошибка возникает, т.к. при приведении проверяется реальный тип объекта-массива. Это делается из-за того, что в общем случае нельзя безопасно привести массив одного типа к массиву другого типа из-за связанных с такой операцией проблем:

Object[] objects = new Object[10];
objects[0] = new Integer(2);
//здесь возникнет ClassCastException
String[] strings = (String[]) objects;
//иначе возникли бы странные проблемы здесь
System.out.println(strings[0].length());

Если реальный тип массива совпадает, то приведение сработает нормально.

Object[] objects = new String[10];
//ОК
String[] strings = (String[]) objects;
//а вот здесь уже ArrayStoreException
objects[0] = new Integer(2);

Внутри обобщенного класса T буквально заменяется на Object, присвоения и приведения типов работают. Но в вызывающем коде используется конкретный тип со всеми вытекающими приведениями. Например, этот код:

CatContainer<String> cats=new CatContainer<>();
System.out.println(cats.names[0]);

Фактически заменяется на:

CatContainer cats=new CatContainer();
System.out.println(((String[]) cats.names)[0]);

Пути выхода из сложившейся ситуации:

  • не открывайте поле для доступа извне, работайте методами (Вы уже нашли этот способ);
  • принимайте объект класса или массива и создавайте массив нужного типа через Array.newInstance:

    import java.lang.reflect.*;
    ...
    //конструктор
    public CatContainer(Class<T> c){
        names=(T[]) Array.newInstance(c, 10);
    }
    ...
    //пример работы с объектом
    CatContainer<String> cats=new CatContainer<>(String.class);
    cats.names[0] = "Murzik";
    System.out.println(cats.names[0]);
  • используйте вместо массива коллекцию, например, ArrayList.

Смотрите также:

  • Generic и массивы — вопрос об объявлении массивов в обобщенных классах
  • How to create a generic array in Java? — такой же вопрос на английском.
Answer 2
  • names представляет собой Object[]
  • Вызов cats.names - это попытка привести Object[] (реальный тип names) к String[] (generic-тип)
  • Это приведение появляется в коде из-за type erasure: T[] names заменяется на Object[] names, а его вызовы - на (T[])names. В данном случае - на ((String[])cats.names)
  • Так как подобное приведение невозможно, то кончается оно исключением

В случае же метода set всё работает нормально, так как там нет приведений типа: вся работа ведётся напрямую с Object[], как и указано во втором примере кода (условный вид кода после type erasure)

Код names = (T[])new Object[10]; в конструкторе CatContainer, кстати, тоже представляет собой "опасную" конструкцию: покуда T ничем не ограничен (то есть T extends Object) всё нормально: Object[] успешно приводится к Object[]. Но стоит сделать, например, <T extends String>, как знакомая ошибка начнёт проявляться уже прямо в конструкторе

READ ALSO
не отрабатывает авторизацию

не отрабатывает авторизацию

Реализую регистрацию и авторизацию пользователей для дальнейшего использования, с регистрацией проблем нем, все данные уходят в БД, а вот...

179
Java Selenium запись видео

Java Selenium запись видео

На сайте есть анимационный блок, анимация которого производится с применением html5, css, и jsМожно ли как то записать эту анимацию в gif? Использую...

164
RemoteServiceException: Bad notification for startForeground

RemoteServiceException: Bad notification for startForeground

Сейчас для того чтобы опубликовать обновление приложения в PlayМаркет, требуется API не ниже 26Иначе выдает ошибку:

198
SQLException при работе с БД

SQLException при работе с БД

Есть такие методы:

183