I’m currently studying generics and I’m trying to understand variance, “in”, and “out”. However, it seems to me that there are some inaccuracies in the book I’m studying. The book I’m reading gives the following code as an intentional example of an error that occurs:
class Barrel <T> (var item: T)
open class Loot(val value: Int)
class Fedora (val name: String, value: Int) : Loot(value)
class Coin (value: Int) : Loot(value)
fun main() {
var fedoraBarrel : Barrel<Fedora> = Barrel(Fedora("a generic-looking fedora", 15))
var lootBarrel: Barrel<Loot> = Barrel(Coin(15))
lootBarrel = fedoraBarrel //type mismatch error here
}
Because this code hasn’t specified the variance for the generic Barrel()
class, the last line-- lootBarrel = fedoraBarrel
--gives a type mismatch error.
The chapter attempts to explain why by walking through what COULD happen if the compiler allowed this code and this code succeeded.
Forgive my longevity here, I’d just rather quote from the book than attempt to summarize due to my poor understanding of the concept:
If the compiler allowed you to assign the fedoraBarrel instance to the lootBarrel variable, lootBarrel would then point to fedoraBarrel, and it would be possible to interface with fedoraBarrel’s item as Loot, instead of Fedora (because of lootBarrel’s type, Barrel<Loot>).
For example, a coin is valid Loot, so it would be possible to assign a coin to lootBarrel.item (which points to fedoraBarrel).
... fun main() { var fedoraBarrel : Barrel<Fedora> = Barrel(Fedora("a generic-looking fedora", 15)) var lootBarrel: Barrel<Loot> = Barrel(Coin(15)) lootBarrel = fedoraBarrel lootBarrel.item = Coin(15) //new line of code added here. This is also a type mismatch. }
So far so good. I understand this. lootBarrel’s declared type is Barrel<Loot>, and changing the value of lootBarrel to equal fedoraBarrel doesn’t change it’s declared type.
- Quick clarification regarding the above quote though: lootBarrel.item doesn’t point to “fedoraBarrel”, right? lootBarrel points to fedoraBarrel, and lootBarrel.item would technically equal fedoraBarrel.item, correct? The difference being the type: lootBarrel.item, as with fedoraBarrel.item, point to a Fedora, whereas fedoraBarrel is a Barrel<Fedora>, and lootBarrel is a Barrel<Loot>.
Regarding the new line of code lootBarrel.item = Coin(15)
, I find this interesting. We went from assigning lootBarrel to a Barrel(Coin(15)), then to a fedoraBarrel, then back to coins by assigning lootBarrel.item to Coin(15). Seems meaningless.
It’s also interesting that this new line of code gives a NEW type mismatch error, stating that it expected a Fedora, and got a Coin instead, despite the fact that setting lootBarrel = fedoraBarrel technically failed because of a type mismatch.
- At this point I’d like to ask if I’m missing something. I’ve noticed several times here that we are accessing and making changes to the .item property of this lootBarrel object, not to the object itself. Is this pertinent to following the chapter’s logic on why the compiler doesn’t allow the lootBarrel = fedoraBarrel assignment?
Finally, the chapter continues:
Now, suppose you tried to access fedoraBarrel.item, expecting a fedora:
... fun main() { var fedoraBarrel : Barrel<Fedora> = Barrel(Fedora("a generic-looking fedora", 15)) var lootBarrel: Barrel<Loot> = Barrel(Coin(15)) lootBarrel = fedoraBarrel lootBarrel.item = Coin(15) val myFedora: Fedora = fedoraBarrel.item //new line of code added here }
The compiler would then be faced with a type mismatch - fedoraBarrel.item is not a Fedora, it is a Coin - and you would be faced with a ClassCastException. This is the problem that arises, and the reason the assignment is not allowed by the compiler.
- This is where I call B.S. We have made absolutely no changes to fedoraBarrel or it’s .item property. fedoraBarrel.item is still a Fedora, and was never assigned a Coin. Indeed, this new line of code does compile without errors. So, am I misunderstanding something, or is this an inaccuracy, because this is really making it hard for me to understand this concept.