ОК, я подумал, что мне нужно взять его на себя, а не просто размещать комментарии. Извините, это будет долго, если вы хотите, чтобы TLDR пропустил до конца.
Как сказал Рэндалл Шульц, здесь _
является сокращением для экзистенциального типа. А именно,
class Foo[T <: List[_]]
является обобщающим для
class Foo[T <: List[Z] forSome { type Z }]
Обратите внимание, что вопреки тому, что упоминает ответ Рэндалла Shulz (полное раскрытие: я получил это тоже неправильно в более ранней версии Fo этот пост, благодаря Jesper Норденберг для указания его) это не то же самое, как:
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
I всегда должен иметь возможность назвать это, независимо от того, 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
ковариантен, это во всех отношениях и purp ose то же самое, что и просто:
class Foo[T <: List[Any]]
Так что в этом случае использование любой нотации - это действительно вопрос стиля.
Однако, если заменить List
с Set
, все меняется:
class Foo[T <: Set[_]]
Set
инвариантно и, таким образом, мы находимся в той же ситуации, как и с Greets
класса из моего примера. Таким образом, вышесказанное действительно сильно отличается от
class Foo[T <: Set[Any]]
Ограничение типа '': Any' никогда ничего не меняет. Каждый тип Scala - '<: Any'. –