9.6. Kontrawariantne parametry typu

Można poprzedzić parametr typu ogólnego znakiem - co oznacza, że jest on kontrawariantny. Taka sytuacja ma miejsce w przypadku definicji klasy Writer i oznacza, że w przypadku klas A i rozszerzającej ją B, Writer[A] jest podtypem dla Writer[B].

Plik Writer.scala:
class Writer[-T] {
  def write(x: T) { println(x) }
}

W związku z tym Writer[Any] jest podtypem Writer[String], a zatem obiekt typu Writer[Any] może być użyty w miejscu, w którym wymagany jest obiekt typu Writer[String].

scala> val anyWriter: Writer[Any] = new Writer
anyWriter: Writer[Any] = Writer@1a34d52

scala> val stringWriter: Writer[String] = anyWriter
stringWriter: Writer[String] = Writer@1a34d52

scala> stringWriter.write("Hi")
Hi

Powróćmy jeszcze do przykładu z dzieckiem i spróbujmy wykonać inną zmianę. Utwórzmy kosz na śmieci, do którego można wyrzucać nie tylko papier, ale również wszelkie inne obiekty.

scala> val universalWasteBin = new WasteBin[Any]("universal waste bin")
universalWasteBin: WasteBin[Any] = universal waste bin

Próba użycia takiego obiektu z klasą DrawingChild nie udaje się.

scala> val paperBox = new Box[Paper]("paper box",new Paper)
paperBox: Box[Paper] = paper box

scala> val mary = new DrawingChild("Mary",paperBox,universalWasteBin)
<console>:12: error: type mismatch;
 found   : WasteBin[Any]
 required: WasteBin[Paper]
Note: Any >: Paper, but class WasteBin is invariant in type T.
You may wish to define T as -T instead. (SLS 4.5)
       val mary = new DrawingChild("Mary",paperBox,universalWasteBin)
                                                   ^

Obiekt UniversalWasteBin jest typu WasteBin[Any], a nie WasteBin[Paper]. Taki obiekt nie może być użyty w miejscu, w którym jest wymagany obiekt typu WasteBin[Paper], gdyż WasteBin[Any] nie jest podtypem WasteBin[Paper].

Problem znowu leży w tym, że parametr T w definicji typu ogólnego WasteBin jest niezmienniczy. Komunikat błędu informuje nas, że wymagany jest typ WasteBin[Paper], a próbowaliśmy dostarczyć obiekt typu WasteBin[Any]. Zauważmy, że aby powyższa operacja udała się, między typami WasteBin[Paper] i WasteBin[Any] powinna zachodzić relacja przeciwstawna do relacji zachodzącej między typami Paper i Any. Paper jest podtypem klasy Any, natomiast my potrzebujemy, aby typ WasteBin[Paper] był nadtypem typu WasteBin[Any]. Potrzebujemy więc parametru typu, który zachowuje się odwrotnie do parametru kowariantnego. Potrzebujemy kontrawariantnego parametru typu. W przeciwieństwie do parametrów kowariantnych, oznaczanych znakiem +, parametr kontrawariantny oznaczany jest znakiem -. W pliku ContravariantWasteBin.scala znajduje się poprawiona definicja typu modelującego kosz na śmieci, definiująca go jako typ kontrawariantny ze względu na parametr typu T.

Plik ContravariantWasteBin.scala:
class ContravariantWasteBin[-T](name: String) {
  def throwAway(t: T) =
    println("Throwing away "+t+" into the "+this)
  override def toString = name
}

Typ ContravariantWasteBin[Paper] jest nadtypem typu ContravariantWasteBin[Any], a zatem obiekt typu ContravariantWasteBin[Any] może być użyty w miejscu, w którym wymagany jest obiekt typu ContravariantWasteBin[Paper].

scala> val universalWasteBin: ContravariantWasteBin[Paper] =
     |   new ContravariantWasteBin[Any]("universal waste bin")
universalWasteBin: ContravariantWasteBin[Paper] = universal waste bin

Plik DrawingChild3.scala zawiera zmienioną wersję klasy modelującej dziecko, używającą typu ContravariantWasteBin[Paper].

Plik DrawingChild3.scala:
class DrawingChild3(name: String,
  box: Box[Paper],
  wasteBin: ContravariantWasteBin[Paper]) {
  def draw = {
    val paper = box.take
    println("Drawing on "+paper)
    wasteBin.throwAway(paper)
  }
  override def toString = name
}

Teraz możemy skutecznie utworzyć obiekt mary.

scala> val paperBox = new Box[Paper]("paper box",new Paper)
paperBox: Box[Paper] = paper box

scala> val universalWasteBin =
     |   new ContravariantWasteBin[Any]("universal waste bin")
universalWasteBin: ContravariantWasteBin[Any] = universal waste bin

scala> val mary = new DrawingChild3("Mary",paperBox,universalWasteBin)
mary: DrawingChild3 = Mary

scala> mary.draw
Taking paper from the paper box
Drawing on paper
Throwing away paper into the universal waste bin

Język programowania Scala Wydanie 2. Copyright © Grzegorz Balcerek 2016

Licencja Creative Commons

Ten utwór jest dostępny na licencji Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0 Międzynarodowe.

Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners.