Muutettu viimeksi 23.3.2010 / Sivu luotu 12.3.2010 / [oppikirjan esimerkit] / [Scala]
Sivun sisältöä:
class Pikkuvarasto (private var määrä: Double, val tuote: String) {
def this() = this(0.0, "(nimetön)") // parametriton konstruktori oletusarvoin
def paljonkoOn = määrä // aksessori on tehtävä koska määrä on private!
def vieVarastoon(paljonko: Double) = if (paljonko > 0) määrä += paljonko
def otaVarastosta(paljonko: Double) =
if (paljonko <= 0) 0.0
else if (paljonko <= määrä) {määrä -= paljonko; paljonko;}
else {val saatiin = määrä; määrä = 0; saatiin;}
override def toString = "("+tuote+": "+määrä+")"
def summa(toinen: Pikkuvarasto) =
new Pikkuvarasto(määrä + toinen.määrä, tuote + toinen.tuote);
}
val mehua = new Pikkuvarasto(10, "Mehu")
val olutta = new Pikkuvarasto(123.4, "Olut")
val bensaa = new Pikkuvarasto(90.1, "Bensa")
println(mehua); println(olutta); println(bensaa)
val a = new Pikkuvarasto(); println(a)
val b = mehua; println("" + b + mehua)
b vieVarastoon(25); println("" + b + mehua)
println("Muuttuja bensaa:" + bensaa + ", määrä=" + bensaa.paljonkoOn +
", nimi=" + bensaa.tuote)
//...
Luvun 1 huomioita:
Nyt aletaan askarrella luokkien ja olioiden kanssa "Scala-henkisemmin".
"Kapseloidaan" aluksi kokonaisluku. (Eipä tässä kyllä oikeastaan mitään kapseloida, kun mitään ei piiloteta eikä edes operaatioita lisätä kapseliin.)
class Intti1 { var arvo = 0 } val a = new Intti1 val b = new Intti1Syntyy jotakin kovin tuttua, mutta on tässä jotakin uuttakin: Myös nolla on olio, johon sekä a:n että b:n kenttä arvo aluksi viittaa! (Kuva luennolla!)
Käyttöä:
a.arvo = 7 b.arvo = a.arvo * 3 // tai myös: a arvo = 7 b arvo = (a arvo) * 3 println(a.arvo) // 7 println(b arvo) // 21Näin saatiin siis rakenne, joka muistuttaa eräiden kielten tietuetta (record, struct). Jos kenttä olisi val, saataisiin muuttumaton vakiotietue.
Vieläkin vaivattomammin saadaan aikaan monipuolisempi versio äskeisestä kirjoittamalla konstruktorille var-parametri:
class Intti2 (var arvo: Int) { } val a = new Intti2(7) val b = new Intti2(0) b.arvo = a.arvo * 3 println(a.arvo) // 7 println(b arvo) // 21Ja jos halutaan parametritonkin konstruktori, se käy helposti kirjoittamalla pääkostruktoria käyttävä lisäkonstruktori:
class Intti2 (var arvo: Int) { def this() = this(0) // HUOM: Toki lisäkonstruktorilla voi olla myös } // oma lohko, jossa on vaikka millainen algoritmi! val a = new Intti2(7) val b = new Intti2 b.arvo = a.arvo * 3Laaditaan sitten klassiseen tyyliin aidosti kapseliin laitettu positiivinen kokonaisluku:
class InttiPos (private var piiloarvo: Int) { def this() = this(0) def aseta(a: Int) { // *** setteri *** piiloarvo = if (a>0) a else 666 // virhetilanteen hoito } def arvo = piiloarvo // *** getteri *** } val a = new InttiPos(7) val b = new InttiPos b aseta a.arvo * 3 a aseta a.arvo - 1000 println(a.arvo) // 666 println(b arvo) // 21Tässä esimerkissä metodi aseta ei palauta mitään arvoa. Sen tyyppi on Unit, mikä vastaa eräiden kielten "void-metodia". Kirjoitustyyli def aseta(a: Int) {...} sopii tähän tilanteeseen. Parametriton funktio arvo taas palauttaa Int-arvon. Siksi se kirjoitetaan yhtäsuuruusmerkkisyntaksilla.
Knoppologiaa: Puolipistepäättelijä ymmärtää rakenteen
x + ykahdeksi lauseeksi/lausekkeeksi! Avun saa joko suluttamalla tai muistamalla säännön: Jos rivin lopussa on infix-operaatio, lause/lauseke jatkuu seuraavalla rivillä. Rivinvaihtojen tyyli on siis:
x + y + z
Kuten jo aeimmin nähtiin, Bytecodeksi käännettävä sovellus voidaan
sisällyttää ainokaiseen olioon. Esimerkkinä vaikka pääohjelman
sisältävä sovellus:
object tervehdi {
def main(args: Array[String]) = {
println("Montako tervehdystä?")
var lkm = readInt
while (lkm>0) {
println("Hoi maailma!")
lkm = lkm-1
}
}
}
Myös vaikkapa joukko kirjastometodeja voidaan koota ainokaiseen,
vrt. Javan kirjastoluokka staattisine metodeineen.
Ainokainen myös alustetaan samaan tapaan kuin Javan staattinen
kalusto: ensimmäinen ohjelman suorittama viittaus ainokaiseen
luo ja alustaa tuon olion.
Vaikka ainokaiset vaikuttavat vain naamioiduilta staticeilta, ne ovat itse asiassa aivan jotakin muuta: nillä voi olla yliluokka, jolta ne perivät ominaisuuksia, niihin voidaan liittää piirteitä ("mixata traitteja"), olio on tyypiltään yliluokkansa ja piirteidensä tyyppinen - kaikkine seurauksineen ...
Luokkamäärittelyn kanssa samassa käännösyksikössä voidaan määritellä luokan kanssa saman niminen olio! Tuollainen olio on luokan oliokumppani (companion object) ja luokka tuon olion luokkakumppani (companion class). Näin voidaan ohjelmoida esim staattisten metodien ja muuttujien Scala-vastineita ja erityisesti Scala-idiomiin kuuluvia factory-metodeita:
class InttiOb(alkuarvo: Int) { var arvo = alkuarvo } object InttiOb { def apply(alkuarvo: Int) = new InttiOb(alkuarvo) // factory-metodi! def huu {println("BÖÖ")} // kirjastometodi } val a = InttiOb(7) // factory-metodin kutsuja val b = InttiOb(14) val c = InttiOb.apply(42) // näinkin toki saa sanoa... println(a.arvo) println(b arvo) println(c arvo) InttiOb.huu // kirjastometodin kutsuJuuri tähän tapaan esim. edellä nähty Map-kummastelun aihe on toteutettu. Ja tuo "kaarisulkeiden semantiikka" tosiaankin eräitä poikkeuksia lukuunottamatta todellakin on apply-metodin kutsun lyhennysmerkintä... Ja oliokumppaneita voi olla myös abstrakteilla otuksilla kuten piirteillä (trait). Ja aivan erityisesti niillä...
object tervehdi extends Application { println("Montako tervehdystä?") var lkm = readInt while (lkm>0) { println("Hoi maailma!") lkm = lkm-1 } }Hieman tämä säästää kirjoitusvaivoja. Rajoituksena on, ettei komentoriviparametreja voi antaa.
Valmistellaan vähän korvaa/silmää tulevaan. Mitä tässä
oikeastaan tapahtuu?
Olioon "mixataan" siis "traitti" eli liitetään piirre Application.
Olio perii piirteestä oikean muotoisen main-metodin, joten
saadaan aikaan suoritettava ohjelma. Aaltosulkeissa oleva koodi
laitetaan syntyvän ainokaisen ensisijaiseen konstruktoriin ja suoritetaan,
kun olio alustetaan. Pian ehkä selviää, mitä tämä kaikki tarkoittaa...