Sisällysluettelo

Tehtävät

Kysely: Kuvaavat adjektiivit ja persoonallisuus

Tutkimus: Kuvaavat adjektiivit ja persoonallisuus

Käytämme tietoja kurssiemme kehittämiseen ja opetuksen tutkimukseen.

Seuraavassa luetellaan joukko adjektiiveja, joilla ihmisillä on tapana kuvata itseään. Käy läpi adjektiivit ja kerro, miten hyvin ne kuvaavat Sinua. Jos olet sitä mieltä, että adjektiivi pätee sinuun erittäin hyvin, valitse 7; jos olet sitä mieltä, että adjektiivi ei päde sinuun lainkaan, valitse 1. Jos adjektiivi on enemmän tai vähemmän totta kohdallasi, valitse sinua parhaiten kuvaava arvo lukujen 1 ja 7 väliltä.

 

Adjektiivi ei kuvaa minua lainkaan.
1
2
3
4
5
6
7
Adjektiivi kuvaa minua erittäin hyvin.

 

Kysely: Minäpystyvyys

Tutkimus: Opiskeluun liittyvät tahtotilat ja strategiat

Käytämme tietoja kurssiemme kehittämiseen ja opetuksen tutkimukseen.

Tällä kyselyllä kartoitetaan erilaisissa tilanteissa selviytymistä. Jos olet sitä mieltä, että väite pätee sinuun erittäin hyvin, valitse 7; jos olet sitä mieltä, että väite ei päde sinuun lainkaan, valitse 1. Jos väite on enemmän tai vähemmän totta kohdallasi, valitse sinua parhaiten kuvaava arvo lukujen 1 ja 7 väliltä.

 

Väite ei päde minuun lainkaan.
1
2
3
4
5
6
7
Väite pätee minuun erittäin hyvin.

 

22 ArrayList ja HashMap

Kerrataan ensin edellisellä viikolla tutuksi tullutta ArrayListia, jonka jälkeen tutustutaan toisenlaiseen tapaan tallentaa tietoa.

22.1 ArrayList

ArrayList on listarakenne, johon voidaan tallentaa lähes rajaton määrä arvoja. Uusi ArrayList-olio luodaan seuraavalla komennolla:

ArrayList<Tyyppi> lista = new ArrayList<>();

Listalle annetaan tyyppiparametri Tyyppi, joka kertoo minkätyyppisiä arvoja listalle tullaan tallentamaan. Tyyppiparametreja ovat esimerkiksi String, Integer (vastaa int-tyyppistä muuttujaa) sekä Double (vastaa double-tyyppistä muuttujaa). Yllä lista on muuttujan nimi, jolla juuri luotuun listaan viitataan ohjelmakoodissa.

ArrayList voi sisältää esimerkiksi lukuja. Alla olevalla ohjelmakoodilla luodaan uusi ArrayList-olio, ja lisätään siihen lukuja.

ArrayList<Integer> luvut = new ArrayList<>();
luvut.add(6);
luvut.add(1);
luvut.add(2);
luvut.add(4);
luvut.add(4);
luvut.add(3);

Metodilla add lisättävät luvut asetetaan aina listan loppuun, ensimmäiseen vapaaseen kohtaan. Listan kohtien numerointi ("indeksointi") alkaa aina nollasta, eli ensimmäinen arvo on kohdassa 0, toinen arvo kohdassa 1, kolmas arvo kohdassa 2 ja niin edelleen.

Jos ylläolevaan listaan lisättäisiin uusi alkio kutsumalla luvut-listan metodia add parametrilla 8, menisi luku 8 listan indeksiin 6 eli seitsemänneksi luvuksi.

Lista toimii vastaavasti merkkijonoilla

Luodaan lista nimet, johon lisätään kuusi nimeä. Tämän jälkeen niistä tulostetaan kolme ensimmäistä.

ArrayList<String> nimet = new ArrayList<>();
nimet.add("Ada-Maaria");
nimet.add("Antti");
nimet.add("Joonas");
nimet.add("Lalli");
nimet.add("Miika");
nimet.add("Otto-Ville");

System.out.println("Nimiä yhteensä: " + nimet.size());

System.out.println("Kohdassa 0: " + nimet.get(0));
System.out.println("Kohdassa 1: " + nimet.get(1));
System.out.println("Kohdassa 2: " + nimet.get(2));

Ohjelman tulostus on seuraava:

Nimiä yhteensä: 6
Kohdassa 0: Ada-Maaria
Kohdassa 1: Antti
Kohdassa 2: Joonas

Listan läpikäynti onnistuu sekä while-toistolauseen avulla että for-each -toistolauseen avulla.

ArrayList<String> nimet = new ArrayList<>();
nimet.add("Sonja");
nimet.add("Terho");
nimet.add("Tiia");
nimet.add("Timo");
nimet.add("Ville-Veikko");

// while-toistolla tapahtuva listan alkioiden tulostaminen
int indeksi = 0;
while (indeksi < nimet.size()) {
    System.out.println(nimet.get(indeksi));
    indeksi++;
}

// for-each toistolla tapahtuva listan alkioiden tulostaminen
for (String nimi: nimet) {
    System.out.println(nimi);
}

Lyhyt yhteenveto ArrayListin komennoista:

  • ArrayList<Tyyppi> lista = new ArrayList<>() - luo uuden lista-nimisen ArrayList-listan, johon tallennetaan Tyyppi-tyyppiparametrin tyyppisiä arvoja.
  • lista.add(Tyyppi alkio) - lisää alkion listalle.
  • lista.get(int indeksi) - palauttaa alkion listan kohdasta indeksi.
  • lista.size() - palauttaa alkion listalla olevien alkioiden lukumäärän.

Tehtävä 79: Desibelimittaukset

Kerrataan listojen käyttöä ja toteutetaan kurssin kolmannen viikon alussa nähty tehtävä "Desibelimittaukset" listojen ja metodien avulla. Kun tämän tehtävän aliosat on toteutettu, seuraava pääohjelma lukee luvut listalle, valitsee niistä vain osan, ja tulostaa lukujen keskiarvon tai ilmoittaa ettei lukuja ole.

Scanner lukija = new Scanner(System.in);
ArrayList<Integer> lista = new ArrayList<>();

lueLuvut(lukija, lista);
lista = valitseLuvutValilta(lista, 0, Integer.MAX_VALUE);

if (lista.isEmpty()) {
    System.out.println("Ei lukuja.");
} else {
    System.out.println("Lukujen keskiarvo: " + keskiarvo(lista));
}

79.1 Lukujen lukeminen listalle

Toteuta metodi public static void lueLuvut(Scanner lukija, ArrayList<Integer> lista), joka käyttää sille parametrina annettua Scanner-lukijaa lukujen lukemiseen, ja tallentaa luetut luvut listalle. Lukeminen tulee lopettaa kun käyttäjä syöttää tyhjän merkkijonon, eli painaa enter.

Merkkijonon lukeminen ja sen muuntaminen kokonaisluvuksi onnistuu osissa seuraavasti:

String luettu = lukija.nextLine();
int luettuLuku = Integer.parseInt(luettu);

Kun olet toteuttanut metodin public static void lueLuvut(Scanner lukija, ArrayList<Integer> lista), toimii ohjelma seuraavasti:

Scanner lukija = new Scanner(System.in);
ArrayList<Integer> lista = new ArrayList<>();

lueLuvut(lukija, lista);

System.out.println();

for (int luku: lista) {
    System.out.println(luku);
}
Syötä lukuja, tyhjä syöte lopettaa.
-41
22
97

-41
22
97

79.2 Lukujen valinta

Toteuta metodi public static ArrayList<Integer> valitseLuvutValilta(ArrayList<Integer> luvut, int pienin, int suurin), joka valitsee parametrina annetusta listasta ne luvut, jotka ovat suurempia tai yhtäsuuria kuin pienin ja pienempiä tai yhtäsuuria kuin suurin, ja palauttaa ne metodista uudessa ArrayList-listarakenteessa.

Kun olet toteuttanut metodin, voit kokeilla sen toimintaa seuraavalla ohjelmakoodilla:

ArrayList<Integer> lista = new ArrayList<>();
lista.add(10);
lista.add(20);
lista.add(30);
lista.add(42);

ArrayList<Integer> rajattu = valitseLuvutValilta(lista, 20, 50);

System.out.println("Lukuja alkuperäisellä listalla: " + lista.size());

for (int luku: rajattu) {
    System.out.println(luku);
}

Ylläolevan ohjelmakoodin tulostuksen pitäisi olla seuraava:

Lukuja alkuperäisellä listalla: 4
20
30
42

79.3 Keskiarvo

Toteuta metodi public static double keskiarvo(ArrayList<Integer> luvut), joka laskee sille annetussa listassa olevien lukujen keskiarvon ja palauttaa sen. Voit olettaa, että metodille parametrina annetulla listalla on aina vähintään yksi luku.

Kun olet toteuttanut metodin, voit kokeilla sen toimintaa seuraavalla ohjelmakoodilla:

ArrayList<Integer> lista = new ArrayList<>();
lista.add(10);
lista.add(15);
lista.add(20);
lista.add(25);
lista.add(30);

System.out.println("Lukujen keskiarvo on " + keskiarvo(lista));

Ylläolevan ohjelmakoodin tulostuksen pitäisi olla seuraava:

Lukujen keskiarvo on 20.0

Tehtävä 80: Vieraslista

Toteutetaan seuraavaksi vieraslistan ylläpitämiseen käytettävä ohjelma. Ohjelman toiminta kokonaisuudessaan on seuraavanlainen:

Syötä nimiä vieraslistalle, tyhjä rivi lopettaa.
Jack Bauer
Jack Bower
Jack Baur
Jack Bowr

Syötä nimiä, tyhjä rivi lopettaa.
Chuck Norris
Nimi ei ole listalla.
Jack Baluer
Nimi ei ole listalla.
Jack Bauer
Nimi on listalla.
Jack Bower
Nimi on listalla.

Kiitos!

Pääohjelmametodi tulee kokonaisuudessaan olemaan seuraavanlainen:

Scanner lukija = new Scanner(System.in);
ArrayList<String> lista = new ArrayList<>();

lueNimet(lukija, lista);
System.out.println("");
tarkistaNimet(lukija, lista);
System.out.println("Kiitos!");

Toteutetaan metodit lueNimet ja tarkistaNimet.

80.1 Nimien lukeminen listalle

Toteuta metodi public static void lueNimet(Scanner lukija, ArrayList<String> lista), joka lukee käyttäjän kirjoittamat syötteet metodille parametrina annetulle listalle. Syötteiden lukeminen lopetetaan kun käyttäjä syöttää tyhjän rivin. Kun metodia kutsutaan pääohjelmasta, tulee tulostuksen olla esimerkiksi seuraavanlainen.

Syötä nimiä vieraslistalle, tyhjä rivi lopettaa.
Jack Bauer
Jack Bower

Kiitos!

Esimerkki kutsusta:

public static void main(String[] args) {
    Scanner lukija = new Scanner(System.in);
    ArrayList<String> lista = new ArrayList<>();

    lueNimet(lukija, lista);
}

// metodi ja sen toteutus

80.2 Nimien tarkistaminen

Toteuta metodi public static void tarkistaNimet(Scanner lukija, ArrayList<String> lista), joka kysyy käyttäjiltä nimiä, ja tarkistaa löytyykö nimiä patametrina saadulla listalla. Kun käyttäjä syöttää tyhjän merkkijonon (eli painaa vain enteriä), nimien tarkastus lopetetaan ja metodista poistutaan.

Kun parametrina annetulla listalla on nimi Coolness, metodin kutsu toimii esimerkiksi seuraavasti:

Syötä nimiä, tyhjä rivi lopettaa.
Chuck Norris
Nimi ei ole listalla.
Jack Bauer
Nimi ei ole listalla.
Coolness
Nimi on listalla.
Testi
Nimi ei ole listalla.

Jos et muista miten alkion listalla olemassaoloa voi tarkistaa helposti, katso edellisen viikon materiaalista vinkkejä.

Kun olet valmis, kokeile vielä että ohjelma toimii halutusti tehtävänannon alussa olevalla koodilla.

ArrayList on erittäin hyödyllinen kun halutaan tallentaa muuttujien arvoja myöhempää käsittelyä varten. Siinä on kuitenkin pieni rajoite. Tiedon yhdistäminen toiseen tietoon, esimerkiksi puhelinnumeroihin liittyvien nimien hakeminen, sanakirjan sanakäännösparien etsiminen tai käsitteiden ilmentymien lukumäärien laskeminen on ArrayListin kanssa -- vaikkakin mahdollista -- on hieman haastavaa. Tutustutaan seuraavaksi HashMap-nimiseen apuvälineeseen, joka on varsin näppärä väline tietoparien tallentamiseen.

22.2 HashMap

HashMap on apuväline tietoparien tallentamiseen. Sitä käytetään silloin kun halutaan tallentaa avain-arvo -pareja, kuten esimerkiksi viivakoodeihin liittyviä tuotteita tai henkilöihin liittyviä saldotilanteita. HashMap-olio luodaan seuraavasti:

HashMap<Tyyppi, Tyyppi> arvoparit = new HashMap<>();

Yllä tyyppiparametrit kuvataan Tyyppi-muuttujien avulla. Tyyppiparametreilla määritellään minkätyyppisiä avaimia ja minkätyyppisiä arvoja luotavaan HashMapiin tallennetaan. Kuten ArrayListissä, tyyppiparametreja ovat esimerkiksi Integer, String ja Double.

Alla olevassa esimerkissä on luotu HashMap-olio kaupunkien hakemiseen postinumeron perusteella, jonka jälkeen HashMap-olioon on lisätty neljä postinumero-kaupunki -paria. Kuten huomaat, parien lisääminen tapahtuu put(Tyyppi avain, Tyyppi arvo)-metodin avulla.

HashMap<String, String> postinumerot = new HashMap<>();
postinumerot.put("00710", "Helsinki");
postinumerot.put("90014", "Oulu");
postinumerot.put("33720", "Tampere");
postinumerot.put("33014", "Tampere");

Toisin kuin listoilla, HashMapissa ei ole varsinaisesti käsitettä "indeksi", vaan arvoja haetaan avaimen perusteella. Ylläolevassa esimerkissä avaimeksi on määritelty postinumero (merkkijono). Voimme siis hakea arvoa merkkijonon perusteella -- HashMapin metodia get(Tyyppi avain) käytetään arvon hakemiseen avaimen perusteella.

HashMap<String, String> postinumerot = new HashMap<>();
postinumerot.put("00710", "Helsinki");
postinumerot.put("90014", "Oulu");
postinumerot.put("33720", "Tampere");
postinumerot.put("33014", "Tampere");

System.out.println(postinumerot.get("33014")); // tulostaa "Tampere"

Jos avainta ei löydy, palauttaa get-metodi arvon null.

HashMap<String, String> postinumerot = new HashMap<>();
postinumerot.put("00710", "Helsinki");
postinumerot.put("90014", "Oulu");
postinumerot.put("33720", "Tampere");
postinumerot.put("33014", "Tampere");

System.out.println(postinumerot.get("00550")); // tulostaa null

HashMap toimii vastaavasti myös muilla muuttujatyypeillä

Seuraavassa esimerkissä luodaan sanakirjaohjelma. Sanakirjaohjelmalle määritellään ensin ne sanaparit, jotka se tuntee, jonka jälkeen ohjelma kyselee käyttäjältä sanoja:

// luodaan HashMap-olio, johon sanat tallennetaan
HashMap<String, String> sanakirja = new HashMap<>();
// lisätään sanapareja
sanakirja.put("mänty", "pine");
sanakirja.put("omena", "apple");
sanakirja.put("ananas", "pineapple");

Scanner lukija = new Scanner(System.in);

while(true) {
    System.out.print("Syötä haettava sana > ");
    String syote = lukija.nextLine();
    if (syote.isEmpty()) {
        break;
    }

    if (sanakirja.containsKey(syote)) {
        System.out.println("Sanan " + syote + " käännös on " + sanakirja.get(syote));
    } else {
        System.out.println("Sanaa " + syote + " ei löydy sanakirjasta.");
    }
}

System.out.println("Kiitos!");

Ohjelma voi toimia esimerkiksi seuraavasti:

Syötä haettava sana > norja
Sanaa norja ei löydy sanakirjasta
Syötä haettava sana > omena
Sanan omena käännös on apple
Syötä haettava sana > ananas
Sanan ananas kännös on pineapple
Syötä haettava sana > pineapple
Sanaa pineapple ei löydy sanakirjasta

HashMap tallentaa tietopareja siten, että avain -- put-metodille ensin annettava muuttuja -- on se, jonka perusteella arvoja -- put-metodille annettava toinen muuttuja -- voi hakea. Yllä olevassa esimerkissä pineapple on tallennettu arvoksi, joten sitä ei löydy avaimena.

Lyhyt yhteenveto HashMapin komennoista:

  • HashMap<Tyyppi1, Tyyppi2> tietoparit = new HashMap<>() - luo uuden tietoparit-nimisen HashMapin, johon tallennetaan Tyyppi2-tyyppiparametrin tyyppisiä arvoja Tyyppi1-tyyppiparametrin tyyppisillä avaimilla.
  • tietoparit.put(Tyyppi1 avain, Tyyppi2 arvo) - lisää tietoparin avain, arvo HashMapiin.
  • tietoparit.get(Tyyppi1 avain) - palauttaa avaimella löytyvät arvon. Jos arvoa ei löydy, palautetaan null.
  • tietoparit.containsKey(Tyyppi1 avain) - palauttaa totuusarvoisen muuttujan (boolean), joka kertoo onko HashMapissa haettua avainta.

Tehtävä 81: Numeropalvelu

Toteuta edellistä sanakirjaesimerkkiä mukaillen ohjelma, joka ensin lukee ja tallentaa käyttäjän syöttämiä puhelinnumeroita ja nimiä. Lukeminen lopetetaan kun käyttäjä syöttää tyhjän merkkijonon. Tämän jälkeen käynnistetään numeropalvelu, joka kertoo soittajan nimen käyttäjän syöttämän puhelinnumeron perusteella. Jos puhelinnumeron omistaja on tiedossa, kerrotaan se, muuten kerrotaan että soittaja on tuntematon. Numeropalvelu sammutetaan kun käyttäjä syöttää tyhjän merkkijonon.

Ohjelman tulee esimerkiksi toimia seuraavasti:

Syötä puhelinnumero, tyhjä lopettaa: 123-12345
Syötä nimi: Andrew

Syötä puhelinnumero, tyhjä lopettaa: 867-5309
Syötä nimi: Jenny

Syötä puhelinnumero, tyhjä lopettaa: 

Kiitos!

* Numeropalvelu *

Mikä numero tarkistetaan? 
> 012-12345
Tuntematon numero.

Mikä numero tarkistetaan? 
> 867-5309
Soittaja on Jenny

Mikä numero tarkistetaan? 
> 

Kiitos!

HashMapin käyttö metodikutsussa

HashMap on viittaustyyppinen muuttuja samalla tavalla kuin ArrayList. Tämä tarkoitta sitä, että kun HashMap-olio annetaan metodille parametrina, metodikutsun yhteydessä kopioituu vain viite, ja metodin sisällä voidaan muokata alkuperäistä HashMap-olioita.

Allaoleva esimerkkiohjelma kuvastaa tätä ominaisuutta.

import java.util.HashMap;

public class Esimerkki {

  public static void main(String[] args) {
    HashMap<String, String> parit = new HashMap<>();
    lisaaArvo(parit, "Esimerkki", "Toimii");

    System.out.println(parit.get("Esimerkki"));
  }

  public static void lisaaArvo(HashMap<String, String> arvot, String avain, String arvo) {
    arvot.put(avain, arvo);
  }
}

Kokeile mitä ylläoleva ohjelma tulostaa!

Arvon päivittäminen

HashMapilla ei ole metodia olemassaolevan arvon päivittämiseen, vaan päivitys tapahtuu edellisen arvon ylikirjoittamisella. Jos HashMapia käytetään asioiden laskemiseen, tulee lisäyksen yhteydessä hakea vanha arvo, kasvattaa sitä yhdellä, ja lisätä kasvatettu arvo takaisin HashMap-olioon. Alla olevassa esimerkissä on toteutettu bongauslaskuri, joka pitää kirjaa erilaisista bongauksista.

import java.util.HashMap;
import java.util.Scanner;

public class Bongauslaskuri {

  public static void main(String[] args) {
    Scanner lukija = new Scanner(System.in);
    HashMap<String, Integer> bongaukset = new HashMap<>();

    while (true) {
      String bongattu = lukija.nextLine();
      if(bongattu.isEmpty()) {
        break; 
      }

      if (!bongaukset.containsKey(bongattu)) {        
        bongaukset.put(bongaus, 1);
      } else {
        bongaukset.put(bongaus, bongaukset.get(bongaus) + 1);      
      }

      System.out.println("Bongauksia " + bongaus + " yhteensä " + bongaukset.get(bongaus));
    }
  }
}

Tehtävä 82: Kahvikassa

Toteutetaan tässä tehtävässä toiminnallisuutta kahvikassan kirjanpitoon. Kahvikassa pitää kirjaa henkilöiden juomista kahveista. Juotuja kahveja voi maksaa pois tuomalla kahvipaikalle uusia kahvipaketteja. Kun ohjelma on valmis, sen tominnallisuus on seuraavanlainen.

** Kahvikassa **
> Sonja
Luodaan tunnus Sonja.
Juotuja kahveja 0.

Kirjoita "juo" jos haluat juoda kahvin, "tuo" jos toit kahvipaketin.
> juo
Juotuja kahveja 1.

** Kahvikassa **
> Sonja
Juotuja kahveja 1.

Kirjoita "juo" jos haluat juoda kahvin, "tuo" jos toit kahvipaketin.
> tuo
Juotuja kahveja -9.

** Kahvikassa **
> Ada
Luodaan tunnus Ada.
Juotuja kahveja 0.

Kirjoita "juo" jos haluat juoda kahvin, "tuo" jos toit kahvipaketin.
> juo
Juotuja kahveja 1.

** Kahvikassa **
...

Kun ohjelmaan kirjautuu uusi käyttäjä, käyttäjätunnus luodaan ja käyttäjälle asetetaan saldoksi 0. Kun käyttäjä kirjoittaa "juo", juotujen kahvien määrä kasvaa yhdellä. Jos taas käyttäjä kirjoittaa "tuo", juotujen kahvien määrä vähenee kymmenellä. Lopeta ohjelman suoritus kun käyttäjä kirjoittaa tyhjän merkkijonon.

Tehdään ohjelma paloissa.

82.1 Tietojen hakeminen ja uuden käyttäjän luominen

Luo metodi public static void haeTiedot(HashMap<String, Integer> kassa, String nimi), joka tarkistaa onko käyttäjää kassa-nimisenä parametrina annetussa HashMapissa. Jos käyttäjää ei löydy, luodaan käyttäjä ja asetetaan käyttäjän juotujen kahvien määräksi 0. Lopulta parametrina annetusta HashMapista haetaan käyttäjän juomien kahvien määrä ja tulostetaan se.

Esimerkkejä metodin toiminnasta:

HashMap<String, Integer> data = new HashMap<>();
haeTiedot(data, "Arto");
Luodaan tunnus Arto.
Juotuja kahveja 0.
HashMap<String, Integer> data = new HashMap<>();
haeTiedot(data, "Arto");
haeTiedot(data, "Arto");
Luodaan tunnus Arto.
Juotuja kahveja 0.
Juotuja kahveja 0.
HashMap<String, Integer> data = new HashMap<>();
haeTiedot(data, "Arto");
haeTiedot(data, "Ada");
haeTiedot(data, "Arto");
Luodaan tunnus Arto.
Juotuja kahveja 0.
Luodaan tunnus Ada.
Juotuja kahveja 0.
Juotuja kahveja 0.

82.2 Saldon muuttaminen

Luo metodi public static void muutaSaldoa(HashMap<String, Integer> kassa, String nimi, int paljonko), joka muuttaa parametrina annetussa kassa-parametrissa olevaan henkilöön nimi liittyvää saldoa parametrina annetun paljonko-muuttujan verran.

Esimerkkejä metodin toiminnasta yhdessä edellisessä osassa toteutetun haeTiedot-metodin kanssa:

HashMap<String, Integer> data = new HashMap<>();
haeTiedot(data, "Arto");
muutaSaldoa(data, "Arto", 1);
haeTiedot(data, "Arto");
Luodaan tunnus Arto.
Juotuja kahveja 0.
Juotuja kahveja 1.
HashMap<String, Integer> data = new HashMap<>();
haeTiedot(data, "Arto");
muutaSaldoa(data, "Arto", -10);
haeTiedot(data, "Arto");
Luodaan tunnus Arto.
Juotuja kahveja 0.
Juotuja kahveja -10.

82.3 Kahvikassan toiminnallisuus

Toteuta pääohjelmametodi, joka hyödyntää edellä toteutettuja metodeja. Käyttäjän tulee ensin syöttää nimi, jonka jälkeen kahvikassa tarkistaa onko käyttäjää olemassa. Jos käyttäjä on olemassa, kerrotaan käyttäjän juomien kahvien määrä. Jos käyttäjää taas ei ole olemassa, luodaan käyttäjä, jonka jälkeen kerrotaan käyttäjän juomien kahvien määrä. Tämän jälkeen käyttäjältä kysytään haluaako hän juoda vai tuoda kahvia. Jos käyttäjä haluaa juoda kahvia, hänen juomiensa kahvien määrää kasvatetaan yhdellä, jonka jälkeen juotujen kahvien määrä tulostetaan. Jos taas käyttäjä haluaa tuoda kahvia, hänen juomiensa kahvien määrää vähennetään kymmenellä, jonka jälkeen juotujen kahvien määrä tulostetaan. Ohjelman suoritus tulee lopettaa kun käyttäjä kirjoittaa nimeksi tyhjän merkkijonon, eli painaa enteriä.

Aloita metodin toteutus siten, että määrittelet Scanner-lukijan ja HashMap<String, Integer> -olion.

public static void main(String[] args) {
    Scanner lukija = new Scanner(System.in);
    HashMap<String, Integer> kahvit = new HashMap<>();

    // kahvikassan toiminnallisuus  
}

// aiemmin toteutetut metodit

23 Olio-ohjelmointi

Proseduraalisessa ohjelmoinnissa, eli tähän asti opiskelemassamme ohjelmointityylissä, ohjelma jäsennellään jakamalla se pienempiin osiin eli metodeihin. Metodi toimii ohjelman erillisenä osana, ja sitä voi kutsua mistä tahansa ohjelmastan sisältä. Metodia kutsuttaessa ohjelman suoritus siirtyy metodin alkuun, ja suorituksen päätyttyä palataan takaisin siihen kohtaan mistä metodia kutsuttiin.

Olio-ohjelmoinnissa, kuten proseduraalisessa ohjelmoinnissa, ohjelma jaetaan pieniin osiin. Näitä pieniä osia kutsutaan olioiksi, ja jokaisella oliolla on oma yksittäinen vastuunsa. Olio sisältää joukon yhteenkuuluvaa tietoa ja toiminnallisuutta. Olio-ohjelmat koostuvat useista olioista, joiden yhteinen toiminta määrittelee järjestelmän toiminnan.

23.1 Olio

Olemme käyttäneet jo Javan valmiita olioita. Esimerkiksi ArrayList:it ovat olioita. Jokainen yksittäinen lista koostuu yhteenkuuluvasta tiedosta, eli olion tilasta. Listaolioihin liittyy toiminnallisuutta, eli metodit joilla olion tilaa voidaan muuttaa. Esimerkiksi seuraavassa ohjelmanpätkässä on kaksi ArrayList-olioa kaupungit ja maat:

public static void main(String[] args) {
    ArrayList<String> kaupungit = new ArrayList<>();
    ArrayList<String> maat = new ArrayList<>();

    maat.add("Suomi");
    maat.add("Saksa");
    maat.add("Hollanti");

    kaupungit.add("Berliini");
    kaupungit.add("Nijmegen");
    kaupungit.add("Turku");
    kaupungit.add("Helsinki");

    System.out.println("maita " + maat.size());
    System.out.println("kaupunkeja " + kaupungit.size());
}

Sekä maat-olio että kaupungit-olio elää omaa elämäänsä. Molempien "tila" on toisten olioiden tilasta riippumaton. Esim. olion maat tila koostuu listalla olevista merkkijonoista "Suomi", "Saksa" ja "Hollanti" ja todennäköisesti myös tiedosta kuinka monta maata listalla on.

Olioon liittyvää metodikutsua tehdessä (esimerkiksi maat.add("Suomi");) pisteen vasemmalle puolelle tulee sen olion nimi, jolle metodia kutsutaan, oikealle metodin nimi. Kun kysytään montako merkkijonoa listalla maat on, kutsu on muotoa maat.size() eli kutsutaan maat oliolle sen metodia size. Metodin palauttama tulos riippuu olion maat tilasta, eli muut oliot kuten kaupungit eivät vaikuta metodin suoritukseen millään tavalla.

23.2 Uusi olio luodaan new-komennolla

Olemme myös jo luoneet olioita koodissamme. Uuden olion luominen tapahtuu komennon new avulla. Esimerkiksi lukijan (Scanner) ja listan (ArrayList) luominen on tapahtunut new-komennolla seuraavasti.

Scanner lukija = new Scanner(System.in);
ArrayList<String> lista = new ArrayList<>();

Komentoa new käytetään kun halutaan luoda uusi olio valmiiksi määritellystä Luokasta. Java-kielessä oliot luodaan aina new-komennolla muutamaa poikkeusta lukuunottamatta.

Eräs näistä poikkeuksista ovat merkkijonot, joiden luomiseen ei aina tarvita new-komentoa. Tuttu tapa merkkijonon luomiseen on oikeastaan Javan lyhennysmerkintä new:in käytölle. Merkkijonon voi luoda myös new:illä kuten muutkin oliot:

String teksti = "tekstiä";       // lyhennysmerkintä merkkijono-olion luomiselle
String toinenTeksti = new String("lisää tekstiä");

Se, minkälainen oliosta tulee, riippuu olion luokasta.

23.3 Luokka

On selvää että kaikki oliot eivät ole keskenään samankaltaisia. Esimerkiksi ArrayList-oliot poikkeavat String-olioista. Kaikilla ArrayList-olioilla on samat metodit (add, contains, remove, size, ...) ja vastaavasti kaikilla String-olioilla on samat metodit (substring, length, charAt, ...). ArrayList- ja String-olioiden metodit eivät ole samat, sillä ne ovat eri tyyppisiä.

Olion tyyppiä kutsutaan luokaksi. ArrayList on luokka, String on luokka, Scanner on luokka, ja niin edelleen... Oliot taas ovat luokasta tehtyjä ilmentymiä.

Kaikista luokasta tehdyillä olioilla on samat metodit sekä samankaltainen tila. Esimerkiksi kaikkien ArrayList-olioiden tila koostuu listalle tallennetuista alkioista. String-olioiden tila taas koostuu merkkijonon muodostavista kirjaimista.

23.4 Luokka ja sen oliot

Luokka määrittelee minkälaisia luokan oliot ovat:

  • mitä metodeita olioilla on
  • minkälainen olioiden tila on tai toisinsanoen mitä ominaisuuksia olioilla on

Luokka kuvaa siitä luotavien olioiden "rakennuspiirustukset".

Otetaan analogia tietokoneiden ulkopuoleisesta maailmasta. Rintamamiestalot lienevät kaikille suomalaisille tuttuja. Voidaan ajatella, että jossain on olemassa piirustukset jotka määrittelevät minkälainen rintamamiestalo on. Piirrustukset ovat luokka, eli ne määrittelevät luokasta luotavien olioiden luonteen:

Yksittäiset oliot eli rintamamiestalot on tehty samojen piirustusten perusteella, eli ne ovat saman luokan ilmentymiä. Yksittäisten olioiden tila eli ominaisuudet (esim. seinien väri, katon rakennusmateriaali ja väri, kivijalan väri, ovien rakennusmateriaali ja väri, ...) vaihtelevat. Seuraavassa yksi "rintamamiestalo-luokan olio":

Luokasta luodaan olio aina kutsumalla olion luovaa metodia eli konstruktoria komennon new avulla. Esimerkiksi Scanner-luokasta luodaan uusi ilmentymä eli olio kun kutsutaan new Scanner(..):

Scanner lukija = new Scanner(System.in);

Konstruktorit saavat parametreja kuten muutkin metodit.

Tehtävä 83: Tilejä

Tehtäväpohjan mukana tulee valmis luokka Tili. Luokan Tili olio esittää pankkitiliä, jolla on saldo (eli jossa on jokin määrä rahaa). Tilejä käytetään näin:

Tili artonTili = new Tili("Arton tili",100.00);
Tili artonSveitsilainenTili = new Tili("Arton tili Sveitsissä",1000000.00);

System.out.println("Alkutilanne");
System.out.println(artonTili);
System.out.println(artonSveitsilainenTili);

artonTili.otto(20);
System.out.println("Arton tilin saldo on nyt: "+artonTili.saldo());
artonSveitsilainenTili.pano(200);
System.out.println("Arton toisen tilin saldo on nyt: "+artonSveitsilainenTili.saldo());

System.out.println("Lopputilanne");
System.out.println(artonTili);
System.out.println(artonSveitsilainenTili);

83.1 Ensimmäinen tilisi

Huom: tämän tehtävän jokaista alikohtaa varten on oma tehtäväpohja, tee tämä tehtävä pohjaan 083.1

Tee ohjelma, joka luo tilin jonka saldo on 100.0, panee tilille 20.0 ja tulostaa tilin. Huom! tee kaikki nämä operaatiot täsmälleen tässä järjestyksessä.

83.2 Ensimmäinen tilisiirtosi

Huom: tämän tehtävän jokaista alikohtaa varten on oma tehtäväpohja, tee tämä tehtävä pohjaan 083.2

Tee ohjelma joka:

  1. Luo tilin nimeltä "Matin tili" saldolla 1000
  2. Luo tilin nimeltä "Oma tili" saldolla 0
  3. Nostaa matin tililtä 100.0
  4. Panee omalle tilille 100.0
  5. Tulosta molemmat tilit

83.3 Tilisiirtoja

Huom: tämän tehtävän jokaista alikohtaa varten on oma tehtäväpohja, tee tämä tehtävä pohjaan 083.3

Yllä siirsit rahaa tililtä toiselle. Tehdään seuraavaksi metodi joka tekee saman!

Toteuta ohjelmapohjaan metodi public static void tilisiirto(Tili mista, Tili minne, double paljonko) joka siirtää rahaa tililtä toiselle. Sinun ei tarvitse tarkistaa että mista-tilin saldo riittää.

Tämän jälkeen tee main-metodissasi seuraavaa:

  1. Luo tili "tili A" saldolla 100.0
  2. Luo tili "tili B" saldolla 0.0
  3. Luo tili "tili C" saldolla 0.0
  4. Siirrä 50.0 tililtä A tilille B.
  5. Siirrä 25.0 tililtä B tilille C.

23.5 Oman luokan määritteleminen - oliomuuttujat

Luokka määritellään jotain mielekästä kokonaisuutta varten. Usein "mielekäs kokonaisuus" kuvaa jotain reaalimaailman asiaa. Jos tietokoneohjelman pitää käsitellä henkilötietoja, voisi olla mielekästä määritellä erillinen luokka Henkilo joka kokoaa yhteen henkilöön liittyvät metodit ja ominaisuudet.

Aloitetaan. Oletetaan että meillä on projektirunko jossa on tyhjä pääohjelma:

public class Main {

    public static void main(String[] args) {

    }
}

Luomme nyt projektiimme eli ohjelmaamme uuden luokan. Tämä tapahtuu valitsemalla NetBeansissa vasemmalta projects-kohdasta hiiren oikealla napilla new ja java class. Avautuvaan dialogiin annetaan luokalle nimi.

Kuten muuttujien ja metodien nimien, myös luokan nimen on aina oltava mahdollisimman kuvaava. Joskus ohjelmoinnin edetessä luokka elää ja muuttaa muotoaan. Tällaisissa tilanteissa luokan voi nimetä uudelleen. Edellisellä viikon alussa löytyy tähän vihjeitä.

Luodaan luokka nimeltä Henkilo. Luokasta muodostuu oma tiedostonsa Henkilo.java. Eli ohjelma koostuu nyt kahdesta tiedostosta, sillä pääohjelma on omassa tiedostossaan. Aluksi luokka on tyhjä:

public class Henkilo {

}

Luokista voi piirtää myös luokkakaavion, jonka merkintätekniikkaan tutustutaan tässä samalla. Luokka, jossa ei ole yhtäkään metodia tai muuttujaa näyttää seuraavalta:

Luokan tulee määritellä mitä metodeja ja ominaisuuksia luokan olioilla on. Päätetään, että jokaisella henkilöllä on nimi ja ikä. Nimi on luonnollista esittää merkkijonona, eli Stringinä, ja ikä taas kokonaislukuna. Lisätään nämä rakennuspiirustuksiimme:

public class Henkilo {
    private String nimi;
    private int ika;
}

Määrittelimme yllä että kaikilla Henkilo-luokan olioilla on nimi ja ika. Määritettely tapahtuu hiukan kuten normaalin muuttujan määrittely. Eteen on kuitenkin nyt laitettu avainsana private. Tämä tarkoittaa sitä, että nimi ja ikä eivät näy suoraan olion ulkopuolelle vaan ovat "piilossa" olion sisällä. Olion sisälle piilottamista kutsutaan kapseloinniksi.

Luokan sisälle määriteltyjä muuttujia kutsutaan oliomuuttujiksi tai olion kentiksi tai olion attribuuteiksi. Rakkaalla lapsella on monta nimeä.

Nyt luokkakaavioon on merkitty luokan nimi sekä muuttujat. Muuttujille määritellään tyyppi muodossa "muuttujanNimi: muuttujanTyyppi". Miinusmerkki muuttujaa ennen kertoo, että se on määritelty private-etuliitteellä.

Olemme nyt määritelleet rakennuspiirustukset -- luokan -- henkilöoliolle. Jokaisella uudella henkilöolioilla on muuttujat nimi ja ika, joissa voi olla oliokohtainen arvo. Henkilöiden "tila" koostuu niiden nimeen ja ikään asetetuista arvoista.

Tehtävä 84: Luokan luominen ja oliomuuttujien asetus

Uuden luokan saa lisättyä seuraavasti: Ruudun vasemmalla reunalla on projektilistaus. Paina projektin nimen kohdalla hiiren oikeaa nappia. Valitse avautuvasta valikosta New ja Java Class. Anna luokan nimeksi (Class Name) Koira.

Luo tehtäväpohjaan luokka nimeltä Koira ja lisää sille oliomuuttujat private String nimi, private String rotu ja private int ika. Luokkakaaviossa luokka Koira näyttää seuraavalta:

Luokalla ei vielä oikeastaan tee mitään, mutta tämän askeleen harjoittelusta on hyötyä myöhempää ajatellen.

23.6 Oman luokan määritteleminen - konstruktori eli tilan alustus

Luotavalle oliolle halutaan asettaa alkutila. Itse määritellyn olion luominen tapahtuu hyvin samaan tapaan kuin Javan valmiiden olioiden kuten ArrayList:ien luominen. Oliot siis luodaan new-komennolla. Olion luomisen yhteydessä on kätevää pystyä antamaan arvot luotavan olion muuttujille. Esimerkiksi uutta henkilö-oliota luotaessa olisi kätevää pystyä antamaan nimi jo luomishetkellä:

public static void main(String[] args) {
    Henkilo ada = new Henkilo("Ada");
    // ...
}

Tämä onnistuu määrittelemällä olion luova metodi, eli konstruktori. Seuraavassa on määritelty Henkilo-luokalle konstruktori, joka luo uuden Henkilo-olion. Alla olevassa esimerkissä olevassa Konstruktorissa henkilön iäksi asetetaan 0 ja nimeksi konstruktorin parametrina tuleva merkkijono:

public class Henkilo {
    private String nimi;
    private int ika;

    public Henkilo(String nimiAlussa) {
        this.ika = 0;
        this.nimi = nimiAlussa;
    }
}

Konstruktori on nimeltään sama kuin luokan nimi. Yllä luokka (class) on Henkilo, ja konstruktori public Henkilo(String nimiAlussa). Konstruktorille parametrina tuleva arvo tulee sulkuihin konstruktorin nimen perään. Konstruktorin voi ajatella olevan metodi, jonka Java suorittaa kun olio luodaan komennolla new Henkilo("Ada"); Oliot luodaan aina konstruktorin avulla.

Muutama huomio: konstruktorin sisällä on komento this.ika = 0. Tässä asetetaan arvo 0 juuri tämän olion, eli "this"-olion sisäiselle muuttujalle ika. Toinen komento this.nimi = nimiAlussa; taas asettaa juuri tämän olion sisäiselle muuttujalle nimi arvoksi parametrina annetun merkkijonon. Olion muuttujat ika ja nimi näkyvät konstruktorissa ja muuallakin olion sisällä automaattisesti. Niihin viitataan this-etuliitteellä. Koska niissä on private-määre, niin olion ulkopuolelle ne eivät näy.

Nyt luokkakaavioon on merkitty luokan nimen ja muuttujien lisäksi myös konstruktori. Konstruktori saa public näkyvyysmääreen takia eteen plussan, jonka lisäksi siitä merkitään sen nimi ja parametrin tyypit (tässä + Henkilo(String)). Konstruktorilla ei ole erikseen palautustyyppiä, joten sitä ei kaavioon merkitä.

Vielä yksi huomio: jos ohjelmoija ei tee luokalle konstruktoria, tekee Java automaattisesti luokalle oletuskonstruktorin. Oletuskonstruktori on konstruktori joka ei tee mitään. Jos konstruktoria ei jostain syystä tarvita, ei sellaista tarvitse ohjelmoida.

Tehtävä 85: Luokan luominen, oliomuuttujien asetus ja konstruktorin käyttö

Uuden luokan saa lisättyä seuraavasti: Ruudun vasemmalla reunalla on projektilistaus. Paina projektin nimen kohdalla hiiren oikeaa nappia. Valitse avautuvasta valikosta New ja Java Class. Jos haluat että luokan nimi on Luokka, aseta luokan nimeksi (Class Name) Luokka.

Luo luokka nimeltä Luokka. Luokalla on oliomuuttujina private String koodi, esimerkiksi "B221", ja private int istumapaikat. Luo tämän jälkeen konstruktori public Luokka(String luokanKoodi, int istumapaikkojenMaara), minkä avulla oliomuuttujiin asetetaan arvot.

Tälläkään luokalla ei vielä oikeastaan tee mitään, mutta seuraavassa tehtävässä luokastamme tehdyllä oliolla voi jo tulostella :).

23.7 Oman luokan määritteleminen - metodit

Alkaa olla korkea aika päästä käyttämään Henkilo-olioita. Osaamme luoda olion ja alustaa olion muuttujat. Järkevään toimintaan pystyäkseen olioilla on oltava myös metodeja. Tehdään Henkilo-luokalle metodi jonka avulla olio tulostaa itsensä ruudulle:

public class Henkilo {
    private String nimi;
    private int ika;

    public Henkilo(String nimiAlussa) {
        this.ika = 0;
        this.nimi = nimiAlussa;
    }

    public void tulostaHenkilo() {
        System.out.println(this.nimi + ", ikä " + this.ika + " vuotta");
    }
}

Metodi kirjoitetaan luokan sisälle konstruktorin alapuolelle. Metodin nimen eteen tulee public void sillä metodin on tarkoitus näkyä ulkomaailmalle ja metodi ei palauta mitään. Huomaa, että sana static ei nyt ilmene missään. Olioiden yhteydessä static-määrettä ei käytetä. Selitämme tulevilla viikoilla hieman tarkemmin mistä tässä on kysymys.

Luokkakaavioon on merkitty luokan nimen, muuttujien lisäksi ja konstruktorin lisäksi nyt myös metodi tulostaHenkilo. Koska metodilla on public-määre, tulee sille alkuun plus, jota seuraa metodin nimi. Metodille ei ole merkitty parametreja, joten niitä ei erikseen merkitä sulkujen sisälle. Metodeille merkitään myös palautustyyppi, tässä "void".

Metodin tulostaHenkilo sisällä on yksi koodirivi joka käyttää hyvakseen oliomuuttujia nimi ja ika -- luokkakaavio ei kerro sisäisestä toteutuksesta. Olion sisäisiin muuttujiin viitataan etuliitteellä this. Kaikki olion muuttujat ovat siis näkyvillä ja käytettävissä metodin sisällä.

Luodaan pääohjelmassa kolme henkilöä ja pyydetään niitä tulostamaan itsensä:

public class Main {

    public static void main(String[] args) {
        Henkilo ada = new Henkilo("Ada");
        Henkilo antti = new Henkilo("Antti");
        Henkilo martin = new Henkilo("Martin");

        ada.tulostaHenkilo();
        antti.tulostaHenkilo();
        martin.tulostaHenkilo();
    }
}

Tulostuu:

Ada, ikä 0 vuotta
Antti, ikä 0 vuotta
Martin, ikä 0 vuotta

Sama screencastina:

Tehtävä 86: Tuote

Luo luokka Tuote joka esittää kaupan tuotetta jolla on hinta, lukumäärä ja nimi.

Uuden luokan saa lisättyä seuraavasti: Ruudun vasemmalla reunalla on projektilistaus. Paina projektin nimen kohdalla hiiren oikeaa nappia. Valitse avautuvasta valikosta New ja Java Class. Anna luokan nimeksi (Class Name) Tuote.

Luokalla tulee olla:

  • Konstruktori public Tuote(String nimiAlussa, double hintaAlussa, int maaraAlussa)
  • Metodi public void tulostaTuote() joka tulostaa tuotteen tiedot tässä muodossa:
    Banaani, hinta 1.1, 13 kpl

Piirrä myös luokkaan liittyvä luokkakaavio itsellesi!

23.8 lisää metodeja

Lisätään aiemmin rakentamallemme Henkilölle metodi, joka kasvattaa henkilön ikää vuodella:

public class Henkilo {
    // ...

    public void vanhene() {
        this.ika = this.ika + 1;
    }
}

Metodi kirjoitetaan tulostaHenkilo-metodin tapaan luokan Henkilo sisälle. Metodissa kasvatetaan oliomuuttujan ika arvoa yhdellä.

Myös luokkakaavio päivittyy.

Kutsutaan metodia ja katsotaan mitä tapahtuu:

public class Main {

    public static void main(String[] args) {
        Henkilo ada = new Henkilo("Ada");
        Henkilo antti = new Henkilo("Antti");

        ada.tulostaHenkilo();
        antti.tulostaHenkilo();

        System.out.println("");

        ada.vanhene();
        ada.vanhene();

        ada.tulostaHenkilo();
        antti.tulostaHenkilo();
    }
}

Ohjelman tulostus on seuraava:

Ada, ikä 0 vuotta
Antti, ikä 0 vuotta

Ada, ikä 2 vuotta
Antti, ikä 0 vuotta

Eli "syntyessään" molemmat oliot ovat nollavuotiaita (konstruktorissa suoritetaan mm. rivi this.ika = 0;). Olion ada metodia vanhene kutsutaan kaksi kertaa. Kuten tulostus näyttää, tämä saa aikaan sen että Adan ikä on vanhenemisen jälkeen 2 vuotta. Kutsumalla metodia Adaa vastaavalle oliolle, toisen henkilöolion ikä ei muutu.

Jokaisella oliolla on siis oma sisäinen tilansa.

Tehtävä 87: Vähenevä laskuri

Tehtäväpohjan mukana tulee osittain valmiiksi toteutettu luokka VahenevaLaskuri:

public class VahenevaLaskuri {
    private int arvo;   // oliomuuttuja joka muistaa laskurin arvon

    public VahenevaLaskuri(int arvoAlussa) {
        this.arvo = arvoAlussa;
    }

    public void tulostaArvo() {
        System.out.println("arvo: " + this.arvo);
    }

    public void vahene() {
        // kirjoita tänne metodin toteutus
        // laskurin arvon on siis tarkoitus vähentyä yhdellä
    }

    // ja tänne muut metodit
}

Seuraavassa esimerkki miten pääohjelma käyttää vähenevää laskuria:

public class Paaohjelma {
    public static void main(String[] args) {
        VahenevaLaskuri laskuri = new VahenevaLaskuri(10);

        laskuri.tulostaArvo();

        laskuri.vahene();
        laskuri.tulostaArvo();

        laskuri.vahene();
        laskuri.tulostaArvo();
    }
}

Pitäisi tulostua:

arvo: 10
arvo: 9
arvo: 8

VahenevaLaskuri-luokan konstruktorille annetaan parametrina alkuarvo. Esimerkin oliota laskuri luodessa laskurille välitetään parametrina arvo 10. Esimerkin laskuri-olioon liittyvään oliomuuttujaan arvo asetetaan siis aluksi arvo 10. Laskurin arvon voi tulostaa metodilla tulostaArvo(). Laskurilla tulee myös olla metodi vahene() joka vähentää laskurin arvoa yhdellä.

87.1 Metodin vahene() toteutus

Täydennä luokan runkoon metodin vahene() toteutus sellaiseksi, että se vähentää kutsuttavan olion oliomuuttujan arvo arvoa yhdellä. Kun olet toteuttanut metodin vahene(), edellisen esimerkin pääohjelman tulee toimia esimerkkitulosteen mukaan.

87.2 Laskurin arvo ei saa olla negatiivinen

Täydennä metodin vahene() toteutus sellaiseksi, ettei laskurin arvo mene koskaan negatiiviseksi. Eli jos laskurin arvo on jo 0, ei vähennys sitä enää vähennä:

public class Paaohjelma {
    public static void main(String[] args) {
        VahenevaLaskuri laskuri = new VahenevaLaskuri(2);

        laskuri.tulostaArvo();

        laskuri.vahene();
        laskuri.tulostaArvo();

        laskuri.vahene();
        laskuri.tulostaArvo();

        laskuri.vahene();
        laskuri.tulostaArvo();
    }
}

Tulostuu:

arvo: 2
arvo: 1
arvo: 0
arvo: 0

87.3 Laskurin arvon nollaus

Tee laskurille metodi public void nollaa() joka nollaa laskurin arvon, esim:

public class Paaohjelma {
    public static void main(String[] args) {
        VahenevaLaskuri laskuri = new VahenevaLaskuri(100);

        laskuri.tulostaArvo();

        laskuri.nollaa();
        laskuri.tulostaArvo();

        laskuri.vahene();
        laskuri.tulostaArvo();
    }
}

Tulostuu:

arvo: 100
arvo: 0
arvo: 0

87.4 Laskurin arvon palautus

Tee laskurille metodi public void palautaAlkuarvo(), joka palauttaa laskurille arvon joka sillä oli alussa:

public class Paaohjelma {
  public static void main(String[] args) {
      VahenevaLaskuri laskuri = new VahenevaLaskuri(100);

      laskuri.tulostaArvo();

      laskuri.vahene();
      laskuri.tulostaArvo();

      laskuri.vahene();
      laskuri.tulostaArvo();

      laskuri.nollaa();
      laskuri.tulostaArvo();

      laskuri.palautaAlkuarvo();
      laskuri.tulostaArvo();
    }
}

Tulostuu:

arvo: 100
arvo: 99
arvo: 98
arvo: 0
arvo: 100

Vihje jotta alkuarvon voi palauttaa, se täytyy "muistaa" toisen oliomuuttujan avulla! Joudut siis lisäämään ohjelmaan oliomuuttujan johon talletetaan laskurin alussa saama arvo.

Listat oliomuuttujina

Oliomuuttujina voi käyttää myös listoja. Olemme aiemmin huomanneet, että listat ovat esimerkiksi näppäriä silloin, silloin kun haluamme pitää kirjaa useammasta erillisestä asiasta. Alla olevassa esimerkissä käsitteelle soittolista on luotu luokka. Soittolista sisältää kappaleita.

// importit

public class Soittolista {
    private ArrayList<String> kappaleet;

    public Soittolista() {
        this.kappaleet = new ArrayList<>();
    }

    public void lisaaKappale(String kappale) {
        this.kappaleet.add(kappale);
    }

    public void poistaKappale(String kappale) {
        this.kappaleet.remove(kappale);
    }

    public void tulostaKappaleet() {
        for (String kappale: this.kappaleet) {
            System.out.println(kappale);
        } 
    }
}

Kokeile ylläolevan soittolistan käyttöä hiekkalaatikossa!

Tehtävä 88: Ruokalista

Kumpulan kampuksella Helsingissä toimivaan Unicafe-nimiseen gourmet-ravintolaan tarvitaan uusi ruokalista. Keittiömestari tietää ohjelmoinnista, ja haluaa listan hallinnointiin tietokonejärjestelmän. Toteutetaan tässä tehtävässä järjestelmän sydän, luokka Ruokalista.

Tehtäväpohjan mukana tulee Main-luokka, jossa voit testata ruokalistan toimintaa. Ruokalistan toteuttamista varten saat seuraavanlaisen tehtäväpohjan:

import java.util.ArrayList;

public class Ruokalista {

    private ArrayList<String> ateriat;

    public Ruokalista() {
        this.ateriat = new ArrayList<>();
    }

    // toteuta tänne tarvittavat metodit
}

Ruokalistaoliolla on oliomuuttujana ArrayList, jonka on tarkoitus tallentaa ruokalistalla olevien ruokalajien nimet.

Ruokalistan tulee tarjota metodit public void lisaaAteria(String ateria), public void tulostaAteriat(), ja public void tyhjennaRuokalista() -- luokkakaaviona lopullinen toteutus näyttää seuraavalta.

88.1 Aterian lisääminen

Toteuta metodi public void lisaaAteria(String ateria), joka lisää uuden aterian ruokalistan ateriat-listaan. Jos lisättävä ateria on jo listassa, sitä ei lisätä uudelleen.

88.2 Aterioiden tulostaminen

Toteuta metodi public void tulostaAteriat(), joka tulostaa ateriat. Esimerkiksi kolmen aterian lisäyksen jälkeen tulostuksen tulee olla seuraavanlainen.

ensimmäisenä lisätty ateria
toisena lisätty ateria
kolmantena lisätty ateria

88.3 Ruokalistan tyhjentäminen

Toteuta metodi public void tyhjennaRuokalista() joka tyhjentää ruokalistan. ArrayList-luokalla on metodi josta on tässä hyötyä. NetBeans osaa vihjata käytettävissä olevista metodeista kun kirjoitat olion nimen ja pisteen. Yritä kirjoittaa ateriat. metodirungon sisällä ja katso mitä käy.

23.9 Muuttujan arvon palauttaminen olion metodista

Aivan kuiten edellisellä viikolla käsittelemämme olioihin liittymättömät metodit, myös olioihin liittyvät metodit voivat palauttaa arvon. Lisätään Henkilölle metodi joka palauttaa henkilön iän:

public class Henkilo {
    // ...

    public int palautaIka() {
        return this.ika;
    }
}

Luokka kokonaisuudessaan:

Eli koska kyseessä olioon liittyvä metodi, ei määrittelyssä ole sanaa static. Olioiden arvon palauttavia metodeja käytetään kuten mitä tahansa arvon palauttavia metodeja:

public class Main {

    public static void main(String[] args) {
        Henkilo pekka = new Henkilo("Pekka");
        Henkilo antti = new Henkilo("Antti");

        pekka.vanhene();
        pekka.vanhene();

        antti.vanhene();

        System.out.println("Pekan ikä: " + pekka.palautaIka());
        System.out.println("Antin ikä: " + antti.palautaIka());

        int yht = pekka.palautaIka() + antti.palautaIka();

        System.out.println("Pekka ja Antti yhteensä " + yht + " vuotta");
    }
}

Ohjelman tulostus on seuraava:

Pekan ikä 2
Antin ikä 1

Pekka ja Antti yhteensä 3 vuotta

Tehtävä 89: Kertoja

Luo luokka Kertoja jolla on:

  • Konstruktori public Kertoja(int luku).
  • Metodi public int kerro(int toinenLuku) joka palauttaa sille annetun luvun toinenLuku kerrottuna konstruktorille annetulla luvulla luku.

Esimerkki luokan käytöstä:

Kertoja kolmellaKertoja = new Kertoja(3);

System.out.println("kolmellaKertoja.kerro(2): " + kolmellaKertoja.kerro(2));

Kertoja neljallaKertoja = new Kertoja(4);

System.out.println("neljallaKertoja.kerro(2): " + neljallaKertoja.kerro(2));
System.out.println("kolmellaKertoja.kerro(1): " + kolmellaKertoja.kerro(1));
System.out.println("neljallaKertoja.kerro(1): " + neljallaKertoja.kerro(1));

Tulostus

kolmellaKertoja.kerro(2): 6
neljallaKertoja.kerro(2): 8
kolmellaKertoja.kerro(1): 3
neljallaKertoja.kerro(1): 4

23.10 Henkilo-luokka laajenee

Jatketaan taas Henkilo-luokan laajentamista. Luokan tämänhetkinen versio on seuraava:

public class Henkilo {
    private String nimi;
    private int ika;

    public Henkilo(String nimiAlussa) {
        this.ika = 0;
        this.nimi = nimiAlussa;
    }

    public void tulostaHenkilo() {
        System.out.println(this.nimi + ", ikä " + this.ika + " vuotta");
    }

    public void vanhene() {
        this.ika = this.ika + 1;
    }

    public int palautaIka() {
        return this.ika;
    }
}

Tehdään henkilölle metodi, jonka avulla voidaan selvittää onko henkilö täysi-ikäinen. Metodi palauttaa totuusarvon -- joko true tai false:

public class Henkilo {
  // ...

    public boolean taysiIkainen() {
        if (this.ika < 18) {
            return false;
        }

        return true;
    }

    /*
    huom. metodin voisi kirjoittaa lyhyemmin seuraavasti:

    public boolean taysiIkainen() {
        return this.ika >= 18;
    }
    */
}

Ja testataan:

public static void main(String[] args) {
    Henkilo pekka = new Henkilo("Pekka");
    Henkilo antti = new Henkilo("Antti");

    int i = 0;
    while (i < 30) {
        pekka.vanhene();
        i++;
    }

    antti.vanhene();

    System.out.println("");

    if (antti.taysiIkainen()) {
        System.out.print("täysi-ikäinen: ");
        antti.tulostaHenkilo();
    } else {
        System.out.print("alaikäinen: ");
        antti.tulostaHenkilo();
    }

    if (pekka.taysiIkainen()) {
        System.out.print("täysi-ikäinen: ");
        pekka.tulostaHenkilo();
    } else {
        System.out.print("alaikäinen: ");
        pekka.tulostaHenkilo();
    }
}
alaikäinen: Antti, ikä 1 vuotta
täysi-ikäinen: Pekka, ikä 30 vuotta

Viritellään ratkaisua vielä hiukan. Nyt henkilön pystyy "tulostamaan" ainoastaan siten, että nimen lisäksi tulostuu ikä. On tilanteita, joissa haluamme tietoon pelkän olion nimen. Eli tehdään tarkoitusta varten oma metodi:

public class Henkilo {
    // ...

    public String getNimi() {
        return this.nimi;
    }
}

Metodi getNimi palauttaa oliomuuttujan nimi kutsujalle. Metodin nimi on hieman erikoinen. Javassa on usein tapana nimetä oliomuuttujan palauttava metodi juuri näin, eli getMuuttujanNimi. Tälläisiä metodeja kutsutaan usein "gettereiksi".

Luokka kokonaisuudessaan:

Muotoillaan pääohjelma käyttämään uutta "getteri"-metodia:

public static void main(String[] args) {
    Henkilo pekka = new Henkilo("Pekka");
    Henkilo antti = new Henkilo("Antti");

    int i = 0;
    while (i < 30) {
        pekka.vanhene();
        i++;
    }

    antti.vanhene();

    System.out.println("");

    if (antti.taysiIkainen()) {
        System.out.println(antti.getNimi() + " on täysi-ikäinen");
    } else {
        System.out.println(antti.getNimi() + " on alaikäinen");
    }

    if (pekka.taysiIkainen()) {
        System.out.println(pekka.getNimi() + " on täysi-ikäinen");
    } else {
        System.out.println(pekka.getNimi() + " on alaikäinen ");
    }
}

Tulostus alkaa olla jo aika siisti:

Antti on alaikäinen
Pekka on täysi-ikäinen

23.11 Olion merkkijonoesitys ja toString-metodi

Olemme syyllistyneet edellä osittain huonoon ohjelmointityyliin tekemällä metodin jonka avulla olio tulostetaan, eli metodin tulostaHenkilo. Suositeltavampi tapa on määritellä oliolle metodi jonka palauttaa olion "merkkijonoesityksen". Merkkijonoesityksen palauttavan metodin nimi on Javassa aina toString. Määritellään seuraavassa henkilölle tämä metodi:

public class Henkilo {
    // ...

    public String toString() {
        return this.nimi + ", ikä " + this.ika + " vuotta";
    }
}

Metodi toString toimii kuten tulostaHenkilo, mutta se ei itse tulosta mitään vaan palauttaa merkkijonoesityksen, jotta metodin kutsuja voi halutessaan suorittaa tulostamisen.

Metodia käytetään hieman yllättävällä tavalla:

public static void main(String[] args) {
    Henkilo pekka = new Henkilo("Pekka");
    Henkilo antti = new Henkilo("Antti");

    int i = 0;
    while (i < 30) {
        pekka.vanhene();
        i++;
    }

    antti.vanhene();

    System.out.println(antti); // sama kun System.out.println(antti.toString());
    System.out.println(pekka); // sama kun System.out.println(pekka.toString());
}

Periaatteena on, että System.out.println-metodi pyytää olion merkkijonoesityksen ja tulostaa sen. Merkkijonoesityksen palauttavan toString-metodin kutsua ei tarvitse kirjoittaa itse, sillä Java lisää sen automaattisesti. Ohjelmoijan kirjoittaessa:

System.out.println(antti);

Java täydentää suorituksen aikana kutsun muotoon:

System.out.println(antti.toString());

Käy niin, että oliolta pyydetään sen merkkijonoesitys. Olion palauttama merkkijonoesitys tulostetaan normaaliin tapaan System.out.println-komennolla.

Voimme nyt poistaa turhaksi käyneen tulostaHenkilo-metodin.

Olioscreencastin toinen osa:

Tehtävä 90: Maksukortti

Helsingin Yliopiston opiskelijaruokaloissa eli Unicafeissa opiskelijat maksavat lounaansa käyttäen maksukorttia. Lopullinen Maksukortti tulee näyttämään luokkakaaviona seuraavalta:

Tässä tehtäväsäsarjassa tehdään luokka Maksukortti, jonka tarkoituksena on jäljitellä Unicafeissa tapahtuvaa maksutoimintaa.

90.1 Luokan runko

Projektiin tulee kuulumaan kaksi kooditiedostoa:

Tehtäväpohjan eli projektin Viikko4_090.Maksukortti mukana tulee kooditiedosto Paaohjelma jonka sisällä on main-metodi.

Lisää projektiin uusi luokka nimeltä Maksukortti. Uuden luokan saa lisättyä seuraavasti: Ruudun vasemmalla reunalla on projektilistaus. Paina projektin nimen Viikko4_090.Maksukortti kohdalla hiiren oikeaa nappia. Valitse avautuvasta valikosta New ja Java Class. Anna luokan nimeksi (Class Name) Maksukortti.

Tee ensin Maksukortti-olion konstruktori, jolle annetaan kortin alkusaldo ja joka tallentaa sen olion sisäiseen muuttujaan. Tee sitten toString-metodi, joka palauttaa kortin saldon muodossa "Kortilla on rahaa X euroa".

Seuraavassa on luokan Maksukortti runko:

public class Maksukortti {
    private double saldo;

    public Maksukortti(double alkusaldo) {
        // kirjoita koodia tähän
    }

    public String toString() {
        // kirjoita koodia tähän
    }
}

Seuraava pääohjelma testaa luokkaa:

public class Paaohjelma {
    public static void main(String[] args) {
        Maksukortti kortti = new Maksukortti(50);
        System.out.println(kortti);
    }
}

Ohjelman tulisi tuottaa seuraava tulostus:

Kortilla on rahaa 50.0 euroa

90.2 Kortilla maksaminen

Täydennä Maksukortti-luokkaa seuraavilla metodeilla:

public void syoEdullisesti() {
    // kirjoita koodia tähän
}

public void syoMaukkaasti() {
      // kirjoita koodia tähän
}

Metodin syoEdullisesti tulisi vähentää kortin saldoa 2.50 eurolla ja metodin syoMaukkaasti tulisi vähentää kortin saldoa 4.30 eurolla.

Seuraava pääohjelma testaa luokkaa:

public class Paaohjelma {
    public static void main(String[] args) {
        Maksukortti kortti = new Maksukortti(50);
        System.out.println(kortti);

        kortti.syoEdullisesti();
        System.out.println(kortti);

        kortti.syoMaukkaasti();
        kortti.syoEdullisesti();
        System.out.println(kortti);
    }
}

Ohjelman tulisi tuottaa kutakuinkin seuraava tulostus:

Kortilla on rahaa 50.0 euroa
Kortilla on rahaa 47.5 euroa
Kortilla on rahaa 40.7 euroa

90.3 Ei-negatiivinen saldo

Mitä tapahtuu, jos kortilta loppuu raha kesken? Ei ole järkevää, että saldo muuttuu negatiiviseksi. Muuta metodeita syoEdullisesti ja syoMaukkaasti niin, että ne eivät vähennä saldoa, jos saldo menisi negatiiviseksi.

Seuraava pääohjelma testaa luokkaa:

public class Paaohjelma {
    public static void main(String[] args) {
        Maksukortti kortti = new Maksukortti(5);
        System.out.println(kortti);

        kortti.syoMaukkaasti();
        System.out.println(kortti);

        kortti.syoMaukkaasti();
        System.out.println(kortti);
    }
}

Ohjelman tulisi tuottaa seuraava tulostus:

Kortilla on rahaa 5.0 euroa
Kortilla on rahaa 0.7 euroa
Kortilla on rahaa 0.7 euroa

Yllä toinen metodin syoMaukkaasti kutsu ei vaikuttanut saldoon, koska saldo olisi mennyt negatiiviseksi.

90.4 Kortin lataaminen

Lisää Maksukortti-luokkaan seuraava metodi:

public void lataaRahaa(double rahamaara) {
    // kirjoita koodia tähän
}

Metodin tarkoituksena on kasvattaa kortin saldoa parametrina annetulla rahamäärällä. Kuitenkin kortin saldo saa olla korkeintaan 150 euroa, joten jos ladattava rahamäärä ylittäisi sen, saldoksi tulisi tulla silti tasan 150 euroa.

Seuraava pääohjelma testaa luokkaa:

public class Paaohjelma {
    public static void main(String[] args) {
        Maksukortti kortti = new Maksukortti(10);
        System.out.println(kortti);

        kortti.lataaRahaa(15);
        System.out.println(kortti);

        kortti.lataaRahaa(10);
        System.out.println(kortti);

        kortti.lataaRahaa(200);
        System.out.println(kortti);
    }
}

Ohjelman tulisi tuottaa seuraava tulostus:

Kortilla on rahaa 10.0 euroa
Kortilla on rahaa 25.0 euroa
Kortilla on rahaa 35.0 euroa
Kortilla on rahaa 150.0 euroa

90.5 Kortin lataus negatiivisella arvolla

Muuta metodia lataaRahaa vielä siten, että jos yritetään ladata negatiivinen rahamäärä, ei kortilla oleva arvo muutu.

Seuraava pääohjelma testaa luokkaa:

public class Paaohjelma {
    public static void main(String[] args) {
        Maksukortti kortti = new Maksukortti(10);
        System.out.println("Pekka: " + kortti);
        kortti.lataaRahaa(-15);
        System.out.println("Pekka: " + kortti);
    }
}

Ohjelman tulisi tuottaa seuraava tulostus:

Pekka: Kortilla on rahaa 10.0 euroa
Pekka: Kortilla on rahaa 10.0 euroa

90.6 Monta korttia

Tee pääohjelma, joka sisältää seuraavan tapahtumasarjan:

  • Luo Pekan kortti. Kortin alkusaldo on 20 euroa
  • Luo Matin kortti. Kortin alkusaldo on 30 euroa
  • Pekka syö maukkaasti
  • Matti syö edullisesti
  • Korttien arvot tulostetaan (molemmat omalle rivilleen, rivin alkuun kortin omistajan nimi)
  • Pekka lataa rahaa 20 euroa
  • Matti syö maukkaasti
  • Korttien arvot tulostetaan (molemmat omalle rivilleen, rivin alkuun kortin omistajan nimi)
  • Pekka syö edullisesti
  • Pekka syö edullisesti
  • Matti lataa rahaa 50 euroa
  • Korttien arvot tulostetaan (molemmat omalle rivilleen, rivin alkuun kortin omistajan nimi)

Pääohjelman runko on seuraava:

public class Main {
    public static void main(String[] args) {
        Maksukortti pekanKortti = new Maksukortti(20);
        Maksukortti matinKortti = new Maksukortti(30);

        // kirjoita koodia tähän
    }
}

Ohjelman tulisi tuottaa seuraava tulostus:

Pekka: Kortilla on rahaa 15.7 euroa
Matti: Kortilla on rahaa 27.5 euroa
Pekka: Kortilla on rahaa 35.7 euroa
Matti: Kortilla on rahaa 23.2 euroa
Pekka: Kortilla on rahaa 30.7 euroa
Matti: Kortilla on rahaa 73.2 euroa

Saatat huomata, että osassa luvuista ilmenee pyöristysvirheitä. Esimerkiksi pekan saldo 30.7 saattaa tulostua muodossa 30.700000000000003. Tämä liittyy siihen, että liukuluvut kuten double tallennetaan oikeasti binäärimuodossa, eli nollina ja ykkösinä vain rajattua määrää lukuja käyttäen. Koska liukulukuja on ääretön määrä (keksitkö miksi? kuinka monta liuku- tai desimaalilukua mahtuu vaikkapa lukujen 5 ja 6 väliin?), ei kaikkia voi esittää rajatulla määrällä nollia ja ykkösiä, ja tietokone joutuu rajoittamaan tallennustarkkuutta.

23.12 Lisää metodeja

Jatketaan taas Henkilo-luokan parissa. Päätetään että haluamme laskea henkilöiden painoindeksejä. Tätä varten teemme henkilölle metodit pituuden ja painon asettamista varten, sekä metodin joka laskee painoindeksin. Henkilön uudet ja muuttuneet osat seuraavassa:

public class Henkilo {
    private String nimi;
    private int ika;
    private int paino;
    private int pituus;

    public Henkilo(String nimiAlussa) {
        this.ika = 0;
        this.paino = 0;
        this.pituus = 0;
        this.nimi = nimiAlussa;
    }

    public void setPituus(int uusiPituus) {
        this.pituus = uusiPituus;
    }

    public void setPaino(int uusiPaino) {
        this.paino = uusiPaino;
    }

    public double painoIndeksi() {
        double pituusPerSata = this.pituus / 100.0;
        return this.paino / (pituusPerSata * pituusPerSata);
    }

    // ...
}

Eli henkilölle lisättiin oliomuuttujat pituus ja paino. Näille voi asettaa arvon metodeilla setPituus ja setPaino. Jälleen käytössä Javaan vakiintunut nimeämiskäytäntö, eli jos metodin tehtävänä on ainoastaan asettaa arvo oliomuuttujaan, on metodi tapana nimetä setMuuttujanNimi:ksi. Arvon asettavia metodeja kutsutaan usein "settereiksi". Seuraavassa käytämme uusia metodeja:

public static void main(String[] args) {
    Henkilo matti = new Henkilo("Matti");
    Henkilo juhana = new Henkilo("Juhana");

    matti.setPituus(180);
    matti.setPaino(86);

    juhana.setPituus(175);
    juhana.setPaino(64);

    System.out.println(matti.getNimi() + ", painoindeksisi on " + matti.painoIndeksi());
    System.out.println(juhana.getNimi() + ", painoindeksisi on " + juhana.painoIndeksi());
}

Tulostus:

Matti, painoindeksisi on 26.54320987654321
Juhana, painoindeksisi on 20.897959183673468

23.13 Parametrilla ja oliomuuttujalla sama nimi!

Edellä metodissa setPituus asetetaan oliomuuttujaan pituus parametrin uusiPituus arvo:

public void setPituus(int uusiPituus) {
    this.pituus = uusiPituus;
}

Parametrin nimi voisi olla myös sama kuin oliomuuttujan nimi, eli seuraava toimisi myös:

public void setPituus(int pituus) {
    this.pituus = pituus;
}

Nyt metodissa pituus tarkottaa nimenomaan pituus-nimistä parametria ja this.pituus saman nimistä oliomuuttujaa. Esim. seuraava ei toimisi sillä koodi ei viittaa ollenkaan oliomuuttujaan pituus -- koodi käytännössä asettaa parametrina saadulle pituus-muuttujalle siinä jo olevan arvon:

public void setPituus(int pituus) {
    // ÄLÄ TEE NÄIN!!!
    pituus = pituus;
}
public void setPituus(int pituus) {
    // VAAN NÄIN!!!
    this.pituus = pituus;
}

23.14 Liukuluvun "siisti" tulostaminen

Desimaalien määrä edellisessä tulostuksessa on hieman liioiteltu. Yksi tapa päästä määräämään tulostettavien desimaalien määrä on seuraava:

System.out.println(matti.getNimi() + ", painoindeksisi on " + String.format("%.2f", matti.painoIndeksi()));
System.out.println(juhana.getNimi() + ", painoindeksisi on " + String.format("%.2f", juhana.painoIndeksi()));

Eli jos luku on liukuluku, voi siitä tehdä komennolla String.format("%.2f", luku) merkkijonon, jossa luku on otettu mukaan kahden desimaalin tarkkuudella. Pisteen ja f:n välissä oleva numero säätää mukaan tulevan desimaalien määrän.

Nyt tulostus on siistimpi:

Matti, painoindeksisi on 26,54
Juhana, painoindeksisi on 20,90

String.format ei ehkä ole kaikkein monikäyttöisin tapa Javassa tulostuksen muotoiluun. Se kuitenkin lienee yksinkertaisin ja kelpaa meille hyvin nyt.

Tehtävä 91: Kello laskurin avulla

Tässä tehtävässä tehdään luokka YlhaaltaRajoitettuLaskuri ja sovelletaan sitä kellon tekemiseen.

91.1 Rajoitettu laskuri

Tehdään luokka YlhaaltaRajoitettuLaskuri. Luokan olioilla on seuraava toiminnallisuus:

  • Laskurilla on oliomuuttuja, joka muistaa laskurin arvon. Laskurin arvo on luku väliltä 0...yläraja.
  • Aluksi laskurin arvo on 0.
  • Olion konstruktori määrittää laskurin ylärajan.
  • Metodi seuraava kasvattaa laskurin arvoa. Mutta jos laskurin arvo ylittää ylärajan, sen arvoksi tulee 0.
  • Metodi toString palauttaa laskurin arvon merkkijonona.

Tehtäväpohjassa on valmiina pääohjelmaa varten tiedosto Paaohjelma. Aloita tekemällä luokka YlhaaltaRajoitettuLaskuri vastaavasti kuin Maksukortti-tehtävässä. Näin tehdään myös tulevissa tehtäväsarjoissa.

Luokan rungoksi tulee seuraava:

public class YlhaaltaRajoitettuLaskuri {
    private int arvo;
    private int ylaraja;

    public YlhaaltaRajoitettuLaskuri(int ylarajanAlkuarvo) {
        // kirjoita koodia tähän
    }

    public void seuraava() {
        // kirjoita koodia tähän
    }

    public String toString() {
        // kirjoita koodia tähän
    }
}

Vihje: et voi palauttaa toStringissä suoraan kokonaislukutyyppisen oliomuuttujan laskuri arvoa. Kokonaislukumuuttujasta arvo saa merkkijonomuodon esim. lisäämällä sen eteen tyhjän merkkijonon eli kirjoittamalla "" + arvo.

Seuraavassa on pääohjelma, joka käyttää laskuria:

public class Paaohjelma {
    public static void main(String[] args) {
        YlhaaltaRajoitettuLaskuri laskuri = new YlhaaltaRajoitettuLaskuri(4);
        System.out.println("arvo alussa: " + laskuri);

        int i = 0;
        while (i < 10) {
            laskuri.seuraava();
            System.out.println("arvo: " + laskuri);
            i++;
        }
    }
}

Laskurille asetetaan konstruktorissa ylärajaksi 4, joten laskurin arvo on luku 0:n ja 4:n väliltä. Huomaa, miten metodi seuraava vie laskurin arvoa eteenpäin, kunnes se pyörähtää 4:n jälkeen 0:aan:

Ohjelman tulostuksen tulisi olla seuraava:

arvo alussa: 0
arvo: 1
arvo: 2
arvo: 3
arvo: 4
arvo: 0
arvo: 1
arvo: 2
arvo: 3
arvo: 4
arvo: 0

91.2 Etunolla tulostukseen

Tee toString-metodista sellainen, että se lisää arvon merkkijonoesitykseen etunollan, jos laskurin arvo on vähemmän kuin 10. Eli jos laskurin arvo on esim. 3, palautetaan merkkijono "03", jos arvo taas on esim. 12, palautetaan normaaliin tapaan merkkijono "12".

Muuta pääohjelma seuraavaan muotoon ja varmista, että tulos on haluttu.

public class Paaohjelma {
    public static void main(String[] args) {
        YlhaaltaRajoitettuLaskuri laskuri = new YlhaaltaRajoitettuLaskuri(14);
        System.out.println("arvo alussa: " + laskuri);

        int i = 0;
        while (i < 16) {
            laskuri.seuraava();
            System.out.println("arvo: " + laskuri);
            i++;
        }
    }
}
arvo alussa: 00
arvo: 01
arvo: 02
arvo: 03
arvo: 04
arvo: 05
arvo: 06
arvo: 07
arvo: 08
arvo: 09
arvo: 10
arvo: 11
arvo: 12
arvo: 13
arvo: 14
arvo: 00
arvo: 01

91.3 Kello, ensimmäinen versio

Käyttämällä kahta laskuria voimme muodostaa kellon. Tuntimäärä on laskuri, jonka yläraja on 23, ja minuuttimäärä on laskuri jonka yläraja on 59. Kuten kaikki tietävät, kello toimii siten, että aina kun minuuttimäärä pyörähtää nollaan, tuntimäärä kasvaa yhdellä.

Tee ensin laskurille metodi arvo, joka palauttaa laskurin arvon:

public int arvo() {
    // kirjoita koodia tähän
}

Tee sitten kello täydentämällä seuraava pääohjelmarunko (kopioi tämä pääohjelmaksesi sekä täydennä tarvittavilta osin kommenttien ohjaamalla tavalla):

public class Paaohjelma {
    public static void main(String[] args) {
        YlhaaltaRajoitettuLaskuri minuutit = new YlhaaltaRajoitettuLaskuri(59);
        YlhaaltaRajoitettuLaskuri tunnit = new YlhaaltaRajoitettuLaskuri(23);

        int i = 0;
        while (i < 121) {
            System.out.println(tunnit + ":" + minuutit);   // tulostetaan nykyinen aika
            // minuuttimäärä kasvaa
            // jos minuuttimäärä menee nollaan, tuntimäärä kasvaa

            i++;
        }
    }
}

Jos kellosi toimii oikein, sen tulostus näyttää suunnilleen seuraavalta:

00:00
00:01
...
00:59
01:00
01:01
01:02
...
01:59
02:00

91.4 Kello, toinen versio

Laajenna kelloasi myös sekuntiviisarilla. Tee lisäksi luokalle YlhaaltaRajoitettuLaskuri metodi asetaArvo, jolla laskurille pystyy asettamaan halutun arvon -- jos et ole ihan varma mitä tässä pitäisi tehdä, kertaa materiaalista kohta missä puhutaan "settereistä".

Jos laskurille yritetään asettaa kelvoton arvo eli negatiivinen luku tai ylärajaa suurempi luku, ei laskurin arvo muutu.

Tämän metodin avulla voit muuttaa kellon ajan heti ohjelman alussa haluamaksesi.

Voit testata kellon toimintaa seuraavalla ohjelmalla

import java.util.Scanner;

public class Paaohjelma {
    public static void main(String[] args)  {
        Scanner lukija = new Scanner(System.in);
        YlhaaltaRajoitettuLaskuri sekunnit = new YlhaaltaRajoitettuLaskuri(59);
        YlhaaltaRajoitettuLaskuri minuutit = new YlhaaltaRajoitettuLaskuri(59);
        YlhaaltaRajoitettuLaskuri tunnit = new YlhaaltaRajoitettuLaskuri(23);

        System.out.print("sekunnit: ");
        int sek = // kysy sekuntien alkuarvo käyttäjältä
        System.out.print("minuutit: ");
        int min = // kysy minuuttien alkuarvo käyttäjältä
        System.out.print("tunnit: ");
        int tun = // kysy tuntien alkuarvo käyttäjältä

        sekunnit.asetaArvo(sek);
        minuutit.asetaArvo(min);
        tunnit.asetaArvo(tun);

        int i = 0;
        while (i < 121) {
            // lisää edelliseen myös sekuntiviisari
            i++;
        }

    }
}

Kokeile laittaa kellosi alkamaan ajasta 23:59:50 ja varmista, että vuorokauden vaihteessa kello toimii odotetusti!

Bonus-tehtävä: ikuisesti käyvä kello (tehtävää ei palauteta!)

Ennen kuin alat tekemään tätä tehtävää, palauta jo tekemäsi kello!

Muuta pääohjelmasi seuraavaan muotoon:

public class Paaohjelma {
    public static void main(String[] args) throws Exception {
        YlhaaltaRajoitettuLaskuri sekunnit = new YlhaaltaRajoitettuLaskuri(59);
        YlhaaltaRajoitettuLaskuri minuutit = new YlhaaltaRajoitettuLaskuri(59);
        YlhaaltaRajoitettuLaskuri tunnit = new YlhaaltaRajoitettuLaskuri(23);

        sekunnit.asetaArvo(50);
        minuutit.asetaArvo(59);
        tunnit.asetaArvo(23);

        while (true) {
            System.out.println(tunnit + ":" + minuutit + ":" + sekunnit);
            Thread.sleep(1000);
            // lisää kellon aikaa sekunnilla eteenpäin
        }
    }
}

Nyt kello käy ikuisesti ja kasvattaa arvoaan sekunnin välein. Sekunnin odotus tapahtuu komennolla Thread.sleep(1000);, komennon parametri kertoo nukuttavan ajan millisekunteina. Jotta komento toimisi, pitää main:in esittelyriville tehdä pieni lisäys: public static void main(String[] args) throws Exception {, eli tummennettuna oleva throws Exception.

Saat ohjelman lopetettua painamalla NetBeans-konsolin (eli sen osan johon kello tulostaa arvonsa) vasemmalla laidalla olevasta punaisesta laatikosta.

23.15 Mistä olio-ohjelmoinnissa oikein on kyse?

Huom! Tässä on tärkeitä kommentteja liittyen olioiden käyttöön. Lue nämä ehdottomasti.

Olio-ohjelmoinnissa on kyse pitkälti käsitteiden eristämisestä omiksi kokonaisuuksikseen tai toisin ajatellen abstraktioiden muodostamisesta. Voisi ajatella, että on turha luoda oliota jonka sisällä on ainoastaan luku, eli että saman voisi tehdä suoraan int-muuttujilla. Asia ei kuitenkaan ole näin. Jos kello koostuu pelkästään kolmesta int-muuttujasta joita kasvatellaan, muuttuu ohjelma lukijan kannalta epäselvemmäksi, koodista on vaikea "nähdä" mistä on kysymys. Aiemmin materiaalissa mainitsimme jo kokeneen ja kuuluisan ohjelmoijan Kent Beckin neuvon "Any fool can write code that a computer can understand. Good programmers write code that humans can understand", eli koska viisari on oikeastaan oma selkeä käsitteensä, on siitä ohjelman ymmärrettävyyden parantamiseksi hyvä tehdä oma luokka, eli YlhaaltaRajoitettuLaskuri.

Käsitteen erottaminen omaksi luokaksi on monellakin tapaa hyvä idea. Ensinnäkin tiettyjä yksityiskohtia (esim. laskurin pyörähtäminen) saadaan piilotettua luokan sisään (eli abstrahoitua). Sen sijaan että kirjoitetaan if-lause ja sijoitusoperaatio, riittää, että laskurin käyttäjä kutsuu selkeästi nimettyä metodia seuraava(). Aikaansaatu laskuri sopii kellon lisäksi ehkä muidenkin ohjelmien rakennuspalikaksi, eli selkeästä käsitteestä tehty luokka voi olla monikäyttöinen. Suuri etu saavutetaan myös sillä, että koska laskurin toteutuksen yksityiskohdat eivät näy laskurin käyttäjille, voidaan yksityiskohtia tarvittaessa muuttaa.

Totesimme että kello sisältää kolme viisaria, eli koostuu kolmesta käsitteestä. Oikeastaan kello on itsekin käsite ja teemme ensi viikolla luokan Kello, jotta voimme luoda selkeitä Kello-olioita. Kello tulee siis olemaan olio jonka toiminta perustuu "yksinkertaisimpiin" olioihin eli viisareihin. Tämä on juuri olio-ohjelmoinnin suuri idea: ohjelma rakennetaan pienistä selkeistä yhteistoiminnassa olevista olioista.

Nyt otamme varovaisia ensiaskelia oliomaailmassa. Kurssin lopussa oliot alkavat kuitenkin olla jo selkärangassa ja nyt ehkä käsittämättömältä tuntuva lausahdus, ohjelma rakennetaan pienistä selkeistä yhteistoiminnassa olevista olioista alkaa tuntua meistä ehkä järkeenkäyvältä ja itsestäänselvältä.

23.16 Oman metodin kutsu

Olio voi kutsua myös omia metodeitaan. Jos esim. halutaan, että toString-metodin palauttama merkkijonoesitys kertoisi myös henkilön painoindeksin, kannattaa toString:istä kutsua olion omaa metodia painoIndeksi:

public String toString() {
    return this.nimi + ", ikä " + this.ika + " vuotta, painoindeksini on " + this.painoIndeksi();
}

Eli kun olio kutsuu omaa metodiaan, riittää etuliite this ja pelkkä metodin nimi. Vaihtoehtoinen tapa on tehdä oman metodin kutsu muodossa painoIndeksi() jolloin ei korosteta, että kutsutaan "olion itsensä" metodia painoindeksi:

public String toString() {
    return this.nimi + ", ikä " + this.ika + " vuotta, painoindeksini on " + painoIndeksi();
}

Olioscreencastin kolmas osa:

Tehtävä 92: Lukutilasto

92.1 Lukujen määrä

Tee luokka Lukutilasto (tiedosto luomaasi luokkaa varten on tehtäväpohjassa valmiina), joka tuntee seuraavat toiminnot :

  • metodi lisaaLuku lisää uuden luvun tilastoon
  • metodi haeLukujenMaara kertoo lisättyjen lukujen määrän

Luokan ei tarvitse tallentaa mihinkään lisättyjä lukuja, vaan riittää muistaa niiden määrä. Metodin lisaaLuku ei tässä vaiheessa tarvitse edes ottaa huomioon, mikä luku lisätään tilastoon, koska ainoa tallennettava asia on lukujen määrä.

Luokan runko on seuraava:

public class Lukutilasto {
    private int lukujenMaara;

    public Lukutilasto() {
        // alusta tässä muuttuja lukujenMaara
    }

    public void lisaaLuku(int luku) {
        // kirjoita koodia tähän
    }

    public int haeLukujenMaara() {
        // kirjoita koodia tähän
    }
}

Seuraava ohjelma esittelee luokan käyttöä:

public class Paaohjelma {
    public static void main(String[] args) {
        Lukutilasto tilasto = new Lukutilasto();
        tilasto.lisaaLuku(3);
        tilasto.lisaaLuku(5);
        tilasto.lisaaLuku(1);
        tilasto.lisaaLuku(2);
        System.out.println("Määrä: " + tilasto.haeLukujenMaara());
    }
}

Ohjelman tulostus on seuraava:

Määrä: 4

92.2 Summa ja keskiarvo

Laajenna luokkaa seuraavilla toiminnoilla:

  • metodi summa kertoo lisättyjen lukujen summan (tyhjän lukutilaston summa on 0)
  • metodi keskiarvo kertoo lisättyjen lukujen keskiarvon (tyhjän lukutilaston keskiarvo on 0)

Luokan runko on seuraava:

public class Lukutilasto {
    private int lukujenMaara;
    private int summa;

    public Lukutilasto() {
        // alusta tässä muuttujat maara ja summa
    }

    public void lisaaLuku(int luku) {
        // kirjoita koodia tähän
    }

    public int haeLukujenMaara() {
        // kirjoita koodia tähän
    }

    public int summa() {
        // kirjoita koodia tähän
    }

    public double keskiarvo() {
        // kirjoita koodia tähän
    }
}

Seuraava ohjelma esittelee luokan käyttöä:

public class Main {
    public static void main(String[] args) {
        Lukutilasto tilasto = new Lukutilasto();
        tilasto.lisaaLuku(3);
        tilasto.lisaaLuku(5);
        tilasto.lisaaLuku(1);
        tilasto.lisaaLuku(2);
        System.out.println("Määrä: " + tilasto.haeLukujenMaara());
        System.out.println("Summa: " + tilasto.summa());
        System.out.println("Keskiarvo: " + tilasto.keskiarvo());
    }
}

Ohjelman tulostus on seuraava:

Määrä: 4
Summa: 11
Keskiarvo: 2.75

92.3 Summa käyttäjältä

Tee ohjelma, joka kysyy lukuja käyttäjältä, kunnes käyttäjä antaa luvun -1. Sitten ohjelma ilmoittaa lukujen summan.

Ohjelmassa tulee käyttää Lukutilasto-olioa summan laskemiseen.

HUOM: älä muuta Lukutilasto-luokaa millään tavalla!

Anna lukuja:
4
2
5
4
-1
Summa: 15

92.4 Monta summaa

Muuta edellistä ohjelmaa niin, että ohjelma laskee myös parillisten ja parittomien lukujen summaa.

HUOM: Määrittele ohjelmassa kolme Lukutilasto-olioa ja laske ensimmäisen avulla kaikkien lukujen summa, toisen avulla parillisten lukujen summa ja kolmannen avulla parittomien lukujen summa.

Jotta testi toimisi, on oliot luotava pääohjelmassa edellä mainitussa järjestyksessä (eli ensin kaikkien summan laskeva olio, toisena parillisten summan laskeva ja viimeisenä parittomien summan laskeva olio)!

HUOM: älä muuta Lukutilasto-luokaa millään tavalla!

Ohjelman tulee toimia seuraavasti:

Anna lukuja:
4
2
5
2
-1
Summa: 13
Parillisten summa: 8
Parittomien summa: 5

23.17 Olioita listalla

Olemme käyttäneet ArrayList:ejä jo monessa esimerkissä ja tehtävässä. ArrayList-olioon pystyy lisäämään esimerkiksi merkkijonoja ja listalla olevien merkkijonojen läpikäynti, etsiminen, poistaminen, järjestäminen ym. ovat vaivattomia toimenpiteitä.

ArrayList:eihin voidaan laittaa minkä tahansa tyyppisiä oliota. Luodaan seuraavassa henkilölista, eli tyyppiä ArrayList<Henkilo> oleva ArrayList ja laitetaan sinne muutama henkilöolio:

public static void main(String[] args) {
    ArrayList<Henkilo> henkilot = new ArrayList<>();

    // voidaan ensin ottaa henkilö muuttujaan
    Henkilo juhana = new Henkilo("Juhana");
    // ja lisätä se sitten listalle
    henkilot.add(juhana);

    // tai voidaan myös luoda olio ja lisätä se heti:
    henkilot.add(new Henkilo("Matti"));
    henkilot.add(new Henkilo("Martin"));

    System.out.println("Henkilöt vastasyntyneenä: ");
    for (Henkilo henkilo : henkilot) {
        System.out.println(henkilo);
    }

    for (Henkilo henkilo : henkilot) {
        henkilo.vanhene();
    }

    System.out.println("1 vuoden kuluttua: ");
    for (Henkilo henkilo : henkilot) {
        System.out.println(henkilo);
    }
}

Ohjelman tulostus:

Henkilöt vastasyntyneenä:
Juhana, ikä 0 vuotta
Matti, ikä 0 vuotta
Martin, ikä 0 vuotta
1 vuoden kuluttua:
Juhana, ikä 1 vuotta
Matti, ikä 1 vuotta
Martin, ikä 1 vuotta

Listalla olevat henkilöt ovat samoja henkilöitä kuin ohjelmassa luodut henkilöt. Olion juhana ikä muuttuu myös jos hän vanhenee listan ulkopuolella.

public static void main(String[] args) {
    ArrayList<Henkilo> henkilot = new ArrayList<>();

    // voidaan ensin ottaa henkilö muuttujaan
    Henkilo juhana = new Henkilo("Juhana");
    // ja lisätä se sitten listalle
    henkilot.add(juhana);

    // tai voidaan myös luoda olio ja lisätä se heti:
    henkilot.add(new Henkilo("Matti"));
    henkilot.add(new Henkilo("Martin"));

    System.out.println("Henkilöt vastasyntyneenä: ");
    for (Henkilo henkilo : henkilot) {
        System.out.println(henkilo);
    }

    for (Henkilo henkilo : henkilot) {
        henkilo.vanhene();
    }

    System.out.println("1 vuoden kuluttua: ");
    for (Henkilo henkilo : henkilot) {
        System.out.println(henkilo);
    }

    juhana.vanhene();
    juhana.vanhene();

    System.out.println("1 vuoden kuluttua -- mutta -- juhana vanheni: ");
    for (Henkilo henkilo : henkilot) {
        System.out.println(henkilo);
    }
}

Ohjelman tulostus:

Henkilöt vastasyntyneenä:
Juhana, ikä 0 vuotta
Matti, ikä 0 vuotta
Martin, ikä 0 vuotta
1 vuoden kuluttua:
Juhana, ikä 1 vuotta
Matti, ikä 1 vuotta
Martin, ikä 1 vuotta
1 vuoden kuluttua -- mutta -- juhana vanheni:
Juhana, ikä 3 vuotta
Matti, ikä 1 vuotta
Martin, ikä 1 vuotta

Tehtävä 93: Musiikkia

Huom! Älä huoli jos kaiuttimista kuuluu pientä "napsuvaa" ääntä -- ongelma liittyy äänen taajuuksista toiseen hyppäämiseen (tai käytettävään kirjastoon), jota ei ole vielä tässä versiossa korjattu. Käytetty äänikirjasto ei myöskään toimi kaikilla konekokoonpanoilla -- kirjaston pitäisi kuitenkin ilmaista jos näin on: voit tehdä tehtävän vaikka ääniä ei kuuluisi.

93.1 Aani

Tee luokka Aani, jolla on double-tyyppinen oliomuuttuja taajuus ja double-tyyppinen oliomuuttuja kesto. Tee luokalle konstruktori, joka saa ensimmäisenä parametrina taajuuden ja toisena parametrina keston. Lisää lisäksi luokalle seuraavat metodit:

  • metodi getTaajuus palauttaa olioon liittyvän taajuus-muuttujan arvon
  • metodi getKesto palauttaa olioon liittyvän kesto-muuttujan arvon
  • metodi toString palauttaa olioa kuvaavan merkkijonoesityksen

Luokan pitäisi toimia seuraavasti:

Aani aani = new Aani(261.6, 2);
System.out.println("Äänen taajuus on " + aani.getTaajuus() + " hertsiä.");
System.out.println("Äänen kesto on " + aani.getKesto() + " sekuntia.");
System.out.println(aani);
Äänen taajuus on 261.6 hertsiä.
Äänen kesto on 2.0 sekuntia.
261.6Hz (2.0s)

93.2 Lista ääniä -- vai musiikkiako?

Sopivat äänet ovat oikeastaan säveliä..

Toteuta luokan Paaohjelma main-metodiin toiminnallisuus, millä käyttäjältä kysytään soitettavat sävelet merkkijonona. Kun merkkijono on saatu, luodaan annetuista merkeistä ääni-olioita, joita lisätään listaan. Lopulta, kun lista on valmis, luo ohjelmassa valmiina tulevasta luokasta Soitin olio ja anna lista soitin-olion soita-metodille.

ArrayList<Aani> aanet = new ArrayList<>();

// luetaan käyttäjältä äänet ja tehdään niistä ääni-olioita, joita
// lisätään aanet-listalle

Soitin soitin = new Soitin();
soitin.soita(aanet);

Käytä seuraavaa taulukkoa käyttäjän syötteen muunnokseen (muunnokset Wikipediasta):

merkki taajuus kesto
C 261.626 0.5
D 293.665 0.5
E 329.628 0.5
F 349.228 0.5
G 391.995 0.5
A 440.000 0.5
H 493.883 0.5

Tämän lisäksi, jos merkkijonossa on tyhjä merkki, lisää listalle taukoa kuvaava ääni-olio, jonka taajuus on 0 ja kesto 0.1. Ohjelman toiminta on esimerkiksi seuraavanlainen:

Kirjoita soitettavat äänet
CDEFG G GFEDC
Soitetaan!

Yllä olevalla merkkijonolla soitetaan nuotit CDEFG, pidetään tauko, soitetaan nuotti G, pidetään tauko, ja soitetaan nuotit GFEDC.

Kokeile myös soittaa merkkijono "E E F G G F E D C C D E E D D E E F G G F E D C C D E D C C"!

93.3 Piilotettu viesti

Lisää ohjelmaan ns. Easter egg, eli tässä tapauksessa salainen kappale, joka soitetaan kun käyttäjä kirjoittaa merkkijonon "TKO-ALY". Saat itse päättää soitettavan ääniyhdistelmän tai kappaleen.

Viikon 4 Loppukysely

Viikon 4 Loppukysely

 

Alla on esitetty viikkoon liittyviä väittämiä. Jos olet täysin eri mieltä väittämän kanssa, valitse 1. Jos olet täysin samaa mieltä väittämän kanssa, valitse 7. Muuten, valitse luku väliltä.

Täysin eri mieltä.
1
2
3
4
5
6
7
Täysin samaa mieltä.

 

1. Viikko oli työläs.
2. Viikko oli opettavainen.
3. Viikko oli turhauttava.
4. Viikko oli mukava.
5. Viikko oli vaikea.