Какая разница между A <: B и + B в Scala?

Какая разница между

[A <: B]

и

[+B]

в Scala?

Ответ 1

Q[A <: B] означает, что класс Q может принимать любой класс A, который является подклассом B.

Q[+B] означает, что Q может принимать любой класс, но если A является подклассом B, то Q[A] считается подклассом Q[B].

Q[+A <: B] означает, что класс Q может принимать только подклассы B, а также распространять отношение подкласса.

Первое полезно, когда вы хотите сделать что-то общее, но вам нужно полагаться на определенный набор методов в B. Например, если у вас есть класс Output с методом toFile, вы можете использовать этот метод в любом классе, который может быть передан в Q.

Второй полезен, когда вы хотите создавать коллекции, которые ведут себя так же, как и исходные классы. Если вы берете B и вы создаете подкласс A, вы можете передать A в любом месте, где ожидается B. Но если вы берете коллекцию B, Q[B], это правда, что вы всегда можете перейти в Q[A] вместо этого? В общем, нет; бывают случаи, когда это было бы неправильно. Но вы можете сказать, что это правильно, если использовать +B (ковариация; Q covaries - следует вместе с отношением наследования подклассов B).

Ответ 2

Я хотел бы расширить отличный ответ Рекса Керра с еще несколькими примерами: Пусть говорят, что у нас есть четыре класса:

 class Animal {}
 class Dog extends Animal {}

 class Car {}
 class SportsCar extends Car {}

Начнем с отклонения:

 case class List[+B](elements: B*) {} // simplification; covariance like in original List

 val animals: List[Animal] = List( new Dog(), new Animal() )
 val cars: List[Car] = List ( new Car(), new SportsCar() )

Как вы видите, List не заботится о том, содержит ли он Животные или Автомобили. Разработчики списка не применяли это, например. Только автомобили могут войти в списки.

Дополнительно:

case class Shelter(animals: List[Animal]) {}

val animalShelter: Shelter = Shelter( List(new Animal()): List[Animal] )
val dogShelter: Shelter = Shelter( List(new Dog()): List[Dog] )

Если функция ожидает параметр List[Animal], вы также можете передать List[Dog] в качестве аргумента функции. List[Dog] считается подклассом List[Animal] из-за ковариации List. Это не сработает, если List будет инвариантным.

Теперь на границах типов:

case class Barn[A <: Animal](animals: A*) {}

val animalBarn: Barn[Animal] = Barn( new Dog(), new Animal() )
val carBarn = Barn( new SportsCar() )
/* 
error: inferred type arguments [SportsCar] do not conform to method apply type parameter bounds [A <: Animal]
    val carBarn = Barn(new SportsCar())
                 ^
*/

Как вы можете видеть Barn - это коллекция, предназначенная только для животных. Здесь нет автомобилей.

Ответ 3

для моего понимания:


Первый - это связанный с параметром тип, в нашем случае верхний и нижний типы - это "параметр типа A, который является подтипом B (или самого B).


Вторая - это аннотация вариации для определения класса, в нашем случае ковариационная подклассов B


Scala: + Java:? расширяет T ковариационное подклассов

Scala: - Java:? супер T контравариантная подклассов

Ответ 4

Я нашел это сообщение в блоге, исследуя этот вопрос. Дает еще более глубокое объяснение дисперсии Scala, включая ее теоретическую основу в Теории категорий

http://blogs.atlassian.com/2013/01/covariance-and-contravariance-in-scala/