Вызывающий конструктор родового типа

Если у меня есть абстрактный класс:

public abstract class Item
{
    private Integer value;
    public Item()
    {
        value=new Integer(0);
    }
    public Item(Integer value)
    {
        this.value=new Integer();
    }
}

И некоторые классы, происходящие из этого элемента:

public class Pencil extends Item
{
    public Pencil()
    {
        super();
    }
    public Pencil(Integer value)
    {
        super(value);
    }
}

Я не понял, почему я не могу вызвать конструктор, используя общий:

public class Box <T extends Item>
{
    T item;
    public Box()
    {
        item=new T(); // here I get the error
    }
}

Я знаю, что возможно иметь тип, у которого нет конструктора, но этот случай невозможно, потому что у карандаша есть конструктор без параметров, а Item - абстрактный. Но я получаю эту ошибку от eclipse: не может управлять типом T Я не понимаю, почему и как этого избежать?

Ответ 1

Невозможно использовать систему типов Java для обеспечения того, чтобы иерархия классов имела единую сигнатуру для конструкторов подклассов.

Рассмотрим:

public class ColorPencil extends Pencil
{
    private Color color;

    public ColorPencil(Color color)
    {
        super();
        this.color=color;
    }   
}

Это делает ColorPencil допустимым T (он расширяет элемент). Однако для этого типа нет конструктора no-arg. Следовательно, T() является бессмысленным.

Чтобы делать то, что вы хотите, вам нужно использовать отражение. Вы не можете воспользоваться проверкой ошибок времени компиляции.

Ответ 2

Это связано с тем, что Java использует стирание для реализации дженериков, см. это:

Чтобы процитировать соответствующие части из вышеупомянутой статьи в Википедии:

Общие данные проверяются во время компиляции для правильности ввода. Затем информация об общем типе удаляется в процессе, называемом стиранием типа.

В результате стирания типа параметры типа не могут быть определены во время выполнения.

Следовательно, создание экземпляра класса Java параметризованного типа невозможно, поскольку для создания экземпляра требуется вызов конструктора, который недоступен, если тип неизвестен.

Вы можете обойти это, предоставив сам класс. Это хорошо объяснено здесь:

Ответ 3

T - это псевдоним для фактического типа, который будет обрабатывать ваш класс, например, если вы создаете экземпляр Box<Item>, тогда T на самом деле является просто псевдонимом для Item. Когда вы объявляете T extends Item, вы знаете, что T будет иметь по крайней мере тот же интерфейс, что и Item, поэтому вы можете рассматривать его как один.

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

public class Box<T extends Item> {
    private T item;

    public T getItem() {
        return this.item;
    }

    public void setItem(T item) {
        return this.item = item;
    }
}

Ответ 4

Потому что во время выполнения тип T неизвестен.

Ответ 5

Невозможно использовать T для вызова конструктора, потому что если бы это было возможно, чем после компиляции, вы получили бы такой код:

public class Box{
    Object item;
    public Box(){
        item = new Object();
    }
}

Итак, если вы будете использовать этот код и передать какой-то объект, чем вы ожидаете, что будет вызван конструктор некоторого определенного типа, но вместо этого вы получите конструктор Object.

Ответ 6

Вы можете разместить абстрактный метод, чтобы класс реализации мог определить, как построить новый объект.

public abstract T constructT();

и вместо вызова

T t = new T()

вы бы назвали

T t = constructT();

В вашем исполняющем вызове он будет создан как:

new Box<Integer>() {
    public Integer constructT(){ return new Integer(); }
}