Nano Hash - криптовалюты, майнинг, программирование

Общий метод с параметрами по сравнению с неуниверсальным методом с подстановочными знаками

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

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

Они приводят пример метода <T> void print1( List <Box<T>> list), который «требует списка ящиков одного типа». Версия с подстановочными знаками, void print2( List <Box<?>> list), «принимает неоднородный список блоков разных типов» и, следовательно, не эквивалентна.

Как вы интерпретируете различия между следующими двумя сигнатурами методов:

 <T extends Iterable<?>> void f(Class<T> x) {}
                         void g(Class<? extends Iterable<?>> x) {}

Интуитивно кажется, что эти определения должны быть эквивалентны. Однако вызов f(ArrayList.class) компилируется с использованием первого метода, а вызов g(ArrayList.class) с использованием второго метода приводит к ошибке времени компиляции:

g(java.lang.Class<? extends java.lang.Iterable<?>>) in Test
    cannot be applied to (java.lang.Class<java.util.ArrayList>)

Интересно, что обе функции можно вызывать с аргументами друг друга, потому что компилируется следующее:

class Test {
    <T extends Iterable<?>> void f(Class<T> x) {
        g(x);
    }
    void g(Class<? extends Iterable<?>> x) {
        f(x);
    }
}

Используя javap -verbose Test, я вижу, что f() имеет общую подпись

<T::Ljava/lang/Iterable<*>;>(Ljava/lang/Class<TT;>;)V;

и g() имеет общую подпись

(Ljava/lang/Class<+Ljava/lang/Iterable<*>;>;)V;

Чем объясняется такое поведение? Как мне интерпретировать различия между сигнатурами этих методов?

19.07.2012

  • +1 за вопрос о дженериках, на который нет ответа, вы не можете сделать это с дженериками для разнообразия. 20.07.2012
  • Разница в семантике понятна, однако мне кажется странным, что она не компилируется. Удаление подстановочного знака Iterable из g по какой-то причине компилируется: void g(Class<? extends Iterable> x). 20.07.2012

Ответы:


1

Ну, судя по спецификации, ни один из вызовов не является законным. Но почему первый тип проверяет, а второй нет?

Разница заключается в том, как методы проверяются на применимость (см. §15.12.2 и §15.12.2.2, в частности).

  • Для применения простого неуниверсального g аргумент Class<ArrayList> должен быть подтипом Class<? extends Iterable<?>>. Это означает, что ? extends Iterable<?> необходимо содержать ArrayList, записанное ArrayList <= ? extends Iterable<?>. Правила 4 и 1 может применяться транзитивно, поэтому ArrayList должен быть подтипом Iterable<?>.

    Переходим по §4.10.2 любая параметризация C<...> является (прямым) подтипом необработанного типа C. Таким образом, ArrayList<?> является подтипом ArrayList, а не наоборот. Переходно, ArrayList не является подтипом Iterable<?>.

    Таким образом, g не применяется.

  • f является общим, для простоты предположим, что аргумент типа ArrayList указан явно. Чтобы проверить применимость f, Class<ArrayList> должен быть подтипом Class<T> [T=ArrayList] = Class<ArrayList>. Поскольку подтипирование является рефлексивным, это верно.

    Также для применения f аргумент типа должен быть в его пределах. Это не потому, что, как мы показали выше, ArrayList не является подтипом Iterable<?>.

Так почему же он все равно компилируется?

Это ошибка. После отчета об ошибке и последующее исправление компилятор JDT явно исключает первый случай (включение аргумента типа). Второй случай по-прежнему счастливо игнорируется, потому что JDT считает ArrayList подтипом Iterable<?> (TypeBinding.isCompatibleWith(TypeBinding)).

Я не знаю, почему javac ведет себя так же, но я предполагаю, что по тем же причинам. Вы заметите, что javac также не выдает непроверенное предупреждение при назначении необработанного ArrayList для Iterable<?>.

20.07.2012

2

Если параметр типа был типом с подстановочными знаками, проблема не возникает:

Class<ArrayList<?>> foo = null;
f(foo);
g(foo);

Я думаю, что это почти наверняка странный случай, возникающий из-за того, что тип литерала класса Class<ArrayList>, поэтому параметр типа в этом случае (ArrayList) является необработанным типом, а отношение подтипа между необработанным ArrayList и подстановочным знаком- параметризованный ArrayList<?> сложен.

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

20.07.2012
  • +1 Мне никогда не было ясно, почему литералы классов универсальных классов параметризуются необработанными типами, а не типами с подстановочными знаками. 20.07.2012
  • @PaulBellora: Интересно, что эта проблема не возникает в Scala, что требует от меня параметризации литералов классов универсальных классов. Я могу успешно вызвать оба метода из Scala с помощью classOf[java.util.ArrayList[_]]. 20.07.2012
  • @newacct: это может быть ошибка. Даже несмотря на класс‹? extends Iterable‹?›› не назначается из Class‹ArrayList›, я могу написать метод <T extends Iterable<?>> Class<? extends Iterable<?>> convert(Class<T> x) { x }, который преобразует ArrayList.class в Class‹? расширяет Iterable‹?››. Прямое присваивание Class<? extends Iterable<?>> cls = ArrayList.class; не выполняется, но Class<? extends Iterable<?>> cls = convert(ArrayList.class); работает, хотя convert() не выполняет приведения. Это позволяет мне вызывать g(convert(ArrayList.class)). 20.07.2012

  • 3

    Угадай: что представляет собой первое ? (ArrayList) не «реализует» ArrayList<E> (в силу двойного вложенного подстановочного знака). Я знаю, это звучит смешно, но....

    Рассмотрим (для исходного списка):

     void g(Class<? extends Iterable<Object> x) {} // Fail
     void g(Class<? extends Iterable<?> x) {}  // Fail
     void g(Class<? extends Iterable x) {}  // OK
    

    И

    // Compiles
    public class Test{
        <T extends Iterable<?>> void f(ArrayList<T> x) {}
        void g(ArrayList<? extends Iterable<?>> x) {}
    
        void d(){
            ArrayList<ArrayList<Integer>> d = new ArrayList<ArrayList<Integer>>();
            f(d);
            g(d);
        }
    }
    

    Этот

    // Does not compile on g(d)
    public class Test{
        <T extends Iterable<?>> void f(ArrayList<T> x) {}
        void g(ArrayList<? extends Iterable<?>> x) {}
    
        void d(){
            ArrayList<ArrayList> d = new ArrayList<ArrayList>();
            f(d);
            g(d);
        }
    }
    
    19.07.2012
  • В случае вашего второго примера, почему вызов f(d) завершается успешно, а вызов g(d) не компилируется? Проводя аналогию с классом Box, описанным в вопросе, является подпись void g(ArrayList<? extends Iterable<?>> x), говорящая, что g() принимает ArrayList Iterables (или подклассы Iterable), которые, возможно, имеют разные типы, тогда как <T extends Iterable<?>> void f(ArrayList<T> x) принимает ArrayList Iterables, которые все одного типа ? Как этот тип рассуждений распространяется на Class<? extends Iterable<?>>, где Class не является коллекцией? 20.07.2012
  • Я не знаю, я бы предположил, что по той же причине ваш код не компилируется. Я использовал ArrayList дважды, чтобы убедиться, что это не что-то конкретное для класса Class. Если хотите, измените его на любой параметризованный тип, например, на box. Я хочу сказать, что похоже, что параметризованный тип стирается из-за вложенных подстановочных знаков, подобно тому, что упоминал @newacct. 20.07.2012

  • 4

    Это не совсем то же самое:

    <T extends Iterable<?>> void f(Class<T> x) {}
    void g(Class<? extends Iterable<?>> x) {}
    

    Разница в том, что g принимает "класс неизвестного, который реализует Iterable of unknown", но ArrayList<T> ограничен реализацией Iterable<T>, а не Iterable<?>, поэтому он не совпадает.

    Чтобы было понятнее, g примет Foo implements Iterable<?>, но не AraryList<T> implements Iterable<T>.

    20.07.2012
  • Вы не имеете смысла здесь. ArrayList<T> реализует Iterable<T>. 20.07.2012
  • @PaulBellora Точно. ArrayList<T> не реализует Iterable<?>. Помните, что Iterable<T> не является экземпляром Iterable<?> в том, что касается общей типизации. 20.07.2012
  • Ты все еще не понимаешь. Откуда взялась часть о ArrayList реализации Iterable<List>? Во-вторых, Iterable<?> можно назначить из любого ArrayList<T>. 20.07.2012
  • @PaulBellora Ага, да. Я имею в виду Iterable<T> (отредактировал мой ответ). Но по-прежнему остается, что ArrayList<T> не соответствует, потому что Iterable<T> не является Iterable<?> в том, что касается соответствия сигнатуре метода - T привязан к классу, а ITerable<?> нет - см. последнее предложение моего ответа 20.07.2012
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..