为什么在这个例子中保留子类型而不使用 out 关键字?

Why is subtyping preserved in this example without using the out keyword?

这里有一个泛型,但我不明白为什么即使我没有使类型 T 协变,我也可以将 Player 的子classes 添加到我的泛型中class 和具有上限 T 的函数:Player;那么为什么在不使用 out 关键字的情况下保留子类型;因此,我可以 - 错误地 - 将 BaseballPlayer 和 GamesPlayer 添加到足球队。 1

class Team<T : Player>(val name: String, private val players: MutableList<T>) {
  fun addPlayers(player: T) {
    if (players.contains(player)) {
      println("Player: ${(player as Player).name} is already in the team.")
    } else {
      players.add(player)
      println("Player: {(player as Player).name} was added to the team.")
    }
  }
}

open class Player(open val name: String)

data class FootballPlayer(override val name: String) : Player(name)
data class BaseballPlayer(override val name: String) : Player(name)
data class GamesPlayer(override val name: String) : Player(name)

val footballTeam = Team<Player>(
  "Football Team",
  mutableListOf(FootballPlayer("Player 1"), FootballPlayer("Player 2"))
)

val baseballPlayer = BaseballPlayer("Baseball Player")
val footballPlayer = FootballPlayer("Football Player")
val gamesPlayer = GamesPlayer("Games Player")

footballTeam.addPlayers(baseballPlayer)
footballTeam.addPlayers(footballPlayer)
footballTeam.addPlayers(gamesPlayer)

您在此行定义的可变列表:

mutableListOf(FootballPlayer("Player 1"), FootballPlayer("Player 2"))

不是 MutableList<FootballPlayer>。它是一个 MutableList<Player>,因为你没有指定它的类型,所以编译器使用类型推断来假设你想要一个 MutableList<Player> 来适合你的 Team<Player> 构造函数的构造函数参数。

因此,将任何类型的 Player 放入 MutableList<Player> 中都是有效的,因为它只能 return 类型 Player 的项目。它仍然是类型安全的。

如果您明确说明了类型,那将是一个编译错误:

val footballTeam = Team<Player>(
  "Football Team",
  mutableListOf<FootballPlayer>(FootballPlayer("Player 1"), FootballPlayer("Player 2"))
  //error, expected MutableList<Player>
)

或者,如果您在团队构造函数中省略了类型,它会假定您想要一个 Team<FootballPlayer>,并且在尝试添加其他类型的玩家时会出现错误:

val footballTeam = Team(
  "Football Team",
  mutableListOf(FootballPlayer("Player 1"), FootballPlayer("Player 2"))
)
// ^^ is a Team<FootballPlayer> because of type inferrence.

footballTeam.addPlayers(BaseballPlayer("Foo")) // error, expected FootballPlayer