Связывание дженериков с ключевым словом «super» - PullRequest
51 голосов
/ 10 мая 2010

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

Например, в интерфейсе Collection почему метод toArray не написан так

interface Collection<T>{
    <S super T> S[] toArray(S[] a);
}

Ответы [ 4 ]

47 голосов
/ 10 мая 2010

super для привязки параметра именованного типа (например, <S super T>) в отличие от подстановочного знака (например, <? super T>) - это ILLEGAL просто потому, что даже если это разрешено, оно не будет делать то, что Вы надеялись, что это сработает, потому что Object является максимальным super из всех ссылочных типов, а все это Object, , в действительности нет границы .

В вашем конкретном примере, поскольку любой массив ссылочного типа представляет собой Object[] (по ковариации Java-массива), поэтому его можно использовать в качестве аргумента для <S super T> S[] toArray(S[] a) (если такая граница допустима ) во время компиляции, и это не помешает ArrayStoreException во время выполнения.

Вы пытаетесь предложить следующее:

List<Integer> integerList;

и с учетом этого гипотетического super, связанного с toArray:

<S super T> S[] toArray(S[] a) // hypothetical! currently illegal in Java

компилятор должен разрешать компилировать только следующее:

integerList.toArray(new Integer[0]) // works fine!
integerList.toArray(new Number[0])  // works fine!
integerList.toArray(new Object[0])  // works fine!

и никаких других аргументов типа массива (поскольку Integer имеет только эти 3 типа как super). То есть вы пытаетесь предотвратить компиляцию:

integerList.toArray(new String[0])  // trying to prevent this from compiling

потому что, по вашему аргументу, String не является super из Integer. Однако , Object - это super из Integer, а String[] - это Object[], поэтому компилятор все еще позволит скомпилировать приведенный выше код, даже если гипотетически вы можете сделать <S super T>!

Таким образом, следующее будет по-прежнему компилировать (как и сейчас), и ArrayStoreException во время выполнения не может быть предотвращено любой проверкой во время компиляции с использованием границ универсального типа:

integerList.toArray(new String[0])  // compiles fine!
// throws ArrayStoreException at run-time

Дженерики и массивы не смешиваются, и это одно из многих мест, где оно отображается.


Пример без массива

Опять, скажем, у вас есть это объявление общего метода:

<T super Integer> void add(T number) // hypothetical! currently illegal in Java

И у вас есть следующие объявления переменных:

Integer anInteger
Number aNumber
Object anObject
String aString

Ваше намерение с <T super Integer> (если это законно) состоит в том, чтобы оно разрешало add(anInteger), add(aNumber) и, конечно, add(anObject), но НЕ add(aString). Ну, String - это Object, поэтому add(aString) все равно будет компилироваться.


См. Также

Похожие вопросы

О правилах набора шаблонов:

При использовании super и extends:

29 голосов
/ 15 апреля 2011

Поскольку никто не дал удовлетворительного ответа, правильный ответ, по-видимому, «без уважительной причины».

Polygenelubricants предоставили хороший обзор плохих вещей, происходящих с ковариацией java-массива, которая сама по себе ужасная особенность. Рассмотрим следующий фрагмент кода:

String[] strings = new String[1];
Object[] objects = strings;
objects[0] = 0;

Этот явно неправильный код компилируется без использования какой-либо "супер" конструкции, поэтому ковариация массива не должна использоваться в качестве аргумента.

Теперь у меня есть совершенно правильный пример кода, требующего super в параметре именованного типа:

class Nullable<A> {
    private A value;
    // Does not compile!!
    public <B super A> B withDefault(B defaultValue) {
        return value == null ? defaultValue : value;
    }
}

Потенциально поддерживает некоторое хорошее использование:

Nullable<Integer> intOrNull = ...;
Integer i = intOrNull.withDefault(8);
Number n = intOrNull.withDefault(3.5);
Object o = intOrNull.withDefault("What's so bad about a String here?");

Последний фрагмент кода не компилируется, если я полностью удаляю B, поэтому B действительно необходим.

Обратите внимание, что возможность, которую я пытаюсь реализовать, легко получить, если я инвертирую порядок объявлений параметров типа, тем самым изменяя ограничение super на extends. Однако это возможно только в том случае, если я переписываю метод как статический:

// This one actually works and I use it.
public static <B, A extends B> B withDefault(Nullable<A> nullable, B defaultValue) { ... }

Дело в том, что это ограничение языка Java действительно ограничивает некоторые возможные другие полезные функции и может потребовать некрасивых обходных путей. Интересно, что произойдет, если нам понадобится withDefault, чтобы быть виртуальным.

Теперь, чтобы соотнести с тем, что сказали полигеномасла, мы используем B здесь не для ограничения типа объекта, переданного как defaultValue (см. Строку, использованную в примере), а для ограничения ожиданий вызывающего абонента относительно объекта мы возвращаемся. Как правило, вы используете extends с требуемыми типами и super с указанными вами типами.

11 голосов
/ 24 ноября 2015

«Официальный» ответ на ваш вопрос можно найти в сообщении об ошибке Sun / Oracle .

ВТ2: ОЦЕНКА

См

http://lampwww.epfl.ch/~odersky/ftp/local-ti.ps

особенно раздел 3 и последний абзац на стр. 9. Признание Переменные типа по обе стороны от ограничений подтипа могут привести к набор уравнений типа без единого лучшего решения; как следствие, вывод типа не может быть сделан с использованием любого из существующих стандартов алгоритмы. Вот почему переменные типа имеют только «расширяемые» границы.

Подстановочные знаки, с другой стороны, не должны быть выведены, так что нет необходимости в этом ограничении.

@###.### 2004-05-25

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

@###.### 2004-05-26

Я вижу проблему. Но я не вижу, как это отличается от проблем мы имеем с нижними границами подстановочных знаков во время вывода, например ::1010 *

List <? супер номер> s;
логический b;
...
s = b? s: s;

В настоящее время мы выводим List , где X расширяет Object как тип условное выражение, означающее, что присвоение недопустимо.

@###.### 2004-05-26

К сожалению, разговор на этом заканчивается. Бумага, на которую ссылалась (теперь мертвая) ссылка: Inferred Type Instantiation для GJ . От просмотра последней страницы все сводится к следующему: если допускаются нижние границы, вывод типа может дать несколько решений, ни одно из которых не является принципалом .

0 голосов
/ 03 октября 2014

Предположим, у нас есть:

  • базовые классы A> B> C и D

    class A{
        void methodA(){}
    };
    class B extends  A{
        void methodB(){}
    }
    
    class C extends  B{
        void methodC(){}
    }
    
    class D {
        void methodD(){}
    }
    
  • классы обёрток работы

    interface Job<T> {
        void exec(T t);
    }
    
    class JobOnA implements Job<A>{
        @Override
        public void exec(A a) {
            a.methodA();
        }
    }
    class JobOnB implements Job<B>{
        @Override
        public void exec(B b) {
            b.methodB();
        }
    }
    
    class JobOnC implements Job<C>{
        @Override
        public void exec(C c) {
            c.methodC();
        }
    }
    
    class JobOnD implements Job<D>{
        @Override
        public void exec(D d) {
            d.methodD();
        }
    }
    
  • и один класс менеджера с 4 различными подходами для выполнения задания на объекте

    class Manager<T>{
        final T t;
        Manager(T t){
            this.t=t;
        }
        public void execute1(Job<T> job){
            job.exec(t);
        }
    
        public <U> void execute2(Job<U> job){
            U u= (U) t;  //not safe
            job.exec(u);
        }
    
        public <U extends T> void execute3(Job<U> job){
            U u= (U) t; //not safe
            job.exec(u);
        }
    
        //desired feature, not compiled for now
        public <U super T> void execute4(Job<U> job){
            U u= (U) t; //safe
            job.exec(u);
        }
    }
    
  • с использованием

    void usage(){
        B b = new B();
        Manager<B> managerB = new Manager<>(b);
    
        //TOO STRICT
        managerB.execute1(new JobOnA());
        managerB.execute1(new JobOnB()); //compiled
        managerB.execute1(new JobOnC());
        managerB.execute1(new JobOnD());
    
        //TOO MUCH FREEDOM
        managerB.execute2(new JobOnA()); //compiled
        managerB.execute2(new JobOnB()); //compiled
        managerB.execute2(new JobOnC()); //compiled !!
        managerB.execute2(new JobOnD()); //compiled !!
    
        //NOT ADEQUATE RESTRICTIONS     
        managerB.execute3(new JobOnA());
        managerB.execute3(new JobOnB()); //compiled
        managerB.execute3(new JobOnC()); //compiled !!
        managerB.execute3(new JobOnD());
    
        //SHOULD BE
        managerB.execute4(new JobOnA());  //compiled
        managerB.execute4(new JobOnB());  //compiled
        managerB.execute4(new JobOnC());
        managerB.execute4(new JobOnD());
    }
    

Есть предложения, как реализовать execute4 сейчас?

========== отредактировано =======

    public void execute4(Job<? super  T> job){
        job.exec( t);
    }

Спасибо всем:)

========== отредактировано ==========

    private <U> void execute2(Job<U> job){
        U u= (U) t;  //now it's safe
        job.exec(u);
    }
    public void execute4(Job<? super  T> job){
        execute2(job);
    }

намного лучше, любой код с U внутри execute2

супер тип U становится именованным!

интересное обсуждение:)

...