Как стирание Java-типа относится к '?' - PullRequest
2 голосов
/ 27 октября 2019

Надеюсь, после понимания общих границ я пытаюсь понять верхние и нижние границы подстановочных знаков. моя ссылка: https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html Я нашел там предложение, которое я могу понять: "Подстановочный знак может использоваться в различных ситуациях: в качестве типа параметра, поля или локальной переменной; "Поля и локальная переменная? не могу себе это представить. Почему такой важный источник не подчеркивает это простым примером?

Я пытаюсь понять, какой ссылочный компилятор Java заменяет (при стирании) '?'. Может быть, у меня есть большое недоразумение, и происходит любое стирание (поэтому все следующие примеры не имеют значения). В следующих примерах: 1.

public static void funcA (List<? extends Number>l)

2.

public static void funcB(List<? super Integer>l)

и есть ли разница между вторым примером и следующим кодом: 3.

public static <T extends Integer> funcC(List<? extends T>l)

И если между примером 2 и следующим есть различие: 4.

public static <T extends Integer> void funcC(List<T>l)

Ответы [ 2 ]

0 голосов
/ 29 октября 2019

Предисловие: Вы задали ряд вопросов о стирании типов, в том числе в чате. Хотя этот ответ будет касаться этого конкретного вопроса, он может ответить и на некоторые другие ваши вопросы (возможно, даже те, которые вы еще не задавали).

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


Что такое стирание типа?

Это было достаточно хорошо рассмотрено вдругие вопросы и ответы, поэтому я просто сошлюсь на них:


Каковы правилатипа стирания?

Правила стирания типа указаны в §4.6 Тип стирания Спецификации языка Java (JLS) :

Стирание типа - это отображение типов (возможно, включая параметризованные типы и переменные типов) на типы (которыеникогда не параметризованные типы или переменные типа). Пишем |T| для стирания типа T. Отображение стирания определяется следующим образом:

  • Стирание параметризованного типа ( §4.5 ) G<T1,...,Tn> равно |G|.

  • Удаление вложенного типа T.C равно |T|.C.

  • Удаление типа массива T[] равно |T|[].

  • Стирание переменной типа ( §4.4 ) - это стирание ее крайней левой границы.

  • Стирание любого другого типаэто сам тип.

Стирание типа также сопоставляет подпись ( §8.4.2 ) конструктора или метода с сигнатурой, которая не имеет параметризованных типов или типовпеременные. Стирание сигнатуры конструктора или метода s - это сигнатура, состоящая из того же имени, что и s, и стирания всех типов формальных параметров, приведенных в s.

Тип возврата метода( §8.4.5 ) и параметры типа универсального метода или конструктора ( §8.4.4 , §8.8.4 ) также стираются, еслисигнатура метода или конструктора стирается.

Удаление сигнатуры универсального метода не имеет параметров типа.

Для этого ответа нам следует сосредоточиться на первой и четвертой пунктах маркировки, которыескажем:

Стирание параметризованного типа ( §4.5 ) G<T1,...,Tn> равно |G|.

И:

Стирание переменной типа ( §4.4 ) - это стирание ее самой левой границы.

Соответственно. В частности, обратите внимание, что четвертая точка маркера только объясняет стирание переменных типа . Почему это важно? Я вернусь к этому.

Терминология

Понимание терминов важно для понимания того, как применяются правила:

  • Genericкласс

  • Общий интерфейс

  • Универсальный метод

  • Общий конструктор

    • Указано в §8.8.4. Общие конструкторы JLS.
    • A универсальный конструктор является конструктором, который объявляет одну или несколько переменных типа .
  • переменную типа

    • Указано в §4.4 Переменные типа JLS.
    • * Переменная типа представлена ​​параметром типа , объявленным вуниверсальный член.
    • Синтаксис переменной типа : {Annotation} TypeIdentifier
  • Тип параметра

    • Указано во многих разделах JLS. В каждом разделе, с которым я ссылаюсь для вышеупомянутых терминов, упоминается параметры типа .
    • параметр типа является объявлением переменной типа плюс ееbounds, для универсального члена.
    • Синтаксис параметра типа : {TypeParameterModifier} TypeIdentifier [TypeBound], где TypeParameterModifier расширяется до Annotation.
  • Параметризованный тип

    • Указано в §4.5 Параметризованные типы JLS.
    • A параметризованный тип - это фактическое использование универсального класса или универсального интерфейса с аргументами типа .
  • аргумент типа

Обратите внимание, что разницамежду параметром типа и аргументом типа аналогично тому, как Java различает параметр метода и аргумент. Когда вы объявляете метод как void bar(Object obj), Object obj является параметром. Однако, когда вы вызываете метод, такой как bar(someObjInstance), значение someObjInstance является аргументом.

Код Примеры терминологии

Просмотр некоторых примеров в коде может помочь понять, какие частикод, к которому относится каждый термин.

универсальный класс с параметрами типа

public class Foo<T extends CharSequence, U> {
    // class body...
}

Есть два параметра типа :

  1. T extends Charsequence

    • Переменная типа равна T.
    • Границы типа равны extends CharSequence
  2. U

    • Переменная типа равна U
    • Границы типа являются extends Object (неявно определены)

Код выглядит аналогично для универсальных интерфейсов.

Универсальный метод с параметром типа

public void <V extends Number> bar(V obj) {
   // method body...
}

Этот метод имеет один параметр типа :

  1. V extends Number
    • Переменная типа равна V.
    • Границы типа : extends Number.

Код выглядит похожим для универсальных конструкторов.

Параметризованный тип (без подстановочных знаков)

public <E extends Number> void bar(List<E> list) {
    // method body...
}

Существует один параметризованный тип :

  1. List<E>
    • Аргумент типа равен E.

Параметризованный тип(с подстановочным знаком)

public void bar(List<? extends Number> list) {
    // method body...
}

Существует один параметризованный тип :

  1. List<? extends Number>
    • Аргумент типа is ? extends Number

Вернуться к правилам

Как я уже говорил, важно отметить, что в правилах упоминается только удаление переменные типа . Это важно по той причине, что подстановочные знаки недопустимы в местах, где можно определить переменные типа (т. Е. В параметры типа ). Подстановочный знак может использоваться только в аргументе типа , который является частью параметризованного типа .

Удаление параметризованного типа имеет видпросто необработанный тип.


Когда имеет значение стирание типа?

В повседневной разработке стирания универсального кода практически не имеет значения. Единственный случай, когда вам нужно узнать, как стирание типов работает в деталях, - это работа с raw types . В идеальном и справедливом мире вы будете работать только с необработанными типами при работе с устаревшим кодом (начиная с дней до Java 5), ​​которые вы не можете изменить. Другими словами, если вы не вынуждены работать с необработанными типами, вы должны всегда надлежащим образом использовать дженерики.

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

public class Foo<T extend CharSequence, U> {

    private List<T> listField;
    private U objField;

    public void bar(List<? extends T> listParam) {
        // method body...
    }

    public U baz(T objParam) {
        // method body...
    }

    public <V extends Number> V qux(V objParam) {
        // method body...
    }

}

И, следуя вышеупомянутым правилам стирания типов, вот как выглядит вышеупомянутый класс впоследствии:

// the raw type of Foo
public class Foo {

    private List listField;
    private Object objField;

    public void bar(List listParam) {
        // method body...
    }

    public Object baz(CharSequence objParam) {
        // method body...
    }

    public Number qux(Number objParam) {
        // method body...
    }

}

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


Применение этих знаний к вашему вопросу

Кое-что из того, что мы узнали до сих пор, заключается в том, что подстановочные знаки могутиспользоваться только в аргументах типа и, следовательно, применимы только к параметризованным типам . Стирание параметризованного типа является просто необработанным типом. Если мы применим эти знания к вашим примерам, вы получите следующее:

  1. Пример # 1

    • Оригинал

      public static void funcA(List<? extends Number> l)
      
    • Стерто

      public static void funcA(List l)
      
  2. Пример # 2

    • Оригинал

      public static void funcB(List<? super Integer> l)
      
    • Стерты

      public static void funcB(List l) 
      
  3. Пример № 3

    • Оригинал (забыл указать тип возвращаемого вопроса, предполагая void)

      public static <T extends Integer> void funcC(List<? extends T> l)
      
    • Стерто

      public static void funcC(List l)
      
  4. Пример № 4

    • Оригинал

      public static <T extends Integer> void funcC(List<T> l)
      
    • Стирание

      public static void funcC(List l)
      

Усиление точки

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

public class Foo<T extends Number> {

    public void bar(T obj) {
        // method body...
    }

    public void baz(List<? extends T> list) {
        // method body...
    }

}

Метод bar имеет единственный параметр типа T. Параметр напрямую использует переменную типа . Стирание переменной типа - это стирание ее крайней левой границы, в данном случае Number. Это означает, что после стирания параметр метода равен Number.

Метод baz имеет единственный параметр типа List<? extends T>. Здесь переменная типа T используется в качестве верхней границы в аргументе type параметризованного типа . Другими словами, несмотря на то, что используется переменная типа , фактически здесь используется стирание с параметризованным типом . Это означает, что после стирания параметр метода равен List. Это может произойти, даже если аргумент типа был подстановочным знаком с ограничением снизу (например, List<? super T>), неограниченным подстановочным знаком (например, List<?>) или даже не подстановочным знаком (например, List<T>).

Как обрабатываются подстановочные знаки с помощью типа Erasure

Чтобы прямо ответить на ваш вопрос о том, как типовое стирание обрабатывает подстановочные знаки, вы получите эффективный ответ: Нет, не напрямую Подстановочные знаки просто исчезают (когда стирается параметризованный тип ) и не имеют значения в результирующем необработанном типе.

Подстановочные знаки обеспечивают гибкость для тех, кто использует универсальный API. Вот некоторые вопросы и ответы, которые касаются этой концепции:

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

0 голосов
/ 27 октября 2019

В примере 2 говорится, что вы можете передать List<? super Integer>, поэтому List<Integer> в порядке, но также List<Number> или List<Object>.

Однако в следующем примере вы можете передать List<? extends T>где <T extends Integer>. Так что на самом деле вы можете передать только List<Integer> и, если они существуют, любой подкласс Integer (который не существует, потому что Integer является окончательным).

Как вы можете видеть, они совершенно противоположны друг другу.

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

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

Стирание типов - это просто тот факт, что во время выполнения универсальных типов не существует во время выполнения. Generics только принудительно проверяет время компиляции. После компиляции у вас будет простой список.

...