Как заставить две переменные экземпляра иметь один и тот же общий тип?

Предполагая, что у меня есть класс с двумя переменными экземпляра родового типа, как я могу заставить эти типы быть одинаковыми?

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

Короче говоря, как я могу заставить эту последнюю строку кода не компилироваться?

class Foo<T> {
    private T value;
}

class Bar {
    private Foo var1;
    private Foo var2;

    public Bar(Foo var1, Foo var2) {
        this.var1 = var1;
        this.var2 = var2;
    }

    public static void main(String[] args) {
        Foo<String> var1 = new Foo<>();
        Foo<Integer> var2 = new Foo<>();
        Bar b = new Bar(var1, var2); // this should not work
    }
}

Ответ 1

Make Bar также общий:

class Bar<T> {
    private Foo<T> var1;
    private Foo<T> var2;

    public Bar(Foo<T> var1, Foo<T> var2) {
        this.var1 = var1;
        this.var2 = var2;
    }

    public static void main(String[] args) {
        Foo<String> var1 = new Foo<>();
        Foo<Integer> var2 = new Foo<>();
        Bar<String> b = new Bar<>(var1, var2); // this does not work
    }
}

Используя общий параметр T для класса Bar вы можете применять одни и те же типы для обеих переменных экземпляра.

Это устраняет также предупреждение использования необработанных типов (которые никогда не должны использоваться), как упоминалось в комментариях @daniu.


Теперь, если вы не используете необработанные типы, но хотите разрешить не одни и те же типы, вы можете работать с подстановочными знаками:

С верхним ограничением, которое допускает только типизированное чтение (var1 и var2 всегда будут производить реализацию типа T):

class Bar<T> {
    private Foo<? extends T> var1;
    private Foo<? extends T> var2;

    public Bar(Foo<? extends T> var1, Foo<? extends T> var2) {
        this.var1 = var1;
        this.var2 = var2;
    }

    public static void main(String[] args) {
        Foo<String> var1 = new Foo<>();
        Foo<Integer> var2 = new Foo<>();
        Bar<Object> b = new Bar<>(var1, var2); // this does now work
    }
}

И с более низким ограничением, которое допускает только написание записи (var1 и var2 всегда будут использовать любую реализацию типа T):

class Bar<T> {
    private Foo<? super T> var1;
    private Foo<? super T> var2;

    public Bar(Foo<? super T> var1, Foo<? super T> var2) {
        this.var1 = var1;
        this.var2 = var2;
    }

    public static void main(String[] args) {
        Foo<Integer> var1 = new Foo<>();
        Foo<Number> var2 = new Foo<>();
        Bar<Integer> b = new Bar<>(var1, var2); // this does now work
    }
}

Подробнее об этой теме вы можете прочитать: Что такое PECS (продюсер продлевает потребительский супер)?

Ответ 2

Ответ Лино хороший. Я хочу добавить один факт:

Если вам не нужно отслеживать тип var s, но только убедитесь, что они одного типа, то вы можете перенести параметр типа из класса Bar в его конструктор:

class Bar {
    // These will always have the same type, but
    // that not visible here
    private Foo<?> var1;
    private Foo<?> var2;

    // The parameter types of the construct ensures that
    // the vars are always of the same type
    public <T> Bar(Foo<T> var1, Foo<T> var2) {
        this.var1 = var1;
        this.var2 = var2;
    }

    public static void main(String[] args) {
        Foo<String> var1 = new Foo<>();
        Foo<Integer> var2 = new Foo<>();

        Bar b1 = new Bar(var1, var1); // Works fine
        Bar b2 = new Bar(var1, var2); // Compilation error
    }
}

Этот метод работает для всех видов методов.