(Staattiset) metodit
Olemme käyttäneet ohjelmointikurssilla aikaisempien aiheiden yhteydessä lukuisia valmiita metodeja. Metodit ovat olleet luonteva osa ongelmanratkaisua, vaikka emme ole toistaiseksi kiinnittäneet niihin suurta huomiota tai toteuttaneet omia metodeja main
-metodia lukuun ottamatta.
Tällä kertaa perehdymme tarkemmin omien staattisten metodien toteuttamiseen ja kutsumiseen sekä siihen, miten voimme välittää arvoja metodista toiseen ja takaisin. Emme vielä harjoittele olio-ohjelmoinnin käytäntöjä, joten palaamme asiaan oliometodien osalta myöhemmin olio-ohjelmoinnin yhteydessä.
Syitä oman ohjelman jakamiseksi useisiin metodeihin on lukuisia. Ensinnäkin metodien avulla voidaan vähentää toisteisuutta, jos samoja operaatioita tehdään useita kertoja tai useissa eri kohdissa koodia. Toiseksi metodien avulla voidaan vähentää kompleksisuutta, eli pilkkoa iso monimutkainen kokonaisuus pienemmiksi, helpommin ymmärrettäviksi paloiksi. Myöhemmin kurssilla olio-ohjelmoinnin yhteydessä metodien merkitys kasvaa entisestään, kun metodeista tulee oliokohtaisia.
Tämä oppimateriaali pohjautuu Helsingin yliopiston Agile Education Research -tutkimusryhmän MOOC-ohjelmointikurssin materiaaliin, jota suositellaan myös tälle ohjelmointikurssille. Lisenssi: Creative Commons BY-NC-SA 4.0.
Sisällysluettelo
- Video: Metodien käsitteet, kutsuminen ja määrittely 50:53
- Metodien käsitteet ja metodikutsut
- Ohjelman suorituksen eteneminen metodikutsuissa
- Metodin määrittely
- Metodin parametrit
- Video: Parametrien välittäminen, ohjelman pilkkominen ja metodikutsut toisista luokista 39:01
- Metodin paluuarvot ja return-käsky
- Metodien näkyvyys, eli mistä metodia voidaan kutsua
- Toisessa luokassa määriteltyjen metodien kutsuminen
- Minkälainen on hyvä metodi?
- Tehtäväideoita tunnille
Video: Metodien käsitteet, kutsuminen ja määrittely 50:53
Videolla esiintyvät lähdekoodit:
- MetodienSuoritusJarjestys.java
- AjankohdanTulostaminen.java
- ParametrienValittaminen.java
- HinnanValintaHTML.java
Metodien käsitteet ja metodikutsut
Olemme tähän mennessä hyödyntäneet Javan standardikirjaston staattisia metodeja mm. Integer
, Double
, Math
, Arrays
ja Collections
-luokista. Metodikutsuissa on esiintynyt tyypillisesti luokan nimi, metodin nimi ja sulut, joiden sisälle on määritelty metodille annettava data. Olemme puolestaan hyödyntäneet metodien palauttamia arvoja sijoittamalla niitä esimerkiksi uusiin muuttujiin. Käsittelemme seuraavissa kappaleissa näitä käsitteitä ja niihin liittyviä esimerkkejä.
Parametriarvot
Metodin kutsumisessa olemme hyödyntäneet parametriarvoja, joiden avulla olemme välittäneet tietoa omasta koodistamme Javan standardikirjastossa toteutetuille koodiriveille:
int pienempi = Math.min(42, 24);
int numero = Integer.parseInt("42");
Toiset metodit eivät tarvitse lainkaan ulkopuolisia arvoja toimiakseen. Näiden metodien kutsussa ei välitetä arvoja:
LocalDate tanaan = LocalDate.now();
Vaikka metodille ei välitettäisi arvoja, kirjoitetaan silti metodikutsuun tyhjät sulut.
Paluuarvot
Vastaavasti olemme vastaanottaneet paluuarvoja, eli dataa, jonka metodi palauttaa sen suorituksen päätyttyä:
int paluuarvo = Integer.parseInt("42");
Yllä olevissa koodiesimerkeissä Javan Math
-luokassa sijaitseva min
on metodi, jolle välitetään kaksi parametriarvoa, ja joka palauttaa suorituksensa jälkeen takaisin paluuarvon.
Kaikki metodit eivät välttämättä tarvitse parametriarvoja tai palauta paluuarvoja. Esimerkiksi System.out.println
ei palauta paluuarvoa, vaan se pelkästään tulostaa saamansa parametriarvon konsoliin:
System.out.println("println-metodi ei palauta arvoa");
Metodit, jotka eivät palauta arvoja, merkitään myöhemmin void-avainsanalla.
Ohjelman suorituksen eteneminen metodikutsuissa
Ohjelman suorituksen edetessä metodikutsuun, siirtyy suoritus suoritettavalta riviltä toisaalle. Kesken jäänyt metodi ja kaikki siinä paikallisesti olevat muuttujat jävät edelleen muistiin niin sanottuun kutsupinoon:
“Java-lähdekoodin suoritusympäristö pitää kirjaa suoritettavasta metodista kutsupinossa. Kutsupino sisältää kehyksiä, joista jokainen sisältää tiedon kyseisen metodin sisäisistä muuttujista sekä niiden arvoista. Kun metodia kutsutaan, kutsupinoon luodaan uusi kehys, joka sisältää metodin sisältämät muuttujat. Kun metodin suoritus loppuu, metodiin liittyvä kehys poistetaan kutsupinosta, jolloin suoritusta jatketaan kutsupinon edeltävästä metodista.”
“Kutsupino tarkoittaa metodien kutsumisen muodostamaa pinoa — juuri suoritettevana oleva metodi on aina pinon päällimmäisenä, ja metodin suorituksen päättyessä palataan pinossa seuraavana olevaan metodiin.”
Agile Education Research, 2019. Ohjelman pilkkominen osiin: metodit. CC BY-NC-SA 4.0
Metodit kutsuvat hyvin usein yhtä tai useampaa muuta metodia, joten kutsupino koostuu tyypillisesti lukuisista keskeneräisistä “kehyksistä”.
Metodin määrittely
Olemme kurssilla tähän asti määritelleet lukuisia kertoja main-metodin:
public static void main(String[] args) {
}
Metodin otsikko koostuu tässä tapauksessa seuraavista avainsanoista:
public
– julkinen metodi, jota voidaan kutsua mistä vainstatic
– staattinen eli luokkametodi, joka ei kuulu millekään yksittäiselle oliollevoid
– metodi ei palauta mitään arvoamain
– metodin nimi, main-nimisellä metodilla on erityinen rooli ohjelman käynnistyksessäString[] args
– yksi parametrimuuttuja: merkkijonotaulukko, jonka nimi onargs
Metodin otsikon jälkeen kirjoitetaan aina aaltosulut { }
, joiden sisään kirjoitetaan metodin runko, kuten olemme aikaisemmissa harjoituksissa tehneet.
static
-avainsana liittyy käyttämäämme staattiseen ohjelmointityyliin, jossa emme vielä mallinna ohjelmaa olioiden avulla. Toistaiseksi kaikki kirjoittamamme metodit kirjoitetaan staattisiksi, eli luokkametodeiksi, mutta käsittelemme tätä tarkemmin siirtyessämme myöhemmillä viikoilla olio-ohjelmointiin.
void
-avainsana tarkoittaa, että metodi ei palauta mitään arvoa. Tällaista metodia kutsuttaessa sen tulosta ei voida esim. asettaa muuttujaan tai muilla tavoin hyödyntää.
Sulkujen sisällä esiintyvä String[] args
on metodin parametrimuuttuja, johon palaamme alempana tässä materiaalissa.
Muut omat metodit
Kuten main
-metodi, myös muut metodit kirjoitetaan luokan sisään, eli lähdekooditiedoston uloimpien aaltosulkujen väliin. Metodeja ei voida määritellä sisäkkäin, eli kaikki metodit ovat luokan sisällä samalla tasolla peräkkäin. Metodien keskenäisellä järjestyksellä ei ole Javan kannalta merkitystä.
Yksinkertaisimmillaan metodi ei tarvitse lainkaan parametriarvoja eikä se palauta paluuarvoa. Tällöin siis sen otsikossa sulut ovat tyhjät ()
ja paluuarvon tyyppinä on void
(tyhjä). Metodin nimen voit itse valita, kunhan se noudattaa Javan nimeämissääntöjä. Main-metodia mukaillen voisimme siis kirjoittaa seuraavan oman metodin:
public static void tulostaKellonaika() {
LocalTime kellonaika = LocalTime.now();
System.out.println("Kello on " + kellonaika);
}
Yllä oleva metodi ei ota lainkaan parametriarvoja tai palauta uutta arvoa, mutta se voisi olla silti hyödyllinen ohjelmassa, jossa on tarpeen tulostaa kellonaikaa eri kohdissa.
Tätä metodia kutsuttaessa saman luokan sisällä kirjoitetaan yksinkertaisesti metodin nimi tulostaKellonaika
ja tyhjät sulut:
tulostaKellonaika();
Vastaavasti voisimme kirjoittaa esimerkiksi tulostaPaivamaara
-metodin ja kutsua näitä molempia main
-metodista:
import java.time.LocalDate;
import java.time.LocalTime;
public class Metodit {
public static void main(String[] args) {
tulostaPaivamaara();
tulostaKellonaika();
}
public static void tulostaPaivamaara() {
LocalDate tanaan = LocalDate.now();
System.out.println("Päivämäärä on " + tanaan);
}
public static void tulostaKellonaika() {
LocalTime kellonaika = LocalTime.now();
System.out.println("Kello on " + kellonaika);
}
}
Kumpikaan edellä määritellyistä omista metodeista ei vastaanota parametriarvoja eikä palauta parametriarvoja, joten niiden vuorovaikutus main-metodin kanssa on vielä vähäistä.
Voit tutustua metodien suoritusjärjestykseen Java Visualizer -visualisoinnin avustuksella:
Metodien nimeäminen ja sisennykset
“Metodit nimetään siten, että ensimmäinen sana kirjoitetaan pienellä ja loput alkavat isolla alkukirjaimella, tälläisestä kirjoitustavasta käytetään nimitystä camelCase. Tämän lisäksi, metodin sisällä koodi on sisennetty taas neljä merkkiä.”
Agile Education Research, 2019. Ohjelman pilkkominen osiin: metodit. CC BY-NC-SA 4.0
// OK:
public static void tulostaPaivamaara() {
LocalDate tanaan = LocalDate.now();
System.out.println("Päivämäärä on " + tanaan);
}
// ei ok:
public static void Tulosta_paivamaara() {
LocalDate tanaan = LocalDate.now();
System.out.println("Päivämäärä on " + tanaan);
}
Yhtenäinen nimeämistyyli helpottaa sekä koodin ymmärtämistä että kirjoittamista. Käytännöt metodien nimeämiselle ja sisentämiselle vaihtelevat eri ohjelmointikielten välillä, mutta jokaisessa kielessä on omat parhaat käytäntönsä.
Metodin parametrit
“Metodille suluissa annettua syötettä kutsutaan metodin parametriksi — metodin parametreilla annetaan metodeille tarkempaa tietoa odotetusta suorituksesta; esimerkiksi tulostuslauseelle kerrotaan parametrin avulla mitä pitäisi tulostaa.”
Agile Education Research, 2019. Ohjelman pilkkominen osiin: metodit. CC BY-NC-SA 4.0
Metodin sisällä sille annetut parametriarvot ovat käytettävissä otsikon sulkujen sisälle määriteltyjen parametrimuuttujien avulla. Parametrimuuttujat ovat käytännössä kuin mitkä tahansa metodin paikalliset muuttujat, mutta niihin asetetaan arvot automaattisesti metodikutsun yhteydessä:
tulostaOtsikko("Ohjelmointi 1 - Metodit"); // teksti välitetään metodille parametrina
public static void tulostaOtsikko(String otsikko) {
System.out.println("<h1>" + otsikko + "</h1>");
}
Yllä olevan metodin kutsun on luonnollisesti oltava jonkun toisen metodin sisällä. Seuraavassa pienessä luokassa main
-metodista kutsutaan tulostaOtsikko
-metodia:
public class Parametrimuuttuja {
public static void main(String[] args) {
String nimi = "Ohjelmointi 1 - Metodit";
tulostaOtsikko(nimi);
}
public static void tulostaOtsikko(String otsikko) {
System.out.println("<h1>" + otsikko + "</h1>");
}
}
Huomaa, että sekä main
-metodin sisällä määritelty nimi
että tulostaOtsikko
-metodissa määritelty otsikko
ovat paikallisia muuttujia, eivätkä ne näy metodien ulkopuolelle. Metodikutsussa käytetään siis esimerkissä eri nimistä muuttujaa kuin metodin otsikossa. Muuttujien nimillä ei ole lainkaan merkitystä, koska metodikutsussa välitetään ainoastaan arvo, eli itse merkkijono.
Video: Parametrien välittäminen, ohjelman pilkkominen ja metodikutsut toisista luokista 39:01
Videolla hyödynnetään Java Visualizer -palvelua lähdekoodin suorituksen havainnollistamiseksi.
Videolla esiintyvät lähdekoodit:
Metodin paluuarvot ja return-käsky
“Metodin määrittelyssä kerrotaan palauttaako metodi arvon. Jos metodi palauttaa arvon, tulee metodimäärittelyn yhteydessä kertoa palautettavan arvon tyyppi. Muulloin määrittelyssä käytetään avainsanaa void. Tähän mennessä tekemämme metodit ovat määritelty avainsanaa void käyttäen eli eivät ole palauttaneet arvoa.”
“Konkreettinen arvon palautus tapahtuu komennolla
return
, jota seuraa palautettava arvo (tai muuttujan tai lauseke, jonka arvo palautetaan).”Agile Education Research, 2019. Ohjelman pilkkominen osiin: metodit. CC BY-NC-SA 4.0
Seuraava metodi palauttaa aina saman merkkijonon, joka sisältää aakkosten kirjaimet yhtenä merkkijonona:
public static String annaPienetKirjaimet() {
return "abcdefghijklmnopqrstuvwxyzåäö";
}
Pienet kirjaimet saadaan nyt pyydettyä tältä metodilta metodikutsun avulla. Metodin palauttama merkkijono voidaan ottaa talteen esimerkiksi uuteen muuttujaan:
String pienet = annaPienetKirjaimet();
Metodit kutsuvat hyvin usein toisiaan. Jos haluaisimme toteuttaa lisäksi annaIsotKirjaimet
-metodin, meidän kannattaisi hyvin todennäköisesti hyödyntää yllä esiteltyä metodia tämän uuden metodin sisällä:
public static String annaIsotKirjaimet() {
String pienet = annaPienetKirjaimet();
return pienet.toUpperCase();
}
Muutaman merkkijonoja palauttavan apumetodin sekä niitä kutsuvan annaSatunnainenMerkki
-metodin avulla voisimme toteuttaa esimerkiksi satunnaisen salasanan generoivan ohjelmaluokan:
import java.util.Random;
// Huom! Tämä esimerkki on suorituskykynsä puolesta heikko
public class Salasanat {
public static void main(String[] args) {
String salasana = generoiSalasana();
System.out.println("Satunnainen salasanasi on " + salasana);
}
public static String generoiSalasana() {
String salasana = "";
while (salasana.length() < 60) {
salasana += annaSatunnainenMerkki();
}
return salasana;
}
public static String annaSatunnainenMerkki() {
Random random = new Random();
String merkit = annaPienetKirjaimet() + annaIsotKirjaimet() + annaNumerot() + annaErikoismerkit();
int indeksi = random.nextInt(merkit.length());
return merkit.substring(indeksi, indeksi + 1);
}
public static String annaErikoismerkit() {
return "<>,;.:-!\"#¤$%&/\\()[]";
}
public static String annaNumerot() {
return "0123456789";
}
public static String annaIsotKirjaimet() {
String pienet = annaPienetKirjaimet();
return pienet.toUpperCase();
}
public static String annaPienetKirjaimet() {
return "abcdefghijklmnopqrstuvwxyzåäö";
}
}
Yllä olevassa esimerkissä kaikki omat metodit palauttavat merkkijonoja, mutta millään niistä ei ole parametriarvoja. Metodit on toteutettu hyvin yksinkertaisiksi, mutta suorituskykynäkökulmasta tässä ratkaisussa olisi vielä kehitettävää.
Jos metodin paluuarvoksi on määritetty jotain muuta kuin void
, sen on aina pakko palauttaa arvo.
Parametrimuuttujat ja paikalliset muuttujat
“Metodille voidaan määritellä useita parametreja. Tällöin metodin kutsussa parametrit annetaan samassa järjestyksessä.”
Agile Education Research, 2019. Ohjelman pilkkominen osiin: metodit. CC BY-NC-SA 4.0
Alla olevassa esimerkkimetodissa on kaksi double
-tyyppistä parametrimuuttujaa:
/** Laskee alennuksen, kun halvempi tuote myydään puoleen hintaan. */
public static double laskeAlennus(double hinta1, double hinta2) {
double halvempi = Math.min(hinta1, hinta2);
return halvempi * 0.5;
}
Tämän metodin kutsussa tulee antaa metodin parametrimuuttujien mukaisesti aina kaksi liukulukua, esim. laskeAlennus(a, b)
:
public static void main(String[] args) {
double a = 123.4;
double b = 234.5;
double alennus = laskeAlennus(a, b);
double loppusumma = a + b - alennus;
System.out.println("Hinta on yhteensä " + loppusumma);
}
Huomaa, että samoja liukulukuja käsitellään sekä main
-metodissa että laskeAlennus
-metodissa. Niillä kuitenkin eri metodeissa eri muuttujanimet (a
, b
, hinta1
ja hinta2
). Paikallisilla muuttujien nimillä ei ole mitään merkitystä metodin ulkopuolella.
Metodien näkyvyys, eli mistä metodia voidaan kutsua
Tämän materiaalin esimerkeissä kaikki metodit on määritelty julkisiksi, eli näkyvyydellä public
. Näkyvyys on tapana määritellä aina mahdollisimman yksityiseksi, eli oikeassa ohjelmassa olisimme määritelleet suurimman osan metodeista yksityiseksi (private
). Julkisia metodeita voidaan kutsua mistä vain muista luokista, kun taas muiden näkyvyyksien kohdalla kutsuminen onnistuu vain rajoitetuista luokista:
Näkyvyys | Selitys |
---|---|
public | Metodi on käytettävissä kaikkialta |
private | Metodi on käytettävissä ainoastaan saman luokan sisältä |
protected | Metodi on käytettävissä saman luokan ja paketin sisältä, sekä aliluokista |
(tyhjä) | Käytettävissä saman luokan ja paketin sisältä. Melko harvoin käytetty. |
Seuraavat neljä metodia on määritelty kukin eri näkyvyydellä:
public static String julkinen() {
return "käytettävissä missä tahansa";
}
private static String yksityinen() {
return "käytettävissä vain tästä luokasta";
}
protected static String suojattu() {
return "käytettävissä mm. aliluokista";
}
static String oletusnakyvyys() {
return "en suosittele tällaista näkyvyyttä";
}
Toisessa luokassa määriteltyjen metodien kutsuminen
Samassa luokassa määritellyn metodin kutsuminen oli yllä helppoa: kirjoitetaan vain metodin nimi, sulut ja tarvittaessa parametriarvot. Toisessa luokassa olevaa staattista metodia kutsutaan luokan nimen avulla:
Metodit.tulostaPaivamaara();
Metodit.tulostaKellonaika();
Sama paluuarvon kanssa:
String satunnainenSalasana = Salasanat.generoiSalasana();
Omia metodejasi kutsutaan siis aivan samalla tavalla kuin Javan valmiita metodeja:
int pienin = Math.min(12, 15);
Huom! Mikäli kutsuttavan metodin luokka sijaitsee eri paketissa kuin kutsuva luokka, joudut lisäämään tiedoston alkuun myös import
-komennon. Ylempänä oppimateriaalissa käsitelty metodin näkyvyys vaikuttaa siihen, mistä muista luokista kyseistä metodia voidaan kutsua.
Minkälainen on hyvä metodi?
Nyt kun osaamme kutsua ja määritellä omia metodeja, on aiheellista pohtia myös metodien laatunäkökulmaa. Pääsääntöisesti kunkin metodin tulisi tehdä vain yksi asia, ja tämän asian tulisi käydä selvästi ilmi metodin nimestä. Java-ohjelmointiin liittyen on monia hyviä käytäntöjä joista monet on julkaistu mm. kirjassa “Clean Code: A Handbook of Agile Software Craftsmanship” (Martin, R). Kirjan oppeja käsitellään tiivistetysti esimerkiksi artikkelissa “Clean Coding in Java” (baeldung.com).
Tehtäväideoita tunnille
Suksien ja sauvojen pituuslaskuri
Perinteisen hiihtotyylin sauvojen ohjepituus on 85 % hiihtäjän pituudesta. Mitta-asteikossa sauvat ilmoitetaan 5 cm pituusvälein.
Suksien suosituspituus on 40 cm enemmän kuin sauvojen pituuden.
Kirjoitetaan ohjelma, joka suosittelee käyttäjän pituuden mukaan sauvoja ja suksia siten, että niiden pituus on laskettu edellä mainituilla kaavoilla ja pyöristetty lähimpään 5 cm jaolliseen pituuteen.
Kovalevyn puuttuvat megatavut
“From all the hard drives I have bought, they never seem to be as large as the advertised size; from 320 GB down to 290 GB, from 500 GB down to 450 GB, etc. Is there a technical reason for this?”
Sam152. Why are hard drives never as large as advertised? https://superuser.com/q/504
Kirjoitetaan metodi, joka laskee kovalevyn koon muutettuna GB- järjestelmästä GiB-järjestelmään.
Tämä oppimateriaali pohjautuu Helsingin yliopiston Agile Education Research -tutkimusryhmän MOOC-ohjelmointikurssin materiaaliin.
Materiaalin on muokannut Haaga-Helian ohjelmointikurssin mukaiseksi Teemu Havulinna.
Lisenssi: Creative Commons BY-NC-SA 4.0.