// --- Eläinten yhteinen yliluokka: abstract class Eläin (nimi:String) { def ääntele: Unit // abstrakti metodi override def toString = nimi // korvattaessa "override" pakollinen! } // --- Aliluokan määrittely, huomaa parametrivälitys: class Kissa(nimi:String, naukumistiheys:Int) extends Eläin(nimi) { override def ääntele = println("Miau") override def toString = super.toString + "-" + naukumistiheys } val kissa = new Kissa("Missu", 7) println(kissa) // Missu-7 kissa.ääntele // Miau // --- Pari imettäväisiin kuuluvaa eläinlajia: class Hevonen(nimi:String) extends Eläin(nimi) { override def ääntele = println("Ihahaa") } val hevonen = new Hevonen("Polle") println(hevonen) // Polle hevonen.ääntele // Ihahaa class Nauta(nimi:String) extends Eläin(nimi) { override def ääntele = println("Ammuu") } val nauta = new Nauta("Julle") println(nauta) // Julle nauta.ääntele // Ammuu // --- Kokeillaan polymorfismia: var x:Eläin = new Kissa("Töpö", 2) println(x) // Töpö-2 x.ääntele // Miau x = new Hevonen("Valma") println(x) // Valma x.ääntele // Ihahaa x = new Nauta("Muurikki") println(x) // Muurikki x.ääntele // Ammuu // -- Ja sitten tehdään "traitti" lypsäville eläimille: trait Lypsävä { def lypsä = {0.0} // tässä oletustoteutus; myös abstrakti metodi "def lypsä:Double" olisi mahdollinen } // Ja pari lypsävää, joihin tuo "traitti" "mixataan": class Lehmä(nimi:String) extends Nauta(nimi) with Lypsävä { override def lypsä = 3.14 * nimi.length // maidon määrä riippuu nimen pituudesta... } val mansikki = new Lehmä("Mansikki") println(mansikki) // Mansikki mansikki.ääntele // Ammuu println(mansikki.lypsä) // 25.12 class Tamma(nimi:String) extends Hevonen(nimi) with Lypsävä { override def lypsä = 1.23 * nimi.length // vähemmän maitoa kuin lehmältä } val tammukka = new Tamma("Tammukka") println(tammukka) // Tammukka tammukka.ääntele // Ihahaa println(tammukka.lypsä) // 9.84 // Lopuksi tehdään juustoa mistä tahansa "traitin saaneesta": def juustoa(tuotantoeläin:Lypsävä):Double = {42 * tuotantoeläin.lypsä} println(juustoa(mansikki)) // 1055.04 println(juustoa(tammukka)) // 413.28
"Traitti" voi sisältää sekä toteutettuja metodeita (jotka peritään) sekä abstrakteja metodeita (jotka on pakko toteuttaa, ellei ole ohjelmoimassa abstraktia luokkaa). Peritty toteutettu metodikin toki voidaan korvata:
// --- Eläinten yhteinen yliluokka: abstract class Eläin (nimi:String) { def ääntele: Unit // abstrakti metodi override def toString = nimi // korvattaessa "override" pakollinen } // --- Pari imettäväisiin kuuluvaa eläinlajia: class Hevonen(nimi:String) extends Eläin(nimi) { override def ääntele = println("Ihahaa") } class Nauta(nimi:String) extends Eläin(nimi) { override def ääntele = println("Ammuu") } // -- Ja sitten tehdään hienompi "traitti" lypsäville eläimille: trait Lypsävä { def lypsä(n:String) = 3.14 * n.length // ei-abstrakti metodi def potkaise:Unit // abstrakti metodi eli Javan interfacen kaltainen "vaatimus" } // Ja pari lypsävää, joihin tuo "traitti" "mixataan" "in": class Lehmä(nimi:String) extends Nauta(nimi) with Lypsävä { // lypsä(n:String) peritään traitista, so. oletustoteutus def potkaise = println("Lehmä potkaisee") } val mansikki = new Lehmä("Mansikki") println(mansikki) // Mansikki mansikki.ääntele // Ammuu println(mansikki.lypsä(mansikki.toString)) // 25.12 mansikki.potkaise // Lehmä potkaisee class Tamma(nimi:String) extends Hevonen(nimi) with Lypsävä { override def lypsä(n:String) = 1.23 * n.length // korvataan peritty omalla def potkaise = println("Tamma potkaisee") } val tammukka = new Tamma("Tammukka") println(tammukka) // Tammukka tammukka.ääntele // Ihahaa println(tammukka.lypsä(tammukka.toString)) // 9.84 tammukka.potkaise // Tamma potkaisee // Lopuksi tehdään juustoa: def juustoa(tuotantoeläin: Lypsävä):Double = {42 * tuotantoeläin.lypsä(tuotantoeläin.toString)} println(juustoa(mansikki)) // 1055.04 println(juustoa(tammukka)) // 413.28
class Ajoneuvo {var nopeus=0} trait Ohjauspyörä {var läpimitta=30} trait Moottori {var hevosvoimia=100} class Auto extends Ajoneuvo with Ohjauspyörä with Moottori {var merkki="perusauto"} val biili = new Auto biili.nopeus = 120 biili.läpimitta = 25 biili.hevosvoimia = 200 biili.merkki = "Maserati" println(biili.nopeus) // 120 println(biili.läpimitta) // 25 println(biili.hevosvoimia) // 200 println(biili.merkki) // Maserati
Tällainen taitaa kuitenkin olla hienon kielen väärinkäyttöä...
// Timantteja: aliluokka ja "traitti" class Henkilö { val nimi="Henkilö" def syö=println("Henkilö syö") } class Opettaja extends Henkilö { override def syö=println("Opettaja syö") } trait Opiskelija extends Henkilö { // vaatii extends Henkilö, ks. selitys alla override def syö=println("Opiskelija syö") } class OpettavaOpiskelija extends Opettaja with Opiskelija {} val x = new OpettavaOpiskelija println(x.nimi) // Henkilö x.syö // Opiskelija syöNimi näkyy periytyvän vain kerran. Huom: Kun "traitti extendeeraa" luokkaa A, vain luokan A jälkeläisiin on luvallista miksata tuo traitti!
Luontevampaa ehkä olisi ohjelmoida symmetrisesti sekä Opettaja että Opiskelija traittina:
// Timantteja: molemmat traitteja class Henkilö { val nimi="Henkilö" def syö=println("Henkilö syö") } trait Opettaja extends Henkilö { override def syö=println("Opettaja syö") } trait Opiskelija extends Henkilö { override def syö=println("Opiskelija syö") } class OpettavaOpiskelija extends Opettaja with Opiskelija {} val x = new OpettavaOpiskelija println(x.nimi) // Henkilö x.syö // Opiskelija syö class OpiskelevaOpettaja extends Opiskelija with Opettaja {} val y = new OpiskelevaOpettaja println(y.nimi) // Henkilö y.syö // Opettaja syöSiistiä: viimeisin miksaus siis voittaa! Voittajan valinta on itse asiassa hieman hieman mutkikkaanpaa, kun miksataan jono traitteja, joilla on itsellään yliluokkia tai "ylitraitteja"! Valintajärjestys on täsmällisesti (ja luontevasti) määritelty ns. periytymishierarkian linearisoinnilla (Odersky et al. s. 228-230):
class Animal trait Furry extends Animal trait HasLegs extends Animal trait FourLegged extends HasLegs class Cat extends Animal with Furry with FourLegged
Type Linearization: Animal Animal, AnyRef, Any Furry Furry, Animal, AnyRef, Any FourLegged FourLegged, HasLegs, Animal, AnyRef, Any HasLegs HasLegs, Animal, AnyRef, Any Cat Cat, FourLegged, HasLegs, Furry, Animal, AnyRef, AnyKokeillaan sitten korvata kenttä kahdessa traitissa:
// Timantteja: korvataan kenttiä class Henkilö { val nimi="Henkilö" def syö=println("Henkilö syö") } trait Opettaja extends Henkilö { override val nimi="Opettaja" override def syö=println("Opettaja syö") } trait Opiskelija extends Henkilö { override val nimi="Opiskelija" // KENTÄN TUPLAMÄÄRITTELYSTÄ TULEE ONGELMA! override def syö=println("Opiskelija syö") } class OpettavaOpiskelija extends Opettaja with Opiskelija {} val x = new OpettavaOpiskelija println(x.nimi) x.syö class OpiskelevaOpettaja extends Opiskelija with Opettaja {} val y = new OpiskelevaOpettaja println(y.nimi) y.syöEi onnistukaan! Saadaan yllättävä ilmoitus:
(fragment of tmp.scala):14: error: error overriding value nimi in trait Opettaja of type java.lang.String; value nimi in trait Opiskelija of type java.lang.String cannot override a value or variable definition in a trait (this is an implementation restriction) class OpettavaOpiskelija extends Opettaja with Opiskelija {} ^ (fragment of tmp.scala):19: error: error overriding value nimi in trait Opiskelija of type java.lang.String; value nimi in trait Opettaja of type java.lang.String cannot override a value or variable definition in a trait (this is an implementation restriction) class OpiskelevaOpettaja extends Opiskelija with Opettaja {} ^ two errors foundOnko kyseessä siis käyttämämme Scala-toteutuksen ongelma? Sallisiko kielen määrittely tilanteen?
// Timantteja: korvataan kenttiä aliluokassa ja traitissa class Henkilö { val nimi="Henkilö" def syö=println("Henkilö syö") } class Opettaja extends Henkilö { override val nimi="Opettaja" override def syö=println("Opettaja syö") } trait Opiskelija extends Henkilö { // vaatii extends Henkilö, ks. selitys alla override val nimi="Opiskelija" override def syö=println("Opiskelija syö") } class OpettavaOpiskelija extends Opettaja with Opiskelija {} val x = new OpettavaOpiskelija println(x.nimi) // Opiskelija x.syö // Opiskelija syö
Laihoista rajapinnoista halvalla rikkaita
Tekniikka muistuttaa Javan abstraktien luokkien yhtä käyttötapaa: konkreettinen aliluokka toteuttaa jotkin abstrakteina perimänsä yksinkertaiset perusoperaatiot ja perii abstraktilta luokalta joukon ei-abstrakteja operaatioita, jotka on ohjelmoitu abstrakteja metodeita kutsumalla.
Esimerkki (Odersky et al. s. 217-219):
trait Rectangular { def topLeft: Point // Vain nämä kaksi ovat abstrakteja, näiden def bottomRight: Point // avulla toteutetaan kaikki muut. def left = topLeft.x def right = bottomRight.x def width = right - left // and many more geometric methods... }Nyt voidaan ohjelmoida tyyliin:
abstract class Component extends Rectangular { // saadaan käyttöön läjäpäin // valmiiksi toteutettuja metodeita // other methods... } ... class Rectangle(val topLeft: Point, val bottomRight: Point) extends Rectangular { // other methods... } ...Ja käytetään:
scala> val rect = new Rectangle(new Point(1, 1), new Point(10, 10)) rect: Rectangle = Rectangle@3536fd scala> rect.left res2: Int = 1 scala> rect.right res3: Int = 10 scala> rect.width res4: Int = 9 ...
trait Ordered - Javan Comparable-interfacen vastine
Esimerkki laihan rajapinnan lähes ilmaisesta rikastamisesta: Annetaan toteutus Ordered-traitin nimeämälle abstraktille compare-metodille ja saadaan sen avulla toteutettuna muita vertailuoperaatioita. Ks. Scala-API.
Traitteja pinoon
(Odersky et al. s. 222-226) Viitaukset traitissa yliluokan piirteisiin (super) tarjoavat tyylikkään/kauhean/selkeän/sekavan (valitse omasi) tavan ketjuttaa ("pinota") "yliluokkien superien" kutsumista.
Abstrakti jono:
abstract class IntQueue { def get(): Int def put(x: Int) }Sen toteutus:
import scala.collection.mutable.ArrayBuffer class BasicIntQueue extends IntQueue { private val buf = new ArrayBuffer[Int] def get() = buf.remove(0) def put(x: Int) { buf += x } }Käyttöä:
scala> val queue = new BasicIntQueue queue: BasicIntQueue = BasicIntQueue@24655f scala> queue.put(10) scala> queue.put(20) scala> queue.get() res9: Int = 10 scala> queue.get() res10: Int = 20Tuplaustraitti:
trait Doubling extends IntQueue { abstract override def put(x: Int) { super.put(2 * x) } }Käyttöä:
scala> class MyQueue extends BasicIntQueue with Doubling defined class MyQueue scala> val queue = new MyQueue queue: MyQueue = MyQueue@91f017 scala> queue.put(10) scala> queue.get() res12: Int = 20Pari lisätraittia:
trait Incrementing extends IntQueue { abstract override def put(x: Int) { super.put(x + 1) } } trait Filtering extends IntQueue { abstract override def put(x: Int) { if (x >= 0) super.put(x) } }Käyttöä:
scala> val queue = (new BasicIntQueue with Incrementing with Filtering) queue: BasicIntQueue with Incrementing with Filtering... scala> queue.put(-1); queue.put(0); queue.put(1) scala> queue.get() res15: Int = 1 scala> queue.get() res16: Int = 2Mutta jos vaihdetaan traittien järjestys:
scala> val queue = (new BasicIntQueue with Filtering with Incrementing) queue: BasicIntQueue with Filtering with Incrementing... scala> queue.put(-1); queue.put(0); queue.put(1) scala> queue.get() res17: Int = 0 scala> queue.get() res18: Int = 1 scala> queue.get() res19: Int = 2