9.2. Ograniczenia górne
Definicja typu ogólnego może zawierać ograniczenia dotyczące parametrów typu. Ograniczenie górne G typu T oznaczamy T <: G. W miejscu tak określonego parametru typu T można użyć typu G lub typu będącego jego podtypem. Poniżej przedstawiony jest przykład, w którym takie ograniczenie typu znajduje zastosowanie.
W pliku TypeParameters2.scala znajdują się definicje klas Person, która reprezentuje osobę oraz dziedziczących z niej klas Man, reprezentującej mężczyznę i Woman, reprezentującej kobietę.
Plik TypeParameters3.scala: abstract class Person(val name: String) class Man(name: String) extends Person(name) class Woman(name: String) extends Person(name)
W pliku TypeParameters4.scala zdefiniowana jest klasa Greeting, służąca do drukowania na ekranie pozdrowień dla poszczególnych osób.
Plik TypeParameters4.scala: class Greeting(greeting: String) { def apply(person: Person) = println(greeting+" "+person.name+"!") }
Korzystając z klasy Greeting można tworzyć i używać obiekty generujące pozdrowienia.
scala> val hello = new Greeting("Hello") hello: Greeting = Greeting@199c6b5 scala> hello(new Man("Peter")) Hello Peter! scala> hello(new Woman("Mary")) Hello Mary!
Spróbujmy teraz utworzyć obiekt, którego pozdrowienia są skierowane do mężczyzn.
scala> val helloMr = new Greeting("Hello Mr.") helloMr: Greeting = Greeting@423aa2
Niestety, tego obiektu można używać zarówno w przypadku obiektów reprezentujących mężczyzn, jak i kobiety.
scala> helloMr(new Man("Peter")) Hello Mr. Peter! scala> helloMr(new Woman("Mary")) Hello Mr. Mary!
Plik TypeParameters5.scala zawiera definicję klasy Greeting2, zawierającą ograniczenie typów, jakie może przyjmować parametr metody apply.
Plik TypeParameters5.scala: class Greeting2[T <: Person](greeting: String) { def apply(person: T) = println(greeting+" "+person.name+"!") }
W metodzie apply znajduje się odwołanie do składowej o nazwie name parametru person typu T. To odwołanie jest prawidłowe, gdyż typ T jest ograniczony z góry klasą Person, a ta klasa ma zdefiniowaną składową o nazwie name. Możemy teraz utworzyć obiekt, który pozwoli na generowanie pozdrowień tylko dla mężczyzn.
scala> val helloMr2 = new Greeting2[Man]("Hello Mr.") helloMr2: Greeting2[Man] = Greeting2@a9d961 scala> helloMr2(new Man("Peter")) Hello Mr. Peter!
scala> helloMr2(new Woman("Mary")) <console>:12: error: type mismatch; found : Woman required: Man helloMr2(new Woman("Mary")) ^
Ostatnie z wyrażeń nie kompiluje się, gdyż metoda obiektu helloMr2 przyjmuje tylko obiekty należące do klasy Man lub do jej podklas, natomiast obiekt mary jest typu Woman.
Definicja obiektu helloMr2 jest prawidłowa, gdyż klasa Man jest podklasą klasy Person. Próba zdefiniowania obiektu klasy Greetings2 z użyciem jako parametru typu klasy, która nie dziedziczy z Person, nie udaje się.
scala> val hello3 = new Greeting2[Int]("Hello Int") <console>:10: error: type arguments [Int] do not conform to class Greeting2's type parameter bounds [T <: Person] val hello3 = new Greeting2[Int]("Hello Int") ^ <console>:10: error: type arguments [Int] do not conform to class Greeting2's type parameter bounds [T <: Person] val hello3 = new Greeting2[Int]("Hello Int") ^
Poniższy przykład pokazuje definicję obiektu hello4, w której nie została określona jawnie wartość parametru T. Kompilator przyjął, że wartością T jest Nothing. W konsekwencji, tak zdefiniowany obiekt nie może być użyty ani z obiektami reprezentującymi meżczyzn, ani z obiektami reprezentującymi kobiety.
scala> val hello4 = new Greeting2("Hello inferred") hello4: Greeting2[Nothing] = Greeting2@84a8e1 scala> hello4(peter) <console>:12: error: not found: value peter hello4(peter) ^ scala> hello4(mary) <console>:12: error: not found: value mary hello4(mary) ^