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
Plik Writer.scala:
class Writer[-T] {
def write(x: T) { println(x) }
}
