Вопрос по java, wildcard, generics, jls – Как JLS определяет, что подстановочные знаки не могут быть формально использованы внутри методов?

4

Я всегда интересовался каким-то странным аспектом обобщений Java и использованием подстановочных знаков. Допустим, у меня есть следующий API:

public interface X<E> {
    E get();
    E set(E e);
}

А затем, скажем, мы объявили следующие методы:

public class Foo {
    public void foo(X<?> x) {
        // This does not compile:
        x.set(x.get());
    }

    public <T> void bar(X<T> x) {
        // This compiles:
        x.set(x.get());
    }
}

Из моего & quot; интуитивного & quot; понимание,X<?> на самом деле так же, какX<T>разве что неизвестно<T> являетсяformally неизвестный клиенту код. Но внутриfoo()Я бы предположил, что компилятор может сделать вывод (псевдо-JLS-код)<T0> := <?>[0], <T1> := <?>[1], etc..., Это то, что большинство программистов делают явно и интуитивно. Они делегируют частному вспомогательному методу, который приводит к большому количеству бесполезного кода котельной пластины:

public class Foo {
    public void foo(X<?> x) {
        foo0(x);
    }

    private <T> void foo0(X<T> x) {
        x.set(x.get());
    }
}

Другой пример:

public class Foo {
    public void foo() {
        // Assuming I could instanciate X
        X<?> x = new X<Object>();
        // Here, I cannot simply add a generic type to foo():
        // I have no choice but to introduce a useless foo0() helper method
        x.set(x.get());
    }
}

Другими словами, компилятор знает, что подстановочный знак вx.set() формально такой же, как подстановочный знак вx.get(), Почему он не может использовать эту информацию? Есть ли формальный аспект вJLS, что объясняет отсутствие функции компилятора?

@EJB:How. Why будет открытым вопросом, не подходящим для переполнения стека Lukas Eder
Ты спрашиваешьhow или жеwhy? user207421
@LukasEder Так почему же «почему»? появиться в теле вашего вопроса? user207421
@JBNizet: Хороший вопрос, я добавлю еще один пример, который должен быть более понятным, поскольку это не относится только к подстановочным знакам, объявленным в сигнатурах методов. Вы можете столкнуться с той же проблемой, если у вас есть локальные символы подстановки Lukas Eder
Разве это не просто ошибка проектирования, чтобы не параметризовать метод foo, так же, как параметризован foo0? JB Nizet

Ваш Ответ

3   ответа
5

Вы задаетесь вопросомwhy isn't it supported? Не менее актуальный вопрос,why should it be supported?

Как подстановочные знаки работают, учитывая декларациюX<? extends Y> xтип выраженияx.get() не является? extends Y или некоторый тип, выведенный компилятором, ноY, Сейчас,Y не присваивается? extends Yтак что вы не можете пройтиx.get() вx.set которая имеет эффективную подписьvoid set(? extends Y e), Единственное, что вы могли бы на самом деле передать этоnull, который назначается каждому ссылочному типу.

Я довольно внимательно слежу за списками рассылки разработчиков JDK, и общий дух заключается в том, что каждая функция, указанная в JLS, должна иметь свой вес - соотношение затрат и выгод должно быть тяжелым с точки зрения выгоды. Теперь отслеживать происхождение значения было бы вполне возможно, но это решило бы только один особый случай, у которого есть несколько других способов его решения. Рассмотрим эти классы:

class ClientOfX {
    X<?> member;
    void a(X<?> param) {
        param.set(param.get());
    }
    void b() {
        X<?> local = new XImpl<Object>();
        local.set(local.get());
    }
    void c() {
        member.set(member.get());
    }
    void d() {
        ClassWeCantChange.x.set(ClassWeCantChange.x.get());
    }
}

class ClassWeCantChange {
    public static X<?> x;
}

Это, очевидно, не компилируется, но с вашим предложенным улучшением, это так. Однако мы можем получить три из четырех методов компиляции без изменения JLS:

  1. For a, when we receive the generic object as a parameter, we can make the method generic and receive an X<T> instead of X<?>.
  2. For b and c, when using a locally declared reference, we don't need to declare the reference with a wildcard (Eric Lippert of Microsoft's C# compiler team calls this situation "if it hurts when you do that, then don't do that!").
  3. For d, when using a reference whose declaration we can't control, we have no option but to write a helper method.

Таким образом, более разумная система типов действительно может помочь только в том случае, когда мы не можем управлять объявлением переменной. И в этой ситуации случай для выполнения того, что вы хотите сделать, довольно схематичен - сам подстановочный знак универсального типа обычно означает, что объект либо предназначен для работы только как производитель (? extends Something) или потребитель (? super Something) объектов, а не обоих.

Версия TL; DR заключается в том, что она принесет мало пользы (и потребует, чтобы подстановочные знаки были чем-то другим, чем они есть сегодня!), Добавив при этом сложности в JLS. Если кто-то придумает очень убедительный аргумент, чтобы добавить его, спецификация может быть расширена - но как только она появится, она будет существовать вечно, и это может вызвать непредвиденные проблемы в будущем, поэтому оставьте это, пока вам действительно не понадобится Это.

Edit: Комментарии содержат более подробное обсуждение о том, что такое подстановочные знаки, а какие нет.

Обратите внимание, делоa может привести к совершенно новому набору угловых случаев, когда задействована перегрузка метода. Очень интересный случай можно увидеть здесь:stackoverflow.com/questions/5361513/…, Так что в JLS,a(original) а такжеa(patched) это совершенно разные методы подписи ... Lukas Eder
Это хороший ответ, объясняющий, почему такая функция могла быть опущена. Я не совсем согласен сcost/benefit ratio в этом конкретном случае, хотя я не понимаю деталей указания чего-либо подобного, поэтому это может быть оправдано. Однако я также ищу формальную спецификацию подстановочных знаков и то, как они объясняют, что в текущей версии JLS это действительно невозможно ... Lukas Eder
Для случая "а", почемуgenerics documentation используйте список & lt;? & gt; в качестве параметра и укажите вспомогательный метод в качестве решения, когда вы можете просто использовать List & lt; T & gt; как параметр
@Lukas Eder: самое близкое, что я могу найти, напоминающее определение подстановочных знаков в JLS, находится вsection 4.5.1: Types Arguments and Wildcards: Type arguments may be either reference types or wildcards. То есть подстановочные знаки могут использоваться только в качестве аргументов типов и больше похожи на предположения о типах, чем на действительные типы. Итак, когда у вас есть декларацияX<?> x, типx.get() ISN & APOS; т? extends Objectэто подтипObject& Quot ;. Таким образом, подстановочный знак «тип» не несет - потому что естьis нет подстановочного типа.
... что означает, что нет объяснения, почему такие вещи, какx.set(x.get()) не будет работать - нет причин, почему этоshould Работа. Выcould заставить его работать, но для этого нужно, чтобы подстановочные знаки отличались от того, чем они являются сейчас. Этого нет в спецификации, потому что если бы каждая строка, которая не является семантически правильной Java, была в спецификации, спецификация была бы бесконечно длинной.
1

Не будучи вовлеченным в написание JLS, я предпочитаю простоту. Вообразите глупые, но (синтаксически) допустимые выражения, такие как(x = new X<Integer>()).set(x.get());, Отслеживание захвата подстановочных знаков может быть сложно.

Я думаю о захвате подстановочных знаков не как шаблон, а как что-то сделанное правильно. Это признак того, что в вашем API нет параметров типа, которые не нужны клиентскому коду.

Редактировать: текущее поведение указано вJLS & # xA7; 4.5.2,x получает конвертированный захват для каждого доступа члена - один раз дляx.set(...) и один раз дляx.get(), Захваты различны и, следовательно, не известно, чтобы представлять один и тот же тип

API, да. Реализация, нет. Я часто сталкиваюсь с этой проблемой. Как хорошо объяснил @gustafc, я могу обойти проблему хитростями. Все же они - уловки. Я задаюсь вопросом о частях JLS, которые определяют текущее поведение, не обязательно предполагаемое обоснование этого. Lukas Eder
@LukasEder Добавлена ссылка на соответствующий абзац.
2

Я только что видел эту специфическую реализациюCollections.swap():

public static void swap(List<?> list, int i, int j) {
    final List l = list;
    l.set(i, l.set(j, l.get(i)));
}

Ребята из JDK прибегают к сырому типу, чтобы справиться с этим локально. Для меня это сильное утверждение, указывающее, что это, вероятно,should поддерживаться компилятором, но по какой-то причине (например, из-за нехватки времени для формального определения этого) просто не было сделано

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