Предупреждение о непроверенной конверсии с дженериками

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

Я реализую неизменяемые объекты с помощью шаблона построителя, для которого я реализовал интерфейс «Неизменяемый» с внутренним интерфейсом «Построитель».

Каждый неизменяемый класс реализует интерфейс Immutable и реализует внутренний статический класс Builder, который реализует интерфейс Builder.

Все это работает нормально.

Теперь я реализую кучу очень простых классов, которым на самом деле не нужен конструктор, но я все еще хочу реализовать интерфейс Immutable, поэтому объекты этих классов являются экземплярами «Immutable», но я не хочу реализовать пустые конструкторы без какой-либо функциональности для каждого класса. Я бы предпочел иметь абстрактный класс, в котором можно реализовать один простой конструктор для всех простых классов. Построитель просто сохранит исходный объект и вернет его через метод build(), поэтому интерфейс Immutable реализован полностью.

Однако метод build() построителя должен возвращать объект реализующего класса. Поэтому я добавил дженерики.

public interface Immutable {
    public interface Builder<T> {
        public T build();
    }
    public <T> Builder<T> builder();
}


public interface Interface extends Immutable {

    public interface BuilderInterface<T> extends Immutable.Builder<T> {

    }
}


public abstract class AbstractClass implements Interface {

    public static class AbstractBuilder<T> implements Interface.BuilderInterface<T> {

        private final T object;

        public AbstractBuilder(T object) {

            this.object = object;
        }

        @Override
        public T build() {

            return this.object;
        }
    }

    protected AbstractClass() {

        super();
    }
}

public class ConcreteClass extends AbstractClass {

    public ConcreteClass() {

    }

    @Override
    public AbstractBuilder<ConcreteClass> builder() {

        return new AbstractClass.AbstractBuilder<ConcreteClass>(this);
    }
}

Я ожидал, что универсальный тип T интерфейса Immutable примет тип реализующего класса, но вместо этого он выглядит как Object, что приводит к следующему предупреждению:

Безопасность типов: возвращаемый тип AbstractClass.AbstractBuilder для builder() из типа ConcreteClass требует непроверенного преобразования, чтобы соответствовать Immutable.Builder из типа Immutable.

EDIT: предупреждение выдается методом builder() класса ConcreteClass.


person Malte    schedule 03.07.2019    source источник
comment
Какая строка кода генерирует это предупреждение?   -  person Eran    schedule 03.07.2019
comment
Итак, вы фактически используете конструктор для создания экземпляров ваших классов. Это не то, как обычно работают при использовании строителей. В вашем случае вы не можете использовать конструктор без предварительного создания элемента с помощью конструктора. Мне это кажется странным. Строители, которым требуется экземпляр, обречены на провал.   -  person RealSkeptic    schedule 03.07.2019
comment
Строка public AbstractBuilder‹ConcreteClass› builder() {   -  person Malte    schedule 03.07.2019
comment
Я удалил все статические фабричные методы для простого примера. Обычно можно создать экземпляр Builder напрямую или через неизменяемый объект, и каждый класс имеет только один конструктор, который является закрытым/защищенным.   -  person Malte    schedule 03.07.2019
comment
Методам построителя по-прежнему требуется экземпляр для создания экземпляра. Я не уверен, что вы удалили, но это не имеет смысла.   -  person RealSkeptic    schedule 03.07.2019
comment
Это относится к вопросу? Может быть, но я так не думаю. Не стесняйтесь притворяться, что существует другой конструктор, который не принимает ни одного аргумента или не принимает необходимые аргументы, если это поможет.   -  person Malte    schedule 03.07.2019


Ответы (1)


Это довольно просто - сигнатура метода Immutable#builder предполагает, что параметр типа T устанавливается "на лету" для фактического вызова метода и не привязан к классу. Чтобы правильно переопределить этот метод, сигнатура в ConcreteClass будет

public <T> Builder<T> builder() {

что явно противоречит вашему определению строителя

return new AbstractClass.AbstractBuilder<ConcreteClass>(this);

Чтобы сделать все это компилируемым, вы должны вывести T для Immutable#builder из класса, а не из вызывающего метода, т.е. что у вас, наконец, есть

public interface Immutable<T> {

    public interface Builder<T> {
        public T build();
    }

    public Builder<T> builder(); 
}

и все унаследованные классы изменились соответствующим образом, чтобы передать T своим предшественникам.

public interface Interface<T> extends Immutable<T> {

    public interface BuilderInterface<T> extends Immutable.Builder<T> {
    }
}


public abstract class AbstractClass<T> implements Interface<T> {

    public static class AbstractBuilder<T> implements Interface.BuilderInterface<T> {

        private final T object;

        public AbstractBuilder(T object) {

            this.object = object;
        }

        @Override
        public T build() {

            return this.object;
        }
    }

    protected AbstractClass() {

        super();
    }
}


public class ConcreteClass extends AbstractClass<ConcreteClass> {

    public ConcreteClass() {

    }

    @Override
    public Builder<ConcreteClass> builder() {
        return new AbstractClass.AbstractBuilder<ConcreteClass>(this);
    }
}
person Smutje    schedule 03.07.2019