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.
Aiemmissa esimerkeissä luokassa määritellyt muuttujat ovat olleet sellaisia jotka syntyvät, kun luokan ilmentymä eli olio syntyy. Tällaisilla muuttujilla on oletusalkuarvo.
Luokassa määritellyt muuttujat voivat olla ilmentymä- eli oliokohtaisia (ei-staattisia) tai kaikille ilmentymille yhteisiä eli luokkakohtaisia (staattisia).
Ohjelmoidaan ensin tuttuun tapaan:
public class Esim1 { private int i; public Esim1(int i) {this.i=i;} public void aseta(int i) {this.i = i;} public int ota() {return this.i;} } // ... Esim1 a = new Esim1(4), b = new Esim1(11), c = new Esim1(-19); System.out.println(a.ota()+" "+b.ota()+" "+c.ota()); a.aseta(5); System.out.println(a.ota()+" "+b.ota()+" "+c.ota()); b.aseta(b.ota()+21); System.out.println(a.ota()+" "+b.ota()+" "+c.ota()); c.aseta(c.ota()+31); System.out.println(a.ota()+" "+b.ota()+" "+c.ota()); /* Tulostus: 4 11 -19 5 11 -19 5 32 -19 5 32 12 */Jokaisella luokan Esim1 ilmentymällä (a, b, c) on oma versionsa kentästä i. 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).
Esimerkki:
public class Esim2 { private int i; private static int j=0; public Esim2(int i) {this.i=i;} public void aseta(int i) {this.i = i;} public int ota() {return this.i;} public void asetaJ(int i) {j = i;} public int otaJ() {return j;} } ... Esim2 a = new Esim2(4), b = new Esim2(5), c = new Esim2(-19); System.out.println(a.otaJ()+" "+b.otaJ()+" "+c.otaJ()); a.asetaJ(5); System.out.println(a.otaJ()+" "+b.otaJ()+" "+c.otaJ()); b.asetaJ(b.otaJ()+21); System.out.println(a.otaJ()+" "+b.otaJ()+" "+c.otaJ()); c.asetaJ(c.otaJ()+31); System.out.println(a.otaJ()+" "+b.otaJ()+" "+c.otaJ()); /* Tulostus: 0 0 0 5 5 5 26 26 26 57 57 57 */Tässä esimerkissä joka oliolla (a, b, c) on oma kenttä i, mutta ne jakavat kentän j.
Huom: "Luokkamuuttuja" ja "luokan muuttuja" tarkoittavat eri asioita: "luokan muuttuja" on mikä tahansa metodien ulkopuolella määritelty muuttuja, staattinen tai ei-staattinen, mikä tahansa luokan kenttä. "Luokkamuuttuja" on nimenomaan luokan staattinen 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)Se määritellään ilman static-määrettä. Ilmentymämetodi voi käyttää sekä oliomuuttujia että luokkamuuttujia ja kutsua sekä ilmentymämetodeita että luokkametodeita.
Edellisen esimerkin (Esim2) luokkamuuttujaa käyttävät metodit olisi luontevampi ohjelmoida staattisina:
public static void asetaJ(int i) {j = i;} public static int otaJ() {return j;}Jos staattisesta metodista yrittää käyttää ilmentymämuuttujaa, saa (legendaarisen!) virheilmoituksen:
Can't make a static reference to nonstatic variable ...Ja jos staattisesta metodista kutsuu ilmentymämetodia, saa ilmoituksen:
Can't make static reference to method ilmentymämetodin nimiEsimerkki 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 voitaisiin ehkä kutsua luokka-aksessoriksi. (En ole itseäni lukuunottamatta tosin kuullut kenenkään käyttävän tätä nimeä, AW 14.11.2000)
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
Luokan kenttä voidaan metodin tapaan määritellä myös julkiseksi (public). Tällöin kenttä on käytettävissä suoraan - so. ilman aksessoreita - luokan 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;Huom: Luokkamuuttujaan voi viitata myös olion kautta:
int luku = a.i + a.j;Mutta tämä ei ole kaunista. Miksei?
Huom: Muuttujia 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.
Huom: Myös metodi (ja luokka!) voi olla ilman näkyvyydensäätelymäärettä. Silloin sekin on käytettävissa luokkansa pakkauksessa.
| 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- | Lue.kluku(), Math.random() | metodit -------|------------------------------|-------------------------- private| KENTÄT: | KENTÄT: | - piilossa pidetyt luokka- | - olion piilossa pidetty | kohtaiset tietorakenteet | tietorakenne, jota käsitel- | METODIT: | lää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 KaikkiMukaan { //-- MUUTTUJIA ELI KENTTIÄ ----------------------------- int i=1; // joka ilmentymällä on oma i! static int j; // luokkamuuttuja eli staattinen kenttä: // j on yhteinen kaikille ilmentymille private int k; // piilossa pidetty kenttä static final int VAKIO=77; // vakio static int[] taulu; int[][] matriisi = {{1,2},{3,4}};
Oletusalkuarvot ovat:
Huom: Javan versiossa 1.1 myös metodien paikallisia muuttujia ja muodollisia parametreja voidaan määritellä vakioiksi!
Huom: Vakioiden nimet on tapana kirjoittaa kokonaan suurin kirjaimin.
//-- KONSTRUKTOREITA ----------------------------------- KaikkiMukaan() { i+=2; j=3+VAKIO; } KaikkiMukaan(int i) { // kuormittaminen! this(); // konstruktorin kutsu! this.i=i+2*this.i; // viittaus olion i:hin! j=3*i; }
Uuden olion luonti voi siis näkyä muille saman luokan ilmentymille luokkamuuttujien arvojen muutoksena! (Näin voidaan toteuttaa vaikkapa olioiden "sarjanumerot": Olioilla on vakiokenttä, jonka alkuarvoksi asetetaan luotuja olioita laskevan luokkamuuttujan arvo)
this(mahdolliset parametrit)(Metodit eivät voi käyttää tätä ilmausta, ne voivat kutsua konstruktoreita vain luomalla luokasta uuden ilmentymän!)
(Tällä välineellä olio mm. voi välittää itsensä parametrina! Ilmaus this on käytettävissä vain ei-staattisissa metodeissa, koska se viittaa olioon!)
//-- METODEITA ----------------------------------------- int laske(int x) { // arvon palauttava, "funktio" return i+j+x+VAKIO; } void tulosta(int x) { // "arvoton", "proseduuri" System.out.println(i+x); } static void tulostaJ() { // staattinen metodi ++j; // eli luokkametodi System.out.println(j); // ++i; ei ole luvallinen! // "Can't make a static reference to nonstatic variable" }
Jälkimmäinen tapaus ei tarkoita sitä, että joka oliolla olisi oma kopionsa metodin koodista! Se tarkoittaa, että metodia voidaan käyttää vain olioihin soveltaen.
//-- AKSESSOREITA ------------ void asetaK(int x) { // piilotetun asetus k = x; } int mitaKOn() { // piilotetun kysely return k; }
//-- STAATTINEN ALUSTUSLOHKO ----------------- static { taulu = new int[8]; for (int i=0; i<taulu.length; ++i) taulu[i] = i*i; }
//-- LOPETUSMETODI --------------------- protected void finalize() throws Throwable { super.finalize(); // ... }
//-- PÄÄOHJELMAMETODI ---------------- public static void main(String[] a) { KaikkiMukaan x = new KaikkiMukaan(); KaikkiMukaan y = new KaikkiMukaan(7); System.out.println(x.i+" "+y.i); System.out.println(x.taulu[5]+" "+ // sama taulu! y.taulu[6]+" "+ KaikkiMukaan.taulu[7]); x.matriisi[0][1] = y.matriisi[1][0]; // eri matriisi! System.out.println(x.matriisi[0][1]+" "+ y.matriisi[0][1]); System.out.println("Böö! "+x.laske(5)+" "+y.laske(6)); x.tulosta(7); y.tulosta(8); x.asetaK(89); // suojatun kentän System.out.println(x.mitaKOn()); // käyttöä operaatioin x.tulostaJ(); y.tulostaJ(); // luokkametodi! tulostaJ(); // oman luokan staattinen! // tulosta(8); //ei ole luvallinen! // "Can't make static reference to method ..." KaikkiMukaan.tulostaJ(); // KaikkiMukaan.tulosta(8); //ei ole luvallinen! // "Can't make static reference to method ..." } }
public static void main(String[] args)luokkaa voidaan käyttää sovelluksena. Pääohjelmametodin tehtävänä on luoda ja käynnistää sovelluksen toteuttava kalusto.
Kun olio tulostetaan tai muuten muunnetaan String-olioksi, muutos tehdään juuri tällä toString()-metodilla. Perustyyppien muunnokset on määritelty luontevasti. Itse ohjelmoitujen luokkien oletusmuunnos String-olioksi ei ole yleensä sovelluksessa käyttökelpoinen (luokan nimi ja teknistä tietoa luokan talletuspaikasta).
Luokkaan voi itse ohjelmoida toString()-metodin, joka korvaa metodin oletusversion. Metodin on oltava public. Esimerkki:
public class NiNu { String nimi; int numero; 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@80cb94a
Sisäluokkia käytetään selkeyttämään ohjelmaa erityisesti graafista käyttöliittymää ohjelmoitaessa. Niillä voidaan kurittomasti ohjelmoiden laatia myös erittäin vaikeaselkoisia ohjelmia!
Tällä kurssilla sisäluokkia ei ole mahdollista käsitellä
tarkemmin. Niistä ei myöskään kokeessa kysellä.
Halukkaiden tiedoksi sisäluokkatyyppejä: