Почему не получается сделать downcast к пользовательскому классу?

Рейтинг: 0Ответов: 3Опубликовано: 21.08.2023

Напишу причину данного и кода и мою проблему. В качестве обучения пытаюсь сам написать HashSet. Обычно реализуют данную коллекцию на основе массива, где каждый элемент - связный список (в моем случае класс Entry). Почему здесь невозможно преобразовать к массиву, содержащий тип Entry? Какой-то метод нужен что ли? Почему тогда компилятор java не ругается, когда мы приводим к T[]? В моем случае появляется такая ошибка -

java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Lorg.example.CarHashSet$Entry;


public class CarHashSet<T> implements CarSet<T> {

    private static final int INITIAL_CAPACITY = 16;
    private Entry[] array;
    int size;
    public CarHashSet() {
        this.size = 0;
        this.array = (Entry[]) new Object[INITIAL_CAPACITY];
    }

    private class Entry {
        T value;
        Entry next;

        private Entry(T value, Entry next) {
            this.value = value;
            this.next = next;
        }

    }
}

Ответы

▲ 1Принят

Вы создаёте массив объектов Object[] и пытаетесь привести его к массиву Entry[], разумеется такой код выбросит исключение, так как на самом деле вам необходимо создать массив обобщённых Entry.

Как давно известно, нельзя просто взять и создать массив дженериков:

this.array = new Entry[INITIAL_CAPACITY]; // error: generic array creation

Однако, если очень нужно, то можно использовать метод java.lang.reflect.Array.newInstance:

this.array = (Entry[]) Array.newInstance(Entry.class, INITIAL_CAPACITY);

Кроме того, придётся указать, что класс Entry также является обобщённым и соответствующие переменные/параметры, и при приведении типов использовать аннотацию @SuppressWarnings("unchecked") во избежание нежелательных предупреждений от компилятора:

import java.util.reflect.Array;

public class CarHashSet<T> implements Set<T> {

    private static final int INITIAL_CAPACITY = 16;
    private Entry<T>[] array;
    int size;
    
    @SuppressWarnings("unchecked")
    public CarHashSet() {
        this.size = 0;
        this.array = (Entry<T>[]) Array.newInstance(Entry.class, INITIAL_CAPACITY);
    }
    
    private class Entry<T> {
        T value;
        Entry<T> next;

        private Entry(T value, Entry<T> next) {
            this.value = value;
            this.next = next;
        }
        
        public T value() {return this.value;}
    }
}
▲ 0

Инициализируя новый Object создаётся именно Object, который не может быть преобразован к дочернему типу. Нельзя преобразовывать родительские типы к дочерним, как минимум потому что в объектах родительских типов (таких как Object) отсутствуют поля из объектов дочерних типов (В твоём случае поле value и next) (за исключением случаев когда родительский тип хранит в себе как раз объект дочернего типа), можно только наоборот (любой дочерний тип к родительскому типу, так как дочерний объект 100% будет содержать поля родительского объекта)

▲ 0

Вы не можете просто преобразовать массив объектов к вашему классу Entry. В место этого создайте новый объект класса Entry:

public class CarHashSet<T> implements CarSet<T> {

    private static final int INITIAL_CAPACITY = 16;
    private Entry array; //без []
    int size;
    public CarHashSet() {
        this.size = 0;
        this.array = new Entry(/* Значение для value */, new Object[INITIAL_CAPACITY]);
    }

    private class Entry {
        T value;
        Entry next;

        public Entry(T value, Entry next) { //публичный конструктор
            this.value = value;
            this.next = next;
        }

    }
}