Object ja luokan periminen
Luokkia käytetään olio-ohjelmoinnissa ongelma-alueeseen liittyvien käsitteiden selkeyttämiseen. Jokainen luomamme luokka lisää ohjelmointikieleen toiminnallisuutta. Tätä toiminnallisuutta tarvitaan kohtaamiemme ongelmien ratkomiseen. Olio-ohjelmoinnissa ratkaisut syntyvät luokista luotujen olioiden välisen interaktion avulla. Olio-ohjelmoinnissa olio on itsenäinen kokonaisuus, jolla on olion tarjoamien metodien avulla muutettava tila. Olioita käytetään yhteistyössä; jokaisella oliolla on oma vastuualue. Esimerkiksi käyttöliittymäluokkamme ovat tähän mennessä hyödyntäneet Scanner
-olioita.
Jokainen Javan luokka perii luokan Object, eli jokainen luomamme luokka saa käyttöönsä kaikki Object-luokassa määritellyt metodit. Jos haluamme muuttaa Object-luokassa määriteltyjen metodien toiminnallisuutta tulee ne korvata (Override
) määrittelemällä niille uusi toteutus luodussa luokassa. Oliomme saavat luokasta Object käyttöönsä mm. metodit equals
ja hashCode
.
Object
perimisen lisäksi myös muiden luokkien periminen on mahdollista. Javan ArrayList-luokan "ohjelmointirajapintaa" eli APIa tarkasteltaessa huomaamme että ArrayList
perii luokan AbstractList
. Luokka AbstractList
perii luokan AbstractCollection
, joka perii luokan Object
.java.lang.Object java.util.AbstractCollection<E> java.util.AbstractList<E> java.util.ArrayList<E>
Kukin luokka voi periä suoranaisesti yhden luokan. Välillisesti luokka kuitenkin perii kaikki perimänsä luokan ominaisuudet. Luokka ArrayList
perii luokan AbstractList
, ja välillisesti luokat AbstractCollection
ja Object
. Luokalla ArrayList
on siis käytössään luokkien AbstractList
, AbstractCollection
ja Object
muuttujat ja metodit.
Luokan ominaisuudet peritään avainsanalla extends
. Luokan perivää luokkaa kutsutaan aliluokaksi (subclass), perittävää luokkaa yliluokaksi (superclass).
Tutustutaan erään autonvalmistajan järjestelmään, joka hallinnoi auton osia. Osien hallinan peruskomponentti on luokka Osa
, joka määrittelee tunnuksen, valmistajan ja kuvauksen.
public class Osa {
private String tunnus;
private String valmistaja;
private String kuvaus;
public Osa(String tunnus, String valmistaja, String kuvaus) {
this.tunnus = tunnus;
this.valmistaja = valmistaja;
this.kuvaus = kuvaus;
}
public String getTunnus() {
return tunnus;
}
public String getKuvaus() {
return kuvaus;
}
public String getValmistaja() {
return valmistaja;
}
}
Yksi osa autoa on moottori. Kuten kaikilla osilla, myös moottorilla on valmistaja, tunnus ja kuvaus. Näiden lisäksi moottoriin liittyy moottorityyppi: esimerkiksi polttomoottori, sähkömoottori tai hybridi.
Perinteinen, ei perintää hyödyntävä tapa olisi toteuttaa luokka Moottori
seuraavasti.
public class Moottori {
private String moottorityyppi;
private String tunnus;
private String valmistaja;
private String kuvaus;
public Moottori(String moottorityyppi, String tunnus, String valmistaja, String kuvaus) {
this.moottorityyppi = moottorityyppi;
this.tunnus = tunnus;
this.valmistaja = valmistaja;
this.kuvaus = kuvaus;
}
public String getMoottorityyppi() {
return moottorityyppi;
}
public String getTunnus() {
return tunnus;
}
public String getKuvaus() {
return kuvaus;
}
public String getValmistaja() {
return valmistaja;
}
}
Huomaamme luokassa Moottori
merkittävän määrän yhtäläisyyksiä luokan Osa
kanssa. Voidaankin sanoa, että Moottori
on luokan Osa
erikoistapaus. Moottori on Osa, mutta sillä on myös ominaisuuksia, joita osalla ei ole, eli tässä moottorin tyyppi.
Tehdään sama luokka Moottori
, ja toteutetaan luokka perintää hyödyntämällä. Luodaan luokan Osa
perivä luokka Moottori
: moottori on osan erikoistapaus.
public class Moottori extends Osa {
private String moottorityyppi;
public Moottori(String moottorityyppi, String tunnus, String valmistaja, String kuvaus) {
super(tunnus, valmistaja, kuvaus);
this.moottorityyppi = moottorityyppi;
}
public String getMoottorityyppi() {
return moottorityyppi;
}
}
Luokkamäärittely public class Moottori extends Osa
kertoo että luokka Moottori
perii luokan Osa
toiminnallisuuden. Luokassa Moottori
määritellään oliomuuttuja moottorityyppi
.
Moottori-luokan konstruktori on mielenkiintoinen. Konstruktorin ensimmäisellä rivillä on avainsana super
, jolla kutsutaan yliluokan konstruktoria. Kutsu super(tunnus, valmistaja, kuvaus)
kutsuu luokassa Osa
määriteltyä konstruktoria public Osa(String tunnus, String valmistaja, String kuvaus)
, jolloin yliluokassa määritellyt oliomuuttujat saavat arvonsa. Tämän jälkeen oliomuuttujalle moottorityyppi
asetetaan siihen liittyvä arvo.
Kutsu on hieman samankaltainen kuin this
-kutsu konstruktorissa; this-kutsulla kutsutaan tämän luokan konstruktoria, super-kutsulla yliluokan konstruktoria. Mikäli konstruktorissa käytetään yliluokan konstruktoria, eli konstruktorissa on super
-kutsu, tulee super
-kutsun olla this
-kutsun lailla konstruktorin ensimmäisellä rivillä.
Kun luokka Moottori
perii luokan Osa
, saa se käyttöönsä kaikki luokan Osa
tarjoamat metodit. Luokasta Moottori
voi tehdä ilmentymän aivan kuten mistä tahansa muustakin luokasta.
Moottori moottori = new Moottori("polttomoottori", "hz", "volkswagen", "VW GOLF 1L 86-91");
System.out.println(moottori.getMoottorityyppi());
System.out.println(moottori.getValmistaja());
polttomoottori volkswagen
Kuten huomaat, luokalla Moottori
on käytössä luokassa Osa
määritellyt metodit.
Näkyvyysmääreet private, protected ja public
Mikäli metodilla tai muuttujalla on näkyvyysmääre private
, se näkyy vain luokan sisäisille metodeille. Se ei näy aliluokille eikä aliluokalla ole mitään suoraa tapaa päästä käsiksi siihen. Moottori-luokasta ei siis pääse suoraan käsiksi yliluokassa Osa määriteltyihin muuttujiin tunnus, valmistaja, kuvaus. Tällä tarkoitetaan sitä, että Moottori-luokassa ohjelmoija ei voi suoraan käsitellä niitä yliluokan muuttujia, joilla on näkyvyysmääre private.
Aliluokka näkee kaiken yliluokan julkisen eli public
-määreellä varustetun kaluston. Jos halutaan määritellä yliluokkaan joitain muuttujia tai metodeja joiden näkeminen halutaan sallia aliluokille, mutta estää muilta, voidaan käyttää näkyvyysmäärettä protected
.
Yliluokan konstruktorin kutsuminen
Yliluokan konstruktoria kutsutaan avainsanalla super
. Kutsulle annetaan parametrina yliluokan konstruktorin vaatiman tyyppiset arvot. Mikäli yliluokalla on useampi konstruktori, super-kutsulle annettavat parametrit määräävät kutsuttavan konstruktorin.
Konstruktorikutsun yhteydessä yliluokassa määritellyt muuttujat alustetaan. Konstruktorikutsussa tapahtuu käytännössä täysin samat asiat kuin normaalissa konstruktorikutsussa. Mikäli yliluokassa ei ole määritelty parametritonta konstruktoria, tulee aliluokan konstruktorikutsuissa olla aina mukana yliluokan konstruktorikutsu.
Alla olevassa esimerkissä demonstroidaan this
-kutsua ja super
-kutsua. Luokka Yliluokka
sisältää oliomuuttujan ja kaksi konstruktoria. Toinen konstruktoreista kutsuu toista this
-kutsulla. Luokka Aliluokka
sisältää parametrillisen konstruktorin, mutta sillä ei ole yhtäkään oliomuuttujaa. Luokan Aliluokka
-konstruktori kutsuu luokan Yliluokka
parametrillista konstruktoria.
public class Yliluokka {
private String oliomuuttuja;
public Yliluokka() {
this("Esimerkki");
}
public Yliluokka(String arvo) {
this.oliomuuttuja = arvo;
}
public String toString() {
return this.oliomuuttuja;
}
}
public class Aliluokka extends Yliluokka {
public Aliluokka() {
super("Aliluokka");
}
}
Yliluokka y = new Yliluokka();
Aliluokka a = new Aliluokka();
System.out.println(y);
System.out.println(a);
Esimerkki Aliluokka
Yliluokan metodin kutsuminen
Yliluokassa määriteltyjä metodeja voi kutsua super
-etuliitteen avulla, aivan kuten tässä luokassa määriteltyjä metodeja voi kutsua this
-etuliitteellä. Esimerkiksi yliluokassa määriteltyä toString
-metodia voi hyödyntää sen korvaavassa metodissa seuraavasti:
@Override
public String toString() {
return super.toString() + "\n Ja oma viestini vielä!";
}
Olion todellinen tyyppi määrää suoritettavan metodin
Olion kutsuttavissa olevat metodit määrittyvät muuttujan tyypin kautta. Esimerkiksi jos edellä toteutetun Opiskelija
-tyyppisen olion viite on talletettu Henkilo
-tyyppiseen muuttujaan, on oliosta käytössä vain Henkilo
-luokassa määritellyt metodit (sekä Henkilo-luokan yliluokan ja rajapintojen metodit):
Henkilo olli = new Opiskelija("Olli", "Ida Albergintie 1 00400 Helsinki");
olli.opintopisteita(); // EI TOIMI!
olli.opiskele(); // EI TOIMI!
System.out.println(olli); // olli.toString() TOIMII
Oliolla on siis käytössä jokainen sen tyyppiin sekä sen yliluokkiin ja rajapintoihin liittyvä metodi. Esimerkiksi Opiskelija-tyyppisellä oliolla on käytössä Henkilo-luokassa määritellyt metodit sekä Object-luokassa määritellyt metodit.
Edellisessä tehtävässä korvasimme Opiskelijan luokalta Henkilö perimän toString
uudella versiolla. Myös luokka Henkilö oli jo korvannut Object-luokalta perimänsä toStringin. Jos käsittelemme olioa jonkun muun kuin sen todellisen tyypin kautta, mitä versiota olion metodista kutsutaan?
Seuraavassa esimerkissä kahta opiskelijaa käsitellään erityyppisten muuttujien kautta. Mikä versio metodista toString suoritetaan, luokassa Object, Henkilo vai Opiskelija määritelty?
Opiskelija olli = new Opiskelija("Olli", "Ida Albergintie 1 00400 Helsinki");
System.out.println(olli);
Henkilo olliHenkilo = new Opiskelija("Olli", "Ida Albergintie 1 00400 Helsinki");
System.out.println(olliHenkilo);
Object olliObject = new Opiskelija("Olli", "Ida Albergintie 1 00400 Helsinki");
System.out.println(olliObject);
Object liisa = new Opiskelija("Liisa", "Väinö Auerin katu 20 00500 Helsinki");
System.out.println(liisa);
Suoritettava metodi valitaan olion todellisen tyypin perusteella, eli sen luokan perusteella, jonka konstruktoria kutsutaan kun olio luodaan. Jos kutsuttua metodia ei ole määritelty luokassa, suoritetaan perintähierarkiassa olion todellista tyyppiä lähinnä oleva metodin toteutus.
Tarkastellaan Polymorfismia toisen esimerkin avulla.
Kaksiulotteisessa koordinaatiostossa sijaitsevaa pistettä voisi kuvata seuraavan luokan avulla:
public class Piste {
private int x;
private int y;
public Piste(int x, int y) {
this.x = x;
this.y = y;
}
public int manhattanEtaisyysOrigosta() {
return Math.abs(x) + Math.abs(y);
}
protected String sijainti(){
return x + ", " + y;
}
@Override
public String toString() {
return "(" + this.sijainti() + ") etäisyys " + this.manhattanEtaisyysOrigosta();
}
}
sijainti
ei ole tarkoitettu ulkoiseen käyttöön, joten se on näkyvyysmääreeltään protected, eli aliluokat pääsevät siihen käsiksi. Esimerkiksi reitinhakualgoritmien hyödyntämällä Manhattan-etäisyydellä tarkoitetaan pisteiden etäisyyttä, jos niiden välin voi kulkea ainoastaan koordinaattiakselien suuntaisesti.Värillinen piste on muuten samanlainen kuin piste, mutta se sisältää merkkijonona ilmaistavan värin. Luokka voidaan siis tehdä perimällä Piste.
public class VariPiste extends Piste {
private String vari;
public VariPiste(int x, int y, String vari) {
super(x, y);
this.vari = vari;
}
@Override
public String toString() {
return super.toString() + " väri: " + vari;
}
}
Luokka määrittelee oliomuuttujan värin talletusta varten. Koordinaatit on valmiiksi määriteltynä yliluokassa. Merkkijonoesityksestä halutaan muuten samanlainen kuin pisteellä, mutta väri tulee myös ilmaista. Ylikirjoitettu metodi toString
kutsuu yliluokan toString-metodia ja lisää sen tulokseen pisteen värin.
Seuraavassa on esimerkki, jossa listalle laitetaan muutama piste. Osa pisteistä on "normaaleja" ja osa väripisteitä. Lopulta tulostetaan listalla olevat pisteet. Jokaisen pisteen metodi toString suoritetaan pisteen todellisen tyypin perusteella, vaikka lista tuntee kaikki pisteet Piste
-tyyppisinä.
public class Main {
public static void main(String[] args) {
ArrayList<Piste> pisteet = new ArrayList<>();
pisteet.add(new Piste(4, 8));
pisteet.add(new VariPiste(1, 1, "vihreä"));
pisteet.add(new VariPiste(2, 5, "sininen"));
pisteet.add(new Piste(0, 0));
for (Piste p: pisteet) {
System.out.println(p);
}
}
}
Haluamme ohjelmaamme myös kolmiulotteisen pisteen. Koska kyseessä ei ole värillinen versio, periytetään se luokasta piste.
public class Piste3D extends Piste {
private int z;
public Piste3D(int x, int y, int z) {
super(x, y);
this.z = z;
}
@Override
protected String sijainti() {
return super.sijainti() + ", " + z; // tulos merkkijono muotoa "x, y, z"
}
@Override
public int manhattanEtaisyysOrigosta() {
// kysytään ensin yliluokalta x:n ja y:n perusteella laskettua etäisyyttä
// ja lisätään tulokseen z-koordinaatin vaikutus
return super.manhattanEtaisyysOrigosta() + Math.abs(z);
}
@Override
public String toString() {
return "(" + this.sijainti() + ") etäisyys " + this.manhattanEtaisyysOrigosta();
}
}
Kolmiulotteinen piste siis määrittelee kolmatta koordinaattia vastaavan oliomuuttujan ja ylikirjoittaa metodit sijainti
, manhattanEtaisyysOrigosta
ja toString
siten, että ne huomioivat kolmannen ulottuvuuden. Voimme nyt laajentaa edellistä esimerkkiä ja lisätä listalle myös kolmiulotteisia pisteitä.
public class Main {
public static void main(String[] args) {
ArrayList<Piste> pisteet = new ArrayList<>();
pisteet.add(new Piste(4, 8));
pisteet.add(new VariPiste(1, 1, "vihreä"));
pisteet.add(new VariPiste(2, 5, "sininen"));
pisteet.add(new Piste3D(5, 2, 8));
pisteet.add(new Piste(0, 0));
for (Piste p: pisteet) {
System.out.println(p);
}
}
}
(4, 8) etäisyys 12 (1, 1) etäisyys 2 väri: vihreä (2, 5) etäisyys 7 väri: sininen (5, 2, 8) etäisyys 15 (0, 0) etäisyys 0
Huomamme, että kolmiulotteisen pisteen metodi toString
on täsmälleen sama kuin pisteen toString. Voisimmeko jättää toStringin ylikirjoittamatta? Vastaus on kyllä! Kolmiulotteinen piste pelkistyy seuraavanlaiseksi.
public class Piste3D extends Piste {
private int z;
public Piste3D(int x, int y, int z) {
super(x, y);
this.z = z;
}
@Override
protected String sijainti() {
return super.sijainti() + ", " + z;
}
@Override
public int manhattanEtaisyysOrigosta() {
return super.manhattanEtaisyysOrigosta() + Math.abs(z);
}
}
Mitä tarkalleenottaen tapahtuu kuin kolmiulotteiselle pisteelle kutsutaan toString-metodia? Suoritus etenee seuraavasti.
- etsitään toString:in määrittelyä luokasta Piste3D, sitä ei löydy joten mennään yliluokkaan
- etsitään toString:in määrittelyä yliluokasta Piste, metodi löytyy, joten suoritetaan sen koodi
- suoritettava koodi siis on
return "("+this.sijainti()+") etäisyys "+this.manhattanEtaisyysOrigosta();
- esimmäisenä suoritetaan metodi sijainti
- etsitään metodin sijainti määrittelyä luokasta Piste3D, metodi löytyy ja suoritetaan sen koodi
- metodin sijainti laskee oman tuloksensa kutsumalla yliluokassa olevaa metodia sijainti
- seuraavaksi etsitään metodin manhattanEtaisyysOrigosta määrittelyä luokasta Piste3D, metodi löytyy ja suoritetaan sen koodi
- jälleen metodi laskee tuloksensa kutsuen ensin yliluokassa olevaa samannimistä metodia
- suoritettava koodi siis on
Metodikutsun aikaansaama toimintoketju siis on monivaiheinen. Periaate on kuitenkin selkeä: suoritettavan metodin määrittelyä etsitään ensin olion todellisen tyypin määrittelystä ja jos sitä ei löydy edetään yliluokkaan. Ja jos yliluokastakaan ei löydy metodin toteutusta siirrytään etsimään yliluokan yliluokasta jne...
Milloin perintää kannattaa käyttää?
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.
Perittäessä aliluokka saa käyttöönsä yliluokan toiminnallisuudet. Jos aliluokka ei tarvitse tai käytä perittyä toiminnallisuutta, ei perintä ole perusteltua. Perityt luokat perivät yliluokkiensa metodit ja rajapinnat, eli aliluokkia voidaan käyttää missä tahansa missä yliluokkaa on käytetty. Perintähierarkia kannattaa pitää matalana, sillä hierarkian ylläpito ja jatkokehitys vaikeutuu perintöhierarkian kasvaessa. Yleisesti ottaen, jos perintähierarkian korkeus on yli 2 tai 3, ohjelman rakenteessa on todennäköisesti parannettavaa.
Perinnän käyttöä tulee miettiä. Esimerkiksi luokan Auto
periminen luokasta Osa
(tai Moottori
) olisi väärin. Auto sisältää moottorin ja osia, mutta auto ei ole moottori tai osa. Voimme yleisemmin ajatella että jos olio omistaa tai koostuu toisista olioista, ei perintää tule käyttää.
Esimerkki perinnän väärinkäytöstä
Pohditaan postituspalveluun liittyviä luokkia Asiakas
, joka sisältää asiakkaan tiedot, ja Tilaus
, joka perii asiakkaan tiedot ja sisältää tilattavan tavaran tiedot. Luokassa Tilaus
on myös metodi postitusOsoite
, joka kertoo tilauksen postitusosoitteen.
public class Asiakas {
private String nimi;
private String osoite;
public Asiakas(String nimi, String osoite) {
this.nimi = nimi;
this.osoite = osoite;
}
public String getNimi() {
return nimi;
}
public String getOsoite() {
return osoite;
}
public void setOsoite(String osoite) {
this.osoite = osoite;
}
}
public class Tilaus extends Asiakas {
private String tuote;
private String lukumaara;
public Tilaus(String tuote, String lukumaara, String nimi, String osoite) {
super(nimi, osoite);
this.tuote = tuote;
this.lukumaara = lukumaara;
}
public String getTuote() {
return tuote;
}
public String getLukumaara() {
return lukumaara;
}
public String postitusOsoite() {
return this.getNimi() + "\n" + this.getOsoite();
}
}
Yllä perintää on käytetty väärin. Luokkaa perittäessä aliluokan tulee olla yliluokan erikoistapaus; tilaus ei ole asiakkaan erikoistapaus. Väärinkäyttö ilmenee single responsibility principlen rikkomisena: luokalla Tilaus
on vastuu sekä asiakkaan tietojen ylläpidosta, että tilauksen tietojen ylläpidosta.
Ratkaisussa piilevä ongelma tulee esiin kun mietimme mitä käy asiakkaan osoitteen muuttuessa.
Osoitteen muuttuessa joutuisimme muuttamaan jokaista kyseiseen asiakkaaseen liittyvää tilausoliota, mikä ei missään nimessä ole toivottua. Parempi ratkaisu olisi kapseloida Asiakas
Tilaus
-luokan oliomuuttujaksi. Jos ajattelemme tarkemmin tilauksen semantiikkaa, tämä on selvää. Tilauksella on asiakas.
Muutetaan luokkaa Tilaus
siten, että se sisältää Asiakas
-viitteen.
public class Tilaus {
private Asiakas asiakas;
private String tuote;
private String lukumaara;
public Tilaus(Asiakas asiakas, String tuote, String lukumaara) {
this.asiakas = asiakas;
this.tuote = tuote;
this.lukumaara = lukumaara;
}
public String getTuote() {
return tuote;
}
public String getLukumaara() {
return lukumaara;
}
public String postitusOsoite() {
return this.asiakas.getNimi() + "\n" + this.asiakas.getOsoite();
}
}
Yllä oleva luokka Tilaus
on nyt parempi. Metodi postitusosoite
käyttää asiakas-viitettä postitusosoitteen saamiseen sen sijaan että luokka perisi luokan Asiakas
. Tämä helpottaa sekä ohjelman ylläpitoa, että sen konkreettista toiminnallisuutta.
Nyt asiakkaan muuttaessa tarvitsee muuttaa vain asiakkaan tietoja, tilauksiin ei tarvitse tehdä muutoksia.
Abstraktit luokat
Perintähierarkiaa pohtiessa tulee joskus esille tilanteita, missä on olemassa selkeä käsite, mutta käsite ei sellaisenaan ole hyvä kandidaatti olioksi. Hyötyisimme käsitteestä perinnän kannalta, sillä se sisältää muuttujia ja toiminnallisuuksia, jotka ovat kaikille käsitteen periville luokille samoja, mutta toisaalta käsitteestä itsestään ei pitäisi pystyä tekemään olioita.
Abstrakti luokka yhdistää rajapintoja ja perintää. Niistä ei voi tehdä ilmentymiä, vaan ilmentymät tehdään abstraktin luokan aliluokista. Abstrakti luokka voi sisältää sekä normaaleja metodeja, joissa on metodirunko, että abstrakteja metodeja, jotka sisältävät ainoastaan metodimäärittelyn. Abstraktien metodien toteutus jätetään perivän luokan vastuulle. Yleisesti ajatellen abstrakteja luokkia käytetään esimerkiksi kun abstraktin luokan kuvaama käsite ei ole selkeä itsenäinen käsite. Tällöin siitä ei tule pystyä tekemään ilmentymiä.
Sekä abstraktin luokan että abstraktien metodien määrittelyssä käytetään avainsanaa abstract
. Abstrakti luokka määritellään lauseella public abstract class *LuokanNimi*
, abstrakti metodi taas lauseella public abstract palautustyyppi metodinNimi
. Tarkastellaan seuraavaa abstraktia luokkaa Toiminto
, joka tarjoaa rungon toiminnoille ja niiden suorittamiselle.
public abstract class Toiminto {
private String nimi;
public Toiminto(String nimi) {
this.nimi = nimi;
}
public String getNimi() {
return this.nimi;
}
public abstract void suorita(Scanner lukija);
}
Abstrakti luokka Toiminto
toimii runkona toimintojen toteuttamiseen. Esimerkiksi pluslaskun voi toteuttaa perimällä luokka Toiminto
seuraavasti.
public class Pluslasku extends Toiminto {
public Pluslasku() {
super("Pluslasku");
}
@Override
public void suorita(Scanner lukija) {
System.out.print("Anna ensimmäinen luku: ");
int eka = Integer.valueOf(lukija.nextLine());
System.out.print("Anna toinen luku: ");
int toka = Integer.valueOf(lukija.nextLine());
System.out.println("Lukujen summa on " + (eka + toka));
}
}
Koska kaikki Toiminto
-luokan perivät luokat ovat myös tyyppiä toiminto, voimme rakentaa käyttöliittymän Toiminto
-tyyppisten muuttujien varaan. Seuraava luokka Kayttoliittyma
sisaltaa listan toimintoja ja lukijan. Toimintoja voi lisätä käyttöliittymään dynaamisesti.
public class Kayttoliittyma {
private Scanner lukija;
private ArrayList<Toiminto> toiminnot;
public Kayttoliittyma(Scanner lukija) {
this.lukija = lukija;
this.toiminnot = new ArrayList<>();
}
public void lisaaToiminto(Toiminto toiminto) {
this.toiminnot.add(toiminto);
}
public void kaynnista() {
while (true) {
tulostaToiminnot();
System.out.println("Valinta: ");
String valinta = this.lukija.nextLine();
if (valinta.equals("0")) {
break;
}
suoritaToiminto(valinta);
System.out.println();
}
}
private void tulostaToiminnot() {
System.out.println("\t0: Lopeta");
int i = 0;
while (i < this.toiminnot.size()) {
String toiminnonNimi = this.toiminnot.get(i).getNimi();
System.out.println("\t" + (i + 1) + ": " + toiminnonNimi);
i = i + 1;
}
}
private void suoritaToiminto(String valinta) {
int toiminto = Integer.valueOf(valinta);
Toiminto valittu = this.toiminnot.get(toiminto - 1);
valittu.suorita(lukija);
}
}
Käyttöliittymä toimii seuraavasti:
Kayttoliittyma kayttolittyma = new Kayttoliittyma(new Scanner(System.in));
kayttolittyma.lisaaToiminto(new Pluslasku());
kayttolittyma.kaynnista();
Toiminnot: 0: Lopeta 1: Pluslasku Valinta: 1 Anna ensimmäinen luku: 8 Anna toinen luku: 12 Lukujen summa on 20
Toiminnot: 0: Lopeta 1: Pluslasku Valinta: 0
Rajapintojen ja abstraktien luokkien suurin ero on siinä, että abstrakteissa luokissa voidaan määritellä metodien lisäksi myös oliomuuttujia sekä konstruktoreja. Koska abstrakteihin luokkiin voidaan määritellä toiminnallisuutta, voidaan niitä käyttää esimerkiksi oletustoiminnallisuuden määrittelyyn. Yllä käyttöliittymä käytti abstraktissa luokassa määriteltyä toiminnan nimen tallentamista.
Muistathan tarkistaa pistetilanteesi materiaalin oikeassa alareunassa olevasta pallosta!