Самореферентные общие типы

Рассмотрим следующий пример:

public final class Main<T extends Main<T>> {

    public static void main(String[] args) {
        Main<?> main = new Main<>();
    }
}

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

Main<?> main = new Main();    

Попытки без сырых типов не работают:

Main<?> main = new Main<?>();              // None of
Main<?> main = new Main<Main<?>>();        // these
Main<?> main = new Main<Main<Main<?>>>();  // compile

Так почему же работает оригинальная версия с бриллиантом? Что такое выведенный тип при написании Main<?> main = new Main<>();?

Является ли это вывод необработанного типа или он выводит какой-то бесконечно вложенный тип, подобный этому?

Main<Main<Main<Main<...>>>>

Ответы

Ответ 1

? in Main<?> является заполнителем, который может быть любым типом во время привязки.

Каждый ?, который вы пишете в источнике, может быть другого типа (упоминается в сообщениях об ошибках как capture#2-of ?), поэтому вы не можете назначить выражение Main<?> переменной любого выражаемого типа.

Алмазный оператор работает здесь, потому что его вывод типа запускается после захвата ? – ваш Main<> становится не Main<?>, а Main<capture#1 of ?> (предполагается, что Main<?> вы назначили его capture#1).

Другими словами, оператор алмаза является единственным синтаксисом, который может непосредственно указывать конкретный захват, так же как var в С# является единственным синтаксисом, который может напрямую указывать анонимный тип. (обратите внимание, что разрешение перегрузки с помощью вывода типа метода также может быть разрешено для определенных захватов)


Что касается кода, new Main<?>() (для любого захвата ?) является сокращением для Main<? extends Object> или, в вашем случае, Main<? extends Main<same ?>> (компилятор автоматически сжимает ? к ограничениям типа). Это становится ковариантным представлением Main<>, где параметр типа может быть конвертирован только в Main<?> (так как это может быть любой тип, поэтому вы не можете принять что-либо за пределами ограничения).

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

Ответ 2

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

final class Main<T extends Main<T>> {

    public static void main(String[] args) {
        Main<?> main = helper();
    }
    private static <T extends Main<T>> Main<T> helper() {
        return new Main<T>();
    }
}