0
votes

In a typical Scala upperbound example

abstract class Animal {
  def name: String
}

abstract class Pet extends Animal {}

class Cat extends Pet {
  override def name: String = "Cat"
}

class Dog extends Pet {
  override def name: String = "Dog"
}

class Lion extends Animal {
  override def name: String = "Lion"
}

What is the difference between this

class PetContainer[P <: Pet](p: P) {
      def pet: P = p
    }

val dogContainer = new PetContainer[Dog](new Dog)
val catContainer = new PetContainer[Cat](new Cat)

and this?

class PetContainer1(p: Pet) {
      def pet: Pet = p
    }

val dogContainer1 = new PetContainer1(new Dog)
val catContainer1 = new PetContainer1(new Cat)

What is the advantage of using an upper type bound vs using the abstract class/trait directly?

1
Try adding a cat to a dog container in both examples. - Jörg W Mittag

1 Answers

3
votes

With upper bound you can have a collection of specific subtype - so limiting to only cats or dogs and you can get a specific subtype back from def pet. It's not true for PetContainer1.

Losing more accurate type info example:

val doggo: Dog = new Dog
val dogContainer1 = new PetContainer1(doggo)
// the following won't compile
// val getDoggoBack: Dog = dogContainer1.pet  

val dogContainer2 = new PetContainer[Dog](doggo)
// that works
val getDoggoBack: Dog = dogContainer2.pet  

You can't put a cat into dog container:

// this will not compile
new PetContainer[Dog](new Cat)

It would get more significant if you would be dealing with collections of multiple elements.