📚 Perintä ja rajapinnat
Perintä (inheritance) ja rajapinnat (interfaces) ovat olio-ohjelmoinnin ratkaisuja koodin uudelleenkäytettävyyden ja yhteensopivuuden edistämiseksi. Interface määrittää metodin nimet, paluuarvot ja parametrit.
🧠Tällä sivulla:
- Perintä
- 🔌 Rajapinnat
-
🔢 Comparable-rajapinta ja
compareTo()-metodi - Mitä rajapinnassa voi olla?
Periytymisen ideaa voisi kuvailla sillä, että kuviteltaisiin netin kauppapaikka, jossa on myynnissä monenlaisia tuotteita esineistä asuntoihin ja ajoneuvoihin. Kaikille tuotteille yhteiset ominaisuudet voitaisiin toteuttaa yhteen luokkaan, jota voidaan laajentaa tapauskohtaisten aliluokkien avulla. Näin vältetään toteuttamasta samoja yhteisiä ominaisuuksia moneen luokkaan. Samalla koodin yhteensopivuus paranee, kun aliluokkien oliot ovat yhteensopivia yliluokan olioiden kanssa. Perinnän avulla sekä autoja, asuntoja että muita tuotteita voidaankin tarvittaessa käsitellä esimerkiksi samalla listalla.

Käytännössä kuitenkin periytymisessä oleellisinta on vain ymmärtää sen periaate ja ymmärtää miten se toimii Javan sisäisissä ja siihen lisätyissä kirjastoissa. Oman periytymishierarkian toteuttaminen teollisuuden oikeaan tarpeeseen on suhteellisen harvinaista eikä ensimmäinen poikkeustapaus, jota pitäisi aina punnita.
Perintää käytetään usein tilanteissa, joissa on olemassa jo jokin toteutus, jota halutaan laajentaa erityistapauksen avulla. Rajapintoja puolestaan käytetään usein tilanteissa, joissa selvää yhteistä toteutusta ei ole. Toisin kuin luokat, rajapinnat ovat abstrakteja, eli niistä ei voida luoda olioita. Rajapintojen avulla voidaan kuitenkin määritellä yksi tai useampia metodeja, jotka rajapinnan täyttävien luokkien on toteutettava.
Perintä
“Perintä on väline käsitehierarkioiden rakentamiseen ja erikoistamiseen; aliluokka on aina yliluokan erikoistapaus. Jos luotava luokka on olemassaolevan luokan erikoistapaus, voidaan uusi luokka luoda perimällä olemassaoleva luokka. Esimerkiksi auton osiin liittyvässä esimerkissä moottori on osa, mutta moottoriin liittyy lisätoiminnallisuutta mitä jokaisella osalla ei ole.”
Lähde: Helsingin Yliopiston Agile Education Research –tutkimusryhmä. Perintä. mooc.fi
Opetuskalvot PowerPoint-kalvot löydät täältä.
🏋️ Exercise ja sen aliluokat
classDiagram
class Exercise {
+String name
+int durationMinutes
+double calculateCaloriesBurned()
}
class RunningExercise {
+double distanceKm
}
class SwimmingExercise {
+double distanceKm
}
class PadelExercise {
+int sets
}
Exercise <|-- RunningExercise
Exercise <|-- SwimmingExercise
Exercise <|-- PadelExercise
💡 Perintäesimerkki
Henkilo-luokka
package perinta;
public class Henkilo {
private String etunimi;
private String sukunimi;
private String email;
public Henkilo(String etunimi, String sukunimi, String email) {
this.etunimi = etunimi;
this.sukunimi = sukunimi;
this.email = email;
}
public String getEtunimi() {
return etunimi;
}
public String getSukunimi() {
return sukunimi;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "Henkilo [etunimi=" + etunimi + ", sukunimi=" + sukunimi + ", email=" + email + "]";
}
}
Henkilo-luokka sisältää henkilön perustietoja, jos ollaan tekemässä oppilaitoksen järjestelmään, tarvitaan vielä ainakin Opiskelija-luokka. Oppilaalla on nimi, sähköposti ja lisäksi opiskelijanumero ja aloitusvuosi (toki oikeasti paljon muitakin ominaisuuksia). Periytymisen avulla voidaan hyödyntää Henkilo-luokkaa Opiskelija-luokkaa määriteltäessä. Huomaa seuraavassa koodiesimerkissä varatut sanat extends, super ja annotaatio @Override.
package perinta;
public class Opiskelija extends Henkilo {
private String opiskelijanumero;
private int aloitusvuosi;
public Opiskelija(String etunimi, String sukunimi, String email, String opiskelijanumero, int aloitusvuosi) {
super(etunimi, sukunimi, email);
this.opiskelijanumero = opiskelijanumero;
this.aloitusvuosi = aloitusvuosi;
}
public String getOpiskelijanumero() {
return opiskelijanumero;
}
public int getAloitusvuosi() {
return aloitusvuosi;
}
@Override
public String toString() {
return super.toString() + " Opiskelija [opiskelijanumero=" + opiskelijanumero + ", aloitusvuosi=" + aloitusvuosi + "]";
}
}
Henkilo-luokka on yliluokka (yläluokka, super class), käytetään myös termiä kantaluokka (base class). Opiskelija-luokka on aliluokka (sub class) tai johdettu luokka (derived class). Opiskelija-luokka perii kaikki kentät ja metodit kantaluokalta, periminen määritellään extends sanalla. Luokka voi periä vain yhden luokan, moniperiytyminen on estetty Java-kielessä. Luokka voi toteuttaa useita rajapintoja. Moniperiytyminen on tarkoituksellisesti jätetty pois Java-kielestä, sen hallitsematon käyttö aiheuttaa enemmän ongelmia kuin tuo hyötyjä, lisäksi rajoituksen voi käytännössä kiertää rajapintojen avulla.
Opiskelija-luokassa huomaa miten aliluokassa käytetään hyväksi yliluokan konstruktoria (super(etunimi, sukunimi, email);) sekä toString-metodissa super.toString(). Super viittaa aina kantaluokkaan. Konstruktorissa kantaluokan konstruktorin kutsuminen super-määrittelyllä on pakko olla ensimmäinen lause.
Henkilo-luokassa on määritelty toString()-metodi. Metodit voidaan ylikirjoittaa aliluokissa, tässä esimerkissä tulostetaan nimen lisäksi opiskelijanumero ja aloitusvuosi. Ylikirjoitus on syytä toteuttaa käyttämällä @Override-annotaatiota.
Lisätään vielä Opettaja-luokka:
package perinta;
public class Opettaja extends Henkilo {
private String opettajanumero;
public Opettaja(String etunimi, String sukunimi, String email, String opettajanumero) {
super(etunimi, sukunimi, email);
this.opettajanumero = opettajanumero;
}
public String getOpettajanumero() {
return opettajanumero;
}
@Override
public String toString() {
return super.toString() + " Opettaja [opettajanumero=" + opettajanumero + "]";
}
}
Sekä opettaja että opiskelija sisältävät nimen ja sähköpostiosoitteen. Näiden lisäksi molemmilla aliluokilla on jotain omia ominaisuuksia. Koska molemmilla on sama yliluokka, voidaan niitä käsitellä täysin samalla tavalla. Tehdään vaikka pieni koodipätkä, jolla tehdään lista opettajista ja oppilaista, jotka osallistuvat ohjattuun taukojumppaan.
import java.util.ArrayList;
import java.util.List;
List<Henkilo> osallistujat = new ArrayList<>();
osallistujat.add(new Opettaja("Teemu", "Terävä", "tt@hotmail.com", "h1234"));
osallistujat.add(new Opiskelija("Olivia", "Ahkera", "b987@hh.fi", "b987", 2018));
osallistujat.add(new Opiskelija("Olli", "Oppilas", "b123@hh.fi", "b123", 2023));
osallistujat.add(new Opettaja("Jukka", "Nokkela", "juno@gmail.com", "h9876"));
System.out.println("Taukotilaisuuteen osallistujat: ");
for (Henkilo h : osallistujat) {
System.out.println(h);
}
Aikaisemmin toString()-metodiin liitetty @Override-annotaatio ei ole aivan pakollinen, mutta sitä kannattaa käyttää. Sen avulla kääntäjä voi tarkistaa että kantaluokassa on varmasti olemassa ylikirjoitettava metodi, tehdä optimointia sekä dokumentoida koodin ylläpitäjälle ylikirjoituksesta. Muutoin koodia luettaessa ei pysty päättelemään pelkästä metodista, onko kyseessä ylikirjoitus vai ei.
Â
Joissakin tilanteissa pitää pystyä selvittämään suorituksen aikana muuttujan tyyppi. Tähän voi käyttää Java-kielessä instanceof-operaattoria.
if (hlo instanceof Opettaja) {
// hlo-muuttuja on Opettaja-luokan instanssi
} else {
// jostain muusta luokasta on kysymys
}
instanceof-operaattorin lisäksi suorituksen aikana voi olion tyyppitiedon kysyä getClass()-metodilla. Metodi palauttaa olion, joka sisältää luokan tyyppitiedon, esimerkiksi luokan nimen tai mitä rajapintoja olio toteuttaa.
String luokanNimi = hlo.getClass().getName();
Perintä on ohjelmoinnissa keino rakentaa luokkien välistä hierarkiaa niin, että aliluokka (esim. Opiskelija) saa automaattisesti käyttöönsä yliluokan (esim. Henkilo) ominaisuudet ja metodit. Tämä säästää toistoa ja mahdollistaa yhteiskäsittelyn: eri aliluokkia voidaan käyttää samassa listassa, koska ne kaikki periytyvät samasta kantaluokasta. Java-kielessä perintä tehdään extends-sanalla, ja yliluokan metodeja voidaan ylikirjoittaa @Override-merkinnällä.
🔌 Rajapinnat
“Rajapinnan (engl.
interface) avulla määritellään luokalta vaadittu käyttäytyminen, eli sen metodit. Rajapinnat määritellään kuten normaalit Javan luokat, mutta luokan alussa olevan määrittelyn “public class ...” sijaan käytetään määrittelyä “public interface ...”. Rajapinnat määrittelevät käyttäytymisen metodien niminä ja palautusarvoina, mutta ne eivät aina sisällä metodien konkreettista toteutusta. Näkyvyysmäärettä rajapintoihin ei erikseen merkitä, sillä se on ainapublic.”Lähde: Helsingin Yliopiston Agile Education Research –tutkimusryhmä. Rajapinta. mooc.fi
Rajapinta (interface) on Javan tapa sanoa:
“Tässä on joukko metodeja, jotka pitää olla, mutta en kerro miten ne tehdään.”
Ajattele rajapintaa kuin sopimus tai to do -lista luokalle. Kun luokka “toteuttaa” (implements) rajapinnan, se lupaa kirjoittaa ne metodit itse.
đź§Ş Yksinkertainen esimerkki
Ajatellaan, että rajapinta on “Ajettava” (interface Ajettava) — se sanoo vain:
“Kaikilla ajettavilla asioilla pitää olla
aja()-metodi.”
Mutta se ei kerro, miten ajetaan. Se jää jokaisen ajettavan olion omaksi hommaksi.
public interface Ajettava {
void aja();
}
Nyt voit tehdä vaikka:
public class Auto implements Ajettava {
public void aja() {
System.out.println("Auto ajaa moottoritiellä");
}
}
public class Polkupyora implements Ajettava {
public void aja() {
System.out.println("Polkupyörä rullaa pyörätiellä");
}
}
Molemmat toteuttavat saman rajapinnan, joten niitä voi käsitellä samassa listassa:
import java.util.ArrayList;
import java.util.List;
List<Ajettava> kulkuneuvot = new ArrayList<>();
kulkuneuvot.add(new Auto());
kulkuneuvot.add(new Polkupyora());
for (Ajettava a : kulkuneuvot) {
a.aja(); // kutsuu oikeaa versiota
}
🔢 Comparable-rajapinta ja compareTo()-metodi
Joskus olioita halutaan järjestää automaattisesti tietyn ominaisuuden mukaan. Tätä varten Javassa käytetään usein Comparable-rajapintaa. Kun luokka toteuttaa Comparable-rajapinnan, sille määritellään compareTo()-metodi, joka kertoo miten kahta oliota verrataan toisiinsa.
compareTo()-metodi palauttaa:
- negatiivisen luvun, jos nykyinen olio on pienempi kuin verrattava olio
- nollan, jos oliot ovat vertailun kannalta samanarvoisia
- positiivisen luvun, jos nykyinen olio on suurempi kuin verrattava olio
Tämä on hyvin tavallinen tapa tehdä luokasta järjestettävä esimerkiksi Collections.sort()-metodia varten.
🌍 Esimerkki: valtioiden järjestäminen BKT:n mukaan
Tehdään luokka Valtio, joka toteuttaa Comparable<Valtio>-rajapinnan. Järjestetään valtiot BKT:n mukaan pienimmästä suurimpaan.
public class Valtio implements Comparable<Valtio> {
private String nimi;
private double bkt;
public Valtio(String nimi, double bkt) {
this.nimi = nimi;
this.bkt = bkt;
}
public String getNimi() {
return nimi;
}
public double getBkt() {
return bkt;
}
@Override
public int compareTo(Valtio toinen) {
return Double.compare(this.bkt, toinen.bkt);
}
@Override
public String toString() {
return nimi + " (BKT: " + bkt + ")";
}
}
Nyt voidaan tehdä lista valtioista ja järjestää ne compareTo()-metodin avulla:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ValtioTesti {
public static void main(String[] args) {
List<Valtio> valtiot = new ArrayList<>();
valtiot.add(new Valtio("USA", 27360.0));
valtiot.add(new Valtio("Venäjä", 2021.0));
valtiot.add(new Valtio("Kiina", 17700.0));
valtiot.add(new Valtio("Intia", 3567.0));
Collections.sort(valtiot);
System.out.println("Valtiot BKT:n mukaan järjestettynä:");
for (Valtio valtio : valtiot) {
System.out.println(valtio);
}
}
}
Tulostus voisi olla esimerkiksi seuraavanlainen:
Valtiot BKT:n mukaan järjestettynä:
Venäjä (BKT: 2021.0)
Intia (BKT: 3567.0)
Kiina (BKT: 17700.0)
USA (BKT: 27360.0)
Tässä Collections.sort(valtiot) käyttää automaattisesti Valtio-luokan compareTo()-metodia. Koska vertailu tehtiin BKT:n perusteella, lista järjestyy BKT-arvon mukaan.
Jos haluttaisiin järjestää suurimmasta pienimpään, voitaisiin lista kääntää vielä erikseen:
Collections.sort(valtiot);
Collections.reverse(valtiot);
💡 Miksi tämä on hyödyllinen?
Comparable on hyödyllinen silloin, kun luokalla on yksi luonteva järjestys. Esimerkiksi:
- opiskelijat opiskelijanumeron mukaan
- tuotteet hinnan mukaan
- päivämäärät aikajärjestykseen
- valtiot BKT:n mukaan
Kun luokka toteuttaa Comparable-rajapinnan, sitä voidaan käyttää helposti lajittelussa ilman että vertailulogiiikkaa tarvitsee kirjoittaa joka kerta uudelleen.
Mitä rajapinnassa voi olla?
Tyypillisesti rajapinnassa on määritelty joukko metodeja eikä muuta. Näin olikin tilanne Javan alkuaikoina, mutta kehitys kehittyy ja rajapintoihin on tullut lisää ominaisuuksia.
Rajapinta voi sisältää:
- vakiomuuttujia (constant variables)
- abstrakteja metodeja (abstract methods)
- static-metodeja (static methods)
- oletusmetodeja (default methods)
Abstrakti metodi on juuri se mitä aikaisemmin materiaalissa on käyty läpi. Oletusmetodi on metodi, jolla on oletustoteutus rajapinnassa. Jos luokka ei toteuta oletusmetodia, käytetään silloin rajapinnassa olevaa koodia. Rajapinnassa olevat muuttujat (kentät) ovat vakioita ja vastaavasti static-metodeja voi kutsua kuten luokan static-metodia.
Näistä muut ovat aika harvinaisia, paitsi aluksi esitelty abstrakti metodi. Abstraktilla metodilla ei ole toteutusta vaan pelkkä otsikko ja rajapinnan toteuttava luokka sisältää metodin toteutuksen eli koodin. Tarkennus vielä että kaikki rajapinnan metodit ovat Javassa automaattisesti abstrakteja, ei tarvitse käyttää varattua sanaa abstract, eikä muutenkaan puheessa korosteta asiaa kun puhutaan rajapinnoista ja niiden metodeista.
Esimerkki rajapinnan toiminnoista
public interface DemoRajapinta {
int MAXKOKO = 1024; // vakio
String kuvaus(); // tämä on toteutettava!
// staattiset kuten missä tahansa luokassa
static boolean onOk(String data) {
return data != null && data.length() >= 2 && data.length() <= 5;
}
// tätä ei ole pakko toteuttaa implementoivassa luokassa
default void tulostaTiedot() {
System.out.println("Oletustoteutus");
}
}
âť“ Miksi rajapintoja tarvitaan?
- Suurissa ohjelmissa: Rajapinnat irrottavat “mitä tehdään” ja “miten se tehdään” toisistaan.
- Testauksessa: Voit testata luokkia rajapinnan kautta ilman että tiedät toteutuksen.
- Frameworkeissa: Spring, Android, yms. käyttävät rajapintoja paljon.
- Kirjastoissa: Monet kirjastot edellyttävät rajapintojen käyttöä.
- Yksi luokka voi toteuttaa monta rajapintaa — toisin kuin perinnässä, jossa voi periä vain yhden luokan.
📝 Yhteenveto
Rajapinta on sopimus siitä, mitä metodit pitää olla. Se ei kerro miten ne toimii — sen tekee toteuttava luokka.
Rajapinnat ovat välttämättömiä jatkokursseilla, isoissa projekteissa ja ammattimaisessa ohjelmoinnissa. Opettele nyt, käytät varmasti myöhemmin.