Sisäkkäiset silmukat
Toistolauseen sisällä oleva käsky voi olla toinen toistolause.
Tällöin sisempi toistolause on kuin mikä tahansa käsky: yhdellä ulomman silmukan suorituskerralla sisempi toistokäsky suoritetaan kokonaan kaikkine toistoineen.
Vasta sitten, kun sisemmän toistolauseen suoritus päättyy, siirrytään ulomman toistolauseen seuraavalle kierrokselle.
Esimerkiksi
int i, j;
System.out.println("i j");
for (i = 0; i < 3; i++)
for (j = 0; j < 2; j++)
System.out.println(i + " " + j);
tulostaa
i j
0 0
0 1
1 0
1 1
2 0
2 1
Metodit
"Oikeat" ohjelmat ovat usein tuhansia tai kymmeniä tuhansia rivejä pitkiä. Jos koko ohjelma olisi kirjoitettu yhdeksi pääohjelmaksi, ohjelman ylläpitäminen olisi liian hankalaa.
Lisäksi usein ohjelmassa halutaan tehdä suunnilleen
sama asia useaan kertaan. Jos jokaista kertaa varten joudutaan kirjoittamaan oma koodinpätkä, on se
työlästä
virhealtista
ja lisäksi ohjelmaa muutettaessa muutos pitää tehdä moneen paikkaan
Tietyn asian suorittavasta ohjelmanpalasta tehdään metodi (aliohjelma), jota voidaan kutsua muualta ohjelmasta. Metodia kutsuttaessa siirrytään suorittamaan metodia. Kun metodi on suoritettu, palataan takaisin siihen kohtaan ohjelmaa, josta kutsu tapahtui.
Esimerkeissä on jo esiintynyt metodien kutsuja, esimerkiksi
System.out.println("Tulos on " + tulos);
x1 = (-b + Math.sqrt(disk)) / (2.0 * a);
Näillä metodeilla on se ero, että System.out.println ei palauta mitään arvoa, kun taas Math.sqrt palauttaa liukuluvun, jota voidaan käyttää kutsuvassa ohjelmassa.
Nämä metodit löytyvät Javan APIn pakkauksista. Seuraavassa opetellaan kirjoittamaan omia metodeja.
Ensimmäinen esimerkki
Kirjoitetaan kaksi metodia: toinen tulostaa rivin tähtejä, toinen huudahduksen. Mukana on myös pääohjelma, josta metodeja kutsutaan.
public class Huudahduksia{
private static void tulostaHuudahdus() {
System.out.println("ELÄKÖÖN! ELÄKÖÖN!");
}
private static void tulostaTahtia() {
System.out.println("************************************");
}
public static void main(String[] args) {
tulostaTahtia();
tulostaHuudahdus();
tulostaTahtia();
System.out.println(); // kaksi tyhjaa rivia
System.out.println();
tulostaTahtia();
tulostaTahtia();
tulostaHuudahdus();
tulostaTahtia();
tulostaTahtia();
}
}
Metodin muoto
Metodin määrittely on muotoa:
määreitä arvotyyppi nimi(parametrit) {
metodin koodi
}
Määreillä kerrotaan, mistä metodia voi kutsua: public tarkoittaa, että metodi on käytettävissä kaikissa luokissa, private, että metodia voi kutsua vain sen omasta luokasta.
Määreillä kerrotaan myös, onko metodi luokkakohtainen (static) vai oliokohtainen.
Arvotyyppi kertoo, minkä tyyppisen arvon metodi palauttaa. Jos arvotyyppinä on void, metodi ei palauta mitään arvoa. (Huom: kuvaruudulle tulostaminen ei ole arvon palauttamista.)
Parametrien avulla kutsuva ohjelma voi välittää tietoa metodille - esimerkiksi sen, minkä luvun neliöjuuri halutaan laskea.
Esimerkki parametrin käytöstä
Metodit tulostaHuudahdus ja tulostaTahtia toimivat joka kerran samalla tavalla. Usein halutaan metodille antaa kuitenkin lähtötietoja - esimerkiksi, minkä luvun neliöjuuri pitää laskea tai mikä merkkijono halutaan tulostaa.
Tietoa voidaan välittää parametrien avulla: Metodin sisällä parametreja käytetään kuin tavallisia muuttujia. Metodin kutsussa parametreille annetaan alkuarvot.
Seuraava metodi tulostaa sille parametrina annettuna merkkijonon halutun monta kertaa:
private static void
tulostaViesti(String viesti, int kertaa) {
int i;
for (i = 0; i < kertaa; i++)
System.out.println(viesti);
}
Metodia voidaan pääohjelmassa kutsua esimerkiksi seuraavasti:
...
tulostaViesti("VAROITUS: kone sammuu", 3);
...
Jolloin kuvaruudulle tulostuu
VAROITUS: kone sammuu
VAROITUS: kone sammuu
VAROITUS: kone sammuu
Metodin määrittelyssä käytettäviä parametreja kutsutaan muodollisiksi parametreiksi ja kutsussa olevia parametreja todellisiksi parametreiksi.
Todellisten parametrien ei tarvitse olla vakioita, vaan kutsussa voidaan käyttää myös muuttujia ja lausekkeita:
....
public static void main(String[] args) {
String tiedotus;
int lkm;
tiedotus = "Käyttökatko on jo ohi";
lkm = 5;
tulostaViesti(tiedotus, lkm-1);
....
}
tulostaa
Käyttökatko on jo ohi
Käyttökatko on jo ohi
Käyttökatko on jo ohi
Käyttökatko on jo ohi
Kun metodia kutsutaan, lasketaan ensin todellisten parametrien arvot. Nämä arvot sijoitetaan metodin parametrien alkuarvoiksi. Sitten suoritetaan metodin koodi.
Arvon palauttavat metodit
Usein metodin laskema arvo halutaan kutsuvan ohjelman käyttöön. Esimerkiksi neliöjuuren laskevasta metodista ei ole paljon iloa, jos laskettua neliöjuurta ei voi mitenkään käyttää kutsuvassa ohjelmassa.
Metodin paikalliset muuttujat ovat käytettävissä vain metodin suorituksen ajan. Niihin ei pääse käsiksi metodin suorituksen loputtua.
Myöskään parametreille metodissa tehdyt muutokset eivät näy kutsuvassa ohjelmassa, jos parametrit ovat yksinkertaista tyyppiä (char, int, double, boolean). Oliot toimivat tässä suhteessa toisella tavalla.
Metodi voi kuitenkin palauttaa arvon, jota voi käyttää kutsuvassa ohjelmassa.
Palautettavan arvon tyyppi kerrotaan metodin otsikossa. Aliohjelma voi palauttaa arvon return-lauseella:
return luku;
palauttaa kutsuvalle ohjelmalle muuttujan luku arvon.
Return-lauseen suoritus lopettaa metodin suorituksen.
Huomaa, että arvon tulostaminen kuvaruudulle ei ole sama kuin arvon palauttaminen.
Esimerkki: kuution laskeminen
Ohjelma korottaa metodin avulla käyttäjän antamia lukuja kolmanteen potenssiin.
public class KuutioEsim {
private static double kolmanteen(double luku) {
double kuutio;
kuutio = luku * luku * luku;
return kuutio;
}
public static void main(String[] args) {
double luku1, kuutio1;
System.out.println("Anna luku:");
luku1 = Lue.dluku();
kuutio1 = kolmanteen(luku1);
System.out.println("kolmanteen: " + kuutio1);
System.out.println("Anna toinen luku:");
luku1 = Lue.dluku();
kuutio1 = kolmanteen(luku1);
System.out.println("kolmanteen: " + kuutio1);
}
}
Esimerkki: kahden luvun minimi
Metodi palauttaa pienemmän sille parametrina annetusta kahdesta luvusta. (Jos luvut ovat yhtäsuuret, ei ole väliä, kumpi palautetaan).
public class Minimi {
private static double min(double eka, double toka) {
if (eka < toka)
return eka;
else
return toka;
}
public static void main(String[] args) {
double luku1, luku2, minimi;
System.out.println("lukujen 3.0 ja 7.0 minimi on "+
min(3.0, 7.0));
System.out.println("Anna kaksi lukua:");
luku1 = Lue.dluku();
luku2 = Lue.dluku();
minimi = min(luku1, luku2);
System.out.println("lukujen minimi on " + minimi);
}
}
Esimerkki: kolmen luvun keskiarvo
Metodi palauttaa sille parametrina annettujen lukujen keskiarvon
private static double keskiArvo( int eka,
int toka,
int kolmas){
double karvo;
karvo = (eka + toka + kolmas) / 3.0;
return karvo;
}
Muuttujaa karvo ei välttämättä tarvita, yhtä hyvin voidaan kirjoittaa
private static double keskiArvo( int eka,
int toka,
int kolmas){
return (eka + toka + kolmas) / 3.0;
}
Metodia voidaan kutsua pääohjelmasta
tulos = keskiArvo(14, 18, 19);
tai
tulos = keskiArvo(luku1, luku2, luku3);
tai
System.out.println(keskiArvo( luku1, luku2, luku3));
jos muuttujille luku1, luku2 ja luku3 on annettu arvot.
Esimerkki: kahden pisteen välinen etäisyys
Ohjelma pyytää käyttäjältä kaksi (x,y)-koordinaatiston pistettä ja laskee niiden etäisyyden. Etäisyyden laskemiseen käytetään Pythagoraan lausetta.
Koska etäisyyden laskemisessa tarvitaan kahdesti neliöön korotusta, kirjoitetaan sitä varten oma metodi.
public class EtaisyysEsim {
private static double etaisyys(int x1, int y1, int x2, int y2) {
double eta;
eta = Math.sqrt(nelio(x1-x2) + nelio(y1-y2));
return eta;
}
private static double nelio(int luku) {
return luku * luku;
}
public static void main(String[] args) {
int ekaX, ekaY, tokaX, tokaY;
double valimatka;
System.out.println("Anna ensimmäisen pisteen koordinaatit");
ekaX = Lue.kluku();
ekaY = Lue.kluku();
System.out.println("Anna toiseen pisteen koordinaatit");
tokaX = Lue.kluku();
tokaY = Lue.kluku();
valimatka = etaisyys(ekaX, ekaY, tokaX, tokaY);
System.out.println("Pisteiden etaisyys on " + valimatka);
}
}
Siistimmin pisteitä voidaan käsitellä olioiden avulla.
Esimerkki: potenssiin korotus
public class Potenssi3{
private static double pot(double kanta, int exp) {
int i;
double tulos;
tulos = 1.0;
for (i=1; i<=exp; i++)
tulos = tulos * kanta;
return tulos;
}
public static void main(String[] args) {
int ekspo;
double kantaluku;
System.out.println("Anna kantaluku ja eksponentti");
kantaluku = Lue.dluku();
ekspo = Lue.kluku();
if (ekspo <= 0)
System.out.println("Liian pieni eksponentti");
else
System.out.println(kantaluku + " potenssiin " + ekspo +
" on " + pot(kantaluku, ekspo));
}
}
Esimerkki: tehokkaampi potenssiin korotus
public class Potenssi4{
private static double pot2(double kanta, int exp) {
double tulos;
tulos = 1.0;
while (exp > 0)
if (exp % 2 == 0) {
kanta = kanta * kanta;
exp = exp / 2;
}
else {
tulos = tulos * kanta;
exp--;
}
return tulos;
}
public static void main(String[] args) {
int ekspo;
double kantaluku;
System.out.println("Anna kantaluku ja eksponentti");
kantaluku = Lue.dluku();
ekspo = Lue.kluku();
if (ekspo <= 0)
System.out.println("Liian pieni eksponentti");
else
System.out.println(kantaluku + " potenssiin " + ekspo +
" on " + pot2(kantaluku, ekspo));
}
}
Jos metodin parametrit eivät ole olioita, niin niihin metodissa tehdyt muutokset eivät näy kutsuvassa ohjelmassa:
class Vaihto {
private static void vaihda(int eka, int toka) {
int apu;
System.out.println("Muuttujat metodissa ennen vaihtoa: "+eka+" "+toka);
apu = eka; // vaihda ekan ja tokan arvot
eka = toka;
toka = apu;
System.out.println("Muuttujat metodissa vaihdon jälkeen: "+eka+" "+toka);
}
public static void main(String[] args) {
int luku1, luku2;
luku1 = 5;
luku2 = 10;
System.out.println("Muuttujat pääohjelmassa aluksi: "+luku1+" "+luku2);
vaihda(luku1, luku2);
System.out.println("Muuttujat pääohjelmassa lopuksi: "+luku1+" "+luku2);
}
}
Esimerkki: opintolainan kertyminen
Ohjelma pyytää käyttäjältä erikseen eri vuosina nostettavan lainaerän ja korkoprosentin.
Oletetaan, että laina nostetaan yhtenä eränä vuoden alussa. Korko liitetään pääomaan vuoden lopussa.
Korkoprosentti voi vaihdella vuosittain, mutta se pysyy samana koko vuoden.
Ohjelma laskee kertyneen lainapääoman opiskeluajan lopussa.
Laskeminen tehdään silmukassa, jossa lasketaan uusi pääoma kunkin vuoden lopussa.
Uusi pääoma = vanha pääoma + vuoden alussa nostettu erä + vuoden aikana kertynyt korko
Laskeminen lopetetaan, kun käyttäjä antaa negatiivisen koron.
public class Opintolaina {
public static void main(String[] args) {
double nostoera, paaoma = 0, korko;
int i;
System.out.println("Opintolainan laskuautomaatti. Lopeta antamalla "
+ "negatiivinen korko.");
System.out.println("Anna 1. vuonna nostettava erä");
nostoera = Lue.dluku();
System.out.println("Anna 1. vuoden korko");
korko = Lue.dluku();
i = 1;
while (korko >= 0) {
paaoma = laskePaaoma(paaoma, nostoera, korko);
i++;
System.out.println("Anna " + i + ". vuonna nostettava erä");
nostoera = Lue.dluku();
System.out.println("Anna " + i + ". vuoden korko");
korko = Lue.dluku();
}
System.out.println("Kertynyt lainasumma opintojen lopussa " +
paaoma + " mk");
}
private static double laskePaaoma(double lainasumma,
double era,
double korkopros) {
lainasumma = lainasumma + era + (lainasumma + era) * korkopros/100;
return lainasumma;
}
}
Asuntolainan kustannukset
Tehdään ohjelma, joka laskee asuntolainan kokonaiskustannukset koko laina-ajalta. Ohjelma antaa kustannukset kahdelle vaihtoehtoiselle maksutavalle:
annuiteettilainassa maksettava kuukausierä (lyhennys + korko) pysyy koko ajan samana
tasalyhenteisessä lainassa lyhennys on joka kuukausi sama, mutta korkoerä vaihtelee jäljellä olevan pääoman mukaan.
Molemmissa vaihtoehdoissa lainaa lyhennetään joka kuukausi. Korkoprosentin oletetaan pysyvän samana koko laina-ajan.
Annuiteettilainan kuukausierä saadaan kaavalla era = (r**n * p * A) / (1200 * (r**n - 1))
missä
p on korko prosenteissa
n on takaisinmaksukuukausien määrä
A on alkuperäinen pääoma
r = 1 + p/1200
Tasalyhenteisessä lainassa korkoerä lasketaan joka kuukausi erikseen
public class Asuntolaina {
private static double annuiteettiKustannus(double paaoma,
double korkopros,
int lainavuodet) {
double rPotn, kustannus, kkEra;
int lainakk;
lainakk = 12 * lainavuodet;
rPotn = Math.pow(1 + korkopros/1200, lainakk);
kkEra = rPotn * korkopros * paaoma / (1200 * (rPotn -1));
kustannus = lainakk * kkEra;
return kustannus;
}
private static double tasalyhKustannus(double paaoma2,
double korkopros2,
int lainavuodet2) {
double kustannus = 0.0, kkLyhennys, loppuera, korkoera;
int lainakk, i;
lainakk = 12*lainavuodet2;
kkLyhennys = paaoma2 / lainakk;
loppuera = paaoma2;
for (i = 1; i <= lainakk; i++) {
korkoera = loppuera * korkopros2 / 1200;
kustannus = kustannus + kkLyhennys + korkoera;
loppuera = loppuera - kkLyhennys;
}
return kustannus;
}
public static void main(String[] args) {
double lainasumma, korko, kustannus1, kustannus2;
int lainaAika;
System.out.println("Anna lainan maara, korkoprosentti ja "+
"takaisinmaksuaika");
lainasumma = Lue.dluku();
korko = Lue.dluku();
lainaAika = Lue.kluku();
kustannus1 = annuiteettiKustannus(lainasumma, korko, lainaAika);
kustannus2 = tasalyhKustannus(lainasumma, korko, lainaAika);
System.out.println("Annuiteettilainan kokonaiskustannus on " +
kustannus1 + "mk ja tasalyhenteisen lainan " +
kustannus2 + "mk koko laina-ajalta");
}
}
Kuormittaminen
Metodin kutsussa on oltava sama lukumäärä parametreja kuin metodin määrittelyssä. Parametrien on myös oltava oikeaa tyyppiä.
Usein samantapaisia asioita halutaan tehdä erityyppisille arvoille, esimerkiksi sekä merkkijonoille että kokonaisluvuille.
Java-ohjelmissa voidaan määritellä useita metodeja, joilla on sama nimi, mutta erityyppisiä (ja mahdollisesti eri lukumäärä) parametreja. Tätä kutsutaan metodin kuormittamiseksi.
Metodia kutsuttaessa kääntäjä päättelee todellisten parametrien tyypeistä ja lukumäärästä, mitä samannimisistä metodeista käytetään.
Esimerkki: kirjoitetaan metodi tuplaa, jota voi käyttää sekä kokonaislukujen, liukulukujen että merkkijonojen tuplaamiseen.
Jos metodi saa parametrikseen luvun, se kertoo sen arvon kahdella. Jos parametrina on merkkijono, palautetaan sama merkkijono kaksi kertaa peräkkäin. Metodia voi kutsua myös ilman parametreja: silloin se tulostaa "Tuplaa tuplasti!".
public class Tuplaus {
private static void tuplaa() {
System.out.println("Tuplaa tuplasti!");
}
private static int tuplaa(int kluku) {
return 2 * kluku;
}
private static double tuplaa(double dluku) {
return 2 * dluku;
}
private static String tuplaa(String mjono) {
return mjono + mjono;
}
public static void main(String[] args) {
tuplaa();
System.out.println("3.5 tuplattuna: " + tuplaa(3.5));
System.out.println("10 tuplattuna: " + tuplaa(10));
System.out.println("\"Ohjelma\" tuplattuna: " + tuplaa("Ohjelma"));
}
}
Tyyliasioita
Ohjelman aiheuttamista kustannuksista itse ohjelmointi on vain pieni osa. Usein valtaosa kustannuksista aiheutuu ohjelman ylläpitämisestä.
Usein vanhojen ohjelmien osia käytetään apuna uusia kirjoitettaessa.
Jotta ylläpito ja uudelleenkäyttö olisi helppoa, ohjelman on oltava selkeä.
Tärkeintä on, että ohjelma on jaettu järkevästi luokkiin ja metodeihin. Mutta myös hyvä ohjelmointityyli on tärkeää.
Ohjelmointityyliin kuuluu esimerkiksi kommentit, käytetyt tunnukset (nimet) ja ohjelman ulkoasu.
Kommenteista
Jokaisen ohjelmatiedoston alkuun on syytä kirjoittaa yleiskommentti:
mistä ohjelmasta tai ohjelman osasta on kysymys
ohjelman kirjoittaja
koska ohjelmaa on viimeksi muutettu
Jokaisen luokan alkuun on syytä kirjoittaa yleiskommentti luokan tarkoituksesta.
Jokaisesta metodista kannattaa kirjoittaa kommentti metodin tarkoituksesta.
Metodin sisällä kommentoi suurempia kokonaisuuksia ja erikoisia ratkaisuja, älä itsestäänselvyyksiä.
Ei: i++; // kasvatetaan i:n arvoa yhdellä
Älä kommentoi liikaa: jos joka toisella rivillä on kommentti, ei olennaisia kommentteja erota epäolennaisista.
Kommentoi jo silloin, kun kirjoitat ohjelmaa, älä vasta sitten, kun ohjelma on valmis.
Tunnuksista (nimistä)
Valitse mahdollisimman kuvaavat nimet.
Yksikirjaimisten nimien käyttö on järkevää vain erikoistapauksissa (silmukkalaskuri, sama tunnus kuin vastaavassa matemaattisessa kaavassa).
Noudata Javan vakiintuneita käytäntöjä:
muuttujien nimet pienillä kirjaimilla luku
luokkien nimet isolla alkukirjaimella Asuntolaina
vakiot kokonaan isoilla kirjaimilla PI
sanavälit erotetaan isoilla kirjaimella autojenLukumaara
Rivinvaihdoista ja sisennyksistä
Jaa eri käskyt omille riveilleen ja käytä tyhjiä rivejä selkeyttämään ohjelmaa
ei
luku1 = 5; luku2 = 6; luku3 = luku1 + luku2;
vaan
luku1 = 5;
luku2 = 6;
luku3 = luku1 + luku2;
Sisennä eri rakenteet niin, että ne erottuvat selvästi omiksi kokonaisuuksikseen.
public class Kertoma2 {
public static void main(String[] args) {
int i, luku, kertoma;
System.out.println("Anna luku: ");
luku = Lue.kluku();
if (luku < 0 || luku > 12)
System.out.println("Ohjelma ei voi laskea kertomaa");
else {
kertoma = 1;
for (i = 1; i <= luku; i++)
kertoma = kertoma * i;
System.out.println("Kertoma on " + kertoma);
}
}
}
Toinen vaihtoehto on kirjoittaa aukeava aaltosulku omalle rivilleen
public class Kertoma2
{
public static void main(String[] args)
{
int i, luku, kertoma;
System.out.println("Anna luku: ");
luku = Lue.kluku();
if (luku < 0 || luku > 12)
System.out.println("Ohjelma ei voi laskea kertomaa");
else
{
kertoma = 1;
for (i = 1; i <= luku; i++)
kertoma = kertoma * i;
System.out.println("Kertoma on " + kertoma);
}
}
}
On makuasia, kumpaa tapaa käyttää, mutta on syytä käyttää systemaattisesti samaa tapaa.