Tehtävät viikolle 4

Pakolliset tehtävät on merkitty harmaalla taustavärillä. Pakollisuus tarkoittaa, että kyseiset tehtävät ovat erityisen oleellisia ja niiden tekeminen on hyvin suositeltavaa. Jos joskus jokin "pakollinen" tehtävä jää tekemättä, kurssi ei kuitenkaan kaadu siihen.

Sanatutkimus

Kotimaisten kielten tutkimuskeskus (Kotus) on julkaissut netissä suomen kielen sanalistan. Tässä tehtävässä käytetään listan muokattua versiota, jonka voit kopioida tästä.

Tallenna tiedosto NetBeans-projektisi päähakemistoon, jotta voit viitata siihen ohjelmassa suoraan nimellä "sanalista.txt". Päähakemisto on samanniminen kuin projektisi nimi.

Sanojen määrä

Tee ohjelma, joka lukee tiedostossa olevat sanat ja tulostaa niiden määrän. Tässä vaiheessa sanoilla ei tarvitse tehdä mitään, riittää laskea niiden määrä.

Ohjelman tulostuksen tulisi olla seuraava:

Sanojen määrä: 91519

z-kirjain

Tee ohjelma, joka tulostaa listan kaikki sanat, joissa on z-kirjain. Tällaisia sanoja ovat esimerkiksi jazz ja zombi.

l-pääte

Tee ohjelma, joka tulostaa listan kaikki sanat, jotka päättyvät l-kirjaimeen. Tällaisia sanoja ovat esimerkiksi kannel ja sammal.

Palindromit

Tee ohjelma, joka tulostaa listan kaikki sanat, jotka ovat palindromeja. Tällaisia sanoja ovat esimerkiksi ala ja enne.

Kaikki vokaalit

Tee ohjelma, joka tulostaa listan kaikki sanat, jotka sisältävät kaikki suomen kielen vokaalit (aeiouyäö). Tällaisia sanoja ovat esimerkiksi myöhäiselokuva ja ympäristönsuojelija.

Oma ominaisuus

Keksi vielä jokin kiinnostava ominaisuus ja tee ohjelma, joka etsii listalta tämän ominaisuuden täyttävät sanat.

Muistava sanakirja

Sanojen luku tiedostosta

Ota pohjaksi viikon 1 tehtävän 4 luokkaa Sanakirja käyttävä sanakirjaohjelma. Voit käyttää pohjana myös täältä löytyvää mallivastausta.

Laajennetan sanakirjaa siten, että se lukee sanakirjan alustavan sisällön tiedostosta. Voit tehdä lukemisen erillisessä metodissa jota sanakirjan käyttäjä kutsuu:

 public static void main(String[] args) {       
        Sanakirja sanakirja = new Sanakirja("suomi-englanti.txt");
        sanakirja.lataa();
        
        // ohjelman päälogiikka (eli käännösten kysely ja uusien käännösten lisääminen) tänne
        // voit copypastata mallivastauksesta tai käyttää omaa versiotasi
 }

Tai vaihtoehtoisesti voit lukea sisällön jo konstruktorissa

Tiedoston nimi annetaan konstruktorin parametrina. Tiedosto koostuu tekstiriveistä jossa on sana-käännös-pareja:

olut beer
apina monkey
ohjelmoija programmer
opiskelija student

Huom: jotta ohjelma löytää tiedoston, pitää se sijoittaa projektin juureen. Tämä onnistuu valitsemalla NetBeansista file -> new file -> other. Sanakirjaprojektin tulee olla pääprojektina kun luot tiedoston. Voit editoida tiedostoa joko NetBeansilla tai tekstieditorilla.

huom2: voit lukea syötteen kahdella tavalla, joko Scannerin metodilla next yksittäinen sana kerrallaa tai nextLine:llä rivi kerrallaan. Jos luet rivi kerrallaan, pitää rivi hajoittaa kahdeksi merkkijonoksi (esim. "olut beer" -> "olut", "beer") jotta lisäys sanakirjaan onnistuu. Tämä voidaan tehdä esim. split-komennolla:

   String rivi = lukija.nextLine();
   String[] osat = rivi.split(" ");
   // nyt osat[0] on rivin ensimmäinen sana ja osat[1] toinen

Sanojen talletus tiedostoon

Tee sanakirjalle metodi talleta, jota kutsuttaessa sanakirjan sisältö kirjoitetaan tiedostoon. Metodia on tarkoitus kutsua hieman ennen ohjelman lopetusta:

 public static void main(String[] args) {       
        Sanakirja sanakirja = new Sanakirja();
        sanakirja.lataa();
        
        // ohjelman päälogiikka

        sanakirja.talleta();
    }

Talletus kannattanee hoitaa siten, että koko käännöslista kirjoitetaan uudelleen vanhan tiedoston päälle, eli materiaalissa esiteltyä append-komentoa ei kannata käyttää.

Varmista että ohjelmasi toimii, eli että uuden käynnistyksen jälkeen edellisessä suorituksessa talletetut sanat löytyvät sanakirjasta.

Olematon tiedosto

Muokaa vielä ohjelmasi sellaiseksi, että se toimii vaikka käynnistettäessä sanakirjatiedostoa ei ole vielä olemassa. Voit poistaa sanakirjan esim. valitsemalla window -> files -> delete

Parempi käännöskone

Muistava sanasto käyttöön

Muokkaa viikolla 3 tekemääsi Käännöskonetta siten, että se käyttää muistavaa sanakirjaa.

Lista tunnistamattomista sanoista

Muokkaa käännöskonetta siten että se toimii seuraavan syötteen mukaan:

Anna lause: Tämä on kääntäjä
käännös: this is compiler 
Anna lause: Tämä on ohjelma
käännös: this is program
Anna lause: Tuo on apina
käännös: *** is *** 
Anna lause: Täma apina on kääntäjä
käännös: this *** is compiler
Anna lause: 

Tuntemattomat sanat:
tuo
apina

(sanakirjasta on ollut ainakin sanojen tämä, on, ohjelma, kaantaja käännökset)

Eli käännöksiä tehdään niin kauan kunnes käyttäjä antaa työhjän syötteen. Jos kääntäjä ei saa sanakirjan avulla selville jonkun sanan käännöstä, tulostuu sanan paikalle ***. Lopussa kääntäjä listaa kaikki syötteessä olleet tuntemattomat sanat. Huomaa että kahdessa lauseessa esiintynyt tuntematon sana ilmoitetaan tuntemattomaksi vaan kertaalleen.

Vihje: testaamisen helpottamiseksi "automatisoi" syöte tulemaan käyttäjän sijaan merkkijonosta. Tämä tapahtuu seuraavasti.

Lisää pääohejlmaasi seuraava metodi:

    private static void asetaSyote() {
        String syote = "Tämä on kääntäjä.\n" +
                       "Tuo on apina\n" +
                       "Tämä on ohjelma\n" +
                       "Tämä apina on kääntäjä\n" +
                       "\n";
        lukija = new Scanner(new ByteArrayInputStream(syote.getBytes()) );
    }

Jokainen merkkijonon merkkeihin \n päättyvä rivi siis simuloi käyttäjän kirjoittamaa enterin painallukseen päättyvää koodiriviä.

Kutsu metodia pääohjelman alussa:

    public static void main(String[] args) {
        asetaSyote();

        Kaannoskone kaantaja = new Kaannoskone("suomi-englanti.txt");
    
        // ...  
    }

Kun haluat antaa syötteen itse, kommentoi metodikutsu.

Tuntemattomille käännökset

Laajenna ohjelmaa siten, että ennen lopetusta kysytään tuntemattomaksi jääneille sanoille käännökset:

Anna lause: Tämä on kääntäjä
käännös: this is compiler 
Anna lause: Tämä on ohjelma
käännös: this is program
Anna lause: Tuo on apina
käännös: *** is *** 
Anna lause: Täma apina on kääntäjä
käännös: this *** is compiler
Anna lause: 

Tuntemattomat sanat:
tuo
apina

Anna tuntemattomille käännökset: 
tuo, käännös:  that
apina, käännös:  monkey

Käännöskone lisää uudet käännökset sanakirjaan. Koska sanakirja tallettaa käännökset tiedostoon, pitäisi uusien sanojen olla käännöskoneen repertuaarissa seuraavalla suorituskerralla. Varmista, että näin todellakin on.

Ennätyslista

Tyypillinen ominaisuus tietokonepelissä on ennätyslista. Tässä tehtäväsarjassa toteutetaan ennätyslista, joka tallennetaan tiedostoon.

Tämän tehtäväsarjan jälkeen puuttuu enää peli, johon ennätyslista liittyy. Sellainen tehdään heti ensi viikolla.

Ennätyslista

Ennätyslistan jokaisella rivillä on nimi ja pistemäärä. Lista on järjestetty pistemäärän mukaan suurimmasta pienimpään. Listan koko on kiinteä (esim. 10 ennätystulosta).

Seuraavassa on ennätyslista, jonka koko on 5 ja jossa on 2 tulosta:

nimipistemäärä
Pekka M.5000
Antti L..1000
--0
--0
--0

Tee luokka Ennatyslista ja lisää siihen seuraavat metodit:

Seuraava ohjelma testaa ennätyslistan toimintaa:

public class Main {
    public static void main(String[] args) {
        Ennatyslista lista = new Ennatyslista(5);
        lista.tyhjenna();
        System.out.println(lista);
    }
}

Ohjelman tulostuksen tulisi olla seuraava:

-- 0
-- 0
-- 0
-- 0
-- 0

Tuloksen lisääminen

Lisää ennätyslistaan metodi lisaaTulos, jonka avulla siihen voi lisätä tuloksen. Tuloksen täytyy mennä oikeaan paikkaan listassa ja syrjäyttää listan viimeinen tulos. Jos tulos on liian huono, se ei pääse listalle.

Seuraava ohjelma testaa metodia:

Seuraava ohjelma testaa ennätyslistan toimintaa:

public class Main {
    public static void main(String[] args) {
        Ennatyslista lista = new Ennatyslista(5);
        lista.tyhjenna();
        lista.lisaaTulos("AAA", 5000);
        lista.lisaaTulos("BBB", 2000);
        lista.lisaaTulos("CCC", 3000);
        lista.lisaaTulos("DDD", 1000);
        lista.lisaaTulos("EEE", 6000);
        lista.lisaaTulos("FFF", 4000);
        System.out.println(lista);

        lista.lisaaTulos("GGG", 7000);
        lista.lisaaTulos("HHH", 3500);
        System.out.println(lista);
    }
}

Ohjelman tulostuksen tulisi olla seuraava:

EEE 6000
AAA 5000
FFF 4000
CCC 3000
BBB 2000

GGG 7000
EEE 6000
AAA 5000
FFF 4000
HHH 3500

Tallennus tiedostoon

Lisää ennätyslistaan metodi tallenna, joka tallentaa ennätyslistan annettuun tiedostoon. Valitse tiedostolle järkevä rakenne. Esimerkiksi voit tallentaa ensimmäiselle riville listan koon ja sitten omille riveilleen kunkin nimen ja pistemäärän.

Testaa myös, että tallennusmetodi toimii.

Lataus tiedostosta

Lisää ennätyslistaan metodi lataa, joka lataa ennätyslistan annetusta tiedostosta.

Testaa myös, että latausmetodi toimii.

Huijaamisen estäminen

Jos ennätykset tallentaa sellaisenaan tiedostoon, ongelmaksi tulee, että epärehellinen pelaaja voi muokata ennätyksiä mielensä mukaan.

Suunnittele ja toteuta tapa, joka estää keskivertopelaajaa huijaamasta muuttamalla ennätystiedostoa.

Kivi, paperi, sakset revisited

Ohjelmoiniin perusteiden viikolla 5 toteutettiin kivi-paperi-sakset-peli. Toteutamme tässä tehtävässä KPS:stä sekä kaksin- että yksinpelin. Käytämme osittain valmista koodia toteutuksemme apuna.

Oli kyseessä yksin- tai kaksinpeli, noudattaa KPS samaa kaavaa. Peli koostuu pelikierroksista, ja jokaisen kierroksen aikana

Jos pelataan tietokonetta vastaan, esim. "pelaaja 2" onkin tekoäly.

Itseasiassa myös viimeviikkoinen tikkupeli etenee saman kaavan mukaan.

Seuraavassa on valmiiksi toteutettu yksinkertaisten kaksinpelien toteuttamiseen tarkoitettu "pelikehys":

import java.util.Scanner;
public abstract class Pelikehys {

    protected int ykkosenVoitot;
    protected int kakkosenVoitot;
    protected int tasapelit;
    protected int kierrokset;
    private Scanner lukija = new Scanner(System.in);

    /**
     *  Metodia kutsuttaessa pelataan yksi kierros. Metodi
     *  palauttaa true jos kumpikaan pelaaja ei halunnut lopettaa.
     *
     *  Kierroksen toiminnallisuus määritellään aliluokassa syrjäyttämällä
     *  pelaaKierros-metodin kutsumat abstraktit ja tyhjät metodit.
     */

    public boolean pelaaKierros(){
       tulostaOhje();
       String eka = ekanVastaus();
       if ( lopetuksenTarkastus(eka) ) return false;

       String toka = tokanVastaus();
       if ( lopetuksenTarkastus(toka) ) return false;

       int voittaja = selvitaVoittaja(eka, toka);
       if ( voittaja==1 )
          ykkosenVoitot++;
       else if ( voittaja==2 )
          kakkosenVoitot++;
       else tasapelit++;

       kierrokset++;
       tulostaKierroksenTiedot( voittaja );
       if ( jokaKierroksellaStatistiikka() ) tulostaStatistiikka();

       return true;
    }

   /*
     * metodi, joka on pakko ylikirjoittaa:
     *
     * palauttaa 1 jos voittaja on pelaaja 1
     *           2 jos voittaja on pelaaja 2
     *           jonkun muun luvun tasapelin tapauksessa
     */
    public abstract int selvitaVoittaja(String eka, String toka);

    // Oletustoiminnallisuudet, jotka SAA ylikirjoittaa aliluokassa.

    // tulostaa joka kierroksella ohjeen pelaajille
    public void tulostaOhje() { }

    // palauttaa ensimmäisen pelaajan vastauksen
    public String ekanVastaus(){
        System.out.print( pelaaja1() +":n vastaus: ");
        return lukija.nextLine();
    }

    // palauttaa toisen pelaajan vastauksen
    public String tokanVastaus()  {
        System.out.print( pelaaja2() +":n vastaus: ");
        return lukija.nextLine();
    }

    // palauttaa true jos pelaajan syöte on ilmaisee pelin lopetuksen
    public boolean lopetuksenTarkastus(String syote) {
        return syote.equals("");
    }

    // palauttaa true jos joka kierroksella halutaan tulostaa tilastotietoja
    public boolean jokaKierroksellaStatistiikka() {
        return true;
    }

    // tulostaa pelikierroksen tuloksen
    public void tulostaKierroksenTiedot(int voittaja) {
        if ( voittaja==1  )
            System.out.println("voittaja "+ pelaaja1());
        else if ( voittaja==2 )
            System.out.println("voittaja "+ pelaaja2());
        else
            System.out.println("tasapeli");
    }


    // tulostaa tilastotiedot
    public void tulostaStatistiikka() {
        System.out.println( "kierroksia " +kierrokset );
        System.out.println( pelaaja1()+" - "+pelaaja2()+ "  " +
                            ykkosenVoitot+ "-"+kakkosenVoitot );
    }

    // pelaajien nimet voidaan vaihtaa ylikirjoittamalla seuraavat
    public String pelaaja1(){
        return "pelaaja 1";
    }

    public String pelaaja2(){
        return "pelaaja 2";
    }
}

Kuten näemme Pelikehys on abstrakti luokka, eli jotta siitä voitaisiin luoda olio, on luokka perittävä ja luokassa oleva abstrakti metodi on pakko ylikirjoittaa.

Pelikehyksen toimintaperiaatteen ymmärtäminen voi olla aluksi hankalaa. Kehyksen käyttö on kuitenkin helppoa kun periaateen ymmärtää.

Luodaan kehyksen avulla yksinkertainen arvauspeli. Jokaisella kierroksella molemmat pelaajat kirjoittavat jonkun sanan. Se kumman sana oli pitempi, voittaa pelin.

Pelin toteuttava luokka ArvausPeli perii luokan Pelikehys. ArvausPelin tulee ylikirjoittaa Pelikehyksen abstrakti metodi selvitaVoittaja. Nimensä mukaisesti metodi määrittelee, yhden pelikierroksen voittajan:

public class ArvausPeli extends Pelikehys {
    
    // palauttaa 1 jos voittaja on pelaaja 1 (parametri eika)
    //           2 jos voittaja on pelaaja 2 (parametri toka)
    //            jonkun muun luvun tasapelin tapauksessa
    public int selvitaVoittaja(String eka, String toka) {
        if ( eka.length() > toka.length() ) return 1;
        else if ( eka.length() < toka.length() ) return 2;
        return 0;
    }
}

Kehyksessä on paljon muitakin metodeja joita ylikirjoittamalla voi säädellä pelin toimintaa. Muita metodeja ei kuitenkaan ole pakko ylikirjoittaa.

Monesta arvauskierroksesta koostuva peli muodostuu seuraavasta pääohjelmasta:

    public static void main(String[] args) {
        ArvausPeli peli = new ArvausPeli();

        while ( true ){
            boolean eiLopeteta = peli.pelaaKierros();
            if ( eiLopeteta==false ) break;
        }
        System.out.println("\nlopetetaan");
        peli.tulostaStatistiikka();
    }

Tutustuminen pelikehykseen

Muuta peliä siten, että pääohjelmassa ensin kysytään pelaajien nimet. Nimet annetaan ArvausPeli:lle konstruktorin parametrina:

    public static Scanner lukija = new Scanner(System.in);
    public static void main(String[] args) {
        System.out.println("Pelaajien nimet: ");
        String pelaaja1 = lukija.nextLine();
        String pelaaja2 = lukija.nextLine();

        ArvausPeli peli = new ArvausPeli(pelaaja1, pelaaja2);

        while ( true ){
            boolean eiLopeteta = peli.pelaaKierros();
            if ( eiLopeteta==false ) break;
        }
        System.out.println("\nlopetetaan");
        peli.tulostaStatistiikka();
    }

Ohjelman tulee nyt tulostaa pelaajien nimet:

Pelaajien nimet: 
Pekka
Antti
Pekka:n vastaus: tietokone
Antti:n vastaus: algoritmi
tasapeli
kierroksia 1
Pekka - Antti  0-0
...

Saat muutoksen aikaan tekemällä ArvausPeli:lle sopivan konstruktorin ja ylikirjoittamalla metodit pelaaja1() ja pelaaja2() sopivalla tavalla.

Erilainen lopetus

Oletusarvoisesti peli lopetetaan jos jompi kumpi pelaajista antaa tyhjän syötteen. Muuta peliä siten, että peli voidaan lopettaa aikaisintaan kolmen pelikierroksen jälkeen. Jälleen muutos edellyttää tietyn metodin ylikirjoittamista. Etsi koodista oikea metodi ja tee muutos.

KPS kaksinpeli

Toteuta kahden ihmispelaajan vastakkainen KPS käyttäen pelikehystä.

Pelin tulostusten pitäisi näyttää suunilleen seuraavalta

.
Pelaajien nimet: 
Pekka
Juhana
Pekka:n vastaus: k
Juhana:n vastaus: p
voittaja Juhana
kierroksia 1
Pekka - Juhana  0-1
Pekka:n vastaus: p
Juhana:n vastaus: s
voittaja Juhana
kierroksia 2
Pekka - Juhana  0-2

KPS tekoälyä vastaan

Toteuta yksinpeli tekoälyä vastaan.

Koska kierroksen voittajan selvityslogiikka on sama kuin kaksinpelissä, kannattaa yksinpelin pelikierroksen toteutus periä kaksinpelistä (joka siis perii pelikehyksen). Tekoäly tulee nyt pelaajaksi 2, joten joudut ylikirjoittamaan metodin tokanVastaus pyytämään tuloksen tekoälyltä. Voit käyttää esim. seuraavaa yksinkertaista tekoälyä:

import java.util.Random;
public class YksinkertainenTekoaly {
    private Random random = new Random();

    public String teeSiirto() {
        int arpa = random.nextInt(3);
        if ( arpa==0 ) return "k";
        else if ( arpa==1 ) return "p";
        else return "s";
    }
}

Pelin tulisi näyttää seuraavalta:

Pelaajan nimi: 
Esko
Esko:n vastaus: k
tekoälyn siirto: s
voittaja Esko
kierroksia 1
Esko - tekoäly  1-0
Esko:n vastaus: k
tekoälyn siirto: k
tasapeli
kierroksia 2
Esko - tekoäly  1-0

KPS muistavaa tekoälyä vastaan

Muuta ohjelmasi käyttämään Ohpen viikon 5 tehtävässä 6.5 tehtyä muistavaa tekoälyä. Jos et ole tehnyt tehtävää tai kadotit oman muistavan tekoälysi, voit käyttää mallivastauksen muistavaa tekoälyä.

Iteraattorit ja iteroitavat

Tässä tehtäväsarjassa toteutetaan muutama iteraattori.

HUOM: Voit jättää kaikissa tehtävissä Iterator-rajapinnan vaatiman remove()-metodin tyhjäksi.

Lukuväli-iteraattori

Tehdään iteraattori, joka käy läpi annetu kokonaislukuvälin. Tee luokka LukuvaliIteraattori, joka toteuttaa rajapinnan Iterator<Integer>. Luokan konstruktori ottaa kaksi int-parametria: ensimmäisen ja viimeisen läpi käytävän luvun.

Iteraattoria voi testata seuraavalla pääohjelmalla:

Iterator<Integer> i = new LukuvaliIteraattori(4, 12);
while (i.hasNext()) {
    System.out.print(i.next() + " ");
}
System.out.println();

Tulostus:

4 5 6 7 8 9 10 11 12

Merkki-iteraattori

Tehdään iteraattori, joka käy läpi merkkijonon jokaisen merkin. Tee luokka MerkkiIteraattori, joka toteuttaa rajapinnan Iterator<Character>. Luokan konstruktori ottaa läpikäytävän merkkijonon.

Iteraattoria voi testata seuraavalla pääohjelmalla:

Iterator<Character> i = new MerkkiIteraattori("Pingviini");
while (i.hasNext()) {
    System.out.print(i.next() + " ");
}
System.out.println();

Tulostus:

P i n g v i i n i

Sivuhuomautus: Kuten arvata saattaa, Character on char-tyyppiä vastaava oliotyyppi (vrt. Integer ja int). Luokkien tyyppiparametreiksi (< ... >) voidaan laittaa vain oliotyyppejä, mutta Java kyllä tekee muunnoksen alkeistyyppien ja vastaavien oliotyyppien välillä automaattisesti tarpeen mukaan.

Iteraattorien käytettävyys

Iteraattoria ei suoraan voi käyttää foreach-tyyppisessä for (Tyyppi alkio : kokoelma) -silmukassa. Foreach vaatii kokoelmaksi Iterable-rajapinnan toteuttavan olion. Esimerkiksi vanha tuttu ArrayList toteuttaa Iterablen. Tehdään iteraattoreitamme varten seuraava yleinen Iterablen toteutus:

public class IteroitavaIteraattori<T> implements Iterable<T> {
    
    private Iterator<T> iteraattori;
    
    public IteroitavaIteraattori(Iterator<T> iteraattori) {
        this.iteraattori = iteraattori;
    }
    
    public Iterator<T> iterator() {
        return iteraattori;
    }
}

Nyt iteraattoreitamme voisi periaatteessa käyttää seuraavasti:

for (char merkki : new IteroitavaIteraattori<Character>(new MerkkiIteraattori("Pingviini"))) {
    System.out.print(merkki + " ");
}
System.out.println();

Hyi! Tehdään tästä vähän vähemmän rumaa kätevillä apumetodeilla.

Tee luokka Iteraattorit, ja lisää sinne sopivat staattiset metodit lukuvali ja merkit niin, että seuraava pääohjelma toimii.

for (int luku : Iteraattorit.lukuvali(3, 7)) {
    System.out.print(luku + " ");
}
System.out.println();

for (char merkki : Iteraattorit.merkit("Pingviini")) {
    System.out.print(merkki + " ");
}
System.out.println();

Tulostus:

3 4 5 6 7
P i n g v i i n i

Käänteisen läpikäynnin iteraattori

Tee iteraattoriluokka KaanteinenIteraattori, joka käy läpi konstruktorissa annetun ArrayList:n alkiot käänteisessä järjestyksessä lopusta alkuun. Halutaan, että iteraattori toimii kaikille ArrayListeille alkion tyypistä riippumatta. Iteraattori täytyy siis tehdä tyyppiparametroitavaksi seuraavasti:

public class KaanteinenIteraattori<T> implements Iterator<T> {
    
    // ...
    
    public KaanteinenIteraattori(ArrayList<T> lista) {
        // ...
    }
    
    // ...
}

Nyt iteraattori toimii kaikilla tyypeillä T. Iteraattorista voi luoda vaikkapa merkkijonoilla toimivan ilmentymän seuraavasti.

ArrayList<String> sanalista = ...;
KaanteinenIteraattori<String> i = new KaanteinenIteraattori<String>(sanalista);

Perehdymme tällaisiin useilla tyypeillä toimiviin "geneerisiin" luokkiin tarkemmin myöhemmin.

Iteraattorit-luokkaan tuleva staattinen metodi tarvitsee nyt myös tyyppiparametrin. Metodi näyttää seuraavalta:

public static <T> Iterable<T> kaannettyna(ArrayList<T> lista) {
    // ...
}

Kaiken tämän jälkeen seuraavan pääohjelman pitäisi toimia:

ArrayList<String> sanoja = new ArrayList<String>();
sanoja.add("hei");
sanoja.add("hoi");
sanoja.add("hui");

ArrayList<Integer> lukuja = new ArrayList<Integer>();
lukuja.add(43);
lukuja.add(84);
lukuja.add(19);
lukuja.add(57);

for (String sana : Iteraattorit.kaannettyna(sanoja)) {
    System.out.print(sana + " ");
}
System.out.println();

for (int luku : Iteraattorit.kaannettyna(lukuja)) {
    System.out.print(luku + " ");
}
System.out.println();

Tulostus

hui hoi hei
57 19 84 43

P.S. Voit halutessasi toteuttaa myös remove()-metodin, jonka tarkoitus on poistaa kokoelmasta next():llä viimeeksi haettu alkio.

Suuret kokonaisluvut

Luonteva tapa tallentaa kokonaisluku Javassa on käyttää int- tai long-muuttujaa. Ongelmaksi voi kuitenkin tulla, että luku on liian suuri eikä se mahdu muuttujaan. Jos käytössä on int-muuttuja, sallittu lukualue on -2^31 ... 2^31-1, ja jos käytössä on long-muuttuja, sallittu lukualue on -2^63 ... 2^63-1.

Yksi ratkaisu ongelmaan on tallentaa luvut merkkijonoina, jolloin niiden pituutta ei ole rajoitettu. Tällöin kuitenkaan Javan tavalliset laskutoimitukset (+, * ym.) eivät sovellu lukujen käsittelyyn vaan niitä varten pitää toteuttaa omia metodeita.

Luvut merkkijonoina

Toteuta metodit summa ja tulo, jotka laskevat kahden merkkijonomuotoisen ei-negatiivisen kokonaisluvun summan ja tulon.

Seuraava ohjelma testaa metodeita:

public class SuuretLuvut {
    public static void main(String[] args) {
        String luku = "123456789123456789123456789";
        System.out.println("Summa: " + summa(luku, luku));
        System.out.println("Tulo: " + tulo(luku, luku));
    }
}

Ohjelman tulisi tuottaa seuraava tulostus:

Summa: 246913578246913578246913578
Tulo: 15241578780673678546105778281054720515622620750190521

BigInteger-luokka

Käytännössä suurten lukujen käsittelyn toteuttaminen yllä olevalla tavalla olisi hankalaa. Javassa onkin myös valmis BigInteger-luokka suurten kokonaislukujen käsittelyyn. Etsi luokasta tietoa Googlella ja toteuta sen avulla edellisen tehtävän ohjelma.