Helsingin yliopisto
/
Tietojenkäsittelytieteen
laitos
Copyright © 2005 Arto Wikla.
Tämän oppimateriaalin käyttö on sallittu vain yksityishenkilöille
opiskelutarkoituksissa. Materiaalin käyttö muihin tarkoituksiin,
kuten kaupallisilla tai muilla kursseilla, on kielletty.
4.6 Pakkaus
(Muutettu viimeksi 13.6.2005)
Ohjelmiston tuotannossa yksi keskeinen ongelma on ohjelman osien,
erilaisten ohjelmatiedostojen hallinta: missä mikin osa sijaitsee,
miten osat löydetään.
Javassa ohjelmatiedostot eli class-tiedostot kerätään ns.
pakkauksiin (package). Itse pakkaukset voidaan
järjestää puumaiseen tiedostorakenteeseen. Pakkaus ei sinänsä
liity olio-ohjelmointiin. Se on käytännön ohjelmistotuotannon
väline!
Yhteen pakkaukseen on tapana kerätä luokkia (ja rajapintaluokkia),
jotka tavalla tai toisella liittyvät toisiinsa.
Jos luokkia on paljon, pakkaukset voidaan jäsennellä
alipakkauksiksi, jne.
Myös Java-kielen valmiit välineet on toteutettu pakkauksina:
Esimerkiksi pakkaus java muodostuu alipakkauksista
java.lang, java.util, java.io, ...
Pakkaus muodostaa "nimiavaruuden", jonka nimien näkyvyys pakkauksen
ulkopuolelle voidaan sallia tai kieltää. (Myös pakkauksen sisällä
nimien näkyvyys luokkien välillä on säädeltävissä, kts. 4.6)
Käännösyksikkö (compilation unit) on tiedosto,
joka voi sisältää (tässä järjestyksessä!):
- package-määreen, joka ilmoittaa mihin pakkaukseen
tiedostossa olevat luokat (ja rajapintaluokat) on tarkoitus
sijoittaa
- import-määreitä, jotka ilmoittavat pakkaukset, jotka
annetaan käännösyksikön luokkien (ja rajapintaluokkien) käyttöön.
- luokkien (ja rajapintaluokkien) määrittelyitä
Jos tiedoston alussa ei ole package-määrettä,
class-tiedostot asetetaan ns. nimettömään pakkaukseen.
Se on Java-toteutuksissa yleensä "nykyhakemisto", so. hakemisto,
jossa class-tiedosto sijaitsee.
Huom: Käännösyksikössä voi olla vain yksi
luokka, jolle on määritelty näkyvyydeksi public!
Kääntäjä tuottaa
jokaisesta käännösyksikön sisältämästä Java-kielisestä
luokkamäärittelystä erillisen Bytecode-kielisen
class-tiedoston. Ja pakkauksiin siis kerätään nimenomaan
näitä class-tiedostoja.
Käännösyksikön alussa oleva määrittely
package paketti;
ilmaisee, että käännösyksikön sisältämät luokat on käännettyinä
määrä sijoittaa pakkaukseen paketti. Useissa käännösyksiköissä
voi olla sama pakkauksen nimi.
Huom: package-ilmaus ei automaattisesti vie
syntyviä class-tiedostoja pakkausta vastaavaan hakemistoon!
Käännösyksikön kuuluminen alipakkaukseen ilmaistaan:
package paketti.alipaketti;
Pakkauksen kaikki luokat saavat "etunimekseen" pakkauksen
nimen!
Esimerkiksi käännösyksiköiden
package pak;
public class Aapeli {
...
}
ja
package pak;
public class Beepeli {
...
}
luokkien koko nimet ovat pak.Aapeli ja pak.Beepeli.
Pakkauksen julkisiin luokkiin (public) voi pakkauksen
ulkopuolelta viitata luokan täydellisellä nimellä, esimerkiksi:
class sovellutututus {
...
pak.Aapeli x = new pak.Aapeli();
...
}
Näin voi menetellä, jos pakkauksen välineitä ei käytetä paljon, ja
jos ohjelmaa ei ole tarkoitus ylläpitää!
Käytännössä on järkevää käyttää aina
import-ilmauksia. Ne kirjoitetaan käännösyksikön
alkuun mahdollisen package-ilmauksen jälkeen. Näin heti
käännösyksikön alusta näkee, mitä pakkauksia luokissa käytetään.
Esimerkki: Käännösyksikkö olkoon seuraavanlainen:
package omapaketti;
import pak1.Luo1;
import pak2.*;
import pak3.pak4.*;
class Oma1 { ... }
class Oma2 {
...
pak5.Kissa a = new pak5.Kissa();
...
}
Tässä
- Luokat Oma1 ja Oma2 on määrä laittaa pakkaukseen omapaketti.
- Luokkien Oma1 ja Oma2 ohjelmoinnissa voi käyttää pakkauksen
pak1 julkista luokkaa Luo1 suoraan nimellä Luo1.
- Luokkien Oma1 ja Oma2 ohjelmoinnissa voi käyttää pakkauksen
pak2 kaikkia julkisia luokkia suoraan näiden luokkien
nimillä.
Huom: Merkki '*' antaa kääntäjälle
luvan löytää pakkauksen pak2 kaikki julkiset
luokat tarvittaessa ("on demand")!
- Luokkien Oma1 ja Oma2 ohjelmoinnissa voi käyttää pakkauksen
pak3 alipakkauksen pak4 kaikkia julkisia luokkia suoraan näiden
luokkien
nimillä.
- Luokka Oma2 käyttää pak5-pakkauksen luokkaa Kissa vaikka pakkaus
ei ollut import-ilmauksissa. Siksi Kissan nimi on kirjoitettu
täydellisenä.
Huomautuksia:
- import-ilmauksella on siis täsmälleen kaksi sallittua
muotoa: yksittäisen luokan
tuonti ja lupa tuoda mikä tahansa pakkauksen luokka.
Erityisesti:
- import pak2.* ei tuo pakkauksen pak2
mahdollisia alipakkauksia!
- import pak3.pak4; on virheellinen!
- Tuotujen nimettyjen luokkien ja käännösyksikössä määriteltyjen
luokkien nimien on oltava yksikäsitteisiä.
- Tuotavissa olevien ("on demand" (*)) luokkien nimen peittäminen
käännösyksikössä on luvallista.
- Nimien täydellistä muotoa ja lyhyttä muotoa voi molempia käyttää,
jos lyhyt muoto on käytettävissä. Ylläolevassa esimerkissä siis
pak1.Luo1 ja Luo1 ovat molemmat luvallisia viittauksia pakkauksen
pak1 luokkaan Luo1. Ohjelmatekstin selkeyden takia voi olla
perusteltua sekä tuoda pakkaus import-määreellä, että
viitata tuotuihin luokkiin täydellisellä nimellä!
- Kaikkiin käännösyksiköihin tuodaan automaattisesti Javan
peruskalusto, pakkaus java.lang. Sen tuomisen saa
halutessaan myös ilmaista (saman pakkauksen toistuva tuominen
ei muutenkaan ole virhe):
import java.lang.*;
Pakkauksiin on syytä koota keskenään yhteenliittyviä luokkia. Jos
luokkia on paljon, ne voidaan jakaa edelleen alipakkauksiin.
Jos tämä jäsentely tehdään hyvin, pakkausten nimien keksimisen ei
pitäisi
olla suuri ongelma.
Tulevaisuuden visioissa Java-pakkaukset on haluttu nähdä koko
Internetin nimiavaruudessa. Tähän tarkoitukseen on suositeltu
ns. domain-nimestä johdettujen nimien käyttöä pakkauksen nimen
alkuosana.
Esimerkiksi kun jokin itse tehty luokka - vaikkapa
MinunIkiomaLuokkani - annettaisiin maailmanlaajuiseen käyttöön,
siihen voitaisiin viitata vaikkapa nimellä:
fi.helsinki.cs.wikla.MinunIkiomaLuokkani
Huom. Näin asiat eivät siis vielä ole! Mahtanevatko koskaan ollakaan?
Pakkausten säilyttämisen tapa on Java-kielen
ulkopuolinen asia. Esimerkikiksi Unixissa/Linuxissa ja
Windows-järjestelmässä luonteva vastine pakkausten puumaiselle rakenteelle
on puumainen hakemistorakenne.
Pakkaukset voitaisiin sijoittaa myös tietokantaan tai vaikkapa ripotella
"ympäri internettiä" kunhan vain käytettävä Java-toteutus tietää,
mistä etsiä!
Unix- ja Linux-järjestelmissä Java-toteutus etsii pakkauksia
hakemistoista,
jotka on ilmoitettu erään ns. ympäristömuuttujan arvona.
Ympäristömuuttuja CLASSPATH tuntee oletusarvoisesti Java-kieleen
valmiiksi
määriteltyjen pakkausten sijainnin. Kun omien pakkausten sijainti
hakemistorakenteessa lisätään muuttujan arvoon, Java-toteutus löytää
myös omat pakkaukset.
Jos pakkaukset on tarkoitettu pysyvään käyttöön, CLASSPATH-muuttujaa
voi täydentää pysyvästi.
Esimerkki:
- Tehdään omaan kotihakemistoon vaikkapa alihakemisto classes,
jonne pakkaukset kerätään. Hakemisto classes ei itse
ole pakkaus, se on hakemisto, josta pakkauksia voi löytyä!
- Täydennetään CLASSPATH-muuttujan oletussisältöä (istunnon ajaksi!)
komennolla:
export CLASSPATH="$CLASSPATH:$HOME/classes"
Nyt Java-toteutus osaa etsiä pakkauksia myös kotihakemiston
alihakemistosta classes.
- Tehdään pakkaus kalut hakemiston classes
alihakemistoksi.
- Ohjelmoidaan pakkaukseen asetettava luokka:
package kalut;
public class Koe {
public int x, y;
}
Käännetään luokka ja sijoitetaan käänöksen tulos
Koe.class hakemistoon classes/kalut.
- Laaditaan sovellus, joka käyttää pakkausta (sovellusta
voi kehitellä haluamassaan hakemistossa):
import kalut.*;
class Sov {
public static void main(String[] args) {
Koe a = new Koe();
a.x = 5; a.y = 6;
System.out.println(a.x+" "+a.y);
}
}
Tässä annetaan lupa hakea mikä tahansa julkinen luokka pakkauksesta
kalut. (Myös pelkkä import kalut.Koe; riittäisi
tässä tapauksessa.)
- Kun sovellus käännetään, kääntäjä löytää luokan Koe
pakkauksesta kalut. Kun sovellus suoritetaan, tulkki
löytää luokan Koe samasta paikasta.
Takaisin luvun 4 sisällysluetteloon.