Muutettu viimeksi 25.2.2016 / Sivu luotu 12.3.2010 / [oppikirjan esimerkit] / [Scala]
Sivun sisältöä:
Kun funktiota käytetään olio-ohjelmoinnissa olion aksessorina, sitä on tapana kutsua metodiksi. [Java-ohjelmoijilla on paha tapa kutsua mitä tahansa nimettyä aliohjelmaa metodiksi...]
// Luokan otsikko on pääkonstruktorin otsikko. // Sen muodollisista parametreista tulee ilmentymämuuttujia. class Esim(piilovakio: Int, val julkivakio: Int, var julkimuuttuja: Int, private var piilomuuttuja: Int) { // pääkonstruktorin algoritmi: piilomuuttuja *= julkimuuttuja // toissijainen konstruktori kutsuu pääkonstruktoria: def this(arvo: Int) { this(1, arvo, 2, 3) } // ... tai toista apukonstruktoria: def this() { this(666) } // aksessorit: def annaPiilomuuttujanArvo = piilomuuttuja // eli // def annaPiilomuuttujanArvo(): Int = {return piilomuuttuja} override def toString = "piilossa on " + piilomuuttuja } val a = new Esim(1,2,3,4) val b = new Esim(4321) val c = new Esim println(a.annaPiilomuuttujanArvo) // 12 println(b.annaPiilomuuttujanArvo) // 6 println(a) // piilossa on 12 println(b) // piilossa on 6 println(c) // piilossa on 6
Aletaan tutustua asiaan yksityiskohtaisemmin:
"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!
Käyttöä:
a.arvo = 7 b.arvo = a.arvo * 3 // tai myös toisin kunhan sallitaan: import scala.language.postfixOps // nykyään pitää antaa lupa! // (ilman lupaa "deprecated) 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 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 * 3
Konstruktorien mahdolliset algoritmit kirjoitetaan siis seuraavaan tyyliin:
class Intti3 (var arvo: Int) { println("Luotiin Intti3-olio: " + arvo) // pääkonstruktorin algoritmi def this() = { // apukonstruktorin algoritmi this(0) // Javan tapaan this pitää olla alussa! println("Kutsuttiin parametritonta konstruktoria.") } } val a = new Intti3(7) val b = new Intti3 b.arvo = a.arvo * 3Tulostus:
Luotiin Intti3-olio: 7 Luotiin Intti3-olio: 0 Kutsuttiin parametritonta konstruktoria.
Laaditaan sitten klassiseen tyyliin aidosti kapseliin laitettu ei-negatiivinen kokonaisluku. Kostruktorille ja setterille annettu negatiivinen parametri hoidellaan petomaisesti:
class InttiPos (private var piiloarvo: Int) { if (piiloarvo < 0) piiloarvo = 666 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 val c = new InttiPos(-13) b aseta a.arvo * 3 a aseta a.arvo - 1000 println(a.arvo) // 666 println(b.arvo) // 21 println(c.arvo) // 666Tässä esimerkissä metodi aseta ei palauta mitään varsinaista 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.
Jos edellistä esimerkkiä jatketaan vaikkapa lauseella:
println(b aseta a.arvo * 3)eli jos tulostetaan Unit-tyyppisen funktion arvo, saadaan tulostus ().
Huom: Jos funktio määritellään ilman yhtäsuuruusmerkkiä tyyliin def aseta(a: Int) {...}, sen tyyppi on Unit, vaikka suoritus päättyisi return-lauseeseen! Kääntäjä osaa onneksi opastaa: Jos yllä getteri korvataan seuraavasti
def arvo {return piiloarvo} // *** getteri ***saadan varoitus: "warning: enclosing method arvo has result type Unit: return value discarded".
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, Javan tavukoodiksi käännettävä sovellus voidaan
ohjelmoida ainokaiseen olioon. Esimerkkinä pelkän pääohjelman
sisältävä sovellus:
object tervehdi {
def main(args: Array[String]) = {
println("Montako tervehdystä?")
var lkm = scala.io.StdIn.readInt // (näikin siis voi tehdä)
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.
Luokkamäärittelyn kanssa samassa käännösyksikössä voidaan määritellä luokan kanssa saman niminen ainokainen olio! Tällainen 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) { // luokkakumppani var arvo = alkuarvo } object InttiOb { // oliokumppani def apply(alkuarvo: Int) = { // factory-metodi! println("Tehdas valmistaa InttiObin " + alkuarvo) new InttiOb(alkuarvo) } def huu {println("BÖÖ")} // kirjastometodi } val a = InttiOb(7) // factory-metodin kutsuja, siis ei new-operaatioita! val b = InttiOb(14) val c = InttiOb.apply(42) // näinkin toki saa sanoa... val d = new InttiOb(49) // ja toki näikin ... println(a.arvo) println(b.arvo) println(c.arvo) println(d.arvo) InttiOb.huu // kirjastometodin kutsuTulostus:
Tehdas valmistaa InttiObin 7 Tehdas valmistaa InttiObin 14 Tehdas valmistaa InttiObin 42 7 14 42 49 BÖÖJuuri 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 piirretyypeillä (trait). Ja erityisesti juuri niillä...
object tervehdi extends App { println("Montako tervehdystä?") var lkm = scala.io.StdIn.readInt while (lkm>0) { println("Hoi maailma!") lkm = lkm-1 } }
Valmistellaan vähän korvaa ja silmää tulevaan. Mitä tuossa oikeastaan tapahtuu?
Olioon "mixataan traitti" eli liitetään piirretyyppi App. Olio perii piirretyypistä oikean muotoisen main-metodin, joten saadaan aikaan suoritettava ohjelma. Aaltosulkeissa oleva koodi laitetaan synnytettävän ainokaisen konstruktoriin ja suoritetaan, kun olio alustetaan. Pian ehkä selviää, mitä tämä kaikki tarkoittaa...
Myös komentoriviparametrit löytyvät piirretyypin mukanaan tuomasta muuttujasta args:
object komentoriviparametrit extends App { println("Parametrit olivat: " + (args mkString ", ")) }