113
Olio-ohjelmoinnin perusteet: opiskeluopas (2016) 1 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT ............................ 4 2. MATERIAALIT JA OHJELMISTOT....................................................................... 5 3. SUORITUSTAVAT ...................................................................................................... 5 3.1 Tentistä ..................................................................................................................... 5 3.2 Tietokonetyöstä ........................................................................................................ 6 4. OPISKELUN ETENEMINEN .................................................................................... 6 VIIKKO 3 .......................................................................................................................... 8 Luokka Object. Kirjastoluokkia osa I: String, StringBuffer, StringBuilder, StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause. ........ 8 Alustus 8 equals-metodi olioiden yhteydessä 14 Luokat StringBuffer ja StringBuilder 15 Luokka StringTokenizer ja luokan String metodi split 18 Tulostuksen muotoilu ja DecimalFormat 20 Merkkijonon ja tekstitiedoston läpikäynti käyttäen luokkaa Scanner 20 Random 22 Luokakirjastojen toteutukset 23 Harjoitustehtävät........................................................................................................... 24 VIIKKO 4 ........................................................................................................................ 25 Kirjastoluokkia osa II: Integer (ja muut kuori- eli kääreluokat). Dynaamiset geneeriset listat: luokat Vector, ArrayList ja LinkedList. Automaattiset tyypinmuunnokset kuoriluokkien ja vastaavien perustyyppien välillä. for-each –rakenne ja iteraattorit. Luokat Arrays ja Collections. .................................. 25 Integer 25 Tyypinmuunnokset sekä autoboxing ja unboxing 27 Johdanto dynaamisiin listoihin (oliokokoelmat) ja geneerisyyteen 29 Java 5.0:sta lähtien: Geneeriset luokat Vector <E>, ArrayList<E> ja LinkedList<E> 31 Esimerkki: Lisäyslajittelu 37 Taulukoiden ja listojen läpikäynti käyttäen for-each lausetta (Java 5.0:sta lähtien) ja iteraattoria 39 Luokat Arrays ja Collections staattisten ja dynaamisten listojen käsittelyyn (mm. lajittelu käyttäen kirjastometodia sort) 41 Harjoitustehtävät........................................................................................................... 42 VIIKKO 5 ........................................................................................................................ 43 Olio-ohjelmoinnin peruskäsitteet: oliot ja luokat, olioiden tietosisältö ja käyttäytyminen, luokan määrittely, olioiden luonti ja käsittely. .................................. 43 Alustus 43 Johdatus: tietueen käsitteestä luokan käsitteeseen käyttäen esimerkkinä henkilötietoja 44 Luokan Henkilo oikeaoppinen määrittely sekä this ja toString 50 Harjoitustehtävät........................................................................................................... 53

1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

  • Upload
    others

  • View
    0

  • Download
    0

Embed Size (px)

Citation preview

Page 1: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

1

1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT ............................ 4

2. MATERIAALIT JA OHJELMISTOT ....................................................................... 5

3. SUORITUSTAVAT ...................................................................................................... 5

3.1 Tentistä ..................................................................................................................... 5

3.2 Tietokonetyöstä ........................................................................................................ 6

4. OPISKELUN ETENEMINEN .................................................................................... 6

VIIKKO 3 .......................................................................................................................... 8

Luokka Object. Kirjastoluokkia osa I: String, StringBuffer, StringBuilder, StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause. ........ 8

Alustus 8

equals-metodi olioiden yhteydessä 14

Luokat StringBuffer ja StringBuilder 15

Luokka StringTokenizer ja luokan String metodi split 18

Tulostuksen muotoilu ja DecimalFormat 20

Merkkijonon ja tekstitiedoston läpikäynti käyttäen luokkaa Scanner 20

Random 22

Luokakirjastojen toteutukset 23

Harjoitustehtävät ........................................................................................................... 24

VIIKKO 4 ........................................................................................................................ 25

Kirjastoluokkia osa II: Integer (ja muut kuori- eli kääreluokat). Dynaamiset geneeriset listat: luokat Vector, ArrayList ja LinkedList. Automaattiset tyypinmuunnokset kuoriluokkien ja vastaavien perustyyppien välillä. for-each –rakenne ja iteraattorit. Luokat Arrays ja Collections. .................................. 25

Integer 25

Tyypinmuunnokset sekä autoboxing ja unboxing 27

Johdanto dynaamisiin listoihin (oliokokoelmat) ja geneerisyyteen 29

Java 5.0:sta lähtien: Geneeriset luokat Vector <E>, ArrayList<E> ja LinkedList<E> 31

Esimerkki: Lisäyslajittelu 37

Taulukoiden ja listojen läpikäynti käyttäen for-each lausetta (Java 5.0:sta lähtien) ja iteraattoria 39

Luokat Arrays ja Collections staattisten ja dynaamisten listojen käsittelyyn (mm. lajittelu käyttäen kirjastometodia sort) 41

Harjoitustehtävät ........................................................................................................... 42

VIIKKO 5 ........................................................................................................................ 43

Olio-ohjelmoinnin peruskäsitteet: oliot ja luokat, olioiden tietosisältö ja käyttäytyminen, luokan määrittely, olioiden luonti ja käsittely. .................................. 43

Alustus 43

Johdatus: tietueen käsitteestä luokan käsitteeseen käyttäen esimerkkinä henkilötietoja 44

Luokan Henkilo oikeaoppinen määrittely sekä this ja toString 50

Harjoitustehtävät ........................................................................................................... 53

Page 2: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

2

VIIKKO 6 ........................................................................................................................ 55

Oman tietotyypin määrittely luokan avulla: Olio-ohjelmointiin liittyvät käsitteet ja niiden ilmaiseminen Javassa ......................................................................................... 55

Alustus 55

Tietotyypin määrittely 56

Luokan tietokentät: esiintymämuuttuja / luokkamuuttuja tai -vakio 57

Luokassa määritellyt metodit: esiintymämetodi / luokkametodi 57

Näkyvyys 58

Kuinka luokan tietokenttiin ja metodeihin voidaan viitata 58

Käytännön ohjeita: main-metodin sisältävä luokka vs. muut luokat 60

Yhteenveto sopivista tietotyypin tietokenttien ja metodien määreistä, kun noudatamme kapselointiperiaatetta 61

Harjoitustehtävät ........................................................................................................... 62

VIIKKO 7 ........................................................................................................................ 63

Olio-ohjelmien suunnittelu, oliot koostuvat olioista, sisäluokka. ................................. 63

Olio-ohjelmoinnin idea lyhyesti 63

Tunnuksien nimeämiskäytäntö 63

Miksi tietosisältö pitäisi kätkeä? 64

Olioiden koostuminen olioista 64

Sisäluokka 65

Harjoitustehtävät ........................................................................................................... 66

VIIKKO 9 ........................................................................................................................ 70

Periytyminen ja luokkahierarkia, abstrakti metodi ja luokka, rajapintaluokka ja Comparable<T>. Tietokonetyön valinta. ...................................................................... 70

Alustus periytymisestä ja luokkahierarkiasta 70

Luokat Employee ja Boss 71

Polymorfismi ja dynaaminen sidonta 73

Abstrakti luokka ja metodi (abstract class and method) 74

Rajapintaluokka (Interface class) 77

Rajapintaluokka Comparable<T> 79

Harjoitustehtävät ........................................................................................................... 82

Tietokonetyön valinta ................................................................................................... 83

LUENTOPÄIVÄ 5.3.2016 .............................................................................................. 84

Joitakin Javan valmiita luokkia ..................................................................................... 84

Olio-ohjelmoinnin periaatteita ...................................................................................... 85

Kokoava esimerkki ....................................................................................................... 85

Luokka Tili 86

Luokka Pankki ja sen testaus 89

Perintä: Luokan Tili aliluokka ShekkiTili, staattinen ja dynaaminen tyyppi 96

Tehtäviä ...................................................................................................................... 101

Page 3: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

3

VIIKKO 10 .................................................................................................................... 103

Poikkeukset. Määre final. Graafiset käyttöliittymät (GUI). Tietokonetyö. ................ 103

Alustus 103

Poikkeukset Javassa 104

Javan poikkeusluokat 105

try … catch –lause 106

Esimerkki poikkeusten käsittelystä: metodi lueInt luokkaa Scanner käyttäen 107

Määre final: sen merkitys eri tilanteissa 108

Harjoitustehtävät ......................................................................................................... 109

VIIKKO 11 .................................................................................................................... 110

Tietokonetyö, hajautustaulu: luokka HashMap<K,V> ............................................... 110

Tietokonetyö 110

Luokka HashMap 110

Harjoitustehtävät ......................................................................................................... 113

Liite 1 ja 2. Kurssisivuilla.

Page 4: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

4

1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT

Opintojaksolla laajennetaan opintojaksolla Algoritmien ja ohjelmoinnin peruskurssi (AOP) annettua kuvaa ohjelmoinnista tutustumalla olio-ohjelmointiin. Kurssin jälkeen opiskelija tuntee olio-ohjelmointikielten käsitteet ja osaa käyttää Javan luokkakirjastoa (esim. merkkijonojen käsittelyyn ja dynaamisten listojen suunnitellut luokat). Opiskelija osaa tehdä olio-ohjelmia, määritellä luokkien avulla omia tietotyyppejä sekä hyödyntää myös perintää ja tuntee poikkeusten käsittelyn perusteet. Keskeisin sisältö on kuitenkin omien tietotyyppien määrittelemisessä. Kurssilla opetetaan myös perintä ja siihen liittyvät käsitteet sekä johdannonomaisesti poikkeukset. Tavoitteena on antaa olio-ohjelmoinnista sellainen kuva, jonka perusteella muiden (olio-)ohjelmointikielten opiskelu sujuu muitta mutkitta.

Olio-ohjelmoinnin opiskelussa voidaan edetä joko menemällä heti ”luokkien ja olioiden maailmaan” tai unohtamalla aluksi oliot tarkastelemalla ensin perinteellistä ohjelmointia, jossa käsitellään vain primitiivistä tietoa (perustyypit, esim. luvut). Tällä opintokokonaisuudella noudatetaan jälkimmäistä lähestymistapaa, koska tällöin ei tarvitse haukata ”koko kakkua” yhdellä kertaa. Nimittäin olio-ohjelmoinnissa tarvitaan myös kaikki perinteellisen ohjelmoinnin välineet: algoritmeja ohjataan peräkkäis-, valinta- ja toistorakenteilla tallettamalla arvoja muistipaikkoihin, joita käsiteltiin jaksolla AOP. Esimerkiksi tehtävissä ”laske syötettyjen lukujen keskiarvo” ja ”tutki onko annettu luku alkuluku” ei ole juurikaan sijaa olioille. Tämän jälkeen huomaamme, että maailma ei koostu pelkästään luvuista ja merkeistä, vaan että asioiden mallinnus tapahtuu paljon korkeammalla tasolla. Tarvitsemme siis keinon, millä voimme esittää, mallintaa (abstrahointi) ja käsitellä suurempia tietoja yhtenä kokonaisuutena. Tähän tarpeeseen vastaa abstraktin tietotyypin (ADT) käsite, joka olio-ohjelmoinnissa toteutetaan luokkana. Näin ollen tällä opintojaksolla Olio-ohjelmoinnin perusteet (OOP) siirrymme motivoidusti olio-ohjelmoinnin peruskäsitteisiin: luokka ja olio.

Esitiedot: Tämän opintojakson esitietoina vaaditaan opintojakson Algoritmien ja ohjelmoinnin peruskurssi (AOP) tiedot. Jakso OOP jatkaa siitä, mihin jaksolla AOP jäätiin. Tällä jaksolla oletetaan, että Javan peruskontrollirakenteet, perustyypit, oliotyypit, taulukot, luokan String ja Random käsittely hallitaan, joskin tällä jaksolla syvennetään luokkien String ja Random ominaisuuksia.

Page 5: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

5

2. MATERIAALIT JA OHJELMISTOT

Opintojaksolla ei ole käytössä opintomonistetta, vaan päämateriaali on tämä opiskeluopas ja sen lisäksi

• harjoitustehtävät ja niiden malliratkaisut, jotka ovat usein laajoja.

• jakson Algoritmien ja ohjelmoinnin peruskurssin opintomonisteen tiettyjä osia käsitellään tällä kurssilla uudestaan perusteellisemmin.

• Liite 1, joka on kurssisivuilla (se on ote Ville Leppäsen opintomonisteesta Ohjelmointi I). Liite 1 sisältää viittauksia kohtiin, joita liitteessä ei ole, mutta nämä eivät ole oleellisia tämän kurssin kannalta.

• Liite 2, joka on kurssisivuilla (se on ote kirjasta Simo Silander, Vesa Ollikainen, Juha Peltomäki: Java, ISBN: 978-951-0-36081-1. Julkaistu: 03/2010, Sivuja: 498 (ks. kurssisivut).

• Kurssisivuille tulee kurssin edetessä lisämateriaalia, joista osa vaaditaan myös tentissä. Vaadittavat osat on merkitty selvästi kurssisivuilla.

• Kurssisivuilla on useita linkkejä, joita voi käyttää opiskelun apuna.

Java-ohjelmointiympäristönä olisi hyvä käyttää JCreatoria tai jotain muuta kehittyneempää ympäristöä, esim. NetBeans. Esim. JCreatorin avulla voidaan havainnollistaa luokan rakennetta käyttäen Class View –näkymää: tietokentät ja metodit näkyvät omassa ikkunassaan eri värisinä. Myös NetBeans (Members view) ja Eclipse näyttävät nämä ja vieläpä tarkemmin. Myös DrJavaa voi käyttää, joskin siinä ei ole edellä mainittuja ominaisuuksia.

3. SUORITUSTAVAT

Opintojaksoon kuuluu tentti ja yksi tietokoneella tehtävä tietokonetyö.

3.1 Tentistä

Tenttioikeuden saa jos on tehnyt hyväksytysti vähintään 30% tehtävistä. Tentin läpäisemiseen vaaditaan n. 50 % maksimipistemäärästä. Tentissä on myös ns. essee-kysymyksiä, jossa ei ole tärkeintä pelkästään muistaa kaikki aiheeseen liittyvät asiat, vaan ymmärtää ne, ja esittää ne jäsentyneesti. Essee-kysymyksessä pyydetään pääasiassa selittämään tiettyjä käsitteitä/asioita tai Javan tiettyjen tärkeiden avainsanojen merkitys. Kysytyt ohjelmointitehtävät ovat hyvin paljon tässä opiskeluoppaassa esitettyjen esimerkkien ja tehtävien kaltaisia. Tenttiin kannattaa valmistautua lukemalla erityisesti oppimateriaalin esimerkit ja harjoitustehtävät.

Page 6: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

6

Huomaa, että ohjelmien teko tentissä melko lyhyessä ajassa ei kerta kaikkiaan onnistu, ellei omaa minkäänlaista ohjelmointirutiinia. Sen vuoksi tenttiin valmistautuessa kannattaa yrittää ratkaista harjoitustehtäviä uudestaan katsomatta valmista ratkaisua, testata niitä koneella ja tutkia tarkasti niiden toimintaa eikä pelkästään lukea valmiita ohjelmia. Valmiin ohjelman ymmärtäminen ei vielä takaa sitä, että pystyisi itse tuottamaan vastaavan toimivan ohjelman; ei, kaukana siitä. Ohjelmien toiminnassa ei ole mitään selittämätöntä, vaan jokaisen lauseen suoritus muuttaa ohjelman tilaa (eli ohjelmien muuttujien arvoja) lauseen toiminnan määräämällä tavalla edeten ohjelman ensimmäisestä lauseesta aina viimeiseen lauseeseen saakka. Yritä aina ymmärtää jokaisen lauseen suorituksen aiheuttama muutos ohjelman tilaan (eli muuttujien arvoihin).

Tentissä annetaan lainaksi joitakin Javan kirjastoluokkien kuvauksia. Ne ovat myös kurssisivuilla, joten tutustu niihin etukäteen.

Tenttipäivät: Ensimmäinen on torstaina 24.3.2016. Muut ilmoitetaan kurssisivuillamme.

Huom. Jakso AOP tulee olla suoritettu kokonaisuudessaan ennen kun voi tenttiä OOPin. Jakson OOP tietokonetyön suunnitelma tulee palauttaa sähköpostitse ennen tenttiä ja olisi hyvä jos koodikin olisi lähes valmis. Katso kurssisivuilla esitetyt vaatimukset tenttien suhteen.

3.2 Tietokonetyöstä

Tehtävän määrittelyt ja viimeinen palautuspäivämäärä kerrotaan kurssisivuilla linkin Tiekonetyö takana. Huomaa, että voitte itse valita työn yksin tai pareittain. Työstä tehdään ensin suunnitelma, jonka muoto kerrotaan kurssisivuilla ja esimerkki suunnitelmasta on s. 66 Murtoluku-tehtävässä. Töitä ohjataan kuten kurssisivuilla kerrotaan. Saat palautteen työstäsi sähköpostitse. Huomaa, että luentopäivällä käsiteltävä pankkisovellus on hyvä lähtökohta tietokonetyölle. Työn nopeata tekemistä kannustetaan antamalla myös ylimääräisiä bonuksia (katso kahden viimeisen viikon Harjoitustehtävät).

4. OPISKELUN ETENEMINEN

Opintoryhmäkokoontumisten lisäksi järjestetään yksi lähiopetustilaisuus, jolloin yliopiston opettaja kertaa siihen mennessä käsitellyistä asioista tärkeimmät. Lisäksi tällöin opiskelijoilla on mahdollisuus kysyä opintojakson epäselviksi jääneistä kohdista.

Opiskelu perustuu itseopiskeluun opiskeluoppaan avustamana, viikoittaisten harjoitustehtävien itsenäiseen laatimiseen ja niistä annettavaan palautteeseen. Lisäksi Moodlessa annetaan vinkkejä tehtävien tekemiseen ja siellä voi myös keskustella tehtävistä.

Harjoitustehtävät on jaettu aiheen perusteella kullekin viikolle niin, että ne kattavat viikon asiat. Harjoitustehtävien huolellinen läpikäynti on opiskelun keskeisin osa.

Koska opintoryhmäkokoontumisten sisältö on aina hyvin samantapainen, ei niiden sisältöä ole aina esitetty ohjelman yhteydessä. Opintoryhmäkokoontumisen yhteydessä käsitellään viikolle tarkoitetut harjoitustehtävät joiden yhteydessä varmistetaan, että viikon asiat ja tässä oppaassa esitetyt esimerkit on ymmärretty. Tällöin opiskelijan tulee olla itse aktiivinen ja kysyä viikon epäselvistä asioista ja esimerkeistä.

Page 7: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

7

Ryhmäkokoontumisissa tuutori jakaa vastuuopettajan laatimat harjoitustehtävien mallivastaukset ja ne tulevat myös Moodleen takautuvasti. Mikäli opintoryhmäkokoontumisiin on suunniteltu jotakin muuta edellisen lisäksi, on siitä maininta viikoittaisen ohjelman kuvauksen yhteydessä.

Ohjelmoinnin oppimisessa ei voi liikaa korostaa itsenäisen opiskelun osuutta. Viikolle merkittyjen harjoitustehtävien miettiminen, tekeminen ja koneella suorittaminen on todella tärkeätä. Laskemalla suoraan opintojakson laajuus opintopisteinä ja sen kesto, niin voimme arvioida, että opiskelijan tulisi käyttää tämän opintojakson opiskeluun keskimäärin n. 14 tuntia viikossa (ryhmäkokoontuminen mukaan lukien) ja kokemuksieni mukaan tämä on aika oikeaan osuva arvio. Tämä arvio edellyttää sen, että jakson AOP asiat ovat hallinnassa. On tietenkin selvää, että arvioitu tuntimäärä vaihtelee hyvinkin paljon riippuen opiskelijasta.

Huomaa taas, että käsitteet ovat hyvin tärkeitä. Ne tarkoittavat tarkasti määriteltyä asiaa tai ominaisuutta. AOP:ssa meillä oli jo mm. seuraavia käsitteitä:

muuttuja, tyyppi, perus- eli primitiivityyppi, olio- eli viittaus- eli viitetyyppi), lause, lauseke, lausekelause, toistolause, valintalause, metodi, muodollinen parametri, todellinen parametri, esiintymämetodi, luokkametodi (eli staattinen metodi).

Tällä kurssilla oletetaan että yo. käsitteet hallitaan.

Sinun kannattaa tehdä itsellesi käsiteluettelo, johon kirjaat aina vastaantulevat uudet käsitteet.

Ohjelmointia opeteltaessa on erittäin tärkeätä testata ohjelmia koneella. Pelkällä ohjelmien lukemisella ei opi ohjelmoimaan. Sen vuoksi sinun kannattaakin kirjoittaa ohjelmia koneella, muuttaa niitä usealla eri tavalla ja katsoa mitä muutoksesi saa aikaan. Tällöin on kuitenkin erityisen tärkeätä se, että ymmärrät miten ohjelma toimii.

Huom. Viikolla 8 ei ole ohjelmaa lainkaan.

Page 8: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

8

VIIKKO 3

Luokka Object. Kirjastoluokkia osa I: String, StringBuffer, StringBuilder, StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause.

Alustus

Jaksolla AOP luokat sisälsivät vain metodeja ja luokissa oli aina main-metodi. Tällöin luokilla ei mallinnettu tietotyyppejä eli tarkastelimme tällöin 'perinteellisiä' ohjelmia. Tämän ja seuraavan viikon tärkeä tavoite on valaista ja tutustuttaa valmiiden luokkien – Javan luokkakirjaston – käyttöön. Kuten muistanette, luokkakirjaston (API eli Application Programming Interface) käyttö on keskeinen osa olio-ohjelmointia. Seuraavassa sanomme kaikkien valmiina olevien luokkien (joita voimme siis käyttää suoraan omissa ohjelmissamme) joukkoa siis luokkakirjastoksi ja sen yksittäistä luokkaa (esim. String) kirjastoluokaksi.

Viikkoina 3 ja 4 käsittelemme luokkia, joissa on main-metodi ja luokat eivät vielä määrittele omia tietotyyppejä. Sen sijaan viikkojen 3 ja 4 jälkeen käsitellään olio-ohjelmoinnin peruskäsitteet (näitä asioita ja käsitteitä tulee tietenkin esille jo viikoilla 3 ja 4) ja määrittelemme itse luokkia, jotka mallintavat tiettyjä asioita ja määrittelevät tietotyypin (eivätkä sisällä main-metodia).

Tämän viikon materiaali on suoraa jatkoa jakson AOPin viimeiselle viikolle ja tässä esitetään

kertauksena useita jakson AOP asioita. Kertaa siis aluksi jakson AOP viimeisen viikon sisältö.

Perus- ja oliotyyppisen muuttujan ero ja asetuslauseen toiminta tulee myös kerrata lukemalla

AOP-opintomonisteen luku 2.5.1.

Edellisellä kurssilla meillä oli käytössä luokan String lyhennetty kuvaus. Niin nytkin, mutta tällä kurssilla keskeisenä kuvauksien lähteinä ovat java-kansioissa (hakemistoissa) olevat englanninkieliset kuvaukset, joiden tiivistelmä on käytössä myös tentissä.

Jaksolla AOP kerrottiin, että Javassa muuttujat ovat joko perustyyppisiä (esim. int) tai viittaus- eli oliotyyppisiä (esim. taulukot, String ja itse tehtyjen luokkien mukaiset oliot). On hyvä huomata, että vaikka taulukot ovat oliotyyppisiä, niin niiden käsittely ei ole samanlaista kuin muiden olioiden käsittely. Nimittäin taulukko-olioiden sisällön tarkastelemiseksi tai muuttamiseksi ei tarvitse käyttää mitään tiettyä metodia, vaan taulukon komponentteihin viitataan Javassa (perinteellisesti) hakasuljenotaatiolla; esim. vek[2]. Näin ollen taulukoiden käsittely ei näytä siltä, että ne olisivat olioita, joskin new:n käyttö taulukkoa luotaessa paljastaa, että kyseessä on olio.

Myös luokan String-olioille voidaan suorittaa operaatioita, jotka eivät noudata yleisiä olion käsittelyperiaatteita: oliota voidaan käsitellä vain ko. luokan metodien avulla. Esimerkiksi vakiomerkkijono voidaan luoda ilman new:tä ja luokan konstruktoria; esim. String st=”java”;

Page 9: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

9

Lisäksi String-tyyppisiä merkkijonoja voidaan liittää peräkkäin eli katenoida käyttäen + operaattoria. Lisäksi voimme muodostaa lausekkeita, joissa on sekaisin lukutyyppisiä ja String-tyyppisiä arvoja, jolloin tuloksena on String-tyyppinen olio. Näin olemme jo tehneet tulostuslauseissa, mutta niin voidaan menetellä muuallakin: voimme esim. kirjoittaa1:

String st =1.5+”333”+’y’;

jossa yhdistetään kolme erityyppistä (double, String, char) arvoa. Tuloksena on String-olio ”1.5333y”. Yhdistyksen tyypinmuunnoksien ehtona on se, että lausekkeessa tulee olla mukana vähintään yksi String-tyyppinen lauseke ja tämähän saadaan helposti aikaan liittämällä lausekkeeseen tarvittaessa tyhjä merkkijono ”” (kaksi lainausmerkkiä peräkkäin). Kaikki nämä temput ovat kiellettyjä muiden oliotyyppien (esim. StringBuffer) yhteydessä.

Taulukoiden ja osin myös merkkijonojen käsittely ovat kielikohtaisia eivätkä tue aina yleisiä olio-ohjelmoinnin periaatteita. Sen sijaan kaikkia muita oliotyyppisiä muuttujia käsitellään aina luokan metodien avulla ja metodin kutsu kohdistetaan ko. luokan mukaiseen olioon käyttäen pistenotaatiota: esim. st.charAt(2), missä st on String-tyyppinen muuttuja (olio) ja charAt on kirjastoluokan String metodi. Kyseinen lauseke palauttaa merkkijonon st kohdassa 2 olevan merkin. Ensiksi olio täytyy kuitenkin luoda 1) käyttäen new-lauseketta tai 2) luokan String-oliota luotaessa suoraan asetuslauseella käyttämättä new-lauseketta.

Javaan kuuluu lukuisa määrä valmiita kirjastoluokkia, jotka ovat ohjelmoijan käytettävissä. Jaksolla AOP käsiteltiin jo luokkia String, Random ja Math (kertaa nämä asiat jakson AOP opintomonisteesta ja opiskeluoppaasta) ja tällä viikolla keskitytään näiden lisäksi kirjastoluokkiin StringBuffer, StringBuilder ja StringTokenizer, joiden avulla voidaan käsitellä merkkijonoja (tekstiä).

On tärkeätä ymmärtää, että jokainen kirjastoluokka (poislukien metodikirjastot, esim. Math) määrittelee tietotyypin. Kyseessä on jonkin abstraktin tietotyypin valmis implementointi Javan luokan avulla. Esimerkiksi merkkijonojen käsittelyyn on useita erilaisia tietotyyppejä, jotka tarjoavat erilaisia ja eri tavalla implementoituja työkaluja (metodeja) merkkijonojen käsittelyyn.

Kaikkien luokkien yliluokka on Object. Se sisältää useita metodeja, jotka kuitenkin ovat usein uudelleenmääriteltyjä sen aliluokissa (esim. luokassa String), koska luokan Object metodit ovat usein liian yleisiä ja eivät sovellu suoraan aliluokkaan sellaisenaan. Jokainen Javan luokka (siis myös meidän itse määrittelemät tietotyypit) perii2 automaattisesti luokan Object kaikki metodit. Perintä tarkoittaa sitä, että aliluokka saa käyttöönsä perittävän luokan määrittelemiä toiminnallisuuksia ja ominaisuuksia. Luokka Object sisältää muun muassa metodin toString, joka palauttaa olion merkkijonoesityksen. Näin ollen se periytyy kaikkiin luokkiin. Kuitenkin sen tuottama merkkijono on usein aika kelvoton ja siksi se määritellään aliluokassa uudelleen niin, että se tuottaa aliluokalle sopivan merkkijonon. Tästä saadaan esimerkkejä jo tällä viikolla ja myös itse määrittelemämme tietotyypin Henkilo yhteydessä.

1 Tämä näyttää aivan järjettömältä!

2 Luokkahierarkiaan ja luokkien väliseen perintään palataan myöhemmin tarkemmin.

Page 10: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

10

Opiskeluohje: Liite 1 sisältää näiden tietotyyppien ja niiden tärkeimpien metodien kuvaukset. Lue ensin alla oleva alla ja sen jälkeen Liitteen 1 s. 87-94 (voit lukea niitä myös rinnakkain), mutta huomaa, että metodien kuvaukset ovat Liitteessä 1. Luokan String metodien vahvasti yksinkertaistettu kuvaus on jakson AOP opintomonisteessa, mutta kaikkien luokkien täydellinen

kuvaus löytyy koneessasi (ja netissä) olevien kirjastoluokkien kuvauksista.

Pakkaus (package) on Javan väline esittää jollain tavalla yhteenkuuluvat luokat (tai oikeastaan niitä vastaavat class-tiedostot) kokonaisuutena. Tämä esitetään kirjoittamalla luokan alkuun package-määre. Pakkaus ei liity olio-ohjelmointiin, vaan se on käytännön ohjelmistotuotannon väline. Omien pakkausten määrittely ja käyttö eivät kuulu tämän kurssin vaatimuksiin. Yksinkertaisimmillaan pakkaus on hakemisto, johon luokat on sijoitettu. Tietyn pakkauksen saa käyttöönsä missä luokassa tahansa kirjoittamalla luokkaan import-lauseen. Myös valmisluokat on koottu pakkauksiksi. Toisia valmisluokkia voidaan käyttää suoraan ilman mitään erityisiä toimenpiteitä (nämä luokat kuuluvat pakkaukseen java.lang3), kun taas toisten valmisluokkien käyttö vaatii import -lauseen, joka kirjoitetaan ennen sen luokan määrittelyä, jossa kirjastoluokkaa käytetään. Ensiksi mainittuja luokkia edustaa mm. luokat String, StringBuffer, Math ja Integer. Sen sijaan luokan Random käyttö vaatii import-lauseen: import java.util.Random; ja luokan StringTokenizer käyttö vaatii import-lauseen4: import java.util.StringTokenizer; tai import

java.util.*; jolloin saa käyttöönsä kummankin ja paljon muutakin eli kaikki pakkauksen java.util luokat. *:n käyttö ei vaikuta ohjelman tehokkuuteen tai kokoon eli kaikki luokkia ei ’ladata’. Pakkaukset java.util ja java.lang ovat pakkauksen java alipakkauksia. Jos tiedoston alussa ei ole package-määrettä, class-tiedostot asetetaan nimettömään pakkaukseen. Se on Java-toteutuksissa nykyhakemisto, so. hakemisto, jossa class-tiedosto sijaitsee. Tällöin ohjelmoija voi käyttää luokassa vain tässä hakemistossa olevia luokkia. Lisätietoja ja esimerkkejä pakkauksista on mm. kursssivujen kohdassa ”Muita hyödyllisiä linkkejä".

Luokan esiintymä eli olio luodaan kirjoittamalla:

new <konstruktorin kutsu>;

missä konstruktorin nimi on aina sama kuin luokan nimi. Mahdolliset konstruktorimetodit esitetään luokkakirjastossa ja huomaa, että niitä on yleensä useita (metodin ylikuormitus!) ja että ne ovat usein parametrisoitu. Katso String-olioiden luontiesimerkkejä jaksolta AOP.

Luokkakirjastossa luokkien kuvauksessa on annettu luokan

• tietokenttien (Field Summary),

• konstruktorien (Constuctor Summary) ja

• metodien (Method Summary)

kuvaukset.

3 Jos installoit Javan dokumentaation, näiden luokkien kuvaukset löytyvät koneesi java-kansion alta kansiosta ..\docs\api\java\lang tai netistä. Luokkien kuvaukset ovat html-tiedostoja, jotka voi avata kaksoisklikkaamalla.

4 Näiden luokkien kuvaukset löytyvät taas kansiosta ..\docs\api\java\util

Page 11: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

11

Tietokenttien ja metodien kuvauksessa voi olla määre static, jolloin se on ns. luokkamuuttuja tai luokkametodi5. Näitä käytetään muodossa

<luokan nimi>.<kentän nimi tai metodi kutsu>

Tällöin ei luoda luokan oliota, vaan metodia käytetään perinteellisen aliohjelman tavoin. Jos taas kuvauksessa ei ole määrettä static, niin kyseessä on ns. esiintymämetodi, joita käytetään muodossa

<olio>.<metodin kutsu>

Näistä puhutaan lisää myös seuraavan esimerkin yhteydessä. Olioita käsitellään kohdistamalla olioon luokan metodin kutsu edellä mainitulla tavalla (poikkeuksena taulukoiden käsittely ja String-olioiden jotkut toiminnot) ja sen vuoksi esiintymämetodeja sanotaan myös olioiden metodeiksi (kuten Liitteessä 1).

Terminologia vaihtelee jonkun verran eri lähteissä. Tässä oppaassa puhutaan aina esiintymämuuttujista ja esiintymämetodeista, mutta muualla saattaa termin esiintymä tilalla olla ilmentymä tai instanssi (vastaava eng. kielinen termi on instance) eli voidaan puhua esim. ilmentymämetodista. Huomaa siis, että ominaisuus: "onko kuvauksessa määre static tai ei ole" määrää sen onko kyseessä luokka- vai esiintymämetodi, joka määrää taas sen miten metodia käytetään (kutsutaan).

Huom. Liitteessä 1 on vain osa luokkien kuvauksista ja ne eivät siis sisällä kaikkia luokkien metodeja. Opettele siis heti lukemaan tietokoneessasi olevia kirjastoluokkien kuvauksia. Kun luet alla olevaa, niin katso luokkakirjastosta käytetyn konstruktorin tai metodin kuvaus.

Aloitamme yksinkertaisilla AOP-kurssin tasoisilla esimerkeillä, jotka käsittelevät luokkaa String.

5 Sanan ’luokka’ käyttö tässä yhteydessä (esim. luokkamuuttuja) viittaa siihen, että ko. asiaa käytetään ilman luokan oliota. Sen sijaan sana ’esiintymä’ (esim. esiintymämetodi), viittaa siihen että, ko. asiaa voidaan käyttää vain luokan olion esiintymän kanssa. Myöhemmin kun määrittelemme omia tietotyyppejä, otamme käyttöön myös termin esiintymämuuttuja.

Page 12: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

12

Esimerkki. Tehdään ohjelma, joka tulostaa annetun merkin esiintymien sijainnit annetussa merkkijonossa. Merkkijono luetaan String-tyyppiseen muuttujaan kohde ja tarkasteltava merkki char-tyyppiseen muuttujaan c käyttäen luokkaa OmaIO. Merkkijono kohde käydään läpi merkeittäin silmukassa. Esimerkiksi jos kohde=”java” ja c=’a’, niin sijainnit ovat 1 ja 3.

public class FindPositions { /** Tulostetaan annetun merkin esiintymät annetussa merkkijonossa. */ public static void main(String[] args) {

System.out.println("Anna merkkijono"); String kohde = OmaIO.lueString( ); System.out.println("Anna etsittava merkki "); char c = OmaIO.lueChar( ); System.out.println("Etsitaan merkin " + c + ” esiintymat merkkijonosta " + kohde); for (int i=0; i<kohde.length( ); i++) { if (kohde.charAt(i)==c) { System.out.println("Loytyi kohdasta " + i + "."); } // if } // for

} // main } // class FindPositions

Edellä for-lauseen runko ja if-lauseen then-haara koostuvat vain yhdestä lauseesta, joten niiden ympärillä ei tarvittaisi lausesulkuja { ja }.

Yllä charAt on luokan String esiintymämetodi ja sen ulkoinen kuvaus luokkakirjastossa (API-dokumentointi ) http://docs.oracle.com/javase/8/docs/api/ on:

char charAt(int index) Returns the char value at the specified index.

Vasemmalla on metodin tulostyyppi, joka on tässä char. Koska tässä ei ole sanaa static ennen tyyppiä char, niin kyseessä on esiintymämetodi. Näin ollen metodin kutsu kohdistetaan aina luokan String-olioon ja näin ollen lausekkeen kohde.charAt(i) arvo on tyyppiä char. Metodin nimi on siis charAt ja sillä on yksi int-tyyppinen parametri, jolla viitataan merkkijonon tiettyyn positioon ja tässäkin merkkijonon indeksointi alkaa nollasta. Klikkaamalla metodin nimeä saa näkyviin tarkemman kuvauksen metodista (tässä tapauksessa lyhyt kuvaus on kuitenkin riittävä):

Page 13: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

13

public char charAt(int index) Metodin otsikkorivi

Returns the char value at the specified index. An index ranges from 0 to length() - 1. The first char value of the sequence is at index 0, the next at index 1, and so on, as for array indexing.

If the char value specified by the index is a surrogate, the surrogate value is returned. Specified by:

charAt in interface CharSequence

(CharSequence on interface eli rajapintaluokka, käsitellään myöhemmin)

Parameters:

index - the index of the char value.

Returns:

the char value at the specified index of this string. The first char value is at index 0.

Throws:

IndexOutOfBoundsException - if the index argument is negative or not less than the length of this string.

Nostaa poikkeuksen eli ‘virheen’ (käsitellään myöhemmin), jos index<0 tai index>=kohdemerkkijonon pituus.

Seuraavassa on vielä ratkaisu, jossa lukemiseen ei käytetä itsetehtyä luokkaa OmaIO, vaan Javan versiosta 5.0:sta lähtien6 mukana olevaa kirjastoluokkaa Scanner. Luokkaa Scanner on käsitelty tarkemmin edellisellä kurssin AOP opiskeluoppaan liitteessä ja se löytyy myös OOPin sivuilta.

import java.util.Scanner;

public class FindPositions2 { /* Tulostetaan annetun merkin esiintymät annetussa merkkijonossa. */ public static void main(String[] args) {

Scanner lukija = new Scanner(System.in); System.out.println("Anna merkkijono"); String kohde = lukija.nextLine(); System.out.println("Anna etsittava merkki "); char c = (lukija.nextLine()).charAt(0); // rivin ensimmäinen ja ainoa merkki System.out.println("Etsitaan merkin " + c +" esiintymat merkkijonosta " + kohde);

6 Javan kehitystyössä on yleensä voimassa seuraava ominaisuus: uudesta versiosta ei poisteta aiemman version ominaisuuksia. Tällä taataan vanhojen Java-ohjelmien toimivuus uudessa ympäristössä.

Page 14: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

14

for (int i=0; i<kohde.length( ); i++) { if (kohde.charAt(i)==c) { System.out.println("Loytyi kohdasta " + i + "."); } // if } // for

} // main } // class FindPositions2

Lauseke (lukija.nextLine()).charAt(0) voidaan kirjoittaa myös ilman sulkeita muodossa

lukija.nextLine().charAt(0),

koska lauseketta jäsennetään vasemmalta oikealle. Tässä lauseke lukija.nextLine() on tyyppiä String ja siihen kohdistetaan luokan String metodi charAt. Huomaa, että luokassa Scanner ei ole metodia yhden merkin lukemiseen, joten yhden merkin lukeminen tulee suorittaa tässä esitetyllä tavalla.

Edellä tarkasteltava merkki tallennettiin char-tyyppiseen muuttujaan c ja se onkin luonnollista. Voimme tarkastella sitä myös yhden merkin mittaisena String-tyyppisenä merkkijonona (alla muuttuja st). Tällöin meidän tulee tutkia onko ko. merkin muodostava merkkijono st alkuperäisen merkkijonon kohde osajono eli onko kohde.substring(i,i+1) sama kuin st. Merkkijonojen yhtäsuuruutta tulee tutkia luokan String metodilla equals eli meidän tulee kirjoittaa: st.equals(kohde.substring(i,i+1)). Voimme siis kirjoittaa:

public static void main(String[] args) { Scanner lukija = new Scanner(System.in); System.out.println("Anna merkkijono"); String kohde = lukija.nextLine(); System.out.println("Anna etsittava merkki "); String st = lukija.nextLine(); System.out.println("Etsitään merkin " + st + " esiintymät merkkijonosta " + kohde); for (int i=0; i<kohde.length( ); i++) { if (st.equals(kohde.substring(i,i+1))) System.out.println("Löytyi kohdasta " + i + "."); } // for } // main

(*** esimerkin loppu ***)

equals-metodi olioiden yhteydessä

Kaikilla luokilla on metodi equals, joka on määritely valmisluokassa Object, joka on kaikkien luokkien (myös itsetehtyjen) yliluokka. Metodi equals on kuitenkin määritelty luokassa Object niin, että se tarkoittaa vain viittauksien yhtäsuuruutta eli ==. Näin ollen se ei ole sellaisenaan käyttökelpoinen, joten joissakin luokissa se on uudelleenmääritelty paremmin. Esim. luokassa String equals tarkoittaa merkkijonojen samuutta kuten nähtiin edellisessä esimerkissä. Monissa muissa luokissa kuten esim. jatkossa esitellyissä muissa merkkijonoluokissa StringBuffer ja StringBuilder kuten myös seuraavalla viikolla käsiteltävien dynaamisten listojen

Page 15: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

15

implementoinnissa metodia ei ole uudelleenmääritelty, joten tarvittaessa equals metodi tulee kirjoittaa itse.

Luokat StringBuffer ja StringBuilder

Luokan StringBuffer oliot ovat merkkijonoja (puhutaan myös merkkijonopuskuriolioista), mutta niiden sisältöä voi muuttaa eli luokassa StringBuffer on metodeja, joilla voimme muuttaa ko. luokan olioita. Luokan String alkiot ovat siis mutatoitumattomia, kun taas luokan StringBuffer oliot ovat mutatoituvia. Esimerkiksi jos halutaan muuttaa tietyssä merkkijonossa tiettyjä merkkejä toisiksi luomatta uutta oliota, niin on järkevää määritellä kyseinen merkkijono luokan StringBuffer olioksi. Luokan olion luonti ja olion viittauksen asetus muuttujaan suoritetaan seuraavasti:

StringBuffer <muuttujan nimi> = new StringBuffer(<String-tyyppinen olio>);

missä <String-tyyppinen olio> on String-tyyppinen vakio, lauseke tai muuttuja.

Tyypit String ja StringBuffer (ja vastaavasti StringBuilder) eivät ole sijoitusyhteensopivia eli String-tyyppiseen muuttujaan ei voi suoraan sijoittaa StringBuffer-tyyppistä alkiota (eikä päinvastoinkaan). Tarvittaessa meidän tulee tehdä tyypin muunnos ja luoda uusi halutun luokan olio, jonka sisältö vastaa alkuperäisen olion sisältöä. Muutos tehdään seuraavasti:

• String-oliosta vastaava StringBuffer-tyyppinen olio: Olkoon st luokan String olio (tai merkkijonovakio). Tällöin voimme luoda uuden StringBuffer-tyyppisen olion stb kirjoittamalla

StringBuffer stb = new StringBuffer(st);

Jos tässä jätetään argumentti st pois, niin silloin olion sisältönä on tyhjä merkkijono.

• StringBuffer-oliosta vastaava String-tyyppinen olio: Olkoon stb luokan StringBuffer olio. Tällöin voimme luoda uuden String-tyyppisen olion st kirjoittamalla

String st = new String(stb);

Vaihtoehtoisesti voimme kirjoittaa

String st = stb.toString( );

Tässä on myös hyvä huomata, että eksplisiittiset tyypinmuunnokset eivät onnistu kirjastoluokkien tyyppien välillä samaan tapaan kuin perustyypeillä (esim. double vs. int); esim. emme voi kirjoittaa

StringBuffer stb = (StringBuffer) st;

Lisäksi on hyvä huomata, että luettaessa merkkijono on se aina tyyppiä String ja jos siitä halutaan muodostaa luokan StringBuffer olio, tulee käyttää em. tyypin muunnosta.

Lisäksi luokasta String tutut operaatiot + (katenointi) ja olion luonti ilman new:tä ei onnistu luokassa StringBuffer, vaan ne tulee suorittaa oikeaoppisesti käyttäen luokan metodeja. Myöskään merkkijonovakiota ei voi asettaa suoraan StringBuffer-olion arvoksi, koska merkkijonovakio on aina tyyppiä String.

Page 16: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

16

Liitteessä 1 on suomennettuna joitakin luokan StringBuffer metodeja ja niiden toiminta. Kattavampi kuvaus löytyy luokkakirjastosta.

Esimerkki. Alla on esimerkki merkkijonon kääntämisestä kun metodi käännä saa argumenttinaan sekä palauttaa StringBuffer-tyyppisen olion. Tämä sama metodi esitettiin myös jakson AOP opiskeluoppaassa, mutta tällöin alkiot olivat String-tyyppisiä.

Tämän metodin tekeminen on turhaa, koska luokassa StringBuffer on valmiina metodi reverse, joka tekee saman asian. Ohjelmasta näet kuitenkin metodien käännä ja reverse erilaisen soveltamistavan:

• Metodia käännä ei kohdisteta olioon, koska se on luokkametodi (määreenä on static). Tällöin käännettävä merkkijono (muuttuja kohde) annetaan metodille parametrina.

• Sen sijaan reverse on luokan StringBuffer esiintymämetodi (luokkakirjastossa metodin reverse kuvauksessa ei ole määrettä static), joten sitä käytetään olio-ohjelmoinnin periaatteiden mukaisesti: metodi kohdistetaan johonkin ko. luokan olioon kirjoittamalla <olio>.<metodin kutsu> eli kuten alla: kohde.reverse( ).

Metodissa käännä käytetään metodia append, joka lisää yhden merkin nykyisen olion perään. Tässä meillä on siis käytössä vain yksi olio, joka mutatoituu (muuttuu) ohjelman suorituksen aikana.

Metodin append kuvaus kirjastoluokassa StringBuffer on muotoa:

StringBuffer append(char c) Appends the string representation of the char argument to this sequence

Kun klikkaamme append-sanaa saamme tarkemman kuvauksen:

public StringBuffer append(char c)

Appends the string representation of the char argument to this sequence.

The argument is appended to the contents of this sequence. The length of this sequence increases by 1.

The overall effect is exactly as if the argument were converted to a string by the method String.valueOf(char) and the character in that string were then appended to this character sequence.

Parameters:

c - a char.

Returns:

a reference to this object. // reference = viittaus

Huom. Vaikka append on esiintymämetodi (koska sana static puuttuu metodin otsikkoriviltä), niin se on funktio, jonka soveltaminen palauttaa StringBuffer-olion. Soveltaminen on siis lausekelause, jota voi käyttää lauseena tai lausekkeena. Esimerkiksi alla kutsulla apu.append(st.charAt(i)) on myös arvo, joka on sama kuin apu:n muuttunut sisältö. Tätä arvoa ei useinkaan ole tarvetta asettaa minkään toisen muuttujan arvoksi, vaikka se mahdollista onkin.

Page 17: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

17

public class Kaanna_buffer { public static void main(String[] args) {

System.out.println(”Anna merkkijono”); String st = OmaIO.lueString(); // String-tyyppisestä oliosta st luodaan StringBuffer-tyyppinen olio nimeltä kohde

StringBuffer kohde = new StringBuffer(st); System.out.println(käännä(kohde)); System.out.println(kohde.reverse( ));

} // end main /** Metodi palauttaa merkkijonon st käännettynä. * Alkuehto: st tulee olla luotu, mutta se voi olla tyhjä. */ public static StringBuffer käännä(StringBuffer st) { // luodaan ensin uusi olio, jonka sisältönä on tyhjä merkkijono StringBuffer apu=new StringBuffer( ); // lisätään sen loppuun st:n merkit yksitellen lopusta alkaen for (int i=st.length( )-1; i>=0; i--) { apu.append(st.charAt(i)); // ks. yllä oleva Huom. } return apu; } // metodi käännä } // class Kaanna_buffer

Lause apu.append(st.charAt(i)); lisää merkkijonon apu perään merkin st.charAt(i) eli merkkijonon st positiossa i olevan merkin.

(*** esimerkin loppu ***)

Java 5.0:sta lähtien mukana on myös luokka StringBuilder, joka on kuin luokka StringBuffer, mutta luokan StringBuilder kuvauksessa suositellaan sen käyttöä StringBufferin sijasta tehokkuuden vuoksi. Katso luokkakirjastosta sen kuvaus.

Kuten jo aiemmin todettiin, luokkien StringBuilder ja StringBuffer osalta tulee huomioida myös seuraava: metodi equals antaa arvon true vain silloin kun viitataan samaan olioon (siis toimii samoin kuin ==).

Page 18: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

18

Luokka StringTokenizer ja luokan String metodi split

Luokkaa StringTokenizer käytetään osien (tokens eli tokenien) erottamiseen ja poimimiseen merkkijonosta, jossa on käytetty tiettyjä erotinmerkkejä. Erotinmerkit voidaan antaa konstruktorille parametrina olion luonnin yhteydessä. Esimerkiksi StringTokenizer stok = new StringTokenizer(<merkkijono josta etsitään>, <erotinmerkit merkkijonona>);

Jos erotinmerkkejä ei anneta, niin oletuksena on white-space merkit (välilyönti, tabulaattori, enter). Lisäksi konstruktorille voidaan antaa kolmas parametri (ks. alla olevan esimerkin muuttujan st4 luonti).

Esimerkki. Esitämme esimerkkiohjelman, jossa havainnollistetaan luokan metodien toimintaa. Metodissa luodaan läpikäytävä merkkijono-olio st2 käyttäen konstruktoria

StringTokenizer(String str, String delim) Constructs a string tokenizer for the specified string.

jossa erotinmerkki delim annetaan merkkijonona. Ohjelmassa käytetään myös luokan StringTokenizer esiintymämetodia nextToken:

String nextToken() Returns the next token from this string tokenizer.

Metodin nextToken kutsu kohdistetaan StrngTokenizer-olioon ja kutsu palauttaa merkkijonon seuraavan osan.

Page 19: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

19

Katso rivien perässä olevat kommentit.

import java.util.*; public class Tokenit { public static void main(String[] args) { StringTokenizer st1= new StringTokenizer("a b 1"); System.out.println(st1.countTokens()); // tulostuu 3 String s = st1.nextToken(); // s="a" s = st1.nextToken(); // s="b" s = st1.nextToken(); // s="1" StringTokenizer st2 = new StringTokenizer("xx, yy,11", ","); s = st2.nextToken(); // s="xx" s = st2.nextToken(); // s=" yy" int i = Integer.parseInt(st2.nextToken()); // i=11 (tyypin muunnos!)

/* jotta yllä oleva lause toimisi, niin merkkijonossa ei saa olla merkkien 11 kummallakaan puolella tyhjiä */

StringTokenizer st3 = new StringTokenizer("23+444-5","+-"); // sekä + että - ovat erottimia s = st3.nextToken(); // s="23" char ch = st3.nextToken().charAt(0); // ch='4' s = st3.nextToken(); // s="5" StringTokenizer st4 = new StringTokenizer("23+444-5","+-", true); // true saa aikaan sen, että myös erotinmerkki tulkitaan tokeniksi s = st4.nextToken(); // s="23" ch = st4.nextToken().charAt(0); // ch='+' s = st4.nextToken(); // s="444" } // main } // class Tokenit

(*** esimerkin loppu ***)

Luokassa String on myös kätevä metodi split, jolla kohdemerkkijono voidaan palastella merkkijonotaulukkoon. Tehokkuussyistä tätä metodia suositellaan käytettävän luokan StringTokenizer metodien sijasta. Lisäksi splitin käyttö on usein helpompaa. Tarkastellaan metodia esimerkin avulla, joten olkoon String st=”123,456,7”; Metodin split argumenttina on ns. säännöllinen lauseke (regular expression)7 String-tyyppisenä, joka määrää sen mistä kohdin palastelu tehdään:

• yksi merkki: esim. lause String[ ] s = st.split(”,”); pilkkoo st:n palasiin pilkkujen kohdilta, jolloin s[0]=”123”,

s[1]=”456”, s[2]=”7”

• useita peräkkäisiä merkkejä: esim. lauseen String[ ] s = st.split(”456”); jälkeen s[0]=”123,” ja s[1]=”,7”

• lista vaihtoehtoisista merkeistä, joiden kohdalta suoritetaan paloittelu. Ne annetaan hakasulkeiden sisällä peräkkäin: esim. lauseen String[ ] s = st.split(”[25]”); jälkeen s[0]=”1” ja s[1]=”3,4” ja s[2]=”6,7”.

• yksi tai useampi välilyönti peräkkäin: split(” +”) (plussan edessä on yksi välilyönti)

7 Ei vaadita yleisesti tällä kurssilla. Alla olevien esimerkkien tapaukset riittää osata.

Page 20: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

20

Seuraavaksi mennään hiukan teknisempään mutta käytännössä hyvinkin tarpeelliseen asiaan: desimaalilukujen muotoilu. Tätä ei kuitenkaan vaadita tentissä. Asiasta esitetään vain esimerkkejä.

Tulostuksen muotoilu ja DecimalFormat

Usein desimaaliluvuissa on tulostuksen yhteydessä liian paljon desimaaleja; meille riittäisi esimerkiksi vain kaksi desimaalia. Jakson AOP harjoitustehtävissä esiteltiin tulostuslause printf, joilla voidaan muotoilla (formatoida) desimaaliluku haluttuun muotoon. Esimerkiksi olkoon:

double d=1.236; System.out.printf("d=%8.2f", d);

Tämä saa aikaan tulostuksen d= ja sen jälkeen luku d tulostetaan 8 merkin levyiseen kenttään kahdella desimaalilla. Tässä voi kirjoittaa myös esim. %2.2f, jolloin desimaaliluvun eteen tulee vain yksi välilyönti ja tarkkuus on edelleen 2 desimaalia. Tässä siis tapahtuu automaattinen kentän laajennus, koska 2 on liian pieni luku koko kentän leveydeksi. Lisäksi jos luvussa on enemmän desimaaleja kuin mitä tulostetaan, niin muunnoksessa suoritetaan pyöristys (jos siis ensimmäinen poisjäänyt desimaali on >=5).

Joskus on tarvetta muodostaa desimaaliluvusta String-tyypin olio, joka vastaa luvun esitystä mutta vähemmillä desimaaleilla. Tällöin voimme käyttää luokkaa DecimalFormat, joka vaatii import-lauseen import java.text.DecimalFormat; Tämän luokan käyttö käy ilmi seuraavasta esimerkistä:

DecimalFormat f = new DecimalFormat("0.00"); // ja oliohan tämä formatoijakin on! double d=1.236; String st = ”d=”+f.format(d);

Tässä f.format(d) palauttaa (johtuen kielivalinnasta kuten Scannerin yhteydessäkin) String-tyyppisen arvon ”1,24”, joten jos haluaa desimaalipisteen pilkun tilalle, niin tulee kirjoittaa f.format(d).replace(’,’,’.’) eli korvataan pilkku pisteellä. Tässäkin tapahtuu automaattinen kentän laajennus, jos muotoilussa (edellä "0.00") on liian vähän nollia desimaalipisteen edessä muotoiltavaan lukuun nähden.

Merkkijonon ja tekstitiedoston läpikäynti käyttäen luokkaa Scanner

Luokkaa Scanner (ks. jakson AOP opiskeluoppaan lopussa oleva Liite) voi käyttää merkkijonon skannaukseen samaan tapaan kuin luokkaa StringTokenizer. Läpikäytäessä merkkijonoa, voi erotinmerkin valita itse. Tarkastellaan esimerkkinä merkkijonossa olevien lukujen läpikäyntiä. Olkoon s merkkijono, joka sisältää double-lukuja, jotka on erotettu #-merkeillä; esim. s=”1,5#2,89#3,13” (huomaa desimaalipilkku!). Nyt luomme luokan Scanner olion, jolle annetaan parametrina se merkkijono, joka käydään läpi. Koska erotinmerkkinä on nyt #, niin meidän tulee kertoa ko. oliolle, että oletuserottimen sijasta erottimena on #. Merkkijonossa ei saa olla kuitenkaan esim. ylimääräisiä välilyöntejä.

Page 21: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

21

public static void main (String[ ] args) {

String s="1,5#2,89#3,13"; int luettuja = 0; double summa = 0; Scanner scan = new Scanner(s); scan.useDelimiter("#"); while (scan.hasNext()) { luettuja++; int luku = scan.nextDouble(); summa = summa + luku; } // while if (luettuja > 0) { System.out.println("Lukujen keskiarvo on " + summa/luettuja); } else { System.out.println("Ei ole mitään laskettavaa."); }

} // main

Jos s:n erotinmerkkeinä olisikin välilyönnit, niin silloin ohjelmasta tulisi poistaa lause scan.useDelimiter("#"); Tällöin s:ssä voisi lukujen välissä olla myös enemmän kuin yksi välilyönti. Jos halutaan erotella merkkijonosta osia merkkijonoina (tyyppiä String), niin silloin tulee kirjoittaa String osajono = scan.next();

Jatketaan edellistä vielä niin, että luvut ovat tekstitiedostossa luvut.txt ja että tiedostossa voi olla useita rivejä, esim. tiedosto voi olla muotoa:

1#2#3 4#5#6

Alla on ohjelma, joka lukee luvut tiedostosta ja laskee niiden keskiarvon. Ohjelma lukee silmukassa tiedostoa rivi kerrallaan merkkijonomuuttujaan rivi. Jakson AOP opiskeluoppaan s. 103 käsiteltiin vastaava esimerkki, mutta tällöin tiedostoa luettiin luku kerrallaan eikä riveittäin ja lukujen erotinmerkkinä oli tavallinen ’white space’-merkki eikä # kuten tässä. Tiedostojen käsittely vaatii aina määreen ”throws IOException” metodin otsikkoriville. Tämä tarkoittaa sitä, että metodi voi nostaa ns. poikkeuksen virheellisen IO-toiminnan yhteydessä. Poikkeuksia tarkastellaan lähemmin myöhemmin. Tässä poikkeusta (virhettä) ei siepata siihen tarkoitetulla lauseella try…catch, vaan ohjelman annetaan ’kaatua’.

Page 22: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

22

import java.util.Scanner; import java.io.*; public class StringiScannerTiedostosta {

public static void main (String[ ] args) throws IOException {

Scanner näppäimistöScan = new Scanner(System.in); //luodaan näppäimistöskanneri System.out.println("Anna tiedoston nimi"); String tiedostonNimi = näppäimistöScan.nextLine(); // luetaan tiedoston nimi näppäimistöltä Scanner tiedostoScan = new Scanner(new File(tiedostonNimi)); // luodaan tiedostoskanneri int luettuja = 0; double summa = 0; while (tiedostoScan.hasNext()) { String rivi=tiedostoScan.nextLine(); // muuttujaan rivi asetetaan tiedoston seuraava rivi System.out.println("Luettu rivi on " + rivi); Scanner merkkijonoScan = new Scanner(rivi); // luodaan merkkijonoskanneri merkkijonoScan.useDelimiter("#"); while (merkkijonoScan.hasNext()) { luettuja++; int luku = merkkijonoScan.nextInt(); summa = summa + luku; } // while merkkijonoScan } // while tiedostoScan if (luettuja > 0) { System.out.println("Lukujen keskiarvo on " + summa/luettuja); } else { System.out.println("Ei ole mitään laskettavaa."); }

} // main

} // class

Anna tiedoston nimi luvut.txt Luettu rivi on 1#2#3 Luettu rivi on 4#5#6 Lukujen keskiarvo on 3.5 Press any key to continue . . .

Random

Luokkaa Random käsiteltiin jo jaksolla AOP, mutta palataan siihen vielä hetkeksi. Kertaa ensin jakson AOP opintomonisteesta luokkaa Random koskeva luku. Voidaksemme generoida satunnaisia lukuja, meidän tulee luoda luokan Random olio (satunnaislukuolio) ja soveltaa siihen luokan esiintymämetodeja nextInt, nextLong, nextFloat tai nextDouble, jotka palauttavat (seuraavan) satunnaisen int-, long-, float tai double-tyyppisen satunnaisluvun. Luokan Random olio luodaan joko lausekkeella Random(); tai Random(x); missä x on long-tyyppinen vakio tai muuttuja. Esimerkiksi

Random generaattori = new Random(); tai Random generaattori = new Random(x);

Page 23: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

23

Jos haluat ohjelman jokaisella suorituskerralla saman satunnaislukujen jonon8, tulee käyttää jälkimmäistä tapaa kiinteällä x:n arvolla.

Luokan Random metodi nextInt(n), missä n:n pitää olla positiivinen kokonaisluku, palauttaa satunnaisen kokonaisluvun väliltä 0...n-1. Jos halutaan asettaa muuttujaan k satunnainen kokonaisluku väliltä a…b (missä a ja b ovat kokonaislukuja ja a<b), niin silloin kirjoitetaan9:

Random sat = new Random(); int k = sat.nextInt(b-a+1)+a;

Esimerkiksi väliltä 1…39: int k = sat.nextInt(39)+1;

Liite 1 s. 93 ohjelma RollDice. Ohjelman tarkoituksena on simuloida nopan heittoa (voimme siis heittää noppaa ilman noppaa). Jokaisen heiton tuloksena on kokonaisluku väliltä 1…6. Ohjelmalla on kaksi käyttötapaa:

• jos sille annetaan argumenttina yksi kokonaisluku, ohjelma tulostaa kyseisen määrän nopanheiton tuloksia;

• annettaessa ohjelmalle argumenttina 2 kokonaislukua, tarkoittaa jälkimmäinen generaattorin siemenlukua, jonka perusteella se laskee ensimmäisen satunaisluvun arvon.

Esimerkiksi käskyllä Rolldice 100 ohjelma tulostaa 100:n nopanheiton tuloksen. Nämä kaksi tapausta voidaan tutkia ehtolausekkeella args.length==2. Ohjelmassa esitellään satunnaislukugeneraattoriolio noppa, jolloin lausekkeella noppa.nextInt() saadaan selville seuraava satunnainen kokonaisluku. Kun se muutetaan ensin positiiviseksi (abs), jaetaan se 6:lla, niin jakolaskun jakojäännös on satunnainen kokonaisluku väliltä 0…5. Kun tähän lisätään 1, saadaan haluttu arvo. Kaikki tämä lasketaan lausekkeella Math.abs(noppa.nextInt()) % 6 + 1. Ohjelma tulostaa 35 tulosta peräkkäin niin, että kahden luvun välissä on aina välilyönti. Sen jälkeen suoritetaan rivinvaihto. Tämä saadaan aikaan ottamalla käyttöön laskuri ja kun se saa arvon 35, tulostetaan rivinvaihto.

Luokakirjastojen toteutukset

Kirjastoluokkien toteutukset (koodit) löytyvät koneeltasi jdk-hakemistosta ja ne ovat tiedostossa src.zip ja löytyvät myös esim. osoitteesta http://sourceforge.net/projects/jdk7src/ Niitä on hyödyllistä tutkia, joskin niiden sisältö ei kuulu kurssivaatimuksiin. Tässä on hyvä huomata, että esim. luokan String pohjalla oleva tallennusrakenne on merkkitaulukko char[] (ks. jaksolta TTP I ohjelmointikielten tallennusrakenteet). Samoin ensi viikolla käsiteltävä dynaaminen lista Vector toteutetaan staattisen taulukon avulla, vaikka luokan Vector käyttäjä ei sitä mistään huomaakkaan.

8 Tämä saattaa olla tarpeellista esim. testausvaiheessa.

9 Tämä oli jakson AOP viimeisen viikon harjoitustehtävä

Page 24: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

24

Harjoitustehtävät

Tutustu kirjastoluokkiin Math, StringBuffer, StringBuilder, StringTokenizer ja Random. Katso koneesi Java-kansion (hakemiston) alta kansioista …\docs\api\java\lang ja …\docs\api\java\util. Kaksoisklikkaa vastaavaa html-tiedostoa. Tutki konstruktoreita ja metodeja ja huomaat, että sieltä löytyy paljon enemmän metodeja kuin Liitteen 1 taulukoista.

http://docs.oracle.com/javase/8/docs/api/

Kaikkien viikkojen tehtävien yhteydessä tulee esittää aina metodien ulkoinen kuvaus ja mahdollinen alkuehto.

Kirjoita myös pääohjelma (main-metodi), jolla voit testata metodiasi. Jos lähetät ratkaisusi, liitä mukaan kuvaruutukaappaus, joka todentaa sen, että metodisi/ohjelmasi toimii.

1. Kirjoita metodi, joka palauttaa tiedon siitä kuinka monta kertaa parametrina annettu merkki (tyyppiä char) esiintyy parametrina annetussa merkkijonossa (tyyppiä StringBuilder). Lisäkysymys: Jos muuttuja st on tyyppiä String, niin miten saat siitä StringBuilder-tyyppisen muuttujan stB? Entä päinvastoin eli StringBuilder -> String? Ovatko luokkien StringBuilder ja StringBuffer alkiot sijoitusyhteensopivia?

2. – 310. Pieni käyttäjän yhteenlaskutaitoa testaava ohjelma: Kirjoita ohjelma, joka 1) generoi kaksi satunnaista kokonaislukua väliltä 1…100, 2) tulostaa ne ruudulle ja kysyy mikä on näiden summa, 3) lukee käyttäjän näpyttelemän vastauksen ja 4) antaa palautteen tuloksen oikeellisuudesta. Rakenna tämän ympärille toistorakenne, jossa kysytään jokaisen laskutoimituksen jälkeen vastaus kysymykseen ”Haluatko jatkaa?”. Lopuksi ohjelma tulostaa kuinka monta prosenttia tuloksista oli oikein. Tee ohjelmasta kaksi versiota: toinen käyttää lukemiseen luokkaa OmaIO ja toinen luokkaa Scanner. Ohje: Tässä tarvitaan luokkaa Random. Tässä riittää pelkkä main eli sinun ei tarvitse välttämättä modularisoida ohjelmaa tekemällä muita metodeja. Luokan Scanner metodeista kannattaa valita nyt sekä merkin että luvun lukemiseen metodi nextLine(). Miksi? Testaa ohjelmasi tietokoneella. Jos lähetät ratkaisun, mukana tulee olla myös kuvaruutunäyttö ohjelman suorituksesta.

4. -5. Kirjoita metodi, jolle annetaan parametrina merkkijono (String), jossa on kahden positiivisen (etumerkittömän) kokonaisluvun yhteen tai vähennyslasku ja joka palauttaa lausekkeen arvon int-tyyppisenä. Tässä siis voi olla joko yhteenlasku tai vähennyslasku ja tämä tulee ottaa huomioon metodissa. Esim. Parametri: ”21+53”, tulos: 74. Tee tästä kaksi eri ratkaisua: Toinen käyttää luokkaa StringTokenizer (Tehtävä 4) ja toinen luokan String metodia split (Tehtävä 5). Testaa ohjelmasi tietokoneella. Jos lähetät ratkaisun, mukana tulee olla myös kuvaruutunäytöt ohjelmien suorituksesta.

10 Kahden tehtävän arvoisista tehtävistä voit merkitä toisen (esim. tehtävän 2), jos olet mielestäsi tehnyt n. puolet koko tehtävästä. Menettele vastaavasti kolmen tehtävän arvoisten tehtävien kohdalla.

Page 25: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

25

VIIKKO 4

Kirjastoluokkia osa II: Integer (ja muut kuori- eli kääreluokat). Dynaamiset geneeriset listat: luokat Vector, ArrayList ja LinkedList. Automaattiset tyypinmuunnokset kuoriluokkien ja vastaavien perustyyppien välillä. for-each –rakenne ja iteraattorit. Luokat Arrays ja Collections.

Liite 1 sisältää tietotyyppien Integer ja Vector tärkeimpien metodien kuvaukset. Lue materiaalit siinä järjestyksessä kuin alla on neuvottu.

Jokaista perustyyppiä int, double, char … kohti on olemassa vastaava oliotyyppi ns. kuoriluokka, joiden käsittely on hyvin samanlaista ja siksi seuraavassa käsitellään vain perustyyppiä int vastaavaa oliotyyppiä Integer. Nämä tyypit ovat erittäin tärkeitä, koska tällä viikolla käsiteltävien luokkien alkioiden tulee olla aina oliotyyppisiä, esim. Integer-tyyppisiä.

Integer

Jotkut kirjastoluokat (kuten tällä viikolla esiteltävät dynaamiset listat) vaativat, että sen alkiot ovat aina oliotyyppisiä. Tällaiseen rakenteeseen ei siis voi tallentaa perustyyppisiä (esim. int) arvoja. Sen vuoksi tarvitsemme ns. kuori- eli kääreluokkia, joiden alkiot ovat oliotyyppisiä, mutta niiden sisältö on kuitenkin luku. Tällaisia kuoriluokkia ovat kirjastoluokat Integer, Long, Double, Float ja Short. Näistä puhuttiin jo jaksolla AOP. Nämä ovat mutatoitumattomia kuten String-oliot. Näin ollen niiden sisältö ei voi muuttaa ja näin ollen jos sisältöä halutaan muuttaa, tulee luoda uusi olio. Jaksolla AOP selitettiin jo perus- ja viittaustyyppisten muuttujien eroa. Seuraavassa on kertauksenomaisesti hyvin yksinkertainen esimerkki tyyppien int ja Integer erosta.

Lisäesimerkki. Tarkastellaan kertauksena yksinkertaisia perustyyppiä ja viittaustyyppiä olevien muuttujien asetuslauseita. Tarkastellaan seuraavia lauseita (vasemmalla on esittely ja arvojen antaminen erikseen ja oikealla puolella esitellään ja annetaan alkuarvot samassa lauseessa, mutta kummankin aikaansaama toiminta on sama).

int x,y; Integer xv, yv; x=2; y=5; xv= new Integer(2), yv= new Integer(5); y=x; yv=xv;

int x=2, y=5; Integer xv = new Integer(2), Integer yv= new Integer(5); y=x; yv=xv;

Page 26: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

26

Tarkastellaan vasemman puoleista ratkaisua, jossa on enemmän 'välivaiheita'. Muuttujat x ja y tarkoittavat 'tavallisia muistipaikkoja', joihin voidaan tallentaa int-tyyppinen kokonaisluku. Sen sijaan muuttujat xv ja yv voivat saada arvokseen viittauksen (osoitteen) muistipaikkaan, johon voidaan tallentaa tyyppiä int oleva luku eli ne voivat saada arvokseen viittauksen luokan Integer esiintymään eli olioon.

Kahden ensimmäisen rivin (jotka ovat siis vain esittelyjä) jälkeen muistin tilaa voidaan kuvata seuraavasti:

x y xv yv

? ?

? ?

Huomaa, että oliotyyppisen muuttujan esittelyn jälkeen ei vielä ole olemassa viittausta ja muuttujalla ei siis ole arvoa (sen arvo ei siis ole null, kuten jo jaksolla AOP todettiin). Oliot pitää luoda erikseen new-lausekkeella.

Tarkastellaan seuraavaksi lausetta xv= new Integer(2). Lauseke new Integer(2) saa aikaan sen, että luodaan (new) uusi Integer-luokan ilmentymä eli olio, joka saa arvokseen kokonaisluvun 2. Tämä tarkoittaa sitä, että luodaan muistialue, johon 'mahtuu' int-tyyppinen arvo ja siihen tallennetaan luku 2. Lausekkeen new Integer(2) arvo on viittaus tähän olioon, ja muuttuja xv saa siis arvokseen tämän viittauksen (xv sisältää siis olion osoitteen). Tämä kuvataan alla (ja myöhemminkin) piirtämällä nuoli laatikon xv sisältä laatikkoon, jossa on luku 2. Yllä olevien viiden lauseen jälkeen muistia voidaan kuvata seuraavasti:

x y xv yv

2 5

2 5

Tarkastellaan seuraavaksi asetuslauseita y=x ja yv=xv. Näiden lauseiden suorituksen jälkeistä muistin tilaa voidaan havainnollistaa alla olevalla kuvalla. Ensimmäisessä asetuslauseessa y=x muuttuja (muistipaikka) y saa uuden arvon, joka on sama kun x:n arvo. Toisessa tapauksessa itse olioiden tietosisällöt eivät muutu, vaan ainoastaan muuttuja yv (joka on viittaus tiettyyn olioon) saa uuden arvon eli tämän jälkeen yv viittaa eri olioon kuin aiemmin. Alla olevassa kuvassa olioon, jonka tietosisältö on 5, ei ole viittausta. Näin ollen siihen ei päästä enää 'käsiksi' vaikkakin se vie muistitilaa. Automaattinen roskienkerääjä kuitenkin huomaa tämän ja vapauttaa tämän muistitilan ennen pitkää.

x y xv yv

2 2

2 5

Page 27: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

27

Edellisen esimerkin opetus asetuslauseesta:

• Kun perustyyppiä olevan muuttujan arvoa muutetaan, sen edustaman muistipaikan sisältö muuttuu, ja vanha arvo katoaa.

• Kun viittaustyyppisen muuttujan arvoa muutetaan, sillä ei ole vaikutusta viitattujen olioiden sisältöön. Yllä luvun 5 sisältävään muistipaikkaan ei enää päästä käsiksi, jolloin siitä tulee roskaa. Systeemi kuitenkin vapauttaa ko. tilan automaattisesti (tähän palataan myöhemmin).

Lue kirjastoluokkaa Integer koskeva teksti Liitteen 1 s. 91-92 ja jatka sen jälkeen alla olevasta.

Tyypinmuunnokset sekä autoboxing ja unboxing

Seuraavassa käsittelemme vain luokkaa Integer, mutta muiden kuoriluokkien käyttö on vastaavanlaista. Muunnokset Integer-olioiden ja perustyyppisten sekä String-arvojen välillä ja päinvastoin ovat tärkeitä:

• String-tyyppisestä arvosta (esim. vakio, muuttuja tai lauseke) st ja int-tyyppisestä arvosta x voidaan luoda Integer luokan olio konstruktorilla Integer: esim. Integer int_olio1 = new Integer(st); Integer int_olio2 = new Integer(x); tai Integer int_olio3 = (Integer) x;

• Integer-luokan oliosta x saadaan double-tyyppinen arvo luokan Integer metodeilla: x.doubleValue();

tai int-tyyppinen arvo: x.intValue();

tai String-tyyppinen olio: x.toString();

• Java 5.0:sta lähtien Integer-olioiden ja int-tyypin välillä on automaattinen tyypin muunnos (autoboxing/autounboxing): ks. alla olevassa esimerkissä oleva Huom.

Tässä on hyvä huomata, että muuttujan tyyppi ei voi muuttua, mutta sen arvolle voidaan tehdä tyypin muunnos. Esim. jos x on int-muuttuja, niin lauseiden Integer int_olio2 = new Integer(x); ja

Integer int_olio3 = (Integer) x; suorituksen jälkeen x on edelleen int-muuttuja, mutta esim. lausekkeen (Integer) x tyyppi on Integer.

Page 28: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

28

Esimerkki. Seuraava ohjelma demonstroi yllä olevia muunnoksia.

public class Integer_koe { public static void main(String[] args) { Integer x_olio = new Integer(22); // luodaan Integer luokan olio tyypiä int-olevasta alkiosta Integer y_olio = new Integer("12"); // luodaan Integer luokan olio tyypiä String-olevasta oliosta int x = x_olio.intValue(); // x=22 String s = y_olio.toString(); // s=”12” boolean b = x_olio.equals(y_olio); // b=false int y = Integer.parseInt(s); // y=12 } } // class Integer_koe

x_olio

y_olio

22

12

Metodin parseInt kuvaus luokassa Integer on

static int parseInt(String s) Parses the string argument as a signed decimal integer.

Näin ollen se on luokkametodi ja metodin kutsua ei voi kohdistaa olioon, vaan se kirjoitetaan muodossa Integer.parseInt(s);

Huom. Java 5.0:sta lähtien voimme kirjoittaa suoraan: Integer x_olio = 22; (vastaa siis lausetta Integer x_olio = new Integer(22);) int x = x_olio; (vastaa siis lausetta int x = x_olio.intValue(); )

joissa kummassakin tapahtuu automaattinen tyypinmuunnos. Ensimmäisessä lauseessa tapahtuu uuden Integer-olion luonti int-luvusta 22 ja kyseinen viittaus asetetaan muuttujan x_olio arvoksi, kuten yllä olevassa kuvassa havainnollistetaan (ns. autoboxing). Toisessa lauseessa taas, jossa Integer-tyypin olion x_olio sisältämästä arvosta muodostetaan int-arvo, joka tallennetaan muuttujaan x (ns. unboxing). Vastaavat tyypinmuunnokset toimivat kaikkien perustyyppien ja niitä vastaavien kuoriluokkien välillä. Nämä automaattiset tyypinmuunnokset helpottavat ohjelmointia, mutta ne ovat sikäli vaarallisia, koska tällöin saattaa hämärtyä se, mitä lauseissa todella tapahtuu.

Lisäksi kuoriluokkien mukaisilla oliolla voidaan laskea (+,-,*,/) normaaliin tapaan, jolloin viimeistään hämärtyy kuva siitä, että ne todellisuudessa ovat olioita; esim. lausekkeen x_olio+y_olio arvo on 34 ja voimme sijoittaa ja tämän joko int-tyyppiseen tai Integer-tyyppiseen muuttujaan! Tässä kuitenkin ennen yhteenlaskua systeemi muuttaa yhteenlaskettavat int-tyyppisiksi.

Page 29: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

29

On myös hyvä huomata11, että vaikka double-tyyppiseen muuttujaan voidaan asettaa int-tyyppinen arvo, niin Double-tyyppiseen muuttujaan ei voi asettaa int- tyyppistä arvoa eli jos meillä on Double x, int y, niin lause x=y on kielletty, joten meidän tulee tehdä eksplisiittisen tyypinmuunnos x=(double)y. Samoin Double tyyppiseen muuttujaan ei voi asettaa Integer tyyppistä arvoa eikä tietenkään päinvastoin. Näin ollen edellä mainitut autoboxing ja unboxing tyypinmuunnokset toimivat pääasiassa perustyypin ja sitä vastavan kuoriluokan (esim. double vs. Double) välillä (tyyppejä ei kannata sekoittaa).

Mitä tapahtuu, jos yllä suoritetaan lause:

x_olio=y_olio;

(*** esimerkin loppu ***)

Tyypit int ja Integer ovat siis aivan eri tyypit. Ne ovat kuitenkin sijoitusyhteensopiva automaattisen tyypinmuunnoksen ansiosta. Sama koskee tyyppejä double ja Double12.

Luokissa Integer ja Long on myös (joskus hyödylliset) luokkamuuttujat MIN_VALUE ja MAX_VALUE, joilla saa käyttöön pienimmän ja suurimman perustyyppisen arvon; esim. voimme kirjoittaa ohjelmassa: Integer.MIN_VALUE (jonka arvo on tyyppiä int) ja Long.MAX_VALUE (tyyppiä long).

Johdanto dynaamisiin listoihin (oliokokoelmat) ja geneerisyyteen

Jaksolla AOP käsiteltiin taulukoita, joista erityisesti 1-ulotteinen taulukko on tärkeä. Usein 1-ulotteista taulukkoa kutsutaan vektoriksi (array), mutta Javan yhteydessä puhumme taulukosta, koska Javassa on luokka Vector. Esimerkiksi int[10] tarkoittaa 1-ulotteista taulukkoa, johon voidaan tallettaa 10 int-tyyppistä lukua. Taulukoiden yhteydessä meillä on seuraava ongelma: taulukot ovat siinä mielessä staattisia, että kun taulukon koko on annettu luonnin yhteydessä (esim. int[] taulu=new int[n]), niin taulukon kokoa ei voi enää muuttaa. Vaikka luonnin yhteydessä voidaankin koko ilmoittaa muuttujan avulla (edellä n), niin rakenne on kiinteän mittainen heti kun se on luotu. Näin ollen dynaamisen listan (voidaan esim. poistaa ja lisätä alkioita listaan mielivaltaiseen kohtaan) toteutus taulukkona on hankalaa. Jaksolla AOP tähän esitettiin ratkaisu (AOP opiskeluopas s. 83 metodi lisaa_loppuun), jossa luotiin uusi taulukko-olio, mutta menetelmä ei ole joustava eikä tehokas.

JIT-kurssilla puhuttiin ADT:sta lista ja sen eri implementaatioista. Tällä viikolla esitellään kolme kirjastoluokkaa, jotka toteuttavat dynaamisen listan:

Luokat Vector, ArrayList ja LinkedList ovat tyyppejä. Näille on ominaista se, että voimme lisätä, korvata tai poistaa alkioita listasta mielivaltaisesta kohdasta. Näin ollen näitä rakenteita voidaan käsitellä dynaamisesti.

11 Tässä kappaleessa mainitut tavallaan sekavat ja tekniset ominaisuudet pätevät Javan versiossa 1.7, mutta saattavat muuttua tulevissa versioissa.

12 Vahingossa isolla d:llä kirjoitettu Double on siis eri asia kuin double.

Page 30: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

30

Huom. Luokat Vector ja ArrayList on implementoitu niin, että pohjalla oleva tallennusrakenne on tavallinen staattinen taulukko, joten ne eivät ole pohjimmiltaan dynaamisia rakenteita, joskin näiden luokkien metodit mahdollistavat rakenteen dynaamiselta vaikuttavan käsittelytavan. Sen sijaan luokan LinkedList pohjalla oleva tallennusrakenne on aidosti dynaaminen lista, koska se on toteutettu käyttäen linkitettyä rakennetta (ks. jakson TTP I opintomoniste). Luokan käyttäjän ei tarvitse tietää mikä on luokan perusteena oleva tallennusrakenne, ellei tarvitse miettiä metodien toimintanopeutta. Käyttäjän ei tarvitse myöskään miettiä sitä, onko rakenteessa tarpeeksi tilaa, koska systeemi varaa tilaa lisää aina tarpeen mukaan (tietysti käytännössä joku raja tälle on, mutta sen huomaa sitten kun systeemi kaatuu). Jos teemme paljon lisäyksiä listan alkuun tai keskelle, niin Vector ja ArrayList eivät ole suositeltavia, koska nämä operaatiot eivät ole tehokkaita. Tällöin on parempi käyttää luokkaa LinkedList. Luokan valinta tapahtuu usein sen mukaan kuinka sopiva metodivalikoima luokassa on (ks. tämän viikon 1. harjoitustehtävä). Luokka Vector vastaa luokkaa ArrayList, mutta luokka Vector on näistä vanhempi ja synkronoitu. Jos teemme säikeitä (ei selitetä tällä kurssilla), niin on parempi käyttää luokkaa ArrayList, kuten luokan Vector kuvauksen yhteydessä luokkakirjastossa sanotaan.

Luokkien käyttö vaatii import-lauseen: import java.util.* (tai *:n tilalla tarvittavan luokan nimi) ja niiden täydellinen kuvaus löytyy kansiosta …\docs\api\java\util, jonka tutkiminen on yksi tämän viikon harjoitustehtävistä.

Näiden luokkien huono puoli on se, että koska ne alun perin on pyritty tekemään niin yleisiksi, että listassa voi olla minkä tyyppisiä alkiota tahansa (listan alkiot voivat olla keskenään erityyppisiä!), niin niiden käsittely ei ole aivan yksinkertaista. Nimittäin näiden listojen alkiot ovat tyyppiä Object, jolloin ohjelmoija joutuu tekemään alkioiden tyyppiin pakottamisia (tyyppipakotus, type cast) eli kertoa mihin oliotyyppiin kyseinen arvo ”kastetaan”. Koska Object on kaikkien oliotyyppien ylityyppi, niin listaan voidaan siis tallentaa minkä tyyppisen olion arvo tahansa (ns. polymorfismi, jota käsitellään s. 73). Yleensä kuitenkin listaan tallennetaan samantyyppisiä alkioita ja kun viittaamme tallennettuun alkioon, tulee siinä yhteydessä ilmaista alkion tyyppi. Java 5.0:sta lähtien tähän on tullut kuitenkin oleellinen laajennus: luokat13 Vector, ArrayList ja LinkedList ovat geneerisiä eli voimme määritellä listan, esim. Vector<Integer>, jolloin listan alkiotyyppi ilmoitetaan taulukon esittelyn yhteydessä aivan kuten staattisten taulukoidenkin yhteydessä; esim. Integer[ ]. Tällöin listan kaikki alkiot ovat samaa oliotyyppiä. Geneerisyys tarkoittaa käännösaikaista tyyppiparametrisointia ja niinpä esim. ArrayList:in määrittely luokkakirjastossa on ArrayList<E>, missä (muodollinen) tyyppiparametri E edustaa jotain oliotyyppiä, jonka me annamme ohjelmassa (todellinen parametri); esim. ArrayList<Integer>. Voimme määritellä myös itse geneerisiä luokkia, jotka sisältävät tyyppiparametrin <E> luokan otsikkorivillä (tätä ei käsitellä tällä kurssilla).

Siis: Luokkien Vector, ArrayList ja LinkedList mukaisten listojen komponentit ovat aina olioita (tarkemmin: viittauksia olioihin). Huomaa, että myös tavallisen staattisen taulukon komponentit voivat olla olioita.

Kun lista esitellään, niin aina kannattaa antaa listan alkiotyypin esittelyn yhteydessä; esim. Vector<Integer>. Seuraavassa esimerkissä listan alkiotyyppi on Integer. Huomaa kuitenkin, että listassa voi olla millaisia olioita tahansa. Luentopäivällä tarkasteltavassa ohjelmassa tallennamme dynaamiseen listaan Tili-olioita: ArrayList<Tili>.

13 Itse asiassa kaikki kokoelmaluokat, joihin nämäkin luokat kuuluvat, ovat geneerisiä.

Page 31: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

31

Lue Liitteen 1 s. 138, jossa esitellään luokan Vector tärkeimmät metodit. Tarkempi kuvaus on luokkakirjastossa.

Java 5.0:sta lähtien: Geneeriset luokat Vector <E>, ArrayList<E> ja LinkedList<E>

Java 5.0:sta lähtien voimme käyttää geneerisiä tyyppejä Vector<E> tai ArrayList<E> tai LinkedList<E>. Näin ollen ohjelmakoodissa voidaan listan esittelyn yhteydessä antaa listan alkiotyyppi (element type) E, joka voi olla mikä tahansa oliotyyppi (esim. Vector<Integer>), mutta ei perustyyppinen (esim. Vector<int> on väärin!). Näin kannattaa menetellä jos listan alkiot ovat samaa tyyppiä, kuten yleensä käytännössä on.

Seuraavassa käsitellään pääasiassa vain luokkaa Vector<E>, mutta esitetyt asiat pätevät myös muille geneerisille listoille.

Voimme määritellä listan, jonka komponenttityyppi määritellään jo listan luonnin yhteydessä: esim. voimme kirjoittaa

Vector<Integer> lista = new Vector<Integer>(); // tässä siis kirjoitetaan myös merkit < ja >14

Tässä luodaan lista, jossa ei ole yhtään alkiota eli lista.size() on nolla. Huomaa, että pelkän esittelyn (esim. Vector<Integer> v; ) jälkeen muuttuja on alustamaton eli sillä ei ole arvoa, ei edes arvoa null. Tietyissä tilanteissa muuttuja alustetaan arvoksi null, joka tarkoittaa sitä, että muuttujalla on arvo, mutta tämä arvo ei sisällä viittausta olioon. Edellä muuttuja lista sisältää viittauksen tyhjään listaan eli siis sen arvo ei ole null.

Kuten edellä todettiin, niin luokka Vector ja ArrayList on toteutettu staattisella taulukolla, joskin käyttäjän ei tarvitse sitä tietää. Tällöin esim. Vector-olion luonnin yhteydessä varataan tietyn kokoinen taulukko (initialCapacity). Kun sen tila ei riitä, niin luodaan uusi pidempi (kasvu: capacityIncrement) taulukko ja kopioidaan vanhan taulukon sisältö siihen käyttäen luokan System nopeata metodia arraycopy. Kopiointi on nopeata, koska taulukon komponentit sisältävät vain viittauksia (osoitteita), koska listojen Vector ja ArrayList komponentit ovat olioita. Halutessaan tämän alkukoon ja koon lisäyksen voi antaa konstruktorille parametreina15:

Vector(int initialCapacity, int capacityIncrement) : Constructs an empty vector with the specified initial capacity and capacity increment.

Esimerkki. Tämä on pitkä ja tärkeä esimerkki. Tarkastellaan ohjelmaa, joka lukee int-lukuja listaan ja määrää listan suurimman alkion arvo. Lukujen loppuminen ilmaistaan syöttämällä merkkijono, joka ei ole int-luvun esitys. Koska luettavien lukujen lukumäärää ei tiedetä etukäteen ja käytimme kiinteän mittaisia taulukoita, niin tällöin jouduimme käyttämään tarpeeksi

14 Yleensä olemme käyttäneet merkkipareja < ja > sekä selittävää tekstiä ilmaisemaan asiaa mitä ko. kohtaan tulee kirjoittaa; esim. <muuttuja>. Kuitenkin tässä (ja vain tässä missä käsitellään geneerisyyttä) merkkejä < ja > ei käytetä em. merkityksessä, vaan ne kuuluvat syntaksiin ja ne tulee kirjoittaa ohjelmakoodiin.

15 Useimmiten kuitenkin riittää käyttää systeemioletuksia eli käytetään parametritonta konstruktoria eikä mietitä sitä, mikä on pohjalla oleva implementointi.

Page 32: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

32

suurta apulistaa, johon luvut ensin luettiin. Ratkaisu on kankea. Näin ollen on parempi käyttää dynaamista listaa, jollainen on Javassa implementoitu mm. luokalla Vector. Hyödynnämme tässä geneerisyyttä ja käytämme listaa Vector<Integer>.

Aluksi lista on tyhjä ja alkioita lisätään listaan käyttäen luokan Vector metodia addElement. Huomaa, että jakson AOP opiskeluoppaan s. 83 teimme metodin lisaa_loppuun, joka lisäsi int-taulukon v loppuun int-luvun x (metodi luo uuden taulukon). Tämä oli staattinen metodi (static), jonka kutsua ei siis kohdistettu taulukko-olioon, vaan kyseinen taulukko on metodin parametrina. Sen sijaan addElement on luokan Vector esiintymämetodi (sen määrittelyssä ei ole sanaa static, joka nähdään ko. metodin kuvauksesta), joten sen kutsu kohdistetaan luokan Vector esiintymään eli olioon.

Metodissa main kutsutaan parametritonta metodia luelista, joka palauttaa Vector-tyyppisen olion. Metodissa luelista luodaan ensin Vector-tyyppinen olio v. Sen koko on 0 (siis v.size() on 0), koska listaan ei ole lisätty vielä yhtään alkiota. Ensin luomme luokan Integer sellaisen esiintymän, jonka tietosisältö on sama kuin luetun int-tyyppisen muuttujan luku arvo eli kirjoitamme: new Integer(luku), joka palauttaa viittauksen kyseiseen olioon. Tämä olio saadaan lisättyä listan v loppuun luokan Vector metodilla addElement, jonka kuvaus on:

void addElement(E obj) Adds the specified component to the end of this vector, increasing its size by one.

Metodin palautustyyppi on void ja se on siis proseduuri. E tarkoittaa kuvauksessa geneeristä tyyppiä, jonka käyttäjä antaa rakenteen esittelyn yhteydessä (tässä Integer). Lisäys v:n loppuun voidaan tehdä komennolla v.addElement(new Integer(luku));

Kun syötteeksi annetaan merkkijono, joka ei ole int-luvun esitys, niin lauseke näppis.hasNextInt() saa arvon false ja lukusilmukan suoritus lopetetaan. Lopuksi metodi luelista() palauttaa listan v, jossa on luetut luvut.

Metodi suurin palauttaa listan v suurimman arvon int-tyyppisenä. Metodissa käydään lista v läpi komponenteittain ja listan i:s alkio saadaan luokan Vector metodilla get, jonka toiminta on sama kuin metodin elementAt, joka kuvataan liitteen 1 s. 138. Näin ollen komento v.get(i) palauttaa listan v positiossa i olevan alkion (tai siis oikeastaan viittauksen siihen koska alkiot ovat olioita). Silmukassa i käy läpi arvot 1,…,v.size()-1. Lauseke v.get(i) on tyyppiä Integer ja sen vastine int-tyyppisenä saadaan luokan Integer metodilla intValue eli lauseke v.get(i).intValue() on tyyppiä int. Tässä voidaan kirjoittaa myös sulut: (v.get(i)).intValue() mutta se ei ole välttämätöntä, koska lauseke jäsennetään vasemmalta oikealle.

Alla on koko luokka.

Page 33: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

33

import java.util.Vector; import java.util.Scanner; public class Suurin_Vector_Gen { public static void main (String[] args) { Vector<Integer> lista = luelista(); System.out.println("Suurin on " + suurin(lista)); } // main

/** * Metodi lukee näppimistöltä kokonaislukuja listaan, joka palautetaan. * Kun halutaan lopettaa, annetaan jokin kirjain * (tai mitä tahansa joka ei ole kokonaisluvun esitys). */

public static Vector<Integer> luelista() { Vector<Integer> v = new Vector<Integer>(); // luodaan tyhjä lista eli v.size()==0 Scanner näppis = new Scanner(System.in); System.out.println("Anna luku"); while (näppis.hasNextInt()) { int luku = näppis.nextInt(); v.addElement(new Integer(luku)); // lisäys listan v loppuun System.out.println("Anna seuraava luku (lopuksi x)"); } // while näppis.nextLine(); // luetaan pois syötetty lopetusmerkki x ja enter return v; } // luelista /** * Funktio palauttaa listan v suurimman alkion arvon. * Alkuehto: lista ei saa olla tyhjä */ public static int suurin(Vector<Integer> v) { Integer obj = v.get(0); int toistaiseksi_suurin = obj.intValue(); // tässä vaiheessa suurin on positiossa 0 oleva olio int apu; for (int i=1; i<v.size(); i++) { apu = v.get(i).intValue(); if ( apu> toistaiseksi_suurin) { toistaiseksi_suurin = apu; } // if } // for return toistaiseksi_suurin; } // suurin } // luokan loppu

Oletetaan, että käyttäjä syöttää 12, 21, 9 ja lopuksi esim. x. Muistia voidaan havainnollistaa seuraavasti: (a) tilanne, kun 12 on lisätty v:n loppuun, (b) 21 ja (c) 9 on lisätty v:n loppuun.

Page 34: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

34

vv

v

(a) (c)(b)

12

1212

21

921

Tässä tulee huomata, että itse asiassa v sisältää vain viittauksen listaan (osoite listan alkuun), jonka komponentit taas ovat viittauksia listan komponenttityyppisiin alkioihin.

Huom. Jos haluamme lisätä alkion listaan, niin ensin meidän tulee luoda lisättävä olio (yllä lauseke new Integer(luku)), joka annetaan parametrina lisäyksen suorittavalle metodille. Huomaa, että v:hen lisätäänkin viittaus olioon eikä itse oliota. Alkion poisto tarkoittaa sitä, että rakenteesta poistetaan viittaus. Näin ollen itse olio jää elämään. Jos mikään muu muuttuja ei viittaa tähän olioon, niin automaattinen roskienkerääjä vapauttaa ko. olion varaaman tilan muistista aikanaan. Esimerkiksi jos (c)-kuvan listaan kohdistetaan komento v.remove(0), niin sen jälkeen v:ssä on vain 2 viittausta, mutta olio, jonka tietosisältö on 12 jää elämään. Tätä voidaan havainnollistaa kirjoittamalla seuraava main-metodi:

public static void main (String[] args)

{ Vector<Integer> v = luelista(); // luetaan luvut näppäimistöltä Integer obj = v.get(0); v.remove(0); System.out.println("Suurin on " + suurin(lista)); }

main-metodin suorituksen jälkeen lukua 12 edustava Integer-olio ei ole enää listassa v, mutta se on olemassa ja muuttuja obj viittaa siihen. Koska lista on dynaaminen, niin silloin lista todella lyhenee eli listan positiossa 0 on lukua 21 edustava olio ja positiossa 1 on lukua 9 edustava olio.

Yllä voidaan syöte antaa yhdellä kertaa, jolloin luvut tulee erottaa toisistaan white space merkeillä (välilyönti, tabulaattori tai rivinvaihto). Esim. syötteenä voidaan antaa 2 3 1 11 x

Tällöin tulee tietenkin ottaa silmukassa oleva hopute System.out.println("Anna seuraava luku

(lopuksi x)"); ottaa pois.

(*** esimerkin loppu ***)

Esimerkki. Luokan Vector toteutus ei ole tehokas ja näin ollen monet luokan metodit toimivat hitaasti. Sen vuoksi Javan versioista 1.2 lähtien mukana on luokka ArrayList, joka on implementoitu paremmin. Ulkoisesti ajatellen luokat eivät eroa toisistaan paljoakaan, joskin metodien nimet ovat - ikävä kyllä - erilaisia. Huomaa kuitenkin, että muunnoksen Vector → ArrayList voi suorittaa usein pelkällä tekstuaalisella korvauksella editorissa. Seuraavassa on yllä oleva ohjelma käyttäen luokkaa ArrayList:

Page 35: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

35

import java.util.ArrayList; import java.util.Scanner; public class Suurin_AL_Gen { public static void main (String[] args) { ArrayList<Integer> lista = luelista(); System.out.println("Suurin on " + suurin(lista)); } // main

/** * Metodi lukee näppimistöltä kokonaislukuja ArrayList-rakenteeseen, joka palautetaan. * Kun halutaan lopettaa, annetaan x (tai miitä tahansa joka ei ole kokonaisluvun esitys). */ public static ArrayList<Integer> luelista() { ArrayList<Integer> v = new ArrayList<Integer> (); Scanner näppis = new Scanner(System.in); System.out.println("Anna luku"); while (näppis.hasNextInt()) { int luku = näppis.nextInt(); v.add(new Integer(luku)); // lisäys listan v loppuun. System.out.println("Anna seuraava luku (lopuksi x)"); } // while näppis.nextLine(); // luetaan pois syötetty lopetusmerkki x ja enter return v; } // luelista /** * Funktio palauttaa listan v suurimman alkion arvon. * Alkuehto: lista ei saa olla tyhjä. */

public static int suurin(ArrayList<Integer> v) { Integer obj = v.get(0); int toistaiseksi_suurin = obj.intValue(); int apu; for (int i=1; i<v.size(); i++) { apu = (v.get(i)).intValue(); if ( apu> toistaiseksi_suurin) toistaiseksi_suurin = apu; } return toistaiseksi_suurin; } // metodi suurin } // class

(*** esimerkin loppu ***)

Luokassa Vector on myös funktionaalinen (palauttaa totuusarvon) metodi add, jonka toiminta on sama kuin proseduurin addElement. Näin ollen edellä luokassa Suurin_Vector_Gen olisi voinut käyttää myös funktiota add proseduurin addElement sijasta. Funktio add on myös luokissa ArrayList ja LinkedList.

Page 36: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

36

Vector, ArrayList ja LinkedList ovat luokkia, joilla on implementoitu yleinen dynaaminen lista. Implementointi on näissä hiukan erilainen, joten rakenteen valinta voidaan tehdä sen perusteella millaisia metodeja luokka tarjoaa eli missä luokassa on tehtävän tekemiseen sopivimmat metodit (vrt. tämän viikon 1. harjoitustehtävä). Kun siirrymme luokasta toiseen, tulee parhaimmassa tapauksessa vain metodien nimet muuttaa16 (tätä tarkastellaan harjoitustehtävissä). Kaikissa näissä luokissa on kuitenkin metodi add(o): lisää olio o listan loppuun (itse asiassa listan loppuun lisätään viittaus o:hon) get(i): palauta listan i:n alkio eli palautetaan viittaus listan i:nteen olioon.

Esimerkki. Javan versiosta 5.0 lähtien voidaan käyttää automaattista tyypinmuunnosta perustyyppien ja niitä vastaavien kuoriluokkien (esim. Integer ↔ int) välillä (ks. s. 28), joten voimme kirjoittaa s. 33 ohjelman jopa näin lyhyessä muodossa (metodien ulkoiset kuvaukset ovat kuten yllä):

import java.util.Vector; import java.util.Scanner; public class Suurin_Vector_Gen2 { public static void main (String[] args) { Vector<Integer> lista = luelista(); System.out.println("Suurin on " + suurin(lista)); } // main

public static Vector<Integer> luelista() { Vector<Integer> v = new Vector<Integer>(); // luodaan tyhjä lista Scanner näppis = new Scanner(System.in); System.out.println("Anna luku"); while (näppis.hasNextInt()) { int luku = näppis.nextInt(); v.add(luku); // lisäys listan v loppuun System.out.println("Anna seuraava luku (lopuksi x)"); } // while näppis.nextLine(); // luetaan pois syötetty lopetusmerkki x ja enter return v; } // luelista

public static int suurin(Vector<Integer> v)

{ int toistaiseksi_suurin = v.get(0); for (int i=1; i<v.size(); i++) { if (v.get(i)> toistaiseksi_suurin) toistaiseksi_suurin = v.get(i); } return toistaiseksi_suurin; } // suurin } // luokan loppu

(*** esimerkin loppu ***)

16 Huomaa tosin, että metodivalikoima on erilainen.

Page 37: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

37

Huom. Aloittelijan on kuitenkin syytä välttää automaattisia tyypin muunnoksia, koska tällöin olioiden ja primitiivityyppisten muuttujien ero häviää. Esimerkiksi yllä lauseke v.add(luku) tarkoittaa samaa kuin v.add(new Integer (luku)), jossa siis ensin luodaan Integer-olio, jonka arvona on muuttujaa luku vastaava arvo ja sen jälkeen ko. Integer-olio lisätään listaan v. Lauseessa int

toistaiseksi_suurin = v.get(0) tapahtuu automaattinen tyypin muunnos, jossa Integer-tyyppinen alkio v.get(0) muunnetaan int-tyyppiseksi ja vasta sen jälkeen suoritetaan sijoitus muuttujaan toistaiseksi_suurin. Myöskin vertailulausekkeessa v.get(i)>toistaiseksi_suurin suoritetaan ennen vertailua vastaava automaattinen tyypinmuunnos.

Esimerkki: Lisäyslajittelu

Aiemmin käsitellyissä lajittelumetodeissa suoritettiin lajittelu samassa tilassa (alkioiden paikkoja vain taulukossa vaihdettiin). Lisäyslajittelu toimii toisin: 1) luomme uuden listan, joka on aluksi tyhjä ja 2) sijoitamme alkioita listaan niin, että lista on koko ajan järjestyksessä (tässä on kyseessä ”lisää oikeaan väliin” -menetelmä). Kun kaikki lajiteltavan taulukon alkiot on käyty läpi, on listassa alkuperäisen taulukon alkiot järjestyksessä. Metodi vaatii siis tuplatilan lajiteltavien alkioiden lukumäärän suhteen, joten menetelmä ei ole kelvollinen, jos lajiteltavia alkioita on (todella) paljon.

Metodi käyttää Java 5.0:n geneerisyyttä. Metodi lajittele saa parametrikseen lajiteltavan listan (lista) ja se palauttaa lajitellun listan (result). Lajiteltavan listan alkiot käydään läpi for-silmukassa ja kukin alkio elem lisätään listaan result (joka on aluksi tyhjä) oikealle paikalleen käyttäen luokan Vector metodia insertElementAt(elem,k), joka siirtää ensin alkioita positioissa k, k+1, ..., yhden verran oikealle ja sen jälkeen lisää alkion elem kohtaan k (siis mitään tietoa ei häviä!)17. Näin ollen meidän tarvitsee ensin selvittää lisäyskohta, jonka selvittämiseksi kirjoitamme metodin haePaikka. Tällöin lisäys suoritetaan lauseilla

int sijainti = haePaikka(result,elem); result.insertElementAt(elem, sijainti);

Seuraavassa on koko ohjelma:

17 Huomaa, että metodilla setElementAt tallennetaan vanhan komponentin ’päälle’.

Page 38: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

38

import java.util.Vector; public class InsertionSort { public static void main(String[] args) { /* luodaan testilista */ int[] q = {4,8,1,2}; Vector<Integer> lista = new Vector<Integer>(); // luodaan tyhjä lista for (int i=0; i<q.length; i++) { lista.add(q[i]); // autom. tyypin muunnos int->Integer } /* Lajittele luvut. */ Vector<Integer> lajiteltulista = lajittele(lista); /* Tulosta lajitellut luvut. */ System.out.println("Luvut ovat"); System.out.print(lajiteltulista.firstElement()); // tulosta 1. (jotta saadaan pisteet väliin) // tulosta muut for (int i=1; i < lajiteltulista.size(); i++) { System.out.print(" ... " + lajiteltulista.get(i)); } System.out.println("."); } // end main /** * Lajittelee listan lista luvut lisäyslajittelulla nousevaan järjestykseen * ja palauttaa uuden lajitellun listan. Ei muuta listaa lista. * Alkuehto: lista tulee olla luotu, mutta se voi olla tyhjä. */

public static Vector<Integer> lajittele(Vector<Integer> lista) { int s = lista.size(); /* Luodaan tyhjä tuloslista. */ Vector<Integer> result = new Vector<Integer>(); for (int i=0; i < s; i++) { Integer elem = lista.get(i); int sijainti = haePaikka(result,elem); result.insertElementAt(elem, sijainti); // lisäys oikeaan väliin ! } return result; } // end (insertion)sort

Page 39: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

39

/** Metodi haePaikka lalauttaa listan vec ensimmäisen position, jossa oleva alkio * on suurempi kuin elem. Jos suurempaa ei ole, palautetaan vec:n koko. * Alkuehto: vec on järjestetty lista, joka voi olla myös tyhjä. */ public static int haePaikka(Vector<Integer> vec, Integer elem) { if (vec.isEmpty()) return 0; for (int i=0; i<vec.size(); i++) { if (vec.elementAt(i) > elem) return i; } return vec.size(); } // end findPosition } // end class InsertionSort

Lue Liitteen 2 s. 216-217, joissa annetaan yleiskuva Javan kokoelmaluokista ja niiden hierarkiasta. Tähän palataan kun käsitellään rajapintaluokkia. Huomaa, että luokalla HashMap<K,V> saa näppärästi toteutettua ns. sanakirjan (ks. viimeisen viikon teksti). Luokkakirjastossa on myös luokka ADT:ta pino18 varten: Stack<E>, joka tuntee vain pino-operaatiot (esim. pop ja push, ks. TTP I-opintomoniste). Javan luokkakirjastossa ei ole ADT:lle jono (ks. TTP I-opintomoniste) suoraan implementointia, vaikka luokkakirjastossa on luokka Queue. Nimittäin Queue on ns. rajapintaluokka (interface, ks. s. 77), jonka pohjalta voi itse määritellä jonon.

Taulukoiden ja listojen läpikäynti käyttäen for-each lausetta (Java 5.0:sta lähtien) ja iteraattoria

Taulukoiden ja listojen iterointi eli läpikäynti suoritetaan yleensä käyttäen tavallista toistorakennetta, jossa silmukkamuuttujaa juoksee läpi rakenteen indeksit. Indeksien käytöstä

voidaan luopua käyttäen ns. for-each toistorakennetta, joka takaa myös sen, että rakenteen kaikki alkiot käydään lävitse ja ohjelmoijan ei tarvitse tietää alkaako indeksointi nollasta vai ykkösestä. Rakenteesta esitetään alla kaksi esimerkkiä, josta käy ilmi mistä on kysymys. Yleinen syntaksi on muotoa:

for (listan_alkiotyyppi tunnus_listan_alkiolle : listan_tunnus) lohko

Huomaa, että usean alkion poisto yhdellä listan läpikäynnillä ei onnistu for-each –rakenteella, vaan tällöin tulee käyttää tavallista silmukkaa tai iteraattoria.

1) Staattiset taulukot. Oletetaan, että muuttuja v viittaa double-taulukkoon eli olemme määritelleet double[] v ja että v on luotu. Silloin lause

for (int i=0; i < v.length; i++) { System.out.println(v[i]); }

voidaan kirjoittaa for-each –rakenteella seuraavasti:

18 Viimeisellä jaksolla TTP tarkastelemme tekoälyyn liittyviä hakualgoritmeja, joissa käytetetään pino- ja jonorakennetta.

Page 40: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

40

for (double x: v) { System.out.println(x); }

Edellä olevassa tunnuksella x merkitään taulukon v alkiota ja sen eteen kirjoitetaan taulukon v alkiotyyppi, joka on tässä double. Tässä x käy vuorollaan läpi kaikki taulukon v komponentit ja x saa siis ikään kuin automaattisesti arvot v[0], v[1], …

Tällä syntaksilla voidaan käsitellä millaisia taulukoita tahansa eli vaikka taulukoita, joiden komponentit ovat oliotyyppisiä.

2) Dynaamiset listat. Oletetaan, että meillä on määrittely: ArrayList<String> lista ja että lista on luotu. Silloin lause

for (int i=0; i < lista.size(); i++) { System.out.println(lista.get(i)); }

voidaan kirjoittaa for-each –rakenteella seuraavasti:

for (String x: lista) { System.out.println(x); }

Tässä voi listana olla myös Vector tai LinkedList. Edellä oleva luetaan seuraavasti: jokaiselle rakenteen lista String-alkiolle x: tulosta se. Tässä esimerkissä listan alkiot ovat tyyppiä String, mutta listan läpikäynti tapahtuu samalla periaatteella olipa listan alkiotyyppi mikä tahansa oliotyyppi (mutta ei perustyyppi, koska ArrayList ja kaikkien muidenkin kokoelmien alkiot tulee aina olla oliotyyppisiä).

Huom. Tätä silmukkarakennetta ei voi käyttää, jos listan pituus muuttuu iteroinnin aikana, esim. poistetaan alkio listasta, ellei poiston jälkeen poistuta heti silmukasta esim. break-lauseella.

Näemme, että for-each on erittäin kätevä, mutta huomaa, että kaikissa kielissä ei ole tällaista rakennetta lainkaan (tai jos on niin sen syntaksi on nimensä mukainen: for each x:lista do …). Tällöin joudumme käyttämään perinteellistä indeksiviittausta tai voimme myös määritellä olio-ohjelmointiperiaatteiden mukaisen iteraattorin, joka saadaan käyttöön kirjoittamalla import java.util.Iterator, joka on siis pakkauksessa util. Näin ollen voimme kirjoittaa myös import java.util.*, jolloin saamme käyttöömme iteraattorin ja kaikki kokoelmaluokat. Iteraattorin käyttö käy ilmi seuraavasta esimerkistä. Oletetaan, että meillä on listarakenne ArrayList<Integer> ar. Tällöin listan läpikäynti (tässä esimerkkinä tulostus) voidaan suorittaa seuraavalla tavalla:

Iterator<Integer> it = ar.iterator();

while (it.hasNext())

{

System.out.println(it.next()); // it.next() on listan seuraava alkio ja on tyyppiä Integer

}

Tässä hasNext() ja next() ovat rajapintaluokan (interface, käsitellään myöhemmin) Iterator metodeja. Kutsu it.next() palauttaa aina rakenteen seuraavan alkion ja näin ollen rakenteeseen viittaavaa indeksiä ei tarvitse käyttää. Lisäksi edellä oleva on rakenne yleisempi kuin for-each -rakenne, koska ohjelmakoodi on sama listan alkiotyypistä riippumatta ja kaikille

kokoelmatyypeille. Kuitenkin jos listaan halutaan tehdä muutoksia, niin silloin voimme käyttää luokan ListIterator iteraattoria (ks. luokkakirjasto), joskin tällöin perinteellinen indeksiviittauksiin perustuva tarkastelu saattaa olla turvallisempaa. Jos edellä olevassa jätetään tyyppi mainitsematta eli kirjoitamme Iterator it = ar.iterator(); niin silloin it.next() on tyyppiä Object, joka voidaan kuitenkin pakottaa tyyppiin Integer kirjoittamalla (Integer) it.next()

Page 41: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

41

Luokat Arrays ja Collections staattisten ja dynaamisten listojen käsittelyyn (mm. lajittelu käyttäen kirjastometodia sort)

Aiemmilla kursseilla on esitetty useita lajittelumenetelmiä ja juuri edellä käsiteltiin ns. lisäyslajittelu. Lajittelualgoritmin ymmärtäminen on erittäin opettavaista, mutta tässä herää tietenkin kysymys eikö missään kirjastoluokassa ole valmiina metodia, jonka avulla lajittelu voidaan suorittaa – onhan lajittelu kuitenkin tärkeä ja yleisesti tarvittava asia. Vastaus: On ja paljon muitakin yleishyödyllisiä metodeja! Kansiossa util on luokat Arrays (staattisia taulukoita varten) ja luokka Collections (dynaamisia listoja varten) ja näin ollen niiden käyttö vaatii import-lauseen: import java.util.Arrays ja import java.util.Collections. Kumpikin luokka on metodikirjasto ja niiden kaikki metodit ovat staattisia eli niiden kutsua ei kohdisteta olioon, vaan lista annetaan metodille parametrina. Tällöin kutsua ennen tulee antaa myös luokan nimi (samoin kuin tehdään esim. luokan OmaIO kanssa).

Luokka Arrays sisältää useita metodeja, joilla voidaan käsitellä staattisia taulukoita, esim. taulukkoa int[]. Luokassa on metodit mm.

binääriseen hakuun: binarySearch palauttaa indeksin josta haettava alkio löytyy,

taulukon kopioimiseen: copyOf palauttaa uuden kopioidun taulukon,

kahden taulukon sisältöjen samuuden testaamiseen: equals palauttaa totuusarvon,

lajitteluun: sort lajittelee parametrina annetun taulukon nousevasti ja

merkkijonoksi muuttamiseen: toString palauttaa taulukon merkkijonona.

Metodit ovat taas ylikuormitettuja eli samannimistä metodia voi käyttää riippumatta siitä mikä on taulukon alkiotyyppi.

Käyttö: Olkoon double[] v; Silloin v:n alkioiden lajittelu nousevaan suuruusjärjestykseen suoritetaan lauseella: Arrays.sort(v); ja ennen luokan alkua tulee olla lause import java.util.Arrays;

Luokka Collections sisältää useita metodeja, joilla voidaan käsitellä dynaamisia listoja, esim. listoja LinkedList tai ArrayList. Siellä on metodit mm. minimin ja maksimin määräämiseen (min ja max), listan alkioiden kääntämiseen päinvastaiseen järjestykseen (reverse) ja lajitteluun (sort). Metodit ovat geneerisiä eli samannimistä metodia voi käyttää riippumatta siitä mikä on listan alkiotyyppi.

Käyttö: Olkoon LinkedList<Integer> v; Silloin v:n alkioiden lajittelu nousevaan suuruusjärjestykseen ja minimin määrääminen suoritetaan lauseilla: Collections.sort(v); int minimi = Collections.min(v);

ja ennen luokan alkua tulee olla lause import java.util.Collections;

Kummassakin luokassa on myös paljon monipuolisempia metodeja ja niiden käyttötapoja kuin mitä yllä esitetään. Esimerkiksi voidaan lajitella lista ja antaa vertailuoperaattori parametrina luokan Comparator (tätä me ei käsitellä) oliona. Jos taas lajitellaan vain lukuja ja merkkijonoja (aakkostus!), niin yllä esitetty metodien käyttötapa riittää.

Page 42: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

42

Harjoitustehtävät

Tutustu luokkien Integer, Double, Vector, ArrayList, LinkedList ja Stack kuvauksiin. http://docs.oracle.com/javase/8/docs/api/

Jos lähetät ratkaisusi, liitä mukaan kuvaruutukaappaus, joka todentaa sen, että metodisi/ohjelmasi toimii.

1. Vertaile luokkien Vector, ArrayList ja LinkedList metodivalikoimaa (erityisesti alkion lisäys- ja poistomahdollisuudet). Millä luokalla saisi helpoiten toteutettua ADT:t jono ja pino. Onkohan luokkakirjastossa valmiit luokat pinolle ja jonolle? Ohje: ks. jaksolla TTP I käytetty opintomoniste ja mieti miten tarvittavat operaatiot saadaan toteutettua eri luokilla.

2. Kirjoita metodi, joka palauttaa parametrina annetun rakenteen LinkedList<Integer> keskiarvon double-tyyppisen arvon. Esitä versiot automaattisen tyypinmuunnoksen (Integer ↔ int) kanssa ja ilman.

3. Selitä edellä olevan ohjelman InsertionSort toiminta tarkasti (kerro siis miten lajiteltu lista muodostuu ko. esimerkkitapauksessa).

4. Kirjoita edellä ollut ohjelma Suurin_Vector_Gen niin, että siinä käytetään rakennetta LinkedList<Double> , jolloin siis luetaan desimaalilukuja. Käytä metodissa suurin for-each toistorakennetta. Testaa ohjelmaasi tietokoneella. Jos lähetät ratkaisun, mukana tulee olla myös kuvaruutunäyttö ohjelman suorituksesta.

5. (Viime viikon asioita) Kirjoita metodi satunnainenJono(String aakkosto, int pituus), joka palauttaa uuden satunnaisen tyyppiä StringBuilder olevan merkkijonon. Palautettava merkkijono on pituudeltaan parametrin pituus mittainen ja muodostuu satunnaisista aakkoston merkeistä. Aakkosto kostuu mielivaltaisesta määrästä eri merkkejä ja se voi olla esim. ”anhb25s”. Testaa ohjelmasi tietokoneella. Jos lähetät ratkaisun, mukana tulee olla myös kuvaruutunäyttö ohjelman suorituksesta.

Page 43: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

43

VIIKKO 5

Olio-ohjelmoinnin peruskäsitteet: oliot ja luokat, olioiden tietosisältö ja käyttäytyminen, luokan määrittely, olioiden luonti ja käsittely.

Tämän viikon materiaali koostuu alla olevasta ja Liitteen 2 s. 114-126. Lue ensin alla oleva ja siirry Liitteeseen 2 kun niin tekstissä sanotaan.

Alustus

Seuraavaksi tarkastellaan oman tietotyypin määrittelyä ja sen mukaisten alkioiden käsittelyä, joka on tämän kurssin keskeisin asia. Tietotyyppi määritellään kirjoittamalla sitä vastaava luokka ja tämän tietotyypin mukaisia alkioita kutsutaan olioiksi. Nyt siis emme kirjoita perinteellisiä algoritmeja, vaan keskitymme tietotyyppien määrittelyyn ja siihen miten näin määriteltyä tietoa käsitellään. Näin ollen seuraavassa puhutaankin mallinnuksesta eikä niinkään algoritmeista kuten tähän saakka. Huomaa, että kirjastoluokan (esim. String) kaikki metodit on jo kirjoitettu ja voimme käyttää luokkaa ja sen metodeja muitta mutkitta. Sen sijaan määriteltäessä omaa luokkaa, kirjoitamme luokan itse kokonaisuudessaan.

Sanan olio tilasta käytetään usein myös sanaa objekti, joka on tavallaan parempi. Nimittäin olio ei tarkoita mitään kummallista, vaan se on loogisesti yhteenkuuluvien tietojen joukko (esim. henkilötiedot) tai jokin erityinen tietorakenne (esim. taulukko, merkkijono, pino), jolla mallinnetaan tiettyä reaalimaailman asiaa tai käsitettä. Tämä mallinnus tehdään olio-ohjelmoinnissa kirjoittamalla mallia vastaava luokka, joka tallennetaan omaksi tiedostokseen. Tietokenttien lisäksi luokassa määritellään kaikki ne operaatiot (metodeina), joita luokan mukaiseen olioon voidaan kohdistaa eli miten esimerkiksi olion tietosisältöä voidaan muuttaa. Tällainen luokka määrittelee siis uuden tietotyypin eikä kyseinen luokka ole ohjelma. Tämä luokka ei ole siis suorituskelpoinen eikä sisällä myöskään main-metodia (=metodi, josta ohjelman suoritus aina alkaa). Kun haluamme käyttää kyseisen luokan mukaisia olioita jossain ohjelmassa, niin ne tulee luoda dynaamisesti ohjelman suorituksen aikana (new -lauseke) ja ne ovat olemassa (elävät systeemissä) niin kauan kuin meillä on pääsy olion tietoihin eli niin kauan kuin joku muuttuja sisältää viittauksen ko. olioon.

Javassa luokan avulla tehdään mallinnuksen ohella muutakin: 1) tavallinen ohjelma, joka sisältää main-metodin, on luokka, 2) appletti (sovelma) toteutetaan luokkana, 3) metodikirjastoja kootaan luokiksi.

Aiemmin käytettiin jo määreitä public, private ja static, mutta niiden merkitykseen ei juurikaan kiinnitetty huomiota. Tällä viikolla näiden merkitys esitetään tarkasti. Tässä on kuitenkin ongelmana se, että aloittelijan on aluksi vaikeata valita oikea määre. Sen vuoksi seuraavalla viikolla esitetään lyhyesti näiden määreiden merkitys ja annetaan myös suositukset niiden

Page 44: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

44

käytöstä (s. 61). Samassa yhteydessä annetaan lyhyet käyttöohjeet luokkien suhteesta pääohjelmaan (luokka, joka sisältää main-metodin). Vilkuile näitä yhteenvetoja aina välillä lukiessasi tämän viikon tekstiä.

Tällä viikolla esille tulevat asiat ovat äärimmäisen keskeisiä olio-ohjelmoinnin peruskysy-myksiä. Niiden sisäistäminen on osoittautunut monille opiskelijoille vaikeaksi. Vaikeuden syynä lienee käsitteiden olio (object)- ja luokka (class) abstraktisuus.

Siirrytään seuraavaksi tarkastelemaan oman luokan määrittelyä, jolloin tavoitteena on siis määritellä oma tietotyyppi. Tällöin tulee luokkaan määritellä

• tietokentät

• metodit

Johdatus: tietueen käsitteestä luokan käsitteeseen käyttäen esimerkkinä henkilötietoja

Seuraavassa pyritään ensin konkretisoimaan asiaa ja lähdetään liikkeelle tietokokoelman esittämisestä, josta päädytään luonnollisesti kapseloituun (ks. TTP I -moniste s. 73) luokan käsitteeseen. On selvää, että jos meidän tulee käsitellä yhden henkilön tietoja, niin ei ole järkevää tallentaa yhden henkilön tietoja useaan eri muuttujaan, vaan on kätevää, jos meillä on käytössä vain yksi muuttuja, jonka takana ovat kaikki tiedot. Näin ollen määrittelemme Henkilo-tietotyypin määrittelemällä luokan sitä varten19. Seuraavassa esitetään ensin tietotyyppi, joka sisältää vain tiedot (tietueen käsite) ja lopullinen olio-ohjelmoinnin periaatteita noudattava luokka Henkilo esitetään s. 50 esimerkissä (katso sitä jo nyt!). Tässä lopullisessa esimerkissä esitetään myös toinen luokka, jossa luodaan ko. luokan tyyppinen olio.

Lue TTP I-monisteesta luvut 2.8.1 (Tyyppi, abstrakti tietotyyppi ja sen implementointi), 2.8.2 (Tietue), 2.8.4 (Abstraktien tietotyyppien implementoinnista poislukien esimerkit) ja 2.8.5 (Oliokeskeinen ohjelmointi) ennen kuin jatkat lukemista eteenpäin. Mieti näitä asioita alla olevan valossa.

Lähdemme liikkeelle tietueen käsitteestä. Tietue on yhteenkuuluva kokoelma tietokenttiä, joita kutsutaan myös attribuuteiksi. Tietue-konstruktio on mukana vanhemmissa ohjelmointikielissä (Pascal: record, C: struct), mutta Javassa tietueet esitetään luokan (kuten kaikki mutkin asiat) avulla ja luokan alkiota kutsutaan olioksi tai olioesiintymäksi. Tarkastellaan esimerkkinä sovel-lusta, jossa käsitellään henkilötietoja. Näitä voisivat olla

• nimi (merkkijono)

• osoite (merkkijono)

• syntymävuosi (kokonaisluku)

19 Koska luokka tulee tallentaa nimellä <luokan nimi>.java, niin sotkujen välttämiseksi emme käytä luokan nimessä skandinaavisia kirjaimia.

Page 45: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

45

Jos samassa ohjelmassa käsitellään useita henkilöitä, niin heillä kaikilla on todennäköisesti samantyyppiset tietokentät, vaikka niiden sisällöt eroavat toisistaan. Meidän tulee siis erottaa kaksi käsitetasoa:

• Olion tyyppi määritellään kirjoittamalla luokka, jossa karakterisoivina tietoina ovat nimi, osoite, syntymävuosi. Tässä esitetään siis ne tietokentät, joilla henkilö karakterisoidaan eli abstrakti malli.

• Olioesiintymä, jossa tietokentillä on tietyt arvot, esim. nimi=”Ville Virtanen”, osoite=”Turku”, ja syntymävuosi =1970. Kun karakterisoivat tiedot annetaan, saadaan siis konkreettinen henkilö.

Olion tyyppi tulee tietenkin määritellä ennen kuin sen mukaisia olioita voidaan luoda. Tyypille tulee antaa nimi, jossa ei ole suositeltavaa käyttää ä-, ö- tai å-kirjaimia, joten annetaan tyypin nimeksi Henkilo, joka on siis uusi, ohjelmoijan määrittelemä tietotyyppi (vrt. perustyypit char, int, boolean, double, jne). Javassa oliotyyppi määritellään kirjoittamalla sitä vastaava luokka, ja sen määrittely tehdään esimerkkitapauksessa (huom. tämä on vain johdattelua ja luokan Henkilo täydellinen ja oikeaoppinen määrittely esitetään s. 50) näin:

/* 1. versio luokasta Henkilo, joka ei noudata olio-ohjelmoinnin periaatteita, vaan on ns. perinteellisen ohjelmoinnin mukainen tietue-tietotyyppi */

public class Henkilo {

String nimi; String osoite; int syntVuosi; }

Luokan nimi on tapana aloittaa isolla kirjaimella ja tietokentät esitellään samaan tapaan kuin muutkin muuttujat. Luokka pitää tallettaa omaksi tiedostokseen ja sen nimi tulee olla sama kuin luokan nimi, jatkeena .java, esim. tässä tapauksessa nimellä Henkilo.java. Luokan kenttiä (nimi, osoite, syntVuosi) kutsutaan tietokentiksi, attribuuteiksi tai luokan muuttujiksi.

Kun luokka Henkilo on käännetty (compile/build), voidaan muissa luokissa esitellä muuttujia, jotka ovat ko. luokan tyyppiä, esim.

Henkilo johtaja, työnTekijä;

jolloin muuttujat johtaja ja työnTekijä ovat tyyppiä Henkilo. Huomaa taas, että myös tässä muuttujien esittelyn syntaksi on samaa muotoa kuin aiemminkin: <tyypin tunnus> <muuttujan tunnus>.

Olioesiintymä on olemassa ohjelman suorituksena aikana vasta, kun se on luotu.

Olioiden luonti (alustava esitys)

Javassa olio luodaan new-lausekkeella, kuten tuli jo aiemmin ilmi. Sen vaikutuksesta systeemi varaa muistista oliolle tilaa. Lauseke muistuttaa funktion kutsua, ja se palauttaa arvonaan viittauksen luotuun olioon, joka yleensä sijoitetaan välittömästi johonkin muuttujaan myöhempää käyttöä varten.

Page 46: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

46

Kun tyyppiä Henkilo oleva olio on luotu, niin olioesiintymän yksittäisiin kenttiin voidaan viitataan valitsinlausekkeilla, eli pistenotaatiolla <oliomuuttuja>.<kenttänimi>. Jos kuitenkin tietokenttä on varustettu private-määreellä (kuten tällä kurssilla on vaatimuksena), niin pistenotaatiota ei saa käyttää (tähän palataan myöhemmin).

Nyt johtaja-niminen olio voitaisiin luoda esim. seuraavasti (siis jossain muussa luokassa kuin Henkilo-luokassa ja siis jossain muussa ohjelmatiedostossa):

Henkilo johtaja = new Henkilo( ); // henkilöolion luonti johtaja.nimi = ”Ville Virtanen”; // tyyppiä String johtaja.osoite = ”Turku”; johtaja.syntVuosi = 1970;

Luokan esiintymät ovat siis olioita. Henkilo-tyyppisen muuttujan arvo on periaatteessa henkilöolio, mutta tekninen toteutus on sellainen, että muuttujan arvo on viittaus kyseiseen olioon. Tämän ymmärtämistä ehkä helpottaa ajatella, että viittaustyyppisen muuttujan arvo = viitatun olion muistiosoite. Seuraava kuva havainnollistaa Henkilo-olioon tapahtuvaa viittausta. Huomaa, että Javassa merkkijono (String) ei ole perustyyppinen, vaan luokan esiintymä eli olio. Merkkijonovakioita ei kuitenkaan tarvitse luoda new-lausekkeella, vaan vakioarvoinen merkkijonolauseke ”Ville Virtanen” pitää sisällään tämän luonnin.

Henkilo johtaja

String nimi ”Ville Virtanen”

String osoite

int syntVuosi 1970 ”Turku”

(*** esimerkin loppu ***)

Vaikka luokka on selvästi ohjelmatekstissä esiintyvä itsenäinen määrittely, niin olioita ei suoraan näy ohjelmissa. Ne ovat olemassa vain ohjelman suorituksen aikana, ja niihin päästään käsiksi muuttujien sisältämien viittausten kautta.

Toinen huomionarvoinen asia on se, että kaksi (tai useampi) muuttujaa voi viitata samaan olioon. Jos edellisen kuvan mukaisen tilanteen yhteydessä suoritetaan asetuslause

Henkilo j = johtaja; /* esitellään uusi Henkilo-tyyppinen muuttuja j, jolle arvo annetaan sama arvo kuin mitä on muuttujalla johtaja */

Oliotyyppisen muuttujan arvona on viittaus olioon ≈≈≈≈ osoitin ≈≈≈≈ ’pointteri’ ≈≈≈≈ olion muistiosoite

Page 47: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

47

niin j ja johtaja tulevat viittaamaan samaan olioon. Tällöin muuttujat j ja johtaja viittaavat samaan Henkilo-olioon, jolloin muistin tilaa voidaan havainnollistaa seuraavalla kuvalla:

Henkilo johtaja

String nimi ”Ville Virtanen”

Henkilo j String osoite

int syntVuosi 1970 ”Turku”

Jos tämän jälkeen asetetaan: j.osoite = ”Helsinki”, niin myös johtaja.osoite on tämän jälkeen ”Helsinki”.

Edellä olevan lisäesimerkin neljä lausetta tekevät kaksi asiaa:

• Olion luonti, eli tilan varaus (new)

• Tietokenttien arvojen asetus eli alustus (annetaan arvot kentille nimi, …)

Javassa on tapana kirjoittaa alustusta varten oma metodinsa, ns. konstruktori eli alustusmetodi, joka määritellään siinä luokassa, jossa ko. tietotyyppi määritellään. Konstruktorilla on oltava sama nimi kuin luokalla. Sen kutsu sijoitetaan new-sanan perään. Itse asiassa edellä ollut lauseke new Henkilo() oli jo tätä muotoa (parametrisuluista () voi päätellä, että Henkilo on parametriton metodi). Selitys on se, että Java-systeemi määrittelee luokalle automaattisesti oletuskonstruktorin, joka alustaa luodun olion tietokentät oletus- eli alkuarvoilla seuraavasti: lukuarvoiset kentät: 0, totuustyppiset: false, viittaus- eli oliotyyppiset: null. Näin ollen edellä oli luvallista kirjoittaa new Henkilo( ); vaikka emme olleet määritelleet luokkaan Henkilo konstruktoria lainkaan. Oletuskonstruktori periytyy luokan yliluokasta (perintää käsitellään myöhemmin) ja tässä tapauksessa kun meillä on vain luokka Henkilo, perintä tapahtuu kaikkien luokkien yliluokasta Object. Kuitenkin oletuskonstruktori on käytettävissä vain sellaisessa

luokassa, jossa ei ole määritelty omaa konstruktoria. Vaikka Java alustaa oliotyyppisen muuttujan tietokentät, niin silti kannattaa määritellä sellainen oma konstruktori, joka antaa arvot halutuille tietokentille itse eikä käyttää systeemin antamia alkuarvoja, koska tämä alustus on kielikohtainen asia ja systeemin asettamat alkuarvot eivät kuitenkaan ole käypiä. Konstruktorissa ei tarvitse antaa arvoja välttämättä kaikille tietokentille, jolloin niiden arvoiksi tulevat oletusarvot.

Tyypillinen konstruktori on sellainen, jolle annetaan kutsun yhteydessä parametreina tietokentille tarkoitetut arvot. Konstruktori on siis metodi, mutta se eroaa muodoltaan tavallisesta metodista siten, että konstruktorimetodilla ei ole palautustyyppiä. Lisäksi konstruktorimetodia ei voi kutsua ilman new-lauseketta. Ne tietokentät, joille ei anneta arvoa konstruktorissa, saavat arvoikseen oletusarvot.

Page 48: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

48

Henkilo-luokan määrittely konstruktorilla laajennettuna on muotoa:

/* 2. versio luokasta Henkilo, joka ei noudata olio-ohjelmoinnin vielä periaatteita, vaan on ns. perinteellisen ohjelmoinnin mukainen tietue-tietotyyppi. Tähän on kuitenkin jo kirjoitettu konstruktori, jolla olion tietokentät voidaan alustaa luonnin yhteydessä (new) */ public class Henkilo { String nimi; String osoite; int syntVuosi; public Henkilo(String ni, String os, int sv) { nimi = ni; osoite = os; syntVuosi = sv; } } // class Henkilo

Nyt voidaan jossain ohjelmassa luoda henkilöitä seuraavaan tapaan: Henkilo johtaja = new Henkilo(”Ville Virtanen”, ”Turku”, 1970); Henkilo työnTekijä = new Henkilo(”Lauri Lahtinen", ”Tampere”, 1980);

Konstruktori siis lisää toiminnallisuutta luokan määrittelyyn. Tätä ajatusta yleistämällä päästään olio-ohjelmoinnin perusajatukseen: Olion sekä toiminnalliset että ei-toiminnalliset piirteet määritellään oliotyyppiä kuvaavan luokan sisällä. Toiminnalliset piirteet ovat metodeja ja ei-toiminnalliset piirteet tietokenttiä.

Luokan metodit tarjoavat keinon päästä ko. luokan esiintymäolion sisälle joko tekemään havaintoja tai muuttamaan tietosisältöä. Tässä tulee kuitenkin noudattaa seuraavaa ns. kapselointiperiaatetta, jota käsiteltiin JTKT-monisteessa.

Kapselointi: Olion sisältöä saa käsitellä luokan ulkopuolelta vain luokan metodien kautta. Kapselointi toteutetaan Javassa siten, että luokan tietokenttien näkyvyysmääreeksi asetetaan private (tai protected, jos luokalla on aliluokkia, joita käsitellään perinnän yhteydessä)

Kapselointi estää tietokenttiin viittaamisen luokan ulkopuolelta (eston perustelut esitetään myöhemmin s. 64). Tällöin jossain muussa kuin Henkilo-luokassa

ei voida kirjoittaa johtaja.nimi kuten tehtiin sivulla 46.

Näin ollen luokkaa tulee täydentää get-muotoisilla havainnointimetodeilla (jolla saadaan ulos olion tietokentän arvo) ja set-muotoisilla muutosmetodeilla (jolla voidaan asettaa tietokentän arvo). Näin ollen:

Page 49: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

49

Tietotyypin määrittelevä luokka koostuu sen

1) tietokentistä, jotka on lähes poikkeuksetta varustettu määreellä private, ja

2) metodeista, jotka voidaan jakaa20 kolmeen ryhmään:

• Luontimetodit eli konstruktorit (constructors), joita voi käyttää vain luonnin (new) yhteydessä. Tällä annetaan usein arvot olion tietokentille.

• Havainnointimetodit eli aksessorit (accesors), jotka palauttavat tietoa oliosta (tietokenttien arvot tai jotain muuta tietokentistä johdettua tietoa), ja ovat siksi funktiotyyppisiä.

• Muutosmetodit eli mutaattorit (mutators), jotka päivittävät tietokenttien arvoja, tai joilla on joitain muita sivuvaikutuksia; nämä ovat proseduurityyppisiä, eli tulostyyppi on yleensä void.

Huom. Termi luontimetodi tai konstruktori on hiukan harhaanjohtava. Metodin kutsu ei itse asiassa luo oliota, vaan luonnin saa aikaan new-lauseke. Sen sijaan konstruktorin koodiin kirjoitetaan lauseet, jotka alustavat tietokentät halutuilla arvoilla luonnin yhteydessä. Näin ollen konstruktoria nimitetään usein myös alustajaksi.

Huom. Muutosmetodien tarkoituksena on muuttaa olion tilaa ja siksi ne eivät yleensä palauta mitään. Usein on kuitenkin tapana tehdä muutosmetodi, jonka palautusarvo on boolean sen mukaan onnistuiko muutos vai eikö.

JavaBeans-komponenttiteknologia määrittelee miten luokat tulisi rakentaa ja nimetä. Siinä määritellään, että muutosmetodin nimen tulisi olla muotoa set<tietokentän nimi> ja sillä on yksi parametri: uusi tietokentän arvo, esim. public void setNimi(String uusiNimi) Havainnointimetodi on muotoa get<tietokentän nimi> ja se on parametriton. Noudata tätä! Tästä johtuen havainnointimetodeja kutsutaan myös gettereiksi ja muutosmetodeja settereiksi.

20 Kyseinen jako on looginen ja se ei siis näy ohjelmakoodissa. Kuitenkin ohjelmakoodissa on tapana ryhmitellä metodit kyseisiin ryhmiin ja varustaa ne vastaavilla kommenteilla.

Page 50: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

50

Luokan Henkilo oikeaoppinen määrittely sekä this ja toString

Seuraavassa on oikeaoppinen21 tietotyypin Henkilo määrittely luokan avulla, olion luonti ja sen käsittely joillakin se metodeilla. Tämä on tärkeä esimerkki!

/* 3. ja lopullinen versio, joka noudattaa OO-periaatteita: kaikki tietokentät on kapseloitu (private) ja tietokenttiä voidaan käsitellä vain luokan metodien kautta */ public class Henkilo // lopullinen versio { // **************************************************************************************** // Tietokentät, jotka ovat kaikki esiintymämuuttujia (selitetään myöhemmin): // **************************************************************************************** /** henkilön nimi */ private String nimi; /** henkilön osoite */ private String osoite; /** henkilön syntymävuosi */ private int syntVuosi;

// *********************************************************************************************** // Konstruktori: luo Henkilo-olion, jonka nimi on ni, osoite on os ja syntymävuosi on sv. // ************************************************************************************************

public Henkilo(String ni, String os, int sv) { this.nimi = ni; this.osoite = os; this.syntVuosi = sv; } // ****************************************************************************************

// Havainnointimetodi kutakin tietokenttää kohti ja // olion sisällön merkkijonona palauttava havainnointimetodi toString:

// **************************************************************************************** /** palauttaa nimen */ public String getNimi( ) { return this.nimi; } /** palauttaa osoitteen */ public String getOsoite( ) { return this.osoite; } /** palauttaa syntymävuoden */ public int getSyntVuosi( ) { return this.syntVuosi; } /** palauttaa nimen, osoitteen ja syntymävuoden peräkkäin (välissä yksi välilyönti). * return-lauseessa voidaan jättää sulkeet ( ) pois. Tätä selitetetään alla. */ public String toString( ) { return (this.nimi + ” ” + this.osoite + ” ” + this.syntVuosi); }

21 Kun teet tietokonetyötäsi, niin ota mallia tämän Henkilo-luokan kommentoititavasta.

Page 51: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

51

// **************************************************************************************** // Muutosmetodit:

// setSyntVuosi on mukana, jotta voitaisiin esim. korjata virheellinen syntymävuosi jälkeen- // päin

// **************************************************************************************** /** muuta kohdeolion nimeksi uusiNimi */ public void setNimi(String uusiNimi) { this.nimi = uusiNimi; } /** muuta kohdeolion osoitteeksi uusiOs */ public void setOsoite(String uusiOs) { this.osoite = uusiOs; } /** muuta kohdeolion syntymävuodeksi uusiVuosi */ public void setsyntVuosi(int uusiVuosi) { this.syntVuosi = uusiVuosi; } } // end class Henkilo

Verrattuna aiempiin versioihin tässä on kolme tärkeätä eroa:

1. Tietokentät on varustettu määreellä private eli tiedot ovat kapseloitu (kuten jatkossa aina tehdään), jonka vuoksi

2. Kullekin tietokentällä kirjoitetaan havainnointimetodi, joka palauttaa tietokentän arvon.

3. Kaikkien tietokenttäviittausten eteen on kirjoitettu this.

Yllä havainnointi- ja muutosmetodien rungot kirjoitetaan heti otsikkorivin perään, koska ne ovat lyhyitä. Tietysti voisimme noudattaa samaa käytäntöä kuin aiemminkin, jolloin kirjoittaisimme esimerkiksi

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

this ja sen käyttö: Tietokenttään voidaan sen luokan sisällä, jossa se on esitelty, viitata joko lausekkeella <tietokentän nimi> tai lausekkeella this.<tietokentän nimi >. this-viittauksen käyttö on suositeltavaa, koska tällöin ero luokan tietokenttien ja metodin muuttujien välillä on selvä. Kun metodia kutsutaan, niin metodissa oleva this tarkoittaa sitä oliota, johon metodi kutsu kohdistetaan, ns. kohdeolioita, kuten seuraavassa havainnollistetaan.

Kun jostain muusta luokasta käsin suoritetaan lauseet (ks. myös luokka KoeHenkilo s. 52 ja sen jälkeinen teksti) Henkilo h = new Henkilo(”Ville Virtanen”, ”Turku”, 1970); // henkilö, jonka osoite muuttuu String u = "Helsinki" ; // Uusi osoite h.setOsoite(u);

niin setOsoite-metodissa oleva this viittaa samaan henkilöolioon kuin kutsun kohde eli muuttuja h. Näin ollen this voidaan ajatella olevan ikään kuin metodin muodollinen parametri, joka saa arvon (viittauksen) kun metodia kutsutaan. this-viittauksen käyttö ei siis ole välttämätöntä, mutta se selventää ohjelmakoodia: jos kirjoitamme this.osoite, niin silloin tiedämme heti, että kyseinen muuttuja tarkoittaa ko. luokan alussa määriteltyä tietokenttää osoite eikä esim. jotain paikallista

Page 52: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

52

muuttujaa nimeltä osoite. Huomaa, että kirjallisuudessa ei aina käytetä this-viittausta, mutta me

käytämme sitä systemaattisesti. this-viittauksen käyttö on kuitenkin välttämätöntä, jos metodimme parametri on saman niminen kuin tietokentän nimi (tämä ei ole kuitenkaan suositeltavaa):

public void setOsoite (String osoite) { this.osoite = osoite; }

jolloin parametrina annetun uuden osoitteen arvo (eli muuttujan u arvo) kopioituu metodin setOsoite muodollisen parametrin osoite arvoksi, joka annetaan metodissa h-olion tietokentän osoite uudeksi arvoksi. Tässä siis muuttuja osoite ei tarkoita luokan tietokenttää, vaan se on metodin setOsoite muodollinen parametri. Huomaa, että jos kirjoitamme tässä {osoite = osoite; }, niin se on syntaksisesti aivan oikein, mutta ei toimi halutulla tavalla.

Luokan metodien yhteydessä on taas hyvä muistaa, että Javassa metodin parametrit ovat aina ns. arvoparametreja ja muodollisen parametrin nimi voi olla mikä tahansa, mutta luokan alussa olevia tietokenttien nimiä on selvyyden vuoksi syytä välttää.

Metodi toString palauttaa String-tyyppisen olion eli merkkijonon, joka saadaan liittämällä peräkkäin (tekstin katenointi) merkkijonot nimi , " " (välilyönti), osoite, " " ja syntVuosi eli metodi palauttaa yhden merkkijonon, jossa on ko. tiedot peräkkäin välilyönneillä erotettuina. Ensimmäisellä viikolla todettiin, että sekatyyppistä tietoa (esim. luku ja merkkijono) voidaan myös katenoida. Tämä on tarpeellista, koska luokan tietokentät ovat yleensä erityyppisiä (kuten ylläkin).

Tietotyypin määrittelevään luokkaa tulee kirjoittaa toString-metodi, joka palauttaa tietokenttien arvot merkkijonona siistissä muodossa. Jos rakenne on ’iso’ (kuten luokassa Pankki), niin sen sijasta voidaan kirjoittaa myös tulosta-metodi, joka tulostaa tietokenttien arvot.

Luokka Henkilo on vain luokan (tietotyypin) määrittely ja kun luokka Henkilö on käännetty (compile/build), niin luokan mukaisten alkioiden luonti ja käsittely tapahtuu yleensä jossain muussa luokassa. Esimerkiksi voisimme kirjoittaa seuraavan (toiminnallisen eli main-metodin sisältävän luokan) luokan, joka tallennetaan levylle nimellä KoeHenkilo.java.

public class KoeHenkilo { public static void main (String[] args) { Henkilo johtaja = new Henkilo(”Ville Virtanen”, ”Turku”, 1970); johtaja.setOsoite("Helsinki"); System.out.println(johtaja + "."); System.out.println("uusi osoite on " + johtaja.getOsoite()); } }

Näistä tiedostot Henkilo.java ja KoeHenkilo.java siis käännetään, mutta vain KoeHenkilo suoritetaan.

Luokan KoeHenkilo ensimmäisen lauseen aikaan saamaa toimintaa havainnollistettiin aiemmin s. 46 kuvalla.

Toisessa lauseessa olioon johtaja kohdistetaan luokan Henkilo metodi setOsoite. Tämä pitää tehdä juuri näin kuin tässä on tehty eli

Page 53: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

53

<oliomuuttuja>.<metodin kutsu> eli tässä tapauksessa: johtaja.setOsoite("Helsinki");

jolloin johtaja-olioon kohdistetaan metodi setOsoite parametrilla "Helsinki". Tällöin metodin setOsoite muodollinen parametri uusiOs saa arvon "Helsinki" ja metodissa tämä arvo asetetaan johtajaolion tietokentän osoite uudeksi arvoksi. Näin ollen olioon johtaja on kohdistettu muutosoperaatio setOsoite. Metodia setOsoite kutsutaan esiintymämetodiksi22, koska sen kutsu tulee aina kohdistaa ko. luokan olioon. Tällaisen metodin otsikkoriville ei saa kirjoittaa määrettä static, kuten jo aiemminkin on tullut ilmi.

toString: Kolmannessa lauseessa

System.out.println(johtaja + ".");

on oliotyyppinen muuttuja johtaja yksin – siis ilman metodin kutsua. Mitähän tällöin tapahtuu? Tällöin systeemi soveltaa olioon (siis vaikka taulukoihin) automaattisesti luokan Object (joka on kaikkien luokkien yläluokka periytymishierarkiassa; perintää käsitellään myöhemmin) metodia toString, joka palauttaa olion tiedot merkkijonona. Tällainen metodi on siis olemassa automaattisesti (on siis luokan Object metodi) ja sitä voidaan soveltaa kaikkiin oliotyyppisiin muuttujiin. Metodi tuottaa usein kuitenkin niin kryptisen merkkijonon, että se ei ole luettava. Sen vuoksi luokkaan kirjoitetaan lähes aina oma toString-metodi eli siis määritellään uudelleen metodi toString, joka palauttaa olion tietokenttien arvot merkkijonona halutun näköisenä. Tällöin puhutaan metodin uudelleenmäärittelystä tai ylikirjoittamisesta (overriding), joka on siis aivan eri asia kuin metodin ylikuormittaminen, jota käsiteltiin jo jaksolla AOP. Javassa on seuraava automaattinen ominaisuus: jos oliotyyppinen muuttuja esiintyy lausekkeessa, jonka kohdalla odotetaan olevan tyyppiä String oleva arvo, niin olioon sovelletaan automaattisesti toString-metodia. Näin ollen Java tulkitsee lauseen ikään kuin se olisi kirjoitettu muodossa: System.out.println(johtaja.toString() + "."); jolloin tulostuu (huomaa piste rivin lopussa):

Ville Virtanen Helsinki 1970.

Lue seuraavaksi Liitteen 2 sivut 114-126.

Harjoitustehtävät

Jos lähetät ratkaisusi, liitä mukaan kuvaruutukaappaus, joka todentaa sen, että metodisi/ohjelmasi toimii.

1. Tarkastellaan Liitteen 2 luokkaa Piste (s. 125–126). Mitä ohjelma PisteTesti tulostaa? Pelkkä tulostuksen esittäminen ei riitä, vaan pitää myös selittää. Kirjoita luokka Piste uudestaan noudattaen yllä olevaa luokan Henkilo kirjoitustapaa (looginen jaottelu, kommentointi, nimeäminen).

2. a) Mitkä ovat luokan Piste tietokentät, konstruktorit, havainnointimetodit ja muutosmetodit? Kirjoita oma luokka, jossa on main-metodi, joka sisältää lauseet seuraavien tehtävien suorittamiseksi: b) luo piste (2,7) ja aseta se esittelemättömän muuttujan p1 arvoksi c) tulosta kyseinen piste

22 tai ilmentymämetodiksi tai instanssimetodiksi

Page 54: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

54

3. jatkuu … d) tulosta pisteen x-koordinaatti, e) aseta pisteen y-koordinaatiksi 10, f) luo uusi piste (11,9) ja aseta se olemassa olevan muuttujan p2 arvoksi, g) Mitä tapahtuu asetuslauseen p1=p2 jälkeen? Havainnollista pisteitä p1 ja p2 kuvilla. Havainnollista kaikissa kohdissa a)-g) olioita p1 ja p2 kuvilla, jossa näkyvät niiden tietokentät (ilman havainnollistusta ratkaisuja ei täysin hyväksytä). Ohje tehtäviin 2 ja 3: Huomaat varmaan, että luokassa Piste ei ole metodeja, jotka mahdollistaisi kohtien d) ja e) suorituksen, joten kirjoita luokkaan Piste tarvittavat lisämetodit. Lisäksi metodin mjonoksi nimeksi kannattaa muuttaa toString, jolloin c)-kohta voidaan kirjoittaa lyhyemmin. Miten?

4. Nyt Jorma on tehnyt seuraavan luokan, jossa käsitellään s. 50 luokkaa Henkilo. Homma ei ole taaskaan hallussa, auta Jormaa selitysten kera (selitä miksi lause on virheellinen ja anna myös korjaus ja kerro mitä se saa aikaan/tulostaa).

public class Testi { public static void main (String[] args) { Henkilo sinä = new Henkilo(Pekka, Turku); sinä.syntVuosi = 1980; System.out.println(sinä.nimi); Henkilo uusi; uusi.setOsoite = "Helsinki"; Henkilö toinen = new Henkilo(); System.out.println(toinen.getSyntVuosi()); } }

5. (Viime viikon asioita) Kirjoita metodi public static void lisää_oikeaan_väliin(LinkedList<Double> lista, double luku)

joka lisää parametrina annettuun suuruusjärjestyksessä (pienimmästä suurimpaan) olevaan listaan lista parametrina annetun double-tyyppisen luvun luku oikealle paikalleen. Tee metodista myös funktionaalinen versio, joka palauttaa muutetun listan (tässä ei siis luoda uutta listaa). Mitkä kolme eri lisäysmahdollisuutta tulee ottaa huomioon metodin testauksessa? Metodia tulee testata monipuolisesti. Listaa ei saa lajitella!

Page 55: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

55

VIIKKO 6

Oman tietotyypin määrittely luokan avulla: Olio-ohjelmointiin liittyvät käsitteet ja niiden ilmaiseminen Javassa

Tämän viikon yhteydessä esitetään kaikki tärkeät käsitteet ja tämän viikon asioihin tulee palata myös myöhemmin. Monet alla esitetyt käsitteet esitettiin jo edellisellä viikolla Henkilo-luokan

yhteydessä, mutta tällä viikolla ne esitetään kootusti ja yleisesti.

Alustus

Olion luonnin vaiheet tulivat esille jo aiemmin:

• new varaa oliolle tilan muistista.

• Konstruktori eli luontimetodi alustaa olion (esiintymämuuttujat).

Konstruktori sijoitetaan luokan muiden metodien joukkoon ennen muita metodeja. Sen tuntee siitä, että sillä ei ole tulostyyppiä lainkaan ja että sen nimi on sama kuin ko. luokan nimi. Konstuktorissa annetaan yleensä arvot luokan tietokentille (tai ainakin osalle niistä) ja nämä arvot ovat konstruktorin parametreina. Konstruktorimetodia ei voi käyttää ilman new:tä, joka

luo olion. Näin ollen olisikin parempi puhua alustajista kuin konstruktoreista.

Samassa luokassa on usein useita konstruktoreita. Kaikilla niillä on sama nimi = luokan nimi. Kyseessä on metodien ylikuormitus, kun parametrijoukko vaihtelee eri luontitilanteiden mukaan. Jos tietokentän alkuarvoksi riittää oletusarvo, niin ko. arvoa ei tarvitse asettaa erikseen, sillä systeemi tekee sen automaattisesti kaikille tietokentille ennen konstruktorin kutsua (ks. s. 47). Koska tämä käytäntö ei päde kaikissa ohjelmointikielissä, niin on suositeltavampaa alustaa konstruktorissa itse kaikki tietokentät.

Abstraktin tietotyypin käsite pitää sisällään kapselointi-idean: Määritellään toimintojen (metodien) rajapinnat, mutta ei toteutuksen yksityiskohtia. Esimerkkinä abstraktista tietotyypistä on pino-tietorakenne. Se on järjestetty kokoelma olioita, jonka tulee mahdollistaa seuraavat toiminnot:

• uuden olion lisäys pinon päällimmäiseksi,

• pinon päällimmäisen olion haku (havainnointi),

• pinon päällimmäisen olion poisto,

• pinon tyhjyystesti.

Page 56: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

56

Lisäys Havainnointi; poisto

Pinoa hyväksikäyttävän ohjelman ei tarvitse tuntea pinon esittämiseen käytettävää tieto-rakennetta. Se voisi olla taulukko, mutta parempi valinta olisi jokin dynaamisesti laajen-nettavissa oleva rakenne. Mainittakoon, että luokkakirjastosta löytyy valmis toteutus pinolle (Stack).

Tietotyypin määrittely

• koostuu tiedoista, joilla luokan mukaiset alkiot karakterisoidaan, näitä sanotaan luokan tietokentiksi tai muuttujiksi tai attribuuteiksi (luokan alussa olevat muuttujien ja vakioiden esittelyt)

• koostuu metodeista, joilla ko. luokan mukaisen

• olion tietokentät alustetaan olion luonnin yhteydessä (konstruktorit). Tämän metodin nimi=luokan nimi ja sillä ei ole tulostyyppiä laisinkaan ja sillä on yleensä parametri kutakin luokan tietokenttää kohti.

• oliota käsitellään

• tietotyypin määrittelevä luokka on yksinkertaisimmillaan (ja tällä kurssilla) muotoa

public class <luokan nimi>

{ <luokan tietokentät eli muuttujat > <luokan konstruktorit> <luokan muut metodit>

}

Seuraavassa käsitellään tarkemmin tietotyypin määrittelyä luokan avulla.

Page 57: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

57

Luokan tietokentät: esiintymämuuttuja / luokkamuuttuja tai -vakio

• Jos tietokentän arvo on mahdollisesti eri luokan eri oliolla, niin tällaista muuttujaa kutsutaan esiintymämuuttujaksi (oliokohtainen). Yleensä luokan kaikki tietokentät ovat tällaisia, kun luokan avulla määritellään tietotyyppi.

• Luokalla voi olla tietokenttä, jonka arvo on yhteinen kaikilla luokan olioilla. Tällaista kutsutaan luokkamuuttujaksi ja se on siis luokkakohtainen ominaisuus. Esimerkiksi Henkilo-luokassa voisi olla (ja tämä on vielä final eli sitä ei voi muuttaa eli kyseessä on luokkavakio)

public static final int TÄYSI_IKÄISYYS = 18;

Luokassa on melko harvoin luokkamuuttujia, jos luokan avulla määritellään tietotyyppi. Sen sijaan jos on tarve määritellä globaalisia muuttujia luokassa, jossa ei määritellä uutta tietotyyppiä, niin ne voidaan määritellä luokkamuuttujiksi tai –vakioksi.

Luokan tietokentille annetaan yleensä arvot olion luonnin yhteydessä, jolloin tietokenttien arvot ovat konstruktorin parametreja (kuten edellä Henkilo-luokassa). Luokan tietokentille voidaan antaa alkuarvot myös luokan tietokenttien esittelyn yhteydessä, joskin esim. Henkilo-luokan yhteydessä tietokenttien arvot ovat selvästi oliokohtaisia, joten niille ei ole järkeä antaa mitään alkuarvoa esittelyn yhteydessä. Sen sijaan luokkamuuttujalle voidaan antaa alkuarvo esittelyn yhteydessä, koska luokkamuuttuja on luokka- eikä oliokohtainen. Jos luokkamuuttujalle annetaan alkuarvo, niin tämä alkuasetus suoritetaan vain luokan ensimmäisen olion luonnin yhteydessä. Jos taas esiintymämuuttujalle annetaan alkuarvo esittelyn yhteydessä, niin tämä alkuasetus suoritetaan jokaisen olion luonnin yhteydessä, koska esiintymämuuttujathan ovat erit eri olioilla.

Luokassa määritellyt metodit: esiintymämetodi / luokkametodi

• Tyypillisesti luokassa määritellään metodeja, joilla voidaan käsitellä ko. luokan mukaisia olioita kohdistamalla ko. metodin kutsu ko. luokan olioon. Tällaisia metodeja kutsutaan esiintymämetodeiksi, koska niitä voidaan kohdistaa vain luokan mukaiseen esiintymään, olioon.

• Joskus taas halutaan luokassa määritellä metodi, jota voidaan käyttää ei-olio-ohjelmoinnin tavoin, jolloin metodi ei vaadi luokan esiintymää eikä metodin kutsua kohdisteta olioon. Tällaista metodia kutsutaan luokkametodiksi.

Java: jos kyseessä on luokkamuuttuja tai luokkametodi, kirjoitetaan sen määreeksi esittelyn yhteydessä static. Näin ollen Javassa luokkamuuttujaa ja luokkametodia kutsutaan joskus myös staattiseksi. Jos määre static puuttuu, tarkoittaa se, että kyseessä on esiintymämuuttuja tai esiintymämetodi.

Siis: aina kun otamme luokkaan mukaan muuttujan tai metodin, niin meidän tulee miettiä mihin yllä olevista ryhmistä se kuuluu eli sitä kirjoitetaanko sen määreeksi static vai eikö. Yleensä ei tule määrettä static.

Page 58: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

58

Esiintymämetodit jaetaan vielä toiminnallisuuden mukaan

• luonti- eli konstruktori- eli alustusmetodeihin (metodin nimi = luokan nimi, käytetään olion luonnin yhteydessä, jolloin suoritetaan tietokenttien alustus),

• havainnointimetodeihin (olion tila ei muutu: tietokenttien arvoja ei muuteta) ja

• muutosmetodeihin (olion tila muuttuu: ainakin yhden tietokentän arvoa muutetaan).

Tätä ei ilmaista millään määreellä itse ohjelmakoodissa, vaan tämä on looginen jako.

Näkyvyys

Ohjelmakoodissa tulee ilmaista metodin ja tietokentän näkyvyys (public/private/protected/määrettä ei ole lainkaan), joka määrää ko. tietokentän tai metodin näkyvyyden ja käytettävyyden muissa luokissa. Näkyvyysalueena on aina pakkaus (package, katso s. 10), joka on yksinkertaisimmillaan kansio (hakemisto), jossa ko. luokka on. Lisäksi käyttäjä voi määritellä omia pakkauksia, mutta tästä asiaa ei käsitellä tällä kurssilla. Näkyvyysmääreet:

• public: käyttö sallittu kaikissa luokissa (mutta vaatii tietenkin import-lauseen, jos halutaan käyttää luokissa, jotka ovat eri pakkauksessa)

• private: käyttö sallittu vain luokan itsensä sisällä

• protected: private + voi käyttää aliluokissa23 ja samassa pakkauksessa olevissa luokissa.

• määre puuttuu: voidaan käyttää niissä luokissa, jotka ovat samassa pakkauksessa.

Ohjeet yllä esitettyjen määreiden käytöstä annetaan kootusti s. 61.

Kuinka luokan tietokenttiin ja metodeihin voidaan viitata

Alla oleva kuvaa tarkan syntaksin, joten lue sen jokainen sana huolella. Nämä ovat tulleet esille jo aiemmin, mutta ne esitetään tässä kootusti.

• viittaus sen luokan sisällä, jossa tietokenttä tai metodi esitellään: Ko. luokan metodeissa voidaan kaikkiin luokan metodeihin ja tietokenttiin viitata suoraan kirjoittamalla kentän tai metodin nimi tai kirjoittamalla this.<metodin kutsu tai tietokentän nimi>. Esim. luokassa Henkilo kirjoitetaan suoraan nimi tai this.nimi. Tietenkin voidaan käyttää myös tietokentän havainnoivaa metodia esim. getNimi() tai this.getNimi(). Kuitenkaan luokkametodin (static) yhteydessä ei saa käyttää this:iä, koska metodin kutsua ei kohdisteta olioon.

23 Liittyy perintään, johon palaamme myöhemmin

Page 59: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

59

• luokan tietokenttien ja metodien käyttö muissa luokissa:

1. jos näkyvyys on private: ei voi viitata suoraan. Tietokenttiä voidaan käsitellä luokassa olevien julkisten (public) havainnointi- ja muutosmetodien kautta; esim. h.getNimi()

2. jos näkyvyys on public (eli kyseessä on julkinen tietokenttä tai metodi):

o jos määreenä ei ole static, niin kyseessä on julkinen esiintymämuuttuja tai esiintymämetodi (kohdistetaan olioon): viitataan muodossa

<olio>.<metodin kutsu tai tietokentän nimi> Esim. h.setOsoite(uusi);

Julkisten tietokenttien käyttö ei noudata meidän vaatimaa kapselointiperiaatetta.

o jos määreenä on static, niin kyseessä on julkinen luokkamuuttuja (tai vakio) tai luokkametodi, jonka kutsu kohdistetaan luokkaan: viitataan muodossa

<luokan nimi, jossa metodi tai tietokenttä on määritelty>. <metodin kutsu tai tietokentän nimi>

Esim. Luokassa Henkilo ei ole tällaisia, mutta esim. luokassa Math on vakio PI, johon viitataan ilmauksella Math.PI, ja metodi max, johon viitataan lausekkeella Math.max(x,y) (se palauttaa suuremman x:stä ja y:stä). Jaksolla AOP käsittelimme myös metodikirjastoja TaulukkoMetodiKirjasto ja OmaIO, jonka kaikki metodit ovat staattisia luokkametodeja.

Esimerkki. luokan Henkilo (s. 50) tietokentät (muuttujat) ovat nimi, osoite ja syntVuosi, jotka esitellään luokan alussa. Ne ovat kaikki esiintymämuuttujia, koska ne voivat olla erilaisia ko. luokan olioilla. Sen vuoksi emme kirjoita näihin määrettä static.

Luokassa ei ole yhtään luokkametodia, vaan jokaista metodia sovelletaan aina luokan olioihin.

Käyttö muissa luokissa: <olion nimi>.<metodin nimi>(<parametrit>);

Kaikki luokan metodit ovat siis esiintymämetodeja, jotka on jaoteltu oikeaoppisesti edellä mainittuihin loogisiin ryhmiin: havainnointi- ja muutosmetodit.

Luokassa määritellyt asiat on ryhmitelty oikeaoppisesti neljään osaan:

• Tietokentät (tässä vain esiintymämuuttujia)

• Konstruktorit (tässä vain yksi kappale)

• Havainnointimetodit

• Muutosmetodit

Java-kieli ei tällaista jakoa vaadi, mutta sitä tulee noudattaa luettavuuden lisäämiseksi. Esimerkissä on myös noudatettu aiemmin esitettyä suositusta suojata muuttujat private-määreellä, mikä aiheuttaa sen, että kutakin muuttujaa kohti on kirjoitettu oma havainnointimetodi.

Page 60: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

60

Käytännön ohjeita: main-metodin sisältävä luokka vs. muut luokat

Luokkaa, jossa on main-metodi kutsutaan (pää)ohjelmaksi ja se on suorituskelpoinen. Sen sijaan luokkaa, jossa ei ole main-metodia ei kutsuta ohjelmaksi, koska sitä ei voi suorittaa, vaan tällaisessa luokassa määritellään joko tietotyyppi (esim. Henkilo, Piste, Tili, ...) tai metodikirjasto. Kuitenkin jokaiseen luokkaan voidaan kirjoittaa main-metodi, joten jokaisesta luokasta saadaan suorituskelpoinen ohjelma. Tätä ominaisuutta voidaan käyttää esim. testauksen yhteydessä, kun halutaan testata luokan avulla määritellyn tietotyypin toimintaa, eikä niinkään vielä käyttää tietotyypin mukaisia olioita oikeassa ohjelmassa.

Yleensä kirjoitamme yhden luokan, joka ei määrittele mitään tietotyyppiä ja joka sisältää main-metodin. Tämä luokka (pääohjelma) toimii eräänlaisen keskusyksikkönä, jossa luodaan ja käsitellään muiden luokkien (esim. kirjastoluokkien Vector ja String tai omien) avulla määriteltyjä olioita. Se sisältää usein myös muita staattisia (joiden määre on siis static) metodeja, jotka ovat usein näkyvyydeltään public, jolloin niitä voidaan käyttää tarvittaessa myös muissa luokissa. Pääohjelmassa voidaan myös määritellä static muuttujia (eli luokkamuuttujia) ja vakioita, jotka ovat usein näkyvyydeltään private ja ne ovat siis käytettävissä vain tässä luokassa.

Jos määrittelet luokkien avulla tietotyyppejä (esim. Henkilo), joita tarvitset ohjelmassasi, niin tee kustakin luokasta omat tiedostonsa, jossa ei ole main-metodia. Kaikki nämä luokat tulee kääntää (compile), jotta ne olisivat muiden luokkien käytettävissä. Tämän jälkeen luokan mukaisia olioita voidaan luoda ja käsitellä luokan julkisilla (näkyvyysmääre public) metodeilla missä muussa luokassa tahansa. Näiden lisäksi sinun tulee tehdä luokka, joka sisältää main-metodin ja joka on siis varsinainen (pää)ohjelma (joka siis käännetään ja suoritetaan). Myös se tulee tallentaa omaksi tiedostokseen. Emme myöskään määrittele omia pakkauksia, vaan sijoitamme kaikki luokat samaan kansioon.

Page 61: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

61

Yhteenveto sopivista tietotyypin tietokenttien ja metodien määreistä, kun noudatamme kapselointiperiaatetta

Erilaisia luokan, (esiintymä- ja luokka) muuttujien, vakioiden ja metodien määreitä on niin paljon, että aluksi käyttäjä kaipaa yksinkertaistettuja sääntöjä määreiden käytöstä. Seuraavassa on ohjeet määreiden käytöstä (ei koske pääohjelmaa eli siis luokkaa, jossa on main-metodi) ja konstruktoreiden ja havainnointimetodien teosta.

Lähes poikkeuksetta luokan, joka määrittelee tietotyypin ja jossa on siis tietokenttiä (eli esiintymä- ja/tai luokkamuuttujia) ja metodeja, määrittelyn alussa on sana public ennen class-sanaa. Tällöin luokka on julkinen ja luokan mukaisia olioita voi muodostaa kaikissa luokissa. Luokan määreenä voi olla myös private, abstract ja final.

Tyypillisesti luokan metodit ovat public (näkyvät kaikissa luokissa), mutta kaikki esiintymämuuttujat ovat private (voidaan käyttää vain siinä luokassa, jossa metodi on esitelty). Jos määrittelemme luokalle aliluokkia, niin yliluokan tietokentän määreeksi voidaan asettaa protected (tarkennamme tätä kun käsittelemme perintää), jolloin se näkyy myös aliluokissa. Metodin määreeksi voi laittaa myös private, jos metodia ei käytetä missään muussa luokassa (tämä on harvinaisempaa). Tietokentän määreeksi voi laittaa myös final, jolloin sen arvoa ei voi muuttaa sen jälkeen kun sille on annettu arvo.

Jos luokan tietokentän tai metodin määre on static, niin ko. tietokenttää (tämä on tällöin luokkamuuttuja, joka on siis jokaiselle oliolle sama) tai metodia voidaan käyttää luomatta ko. luokan oliota. Jos lisäksi on määre public, niin sitä voidaan käyttää kaikissa luokissa. Lähes poikkeuksetta määrettä static ei käytetä, jolloin kaikki operaatiot kohdistetaan olioihin. Tästä syystä puhumme olio- tai oliokeskeisestä ohjelmoinnista.

Tekninen huomautus: Ilmaus ”kaikissa luokissa” edellyttää sitä, että kääntäjä tietää missä pakkauksessa luokan käännetty versio sijaitsee. Jos se ei sijaitse samassa kansiossa missä on sitä käyttävä luokka, niin luokan sijainti tulee ilmoittaa classpath-ympäristömuuttujalla. Mutta kuten aiemmin todettiin, me sijoitamme kaikki luokat samaan kansioon tietokoneessa, jolloin classpath-muuttujalle ei tarvitse yleensä tehdä mitään (ks. kurssisivut).

Page 62: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

62

Harjoitustehtävät

Jos lähetät ratkaisusi, liitä mukaan kuvaruutukaappaus, joka todentaa sen, että metodisi/ohjelmasi toimii.

1. Tarkastellaan luentopäivän yhteydessä olevaa luokkaa Tili (s. 86). Kirjoita lauseet seuraavien tehtävien suorittamiseksi: (a) luo Tili-olio kummallakin konstruktorilla (keksi tiedot itse, piirrä kuvat!) (b) luo Tili-olio x (siis x viittaa siihen), jonka tilikoodi=”123”, omistaja=”Oili” ja saldo=698000. Kirjoita seuraavat lauseet tämän tilin käsittelemiseksi: - sijoita muuttujaan s tilin saldo - talleta tilille muuttujassa z oleva rahamäärä (saldo siis kasvaa) - muuta korkoprosentiksi 2 Kirjoita testiluokka, jossa testataan näiden lauseiden toiminta.

2. – 3. Tarkastellaan yksinkertaistettuja automaattivaihteisia autoja. Oletetaan, että toiminnallisuutena ominaisuutena autolla on vain nopeus 0…200 km/t ja että auto on aina tilassa ”pakkipäällä” tai ”eteenpäin”. Lisäksi autolla on merkki ja väri (merkkijonoja). Kirjoita luokka Auto, jossa on seuraavat metodit: Auto(String väri, String merkki) luo parametrien mukaisen auton, jonka nopeus 0 ja automaattivaihteisto on asennossa ”eteenpäin”. mikaNopeus() palauttaa auton nopeuden mikaVari() palauttaa auton värin mikaMerkki() palauttaa auton merkin mikaSuunta() palauttaa tiedon auton kulkusuunnasta pysayta() asettaa auton nopeudeksi nolla pakkiPaalle() asettaa peruutusvaihteen päälle (asettaa ensin nopeuden nollaksi). Jos peruutusvaihde on jo päällä, metodi ei tee mitään. eteenpainPaalle() asettaa automaattivaihteen tilaan ”eteenpäin” (asettaa ensin nopeuden nollaksi). Jos eteenpäinvaihde on jo päällä, metodi ei tee mitään. kaasuta(int m) lisää/vähentää nopeutta muuttujan m arvolla (negatiivinen parametri vähentää eli kyseessä on jarrutus). Kuitenkaan arvoa 0 ei saa alittaa eikä arvoa 200 saa ylittää. Lisäksi pakitustilassa maksiminopeus on 50. Ohje: luokan tietokenttinä ovat väri, merkki, nopeus ja suunta (joka voi olla esim. boolean tyyppinen; true: eteenpäin ja false: pakitus).

4. Tarkastellaan luokkaa Auto. Kirjoita testiluokka, jossa ovat ainakin seuraavat lauseet: 1) Luo punainen Lada, 2) aseta sen nopeudeksi 100, 3) tulosta auton nopeus, 4) pysäytä auto ja lähde pakittamaan 30:iä, ja 5) tulosta auton tila ja ominaisuudet (tee tätä varten luokkaan Auto toString-metodi).

5. Tarkastellaan luentopäivällä käsiteltävää luokkaa Pankki. Kirjoita luokkaan metodi /** Tulosta sellaisten henkilöiden tilitiedot, joiden saldo>raja */ public void tulostaRajanYlittävät(double raja)

Page 63: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

63

VIIKKO 7

Olio-ohjelmien suunnittelu, oliot koostuvat olioista, sisäluokka.

Tämän viikon materiaali koostuu alla olevasta ja Liitteen 2 s. 134–136. Lue ensin alla oleva ja siirry Liitteeseen 2 kun niin tekstissä sanotaan.

Tällä viikolla uutta asiaa on vähän, joten sinun tulee tutustua jo tällä viikolla seuraavan viikon materiaaliin, joka on laaja. Sitä ei ole haluttu jakaa osiin, koska se on kokonaisuus. Tutustu myös Luentopäivän materiaaliin.

Olio-ohjelmoinnin idea lyhyesti

Suuren ohjelmointitehtävän hallitsemiseksi ja jäsentämiseksi pitää soveltaa joitain järkeviä periaatteita. Olio-ohjelmoinnissa strategiana on etsiä toteutettavasta sovelluksesta mielekkäitä olioita ja ryhmitellä samankaltaiset oliot luokiksi. Oliot rakennetaan niiden sisältämän tietosisällön ympärille, toteuttamalla tietojen käsittelyssä tarvittava toiminnallisuus. Luokkia löydetään usein etsimällä sovellusalueen kuvauksesta substantiiveja. Esim. pankkisovelluksessa näitä voisivat olla Asiakas, Tili, Laina jne. Toimintoja olisivat mm. tilin perustaminen, lainan otto, talletus, nosto, koron maksu, saldokysely jne., jotka toteutetaan kirjoittamalla luokkaan vastaavat metodit. Suurissa järjestelmissä luokkia ja niiden metodeja kehitetään usein välittömiä sovellustarpeita laajemmin varautuen tuleviin vaatimuksiin. Tärkeänä motivaationa on mallinnuksen lisäksi luokkien ja metodien uudelleenkäyttö; samaa ohjelmakoodin pätkää ei tulisi kirjoittaa moneen kertaan.

Tunnuksien nimeämiskäytäntö

• luokat nimetään yleisesti substantiiveilla ja niiden nimen ensimmäinen kirjain kirjoitetaan isona kirjaimena

• kenttien nimet ovat yleensä substantiiveja (totuusarvot voivat olla myös adjektiiveja tai kysymyksiä) ja niiden ensimmäinen kirjain kirjoitetaan pienellä, mutta vakioiden nimet kirjoitetaan kokonaan isoilla

• metodien nimet ovat yleensä verbimuotoisia ja ne aloitetaan pienellä kirjaimella lisäksi on hyvä käyttää get- ja set-etuliitettä: esim. getNimi(), setNimi(String uusiNimi)

• jos nimi koostuu useammasta sanasta, toisesta sanasta eteenpäin kaikki sanat kirjoitetaan isolla alkukirjaimella tai sanojen välissä voi käyttää alaviivaa

• tunnuksien tulee olla kuvaavia ja ytimekkäitä

Page 64: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

64

Miksi tietosisältö pitäisi kätkeä?

Luokan metodit tarjoavat keinon päästä ko. luokan esiintymäolion sisälle joko tekemään havaintoja tai muuttamaan tietosisältöä. Tämä kuvaa samalla kapselointi-ideaa: olion sisältöön tulisi ulkopuolelta päästä käsiksi vain metodien kautta. Nämä muodostavat eräänlaisen rajapinnan luokan ympärille. Tässä herää ehkä kysymys, miksi tietosisältö pitäisi kätkeä (tietokentän määreenä siis private); joudutaanhan sen vuoksi kirjoittamaan useita ylimääräisiä metodeja, jotta tietoihin päästään käsiksi. Tarkoituksena on estää tietosisällön kontrolloimaton muuttaminen ja havainnointi. Esim. luokassa Tili on estetty se, että luokan käyttäjä (esim. joku muu ohjelma) voisi asettaa saldoksi tietyn summan suoraan24 (vain nosto ja talletus on sallittu). Lisäksi tietotyyppi saattaa sisältää kenttiä, joiden sisältöä ei saisi muuttaa lainkaan. Tällöin kenttä varustetaan metodilla private ja tietokentälle ei kirjoiteta muutosmetodia lainkaan. Nämä seikat liittyvät olion eheyteen eli siihen, että olion tietokenttien arvojen tulee olla aina ’laillisia’ ja että niihin voidaan suorittaa vain tietynlaisia operaatioita. Olioiden tietosisältöä pystytään kontrolloimaan paremmin, kun kaikki muutokset tehdään hyvin suunniteltujen muutosmetodien kautta. Toisaalta huonosti suunniteltujen muutosmetodienkin kautta olio voi joutua hetkellisesti laittomaan tilaan. Esimerkiksi viime viikon Auto-luokan metodi kaasuta voidaan tehdä niin, että nopeuden lisäys suoritetaan heti nykyiseen nopeuteen ja vasta sen jälkeen tarkistetaan ylittyykö ala- tai yläraja ja suoritetaan tarvittaessa korjaus. Tällöin olio on hetken laittomassa tilassa ja näin rikomme olion eheyttä.

Luokan kirjoittaja määrää sen, mitkä tiedot näkyvät ulospäin, ja mitä luokan mukaisille alkioille saa muualla tehdä: mitä tietoja voi katsoa (havainnointimetodit) ja mitä tietoja saa muuttaa (muutosmetodit) ja minkälaiset muutokset ovat sallittuja. Kyseessä on siis mallinnuksen lisäksi systeemin suunnitteluperiaate.

Olioiden koostuminen olioista

Lue Liitteen 2 s. 134–136 ja sen jälkeen alla oleva.

Luokkien väliset rakenteelliset suhteet ovat erittäin tärkeitä suunnittelukohteita olio-ohjelmoinnissa. Kun oliot koostuvat olioista, puhutaan usein myös ns. toimittaja – asiakas suhteesta. Kysymys on käsittelyn jakamisesta luonteviin osiin, pyrkien lisäämään ohjelmakoodin uudelleenkäyttöä ja helpottamaan ylläpitoa.

Luokkien väliset suhteet voivat kuvastaa erilaisia yhteyksiä olioiden välillä:

• Olioiden rakenteellinen jako osiin, esim. yliopisto – tiedekunta – laitos – oppiaine – opettaja

• Omistajasuhde, esim. asiakas – pankkitili

• Hierarkkinen ylempi-alempi –suhde, esim. esimies – alainen, vanhempi – lapsi

Se kumpi olio on toisen osa riippuu tilanteesta. Pankkiesimerkissä tilin omistaja voi olla tilin osa:

24 Tosin hakkeri olisi tästä kyllä mielissään …

Page 65: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

65

public class Tili { private long tilikoodi; … private Henkilo tilinOmistaja; ... } // Tili

Kuitenkin tilanne voi olla myös toisin päin; omistajalla voi olla useita tilejä (taulukko): public class TilinOmistaja { private String hetu, nimi, osoite; // Henkilötiedot private Tili[] omistajanTilit; }

Itse asiassa tilanne voi olla symmetrinen. Tällöin saattaa olla kyse redundanssista, eli sama suhde esitetty kahteen kertaan. Tällöin pitää olla erityisen tarkkana muutosten suhteen; molempia osapuolia voidaan joutua muuttamaan. Jos esimerkiksi tilin omistaja vaihtuu, pitää muutos tehdä sekä Tili-olioon että vanhaan ja uuteen omistajaan.

Erityisesti kannattaa huomiota, että tässä ei ole perintäsuhde, jota käsitellään myöhemmin.

Sisäluokka

Tällä kurssilla emme määrittele sisäluokkia eikä tätä vaadita tentissä. Alla on lyhyt kuvaus sisäluokasta.

Luokan sisälle voidaan kirjoittaa myös tavallinen luokan määrittely, jolloin puhutaan sisäluokasta (inner class). Tyypillisesti sisäluokkaa käytetään ympäröivän luokan apuna eikä muissa luokissa. Nimittäin luokkahan ja myös siis sisäluokka määrittelee yleensä tyypin ja tällainen tyyppi (luokka) voidaan siis sijoittaa luokkaan, jossa sitä tarvitaan, sisälle. Toinen mahdollisuus on tietenkin tehdä siitä ihan oma luokkansa. Sisäluokan näkyvyydeksi asetetaan usein private, jolloin sitä ei voi käyttää mistään muusta luokasta käsin. Ulko- ja sisäluokassa voidaan käyttää kummankin private piirteitä (tietokenttiä ja metodeja). Sisäluokasta annetaan esimerkki kurssisivuilla, jossa määritellään luokka Kortti (Cad), jonka sisäluokkana määritellään luetellut tyypit Rank (kortin numero) ja Suitte (kortin maa) lueteltuna tyyppinä sisäluokan avulla.

Page 66: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

66

Harjoitustehtävät

Jos lähetät ratkaisusi, liitä mukaan kuvaruutukaappaus, joka todentaa sen, että metodisi/ohjelmasi toimii.

1. – 2. Murtoluku on muotoa a/b, missä osoittaja a ja nimittäjä b ovat kokonaislukuja. Alla on suunnitelma luokalle Murtoluku, mutta kirjoita metodien koodit. Murtoluku tulee esittää supistetussa muodossa ja supistamista varten voit käyttää jaksolla AOP esitettyä suurimman yhteisen tekijän laskevaa metodia. Luokassa on siis parametriton esiintymämetodi supista, joka supistaa kohdemurtoluvun käyttäen apunaan luokkametodia syt. Havainnollista Murtoluku-olioiden toimintaa selittämällä alla olevan pääohjelman KoeMurtoluku toiminta (piirrä oliot!). Lisäohjeita ja tehtäviä, joihin pitää siis vastata: Yhteenlasku:

4

17

8

3424

74232

7

4

3==

⋅+⋅=+ ,

joka tarkoittaa desimaalilukua 4,25. Tämä voidaan esittää myös muodossa, jossa on kokonaisosa ja murto-osa, mutta käsittelemme tässä murtolukuja, joissa on vain osoittaja ja nimittäjä. Negatiiviset murtoluvut: osoittaja on negatiivinen ja nimittäjä positiivinen, esim. -2/3, nolla esitetään aina muodossa, jossa nimittäjä on aina 1 eli 0/1.

Yhteenlaskun toteuttavan metodin otsikko alla on muotoa

public Murtoluku add(Murtoluku toinen)

ja sen kutsu x.add(y) palauttaa uuden murtoluvun x+y eikä se muuta kohdemurtolukua x. Näin ollen kutsut x.add(y) ja y.add(x) saavat aikaan saman toiminnan. Laveasti ajatellen voisi ajatella, että kyseessä on konstruktori, mutta emme sijoita tällaisia metodeja niiden alle, vaan teemme tällaisia metodeja varten loogisessa mielessä uuden kategorian: murtolukujen väliset laskutoimitukset.

Metodien implementoinnissa on kaksi vaihtoehtoa: joko kohdemurtoluku muuttuu tai sitten ei. Tässä on valittu jälkimmäinen tapa, joka on luonnollisempi. Metodit voitaisiin toteuttaa myös ’perinteellisesti’ kahdella parametrilla:

public static Murtoluku add(Murtoluku m1, Murtoluku m2)

Millainen tämän metodin koodi on? Esitä tätä metodia käyttävä lause, joka sijoittaa murtoluvun s arvoksi murtolukujen eka ja toka summan. Kumpi edellä mainituista add-metodeista on sinusta luonnollisempi?

Alla oleva luokan Murtoluku suunnitelma ei ole läheskään täydellinen esim. laskutoimitusten suhteen, vaan siitä puuttuu useita metodeja. Mitähän?

Page 67: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

67

Luokan Murtoluku suunnitelma: /***************************************************************** * Murtolukujen (eli rationaalilukujen) luokka. Murtoluku on * muotoa osoittaja/nimittäjä missä osoittaja ja nimittäjä ovat * kokonaislukuja, ja nimittäjä > 0. Negatiivisessa murtoluvussa * osoittaja on < 0. Nolla esitetään muodossa 0/1. *****************************************************************/ public class Murtoluku { /** Murtoluvun osoittaja */ private int os; /** Murtoluvun nimittäjä */ private int nim; // *************************************************************************** // Konstruktori // *************************************************************************** /** Luodaan murtoluku, kun osoittaja ja nimittäjä on annettu, * ja muutetaan esitys standardimuotoon eli nimittäjä b on positiivinen, * jos b on negatiivinen, niin suoritetaan lavennus -1:llä. * Luotu murtoluku on supistetussa muodossa. * Alkuehto: b ei ole nolla. */ public Murtoluku(int a, int b) // *************************************************************************** // Havainnointimetodit // ************************************************************************** /** Palauttaa murtoluvun osoittajan */ public int getOs() /** Palauttaa murtoluvun nimittäjän */ public int getNim() /** Palauttaa kohdemurtoluvun esityksen merkkijonona, esim. 2/3 . Murtoluku on aina * supistetussa muodossa. * Alussa ja lopussa on välilyönnit. */ public String toString()

Page 68: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

68

// ************************************************************************************************************ // Murtolukujen väliset laskutoimitukset. Metodit palauttavat aina uuden murtolukuolion ja // kohdemurtoluku ei muutu. // ************************************************************************************************************* /** Murtolukujen yhteenlasku: Funktio add palauttaa kohdemurtoluvun * ja parametrina annetun murtoluvun (toinen) summan uutena murtolukuna, joka * on supistetussa muodossa. * Kohdemurtoluku ei muutu. * Alkuehto: murtoluku toinen tulee olla luotu. */ public Murtoluku add(Murtoluku toinen) // **************************************************************************************************** // Muutosmetodit // ****************************************************************************************************/ /** Kohdemurtoluvun supistaminen osoittajan ja nimittäjän syt:llä, jos syt on > 1. */ private void supista() // *************************************************************************************************** // Apumetodi // ***************************************************************************************************/ /** Kahden kokonaisluvun suurin yhteinen tekijä, ks. esim. JTKT-moniste. * Alkuehto: x>0 ja y>0. */ private static int syt(int x, int y) } // luokan Murtoluku loppu

public class KoeMurtoluku { public static void main(String[] args) { int os1 = 40; int nim1 = 80; int os2 = -6; int nim2 = -9; // Luodaan murtoluvut. Murtoluku eka = new Murtoluku(os1, nim1); Murtoluku toka = new Murtoluku(os2, nim2); // Tulostetaan murtoluvut ruudulle. System.out.println("Murtoluvut: " + eka + " ja " + toka); // Lasketaan näiden summa ja tulostetaan summa. Murtoluku s = eka.add(toka); // Tässä luodaan uusi murtoluku !!! System.out.println("Summa: " + s); System.out.println("Sen nimittaja on " + s.getNim()); } // main } //luokan KoeMurtoluku loppu

Page 69: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

69

3. Testaa luokkia Murtoluku ja KoeMurtoluku. Murtolukujen eka ja toka summa on 7/6. Tässä tulee piirtää myös kuvat murtolukuolioista. Testaa luokkaasi. Jos lähetät ratkaisusi, mukana tulee olla kuvaruutunäyttö ohjelman suorituksesta.

4. Jatketaan luokkaa Murtoluku. Tee myös metodit less ja equals, joilla on yksi parametri (esim. m1.less(m2) palauttaa arvon true, jos m1<m2; mutta miten verrataan kahta murtolukua?). Sijoita nämä luokassa oikeaan kohtaan. Testaa metodejasi. Jos lähetät ratkaisusi, mukana tulee olla kuvaruutunäyttö ohjelman suorituksesta.

5. – 6. Tee suunnitelma luokalle Kellonaika, joka mallintaa kellonaikaa, kun esitystapana on tunnit (<24), minuutit (<60) ja sekunnit (<60). Suunnitelma tarkoittaa sellaista luokkaa, jossa metodeista on annettu vain niiden ulkoinen kuvaus mutta ei itse ohjelmakoodia (kuten edellä luokka Murtoluku). Toimintoina halutaan mm. kellonajan asetus, lisätä annettua kellonaikaa annetulla kellonajalla (luo uuden Kellonaika-olion ja ’vaihtaa tarpeen mukaan vuorokautta’ eli tunnit on aina <24), sekä laskea kahden kellonajan erotus (alkuehto?). Lisäyksessä ja erotuksessa palautetaan uusi Kellonaika-olio ja parametrina annetaan lisättävä Kellonaika-olio. Näiden lisäksi luokassa tulee olla konstruktori sekä tavalliset havainnointimetodit sekä metodi toString (joka palauttaa kellonajan muodossa 22:58:1). Mukana tulee olla myös metodit, joiden avulla voidaan muuttaa kellonaika sekunneiksi ja pänvastoin. Ohje: lisäysmetodi on muotoa:

public Kellonaika lisääAika(Kellonaika lisäys)

Seuraavalla viikolla tulisi olla valittu tietokonetyö valittu. Näin ollen ottakaa tämä puheeksi jo nyt.

Page 70: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

70

VIIKKO 9

Periytyminen ja luokkahierarkia, abstrakti metodi ja luokka, rajapintaluokka ja Comparable<T>. Tietokonetyön valinta.

Tällä viikolla käsitellään alla oleva ja Liitteen 1 sivut 120–122 sekä Liitteen 2 sivut 137–138. Etene taas alla olevan mukaisesti.

Alustus periytymisestä ja luokkahierarkiasta

Lue Liitteen 2 s. 137–138.

Mallinnettaessa todellisen maailman asioita ja kohteita, ne ovat yleensä jossain suhteessa toisiinsa. Asia saattaa olla jonkin toisen asian erikoistapaus tai yleistys. Tällaiset suhteet voidaan esittää olio-mallinnuksessa periytymisen (inheritance) avulla. Esimerkiksi luokan Eläin aliluokka voisi olla luokka Lintu, koska jokainen lintu on eläin (ns. is a - relaatio). Tällöin tulee

kirjoittaa ensin luokan Eläin määrittely, jonka jälkeen voidaan määritellä luokan Lintu otsikko seuraavasti:

public class Lintu extends Eläin {…}

Luokka Lintu on siis luokan Eläin laajennus (siis sillä on enemmän ominaisuuksia eli piirteitä) ja tällöin sanotaan, että luokka Lintu on luokan Eläin aliluokka. Aliluokka on siis yliluokan tarkennus ja sillä on siis enemmän piirteitä (eli tietokenttiä ja metodeja) kuin yliluokalla. Tämä suhde havainnollistetaan seuraavasti:

Eläin

Lintu

Seuraavaksi esitetään tärkeimmät periytyvyyteen liittyvät asiat ja säännöt, jotka konkretisoidaan sen jälkeen esimerkeillä.

Page 71: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

71

Yleisempi, piirteiltään (tietokentät ja metodit) suppeampi, on yliluokka. Tällöin aliluokka Lintu perii automaattisesti yliluokan Eläin kaikki tietokentät (eli ne ovat olemassa uudelleen kirjoittamatta myös aliluokan olioille) ja kaikki protected/public-metodit. Näiden yhteisten piirteiden lisäksi aliluokalle Lintu voidaan määritellä linnuille tyypillisiä ominaisuuksia (esim. tietokenttä siipien_välin_pituus ja metodi lennä). Luokan Lintu määrittelyssä kirjoitetaan näkyviin vain nämä uudet tietokentät ja metodit, mutta niiden lisäksi luokan Eläin metodin voi kirjoittaa (määritellä) luokassa Lintu uudelleen eri tavalla. Yliluokan private-tietokentät eivät kuitenkaan näy aliluokkaan niin, että niihin voitaisiin suoraan viitata. Näin ollen yliluokan private-tietokenttiä tulee käsitellä aliluokassa yliluokan ei-private (public/protected) metodien kautta. Jos halutaan että tietokenttiin voidaan viitata suoraan aliluokassa, niiden määreeksi asetetaan protected. Tällöin ne näkyvät aliluokissa ja siinä pakkauksessa (s. 10), jossa tarkasteltava luokka sijaitsee.

Luokka määrittelee tyypin, esim. yllä voidaan puhua myös tyypeistä Lintu ja Eläin, ja niinpä joskus sanotaankin aliluokan sijasta alityyppi ja vastaavasti ylityyppi.

Muita esitettyjä käytännön esimerkkejä ovat luentopäivän ohjelmassa olevat Tili ja ShekkiTili sekä Liitteen 1 Employee ja Boss (s. 120, 121). Näissä kaikissa on selvä hierarkia: jokainen lintu on eläin, jokainen shekkitili on myös tili ja myös pomo on työntekijä. Mallinnuksen lisäksi luokkahierarkiasta saatava hyöty on koodin uudelleenkäytettävyys: luokan yhteiset piirteet tarvitsee kirjoittaa vain kerran (ylimpään luokkaan): esimerkiksi luokissa Tili ja ShekkiTili on kummassakin metodit getSaldo, getOmistaja, …, joten niiden ohjelmakoodi tulee kirjoittaa vain yliluokkaan Tili. Lisäksi aliluokassa voidaan määritellä

• uusia esiintymämuuttujia ja metodeja (esim. luokassa Lintu: esiintymämuuttuja: siipien_välin_pituus, metodi: lennä)

• uudelleen jokin esiintymämuuttuja tai metodi. Tällöin usein tarkennetaan metodin toimintaa, koska sitä sovelletaan aliluokan mukaiseen olioon, joka on yliluokan olion erikoistapaus, joten siitä tiedetään enemmän (esim. siipienvälin pituuden palauttava metodi). Tällöin on kyseessä metodin uudelleenmäärittely tai ylikirjoittaminen (overriding) ja esim. metodien yhteydessä metodin tulee olla parametreiltaan ja palautustyypiltään täsmälleen samanlainen kuin yliluokan uudelleenmääritelty metodi. Huomaa, että uudelleenmäärittely on täysin eri asia kuin ylikuormittaminen (ks. jakso AOP). Jos yliluokan metodin tai tietokentän määreenä on final, niin sitä ei voi uudelleenmääritellä aliluokassa.

Aliluokalla voi itselläänkin olla aliluokkia ja näin periytymisellä voidaan rakentaa puumainen luokkien hierarkia.

Luokat Employee ja Boss

Tutustu tässä välissä Liitteen 1 sivuilla 120–122 oleviin luokkiin, joita selvennetään alla. Luokat ovat epätäydellisiä (puuttuu paljon metodeja) ja toimivat vain erimerkkinä perinnästä.

Page 72: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

72

Luokka Employee:

Esiintymämuuttujat ovat nimi, kkpalkka, sotu25 ja osoite. Niille on annettu määre protected (ks. s. 58 sekä Tili/ShekkiTili esimerkki), joten niihin voi viitata suoraan luokan Employee aliluokassa Boss. Luokassa ei ole luokkamuuttujia (tieto, joka olisi kaikille luokan olioille sama), koska muuttujissa ei esiinny määrettä static.

2 konstruktoria, joilla voidaan luoda luokan Employee olioita. Toisessa annetaan nimi, kuukausipalkka ja sotu, mutta osoitteelle annetaan arvo ” ” (blankko, yhden välilyönnin sisältävä merkkijono-olio26). Toiselle konstruktorille annetaan parametreina vain nimi ja sotu ja konstruktorissa asetetaan palkaksi 0 ja osoitteeksi ” ” .

Havainnointimetodit ovat annaNimi ja annaKuukausiPalkka. Luokassa on lisäksi metodi, joka palauttaa nimiketiedon (annaNimike) ja metodi tulosta, joka tulostaa kaikki tiedot.

Luokka Boss:

Koska Boss on luokan Employee aliluokka, luokan määrittely alkaa seuraavasti: public class Boss extends Employee (eli Boss laajentaa luokkaa Employee).

Luokassa Boss on automaattisesti käytössä kaikki luokan Employee esiintymämuuttujat ja metodit. Sen lisäksi luokassa Boss on määritelty uusi esiintymämuuttuja bonus. Luokassa Boss on määritelty 3 konstruktoria (joissa olisi hyvä olla määre public), joissa kaikissa kutsutaan yliluokan konstruktoria Employee. Kutsu on muotoa: super(…)27, missä sulkeisiin kirjoitetaan parametrit sen mukaan, mitä luokan Employee konstruktoria halutaan käyttää. Lisäksi konstruktorissa annetaan arvo esiintymämuuttujalle bonus. Luokassa on uudelleenmääritelty metodit annaNimike, annaKuukausiPalkka ja tulosta. Lisäksi metodin tulosta koodissa on käytetty sen yliluokan eli luokan Employee metodia tulosta: super.tulosta().

Luokka TestEmployees:

Luokka sisältää main-metodin ja on siis suorituskelpoinen ohjelma. Ohjelmassa luodaan ensin luokan Employee esiintymä virtanen. Kutsun parametrien mukaan valitaan sovellettavaksi ensimmäistä konstruktoria. Seuraavaksi luodaan luokan Boss esiintymä soveltamalla sen ensimmäistä konstruktoria, jolloin bonukselle annetaan arvo 0.0f. Sen jälkeen olion murikka bonus-kenttään tehdään parametrina annettu lisäys. Seuraavaksi olioiden virtanen ja murikka tietosisällöt tulostetaan. Sen jälkeen suoritetaan lause employee=murikka, jossa muuttujat edustavat eri luokkia ja ovat siis eri tyyppiä. Ns. dynaaminen sidonta kuitenkin takaa sen, että tämä on mahdollista, kuten alla selitetään.

Huomaa, että tässä meillä on samassa kansiossa kolme tiedostoa Employee.java, Boss.java ja

TestEmployees.java (pääohjelma testausta varten), joista 2 ensin mainittua käännetään ja kolmas (ainoa, jossa on main-metodi) sekä käännetään että suoritetaan (ks. myös opiskeluoppaan s. 60 luku ”Käytännön ...”).

25 pitäisi oikeastaan puhua henkilötunnuksesta

26 olisi parempi antaa tyhjä merkkijono ””

27 varattua sanaa super on käsitelty enemmän luentopäivällä käsitellyssä luokassa ShekkiTili.

Page 73: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

73

Polymorfismi ja dynaaminen sidonta

Perinnän yhteydessä tulee esille kaksi tärkeää asiaa: polymorfismi ja dynaaminen sidonta28. Alla nämä selitetään tarkastelemalla tilannetta, jossa e tyyppiä Employee ja b on tyyppiä Boss.

Employee e

Boss b

Polymorfismi (monimuotoisuus): Yliluokan tyyppisellä muuttujalla voi olla arvonaan myös aliluokan tyyppinen olio eli tässä tapauksessa yliluokan Employee muuttujaan e voidaan asettaa aliluokan tyyppinen arvo b. Tällöin sanotaan, että b on luokan Employee mukainen olio, vaikka se on tyyppiä Boss. Näin ollen asetuslause e=b on mahdollinen. Tällöin e sisältää siis viittauksen aliluokan Boss olioon. Tämän jälkeen e:n tyyppi on edelleen Employee, mutta e:n arvona on tyyppiä Boss oleva olio b. Tämän asetuslauseen jälkeen nousee esiin kysymys: kun e:hen sovelletaan jotain sellaista metodia, joka on uudelleen määritelty sen aliluokassa Boss, niin kumpaa metodia e:hen sovelletaan. Luokan Employee vaiko luokan Boss? Tähän antaa vastauksen dynaaminen sidonta.

Dynaaminen sidonta ohjelman suorituksen aikana: Sovelletaan olion arvona olevan tyypin mukaista metodia. Eli edellisessä tapauksessa e:hen sovelletaan Boss-luokan metodia, koska olio b on tyyppiä Boss. Tämä tuntuukin hyvin luonnolliselta. Liitteessä 1 s. 122 annetaan esimerkki tällaisesta tilanteesta juuri näiden luokkien yhteydessä, kun metodina on tulosta: employee.tulosta(); Jos taas metodia tulosta ei olisi uudelleenmääritelty luokassa Boss, niin silloin sovellettaisiin luokan Employee tulosta-metodia.

Perustyyppisten muuttujien yhteydessä sanottiin, että muuttujan tyyppi ei voi ikinä muuttua. Viittaustyyppisten muuttujien eli olioiden yhteydessä tilanne on hiukan erilainen, koska muuttuja voi viitata alityypin mukaiseen olioon. Sen vuoksi viitaustyyppisellä muuttujalla sanotaankin olevan staattinen tyyppi ja dynaaminen tyyppi. Staattinen tyyppi on se mikä on voimassa käännösvaiheessa (näkyy suoraan muuttujan esittelyssä) ja dynaaminen tyyppi, joka on voimassa suoristusvaiheessa. Lauseen e=b jälkeen e:n staattinen tyyppi on Employeee mutta dynaaminen tyyppi on Boss.

28 Termit ovat hienoja, mutta asia sinällään ei ole mitenkään ihmeellinen.

Page 74: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

74

Muuttujaan voidaan kohdistaa suoraan vain muuttujan staattisen tyypin mukaisia metodeja eli niitä metodeja, joka on määritelty ko. tyypin määrittelevässä luokassa. Jos metodi on uudelleen määritelty aliluokassa, muuttujaan sovelletaan muuttujan arvon tyypin (eli dynaamisen tyypin) mukaista metodia eikä muuttujan staattisen tyypin mukaista metodia (=dynaaminen sidonta). Jos yliluokan muuttujan arvona on alityypin olio, voidaan siihen kohdistaa myös alityypin mukaista metodia suorittamalla downcasting (tyyppipakotus arvon mukaiseen tyyppiin).

Edellinen sanoo siis sen, että lauseen e=b jälkeen e:hen voidaan soveltaa suoraan vain luokan Employee metodeja ja toteutus haetaan luokasta Boss jos metodi on uudelleenmääritelty luokassa Boss. Luokassa Boss on metodi nostaBonusta, jota ei ole luokassa Employee. Tätä voidaan tietenkin soveltaa suoraan muuttujaan b, mutta miten sitä sovelletaan e:hen lauseen e=b jälkeen. Tässä tulee tehdä downcasting eli meidän tulee kirjoittaa: ((Boss) e).nostaBonusta(…). Tätä samaa asiaa käsitellään myös luokan ShekkiTili yhteydessä.

Tätä samaa asiaa (downcasting) voidaan havainnollistaa myös seuraavalla yksinkertaisella esimerkillä. Olkoon Integer a = new Integer(3); Object x=a; Tällöin lause x.intValue() antaa virheilmoituksen ” Static Error: No method in Object has name 'intValue'”. Meidän tulee kirjoittaa siis: ((Integer) x).intValue().

Huom. Javassa on mahdollista vain yksittäisperintä eli luokalla voi olla vain yksi välitön yliluokka, kun taas esim. C++:ssa on käytössä moniperintä (jonka avulla voi saada aikaan tosi sotkun, koska nythän luokalla voi olla useita yliluokkia). Huomaa lisäksi, että kirjastoluokka Object on kaikkien luokkien yliluokka ja näin ollen luokkien perintähierarkia muodostaa puun, jonka juurena on luokka Object.

Tärkeä: Lue seuraavaksi luentopäivän yhteydessä oleva luokan Tili aliluokka ShekkiTili (s. 96) ja sen yhteydessä oleva teksti.

Seuraavaksi käsitellään lyhyesti abstrakteja luokkia ja metodeja sekä rajapintaluokkia. Kurssisivuilla annetaan linkkejä, joista löytyy näistä enemmän tietoa ja myös esimerkkejä. Tässä on hyvä huomata, että kumpikin näistä määrittelee tyypin.

Abstrakti luokka ja metodi (abstract class and method)

Abstrakti luokka ja metodi ilmaistaan kirjoittamalla sen otsikkoriville määre abstract. Abstraktista metodista kirjoitetaan vain sen otsikko, mutta metodin koodin tilalla on vain puolipiste eli toteutusta ei ole kirjoitettu. Abstrakti luokka on kuten muutkin luokat, mutta ainakin yksi sen metodeista on abstrakti. Abstrakti luokka on ikään kuin luokkapohja, jolle voidaan tehdä konkreettinen aliluokka määrittelemällä aliluokassa abstraktit metodit täydellisesti. Näin ollen abstraktista luokasta ei voi luoda olioita, mutta sen konkreettisesta aliluokasta voidaan luoda olioita. Abstraktien luokkien käytöstä on hyötyä esim. mallinnuksessa, jossa ylin luokka ei ole konkreettinen vaan kokoaa yhteiset ominaisuudet ja metodit, jotka tarkennetaan ja implementoidaan aliluokissa. Abstrakti luokka on kuitenkin tyyppi ja se kelpaa mm. muuttujan ja metodin parametrin tyypiksi. Kutsussa olevan todellisen parametrin tulee olla kuitenkin abstraktin luokan konkreettisen aliluokan esiintymä (koska todellisella parametrilla tulee olla arvo ja näin ollen olio tulee olla luotu ja olioitahan voidaan luoda vain konkreettisista luokista). Kuten perinnän tapauksessa yleensä: jos muuttuja on abstraktin yliluokan tyyppiä

Page 75: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

75

(josta ei siis voi luoda oliota), niin sen arvoksi voidaan asettaa sen konkreettisen aliluokan esiintymä (kuten alla olevassa esimerkissä muuttujan k arvoksi, joka on siis tyyppiä Kuvio, voidaan asettaa luokan Ympyra ja Nelikulmio esiintymä).

Esimerkki. Havainnollistetaan abstraktin luokan käyttöä seuraavalla yksinkertaisella esimerkillä, jossa mallinnetaan kuvioita ympyrä ja nelikulmio. Kuviolla on aina sijainti, joka annetaan aiemmin harjoitustehtävissä käsitellyn laajennetun luokan Piste esiintymänä. Ympyrän sijainnin määrää keskipiste ja nelikulmion sijainti annetaan sen vasemman alanurkan koordinaatteina. Luokka Kuvio on abstrakti, koska siinä ei ole annettu metodin getAla() toteutusta, mutta ne annetaan luokan Kuvio aliluokissa Ympyra ja Nelikulmio. Jos tässä olisi jätetty metodi getAla() pois luokasta Kuvio, niin se ei olisi enää ollut abstrakti, vaan konkreettinen yliluokka, jonka mukaisia olioita voitaisiin luoda. Abstraktin luokan käytöllä halutaan usein myös korostaa sitä, että ko. luokka ei ole täydellinen vaan luokkapohja, jolloin sen mukaisia olioita ei voi luoda. Alla on luotu aina uusi Piste-olio, joka asetetaan esiintymämuuttujan sijainti arvoksi. Alla oleva luokka ei ole suinkaan täydellinen ja ei sisällä kaikkia vaadittuja metodeja ja kommentointeja. Luokat Kuvio, Ympyra, Nelikulmio ja Kuviotestaus tallennetaan kukin omaan tiedostoon. Alla käytetään luokan Ympyra toString-metodissa ensimmäisellä viikolla käsiteltyä desimaalilukujen formatoijaa DecimalFormat, jotta ympyrän ala saadaan esitettyä vain kahdella desimaalilla. public abstract class Kuvio { protected Piste sijainti; public abstract double getAla(); public Piste getSijainti() { return sijainti; } public void setSijainti(Piste p) { this.sijainti= new Piste(p.getX(), p.getY());} } // class Kuvio import java.text.DecimalFormat; public class Ympyra extends Kuvio {

protected double säde; public Ympyra(double s, Piste keskipiste) { this.säde = s; this.sijainti = new Piste(keskipiste.getX(),keskipiste.getY()); // luodaan uusi piste, joka antaa sijainnin } public double getAla() { return Math.PI * this.säde * this.säde; } // getAla public String toString() { DecimalFormat f = new DecimalFormat(”0.00”); String ala = f.format(this.getAla()) . replace(‘,’ ,’.’); return “ala on “ + ala +” ja sijainti (keskipiste) on “ +this.getSijainti(); } // toString

} // class Ympyra

Page 76: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

76

public class Nelikulmio extends Kuvio {

protected double leveys, korkeus; public Nelikulmio (double le, double ko, Piste p) { this.leveys = le; this.korkeus = ko; this.sijainti = new Piste(p.getX(), p.getY()); } public double getAla() { return this.leveys * this.korkeus; } // getAla public String toString() { return “ala on “ +this.getAla()+” ja sijainti (vasen alanurkka) on “ +this.getSijainti(); } // toString

} // class Nelikulmio public class KuvioTestaus { public static void main(String[] args) { Kuvio k; Piste p1 = new Piste (0,0); k = new Ympyra(2.3, p1); tulostaTiedot(k); Piste p2 = new Piste (5,5); k = new Nelikulmio(1.9, 3.5,p2); tulostaTiedot(k); } // main public static void tulostaTiedot(Kuvio k) { if (k.getClass() == Ympyra.class) System.out.print("Kyseessä on ympyrä, jonka "); if (k.getClass() == Nelikulmio.class) System.out.print("Kyseessä on nelikulmio, jonka "); System.out.println(k); // Edellä sovelletaan k:hon joko luokan Ympyra tai Nelikulmio metodia toString riippuen siitä // kumman tyyppinen arvo k:ssa on. } // tulostaTiedot } // class Kuviotestaus

Ohjelma tulostaa: Kyseessä on ympyrä, jonka ala on 16.62 ja sijainti (keskipiste) on (0,0) Kyseessä on nelikulmio, jonka ala on 6.6499999999999995 ja sijainti (vasen alanurkka) on (5,5).

Page 77: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

77

Rajapintaluokka (Interface class)

Javassa ei ole moniperintää. Kuitenkin joskus tilanne voi olla se, että luokka perisi luonnollisesti joitakin ominaisuuksia ja metodeja usealta yliluokalta. Tätä varten Javassa on otettu käyttöön rajapintaluokan käsite. Lisäksi luokat voivat ominaisuuksiltaan ja käyttötavaltaan olla osittain samanlaisia, mutta niillä ei ole olemassa suoraa perintäsuhdetta. Jos on tarvetta käyttää toisistaan eroavien luokkien yhteisiä metodeja ottamatta kantaa siihen, mikä erityinen luokka on kyseessä, niin silloin yhteiset metodit voi koota rajapintaluokaksi. Rajapintaluokka pelkästään luettelee noiden yhteisten metodien otsikot ja toteutusta ei saa esittää. Toteutus tulee esittää aliluokissa (kääntäjä tarkastaa tämän) ja toteutukset ovat usein erilaisia, vaikka niiden metodien otsikot ovat samat. Rajapintaluokka voi sisältää myös julkisia luokkavakioita (public static final), mutta ei esiintymämuuttujia. Rajapintaluokan tietokenttien yhteydessä ei tarvitse käyttää määreitä public, static eikä final, koska kaikki muuttujat ovat oletuksena tällaisen tyyppisiä; muut määreet on kielletty! Rajapintaluokan metodien esittelyn yhteydessä ei tarvitse käyttää määreitä public eikä abstract, koska kaikki metodit ovat oletuksena tällaisen tyyppisiä ja muut määreet on kielletty. Näin ollen emme voi luoda rajapintaluokan tyyppisiä olioita. Rajapintaluokka voi myös periä rajapintaluokan.

Rajapintaluokka esitellään käyttäen varattua sanaa interface:

public interface nimi1

Luokan nimi2 sanotaan toteuttavan (implementoi) rajapintaluokan nimi1 kun luokan nimi2 metodit ovat täydellisesti kirjoitettuja, ja rajapintaluokan nimi1 jokainen metodi on implementoitu luokassa nimi2. Tällaisen luokan otsikko on muotoa:

public class nimi2 implements nimi1

Luokka voi myös implementoida useita rajapintaluokkia, jolloin nämä kirjoitetaan pilkuilla erotettuina implements-sanan jälkeen.

Luokkakirjastossa on useita rajapintaluokkia. Ne tunnistaa siitä, että verkossa olevissa kuvauksissa luokan nimi on kursiivilla. Esimerkiksi List<E> on rajapintaluokka ja esimerkiksi Vector<E> , ArrayList<E> ja LinkedList<E> toteuttavat rajapintaluokan List<E>. Näin ollen näiden luokkien samannimiset metodi voi helposti nähdä luokasta List<E> (katso heti!). Rajapintaluokka Collection<E> on taas rajapintaluokan List<E> yliluokka. Katso Liitteen 2 s. 216-217, joissa annetaan yleiskuva Javan kokoelmaluokista ja niiden hierarkiasta. Lisäksi luokat String, StringBuffer ja StringBuilder implementoivat rajapintaluokan CharSequence ja luokat StringBuffer ja StringBuilder implementoivat rajapintaluokan Appendable.

On hyvä huomata, että vaikka emme voi luoda rajapintaluokka-tyyppisiä olioita (esim. lause List<Integer>()=new List<Integer>() on kielletty), niin rajapintaluokka on tyyppi ja se kelpaa mm. muuttujan tyypiksi ja muodollisen parametrin tyypiksi. Lisäksi asetus on sallittu vain rajapintaluokan muuttujaan: esim. jos m1 on tyyppiä List<E> ja m2 on tyyppiä ArrayList<E> oleva olio, niin asetus m1=m2 on sallittu mutta m2=m1 on kielletty. Toinen esimerkki: meillä on luotu StringBuffer olio sb ja CharSequence sc, joka viittaa johonkin merkkijono-olioon. Tällöin sc=sb on sallittu, mutta sb=sc on aina kielletty. sc:sta voidaan tietenkin tehdä StringBuffer olio, jolloin asetus onnistuu: sb=new StringBuffer(sc); Tyyppiin pakottaminen (type cast) alaspäin (downcasting eli erikoistulkinta) eli sb = (StringBuffer)sc; onnistuu, vain jos sc viittaa StringBuffer-olioon eli muuttujan sc dynaaminen tyyppi on StringBuffer.

Page 78: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

78

Muista että muuttujan tyyppi ja sen viittauksen päässä olevan olion tyyppi voivat olla erit. Esimerkiksi voimme kirjoittaa

List <Integer> x = new LinkedList <Integer>();

Tällöin x on tyyppiä List (staattinen tyyppi) ja x:ssä on viittaus LinkedList <Integer> tyyppiseen olioon (dynaaminen tyyppi).

Ongelmia, jotka ratkeavat rajapintaluokan avulla:

1. Meidän tulee tehdä metodi, joka palauttaa esim. suurimman alkion dynaamisesta listasta ja sen pitää toimia olipa lista tyyppiä Vector<Integer>, ArrayList<Integer> tai LinkedList<Integer>.

2. Meidän tulee yksi tehdä metodi, jolla voidaan käsitellä merkkijonoja, jotka ovat tyyppiä String, StringBuffer ja StringBuilder.

Mikä avuksi? Tietysti voimme ylikuormittaa metodeja kirjoittamalla oma metodi jokaista tyyppiä kohti. Tämä on työlästä ja tuntuu turhanpäiväiseltä. Parempi tapa on käyttää rajapintaluokkaa, jos se tarjoaa sopivat metodit, jolla tehtävä ratkeaa.

Esimerkki tapauksesta 1. Kirjoitetaan metodi suurin, jonka parametri on tyyppiä List. Mukana on esimerkinomaisesti myös olion dynaamisen tyypin tarkistus (getClass) ja lopussa vielä lause, jossa tehdään tyyppiin pakottaminen alaspäin.

import java.util.*; public class Suurin_List_Gen {

public static void main (String[] args) { List <Integer> a = new ArrayList<Integer>(); a.add(3); a.add(5); System.out.println(a.getClass()); // tulostuu: class java.util.ArrayList System.out.println("Suurin on " + suurin(a)); List <Integer> b = new LinkedList <Integer>(); b.add(1); b.add(9); System.out.println("Suurin on " + suurin(b)); ArrayList<Integer> h = (ArrayList<Integer>) a // downcasting eli erikoistulkinta } // main /** * Funktio palauttaa listan v suurimman alkion arvon. * Alkuehto: lista ei saa olla tyhjä. */ public static int suurin(List<Integer> v) { int toistaiseksi_suurin = v.get(0); // automaattinen tyypin muunnos! for (int i=1; i<v.size(); i++) if ( v.get(i) > toistaiseksi_suurin) toistaiseksi_suurin = v.get(i); return toistaiseksi_suurin; } // metodi suurin } // class

Page 79: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

79

Edellä a on tyyppiä List mutta viittaa ArrayList-olioon. Tyyppiin pakottaminen onnistuu vain, jos käytetty tyyppi on oikea eli esim. LinkedList<Integer> h = (LinkedList<Integer>) a

ei toimi.

Esimerkki tapauksesta 2. Tarkastellaan ensimmäisen viikon harjoitustehtävää 1 (merkin lukumäärä merkkijonossa). Jos haluamme tehdä metodin, jota voi kutsua olipa merkkijonon tyyppi String, StringBuffer tai StringBuilder, niin silloin merkkijonoparametrin tyypiksi tulee asettaa CharSequence. Nimittäin rajapintaluokassa CharSequence on metodi charAt, jota metodissa tarvitaan. Huomaa, että rajapintaluokassa on myös metodi toString, jolla merkkijono voidaan aina muuttaa String-tyyppiseksi.

Rajapintaluokka Comparable<T>

Kun verrataan olioita, niin useimmiten ei ole selvää mikä on olioiden välinen järjestysrelaatio eli se milloin jokin olio on pienempi (tai yhtäsuuri) kuin joku toinen olio. Tämä määrittely voidaan tehdä rajapintaluokan Comparable<T>, missä T on mikä tahansa oliotyyppi, avulla. Luokka Comparable on pakkauksessa java.lang (ei tarvita siis import-lausetta) ja se sisältää vain yhden metodin

public int compareTo(T o),

joka palauttaa

– negatiivisen kokonaisluvun, jos kohdeolio (this) on pienempi kuin parametri o – nollan, jos ne ovat yhtäsuuret – positiivisen kokonaisluvun, jos kohdeolio (this) on suurempi kuin parametri o Huomaa siis, että ainoastaan palautuksen merkki (negatiivinen/positiivinen/nolla) on merkityksellinen, ei itse arvo. Luokan Comparable<T> sisältö (ilman kommentteja) on:

public interface Comparable<T> {

public int compareTo(T o); }

Tämä on siis rajapintaluokka ja koodia ei ole annettu. Jos luokassa T on metodi compareTo implementoitu, ko. luokan otsikkorivi kirjoitetaan muotoon:

public class T implements Comparable<T >

Joissakin valmisluokissa compareTo on määritelty metodi. Esimerkiksi luokassa String. Voimme siis kirjoittaa esim. ”asd”.compareTo(”abc”), joka palauttaa jonkun positiivisen luvun. Merkkijonojen vertailu tehdään merkki merkiltä ja se perustuu merkkien UNICODE-koodaukseen, joka kirjainten osalta tarkoittaa aakkosjärjestystä.

Luokassa Double (ja kaikissa muissakin kuoriluokissa) on valmiina implementoitu metodi compareTo ja luokan otsikko on muotoa:

Page 80: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

80

public final class Double extends Number implements Comparable<Double>

(Luokat BigDecimal, BigInteger, Byte, Double, Float, Integer, Long, and Short ovat luokan Number aliluokkia. Näistä kahta ensimmäistä emme ole käsitelleet, mutta tutustu!)

Mekin voimme kirjoittaa omaan tietotyypin määrittelevään luokkaamme T metodin compareTo, jolloin me määrittelemme olioiden välisen järjestyksen. Yleensä toteutuksessa negatiiviseksi palautusarvoksi valitaan -1 ja positiiviseksi arvoksi +1. Määrittelyä ei saa tietenkään tehdä miten vain. Määrittelyn tulee toteuttaa luonnollinen transitiivisuuden vaatimus: jos (x.compareTo(y)>0 && y.compareTo(z)>0), niin silloin pitää olla x.compareTo(z)>0 eli käyttäen ”>” operaattoria kuvaamaan metodin compareTo toimintaa: Jos x>y ja y>z, niin silloin pitää olla voimassa x>z olivatpa x, y ja z mitä luokan T olioita tahansa. Lisäksi vaaditaan että equals-metodi ja compareTo antavat saman tuloksen kun verrataan olioiden samuutta. Tämä johtuu siitä, että joissakin valmisluokissa näitä käytetään sekaisin. Metodi equals on määritelty luokassa Object, mutta se tulee määritellä useimmiten uudestaan aliluokassa, jotta se toimisi halutulla tavalla. Näin ollen lausekkeella (x.compareTo(y) == 0) ja x.equals(y) tulee olla sama totuusarvo kaikilla mahdollisilla luokan T olioilla x ja y.

Esimerkki. compareTo-metodin voi kirjoittaa millaisen tietotyypin yhteyteen tahansa, jos haluamme vertailla olioiden järjestystä (vaikka luokkiin Auto, Tili, Kellonaika jne). Edellä tarkasteltiin luokkaa Murtoluku, johon kirjoitettiin metodit less ja equals. Luokkaan kannattaa kirjoittaa myös metodi

public int compareTo(Murtoluku m) {

if (this.os * m.nim < this.nim * m.os) return -1; else if (this.os * m.nim == this.nim * m.os) return 0; else return 1; }

Tällöin luokan Murtoluku otsikkorivi tulee muuttaa muotoon:

public class Murtoluku implements Comparable<Murtoluku>

Sen jälkeen voimme vertailla murtolukuja m1 ja m2 muissa luokissa seuraavasti m1.compareTo(m2), joka siis palauttaa arvon -1, 0 tai 1. Tämän jälkeen metodit less ja equals saadaan toteutettua helposti metodin compareTo avulla.

Mitä hyötyä compareTo-metodista on?

Metodi compareTo kannattaa liittää luokkaan, koska sitä myötä saamme käyttöön monien kirjastoluokkien metodeja, jotka vaativat compareTo metodin. Esimerkiksi lajittelu, kuten seuraavasta näemme. Edellä puhuttiin metodikirjastosta Collections, joka sisältää useita staattisia metodeja dynaamisten listojen käsittelyyn, esimerkiksi lajittelu, joka vaatii sen, että alkioiden välille on määritelty järjestysrelaatio eli listan alkiot toteuttavat luokan Comparable. Edellä tarkastelimme tilanteita, joissa listojen alkiot olivat esim. tyyppiä Double ja Integer. Näille compareTo on määritelty ko. luokissa ja lisäksi automaattisten tyypinmuunnosten vuoksi voimme vertailla näitä olioita samoilla operaattoreilla kuin vastaavia perustyyppistä alkiota: <, <= jne. Sen sijaan jos haluamme lajitella esim. murtolukuja, niin se onnistuu, jos menettelemme kuten yllä on esitetty. Tällöin voimme kirjoittaa esimerkiksi:

Page 81: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

81

Murtoluku eka = new Murtoluku(2, 4); Murtoluku toka = new Murtoluku(2, 8);

System.out.println(eka.compareTo(toka)); // tulostuu: 1 LinkedList<Murtoluku> lista = new LinkedList<Murtoluku>(); lista.add(eka); lista.add(toka); Collections.sort(lista); // murtoluvut lajitellaan suuruusjärjestykseen System.out.println(lista); // tulostuu: [ 1/4 , 1/2 ]

Viimeisessä lauseessa tulostetaan koko lista ja se tapahtuu niin, että listan oliot tulostuvat hakasulkeiden sisälle pilkuilla erotettuina ja jokaiseen listan olioon sovelletaan luokan toString-metodia.

Edellä mainittiin, että equals tulisi uudelleen määritellä (overriding). Jos compareTo on määritelty, niin sen avulla voidaan equals uudelleen määritellä helposti. Luokassa Murtoluku se voisi olla muotoa:

@Override public boolean equals (Object ob) {

if (this ==ob) return true; if (ob==null || !(ob instanceof Murtoluku)) return false; Murtoluku m = (Murtoluku) ob; return this.compareTo(m) ==0;

}

Tämä voidaan yleistää muihin tyyppeihin korvaamalla tyyppi Murtoluku tarkasteltavalla tyypillä. Huomaa, että aiemmin kirjoitimme metodin equals (oli harjoitustehtävänä), mutta sen parametrina oli Murtoluku eli se ei ole luokan Object metodin equals uudelleen määrittely kuten yllä oleva.

Edellä voitaisiin kirjoittaa myös lausekkeen !(ob instanceof Murtoluku) sijasta tutumpi lauseke (ob.getClass() != Murtoluku.class. Operaattori instanceof (ei vaadita tentissä) on ’laajempi’ kuin getClass, koska instanceof antaa arvon true, jos muuttujan ob dynaaminen tyyppi on Murtoluku tai sen alityyppi. Tässä tapauksessa näillä ei ole eroa, koska luokalla Murtoluku ei ole aliluokkia.

Page 82: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

82

Harjoitustehtävät

Jos lähetät ratkaisusi, liitä mukaan kuvaruutukaappaus, joka todentaa sen, että metodisi/ohjelmasi toimii.

1. – 2. Viime viikolla tehtiin suunnitelma luokalle Kellonaika. Implementoi siihen metodien koodit. Käytä siis viime kerran malliratkaisun luokkaa pohjana äläkä nimeä metodeja uudestaan (tai voit kyllä nimetä uudestaan kunhan sitten muutat tehtävässä 3 kutsut vastaaviksi). Tehtävässä 3 on luokalle Kellonaika testiluokka.

3. Kerro tarkasti mitä tapahtuu kun alla oleva luokka KoeKellonaika suoritetaan. Käytä juuri alla olevaa luokkaa ja anna myös tulostus. Piirrä taas kuvat olioista. Testaa ohjelmasi. Jos lähetät ratkaisusi, mukana tulee olla kuvaruutunäyttö ohjelman suorituksesta.

public class KoeKellonaika { public static void main(String[] argumentit) { Kellonaika aika1 = new Kellonaika(12,53,51); System.out.println("aika1 on:"); aika1.tulostaKellonaika(); System.out.println("aika1:n minuutit ovat " + aika1.getMinuutit() +"\n\n"); Kellonaika aika2 = new Kellonaika(15,11,18); aika2.setAika (17,12,19); Kellonaika aika3 = aika2.erotus(aika1); // 'aika3=aika2-aika1' System.out.println("aika2 on " + aika2 + " ja aika1 on " + aika1 + " ja niiden erotus on "+ aika3); Kellonaika aika4 = aika3.lisääAika(aika1); // 'aika4=aika3+aika1' System.out.println("aika4 on " + aika4); aika4.lisääAikaMuuttamallaKohdeOliota(aika1); // 'aika4=aika4+aika1' System.out.println("lisataan aika4:aan aika1, jolloin saadaan " + aika4); } // main } // class

4. Kirjoita luokkaan Kellonaika compareTo-metodi. Testaa metodin toimivuutta lisäämällä luokkaan KoeKellonaika sitä vastaavat lauseet.

Page 83: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

83

5. Tutkitaan polymorfismia ja dynaamisen sidonnan toimintaa. Alla ovat luokat Sika ja PikkuPossu. Ovatko metodeissa olevat kutsut ja asetukset oikein ja muuta tarvittaessa? Jos eivät ole, tee vaadittavat muutokset. Selitä perustellen mitä tulostuu (siis pelkkä tulostus ei riitä, vaan sinun tulee selittää jokaisen main-metodin lauseen toiminta).

public class Sika { public Sika() { } public String äännähdys() { return "röh röh!” ; } public String syö() { return "rousk rousk " + äännähdys(); } } // end class Sika

public class PikkuPossu extends Sika { public PikkuPossu() { super(); } public String äännähdys() { return super.äännähdys() +"oink oink"; } public String itke() { return “viuviu”; } } // end class PikkuPossu

public class SikaKoe

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

Sika s = new Sika(); System.out.println(s.äännähdys()); System.out.println(s.syö()); PikkuPossu p = new PikkuPossu(); s = p; System.out.println(s.getClass()); System.out.println(s.äännähdys()); System.out.println(s.syö()); System.out.println(p.itke()); System.out.println(s.itke());

} // main } // class

Keksi luokille esiintymämuuttujia.

Tietokonetyön valinta

Tarkastele tietokonetöiden aiheita ja niiden tekemiseen liittyviä kurssisivuilla olevia ohjeita. Tällä viikolla työryhmät tulisi olla muodostettu ja aihe valittu. Jos työ tehdään ryhmätyönä, sopikaa säännölliset kokoontumiset/yhteydenotot työn edistämiseksi.

Page 84: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

84

LUENTOPÄIVÄ 5.3.2016

Varmista luentopäivän ajankohta kurssisivuilta. Aluksi tarkastellaan Javan kirjastoluokkia. Sen jälkeen käsitellään olio-ohjelmointiin liittyvät käsitteet ja periaatteet. Päivän pääaiheena on alla esitetty kokoava esimerkki, jonka puitteissa esitellään olio-ohjelmointiin liittyvät käsitteet ja käytännöt. Lopuksi puhutaan tietokonetyöstä ja tentistä.

Tutustu alla olevaan pääpiirteissään ennen luentopäivää.

Joitakin Javan valmiita luokkia

• merkkijonojen käsittely: String, StringBuffer, StringBuilder, StringTokenizer

• matemaattiset funktiot ja vakiot (metodikirjasto): Math

• perustyyppien int ja double kuoriluokat: Integer, Double

• satunnaislukujen generointi: Random

• geneeristen dynaamisten listojen toteutus: Vector<E>, ArrayList<E>, LinkedList<E>.

• pinon toteutus: Stack<E>

• jonon toteutus: Queue<E> (rajapintaluokka eli Interface, käsitellään seuraavalla viikolla)

• ”sanakirjan” toteutus: HashMap<E1, E2> (ei vaadita)

• joukon toteutus: HashSet<E> (ei vaadita)

• staattisia metodeja taulukoille ja listoille (esim. lajittelu): Arrays ja Collections (metodikirjastoja):

Luokkien String, Integer ja Double alkiot eivät ole mutatoituvia eli olioiden sisältöä ei voi muuttaa.

Luokkia tarkasteltaessa tulee kiinnittää erityistä huomiota luokan metodeihin:

• konstruktorimetodit (nimi on sama kuin luokan nimi ja niitä on tyypillisesti useita eri parametreilla, ylikuormitus!)

• muut metodit: luokkakirjastossa ei erotella luokka- ja esiintymämetodeja, mutta se näkyy annetussa ulkoisessa kuvauksessa. Nimittäin luokka- eli staattiset metodit ovat kuvauksissa varustettu määreellä static.

Esimerkki esiintymämetodin käytöstä: jos String st, niin lausekkeen st.substring(0, kohta) arvo on tyyppiä String. Tässä substring on luokan String esiintymämetodi (määrittelyssä ei ole määrettä static).

Page 85: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

85

Esimerkki luokkametodin käytöstä: jos double x, niin lausekkeen Math.abs(x) arvo on tyyppiä double. abs on luokan Math luokkametodi (määrittelyssä on määre static).

Kirjastoluokkien metodeja tutkiessasi kiinnitä huomiota:

• onko metodi funktio vai proseduuri

• mitkä ovat metodin parametrit

• onko metodissa määre static, joka määrää sen onko kyseessä luokka- vai esiintymämetodi ja siis sen miten sitä käytetään (kutsutaan)

Olio-ohjelmoinnin periaatteita

• Olio rakentuu sisältämänsä tiedon ympärille (tietokentät). Olio on luokan esiintymä ja on olemassa vain ohjelman suorituksen aikana. Olio vastaa siis ajonaikaista muistialuetta ja luokka tyyppiä.

• Tieto ja sitä käsittelevät metodit sijoitetaan aina samaan luokkaan. Olion tietokenttiä tulee käsitellä vain olion luokan metodien avulla (poikkeuksena luokka jossa olion tyyppi määritellään).

• Staattinen tietokenttä (static) on kaikille luokasta luoduille olioille yhteinen ja ei-staattinen tietokenttä on jokaiselle oliolle oma.

• Tee luokan käyttäytymisestä (metodit) julkista (public) mutta salaa sen tietokentät (private). Tietosisältö ja toteutustapa on siis kapseloitu asiakkaan (luokan käyttäjän) näkymättömiin.

• Jokainen metodi on joko funktio (havainnointimetodi: olion tilaa eli tietokenttien arvoja havainnoiva) tai proseduuri (muutosmetodi: olion tilaa muuttava). Konstruktori eli alustaja on erikoismetodi, joka saattaa uuden olion eheään tilaan luonnin yhteydessä (new).

• Metodin kutsuja vastaa siitä, että se kutsuu metodia oikein. Luokan on tarjottava riittävät havainnoijat kutsujalle.

Kokoava esimerkki

Seuraavassa esitetään tärkeä kokoava esimerkki: pankin toimintaan liittyvät luokat Tili ja Pankki sekä niiden testaamiseen tehty luokka PankkiKoe. Luokat ovat tietenkin yksinkertaistettuja toimien vain esimerkkeinä. Luokka Tili kuvaa yhden henkilön tilitietoja ja luokka Pankki sisältää dynaamisen listan, johon tallennetaan kaikkien tilinomistajien tilit. Tämä lista koostuu siis Tili-olioista ja se on muotoa ArrayList<Tili>.

Page 86: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

86

Luokka Tili

Ensin valitaan luokan Tili karakterisoivat tiedot eli tietokentät. Seuraavaksi kirjoitetaan luokalle konstruktorit. Sen jälkeen kullekin tietokentälle kirjoitetaan havainnointimetodi ja muutosmetodi. Luokkaan kirjoitetaan myös metodit toString ja tulosta, joista tulosta on turha, koska tässä se tuottaa samanlaisen tulostuksen. Lisäksi luokka sisältää joitakin metodeja tilin käsittelyyn. Tässä kirjoitetaan esim. metodi, jolla saldoon lisätään vuosikorko.

Kun Tili luodaan, on sen korkoprosentti oletusarvoisesti aina 1.0, joskin sitä voi muuttaa luokan muutosmetodilla setKorkoprosentti.

Koska alla on määritelty private double korkoprosentti = 1.0; niin korkoprosentti on esiintymämuuttuja (määrittelyssä ei ole static). Näin ollen jokaisella tilillä on oma korkoprosenttinsa, jota voidaan muuttaa setKorkoprosentti-metodilla. Voisimme myös varustaa sen määreellä static, jolloin se olisi luokkamuuttuja, joka tarkoittaisi sitä, että jokaisella tilillä olisi sama korkoprosentti (tarkemmin: tietokentälle korkoprosentti on varattu vain yksi muistipaikka, joka on yhteinen kaikille tili-olioille). Jos tällöin muuttaisimme yhdenkin tilin korkoprosenttia, niin se olisi myös kaikkien muidenkin tilien korkoprosentti.

Metodin toiminnan kuvausta ja metodin otsikkoriviä yhdessä kutsutaan metodin ulkoiseksi kuvaukseksi. Sen perusteella metodin toiminta tulee selvitä katsomatta itse ohjelmakoodia. Esimerkiksi Javan kirjastoluokissa annetaan vain metodien ulkoiset kuvaukset. Kun teet tietokonetyön suunnitelman, niin siinä tulee esittää vain metodien ulkoiset kuvaukset. public class Tili

{

// **********************************

// Tietokentät:

// **********************************

/** Tilin koodi */

private String tilikoodi;

/** Tilin korkoprosentti */

private double korkoprosentti = 1.0;

/** Tilin omistaja */

private String omistaja;

/** Tilin saldo*/

private double saldo;

// *********************************************

// Alustus olion luonnin yhteydessä: konstruktorit

// *********************************************

/** Luo tili, jonka tilikoodi on tk ja omistaja o. */

public Tili(String tk, String o)

{

this.tilikoodi = tk; this.omistaja = o; this.saldo = 0.0;

}

Page 87: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

87

/** Luo tili, jonka tilikoodi on tk, omistaja o ja saldo s. */

public Tili(String tk, String o, double s)

{

this.tilikoodi = tk; this.omistaja = o; this.saldo = s;

}

/** Parametriton konstruktori: tietokentät saavat oletusarvon */

public Tili() { }

// **********************************

// Havainnointimetodit:

// **********************************

/** Palauttaa tilin saldon. */

public double getSaldo() { return this.saldo; }

/** Palauttaa tilin omistajan */

public String getOmistaja() { return this.omistaja; }

/** Palauttaa tilin koodin. */

public String getTilikoodi() { return this.tilikoodi; }

/** Palauttaa tilin korkoprosentin. */

public double getKorkoprosentti() { return this.korkoprosentti; }

/** Palauttaa vuosikoron rahallisen määrän. */

public double koronMäärä() { return (this.korkoprosentti/100.0)*this.saldo; }

/** Palauttaa true, jos tilin saldo positiivinen. Muutoin palautetaan false. */

public boolean onkoPlussalla() { return (this.saldo > 0.009); }

/** Palauttaa true, jos tilin saldo = 0. */

public boolean onkoTyhjä() { return (Math.abs(this.saldo-0.0) < 0.001); }

/** Tulostaa omille riveilleen tilin koodin, omistajan, saldon ja korkoprosentin */

public void tulosta()

{

System.out.println("Tilikoodi: " + this.tilikoodi);

System.out.println("Omistaja: " + this.omistaja);

System.out.println("Saldo: " + this.saldo);

System.out.println("Korkoprosentti: " + this.korkoprosentti);

}

/** Palauttaa merkkijonon, joka on samanlainen kuin metodin tulosta aikaansaama tulostus */

public String toString()

{

return ("\nTilikoodi: " + this.tilikoodi + "\nOmistaja: " + this.omistaja +

"\nSaldo: " + this.saldo + "\nKorkoprosentti: " + this.korkoprosentti);

}

Page 88: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

88

// **********************************

// Olion muuttaminen: muutosmetodit

// **********************************

/** Lisää rahamäärä tilin saldoon. Alkuehto: rahamäärä >= 0. */

public void talleta(double rahamäärä)

{

this.saldo += rahamäärä;

}

/** Nosta tililtä rahamäärä. Alkuehto: rahamäärä >= 0 ja this.saldo >= rahamäärä*/

public void nosta(double rahamäärä)

// Tästä tehdään luentopäivällä boolean-arvon palauttava funktio! Miksi?

{

this.saldo –= rahamäärä;

}

/** Lisää tilin saldoa vuosikoron verran. */

public void lisääKorko()

{

this.saldo += this.koronMäärä();

}

/** Muuta tilin uudeksi omistajaksi uusiOmistaja. */

public void setOmistaja(String uusiOmistaja)

{

this.omistaja = uusiOmistaja;

}

/** Muuta tilin uudeksi korkoprosentiksi uusiProsentti. */

public void setKorkoprosentti(double uusiProsentti)

{

this.korkoprosentti = uusiProsentti;

}

} // end class Tili

Esimerkki Tili-olion luonnista:

Tili tili = new Tili(”56789”, ”Seppo Mäkelä”, 25.84);

(vrt. Integer x = new Integer(4); String st = new String(”Java”); ) tai

Tili tili; tili = new Tili(”56789”, ”Seppo Mäkelä”, 25.84);

Tällöin muuttuja tili on tyyppiä Tili.

Page 89: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

89

Esimerkkilauseita:

double s = tili.getSaldo(); System.out.println("Saldo on " + s); double z = 120.0; tili.talleta(z); System.out.println(tili);

Huomaa, että lauseet System.out.println(tili) ja tili.tulosta() saavat aikaan saman tulostuksen, koska lauseessa System.out.println(tili) sovelletaan automaattisesti metodia toString muuttujaan tili. Koska luokassa on määritelty toString, niin metodi tulosta voitaisiin kirjoittaa ekvivalentisti muodossa:

public void tulosta()

{

System.out.println(this);

}

jolloin tulostuslauseen sisällä kohdeolioon (this) sovelletaan ensin metodia toString.

Luokka Pankki ja sen testaus

Määritellään seuraavaksi luokka Pankki ja tehdään testiohjelma PankkiKoe, jolla voimme testata luokkia Tili ja Pankki. Meillä on siis 3 tiedostoa: Tili.java, Pankki.java ja PankkiKoe.java, joista 2 ensimmäistä vain käännetään ja viimeksi mainittu käännetään ja suoritetaan. Kaikkien tiedostojen tulee olla samassa kansiossa.

Kun pankki luodaan (perustetaan), pankilla ei ole tilejä lainkaan. Luokkaan Pankki tehdään metodi lisääTili, jolla voidaan lisätä parametrina annettu tili-olio ArrayList-rakenteen tilit loppuun. Pankin kaikkien tilien tiedot tallennetaan siis ArrayList-rakenteeseen, jonka jokaisen komponentin arvo on luokan Tili olio. Luokka Pankki ei ole lähellekään täydellinen. Mitä metodeja esim. puuttuu?

Hyödynnämme ohjelmassa geneerisyyttä eli käytämme rakennetta ArrayList<Tili>, jolloin jokainen komponentti on tyyppiä Tili.

Huomaa, että alla on mm. metodi muutaKorkoprosentit, joka muuttaa jokaisen tilin kokoprosentiksi saman arvon. Tämä ei tietenkään vastaa todellisuutta, vaan on raju yksinkertaistus.

Page 90: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

90

import java.util.*;

public class Pankki

{

// *********************************************

// Tietokentät

// *********************************************

/** Pankin nimi */

private String pankinNimi;

/** Pankin tilit listana */

private ArrayList<Tili> tilit;

/** Pankin katuosoite */

private String osoite;

// *********************************************

// Alustus olion luonnin yhteydessä: konstruktorit

// *********************************************

/** Luo pankki-olion, jonka nimi on n ja osoite on o. Luo tyhjän tililistan. */

public Pankki (String n, String o)

{

this.pankinNimi = n;

this.tilit = new ArrayList<Tili> ();

this.osoite = o;

} // Pankki

/** Parametriton konstruktori: tietokentät saavat oletusarvon */

public Pankki() { }

// *********************************************

// Havainnointimetodit:

// *********************************************

/** Tulostaa pankin tilitiedot. */

public void tulostaTilit()

{

for (int i=0; i<this.tilit.size(); i++) System.out.println(this.tilit.get(i));

} // tulostaTilit

/** Palauttaa sen henkilön nimen (String) , jolla on suurin saldo. */

public String kenelläSuurinSaldo()

{

double suurin = 0.0; String nimi="";

for (int i=0; i < this.tilit.size(); i++)

{

double s = this.tilit.get(i).getSaldo();

if (s>suurin)

{

nimi = this.tilit.get(i).getOmistaja();

suurin = s;

} // if

} // for

return nimi;

} // kenelläSuurinSaldo

Page 91: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

91

/** Palauttaa sen Tili-olion, jolla on suurin saldo. */

public Tili suurinTili()

{

double suurin = 0.0; Tili t = null;

for (int i=0; i < this.tilit.size(); i++)

{

double s = this.tilit.get(i).getSaldo();

if (s>suurin)

{

t = this.tilit.get(i);

suurin = s;

} //if

} // for

return t;

} // suurinTili

/** Palauttaa kaikkien tilien yhteissaldon */

public double saldotYhteensä()

// tässä käytetään for-each rakennetta

{

double summa = 0.0;

for (Tili t : this.tilit)

{

summa = summa + t.getSaldo();

}

return summa;

} // saldotYhteensä

/** Palauttaa kaikkien tilien keskimääräisen saldon */

public double keskimääräinenSaldo()

{

return this.saldotYhteensä() / this.tilit.size();

} // keskimääräinenSaldo

// *********************************************

// Muutosmetodit:

// *********************************************

/** Muuttaa pankin jokaisen tilin korkoprosentiksi arvon uusi. */

public void muutaKorkoprosentit(double uusi)

{

for (int i=0; i<this.tilit.size(); i++) this.tilit.get(i).setKorkoprosentti(uusi);

} // muutaKorkoprosentit

/** Lisää tililistaan tilit tilin t */

public void lisääTili(Tili t)

{

this.tilit.add(t);

} // lisääTili

Page 92: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

92

/** Lukee näppäimistöltä tilitietoja, perustaa vastaavat tilit ja lisää ne tililistan tilit perään. */

public void lueKaikkiTilitiedotNäppäimistöltä ()

{

Tili tili; // tähän tallennetaan näppäimistöltä luetut tiedot

Scanner nappis = new Scanner(System.in);

String s;

do

{

System.out.println("\nAnna tilikoodi ");

String koodi = nappis.nextLine();

System.out.println("Anna tilin omistaja ");

String omistaja = nappis.nextLine();

System.out.println("Anna saldo ");

s = nappis.nextLine();

double saldo = Double.parseDouble(s);

tili = new Tili(koodi, omistaja, saldo);

this.tilit.add(tili);

System.out.println("\nHaluatko jatkaa (k/e) ");

s = nappis.nextLine();

} while (!s.equalsIgnoreCase ("e"));

} // lueKaikkiTilitiedotNäppäimistöltä

/* Ja muut tarvittavat metodit */

} // luokan Pankki loppu

Tarkastellaan hiukan metodia suurinTili, joka palauttaa viittauksen siihen tiliolioon, jonka saldokenttä on suurin. Koska voimme olettaa, että ainakin yksi saldo pankissa on positiivinen, niin suurimman alkuarvoksi voimme asettaa luvun 0 eli asetamme double suurin = 0.0. Lisäksi meillä tulee olla Tili-muuttuja, johon asetamme suurimman tilin. Se on tässä muuttuja t ja sen alkuarvoksi asetetaan null, joka tarkoittaa sitä, että t ei viittaa mihinkään olioon. Sen jälkeen käydään for-silmukassa läpi kaikki ArrayList-rakenteessa tilit olevat tilioliot. Lauseke this.tilit.get(i) palauttaa rakenteen positiossa i olevan tiliolion ja lauseke this.tilit.get(i).getSaldo() sen saldokentän. Jos löydämme suuremman saldon, niin muuttujia suurin ja t päivitetään vastaavasti.

Seuraavassa on luokka PankkiKoe, jolla voidaan testata edellä esitettyjen luokkien toimivuutta.

Luokassa PankkiKoe ei saa näkyä se millaiseen rakenteeseen tilitiedot on tallennettu (tässä ArrayList). Tällöin luokassa Pankki voidaan esim. muuttaa tilitietojen tallennusrakennetta ja luokan Pankki käyttäjän (PankkiKoe) ei tarvitse tietää siitä mitään.

Page 93: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

93

import java.util.*;

public class PankkiKoe

{

public static void main(String[] args)

{

Scanner lukija = new Scanner(System.in);

Pankki pankki = new Pankki("Handelsbanken", "Pankinkatu 2");

pankki.lueKaikkiTilitiedotNäppäimistöltä();

System.out.println("\nAlkuperäiset tilit\n");

pankki.tulostaTilit();

System.out.print("\nPaina enteriä kun haluat jatkaa");

lukija.nextLine();

System.out.println("\nMuutan korkoprosentin \n");

pankki.muutaKorkoprosentit(1.5);

System.out.println("\nMuutoksen jälkeen:\n");

pankki.tulostaTilit();

System.out.print("\nPaina enteriä kun haluat jatkaa");

lukija.nextLine();

System.out.println("\nSuurin saldo on asiakkaalla " + pankki.kenelläSuurinSaldo());

Tili tili; // tähän tallennetaan tili, jossa on suurin saldo

tili = pankki.suurinTili();

System.out.println("Suurin saldo on: " + tili.getSaldo());

System.out.println("ja se on asiakkaalla " + tili.getOmistaja());

System.out.println("Saldot yhteensä ovat " + pankki.saldotYhteensä());

System.out.println("Keskimääräinen saldo " + pankki.keskimääräinenSaldo());

} // main

} // luokan loppu

Alla on ohjelman esimerkkiajo, jossa käyttäjän syöttämä teksti on alleviivattu ja merkki # tarkoittaa enterin painamista. Anna tilikoodi 123 # Anna tilin omistaja xxx # Anna saldo 123 # Haluatko jatkaa (k/e) Anna k tai e k # Anna tilikoodi 321 # Anna tilin omistaja yyy # Anna saldo 321 # Haluatko jatkaa (k/e) Anna k tai e e # Alkuperäiset tilit Tilikoodi: 123 Omistaja: xxx

Page 94: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

94

Saldo: 123.0 Korkoprosentti:1.0 Tilikoodi: 321 Omistaja: yyy Saldo: 321.0 Korkoprosentti: 1.0 Paina enteriä kun haluat jatkaa # Muutan korkoprosentin Muutoksen jälkeen: Tilikoodi: 123 Omistaja: xxx Saldo: 123.0 Korkoprosentti:1.5

Tilikoodi: 321 Omistaja: yyy Saldo: 321.0 Korkoprosentti:1.5 Paina enteriä kun haluat jatkaa # Suurin saldo on asiakkaalla yyy Suurin saldo on: 321.0 ja se on asiakkaalla yyy Saldot yhteensä ovat 444.0 Keskimääräinen saldo 222.0

Page 95: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

95

Harjoitus: Piirrä alle kuva, joka mallintaa muistin tilaa (olioita), kun ohjelmalle on syötetty yllä olevat tiedot (tehdään luentopäivällä).

Page 96: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

96

Perintä: Luokan Tili aliluokka ShekkiTili, staattinen ja dynaaminen tyyppi

Perintä on tämän viikon asioita ja seuraavassa esitetään perinnästä yksinkertainen esimerkki. Kirjoitetaan luokka ShekkiTili, joka on hiukan keinotekoisesti luokan Tili sellainen aliluokka, jonka korkoprosentti on aina 0 (eli sitä ei voi muuttaa). Tämä saadaan aikaan kirjoittamalla konstruktoriin sitä vastaava lause. Luokassa määritellään uudelleen metodit tulosta ja setKorkoprosentti. Tässäkin metodi tulosta on turha, koska meillä on metodi toString, mutta se on mukana esimerkinomaisesti, koska sama metodihan on luokassa Tili. Luokassa ShekkiTili on luokkaan Tili nähden uusi tietokenttä maxOtto ja metodi periKulumaksu. Kentälle maxOtto tulee kirjoittaa havainnointi- ja muutosmetodit.

Luokan Tili kaikki tietokentät olivat näkyvyydeltään private, joten ne eivät näy edes sen aliluokassa ShekkiTili. Nämä tietokentät kuitenkin periytyvät luokkaan ShekkiTili, mutta niitä

tulee havainnoida ja muuttaa käyttäen luokan Tili vastaavia julkisia metodeja. Luokan Tili kaikkien tietokenttien näkyvyysmääreeksi tulee muuttaa protected (ks. s. 58), jos halutaan että ne näkyisivät ja niihin voisi siis suoraan viitata luokassa ShekkiTili. Näkyvyys protected

noudattaa kapselointiperiaatetta siinä mielessä, että luokan Tili tietokentät näkyvät vain siinä

pakkauksessa, missä luokka Tili sijaitsee ja myös luokan Tili aliluokissa (ks. s.10 ja s. 58). Koska emme ole käyttäneet package-määrettä luokassa Tili, niin tämä pakkaus on se hakemisto, jossa Tili sijaitsee.

Luokan ShekkiTili metodeissa käytetään kutsuja super(…): esim. super(tk,o); konstruktorissa ja super.tulosta( ); Niiden avulla voidaan suorittaa yliluokan vastaava metodi. Varattu sana super on siis viittaus kuten this:kin, mutta super viittaa aina yliluokkaan kun taas this viittaa aina siihen luokkaan mihin se on kirjoitettu. Näitä voidaan käyttää myös yksinään ilman metoditarkennusta (esim. alla super(tk,o)), jolloin kutsu super(…) tarkoittaa yliluokan parametreiltaan sopivaa konstruktoria ja this(…) ja tarkoittaa saman luokan sopivaa konstruktoria. Yliluokan konstruktoria (alla super(tk,o);) voi kutsua vain aliluokan

konstruktorissa ja sen tulee olla konstruktorin ensimmäinen lause. Tällaisella kutsulla tyypillisesti alustetaan perittyjä yksityisiä (private) kenttiä. Jos aliluokan konstruktorissa ei kutsuta yliluokan konstruktoria (näin ei ole kuitenkaan luokassa ShekkiTili), niin siinä kutsutaan automaattisesti yliluokan parametritonta konstruktoria ja jos sitä ei ole kirjoitettu, seuraa käännösaikainen virhe. Tämän vuoksi jokaiseen luokkaan on syytä kirjoittaa parametriton konstruktori (vaikka sillä muuten ei välttämättä mitään virkaa olisikaan).

Page 97: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

97

/* Shekkitili; korkoprosentti on aina 0. */

public class ShekkiTili extends Tili

{

// *********************************************

// Tietokentät

// *********************************************

private double maxOtto=500.0;

// *********************************************

// Konstruktorit

// *********************************************

/** Luo shekkitilin, jonka tilikoodi on tk ja omistaja o. */

public ShekkiTili(String tk, String o)

{

super(tk,o); // suoritetaan yliluokan Tili vastaava konstruktori

super.setKorkoprosentti(0.0);

}

/** Luo shekkitilin, jonka tilikoodi on tk, omistaja o, saldo s ja maxotto m. */

public ShekkiTili(String tk, String o, double s, double m)

{

super(tk,o,s);

super.setKorkoprosentti(0.0);

this.maxOtto = m;

}

/** Parametriton konstruktori: tietokentät saavat oletusarvon */

public ShekkiTili() { }

// *********************************************

// Havainnointimetodit

// *********************************************

/** Palauttaa kentän maxOtto arvon */

public double getMaxOtto()

{

return this.maxOtto;

}

/** Tulostaa tilitiedot ja tekstin Tämä on shekkitili */

public void tulosta()

{

super.tulosta( ); // suoritetaan yliluokan Tili metodi tulosta

// Mitä tapahtuisi jos kirjoittaisimme tähän this.tulosta()

System.out.println("maxOtto: " + this.maxOtto);

System.out.println("Tämä on shekkitili");

}

Page 98: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

98

/** Palauttaa merkkijonona tilitiedot ja tekstin Tämä on shekkitili */

public String toString()

{

return super.toString()+"\nmaxOtto: "+this.maxOtto+"\nTämä on shekkitili\n";

/* suoritetaan yliluokan metodi toString joka palauttaa tilitiedot merkkijonona. Liitetään

perään rivinvaihto ja ko. teksti */

}

// *********************************************

// Muutosmetodit

// *********************************************

/** Asettaa kentän maxOtto arvoksi m:n */

public void setMaxOtto(double m)

{

this.maxOtto=m;

}

/** Pitää korkoprosentin nollassa eli parametrilla uusiProsentti ei ole merkitystä. */

public void setKorkoprosentti(double uusiProsentti)

{

super.setKorkoprosentti(0.0); // tämä lause on turha, mutta metodi pitää olla. Miksi?

}

/* Jos saldo >=r, vähennetään r saldosta ja palautetaan true.

* Muuten ei tehdä vähennystä ja palautetaan false */

public boolean periKulumaksu(double r)

{

if (super.getSaldo()>=r)

{

super.nosta(r);

return true;

}

else return false;

}

} // end class ShekkiTili

Metodissa periKulumaksu voitaisiin kirjoittaa myös this.getSaldo() ja this.nosta(r), koska luokassa ShekkiTili ei ole sen nimisiä metodeja, jolloin sovellettaisiin automaattisesti yliluokan vastaavia metodeja. Sen sijaan metodissa tulosta pitää olla super.tulosta(). Mitä this.tulosta() saisi tässä aikaan? Esimerkiksi voimme luoda ShekkiTili-olion: ShekkiTili s = new ShekkiTili (”123”, ”Seppo Pohjola”, 124.15,200.0);

Huom. Tällöin muuttuja s on tyyppiä ShekkiTili, mutta ei tyyppiä Tili. Muuttuja s on kuitenkin luokan Tili mukainen (eli sanotaan, että se käy luokan Tili oliosta) eli asetuslause t=s on sallittu, kun t on tyyppiä Tili. Sen sijaan lause s=t on kielletty. Näitä asioita käsiteltiin jo aiemmin (polymorfismi ja dynaaminen sidonta s. 73). Näin ollen rakenteeseen ArrayList<Tili> voidaan tallentaa myös ShekkiTili-olioita. Tällöin kuitenkin meidän tulee tietää kumpi olio rakenteeseen lisätään, jotta voimme luoda oikeanlaisen olion eli joko Tili- tai ShekkiTili-tyyppisen olion. Tämä voitaisiin toteuttaa esim. seuraavasti:

Page 99: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

99

… System.out.print("Onko kyseessa tavallinen tili (t) vaiko shekkitili (s) ");

char muoto = nappis.nextLine().charAt(0);

if (muoto=='t')

{

tili = new Tili(koodi, omistaja, saldo);

}

else

{ // luetaan arvo kentälle maxOtto, jota ei ollut luokassa Tili

System.out.println("Anna maksimioton määrä");

s = nappis.nextLine();

double m = Double.parseDouble(s);

tili = new ShekkiTili(koodi, omistaja, saldo, m);

}

this.tilit.add(tili);

// nyt tili on tyyppiä Tili, mutta se olio johon tili viittaa on tyyppiä Tili tai ShekkiTili

Tämä kirjoitetaan luokkaan Pankki kohtaan, jossa luetaan tilitiedot näppäimistöltä.

Koska luokissa Tili ja ShekkiTili on kummassakin tulosta-metodi, niin dynaaminen sidonta (ks. s. 73) takaa sen, että kyseiseen olioon sovelletaan oikean luokan mukaista metodia eli luokan ShekkiTili olioon sovelletaan aina luokan ShekkiTili tulosta-metodia, koska se on ko. luokassa määritelty. Sama koskee metodia muutaKorkoprosentit. Sen sijaan jos luokan ShekkiTili olioon kohdistetaan esim. getSaldo-metodi, niin silloin suoritetaan luokan Tili getSaldo-metodi, koska metodia getSaldo ei ole uudelleen määritelty luokassa ShekkiTili.

Tähän saakka olemme puhuneet vaan muuttujan tyypistä, mutta nyt tarkennamme tätä: viittaustyyppisellä muuttujalla on staattinen tyyppi ja dynaaminen tyyppi. Staattinen tyyppi tarkoittaa tyyppiä, joka annetaan muuttujan esittelyssä. Muuttujan t staattinen tyyppi on aina Tili, mutta sen dynaaminen tyyppi voi olla myös luokan Tili aliluokan tyyppi. Nimittäin asetuslauseen t=s jälkeen muuttujan t dynaaminen tyyppi on ShekkiTili (eli siis sen olion tyyppi johon t viittaa). Dynaamisen tyypin saa selville metodilla getClass(), joka on määritelty kaikkien luokkien yliluokassa Object.

Dynaamista tyyppiä hyödyntäen luokkaan Pankki voitaisiin lisätä metodi, joka tulostaa vain shekkitilit (huomaa myös lauseke ShekkiTili.class):

public void tulostaShekkiTilit()

{

for (int i=0; i < this.tilit.size(); i++)

{

if (this.tilit.get(i).getClass() == ShekkiTili.class) System.out.println(this.tilit.get(i));

}

}

Yllä sovelletaan tulostuslauseessa lausekkeeseen this.tilit.get(i) automaattisesti metodia toString. Koska luokassa ShekkiTili on määritelty metodi toString, niin dynaaminen sidonta takaa sen, että siihen sovelletaan luokan ShekkiTili metodia toString eikä luokan Tili metodia toString.

Edellä voitaisiin käyttää myös instanceof operaattoria (ei vaadita tentissä) eli voisimme kirjoittaa if (this.tilit.get(i) instanceof ShekkiTili)…. Tämä operaattori testaa sitä onko this.tilit.get(i)):n dynaaminen tyyppi ShekkiTili tai sen alityyppi, jolloin se palauttaa arvon true.

Page 100: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

100

Tässä tapauksessa näillä ei ole eroa, koska luokalla ShekkiTili ei ole aliluokkaa. Jos taas luokalla ShekkiTili olisi aliluokka A, niin silloin t instanceof ShekkiTili antaa arvon true, jos muuttujan Tili t dynaaminen tyyppi on A eli lausekkeet t instanceof ShekkiTili ja t.getClass()==A.class ovat true, mutta t.getClass()==ShekkiTili.class on false.

Rakenne tilit on muotoa ArrayList<Tili> ja se sisältää siis viittauksia Tili- tai ShekkiTili-olioihin. Dynaaminen sidonta takaa sen, että olioihin sovelletaan oikeata metodia, jos metodi on määritelty kummassakin luokassa. Jos metodi on määritelty vain yliluokassa, niin silloin aliluokan olioon sovelletaan tätä yliluokan metodia. Jos taas aliluokassa on määritelty jokin uusi metodi (kuten metodi periKulumaksu luokassa ShekkiTili), niin meidän tulee tehdä tyyppiin pakottaminen eli tyypin kastaus (type cast) alaspäin (downcasting eli erikoistulkinta).

Koska rakenteen alkiot ovat tyyppiä Tili, niin rakenteen alkioihin voidaan soveltaa suoraan vain niitä metodeja, jotka esiintyvät luokassa Tili (jotka on mahdollisesti uudelleenmääritelty luokassa ShekkiTili). Näin ollen emme voi suoraan soveltaa luokan ShekkiTili metodia, vaikka viittauksen päässä olisikin ShekkiTili-olio. Esimerkiksi jos haluaisimme tehdä luokkaan Pankki metodin, joka perii saman kulumaksun kaikilta shekkitileiltä, niin meidän tulee kirjoittaa metodi:

/* Vähennetään jokaisen tilin saldosta r, jos tilillä on sen verran katetta */

public void periKulutShekkiTileiltä(double r)

{

for (int i=0; i < this.tilit.size(); i++)

{

if (this.tilit.get(i).getClass() == ShekkiTili.class)

((ShekkiTili) this.tilit.get(i))).periKulumaksu(r); // downcasting!!!

}

}

Edellä (kuten allakin) tulee olla myös olla uloimmat sulut ennen metodin periKulumaksu kutsua. Kun kirjoitamme tämän käyttäen foreach-rakennetta, niin näemme lausekkeiden tyypit paremmin. Lisäksi muutamme metodia niin, että se tulostaa kaikkien niiden nimet, joilta veloitusta ei voi tehdä. Tässä voitaisiin tietysti tutkia saldo ennen metodin periKulumaksu soveltamista, mutta tässä käytetään metodin palautusarvoa, joka kertoo sen onnistuiko veloitus (huomaa erilainen for-lause).

public void periKulutShekkiTileiltä(double r)

{

for (Tili t: this.tilit)

if (t.getClass() == ShekkiTili.class)

{

boolean b = ((ShekkiTili) t).periKulumaksu(r); // downcasting!!!

if (!b) System.out.println(t.getOmistaja());

// tässä sovelletaan luokan Tili metodia getOmistaja()

}

}

Tyyppiin pakottaminen pätee tietenkin myös muuttujien asetuksen suhteen. Esim. jos meillä on tyyppiä Tili oleva muuttuja t, joka viittaa ShekkiTili-olioon ja Shekkitili-muuttuja s, niin muuttujan s arvoksi voidaan asettaa t (s=t on kielletty) kirjoittamalla s=(ShekkiTili) t;

Page 101: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

101

Koska protected ei noudata täydellisesti kapselointiperiaatetta on yliluokassa turvallisempaa käyttää tietokenttien määreenä private, jolloin aliluokassa tulee käsitellä perittyjä tietokenttiä vain yliluokan metodien välityksellä. Näinhän yllä on tehty. Toisaalta protected antaa suojauksen pakkauksen sisällä (ks. s. 10), joten jos sijoitamme kaikki yhteenkuuluvat luokat samaan pakkaukseen, niin silloin voimme käyttää protected-määrettä, joka sallii suoran viittauksen tietokenttiin. Tällöin voisimme kirjoittaa luokassa ShekkiTili esimerkiksi lauseen super.setKorkoprosentti(0.0); sijasta lauseen this.korkoprosentti = 0.0;

Huom. Luokat Tili, ShekkiTili, Pankki ja PankkiKoe edellä olevin laajennuksin ovat Moodlessa OOP-foorumilla.

Tehtäviä

Seuraavassa on vapaaehtoisia tehtäviä koko kurssin alueelta. Kaikki tehtävät eivät ole ihan helppoja. Luentopäivän aikana tarkastelemme muutamaa alla olevista tehtävistä.

1. Kirjoita metodi, joka korvaa parametrina annetussa merkkijonossa st parametrina annetun merkkijonon korvattava jokaisen esiintymän parametrina annetulla merkkijonolla korvaava. Luokassa String on vastaava metodi replace, joka tekee saman toiminnon. Ohje: metodin otsikko on public static String korvaa (String st, String korvattava, String korvaava)

2. Kirjoita metodi, public static LinkedList<Integer> satunnaisia(int määrä, int alaraja, int yläraja)

joka palauttaa (ja siis luo) määrä -komponenttisen LinkedList<Integer> olion, jonka jokainen alkio on vähintään alaraja mutta enintään yläraja. Alkiot tulee arpoa käyttäen satunnaislukuja ja lisäksi kaikkien alkioiden tulee olla erisuuria. Kirjoita lause, joka tallentaa muuttujaan lotto satunnaisen lottorivin.

3. Edellä sanotaan: ”Luokkien StringBuilder ja StringBuffer osalta tulee huomioida myös seuraava: equals antaa arvon true vain silloin kun viitataan samaan olioon (siis toimii samoin kuin ==)”. Kirjoita metodi samat, jolla on parametrina kaksi StringBuilder tyyppistä merkkijonoa ja joka palauttaa arvon true, jos näitä edustavien merkkijonojen sisällöt ovat identtiset ja arvon false muussa tapauksessa. Merkkijonoja ei saa muuttaa String-tyyppisiksi. Ohje: Tutki jokainen merkki, jos merkkijonot ovat yhtä pitkiä. Miten tämän voi tehdä lyhyemmin muuttamalla merkkijonot String-tyyppisiksi?

4. Tarkastellaan luokkaa Pankki. Kirjoita luokkaan metodi public void poistaTili(String koodi)

joka poistaa tilikokoelmasta tilin, jonka tilikoodi on koodi. Ohje: Merkkijonojen samuutta tulee tarkastella metodilla equals.

Page 102: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

102

5. Kirjoita metodi public static String taulukkoMerkkijonoksi (int[] v)

joka palauttaa parametrina annetun int-taulukon merkkijonomuodossa, jossa luvut on erotettu toisistaan yhdellä välilyönnillä (esim. jos v={1,2,3}, niin metodi palauttaa merkkijonon "1 2 3"). Ohje: Esim. lausekkeen ””+1 arvo on String-olio ”1”

6. Kirjoita metodi public static int[] merkkijonoTaulukoksi (String s)

joka palauttaa parametrina annetun kokonaislukuja sisältävän (erotettu välilyönneillä) merkkijonon int-taulukkona (esim. jos s="11 22 3", niin metodi palauttaa taulukon {11,22,3}. Ohje: StringTokenizer tai luokan String metodi split.

7. Toteuta Jana -luokka, joka mallintaa kaksiulotteisen koordinaatiston janoja. Käytä Liitteen 2 Piste-luokkaa hyväksi. Tee luokalle konstruktori ja toteuta luokkaan metodi, - joka palauttaa janan pituuden (ks. jakson TTP I opiskeluopas) - jolla voidaan selvittää kumpi kahdesta janasta on pidempi - joka voidaan siirtää jana alkupiste parametrina annettuun pisteeseen (x,y). Mitkä näistä ovat havainnointimetodeja ja mitkä muutosmetodeja. Mitä muita metodeja tarvitaan? Havainnollista luokkaa luomalla Jana-olio ja sovella siihen tekemiäsi metodeja. Ohje: Jana janalla kaksi päätepistettä, jotka ovat tyyppiä Piste. Jana karakterisoidaan siis kahdella Piste-tyyppisellä tietokentällä.

Page 103: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

103

VIIKKO 10

Poikkeukset. Määre final. Graafiset käyttöliittymät (GUI). Tietokonetyö.

Käsitelkää ensin poikkeukset pääpiirteissään alla olevan tekstin ja harjoitustehtävän valossa ja sen jälkeen tietokonetyötä. Älkää kuluttako poikkeuksiin kuin enintään 45 min, koska kokoontumisen tärkein asia on tietokonetyön tarkastelu. Graafisista käyttöliittymistä (Graphical User Interface = GUI) annetaan esimerkki kurssisivuilla, mutta sitä ei vaadita tentissä.

Alustus

Poikkeus on eräänlainen virhetilanne, epänormaali tilanne, ohjelman suorituksen aikana ja se pitää hoitaa jollain tavoin ennen kuin ohjelma voi jatkaa suoritustaan. Poikkeuksesta voi joskus selviytyä, ts. ohjelma voi jatkaa toimintaansa ja ohjelman suoritus voi onnistua. Usein kuitenkin käy niin, että suoritus on pakko keskeyttää, ja informoida käyttäjää asiasta. Poikkeustilanne voidaan hoitaa perinteellisesti ohjelmallisesti (tällöin ei oikeastaan puhuta edes poikkeuksista; kohta 1. alla) tai käyttämällä siihen tarkoitettu mekanismia (Javassa try…catch; kohta 2. alla). Poikkeusten syitä voivat esim. olla:

1. Metodin parametreilla on sopimattomat arvot: alkuehto ei toteudu; esim. kertoma-metodin parametri on negatiivinen. Alkuehdon paikkansapitävyys tulisi tarkistaa ennen kutsua.

2. Ohjelman ulkoinen ympäristö aiheuttaa ongelmatilanteen, esim. virheellinen syöttö näppäimistöltä.

Yksinkertaisia ohjelmia laadittaessa poikkeuksiin pitää varautua lähinnä vain syöttö-/tulostustoimintojen yhteydessä, koska silloin voi aina tapahtua jotain odottamatonta. Erityisesti lukuoperaation kohdalla ei voida luottaa siihen, että käyttäjä syöttää datan virheettömästi, tai että levyltä luettu tieto on oikean muotoista.

Virheellisistä parametriarvoista johtuvat poikkeukset voi useimmiten välttää, jos voimme itse kirjoittaa sekä kutsuvan että kutsuttavan ohjelman. Sen sijaan yleisiin ohjelmakirjastoihin tarkoitetuissa luokissa pitää varautua poikkeuksiin, sillä kutsujasta ja kutsun laillisuudesta ei tällöin ole varmuutta.

Metodin kutsujan pitää hoitaa poikkeus jotenkin; vaihtoehdot ovat:

1. Kutsuja sieppaa poikkeuksen ja käsittelee sen.

2. Kutsuja välittää poikkeuksen ylöspäin omalle kutsujalleen. Tällöin metodin otsikkorivin lopuksi tulee kirjoittaa: throws <poikkeusluokka>

Page 104: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

104

Pääohjelman (main) nostamat poikkeukset käsittelee Javan runtime-systeemi, joka tulostaa asiaankuuluvan virheilmoituksen ja päättää ohjelman suorituksen. Itse käsitellyn poikkeuksen jälkeen voi periaatteessa jatkaa suoritusta, jos ohjelman logiikka sen sallii.

Huom. Tiedostojen käsittelyn (ja siis myös näppäimistöltä syötön ja ruudulle kirjoittamisen yhteydessä) tarvitaan välttämättä poikkeusten käsittely ohjelmakoodissa. Näin ollen poikkeukset tulee ymmärtää tehtäessä yksinkertaisiakin Java-ohjelmia, jotka lukevat syötteen näppäimistöltä ohjelman suorituksen aikana. Me olemme kuitenkin käyttäneet syöttöön ja tulostukseen myös luokkaa OmaIO, jonka metodit sisältävät poikkeusten käsittelyn. Luokan OmaIO metodit ovat sitkeitä: jos käyttäjä antaa virheellisen muotoisen syötteen (esim. lukua luettaessa annetaan kirjaimia), niin tästä annetaan käyttäjälle ilmoitus ja kysytään syötettä uudestaan.

Poikkeukset Javassa

Javassa oliopohjaisuus on ulotettu myös poikkeuksiin: poikkeus on olio ja kuuluu johonkin poikkeusluokkaan. Poikkeusolio luodaan operaatiolla new ja alustetaan konstruktorilla kuten muutkin oliot. Poikkeusluokassa saa konstruktorimetodien ohella olla kaikkia niitä komponentteja kuin muissakin luokissa: luokka- ja esiintymämuuttujia sekä luokka- ja esiintymämetodeja.

Poikkeus nostetaan virhetilanteen yhteydessä kirjoittamalla ohjelmaan throw-lause: throw <poikkeusolio>

Hyvin usein poikkeusolio luodaan juuri throw-lauseessa, joten se tulee muotoon throw (new <poikkeusluokka>(<parametrit>) )

Metodin mahdollisesti nostamat poikkeustyypit luetellaan metodin otsikossa: <määreet> <tulostyyppi> <metodin nimi> ( <parametrit> ) throws <poikkeusluokat> { <runko> }

Huom. Metodin otsikossa tulee olla throws ... vain silloin, kun metodissa (tai sitä kutsuvassa metodissa) on throw-lause.

Oletetaan alla olevan kuvan mukaisesti, että metodi a kutsuu metodia b, ja b kutsuu metodia c. Oletetaan lisäksi, että c voi nostaa poikkeuksen P, mutta b ei halua käsitellä sitä. Tällöin poikkeus P pitää mainita metodin b otsikossa, jotta se välittyisi ylöspäin metodiin a. Tämä voi puolestaan joko käsitellä sen tai välittää sen omalle kutsujalleen, jne. Näin syntyy kutsuketjulle käänteinen poikkeuksen heittoketju, joka päättyy viimeistään Javan runtime-systeemiin.

Page 105: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

105

Kutsuu b siirtää poikkeuksen ylöspäin Kutsuu c nostaa poikkeuksen

Javan poikkeusluokat

Javan poikkeusluokat muodostavat perimyshierarkian, jossa luokka Throwable (”heitettävissä oleva”) on kaikkien poikkeusluokkien kantaisä. Sillä on kaksi aliluokkaa:

1. Error edustaa virheitä, joita ei periaatteessa pitäisi syntyä (epänormaaleja tilanteita, esim. laitevikoja, muistin loppuminen). Error-poikkeuksia ei tarvitse siepata, eikä esitellä metodien otsikoissa. Ohjelman suoritus päättyy, jos tällainen virhe syntyy.

2. Exception pitää sisällään normaalit virhetilanteet, ja ne pitää yleensä käsitellä. Poikkeuksena on sen aliluokka RuntimeException (ja sen aliluokat); sen esiintymät ovat poikkeuksia, joiden syntymisen kutsuja voi periaatteessa estää (noudattamalla kutsun kohteen asettamia rajoituksia esim. parametrien suhteen). Jos tällainen poikkeus kuitenkin pääsee syntymään, niin se välittyy automaattisesti kutsuketjussa ylöspäin, jos sitä ei käsitellä ko. kohdassa. Jos sitä ei käsitellä millään tasolla, ohjelman suoritus päättyy virheeseen, ja Javan runtime-systeemi kertoo kuvaruudulla, mikä poikkeus lopetuksen aiheutti. Esimerkkejä RuntimeExceptionin aliluokista ovat: ArithmeticException: esim. nollalla jako ArrayIndexOutOfBoundsException: viittaus taulukon ulkopuolelle ClassCastException: virheellinen tyyppimuunnos NumberFormatException: syöte ei ole oikeanlaisen luvun esitys

Exception-luokalla on luokkakirjastossa paljon valmiita aliluokkia eri tilanteita varten. Niille on yleensä valittu kuvaavat nimet, jotka päättyvät sanaan –Exception.

Ohjelmoija voi tehdä omia poikkeusluokkia, jotka perivät Exception-luokan. Poikkeus-luokkaan kannattaa sijoittaa esiintymämuuttujia, jotka tallettavat tietoa virhetilanteesta. Poikkeuksen sieppaaja voi sitten tutkia poikkeusolion sisältöä ja jatkaa toimintaa sen mukaan.

Poikkeusluokassa on yleensä määritelty metodi toString(), joka tuottaa asiaankuuluvan virheilmoitustekstin.

public void a( ) { … b(…) …}

public void b(…) throws P { … c(…) … }

public void c(…) throws P { ... throw new P(...);

… }

Page 106: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

106

try … catch –lause

Poikkeusten sieppauksessa tarvitaan uusi kontrollirakenne, try ... catch ... –lause, jonka rakenne on seuraavanlainen:

try <lohko> catch ( <Poikkeusluokka1> <muuttuja1> ) <lohko1> catch ( <Poikkeusluokka2> <muuttuja2> ) <lohko2> ..... catch ( <Poikkeusluokkan> <muuttujan> ) <lohkon>

finally // voi puuttua <lohko>

Lauseen toiminta ovat seuraava:

1. Jos jossain try-lohkon lauseessa (usein metodin kutsu) nostetaan poikkeus, niin try-lohkon suoritus lopetetaan ja siirrytään sopivaan catch-lohkoon. Jos try-lohkon missään lauseessa ei nosteta poikkeusta, catch-lohko(t) ohitetaan. Poikkeuksen syystä luotu poikkeusolio sijoitetaan ensimmäiseen sellaiseen muuttujaan (muuttuja1, muuttuja2,...), joka on poikkeusolion kanssa yhteensopivaa tyyppiä. ”Sijoittaminen” tarkoittaa, että muuttuja tulee viittaamaan poikkeusolioon. ”Yhteensopiva” tarkoittaa, että poikkeusluokka on joko nostetun poikkeusolion tyyppiä tai jonkin sen yliluokan tyyppiä. Usein catch-lohkoja on vain yksi.

2. Jos finally-osa on mukana, se suoritetaan joka tapauksessa lopuksi.

3. try-lohkoja voi olla sisäkkäin. Poikkeustilanteessa valitaan sisin sellainen (poikkeuksen aiheuttanutta metodikutsua ympäröivä) try-lohko, josta löytyy sopiva ”käsittelijä”, eli catch-haara.

Esimerkki: Syöttövirheen sieppaus. try { ... // Luetaan jotain näppäimistöltä; voi syntyä virhe ... } catch (NumberFormatException e) { System.out.println(“Syöttövirhe näppäimistöltä ” + e); }

NumberFormatException on sellainen, josta metodi ”toipuu” eli ohjelma ei kaadu. Tässä muuttuja e tulee sisältämään viittauksen poikkeusolioon. Hyvin usein catch-haarassa vain tulostetaan virheilmoitus ruudulle. Liittämällä e tulostustekstiin saadaan näkyviin systeemin generoimaa tietoa virhetilanteesta. Huomaa, että oikeastaan tämä tarkoittaa e.toString(), eli kutsutaan toString-metodia, jollainen useimmissa poikkeusluokissa on määritelty. Ellei ole, niin toString-metodi periytyy joka tapauksessa Object-luokasta; kaikkiin olioihin voidaan soveltaa toString-metodia.

Page 107: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

107

Luokan OmaIO metodeissa on sovellettu poikkeusten käsittelyä lukemiseen ja syötteen tarkistamiseen. OmaIO:ssa on mm. metodi lueInt, joka ei käytä luokkaa Scanner, vaan yleistä tiedostonkäsittelyä, jossa käytetään poikkeusluokkaa IOException, joka nostetaan jos tiedoston käsittelyssä tapahtuu virhe (tiedostoa ei ole, tiedosto on loppu jne.). Tutki niitä, mutta katso ensin alla oleva yksinkertaisempi esimerkki!

Esimerkki poikkeusten käsittelystä: metodi lueInt luokkaa Scanner käyttäen

Seuraavaksi esitetään esimerkki sitkeästä int-luvun lukijasta lueInt luokan Scanner avulla. Mukana on kaksi versiota samasta metodista, ts. kyseessä on ylikuormitus. Ensimmäisessä versiossa parametreina annetaan käyttäjälle tulostettava ”hopute”. Toisella versiolla ei ole parametria lainkaan ja metodi kutsuu ensimmäistä versiota, jossa hopute on tyhjä. public static int lueInt(String hopute) { boolean onnistui; int luku=0; Scanner lukija = new Scanner(System.in); System.out.println(hopute); do { String rivi = lukija.nextLine().trim(); // alku- ja loppuvälilyönnit pois merkkijonosta try { luku = Integer.parseInt(rivi); // tässä voi nousta NumberFormatException onnistui = true; // Tämä lause suoritetaan, jos edellä ei noussut poikkeusta } catch (NumberFormatException e) // suoritetaan jos ei annettu int-luvun esitystä { System.out.println("Merkkijono \"" + rivi + "\" ei esitä kokonaislukua. Anna luku uudestaan."); System.out.println("Kokonaisluvun (int) lukeminen nosti poikkeuksen!? Poikkeus: " + e); onnistui = false; } } while (!onnistui); return luku; } // lueInt public static int lueInt() { return lueInt(""); }

NumberFormatException nostetaan, jos merkkijono rivi ei sisällä int-luvun esitystä, jolloin metodi parseInt ei pysty muodostamaan merkkijonosta int-lukua. Tällöin try-lohkon suoritus keskeytetään ja siirrytään catch-lohkoon. Jos taas merkkijono sisältää int-luvun esityksen, niin onnistui saa arvon true ja catch-lohko ohitetaan. Tulostuksessa halutaan, että muuttujan rivi sisällön ympärillä on lainausmerkit, joten siksi kirjoitamme \” ennen merkkijonon päättävää lainausmerkkiä. Tässä voitaisiin käyttää poikkeusluokan NumberFormatException tilalla kaikkien poikkeuksien yliluokkaa Exception, joskin kaikissa tilanteissa se ei anna tarpeeksi tarkkaa virheilmoitusta (mutta kylläkin yo. esimerkin tapauksessa).

Esimerkiksi jos käyttäjä antaa syötteen 1.2, niin metodi vastaa: Kokonaisluvun (int) lukeminen nosti poikkeuksen!? Poikkeus: java.lang.NumberFormatException: For input string: "1.2" Tästä

Page 108: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

108

nähdään myös se, että luokka NumberFormatException kuuluu pakkaukseen java.lang, joten sen käyttöönotto ei vaadi import-lausetta.

Edellä oleva poikkeusten käsittely try-catch voidaan tarvittaessa siirtää myös kutsuvaan metodiin, jolloin metodin lueInt otsikkorivin loppuun pitää kirjoittaa: throws NumberFormatException.

Jos luetaan merkkijonoa, niin mitä tahansa merkkijono kelpaa. Kuitenkin usein luokan Scanner avulla luetaan kokonaislukuja nextInt- tai desimaalilukuja nextDouble-metodilla. Jos käyttäjän antama luku ei ole oikeassa muodossa, aiheutuu poikkeus InputMismatchException. Tähän poikkeukseen on syytä varautua (vaikka yksinkertaisuuden vuoksi siihen ei ole tähänastisissa esimerkkiohjelmissa varauduttu), koska virhetilanne on käytännössä todella yleinen, erityisesti desimaalipilkun käyttö desimaalilukuja syötettäessä. Poikkeus InputMismatchException kuuluu pakkaukseen java.util kuten myös luokka Scanner, joka pitää tuoda ohjelman käyttöön import-lauseella import java.util.*;

Tentissä poikkeuksista voidaan kysyä joko esseekysymys tai jokin yksinkertainen ohjelmointitehtävä. Huomaa että myös luentopäivällä käsiteltiin ohjelma, joka sisälsi yksinkertaisen poikkeusten käsittelyn.

Määre final: sen merkitys eri tilanteissa

Se voidaan liittää eri asioihin kuten yllä on käynyt ilmi. Kootaan nyt yhteen nämä asiat. Määreen käyttö ei ole pakollista, mutta se selkeyttää ohjelmakoodia: näemme helposti ohjelman vakiot, mille luokalla voidaan kirjoittaa aliluokka ja mitkä metodit voidaan määritellä uudelleen perivässä luokassa. Näin ollen se on myös suunnittelun apuvälinen. Sen syntaktinen sijainti määreisiin static, public, protected ja tyyppiin nähden käy ilmi alla olevista esimerkeistä.

Metodin parametrit ja muut paikalliset (lokaaliset) muuttujat: Mikäli parametri tai muu paikallinen (lokaalinen) muuttuja esitellään määreellä final, sen arvoa ei voi enää muuttaa alustuksen jälkeen eli siihen voidaan asettaa arvo vain yhden kerran. final-määreellä onkin tapana merkitä sellaiset muuttujat, joiden arvon ei ole tarkoitus muuttua. Jos muuttuja on viittaustyyppinen, niin kuitenkin olio johon muuttuja viittaa voi muuttua, mutta viittaukselle ei saa antaa uutta arvoa.

Luokkamuuttujat (static): Esim. luokkaan Henkilo voidaan laittaa olioiden yhteiseksi tietokentäksi täysi-ikäisyyden ilmaiseva muuttuja. Tämä on siis sama arvo kaikilla olioilla. Jos sen määreeksi asetetaan myös final, niin kyseessä on vakio, jota ei voi muuttaa missään:

public static final int TÄYSI_IKÄISYYS = 18;

Tällaisten vakioiden nimet kirjoitetaan yleensä kokonaan isoilla kirjaimilla.

Esiintymämuuttujat (ei-static): Esim. luokassa Henkilo voitaisiin määritellä henkilötunnus:

public final String hetu;

Page 109: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

109

Tämä tarkoittaa, että mikäli esiintymämuuttujalle ei ole annettu arvoa esittelyn yhteydessä, täytyy arvo antaa jokaisessa konstruktorissa. Tämän jälkeen esiintymämuuttujan arvoa ei voi enää muuttaa. Tällaista esiintymämuuttujaa ei voi myöskään muuttaa eikä uudelleen määritellä aliluokassa.

Metodit: Mikäli metodille on annettu final-määre, sitä ei voida toteuttaa uudelleen perivässä luokassa.

Luokat: Mikäli luokalle on annettu final-määre, niin sille ei voi kirjoittaa aliluokkaa.

Harjoitustehtävät

1. Kirjoita metodi lueDouble, joka lukee näppäimistöltä desimaalilukua toistuvasti käyttäen metodia nextDouble kunnes käyttäjä antaa oikeanlaisen syötteen. Tässä pitää varautua syötteen virheellisyyteen käyttäen poikkeusluokkaa InputMismatchException. Voit käyttää desimaalierottimena pilkkua, jolloin ei tarvita Locale-määritystä. Testaa metodiasi ja jos lähetät ratkaisusi, mukana tulee olla kuvaruutunäyttö ohjelman suorituksesta. Jaksolta AOP: Jos haluat käyttää syötettäessä desimaalipistettä, niin ohjelmaan tulee kirjoittaa Scanner-olion scan luonnin jälkeen lause: scan.useLocale(Locale.US). Lisäksi luokan alkuun pitää kirjoittaa rivi: import java.util.Locale;

2. -3. Esitä tietokonetyösi suunnitelma. Sen sisällöstä kerrotaan Tietokonetyön sivuilla ja suunnitelmasta annetaan esimerkki s. 66 luokan Murtoluku yhteydessä. Tästä saa kaksi ylimääräistä ruksia, jos olet lähettänyt suunnitelmasi vastuuopettajalle viimeistään 11.3.2016 ja jos se vaatii vain pienet korjaukset.

Lopuksi tehdään Tietokonetyötä.

Page 110: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

110

VIIKKO 11

Tietokonetyö, hajautustaulu: luokka HashMap<K,V>

Tietokonetyö

Tässä vaiheessa jokaisella tulisi olla työstä ainakin suunnitelma. Jakson tenttiin voi osallistua, jos jakso AOP on suoritettu kokonaisuudessaan ja jakson OOP tietokonetyön suunnitelma on lähetetty työn tarkastajalle. Tämän viikon pääasia on tietokonetyö.

Luokka HashMap

Tätä ei vaadita mutta sitä voi käyttää tietokonetyössä. Seuraavassa luokasta annetaan hyvin lyhyt kuvaus.

Aiemmin käsiteltiin jo dynaamisia listoja, mutta niiden hakuominaisuudet eivät ole kovin kehittyneitä. Jos rakenteeseen tehdään pääasiassa hakuja ja lisäyksiä jonkun avaimen mukaan (esim. nimen, hetun …) mukaan kannattaa käyttää hajautustaulua HashMap, joka sisältää tehokkaasti implementoidut haku- ja lisäysominaisuudet. Sen sijaan ”alusta loppuun” käsittelyyn rakenne ei sovellu.

Luokan HashMap esiintymä on rakenne, jolla K-tyyppisiin arvoihin, avaimiin ("key"), voidaan liittää V-tyyppisiä arvoja ("value"). Tässä sekä K että V ovat oliotyyppisiä. Tavallaan HashMap-olio on kuin taulukko, joka "indeksoidaan" V-tyyppisillä arvoilla. Nämä indeksit voivat olla minkä tyyppisiä olioita tahansa kunhan ne kaikki ovat samaa tyyppiä (tai alityyppiä) yhden taulun sisällä. Jos haluamme käydä taulun läpi ”alusta loppuun”, kannattaa kaikki avaimet tallentaa tavalliseen taulukkoon.

Luokan K on toteutettava hashCode- ja equals-metodit (mm. tyypit String, Integer ja Double, kelpaavat).

Page 111: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

111

Seuraavassa on joitakin HashMap-luokan metodeja (ks. lisää luokkakirjastosta):

public HashMap<K,V>() luo tyhjän HashMap-olion, jonka avaimiksi kelpaavat K-tyyppiset oliot, arvoiksi kelpaavat V-tyyppiset oliot. Tarvittaessa perustyypin arvot (esim. int) muutetaan (autoboxing) vastaaviksi olioiksi (Integer).

public V put(K k, V v) liittää HashMap-olioon k arvon v. Jos avaimeen liittyy jo arvo, metodi korvaa sen uudella ja palauttaa arvonaan vanhan arvon. Jos avainta ei ole, metodi palauttaa arvon null.

public V get(Object k) palauttaa arvonaan avaimeen k liitetyn arvon. Jos avainta ei ole, metodi palauttaa arvon null. Metodi palauttaa null-arvon myös, jos avaimen arvona on null; nämä kaksi tapausta voi erottaa toisistaan containsKey-metodin avulla

public boolean containsKey(Object k) arvona on true jos avainten joukossa on k, muuten false.

public boolean containsValue(Object v) arvona on true jos yhteen tai useampaan avaimeen liittyy arvo on v, muuten false.

public V remove(Object k) poistaa avaimen k ja siihen liittyneen arvon. Metodi palauttaa arvonaan poistetun arvon. Jos avainta ei löydy, palautetaan null. Metodi palauttaa null-arvon myös, jos avaimen arvona on null; nämä kaksi tapausta voi erottaa toisistaan containsKey-metodin avulla)

public String toString() tuottaa merkkiesityksen HashMap-oliosta, komponentit muokataan niiden omilla toString()-metodeilla

Page 112: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

112

Esimerkki: "Kielenkäännösohjelma". Ohjelma opiskelee ensin sanapareja "sana alkukielelellä" - "sana käännettynä toiselle kielelle". Sitten ohjelma tarjoaa sanojen käännöspalvelun. Ohjelma ei ole modulaarinen, vaan toimii vain esimerkkinä HashMap-rakenteen käytöstä. Kehtitä tätä ja tee siitä modulaarinen!

Käytännössä nämä tulisi tietenkin tallentaa tekstitiedostoon, josta muodostettaisiin HashMap-rakenne. Kurssisivuilla on esimerkki tästä (Pankkisovellus).

import java.util.*; public class HashMapEsim { private static Scanner lukija = new Scanner(System.in); public static void main(String[] args) { HashMap<String,String> sanakirja = new HashMap<String,String>(); String suomi, eng; do { System.out.print("Sana suomeksi? (enter lopettaa) "); suomi = lukija.nextLine(); if (suomi.equals("")) break; if (sanakirja.containsKey(suomi)) { System.out.println("Sana \"" + suomi + "\" löytyy jo!"); System.out.println("Vanha käännös " + "\"" +sanakirja.get(suomi) + "\" jää voimaan!"); continue; // uusi alkukielinen sana } System.out.print("Kyseinen sana englanniksi? "); eng = lukija.nextLine(); sanakirja.put(suomi, eng); } while (true); System.out.println(sanakirja); do { System.out.print("Minkä sanan käännöksen haluat tietää? " + "(enter lopettaa) "); suomi = lukija.nextLine(); if (suomi.equals("")) break; // kysely loppui! if (!sanakirja.containsKey(suomi)) System.out.println("Sanan \"" + suomi +"\" käännös on tuntematon!"); else System.out.println("Sanan \"" + suomi + "\" käännös on \"" + sanakirja.get(suomi) +"\""); } while (true); // loppuu tyhjään sanaan } // main } // class

Page 113: 1. OPINTOJAKSON TAVOITTEET, SISÄLTÖ JA ESITIEDOT 4 2 ...staff.cs.utu.fi/AvoinYo/56/OOP/OOPopas16.pdf · StringTokenizer, DecimalFormat, Math ja Random. Pakkaukset ja import-lause

Olio-ohjelmoinnin perusteet: opiskeluopas (2016)

113

Harjoitustehtävät

1-2. Esittele tietokonetyösi ja sen suunnitelma. Tämä tehtävä on siis kahden tehtävän arvoinen ja kuuluu tehtävien kokonaismäärään toisin kuin viime viikon vastaava tehtävä. Tästä saa kaksi ruksia, jos olet lähettänyt suunnitelmasi vastuuopettajalle viimeistään 21.3.2016 ja se hyväksytään (yhden ruksin jos se vaatii vain pienet korjaukset).