Ohjelmointi on tietokoneohjelmien luomista. Osoitteessa mooc.fi on Helsingin yliopiston tietojenkäsittelytieteen laitoksen kaikille avoin ja maksuton verkkokurssi, jonka avulla opit ohjelmoinnin perusteiden lisäksi taitoja, joiden avulla jatkat alan ammattilaiseksi. Hyville ohjelmoijille riittää aina töitä.
Tämä sivu sisältää ASM-MOOC -tehtävät, jotka on pääosin otettu MOOC-ohjelmointikurssin tehtävistä. Jos ohjelmointi tai Java ei ole ennestään tuttua, vilkuile tehtävien tekemisen ohella myös MOOC-kurssien ohjelmoinnin perusteet ja ohjelmoinnin jatkokurssi meteriaaleja. Joistain tehtävänannoista löytyy lisäksi linkkejä relevantteihin materiaalin osiin.
Apua tehtävien tekemiseen löydät materiaalin
lisäksi myös IRCnetin
kanavalta #mooc.fi sekä
Nuorisoasiainkeskuksen ständillä olevasta
MOOC-päivystyksestä.
ASM-MOOC tehtävät
Tee ohjelma, jonka tulostus on seuraava:
Hei Maailma! (Ja Mualima!)
Tee ohjelma, joka kysyy käyttäjältä kaksi kokonaislukua ja tulostaa niiden summan.
Anna ensimmäinen luku: 6 Anna toinen luku: 2 Lukujen summa: 8
Esimerkissä punainen väri tarkoittaa käyttäjän kirjoittamaa tekstiä. Tätä käytäntöä noudatetaan myös tulevissa tehtävissä.
Tee ohjelma, joka kysyy käyttäjältä kaksi kokonaislukua ja tulostaa luvuista suuremman.
Anna ensimmäinen luku: 20 Anna toinen luku: 14 Luvuista suurempi: 20
Tee ohjelma, joka tulostaa kokonaisluvut väliltä 1–100.
Ohjelman tulostus on seuraava:
1 2 3 (välissä paljon rivejä) 98 99 100
Tee ohjelma joka laskee rajoitetun lukusarjan summan siten, että käyttäjä määrää summan laskemisen aloituskohdan ja lopetuskohdan. Esimerkiksi jos käyttäjä syöttää luvut 1 ja 3, laskee ohjelma laskun 1+2+3 ja tulostaa "Summa on 6". Voit olettaa, että käyttäjä antaa ensin pienemmän luvun ja sitten suuremman luvun.
Esimerkkitulostuksia:
Ensimmäinen: 3 Viimeinen: 5 Summa on 12
Ensimmäinen: 2 Viimeinen: 8 Summa on 35
Huom! Joissakin tehtävissä on alikohtia, joiden avulla tehtävä on pilkottu pienempiin, ohjelmoijan etenemistä tukeviin osiin. Arvan saamiseen tehtävästä tarvitaan jokaisen alikohdan tekeminen. Vaikka tehtävässä olisikin useampi alikohta, vastaa se kokonaisuudessaan yhtä arpaa.
Tee metodi tulostaKolmio(int koko) joka tulostaa kolmion käyttäen tulostaTahtia-metodia. Siis esimerkiksi kutsu tulostaKolmio(4) tulostaa seuraavaa:
* ** *** ****
Neuvo! Toteuta ensin metodi tulostaTyhjaa(int montako) joka tulostaa annetun lukumäärän välilyöntejä.
Tee metodi jouluKuusi(int korkeus) joka tulostaa joulukuusen. Joulukuusi koostuu annetun korkuisesta kolmiosta ja jalasta. Jalka on kaksi tähteä korkea ja kolme tähteä leveä ja se on keskellä kolmion pohjaa. Siis esimerkiksi kutsu jouluKuusi(4) tulostaa seuraavaa:
* *** ***** ******* *** ***
Kutsu jouluKuusi(10) tulostaa:
*
***
*****
*******
*********
***********
*************
***************
*****************
*******************
***
***
Huom! Rakenna kuusi käyttämällä tulostukseen metodeja tulostaTyhjaa ja tulostaTahtia!
Huom! Korkeuksien jotka ovat alle 3 ei tarvitse toimia!
Tee ohjelma, joka kysyy käyttäjältä sanoja, kunnes käyttäjä antaa tyhjän merkkijonon. Sitten ohjelma tulostaa käyttäjän antamat
sanat uudestaan. Kokeile tässä for-toistolauseketta. Käytä ohjelmassa ArrayList-rakennetta, joka määritellään seuraavasti:
ArrayList<String> sanat = new ArrayList<String>();
Anna sana: Mozart Anna sana: Schubert Anna sana: Bach Anna sana: Sibelius Anna sana: Liszt Anna sana: Annoit seuraavat sanat: Mozart Schubert Bach Sibelius Liszt
Vihje: tyhjä merkkijono voidaan havaita seuraavasti
String sana = lukija.nextLine();
if ( sana.isEmpty() ) { // myös tämä tomisi: sana.equals("")
// sana oli tyhjä eli pelkkä enterin painallus
}
Tee metodi palindromi, joka kertoo, onko merkkijono palindromi (merkkijonon sisältö on sama alusta loppuun ja lopusta alkuun luettuna). Metodin tyyppi on boolean, joten se palauttaa joko arvon true (merkkijono on palindromi) tai false (merkkijono ei ole palindromi).
public static boolean palindromi(String merkkijono) {
// kirjoita koodia tähän
}
public static void main(String[] args) {
Scanner lukija = new Scanner(System.in);
System.out.println("Anna merkkijono: ");
String merkkijono = lukija.nextLine();
if (palindromi(merkkijono)) {
System.out.println("Merkkijono on palindromi!");
} else {
System.out.println("Merkkijono ei ole palindromi!");
}
}
Ohjelman tulostuksia:
Anna merkkijono: saippuakauppias Merkkijono on palindromi!
Anna merkkijono: esimerkki Merkkijono ei ole palindromi!
Tee metodi keskiarvo, joka laskee parametrinaan saamansa kokonaislukuja sisältävän listan lukujen keskiarvon. Metodin on laskettava parametriensa summa käyttäen apuna edellisen tehtävän metodia summa.
Tee metodi seuraavaan runkoon:
public static double keskiarvo(ArrayList<Integer> lista) {
// kirjoita koodia tähän
}
public static void main(String[] args) {
ArrayList<Integer> lista = new ArrayList<Integer>();
lista.add(3);
lista.add(2);
lista.add(7);
lista.add(2);
System.out.println("Keskiarvo: " + keskiarvo(lista));
}
Ohjelman tulostus:
Keskiarvo: 3.5
Kumpulan kampuksella Helsingissä toimivaan Unicafe-nimiseen gourmet-ravintolaan tarvitaan uusi ruokalista. Keittiömestari tietää ohjelmoinnista, ja haluaa listan hallinnointiin tietokonejärjestelmän. Toteutetaan tässä tehtävässä järjestelmän sydän, luokka Ruokalista.
Tehtäväpohjan mukana tulee Main-luokka, jossa voit testata ruokalistan toimintaa. Ruokalistan toteuttamista varten saat seuraavanlaisen tehtäväpohjan:
import java.util.ArrayList;
public class Ruokalista {
private ArrayList<String> ateriat;
public Ruokalista() {
this.ateriat = new ArrayList<String>();
}
// toteuta tänne tarvittavat metodit
}
Ruokalistaoliolla on siis oliomuuttujana ArrayList, jonka on tarkoitus tallentaa ruokalistalla olevien ruokalajien nimet.
Ruokalistan tulee tarjota metodit public void lisaaAteria(String ateria), public void tulostaAteriat(), ja public void tyhjennaRuokalista().
Toteuta metodi public void lisaaAteria(String ateria), joka lisää uuden aterian ruokalistan ateriat-listaan. Jos lisättävä ateria on jo listassa, sitä ei lisätä uudelleen.
Toteuta metodi public void tulostaAteriat(), joka tulostaa ateriat. Esimerkiksi kolmen aterian lisäyksen jälkeen tulostuksen tulee olla seuraavanlainen.
ensimmäisenä lisätty ateria toisena lisätty ateria kolmantena lisätty ateria
Toteuta metodi public void tyhjennaRuokalista() joka tyhjentää ruokalistan. ArrayList-luokalla on metodi josta on tässä hyötyä. NetBeans osaa vihjata käytettävissä olevista metodeista kun kirjoitat olion nimen ja pisteen. Yritä kirjoittaa ateriat. metodirungon sisällä ja katso mitä käy.
Joukkue-luokkaTee luokka Joukkue,
johon tallennetaan joukkueen nimi (String).
Tee luokkaan seuraavat metodit:
haeNimi, joka palauttaa joukkueen nimenSeuraava pääohjelma testaa luokan toimintaa:
public class Main {
public static void main(String[] args) {
Joukkue tapiiri = new Joukkue("FC Tapiiri");
System.out.println("Joukkue: " + tapiiri.haeNimi());
}
}
Ohjelman tulostus on seuraava:
Joukkue: FC Tapiiri
Luo luokka Pelaaja, johon tallennetaan pelaajan nimi ja tehtyjen maalien määrä. Tee luokkaan kaksi konstruktoria: yksi jolle annetaan vain pelaajan nimi, toinen jolle annetaan sekä pelaajan nimi että pelaajan tekemien maalien määrä. Lisää pelaajalle myös metodit:
haeNimi, joka palauttaa pelaajan nimenmaalit, joka palauttaa tehtyjen maalien määräntoString, joka palauttaa pelaajan merkkijonoesityksen
public class Main {
public static void main(String[] args) {
Joukkue tapiiri = new Joukkue("FC Tapiiri");
System.out.println("Joukkue: " + tapiiri.haeNimi());
Pelaaja matti = new Pelaaja("Matti");
System.out.println("Pelaaja: " + matti);
Pelaaja pekka = new Pelaaja("Pekka", 39);
System.out.println("Pelaaja: " + pekka);
}
}
Joukkue: FC Tapiiri Pelaaja: Matti, maaleja 0 Pelaaja: Pekka, maaleja 39
Lisää luokkaan Joukkue seuraavat metodit:
lisaaPelaaja, joka lisää pelaajan joukkueeseentulostaPelaajat, joka tulostaa joukkueessa olevat pelaajatTallenna joukkueessa olevat pelaajat Joukkue-luokan
sisäiseen ArrayList-listaan.
Seuraava pääohjelma testaa luokan toimintaa:
public class Main {
public static void main(String[] args) {
Joukkue tapiiri = new Joukkue("FC Tapiiri");
Pelaaja matti = new Pelaaja("Matti");
Pelaaja pekka = new Pelaaja("Pekka", 39);
tapiiri.lisaaPelaaja(matti);
tapiiri.lisaaPelaaja(pekka);
tapiiri.lisaaPelaaja(new Pelaaja("Mikael", 1)); //vaikutus on sama kuin edellisillä
tapiiri.tulostaPelaajat();
}
}
Ohjelman tulostuksen tulisi olla seuraava:
Matti, maaleja 0 Pekka, maaleja 39 Mikael, maaleja 1
Lisää luokkaan Joukkue seuraavat metodit:
asetaMaksimikoko(int maksimikoko), joka asettaa joukkueen maksimikoon (eli maksimimäärän pelaajia)koko, joka palauttaa pelaajien määrän (int)Joukkueen suurin sallittu pelaajamäärä on oletusarvoisesti 16. Metodin asetaMaksimikoko avulla tätä rajaa voi muuttaa. Muuta metodia lisaaPelaaja niin, että se ei lisää pelaajaa joukkueeseen, jos sallittu pelaajamäärä ylittyisi.
HUOM: muista lisätä oletusarvoinen maksimikoko koodiisi sillä muuten arvoksi tulee 0. Tämä aiheuttaa edellisen kohdan testien hajoamisen, sillä testit luovat oletusmaksimikokoisia joukkueita ja jos joukkueen maksimikoko on 0, ei joukkueeseen voi lisätä yhtään pelaajaa.
Seuraava pääohjelma testaa luokan toimintaa:
public class Main {
public static void main(String[] args) {
Joukkue tapiiri = new Joukkue("FC Tapiiri");
tapiiri.asetaMaksimikoko(1);
Pelaaja matti = new Pelaaja("Matti");
Pelaaja pekka = new Pelaaja("Pekka", 39);
tapiiri.lisaaPelaaja(matti);
tapiiri.lisaaPelaaja(pekka);
tapiiri.lisaaPelaaja(new Pelaaja("Mikael", 1)); //vaikutus on sama kuin edellisillä
System.out.println("Pelaajia yhteensä: " + tapiiri.koko());
}
}
Pelaajia yhteensä: 1
Lisää luokkaan Joukkue metodi:
maalit, joka palauttaa joukkueen pelaajien tekemien maalien yhteismäärän.Seuraava pääohjelma testaa luokan toimintaa:
public class Main {
public static void main(String[] args) {
Joukkue tapiiri = new Joukkue("FC Tapiiri");
Pelaaja matti = new Pelaaja("Matti");
Pelaaja pekka = new Pelaaja("Pekka", 39);
tapiiri.lisaaPelaaja(matti);
tapiiri.lisaaPelaaja(pekka);
tapiiri.lisaaPelaaja(new Pelaaja("Mikael", 1)); //vaikutus on sama kuin edellisillä
System.out.println("Maaleja yhteensä: " + tapiiri.maalit());
}
}
Maaleja yhteensä: 40
Luo ohjelma tähtitaivaan tulostamiseen. Tähtitaivaan tähtien määrä kerrotaan tiheyden avulla. Esimerkiksi jos tähtitaivaan tiheys on 0.2, on noin 20% tähtitaivaasta peitettynä tähdillä. Pääset harjoittelemaan siis myös satunnaisuuslukujen käyttöä.
Käytä tähtien tulostamiseen *-merkkiä. Alla on esimerkki lopullisen Tahtitaivas-luokan käytöstä ja käyttöä vastaavasti tulostuksesta.
Tahtitaivas tahtitaivas = new Tahtitaivas(0.1, 39, 10);
tahtitaivas.tulosta();
System.out.println("Tähtiä: " + tahtitaivas.tahtiaViimeTulostuksessa());
System.out.println("");
tahtitaivas = new Tahtitaivas(0.2, 15, 6);
tahtitaivas.tulosta();
System.out.println("Tähtiä: " + tahtitaivas.tahtiaViimeTulostuksessa());
* * * * * * * ** * * * * * * * * * * * * * * * * * * * ** * * * * * * Tähtiä: 36 * * * * * * * * * * * * * * * * * ** ** * Tähtiä: 22
Huom! tehtävissä kannattaa käyttää for-lauseketta. Vaikka edellinen luku puhuukin sisäkkäisistä toistolauseista, tässä tehtävässä "sisempi" toisto piilotetaan metodin sisälle.
Luo luokka Tahtitaivas, jolla on kolme oliomuuttujaa: tiheys (double), leveys (int), ja korkeus (int). Luo luokalle myös kolme konstruktoria:
public Tahtitaivas(double tiheys) Luo tähtitaivas-olion, jolla on parametrina annettu tiheys, leveys saa arvon 20, korkeus saa arvon 10.public Tahtitaivas(int leveys, int korkeus) Luo tähtitaivas-olion, jolla on parametrina annetut leveys ja korkeus, tiheys saa arvon 0.1.public Tahtitaivas(double tiheys, int leveys, int korkeus) Luo tähtitaivas-olion, jolla on parametrina annetut tiheys, leveys ja korkeus.Lisää luokalle Tahtitaivas metodi tulostaRivi, joka tulostaa yhden rivin. Rivin leveyden määrää oliomuuttuja leveys. Oliomuuttuja tiheys kertoo todennäköisyyden tähdelle. Arvo jokaisen merkin kohdalla tulostetaanko tähti vai ei Random-luokan nextDouble-metodin avulla.
Testaa ohjelmaasi, esimerkkinä seuraava kutsu ja esimerkkitulostus.
Tahtitaivas tahtitaivas = new Tahtitaivas(0.1); tahtitaivas.tulostaRivi();
* * *
Luo Tahtitaivas-luokalle metodi tulosta, joka tulostaa koko tähtitaivaan. Käytä tässä hyödyksesi aiempaa tulostaRivi-metodia.
Tahtitaivas tahtitaivas = new Tahtitaivas(8, 4); tahtitaivas.tulosta();
* * *
Lisää Tahtitaivas-luokalle oliomuuttuja tahtiaViimeTulostuksessa (int) ja metodi tahtiaViimeTulostuksessa(), joka palauttaa viime tulostuksessa tulostuneiden tähtien lukumäärän. Toteuta ohjelmaasi tähtien laskeminen.
Tahtitaivas tahtitaivas = new Tahtitaivas(8, 4);
tahtitaivas.tulosta();
System.out.println("Tähtiä: " + tahtitaivas.tahtiaViimeTulostuksessa());
System.out.println("");
tahtitaivas.tulosta();
System.out.println("Tähtiä: " + tahtitaivas.tahtiaViimeTulostuksessa());
* Tähtiä: 1 * * * Tähtiä: 3
Kumpulan tiedekirjasto tarvitsee uuden järjestelmän kirjojen hallintaan. Tässä tehtävässä toteutetaan prototyyppi, jossa toteutetaan kirjan haku nimen, julkaisijan tai julkaisuvuoden perusteella.
Rakennetaan järjestelmä osista, ensin toteutetaan oleelliset luokat eli Kirja ja Kirjasto. Luokka Kirja sisältää kirjaan liittyvät tiedot, luokka Kirjasto tarjoaa erilaisia hakutoiminnallisuuksia kirjoihin liittyen.
Luodaan ensiksi luokka Kirja. Kirjalla on oliomuuttujina nimike, eli kirjan nimi, julkaisija, eli kirjan julkaisija, ja julkaisuvuosi eli vuosi jolloin kirja on julkaistu. Kaksi ensimmäistä muuttujaa on merkkijonotyyppisiä, viimeisin on kokonaisluku. Oletamme tässä että kirjalla on aina vain yksi kirjoittaja.
Toteuta luokka Kirja. Kirjalla tulee olla myös konstruktori public Kirja(String nimike, String julkaisija, int julkaisuvuosi) sekä metodit public String nimike(), public String julkaisija(), public int julkaisuvuosi() ja public String toString(). Arvannet mitä metodien tulee tehdä, alla esimerkki.
Testaa luokan toimintaa:
Kirja cheese = new Kirja("Cheese Problems Solved", "Woodhead Publishing", 2007);
System.out.println(cheese.nimike());
System.out.println(cheese.julkaisija());
System.out.println(cheese.julkaisuvuosi());
System.out.println(cheese);
Cheese Problems Solved Woodhead Publishing 2007 Cheese Problems Solved, Woodhead Publishing, 2007
Kirjaston tehtävä on antaa käyttäjälle mahdollisuus kirjojen lisäämiseen ja niiden hakemiseen. Luo luokka Kirjasto, jolla on konstruktori public Kirjasto() ja metodit public void lisaaKirja(Kirja uusiKirja) ja public void tulostaKirjat()
Kirjasto kirjasto = new Kirjasto();
Kirja cheese = new Kirja("Cheese Problems Solved", "Woodhead Publishing", 2007);
kirjasto.lisaaKirja(cheese);
Kirja nhl = new Kirja("NHL Hockey", "Stanley Kupp", 1952);
kirjasto.lisaaKirja(nhl);
kirjasto.lisaaKirja(new Kirja("Battle Axes", "Tom A. Hawk", 1851));
kirjasto.tulostaKirjat();
Cheese Problems Solved, Woodhead Publishing, 2007 NHL Hockey, Stanley Kupp, 1952 Battle Axes, Tom A. Hawk, 1851
Kirjastosta tulee pystyä etsimään kirjoja nimikkeiden ja julkaisijoiden perusteella. Lisää kirjastolle metodit public ArrayList<Kirja> haeKirjaNimikkeella(String nimike), public ArrayList<Kirja> haeKirjaJulkaisijalla(String julkaisija) ja public ArrayList<Kirja> haeKirjaJulkaisuvuodella(int julkaisuvuosi). Metodit palauttavat listan kirjoista, joissa on haluttu nimike, julkaisija tai julkaisuvuosi.
Huom: joudut siis tehdä metodin jonka paluuarvona on ArrayList. Tämä onnustuu seuraavaa metodirunkoa hyödyntäen:
public class Kirjasto {
// ...
public ArrayList<Kirja> haeKirjaNimikkeella(String nimike) {
ArrayList<Kirja> loydetyt = new ArrayList<Kirja>();
// käy läpi kaikki kirjat ja lisää ne joilla haetun kaltainen nimike listalle loydetyt
return loydetyt;
}
}
Huom! Kun haet teet hakua merkkijonon avulla, älä tee tarkkaa hakua (metodi equals) vaan käytä String-luokan metodia contains. Huomaat todennäköisesti myös että sinulla on ns. copy-paste -koodia Kirjasto-luokan koodissa. Keksitkö tavan päästä siitä eroon?
Kirjasto kirjasto = new Kirjasto();
kirjasto.lisaaKirja(new Kirja("Cheese Problems Solved", "Woodhead Publishing", 2007));
kirjasto.lisaaKirja(new Kirja("The Stinky Cheese Man and Other Fairly Stupid Tales", "Penguin Group", 1992));
kirjasto.lisaaKirja(new Kirja("NHL Hockey", "Stanley Kupp", 1952));
kirjasto.lisaaKirja(new Kirja("Battle Axes", "Tom A. Hawk", 1851));
ArrayList<Kirja> hakutulos = kirjasto.haeKirjaNimikkeella("Cheese");
for (Kirja kirja: hakutulos) {
System.out.println(kirja);
}
System.out.println("---");
for (Kirja kirja: kirjasto.haeKirjaJulkaisijalla("Penguin Group ")) {
System.out.println(kirja);
}
System.out.println("---");
for (Kirja kirja: kirjasto.haeKirjaJulkaisuvuodella(1851)) {
System.out.println(kirja);
}
Cheese Problems Solved, Woodhead Publishing, 2007 The Stinky Cheese Man and Other Fairly Stupid Tales, Penguin Group, 1992 --- --- Battle Axes, Tom A. Hawk, 1851
Hakutoiminnallisuutemme on jo hyvä, mutta se ei ymmärrä isojen ja pienten kirjainten eroa. Yllä olleessa esimerkissä haku nimikkeellä "cheese" ei olisi tuottanut yhtäkään tulosta. Myös toinen esimerkki, jossa oli ylimääräisiä välilyöntejä, ei näyttänyt haluttua tulosta. Haluamme että nimikkeiden ja julkaisijoiden nimillä haettaessa ei välitetä merkkien koosta, ja että käyttäjä voi syöttää ylimääräisiä välilyöntejä kirjan nimen alkuun tai loppuun (meidän ei tarvitse välittää sanojen välillä olevista tyhjistä!). Toteutetaan pieni apukirjasto StringUtils merkkijonojen vertailuun.
Luo luokka StringUtils, ja lisää sille staattinen metodi public static boolean sisaltaa(String sana, String haettava), joka tarkistaa sisältääkö merkkijono sana merkkijonon haettava. Jos jommankumman merkkijonon arvo on null, metodin tulee palauttaa arvo false. Metodin tarjoaman vertailun tulee olla välittämättä merkin koosta.
Lisää metodille sisaltaa myös toiminnallisuus, joka poistaa merkkijonojen sana ja haettava alusta ja lopusta ylimääräiset välilyönnit. Käytä tähän String-luokan metodia trim, esim. trimmattu = trimmattava.trim()
Vinkki! String-luokan metodista toUpperCase() on hyötyä kun haluat verrata ovatko kaksi merkkijonoa samat -- riippumatta niiden alkuperäisestä merkkikoosta.
Kun olet saanut metodin valmiiksi, käytä sitä Kirjasto-luokassa. Alla esimerkki:
if(StringUtils.sisaltaa(kirja.nimike(), haettuNimike)) {
// kirja löytyi!
}
Kirjasto kirjasto = new Kirjasto();
kirjasto.lisaaKirja(new Kirja("Cheese Problems Solved", "Woodhead Publishing", 2007));
kirjasto.lisaaKirja(new Kirja("The Stinky Cheese Man and Other Fairly Stupid Tales", "Penguin Group", 1992));
kirjasto.lisaaKirja(new Kirja("NHL Hockey", "Stanley Kupp", 1952));
kirjasto.lisaaKirja(new Kirja("Battle Axes", "Tom A. Hawk", 1851));
for (Kirja kirja: kirjasto.haeKirjaNimikkeella("CHEESE")) {
System.out.println(kirja);
}
System.out.println("---");
for (Kirja kirja: kirjasto.haeKirjaJulkaisijalla("PENGUIN ")) {
System.out.println(kirja);
}
Cheese Problems Solved, Woodhead Publishing, 2007 The Stinky Cheese Man and Other Fairly Stupid Tales, Penguin Group, 1992 --- The Stinky Cheese Man and Other Fairly Stupid Tales, Penguin Group, 1992
Muuton yhteydessa tarvitaan muuttolaatikoita. Laatikoihin talletetaan erilaisia esineitä. Kaikkien laatikoihin talletettavien esineiden on toteutettava seuraava rajapinta:
public interface Talletettava {
double paino();
}
Lisää rajapinta ohjelmaasi. Rajapinta lisätään melkein samalla tavalla kuin luokka, new Java class sijaan valitaan new Java interface.
Tee rajapinnan toteuttavat luokat Kirja ja CDLevy. Kirja saa konstruktorin parametreina kirjan kirjoittajan (String), kirjan nimen (String), ja kirjan painon (double). CD-Levyn konstruktorin parametreina annetaan artisti (String), levyn nimi (String), ja julkaisuvuosi (int). Kaikkien CD-levyjen paino on 0.1 kg.
Muista toteuttaa luokilla myös rajapinta Talletettava. Luokkien tulee toimia seuraavasti:
public static void main(String[] args) {
Kirja kirja1 = new Kirja("Fedor Dostojevski", "Rikos ja Rangaistus", 2);
Kirja kirja2 = new Kirja("Robert Martin", "Clean Code", 1);
Kirja kirja3 = new Kirja("Kent Beck", "Test Driven Development", 0.5);
CDLevy cd1 = new CDLevy("Pink Floyd", "Dark Side of the Moon", 1973);
CDLevy cd2 = new CDLevy("Wigwam", "Nuclear Nightclub", 1975);
CDLevy cd3 = new CDLevy("Rendezvous Park", "Closer to Being Here", 2012);
System.out.println(kirja1);
System.out.println(kirja2);
System.out.println(kirja3);
System.out.println(cd1);
System.out.println(cd2);
System.out.println(cd3);
}
Tulostus:
Fedor Dostojevski: Rikos ja Rangaistus Robert Martin: Clean Code Kent Beck: Test Driven Development Pink Floyd: Dark Side of the Moon (1973) Wigwam: Nuclear Nightclub (1975) Rendezvous Park: Closer to Being Here (2012)
Huom! Painoa ei ilmoiteta tulostuksessa.
Tee luokka laatikko, jonka sisälle voidaan tallettaa Talletettava-rajapinnan toteuttavia tavaroita. Laatikko saa konstruktorissaan parametrina laatikon maksimikapasiteetin kiloina. Laatikkoon ei saa lisätä enempää tavaraa kuin sen maksimikapasiteetti määrää. Laatikon sisältämien tavaroiden paino ei siis koskaan saa olla yli laatikon maksimikapasiteetin.
Seuraavassa esimerkki laatikon käytöstä:
public static void main(String[] args) {
Laatikko laatikko = new Laatikko(10);
laatikko.lisaa( new Kirja("Fedor Dostojevski", "Rikos ja Rangaistus", 2) ) ;
laatikko.lisaa( new Kirja("Robert Martin", "Clean Code", 1) );
laatikko.lisaa( new Kirja("Kent Beck", "Test Driven Development", 0.7) );
laatikko.lisaa( new CDLevy("Pink Floyd", "Dark Side of the Moon", 1973) );
laatikko.lisaa( new CDLevy("Wigwam", "Nuclear Nightclub", 1975) );
laatikko.lisaa( new CDLevy("Rendezvous Park", "Closer to Being Here", 2012) );
System.out.println( laatikko );
}
Tulostuu
Laatikko: 6 esinettä, paino yhteensä 4.0 kiloa
Huom: koska painot esitetään doubleina, saattaa laskutoimituksissa tulla pieniä pyöristysvirheitä. Tehtävässä ei tarvitse välittää niistä.
Jos teit laatikon sisälle oliomuuttujan double paino, joka muistaa laatikossa olevien esineiden painon, korvaa se metodilla, joka laskee painon:
public class Laatikko {
//...
public double paino() {
double paino = 0;
// laske laatikkoon talletettujen tavaroiden yhteispaino
return paino;
}
}
Kun tarvitset laatikon sisällä painoa esim. uuden tavaran lisäyksen yhteydessä, riittää siis kutsua laatikon painon laskevaa metodia.
Metodi toki voisi palauttaa myös oliomuuttujan arvon. Harjoittelemme tässä kuitenkin tilannetta, jossa oliomuuttujaa ei tarvitse eksplisiittisesti ylläpitää vaan se voidaan tarpeentullen laskea. Seuraavan tehtävän jälkeen laatikossa olevaan oliomuuttujaan talletettu painotieto ei kuitenkaan välttämättä enää toimisi. Miksi?
Tässä tehtävässä teemme eliöita ja eliöistä koostuvia laumoja jotka liikkuvat ympäriinsä. Eliöiden sijaintien ilmoittamiseen käytetään kaksiulotteista koordinaatistoa. Jokaiseen sijaintiin liittyy kaksi lukua, x- ja y-koordinaatti. Koordinaatti x kertoo, kuinka pitkällä "nollapisteestä" mitattuna sijainti on vaakasuunnassa, ja koordinaatti y vastaavasti kuinka pitkällä sijainti on pystysuunnassa. Jos koordinaatiston käsite ei ole tuttu, voit lukea siitä lisää esimerkiksi wikipediasta.
Tehtävän mukana tulee rajapinta Siirrettava, joka kuvaa asiaa jota voidaan siirtää paikasta toiseen. Rajapinta sisältää metodin void siirra(int dx, int dy). Parametri dx kertoo, paljonko asia siirtyy x-akselilla ja dy y-akselilla.
Tehtävässä toteutat luokat Elio ja Lauma, jotka molemmat ovat siirrettäviä. Toteuta kaikki toiminnallisuus pakkaukseen siirrettava.
Luo pakkaukseen siirrettava luokka Elio, joka toteuttaa rajapinnan Siirrettava. Eliön tulee tietää oma sijaintinsa (x, y -koordinaatteina). Luokan Elio APIn tulee olla seuraava:
"x: 3; y: 6". Huomaa että koordinaatit on erotettu puolipisteellä (;)dx sisältää muutoksen koordinaattiin x, muuttuja dy sisältää muutoksen koordinaattiin y. Esimerkiksi jos muuttujan dx arvo on 5, tulee oliomuuttujan x arvoa kasvattaa viidelläKokeile luokan Elio toimintaa seuraavalla esimerkkikoodilla.
Elio elio = new Elio(20, 30); System.out.println(elio); elio.siirra(-10, 5); System.out.println(elio); elio.siirra(50, 20); System.out.println(elio);
x: 20; y: 30 x: 10; y: 35 x: 60; y: 55
Luo seuraavaksi pakkaukseen siirrettava luokka Lauma, joka toteuttaa rajapinnan Siirrettava. Lauma koostuu useasta Siirrettava-rajapinnan toteutavasta oliosta, jotka tulee tallettaa esimerkiksi listarakenteeseen.
Luokalla Lauma tulee olla seuraavanlainen API.
Siirrettava-rajapinnan toteuttavan olionKokeile ohjelmasi toimintaa alla olevalla esimerkkikoodilla.
Lauma lauma = new Lauma(); lauma.lisaaLaumaan(new Elio(73, 56)); lauma.lisaaLaumaan(new Elio(57, 66)); lauma.lisaaLaumaan(new Elio(46, 52)); lauma.lisaaLaumaan(new Elio(19, 107)); System.out.println(lauma);
x: 73; y: 56 x: 57; y: 66 x: 46; y: 52 x: 19; y: 107
Kaikki sovelluksessa oleva koodi tulee sijoittaa pakkaukseen sovellus.
Käytössämme on seuraava rajapinta:
public interface Sensori {
boolean onPaalla(); // palauttaa true jos sensori on päällä
void paalle(); // käynnistä sensorin
void poisPaalta(); // sulkee sensorin
int mittaa(); // palauttaa sensorin lukeman jos sensori on päällä
// jos sensori ei päällä heittää poikkeuksen IllegalStateException
}
Tee luokka Vakiosensori joka toteuttaa rajapinnan Sensori.
Vakiosensori on koko ajan päällä. Metodien paalle ja poisPaalta kutsuminen ei tee mitään. Vakiosensorilla tulee olla konstruktori, jonka parametrina on kokonaisluku. Metodikutsu mittaa palauttaa aina konstruktorille parametrina annetun luvun.
Esimerkki:
public static void main(String[] args) {
Vakiosensori kymppi = new Vakiosensori(10);
Vakiosensori miinusViis = new Vakiosensori(-5);
System.out.println( kymppi.mittaa() );
System.out.println( miinusViis.mittaa() );
System.out.println( kymppi.onPaalla() );
kymppi.poisPaalta();
System.out.println( kymppi.onPaalla() );
}
Tulostuu:
10 -5 true true
Tee luokka Lampomittari joka toteuttaa rajapinnan Sensori.
Aluksi lämpömittari on poissa päältä. Kutsuttaessa metodia mittaa kun mittari on päällä mittari arpoo luvun väliltä -30...30 ja palauttaa sen kutsujalle. Jos mittari ei ole päällä, heitetään poikkeus IllegalStateException.
Tee luokka Keskiarvosensori joka toteuttaa rajapinnan Sensori.
Keskiarvosensori sisältää useita sensoreita. Rajapinnan Sensori määrittelemien metodien lisäksi keskiarvosensorilla on metodi
public void lisaaSensori(Sensori lisattava) jonka avulla keskiarvosensorin hallintaan lisätään uusi sensori.
Keskiarvosensori on päällä silloin kuin kaikki sen sisältävät sensorit ovat päällä. Kun keskiarvosensori käynnistetään, täytyy kaikkien sen sisältävien sensorien käynnistyä jos ne eivät ole käynnissä. Kun keskiarvosensori suljetaan, täytyy ainakin yhden sen sisältävän sensorin mennä pois päältä. Saa myös käydä niin että kaikki sen sisältävät sensorit menevät pois päältä.
Keskiarvosensorin metodi mittaa palauttaa sen sisältämien sensoreiden lukemien keskiarvon (koska paluuarvo on int, pyöristyy lukema alaspäin kuten kokonaisluvuilla tehdyissä jakolaskuissa). Jos keskiarvosensorin metodia mittaa kutsutaan sensorin ollessa poissa päältä, tai jos keskiarvosensorille ei vielä ole lisätty yhtään sensoria heitetään poikkeus IllegalStateException.
Seuraavassa sensoreja käyttävä esimerkkiohjelma (huomaa, että sekä Lämpömittarin että Keskiarvosensorin konstruktorit ovat parametrittomia):
public static void main(String[] args) {
Sensori kumpula = new Lampomittari();
kumpula.paalle();
System.out.println("lämpötila Kumpulassa "+kumpula.mittaa() + " astetta");
Sensori kaisaniemi = new Lampomittari();
Sensori helsinkiVantaa = new Lampomittari();
Keskiarvosensori paakaupunki = new Keskiarvosensori();
paakaupunki.lisaaSensori(kumpula);
paakaupunki.lisaaSensori(kaisaniemi);
paakaupunki.lisaaSensori(helsinkiVantaa);
paakaupunki.paalle();
System.out.println("lämpötila Pääkaupunkiseudulla "+paakaupunki.mittaa() + " astetta");
}
tulostuu (tulostetut lukuarvot riippuvat tietenkin arvotuista lämpötiloista):
lämpötila Kumpulassa -7 astetta lämpötila Pääkaupunkiseudulla -10 astetta
Huom: kannatata käyttää Vakiosensori-oliota keskiarvosensorin testaamiseen!
Lisää luokalle Keskiarvosensori metodi public List<Integer> mittaukset(), joka palauttaa listana kaikkien keskiarvosensorin avulla suoritettujen mittausten tulokset. Seuraavassa esimerkki metodin toiminnasta:
public static void main(String[] args) {
Sensori kumpula = new Lampomittari();
Sensori kaisaniemi = new Lampomittari();
Sensori helsinkiVantaa = new Lampomittari();
Keskiarvosensori paakaupunki = new Keskiarvosensori();
paakaupunki.lisaaSensori(kumpula);
paakaupunki.lisaaSensori(kaisaniemi);
paakaupunki.lisaaSensori(helsinkiVantaa);
paakaupunki.paalle();
System.out.println("lämpötila Pääkaupunkiseudulla "+paakaupunki.mittaa() + " astetta");
System.out.println("lämpötila Pääkaupunkiseudulla "+paakaupunki.mittaa() + " astetta");
System.out.println("lämpötila Pääkaupunkiseudulla "+paakaupunki.mittaa() + " astetta");
System.out.println("mittaukset: "+paakaupunki.mittaukset());
}
tulostuu (tulostetut lukuarvot riippuvat jälleen arvotuista lämpötiloista):
lämpötila Pääkaupunkiseudulla -10 astetta lämpötila Pääkaupunkiseudulla -4 astetta lämpötila Pääkaupunkiseudulla 5 astetta mittaukset: [-10, -4, 5]
Tässä tehtävässä tehdään sovellus tiedoston rivi- ja merkkimäärän laskemiseen.
Tee pakkaukseen tiedosto luokka Analyysi, jolla on konstruktori public Analyysi(File tiedosto). Toteuta luokalle metodi public int rivimaara(), joka palauttaa konstruktorille annetun tiedoston rivimäärän.
Metodi ei saa olla "kertakäyttöinen", eli sen pitää tuottaa oikea tulos myös usealla peräkkäisellä kutsulla. Huomaa, että kun teet tiedostoa vastaavan Scanner-olion, ja luet tiedoston koko sisällön nextLine-komennoilla, et voi käyttää enää samaa skanneria tiedoston uudelleenlukemiseen!
Huom: jos testit sanovat timeout, et todennäköisesti muista lukea tiedostoa ollenkaan, eli nextLine-kutsut puuttuvat!
Toteuta luokkaan Analyysi metodi public int merkkeja(), joka palauttaa luokan konstruktorille annetun tiedoston merkkien määrän.
Metodi ei saa olla "kertakäyttöinen", eli sen pitää tuottaa oikea tulos myös usealla peräkkäisellä kutsulla.
Voit itse päättää miten reagoidaan jos konstruktorin parametrina saatua tiedostoa ei ole olemassa.
Projektisi testipakkauksessa on testausta varten tiedosto testitiedosto.txt. Ohjelmasta avatessa tiedoston nimeksi tulee antaa test/testitiedosto.txt. Tiedoston sisältö on seuraava:
rivejä tässä on 3 ja merkkejä koska rivinvaihdotkin ovat merkkejä
Ohjelman toiminta testaustiedostolla:
File tiedosto = new File("src/testitiedosto.txt");
Analyysi analyysi = new Analyysi(tiedosto);
System.out.println("Rivejä: " + analyysi.rivimaara());
System.out.println("Merkkejä: " + analyysi.merkkeja());
Rivejä: 3 Merkkejä: 67
Tehtäväpohjassa tulee mukana luokka Varasto, jonka tarjoamat konstruktorit ja metodit ovat seuraavat:
Tehtävässä rakennetaan Varasto-luokasta useampia erilaisia varastoja. Huom! Toteuta kaikki luokat pakkaukseen varastot.
Luokka Varasto hallitsee tuotteen määrään liittyvät toiminnot. Nyt tuotteelle halutaan lisäksi tuotenimi ja nimen käsittelyvälineet. Ohjelmoidaan Tuotevarasto Varaston aliluokaksi! Toteutetaan ensin pelkkä yksityinen oliomuuttuja tuotenimelle, konstruktori ja getteri nimikentälle:
Muista millä tavoin konstruktori voi ensi toimenaan suorittaa yliluokan konstruktorin!
Käyttöesimerkki:
Tuotevarasto mehu = new Tuotevarasto("Juice", 1000.0);
mehu.lisaaVarastoon(1000.0);
mehu.otaVarastosta(11.3);
System.out.println(mehu.getNimi()); // Juice
System.out.println(mehu); // saldo = 988.7, tilaa 11.3
Juice saldo = 988.7, vielä tilaa 11.3
Kuten edellisestä esimerkistä näkee, Tuotevarasto-olion perimä toString() ei tiedä (tietenkään!) mitään tuotteen nimestä. Asialle on tehtävä jotain! Lisätään samalla myös setteri tuotenimelle:
Uuden toString()-metodin voisi toki ohjelmoida käyttäen yliluokalta perittyjä gettereitä, joilla perittyjen, mutta piilossa pidettyjen kenttien arvoja saa käyttöönsä. Koska yliluokkaan on kuitenkin jo ohjelmoitu tarvittava taito varastotilanteen merkkiesityksen tuottamiseen, miksi nähdä vaivaa sen uudelleen ohjelmointiin. Käytä siis hyväksesi perittyä toStringiä.
Muista miten korvattua metodia voi kutsua aliluokassa!
Käyttöesimerkki:
Tuotevarasto mehu = new Tuotevarasto("Juice", 1000.0);
mehu.lisaaVarastoon(1000.0);
mehu.otaVarastosta(11.3);
System.out.println(mehu.getNimi()); // Juice
mehu.lisaaVarastoon(1.0);
System.out.println(mehu); // Juice: saldo = 989.7, tilaa 10.299999999999955
Juice Juice: saldo = 989.7, tilaa 10.299999999999955
Toisinaan saattaa olla kiinnostavaa tietää, millä tavoin jonkin tuotteen varastotilanne muuttuu: onko varasto usein hyvin vajaa, ollaanko usein ylärajalla, onko vaihelu suurta vai pientä, jne. Varustetaan siksi Tuotevarasto-luokka taidolla muistaa tuotteen määrän muutoshistoriaa.
Aloitetaan apuvälineen laadinnalla.
Muutoshistorian muistamisen voisi toki toteuttaa suoraankin ArrayList<Double>-oliona luokassa Tuotevarasto, mutta nyt laaditaan kuitenkin oma erikoistettu väline tähän tarkoitukseen. Väline toteutetaan kapseloimalla ArrayList<Double>-olio.
Muutoshistoria-luokan julkiset konstruktorit ja metodit:
Muutoshistoria-olion.
Täydennä Muutoshistoria-luokkaa analyysimetodein:
Täydennä Muutoshistoria-luokkaa analyysimetodein:
Ohjeen varianssin laskemiseksi voit katsoa esimerkiksi Wikipediasta kohdasta populaatio- ja otosvarianssi. Esimerkiksi lukujen 3, 2, 7, 2 keskiarvo on 3.5, joten otosvarianssi on ((3 - 3.5)² + (2 - 3.5)² + (7 - 3.5)² + (2 - 3.5)²)/(4 - 1) ≈ 5,666667.)
Toteuta luokan Tuotevarasto aliluokkana MuistavaTuotevarasto.
Uusi versio tarjoaa vanhojen lisäksi varastotilanteen muutoshistoriaan liittyviä
palveluita. Historiaa hallitaan Muutoshistoria-oliolla.
Julkiset konstruktorit ja metodit:
Huomaa että tässä esiversiossa historia ei vielä toimi kunnolla; nyt vasta vain aloitussaldo muistetaan.
Käyttöesimerkki:
// tuttuun tapaan:
MuistavaTuotevarasto mehu = new MuistavaTuotevarasto("Juice", 1000.0, 1000.0);
mehu.otaVarastosta(11.3);
System.out.println(mehu.getNimi()); // Juice
mehu.lisaaVarastoon(1.0);
System.out.println(mehu); // Juice: saldo = 989.7, vielä tilaa 10.3
...
// mutta vielä historia() ei toimi kunnolla:
System.out.println(mehu.historia()); // [1000.0]
// saadaan siis vasta konstruktorin asettama historian alkupiste...
...
Tulostus siis:
Juice Juice: saldo = 989.7, vielä tilaa 10.299999999999955 [1000.0]
On aika aloittaa historia! Ensimmäinen versio ei historiasta tiennyt kuin alkupisteen. Täydennä luokkaa metodein
Varasto-luokan metodi, mutta muuttunut tilanne kirjataan historiaan. Huom: historiaan tulee kirjata poiston jälkeinen varastosaldo, ei poistettavaa määrää!Käyttöesimerkki:
// tuttuun tapaan:
MuistavaTuotevarasto mehu = new MuistavaTuotevarasto("Juice", 1000.0, 1000.0);
mehu.otaVarastosta(11.3);
System.out.println(mehu.getNimi()); // Juice
mehu.lisaaVarastoon(1.0);
System.out.println(mehu); // Juice: saldo = 989.7, vielä tilaa 10.3
...
// mutta nyt on historiaakin:
System.out.println(mehu.historia()); // [1000.0, 988.7, 989.7]
...
Tulostus siis:
Juice Juice: saldo = 989.7, vielä tilaa 10.299999999999955 [1000.0, 988.7, 989.7]
Muista miten korvaava metodi voi käyttää hyväkseen korvattua metodia!
Täydennä luokkaa metodilla
Käyttöesimerkki:
MuistavaTuotevarasto mehu = new MuistavaTuotevarasto("Juice", 1000.0, 1000.0);
mehu.otaVarastosta(11.3);
mehu.lisaaVarastoon(1.0);
//System.out.println(mehu.historia()); // [1000.0, 988.7, 989.7]
mehu.tulostaAnalyysi();
Metodi tulostaAnalyysi kirjoittaa ilmoituksen tyyliin:
Tuote: Juice Historia: [1000.0, 988.7, 989.7] Suurin tuotemäärä: 1000.0 Pienin tuotemäärä: 988.7 Keskiarvo: 992.8
Täydennä analyysin tulostus sellaiseksi, että mukana ovat myös muutoshistorian suurin muutos ja historian varianssi.
Tehtävässä on tarkoitus toteuttaa yksinkertainen laskin. Laskimen käyttöliittymän tulee olla seuraavanlainen:

Tehtäväpohjan mukana tulee käynnistyksen suorittava pääohjelma sekä graafisen käyttöliittymän sisältävä luokka GraafinenLaskin. Käyttöliittymän on oltava täsmälleen seuraavassa osassa kuvaillulla tavalla tehty, muuten saat vapaasti suunnitella ohjelman rakenteen.
Käyttöliittymän pohjana olevassa JFramessa tulee käyttää asettelijana GridLayoutia jossa on kolme riviä ja yksi sarake. Ylimpänä on tuloskenttänä toimiva JTextField, joka täytyy asettaa "pois päältä" metodikutsulla setEnabled(false). Toisena on syötekenttänä toimiva JTextField. Tuloskentässä on aluksi teksti 0 ja syötekenttä on tyhjä.
Alimpana komponenttina sijaitsee JPanel, jonka asettelijana on GridLayout, jossa on yksi rivi ja kolme saraketta. JPanelissa on kolme JButtonia, joissa tekstit "+", "-" ja "Z".
Laskimen käyttöliittymän koon on oltava vähintään 300*150.
Laskimen toimintalogiikka on seuraava. Käyttäjän kirjoittaessa syötekenttään luvun n ja painaessa +, lisätään tuloskentässä olevaan arvoon n ja päivitetään tuloskenttä uuteen arvoon. Vastaavasti käyttäjän kirjoittaessa syötekenttään luvun n ja painaessa -, vähennetään tuloskentässä olevasta arvosta n ja päivitetään tuloskenttä uuteen arvoon. Jos käyttäjä painaa Z, nollautuu tuloskenttä.
Vihje: joskus on tarkoituksenmukaista hoitaa yhdellä tapahtumankuuntelijalla usean napin painallusten käsittely. Tämä onnistuu kysymällä actionPerformed-metodin parametrilta kuka oli tapahtuman aiheuttaja. Seuraava koodi olettaa, että tapahtumankäsittelijällä on oliomuuttujat JButton plus ja JButton miinus jotka viittaavat plus- ja miinus-nappeihin:
@Override
public void actionPerformed(ActionEvent ae) {
if (ae.getSource() == plus) {
// käsittele plus-painike
} else if (ae.getSource() == miinus) {
// käsittele miinus-painike
} else ...
Laajennetaan vielä ohjelmaa seuraavilla ominaisuuksilla:
setEnabled(false). Muissa tilanteissa napin tulee olla päällä.Nelisenkymmentä vuotta sitten julkaistiin peli nimeltä Space Race, joka lupasi kahdelle pelaajalle huikean kilpakokemuksen avaruusajelun herruudesta. Katso peliä kuvaava Youtube-video täältä. Tässä tehtävässä tarkoituksena on muokata pelipohjaa, johon Space Race on toteutettu. Toteutuksessa on käytetty Slick2D nimistä apukirjastoa, joka tarjoaa hyötyvälineitä 2-ulotteisten pelien rakentamiseen.
Tästä tehtävästä voi saada 3 arpaa. Ensimmäinen arpa tulee pelin muokkauksesta, toinen arpa muokkauksen luovuudesta, ja kolmas muokkauksen lisäämästä hauskuudesta. Koska kaksi jälkimmäistä arpaa ovat subjektiivisia, päättävät kisan järjestäjät kullekin osallistujalle annettavat pisteet ennen arvontaa.