7. Rajapinnat

Rajapinta

Rajapinta vaatii, että luokka sisältää tietyt metodit. Rajapintojen avulla samalla koodilla voidaan käsitellä erityyppisiä olioita, joissa on kuitenkin yhteisiä piirteitä.

Rajapinta määritellään muuten samalla tavalla kuin luokka, mutta sanan class tilalla on sana interface ja rajapinnan ainoa sisältö on joukko metodien runkoja.

Esimerkki: Puhuvat eläimet

Seuraava rajapinta Puhuva vaatii, että luokassa on metodi puhu:

public interface Puhuva {
    public void puhu();
}

Luokat Koira, Kissa ja Lehma toteuttavat rajapinnan Puhuva:

public class Koira implements Puhuva {
    public void puhu() {
	System.out.println("Hau hau!");
    }
}
public class Kissa implements Puhuva {
    public void puhu() {
	System.out.println("Miau!");
    }
}
public class Lehma implements Puhuva {
    public void puhu() {
	System.out.println("Ammuu!");
    }
}

Seuraava koodi esittelee luokkien käyttämistä:

ArrayList<Puhuva> elaimet = new ArrayList<Puhuva>();

elaimet.add(new Koira());
elaimet.add(new Kissa());
elaimet.add(new Lehma());

for (Puhuva elain : elaimet) {
    elain.puhu();
}

Ohjelman tulostus on seuraava:

Hau hau!
Miau!
Ammuu!

Tässä rajapinta Puhuva takaa, että kaikki rajapinnan toteuttavat luokat sisältävät metodin puhu. Tämän ansiosta metodia voidaan kutsua kaikille listan olioille, vaikka niiden tarkemmasta tyypistä ei ole tietoa.

Comparable-rajapinta

Yksi Javan valmiista rajapinnoista on Comparable. Tämä rajapinta vaatii, että luokassa on metodi compareTo, jolla voi verrata kahden luokkaa vastaavan olion järjestystä keskenään.

Metodin kutsutapa on a.compareTo(b), ja metodin palautusarvon tulisi olla seuraava:

Luokka String sisältää valmiiksi metodin compareTo. Tässä tapauksessa järjestys tarkoittaa merkkijonojen aakkosjärjestystä. Seuraava koodi esittelee metodin toimintaa:

String eka = "Aapeli";
String toka = "Maija";
String kolmas = "Aapeli";

System.out.println(eka.compareTo(toka));
System.out.println(toka.compareTo(eka));
System.out.println(eka.compareTo(kolmas));

Koodin tulostus on seuraava:

-12
12
0

Merkkijono "Aapeli" on aakkosissa ennen kuin "Maija", joten ensimmäinen arvo on negatiivinen ja toinen on positiivinen. Tässä ei ole oleellista se, että arvot sattuvat olemaan nimenomaan -12 ja 12, vaan se, että niiden negatiivisuus ja positiivisuus kertovat merkkijonojen järjestyksen.

Metodia compareTo tarvitaan esimerkiksi silloin, kun listan sisältö halutaan järjestää. Esimerkiksi seuraava koodi toimii, koska luokkaan String on toteutettu metodi compareTo:

ArrayList<String> nimet = new ArrayList<String>();
nimet.add("Uolevi");
nimet.add("Aapeli");
nimet.add("Maija");

Collections.sort(nimet);

System.out.println(nimet);

Koodin tulostus on seuraava:

[Aapeli, Maija, Uolevi]

Jos oman luokan olioita on tarpeen vertailla, metodi compareTo täytyy toteuttaa itse. Näin luokan saa toteuttamaan rajapinnan Comparable, joka on vaatimuksena esimerkiksi metodin Collections.sort käyttämiselle.

Seuraavassa esimerkissä luokkaan Henkilo on lisätty metodi compareTo. Tässä järjestys määräytyy ensisijaisesti henkilön sukunimen ja toissijaisesti henkilön etunimen perusteella.

public class Henkilo implements Comparable<Henkilo> {
    private String etunimi;
    private String sukunimi;

    public Henkilo(String etunimi, String sukunimi) {
	this.etunimi = etunimi;
	this.sukunimi = sukunimi;
    }

    public String toString() {
	return etunimi + " " + sukunimi;
    }

    public int compareTo(Henkilo toinen) {
	if (sukunimi.equals(toinen.sukunimi)) {
	    return etunimi.compareTo(toinen.etunimi);
	} else {
	    return sukunimi.compareTo(toinen.sukunimi);
	}
    }
}

Luokka hyödyntää merkkijonometodeja equals ja compareTo: jos sukunimet ovat samat, palautetaan etunimien vertailun tulos, ja muuten palautetaan sukunimien vertailun tulos.

Huomaa, että metodissa compareTo viitataan toisen olion yksityisiin muuttujiin. Tämä on mahdollista silloin, kun toisen olion luokka on sama kuin oman olion luokka.

Nyt henkilöiden lista voidan järjestää seuraavasti:

ArrayList<Henkilo> lista = new ArrayList<Henkilo>();
lista.add(new Henkilo("Roope", "Ankka");
lista.add(new Henkilo("Mikki", "Hiiri");
lista.add(new Henkilo("Aku", "Ankka");

Collections.sort(lista);

System.out.println(lista);

Koodin tulostus on seuraava:

[Aku Ankka, Roope Ankka, Mikki Hiiri]

Esimerkki: Alueet

Seuraava rajapinta Alue vastaa maantieteellistä aluetta:

public interface Alue {
    public int vakiluku();
    public void tulosta(int sisennys);
}

Metodi vakiluku palauttaa alueen asukasmäärän ja metodi tulosta tulostaa alueen tiedot halutusti sisennettynä. Esimerkkiohjelma selventää, mitä sisennys tarkoittaa käytännössä.

Määritellään rajapinnan toteuttavia luokkia:

public class Valtio implements Alue {
    private String nimi;
    private int vakiluku;

    public Valtio(String nimi, int vakiluku) {
	this.nimi = nimi;
	this.vakiluku = vakiluku;
    }

    public int vakiluku() {
	return vakiluku;
    }

    private void tulostaSisennys(int sisennys) {
	for (int i = 0; i < sisennys; i++) {
	    System.out.print(" ");
	}
    }

    public void tulosta(int sisennys) {
	tulostaSisennys(sisennys);
	System.out.println(nimi  + " (" + vakiluku + " asukasta)");
    }
}
import java.util.*;

public class Aluejoukko implements Alue {
    private String nimi;
    private ArrayList<Alue> alueet = new ArrayList<Alue>();

    public Aluejoukko(String nimi) {
	this.nimi = nimi;
    }

    public void lisaaAlue(Alue alue) {
	alueet.add(alue);
    }

    public int vakiluku() {
	int tulos = 0;
	for (Alue alue : alueet) {
	    tulos += alue.vakiluku();
	}
	return tulos;
    }

    private void tulostaSisennys(int sisennys) {
	for (int i = 0; i < sisennys; i++) {
	    System.out.print(" ");
	}
    }

    public void tulosta(int sisennys) {
	tulostaSisennys(sisennys);
	System.out.println(nimi);
	for (Alue alue : alueet) {
	    alue.tulosta(sisennys + 4);
	}
    }
}

Seuraava ohjelma esittelee luokkien käyttämistä:

Valtio suomi = new Valtio("Suomi", 5300000);
Valtio ruotsi = new Valtio("Ruotsi", 9400000);
Valtio norja = new Valtio("Norja", 4600000);
Valtio tanska = new Valtio("Tanska", 5400000);
Valtio viro = new Valtio("Viro", 1200000);
Valtio latvia = new Valtio("Latvia", 2200000);
Valtio liettua = new Valtio("Liettua", 3500000);

Aluejoukko pohjoismaat = new Aluejoukko("Pohjoismaat");
pohjoismaat.lisaaAlue(suomi);
pohjoismaat.lisaaAlue(ruotsi);
pohjoismaat.lisaaAlue(norja);
pohjoismaat.lisaaAlue(tanska);

Aluejoukko baltianMaat = new Aluejoukko("Baltian maat");
baltianMaat.lisaaAlue(viro);
baltianMaat.lisaaAlue(latvia);
baltianMaat.lisaaAlue(liettua);

Aluejoukko itamerenMaat = new Aluejoukko("Itämeren maat");
itamerenMaat.lisaaAlue(pohjoismaat);
itamerenMaat.lisaaAlue(baltianMaat);

System.out.println("Itämeren maiden väkiluku: " + itamerenMaat.vakiluku());
itamerenMaat.tulosta(0);

Ohjelman tulostus on seuraava:

Itämeren maiden väkiluku: 31600000
Itämeren maat
    Pohjoismaat
        Suomi (5300000 asukasta)
        Ruotsi (9400000 asukasta)
        Norja (4600000 asukasta)
        Tanska (5400000 asukasta)
    Baltian maat
        Viro (1200000 asukasta)
        Latvia (2200000 asukasta)
        Liettua (3500000 asukasta)

Tässä aluejoukko voi muodostua toisista aluejoukoista, mikä mahdollistaa puumaisen rakenteen toteuttamisen.