- COMP.CS.140
- 4. Suuren ohjelman toteuttaminen
- 4.2 Javan luokkien perusteet
- 4.2.2 Javan pakkauksista
Javan pakkauksista¶
Aiemmin Javan lähdekooditiedoston rakennetta kuvaavassa osiossa mainittiin, että tiedoston
alussa voi olla muotoa package pakkauksen_nimi;
oleva pakkausmääritys. Pakkausmääritys
ilmaisee, että kaikki kyseisessä tiedostossa määritetyt luokat kuuluvat pakkausmäärityksessä
nimettyyn pakkaukseen. Tarkastellaan esimerkiksi seuraavaa yksinkertaista lähdekoodia:
package fi.tuni.prog3.helloworld;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
Edellä luokka HelloWorld
määritettin kuuluvaksi pakkaukseen fi.tuni.prog3.helloworld
.
Tämän seurauksena HelloWorld
onkin tarkemmin ottaen luokka
fi.tuni.prog3.helloworld.HelloWorld
. Pakkaus määrittää nimiavaruuden, johon luokka kuuluu.
Tämä mekanismi muistuttaa esimerkiksi C++:n nimiavaruuksia. Javassa pakkauksen sisältämiin luokkiin
ja rajapintoihin viitataan samanlaisella pistenotaatiolla kuin luokkien jäseniin. Näin ollen
toisaalta fi.tuni.prog3.helloworld.HelloWorld
on pakkauksessa fi.tuni.prog3.helloworld
määritetty luokka HelloWorld
, ja toisaalta fi.tuni.prog3.helloworld.HelloWorld.main
on
kyseisessä luokassa määritetty main
-funktio.
Edellä pakkauksen nimeksi oli määritetty fi.tuni.prog3.helloworld
, joka on esimerkki
vallitsevasta käytännöstä, että pakkausten nimet pohjautuvat koodin kirjoittajan
(taustaorganisaation) internet-osoitteeseen, jonka osat luetellaan käänteisessä järjestyksessä.
Tässä pakkauksen nimen pohjana toimi Tampereen yliopiston internet-osoite tuni.fi
, jonka
perään oli lisätty tarkenteet prog3
ja helloworld
kuvaamaan koodin liittymistä tähän kurssiin.
Niin sanotussa oikeassa maailmassa esimerkiksi Googlen julkaisemat Java-kirjastot sijaitsevat
com.google
-alkuisissa pakkauksissa, Microsoftin com.microsoft
-alkuisissa, jne.
Pakkausten avulla voidaan välttää keskenään samannimisten luokkien nimien yhteentörmäys.
Esimerkiksi Javan omassa luokkakirjastossa on kaksi eri luokkaa, joiden nimi on Date
:
toinen on pakkauksessa java.sql
ja toinen pakkauksessa java.util
. Ilman pakkauksia niitä
ei voisi käyttää samassa ohjelmassa, koska Java-kääntäjä ei tietäisi, kumpaan luokkaan nimi
Date
viittaa. Luokkiin kuitenkin voidaan viitata koodissa myös pakkauksen nimen kera, jolloin
edellä mainittuihin Date
-luokkiin voidaan viitata yksiselitteisesti muodoissa
java.sql.Date
ja java.util.Date
. Samaan pakkaukseen ei luonnollisestikaan pidä
määrittää kahta keskenään samannimistä luokkaa, koska silloin päädyttäisiin jälleen nimien
yhteentörmäykseen.
Pakkaukset vs import
Aiemmin on mainittu, että lähdekooditiedoston alussa olevilla import
-lauseilla voidaan
tuoda käyttöön muualla määritettyjä luokkia. Tarkemmin ottaen import
-lauseiden roolina
on mahdollistaa jossain muussa pakkauksessa määritettyyn luokkaan viittaaminen luokan
pelkistetyllä nimellä. Jos luokkaan viittaa pakkauksen kera, ei import
-lausetta tarvita.
Esimerkiksi seuraavat kaksi pakkauksessa java.util
määritetyn luokan Arrays
sort
-funktiota käyttävää koodia ovat toiminnallisesti identtisiä:
import java.util.Arrays;
public class SortedParameters {
public static void main(String[] args) {
Arrays.sort(args);
for(String arg : args) {
System.out.println(arg);
}
}
}
public class SortedParameters {
public static void main(String[] args) {
java.util.Arrays.sort(args);
for(String arg : args) {
System.out.println(arg);
}
}
}
Kuten edellä mainittiin, import
koskee nimenomaan eri pakkauksissa määritettyjä luokkia.
Kooditiedostosta voi viitata sen kanssa samassa pakkauksessa määritettyihin muihin luokkiin
suoraan pelkällä luokan nimellä tarvitsematta import
-lausetta.
Java-kielen tärkeimmät luokat sisältävä java.lang
-pakkaus saadaan aina käyttöön
automaattisesti ilman import
-lausetta.
Toinen motiivi pakkausten käyttöön on koodin organisointi niin, että samantapaisiin tehtäviin liittyvät luokat on määritetty keskenään samaan pakkaukseen. Tämä osaltaan helpottaa koodin ylläpitoa suurissa ohjelmistoissa, joissa voi olla satoja tai tuhansiakin luokkia. Pakkausten käyttö itse asiassa suoranaisesti pakottaakin organisoimaan tiedostot hierarkkiseen hakemistorakenteeseen: Java tulkitsee pakkauksen nimen pisteillä erotelluksi alihakemistopoluksi, joka kertoo, missä alihakemistossa kyseisen pakkauksen tiedostot sijaitsevat.
Esimerkiksi ylempänä esitetty pakkaukseen fi.tuni.prog3.helloworld
määritetty luokka HelloWord
pitää sijoittaa alihakemistoon fi/tuni/prog3/helloworld
(Mac ja Linux) tai alihakemistoon
fi\tuni\prog3\helloworld
(Windows). Ohjelmaa ajettaessa Java-virtuaalikoneelle tulee antaa
luokan nimi pakkauksen kera eli ajo tehtäisiin tapaan java fi.tuni.prog3.helloworld.HelloWorld
.
Tällöin virtuaalikone osaa aloittaa suorituksen suorituksen luokkatiedostosta
fi/tuni/prog3/helloworld/HelloWorld.class
(Mac ja Linux) tai
fi\tuni\prog3\helloworld\HelloWorld.class
(Windows).
Virtuaalikone olettaa ilman lisämäärityksiä, että ajokomento annetaan hakemistossa,
jonka välittömänä alihakemistona pakkaushakemisto on. Jos yllä annettu pakkaus on
esimerkiksi Mac- tai Linux-järjestelmässä osa Maven-projektia eli hakemistossa
src/main/java/fi/tuni/prog3/helloworld/
, niin ajokomento java fi.tuni.prog3.helloworld.HelloWorld
tulee antaa hakemistossa java
, jotta ohjelma saadaan suoritettua.
Kun käytetään aiemmin mainittua periaatetta, että pakkaukset nimetään käänteisesti internet-osoitteen mukaan, tulee koodi organisoitua hakemistorakenteeseen, jossa esimerkiksi kaikki saman organisaation koodit ovat keskenään samassa alihakemistossa.
Maven ja pakkaukset¶
Maven-projekteissa on suotavaa käyttää pakkauksia. Samaan tapaan kuin edellä
kooditiedoston alkuun laitetaan pakkausmääritys, tässä esimerkissä package fi.tuni.prog3.helloworld;
.
Kooditiedostot myös organisoidaan pakkauksia vastaavaan kansiorakenteeseen. Edellä esitetty esimerkkiluokka
tulisi Maven-projektissa siis sijoittaa polkuun src/main/java/fi/tuni/prog3/helloworld/HelloWorld.java
.
Pakkauksien käyttö edellyttää myös muutamia muutoksia projektitiedostoon pom.xml
. HelloWorld
-luokalle
tehdyn Maven-projektin projektitiedosto voisi näyttää esimerkiksi tältä:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>fi.tuni.prog3</groupId>
<artifactId>helloworld</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>com.jolira</groupId>
<artifactId>onejar-maven-plugin</artifactId>
<version>1.4.4</version>
<executions>
<execution>
<configuration>
<mainClass>fi.tuni.prog3.helloworld.HelloWorld</mainClass>
<onejarVersion>0.97</onejarVersion>
<attachToBuild>true</attachToBuild>
</configuration>
<goals>
<goal>one-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Mavenilla hallituissa Java-projekteissa on vakiintunut käytäntö se, että groupId
ja artifactId
muodostavat yhdessä projektin Java-pakkauksen nimen. Jos tavoitteena
on esimerkiksi sijoittaa projektin tiedostot fi.tuni.prog3.helloworld
-nimiseen pakkaukseen,
annetaan groupId
-elementin arvoksi fi.tuni.prog3
ja artifactId
-elementin
arvoksi helloworld
. Mavenia tuntevat ohjelmoijat olettavat, että tätä käytännettä
noudatetaan. Jatka siksi perinnettä itsekin, vaikka pakkaus ja elementti voivat
olla teknisellä tasolla eri nimisiä.
Huomaa myös, että OneJar-pluginin mainClass
-elementissä on annettava pääluokka pakkauksen
kanssa. Tässä esimerkissä elementin sisältö on fi.tuni.prog3.helloworld.HelloWorld
.
OneJar-liitännäinen nimeää pakatut Maven projektit samoin kuin pakkaamattomat projektit
ja myös sijoittaa projektit samaan paikkaan. JAR-tiedoston nimessä käytetään aiemmin esitettyyn
tapaan Maven projektin artifactId
- ja version
-elementtien arvoja ja tiedosto
tallentuu target
-hakemistoon. Esimerkiksi tässä luvussa käsitellyn paketoidun HelloWorld-esimerkin
JAR-tiedosto olisi Linux- ja Mac-järjestelmissä target/helloworld-1.0.one-jar.jar
ja
Windows-järjestelmissä target\helloworld-1.0.one-jar.jar
. JAR-tiedosto voidaan suorittaa
samaan tapaan kuin aiemmin eli ilman pakkaukseen liittyviä lisäargumentteja projektin
juurihakemistosta komennolla java -jar target/helloworld-1.0.one-jar.jar
.
Tämän viikon ja seuraavien viikkojen tehtävissä kooditiedostot tulee aina sijoittaa annettuun pakkaukseen, jotta tehtävät menevät tarkistimesta läpi.