// --- 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 found
Onko 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 = 2
Mutta 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
Takaisin sisältösivulle.