Ответ 1
Ладно, я решил, что мне нужно принять это вместо того, чтобы просто публиковать комментарии. Извините, это будет долго, если вы хотите, чтобы TL; DR пропустил до конца.
Как сказал Рэндалл Шульц, здесь _
сокращение от экзистенциального типа. А именно,
class Foo[T <: List[_]]
это сокращение для
class Foo[T <: List[Z] forSome { type Z }]
Обратите внимание, что вопреки тому, что упоминает ответ Рэндалла Шульца (полное раскрытие: я тоже ошибся в более ранней версии этого поста, спасибо Джесперу Норденбергу за указание на это), это не то же самое, что:
class Foo[T <: List[Z]] forSome { type Z }
и это не то же самое, что:
class Foo[T <: List[Z forSome { type Z }]]
Осторожно, легко ошибиться (как показывает мой предыдущий обман): автор статьи, на которую ссылается ответ Рэндалла Шульца, сам ошибся (см. Комментарии) и исправил ее позже. Моя главная проблема в этой статье состоит в том, что в показанном примере использование экзистенциалов должно избавить нас от проблемы типирования, но это не так. Проверьте код и попробуйте скомпилировать compileAndRun(helloWorldVM("Test"))
или compileAndRun(intVM(42))
. Да, не компилируется. Простое создание compileAndRun
generic в A
приведет к компиляции кода, и это будет намного проще. Короче говоря, это, вероятно, не лучшая статья, чтобы узнать об экзистенциалах и для чего они хороши (сам автор признается в комментарии, что статья "нуждается в уборке").
Поэтому я бы порекомендовал прочитать эту статью: http://www.artima.com/scalazine/articles/scalas_type_system.html, в частности, разделы "Экзистенциальные типы" и "Дисперсия в Java и Scala".
Важный момент, который вы должны получить из этой статьи, заключается в том, что экзистенциалы полезны (помимо возможности иметь дело с общими классами Java) при работе с нековариантными типами. Вот пример.
case class Greets[T]( private val name: T ) {
def hello() { println("Hello " + name) }
def getName: T = name
}
Этот класс является универсальным (обратите внимание, что он инвариантен), но мы можем видеть, что hello
действительно не использует параметр типа (в отличие от getName
), поэтому, если я получаю экземпляр Greets
я всегда должен вызывать это, независимо от того, что T
Если я хочу определить метод, который принимает экземпляр Greets
и просто вызывает его метод hello
, я мог бы попробовать это:
def sayHi1( g: Greets[T] ) { g.hello() } // Does not compile
Конечно же, это не компилируется, так как T
появляется из ниоткуда.
Хорошо, тогда давайте сделаем метод общим:
def sayHi2[T]( g: Greets[T] ) { g.hello() }
sayHi2( Greets("John"))
sayHi2( Greets('Jack))
Отлично, это работает. Мы могли бы также использовать экзистенциалы здесь:
def sayHi3( g: Greets[_] ) { g.hello() }
sayHi3( Greets("John"))
sayHi3( Greets('Jack))
Тоже работает Таким образом, в целом, здесь нет реальной выгоды от использования экзистенциального (как в sayHi3
) параметра типа (как в sayHi2
).
Однако это изменяется, если Greets
появляется как параметр типа для другого универсального класса. Например, мы хотим сохранить несколько экземпляров Greets
(с разными T
) в списке. Давай попробуем это:
val greets1: Greets[String] = Greets("John")
val greets2: Greets[Symbol] = Greets('Jack)
val greetsList1: List[Greets[Any]] = List( greets1, greets2 ) // Does not compile
Последняя строка не компилируется, потому что Greets
является инвариантом, поэтому Greets[String]
и Greets[Symbol]
не могут рассматриваться как Greets[Any]
хотя String
и Symbol
оба расширяют Any
.
Хорошо, давайте попробуем с экзистенцией, используя сокращенную запись _
:
val greetsList2: List[Greets[_]] = List( greets1, greets2 ) // Compiles fine, yeah
Это прекрасно компилируется, и вы можете сделать, как и ожидалось:
greetsSet foreach (_.hello)
Теперь, помните, что причина, по которой у нас возникла проблема проверки типов, заключалась в том, что Greets
инвариантен. Если бы он был превращен в ковариантный класс (class Greets[+T]
), то все бы работало "из коробки", и нам никогда бы не понадобились экзистенциальные компоненты.
Итак, чтобы подвести итог, экзистенциалы полезны для работы с универсальными инвариантными классами, но если универсальный класс не должен появляться сам как параметр типа для другого универсального класса, есть вероятность, что вам не нужны экзистенциалы и простое добавление параметра типа к твоему методу будет работать
Теперь вернитесь (наконец, я знаю!) К вашему конкретному вопросу, касающемуся
class Foo[T <: List[_]]
Поскольку List
является ковариантным, это для всех намерений и целей такое же, как просто сказать:
class Foo[T <: List[Any]]
Так что в этом случае использование любого обозначения на самом деле просто вопрос стиля.
Однако, если вы замените List
на Set
, все изменится:
class Foo[T <: Set[_]]
Set
инвариантен, и поэтому мы находимся в той же ситуации, что и с классом Greets
из моего примера. Таким образом, вышесказанное действительно очень отличается от
class Foo[T <: Set[Any]]