Luokka on Java-ohjelmoinnin peruskäsite: Jokainen olio on jonkin luokan ilmentymä, kaikki algoritmit ja muuttujat sijaitsevat jossakin luokassa. Itse asiassa lähes kaikki Javan lauseet ja määrittelyt ihan konkreettisestikin kirjoitetaan jonkin luokan sisään!
Luokka on viittaustyypin (reference type) määrittely, luokan ilmentymä on luokan määrittelemää viittaustyyppiä oleva olio.
Tähän mennessä nähdyt esimerkit muuttujista metodien ulkopuolella ovat olleet oliokohtaisia ilmentymämuuttujia, "piirustuksia muuttujista", "ei-staticceja" muuttujia. Näin määritelty muuttuja syntyy, kun luokasta luodaan ilmentymä eli olio. Jokainen luotava olio saa oman versionsa tällaisesta muuttujasta. Ilmentymämuuttujilla - kuten myös nyt opittavilla luokkamuuttujilla - on muuttujan tyypin mukainen oletusalkuarvo (0, 0.0, false, null).
Luokassa voidaan metodien ulkopuolella määritellä oliokohtaisten ilmentymämuuttujien lisäksi myös luokkakohtaisia luokkamuuttujia eli "static-muuttujia". Tällainen muuttuja on olemassa, vaikka luokasta ei luotaisi ensimmäistäkään ilmentymää. Ja jos ilmentymiä luodaan, ne jakavat tällaisen muuttujan keskenään.
Esimerkki:
public class Esim { private int ilme; // ilmentymämuuttuja private static int luok; // luokkamuuttuja public Esim(int a, int b) { ilme = a; luok = b; } // setterit: public void asetaIlm(int a) {ilme = a;} public void asetaLuo(int a) {luok = a;} public String toString() { return "ilme = " + ilme +", luok= " + luok; } // ... Käyttöesimerkki pikku pääohjelmalla: public static void main(String[] args) { Esim x = new Esim(1, 2); System.out.println("X: " + x); // tulostus: X: ilme = 1, luok= 2 Esim y = new Esim(3, 4); System.out.println("Y: " + y); System.out.println("X: " + x); // tulostus: Y: ilme = 3, luok= 4 // X: ilme = 1, luok= 4 !! y.asetaIlm(8); y.asetaLuo(9); System.out.println("Y: " + y); System.out.println("X: " + x); // tulostus: Y: ilme = 8, luok= 9 // X: ilme = 1, luok= 9 !! } }Molemmilla luokan Esim ilmentymillä (x, y) on oma versionsa kentästä ilme. Tämä kenttä on siis oliokohtainen eli ilmentymämuuttuja (instance variable) eli ei-staattinen muuttuja.
Kun luokan kenttä määritellään määreella static, kenttä liittyy luokkaan - ei erikseen kuhunkin olioon - ja kaikki luokan ilmentymät jakavat tuon saman luokkakohtaisen eli staattisen kentän, luokkamuuttujan (class variable). Esimerkissä oliot (x, y) siis jakavat kentän luok.
Huom: "Luokkamuuttuja" ja "luokan muuttuja" tarkoittavat eri asioita: "luokan muuttuja" voi tarkoittaa mitä tahansa luokassa (siis ei metodin sisällä!) määriteltyä muuttujaa. "Luokkamuuttujalla" tarkoitetaan nimenomaan ja vain luokan staattista kenttää!
Esimerkki luokkamuuttujan käytöstä: Jonotuskone
Toteutetaan jonotusnumeroita antaville olioille luokka. Laitteita voi olla siis useita, mutta ne jakavat saman "numerovaraston". Jokaisessa laitteessa on lisäksi laskuri, joka laskee laitteen käyttökertojen määrän.
public class Jonotuskone { private static int yhteinenJuoksevaNumero = 0; // aina "edellinen" private int käyttökertoja; // ilmentymämuuttuja public Jonotuskone() { käyttökertoja = 0; } public int annaNumero() { ++käyttökertoja; // ilmentymämuuttujan kasvatus ++yhteinenJuoksevaNumero; // luokkamuuttujan kasvatus return yhteinenJuoksevaNumero; } public int käyttöLkm() { return käyttökertoja; } }Testataan:
public class TestaaJonotuskone { public static void main(String[] args) { Jonotuskone a = new Jonotuskone(), b = new Jonotuskone(); System.out.println("a:sta saadaan " + a.annaNumero() + ", a:lla käyttäjiä " + a.käyttöLkm()); System.out.println("b:stä saadaan " + b.annaNumero() + ", b:llä käyttäjiä " + b.käyttöLkm()); System.out.println("Otetaan vaiteliaina b:stä 100 numeroa."); for (int i=0; i<100; ++i) b.annaNumero(); // lausekelause lauseena System.out.println("a:sta saadaan " + a.annaNumero() + ", a:lla käyttäjiä " + a.käyttöLkm()); System.out.println("b:stä saadaan " + b.annaNumero() + ", b:llä käyttäjiä " + b.käyttöLkm()); } }Ohjelma tulostaa:
a:sta saadaan 1, a:lla käyttäjiä 1 b:stä saadaan 2, b:llä käyttäjiä 1 Otetaan vaiteliaina b:stä 100 numeroa. a:sta saadaan 103, a:lla käyttäjiä 2 b:stä saadaan 104, b:llä käyttäjiä 102Huom: Todellisuudessa asia on vähän mutkikkaampi: jos useita jonotuskoneita toimisi rinnakkain, pitäisi jotenkin pitää huoli siitä, että vain yksi kone kerrallaan pääsee käyttämään jaettua muuttujaa. Javassa on tähän välineitä, mutta niitä ei käsitellä tällä kurssilla.
Luokka.metodi(parametrit)Luokkametodi voi käyttää vain luokkamuuttujia ja kutsua vain toisia luokkametodeita!
olio.metodi(parametrit)Ilmentymämetodi siis määritellään ilman static-määrettä. Ilmentymämetodi voi käyttää sekä ilmentymämuuttujia että luokkamuuttujia ja kutsua sekä ilmentymämetodeita että luokkametodeita.
Yllä nähdyn Esim-esimerkkiluokan luokkamuuttujan arvon asettava metodi olisi tyylikkäämpää ohjelmoida staattisena:
public static void asetaLuo(int a) { luok = a; }Jos staattisesta metodista yrittää käyttää ilmentymämuuttujaa, saa (legendaarisen!) virheilmoituksen:
non-static variable XXXXXX cannot be referenced from a static contextJa jos staattisesta metodista kutsuu ilmentymämetodia, saa ilmoituksen:
non-static method XXXXXX cannot be referenced from a static contextEsimerkki luokkametodin käytöstä: laajennettu Jonotuskone
Jatketaan äskeistä Jonotuskone-esimerkkiä. Toisinaan, esim. aamuisin, jonotusnumerot halutaan aloittaa uudelleen ykkösestä. Täydennetään luokkaa luokkametodilla nollaaJonotus():
public class Jonotuskone { private static int yhteinenJuoksevaNumero = 0; // aina "edellinen" public static void nollaaJonotus() { yhteinenJuoksevaNumero = 0; } private int käyttökertoja; // ilmentymämuuttuja public Jonotuskone() { käyttökertoja = 0; } public int annaNumero() { ++käyttökertoja ; // ilmentymämuuttujan kasvatus ++yhteinenJuoksevaNumero; // luokkamuuttujan kasvatus return yhteinenJuoksevaNumero; } public int käyttöLkm() { return käyttökertoja; } }Tuollaista luokkametodia voidaan kutsua luokka-aksessoriksi.
Käyttöesimerkki:
public class TestaaJonotuskone { public static void main(String[] args) { Jonotuskone a = new Jonotuskone(), b = new Jonotuskone(); System.out.println("a:sta saadaan " + a.annaNumero() + ", a:lla käyttäjiä " + a.käyttöLkm()); System.out.println("b:stä saadaan " + b.annaNumero() + ", b:llä käyttäjiä " + b.käyttöLkm()); System.out.println("Uusi aamu!"); Jonotuskone.nollaaJonotus(); System.out.println("a:sta saadaan " + a.annaNumero() + ", a:lla käyttäjiä " + a.käyttöLkm()); System.out.println("b:stä saadaan " + b.annaNumero() + ", b:llä käyttäjiä " + b.käyttöLkm()); } }Ohjelma tulostaa:
a:sta saadaan 1, a:lla käyttäjiä 1 b:stä saadaan 2, b:llä käyttäjiä 1 Uusi aamu! a:sta saadaan 1, a:lla käyttäjiä 2 b:stä saadaan 2, b:llä käyttäjiä 2
Luokassa esiteltyjä ilmentymämuuttujia ja luokkamuuttujia voidaan -- metodien tapaan -- määritellä myös julkisiksi (public). Tuollaiset kentät ovat käytettävissä suoraan - so. ilman aksessoreita - luokkamäärittelyn ulkopuolelta.
Esimerkikiksi, kun määritellään:
public class Kenttia { public int i; public static int j=56; public Kenttia() {this.i=1;} // ... }voidaan ohjelmoida vaikkapa:
Kenttia a = new Kenttia(); int luku = a.i + Kenttia.j; a.i = Kenttia.j; // jne...Huom: Luokkamuuttujaan voi viitata myös olion kautta
int luku = a.i + a.j;koska "jokaisella oliolla on luokka". Tämä ei kuitenkaan ole kaunista. Miksi ei?
Huom: Ilmentymä- ja luokkamuuttujia voidaan määritellä myös ilman näkyvyydensäätelymäärettä:
public class Kenttia2 { int i; static int j=56; }Tällöin kentät ovat käytettävissä siinä pakkauksessa, johon luokka itse kuuluu. Oletusarvoisesti luokka kuuluu ns. nimettömään pakkaukseen. Se tarkoittaa yleensä kaikkia luokkia, jotka ovat samassa hakemistossa. Myös metodit, konstruktorit ja luokatkin voi määritellä ilman näkyvyydensäätelymäärettä. Myös ne ovat tällöin käytettävissa vain pakkauksessa, jossa ne itse sijaitsevat. Pakkauksista lisää luvussa 4.6.
Muutamissa tilanteissa Java-ohjelmoinnissa luokkamuuttujat ovat silti käyttökelpoisia:
public class Otus { private static int otuslaskuri = 0; // laskee luotujen olioiden määrää private final int hetu; // järjestysnumero (vakio!) private String nimi; public Otus(String nimi) { ++otuslaskuri; // luotujen otusten määrä kasvaa aina kun otus luodaan hetu = otuslaskuri; // Java-vakioon saa sijoittaa kerran, mutta vain kerran! this.nimi = nimi; } // muuta kalustoa .... }
import java.util.Scanner; public class Perinne { static Scanner lukija = new Scanner(System.in); static int eka, toka; // "globaalit muuttujat" static void lueluvut() { eka = lukija.nextInt(); toka = lukija.nextInt(); } static int laskeyhteen() { return eka + toka; } public static void main(String[] args) { lueluvut(); int summa = laskeyhteen(); System.out.println(summa); } }Huh, huh! Tuohon tyyliin ennen vanhaan ohjelmoitiin... Nykyään voisi ajatella, että jos joutuu muokkaamaan jostakin vanhasta C- tai Pascal-ohjelmasta välttämättä jonkinlaisen Java-ohjelman - laadusta välittämättä - tämä tyyli voisi olla vaivattomin.
| static | ei-static -------|------------------------------|------------------------- public | KENTÄT: | KENTÄT: | - julkiset kirjastovakiot, | - "tietueen" kentät | esim Math.PI | METODIT: | METODIT: | - aksessorit ja muut | - pääohjelmametodi | piilossa pidetyn tieto- | - kirjastometodit, esim. | rakenteen käsittely- | Math.random() | metodit | -"luokka-aksessorit" | -------|------------------------------|-------------------------- private| KENTÄT: | KENTÄT: | - piilossa pidetyt luokka- | - olion piilossa pidetty | kohtaiset tietorakenteet | tietorakenne, jota käsi- | METODIT: | tellään aksessorein | - pääohjelman "pikku apu- | METODIT: | laiset" | - aksessoreiden "pikku | - kirjastometodien "pikku | apulaiset" | apulaiset" | -------|------------------------------|---------------------------
Muitakin käyttötapoja toki on. Ja näkyvyysmääreitä on muitakin kuin nuo public ja private.
Luokassa voi olla seuraavia rakenneosia:
public class Luokka { //-- muuttujia eli kenttiä ----------------------------- private int i=1; // joka ilmentymällä on oma i private static int j; // luokkamuuttuja eli staattinen kenttä: // j on yhteinen kaikille ilmentymille private int k; // yksityinen ilmentymämuuttuja public static int m; // jukinen luokkamuuttuja int n; // pakkausnäkyvyys protected int o; // pakkaus- ja aliluokkanäkyvyys public static final int VAKIO=77; // jukinen luokkavakio private static int[] taulu; // yksityinen luokkataulukko public int[][] matriisi = {{1,2},{3,4}}; // julkinen ilmentymämatriisi
//-- konstruktoreita ----------------------------------- public Luokka() { i+=2; j=3+VAKIO; } public Luokka(int i) { // kuormittaminen! this(); // konstruktorin kutsu! this.i=i+2*this.i; // viittaus olion i:hin! j=3*i; }
//-- metodeita ----------------------------------------- public int laske(int x) { // arvon palauttava ilmentymämetodi, return i+j+x+VAKIO; // "funktio" } private void tulosta(int x) { // "arvoton" metodi, "proseduuri" System.out.println(i+x); } static void tulostaJ() { // staattinen metodi eli luokkametodi ++j; // pakkausnäkyvyydellä System.out.println(j); // ++i; ei olisi luvallinen: // "non-static variable i cannot be referenced from a static context" }
//-- aksessoreita ------------ public void asetaK(int x) { // piilotetun asetus, k = x; // asettava aksessori } public int mitaKOn() { // piilotetun kysely, return k; // ottava aksessori }
//-- staattinen alustuslohko ----------------- static { taulu = new int[8]; for (int i=0; i<taulu.length; ++i) taulu[i] = i*i; }
//-- pääohjelmametodi ---------------- public static void main(String[] args) { Luokka olioA = new Luokka(); Luokka olioB = new Luokka(7); System.out.println(olioA.i+" "+olioB.i); System.out.println(olioA.taulu[5]+" "+ olioB.taulu[6]+" "+ Luokka.taulu[7]); olioA.matriisi[0][1] = olioB.matriisi[1][0]; System.out.println(olioA.matriisi[0][1]+" "+ olioB.matriisi[0][1]); System.out.println("Böö! "+olioA.laske(5)+" "+olioB.laske(6)); olioA.tulosta(7); olioB.tulosta(8); olioA.asetaK(89); System.out.println(olioA.mitaKOn()); olioA.tulostaJ(); olioB.tulostaJ(); tulostaJ(); // oman luokan staattinen! // tulosta(8); ei olisi luvallinen! // "non-static method tulosta(int) cannot be referenced from a // static context" Luokka.tulostaJ(); // Luokka.tulosta(8); ei myöskään olisi luvallinen! } }
Kun olio tulostetaan tai muuten muunnetaan String-olioksi, muunnos tehdään juuri tällä toString()-metodilla.
Omiin luokkiinkin on järkevää ohjelmoida toString()-metodi, joka korvaa metodin oletusversion. Esimerkki:
public class NiNu { private String nimi; private int numero; public NiNu(String ni, int nu) { nimi = ni; numero = nu; } public String toString() { return nimi + " no. " + numero; } }Kun ohjelmoidaan:
NiNu a = new NiNu("Kissa", 9); System.out.println(a);tulostuu:
Kissa no. 9Ilman omaa toString()-metodia lauseet tulostavat esim.:
NiNu@1f6a7b9
Sisäluokkia käytetään erityisesti graafisia käyttöliittymiä ohjelmoitaessa.
Tällä kurssilla sisäluokkia ei käsitellä tarkemmin. Niistä ei myöskään kokeessa kysellä.
Halukkaiden tiedoksi sisäluokkatyyppejä: