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

Time:05-30

Here a have a generic type, but I don't understand why even though I didn't made the type T covariant, I can add subclasses of Player to my generics class and function which has an upper bound T: Player; so why is the subtyping preserved without using the out keyword; and because of this I can - wrongly- add a BaseballPlayer and GamesPlayer to a football team. 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)

CodePudding user response:

The mutable list you defined on this line:

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

is not a MutableList<FootballPlayer>. It is a MutableList<Player>, since you didn't specify its type, so the compiler used type inference to assume you want a MutableList<Player> to fit the constructor argument for your Team<Player> constructor.

So, it is valid to put any kind of Player into a MutableList<Player>, since it can only ever return items of type Player. It's still type safe.

If you had been explicit about the type, it would have been a compile error:

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

Or if you had omitted the type from the Team constructor, it would have assumed you wanted a Team<FootballPlayer> and you would have had an error when trying to add other types of player:

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
  • Related