Huom: Muutettu viimeksi 22.4.2010 / Sivu luotu 19.4.2010 / [oppikirjan esimerkit] / [Scala]
Sivun sisältöä:
Javassa pakkaus on luokkakokoelma tiedostoina jossakin hakemistossa, ja Javan ilmauksella import tuodaan käännösyksikköön yksittäisiä luokkia tai annetaan lupa löytää kaikki jonkin pakkauksen luokat.
Scalassa molemmat ovat paljon monipuolisempia ja vahvempia. Niiden avulla mm. voidaan toteuttaa "moduleita klassiseen tyyliin", ym., ym.
package bobsrockets.navigation class NavigatorTosin Javan käännösyksikkö voi sisältää vain luokkia. Scalassa monenlaista muutakin sälää.
Mutta Scalassa pakkauksia voidaan rakennella myös toisin - sisäkkäin määritellen - tässäkin asiassa Scala on "ei-litteä":
package bobsrockets {
package navigation {
// In package bobsrockets.navigation
class Navigator
package tests {
// In package bobsrockets.navigation.tests
class NavigatorSuite
}
}
}
Jos bobsrockets ei sisällä kuin pakkauksen
navigation, voi kirjoittaa lyhyesti:
package bobsrockets.navigation {
// In package bobsrockets.navigation
class Navigator
package tests {
// In package bobsrockets.navigation.tests
class NavigatorSuite
}
}
Luvun ensimmäinen esimerkki olikin oikeastaan vain scalamainen
lyhennysmerkintä seuraavalle:
package bobsrockets.navigation {class Navigator}
Vaikka Javan pakkaukset jäsentyvätkin alipakkauksiksi
hakemistojen puumaiseen hierarkiaan, ne eivät ole
nimiavaruuksina hierarkioita. Mutta Scalassapa
muiden sisäkkäisten rakenteiden tapaan ovat:
package bobsrockets {
package navigation {
class Navigator
}
package launch {
class Booster {
// No need to say bobsrockets.navigation.Navigator
val nav = new navigation.Navigator
}
}
}
Muiden lohkorakenteiden tapaan myös sisäkkäisissä pakkauksissa
tunnuksille voidaan antaa uusia merkityksiä sisempänä ja
samalla peittää ulomman tason nimiä näkymästä:
// In file launch.scala
package launch {
class Booster3
}
// In file bobsrockets.scala
package bobsrockets {
package navigation {
package launch {
class Booster1
}
class MissionControl {
val booster1 = new launch.Booster1
val booster2 = new bobsrockets.launch.Booster2
val booster3 = new _root_.launch.Booster3
}
}
package launch {
class Booster2
}
}
Tässä vain tuo _root_.launch.Booster3 kaivannee
lisävalaistusta: kyseessä on kaikkien ohjelmoijan kannalta
uloimman tason pakkausten yhteinen ylipakkaus.
Javan tapaan sillä voidaan tietenkin tuoda pakkauksen luokka "käännösyksikköön". Olkoot Bobin herkut paketoidut:
package bobsdelights
abstract class Fruit(
val name: String,
val color: String
)
object Fruits {
object Apple extends Fruit("apple", "red")
object Orange extends Fruit("orange", "orange")
object Pear extends Fruit("pear", "yellowish")
val menu = List(Apple, Orange, Pear)
}
Kun tämän kääntää scalac-komennolla, käännöshakemistoon
syntyy alihakemisto bobsdelights, joka sisältää luokkatiedostot:
Fruit.class Fruits.class Fruits$Orange$.class Fruits$Apple$.class Fruits$.class Fruits$Pear$.classJa nyt käyttöön voi Javan tapaan saada pakkauksen nimetyn luokan:
import bobsdelights.Fruittai pakkauksen kaikki jäsenet:
import bobsdelights._ja samaan tapaan myös vaikkapa pakkauksen sisältämän olion alioliot ja kentän(!!):
import bobsdelights.Fruits._Kokeillaan malliksi tuota viimeistä
import bobsdelights.Fruits._
object Hedelma extends Application {
println(menu(0).name) // apple
println(menu(2).color) // yellowish
}
(Objektin kenttien tuonti vastaa Javan staattisten kenttien tuomista.)
Toki tuonti toimii myös "suoritettavaan tekstitiedostoon", ei pelkästään yllä nähdyllä tavalla sovellukseen.
Vaan eipä tässä vielä kaikki! Ensinnäkin import-ilmauksia saa kirjoitella muuallekin kuin "käännösyksikön" alkuun. Toiseksi niillä voidaan tuoda näkyviin paljon muutakin kuin luokkia. Esimerkki:
def showFruit(fruit: Fruit) {
import fruit._
println(name +"s are "+ color)
}
Tässä siis tuodaan parametrin luokan jäsenet!
Ja tulostuslauseessa käytetään pelkkiä kenttänimiä sen sijaan,
että kirjoitettaisiin fruit.name ja fruit.color!
Scalassa - Javasta poiketen - voidaan tuoda myös itse pakkauksia, ei vain pakkausten luokkia:
import java.util.regex
class AStarB {
// Accesses java.util.regex.Pattern
val pat = regex.Pattern.compile("a*b")
}
Tuotujen rakenteiden kenttiä voidaan myös kätkeä tai
nimetä uudelleen:
// Tuodaan vain nimetyt jäsenet:
import Fruits.{Apple, Orange}
// Tuodaan nimetyt ja nimetään uudelleen
import Fruits.{Apple => McIntosh, Orange}
// Nimetään sql-nimi uudelleen, jotta Java-nimi jää käyttöön
import java.sql.{Date => SDate}
// Lyhenne Javan sql-pakkaukselle
import java.{sql => S}
// Pidempi ilmaus lyhyemmälle import Fruits._
import Fruits.{_}
// Kaikki tuodaan, mutta yhdelle uusi nimi
import Fruits.{Apple => McIntosh, _}
// Tuodaan kaikki paitsi päärynä
import Fruits.{Pear => _, _}
Tuosta viimeisestä melkein järkevä esimerkki:
import Notebooks._
import Fruits.{Apple => _, _}
Tuodaan kaikki Notebooksista ja Fruitsistakin kaikki, paitsi
Apple. Näin hedelmistä Apple jääkin tarkoittamaan Notebooksin
Applea. Jos Noteboksissa sattui olemaan Orange, se ei ole suoraan
käytössä, koska Fruitsin Orange voittaa...
import java.lang._ // everything in the java.lang package import scala._ // everything in the scala package import Predef._ // everything in the Predef objectEnsikisikin siis kaikki Javan oletuskalusto on käytettävissä. Pakkauksessa scala on Scalan oma peruskalusto Anystä UninitializedFieldErroriin. Predef-oliosta saadaan tyyppejä ArrayIndexOutOfBoundsExceptionista unitiin, "staattista" kalustoa, mm. Map- ja Set-tehtaat ja "luokkametodeita" joka lähtöön, mm. assert, int2double, println, readInt, yms. Katso Scala-API.
Huom: Tällaisissa import-jonoissa myöhemmin luetellun pakkauksen tunnukset peittävät aiemmin mainituista löytyvät kaimansa. Jos vaikka jostain syystä haluaa välttämättä käyttää Javan StringBuilderia, siihen pitää viitata koko nimellä java.lang.StringBuilder, koska myös Scalan omassa oletuspakkauksessa on saman niminen luokka.
Näkyvyysalueita, joihin näkyvyys voidaan rajoittaa, on paljon enemmän kuin Javassa. Toisaalta onhan Scalassa toki rakenteita ja rakenteiden sisäkkäisyyksia Javaa enemmän.
Luokan sisältämä luokka on (Javasta poiketen) oma näkyvyysalueensa:
class Outer {
class Inner {
private def f() { println("f") }
class InnerMost {
f() // OK
}
}
(new Inner).f() // error: f is not accessible
}
Tässäkin asiassa Java on litteä: Luokasta pääsee käsiksi privaatteihin
sisäluokkiin!
Miksi ihmessä?
Arvelen asian johtuvan siitä, että Javaan "liimattiin päälle"
sisäluokat aika myöhään.
Määre protected rajoittaa näkyvyyden oman luokan lisäksi vain aliluokkiin:
package p {
class Super {
protected def f() { println("f") }
}
class Sub extends Super {
f() // OK
}
class Other {
(new Super).f() // error: f is not accessible
}
}
Scalan protected on juuri sellainen kuin sen
toivoin Javassakin olevan ja petyin. Javassahan määre päästää
myös luokan oman pakkauksen muut luokat käpälöimään
protected-jäseniä.
Näkyvyysmääreitä (access modifiers) voidaan tarkentaa tarkentein (qualifiers) tyyliin private[X] ja protected[X], missä X on luokan, olion tai pakkauksen nimi. Tarkennetulla määrellä säädellään tunnuksen näkyvyyttä nimenomaan ja erityisesti määrittelykohtaa (luokka, olio, pakkaus) ympäröivän rakenteen (luokka, olio, pakkaus) sisällä; "mille tasolle ulospäin näytetään":
package bobsrockets {
package navigation {
private[bobsrockets] class Navigator {
protected[navigation] def useStarChart() {}
class LegOfJourney {
private[Navigator] val distance = 100
}
private[this] var speed = 200
}
}
package launch {
import navigation._
object Vehicle {
private[launch] val guide = new Navigator
}
}
}
Myös siis this-viite olioon kelpaa!
Tähän olioprivaattiuteen törmäsimme jo aiemminkin.
Siis vaikkapa seuraava on kielletty Navigator-luokan
sisällä:
val other = new Navigator other.speedNäkyvyys olio- ja luokkakumppanien kesken muistuttaa "hämmästyttävän" paljon Javan staattisen ja ei-staattisen kaluston tapausta: Kumppanukset jakavat saman näkyvyysalueen:
class Rocket {
import Rocket.fuel
private def canGoHomeAgain = fuel > 20
}
object Rocket {
private def fuel = 10
def chooseStrategy(rocket: Rocket) {
if (rocket.canGoHomeAgain)
goHome()
else
pickAStar()
}
def goHome() {}
def pickAStar() {}
}
Ja kuten Javan static-puolella, viittaus oliokumppanista
luokkakumppaniin edellyttää luokan ilmentymää, johon
luokkakumppanin metodia sovelletaan.
Yksi ero Javaan verraten: Javassa aliluokasta pääsee käsiksi yliluokan staattisiin jäseniin. Scalassa oliokumppanilla ei voi olla olemassa "alioliokumppaneita", koska olioilla ei yleensäkään ole "aliolioita".