Helsingin yliopisto / Tietojenkäsittelytieteen laitos
Copyright © 2005 Arto Wikla. Tämän oppimateriaalin käyttö on sallittu vain yksityishenkilöille opiskelutarkoituksissa. Materiaalin käyttö muihin tarkoituksiin, kuten kaupallisilla tai muilla kursseilla, on kielletty.

2.3 Kirjoittamista ja lukemista

(Muutettu viimeksi 9.9.2008, edellinen muutos 8.9.2005)

Tässä luvussa opetellaan ysinkertaisia välineitä syöttöön ja tulostukseen: kirjoitetaan teksti-ikkunaan (konsoli-ikkunaan) ja luetaan näppäimistöltä.

Nykyään useimmat "tavalliselle käyttäjälle" tarkoitetut tietokoneohjelmat perustuvat graafiseen käyttöliittymään. Sellaisen tekeminen Javalla vaatii kuitenkin melko paljon tietoa ja ymmärrystä kielestä ja sen kalustosta. Ohjelmoinnissa on paljon opittavaa ennen sitä.

Kirjoittamista

Ns. standarditulosvirtaan kirjoitetaan valmiiksi määritellyn luokan System välineillä. Muutamassa esimerkissä jo vähän tulosteltiinkin:
   System.out.println("Hoi maailma!");
   System.out.println( a/b );
   System.out.println(tulos);

Pitkähän tuo tulostusoperaatio on kirjoittaa, mutta onneksi sen käyttö on helppoa. Tulostettavana on aina korkeintaan yksi arvo. Jos haluaa tulostaa vaikkapa tekstejä ja lukuarvoja, niistä on tehtävä yksi merkkijono. Esimerkiksi lauseet:
     int kpl = 123;

     System.out.println("Lukumäärä on nyt" + kpl + "kappaletta.");
     System.out.println("Lukumäärä on nyt " + kpl + " kappaletta.");
tulostavat:
Lukumäärä on nyt123kappaletta.
Lukumäärä on nyt 123 kappaletta.

Operaatio print tulostaa ilman rivinvaihtoa. Edellisen esimerkin jälkimmäinen tulostus saadaan aikaan siis vaikkapa lausein:
     System.out.print("Lukumäärä on nyt ");
     System.out.print(kpl);
     System.out.println(" kappaletta.");
tai:
     System.out.print("Lukumäärä on nyt ");
     System.out.print(kpl);
     System.out.print(" kappaletta.");
     System.out.println();

Koska merkkijono alkaa ja loppuu lainausmerkkiin, tulostettava lainausmerkki ilmaistaan merkkiyhdistelmänä \". Myös eräille muille erikoistulostuksille on oma ilmauksensa, uusi rivi on \n, tabulaattori on \t ja takakeno on \\. Esimerkiksi lauseet:

     System.out.println("Hän sanoi \"kiitos\"!");

     System.out.println("Tyhjiä rivejä:\n\n\n");

     System.out.println("\tTabuloitu kerran");
     System.out.println("\t\tTabuloitu kahdesti");

     System.out.println("Takakeno: \\");
     System.out.println("Pari takakenoa: \\\\");
tulostavat:
Hän sanoi "kiitos"!
Tyhjiä rivejä:



        Tabuloitu kerran
                Tabuloitu kahdesti
Takakeno: \
Pari takakenoa: \\


Huom:Merkkijonoja ja lukuarvoja tulostettaessa on muistettava, että "+" voi tarkoittaa yhteenlaskua tai katenointia, järjestys ratkaisee. Lauseet:
   System.out.println("luku on "+2+3);
   System.out.println(2+3+" on luku");

   System.out.println(77+88);
   System.out.println(""+77+88);
tulostavat
luku on 23
5 on luku
165
7788

Huom: Javan versiossa 1.5 kieleen otettiin mukaan C-kielen mallin mukainen muotoiltu tulostus, "printf"-lauseineen. Tässä vaiheessa tyydytään kuitenkin Javan "klassiseen" tulostustapaan.

Lukemista

Syöttötietojen lukeminen mm. näppäimistöltä oli Javassa aika monimutkaista ennen kielen versiota 1.5. Uusi versio toi mukanaan aloittelijallekin soveltuvia lukemisen välineitä. Jos käytettävissä on vain kielen jokin vanhempi versio, tietoja voi lukea tällä kurssilla aiemmin käytetyllä välineellä, luokan Lue avulla.

Tämän luvun esimerkeissä ja selityksissä esiintyy koko joukko sanoja ja ilmaisuja, jotka voi kunnolla ymmärtää vasta opittuaan melkoisesti lisää Javaa. Ei siis kannata vielä huolestua!

Jatkossa selitykset, jotka voi ymmärtää myöhemmin tällä kurssilla tai vasta Java-kurssin aikana, on merkitty ilmauksella: "Opitaan aikanaan:". Tällaiset selitykset täällä ovat enemmän jo osaavien iloksi ja ennen kaikkea siksi, että niihin voi enemmän opittuaan palata.

Ohjelmalle tarkoitetut syötteet saadaan ns. standardisyöttövirrasta, joka yleensä tarkoittaa näppäimistöä. Tuo virta on ohjelmoijan käytettävissä nimellä System.in. Tuolla kaikki tieto näyttäytyy ns. tavuina (byte), aloittelevan ohjelmoijan kannalta pelkkänä "bittimoskana". Luokka Scanner tarjoaa keinoja jalostaa tuosta tavujen jonosta kokonaislukuja, desimaalilukuja, merkkijonoja, ...

Esimerkkiohjelma Kaiuta.java lukee ja tulostaa kokonaisluvun:

import java.util.Scanner;

public class Kaiuta {

  private static Scanner lukija = new Scanner(System.in);

  public static void main(String[] args) {
    int i;
    i = lukija.nextInt();
    System.out.println(i);
  }
}

Opitaan aikanaan: Ensin ohjelman käyttöön tuodaan Scanner-luokka ohjelmatiedoston alussa olevalla ilmauksella import java.util.Scanner; Sitten ns. luodaan Scanner-olio ja asetetaan se yksityisen luokkamuuttujan lukija arvoksi ilmauksella private static Scanner lukija = new Scanner(System.in); Scanner-oliolle annetaan tieto siitä, että halutaan "skannata tietoja" nimenomaan standardisyöttövirrasta System.in. Luokkamuuttuja lukija on käytettävissä kaikkialla luokan sisällä, erityisesti luokan kaikissa metodeissa.

Scanner-olio osaa erinäisiä asioita, mm. lukea kokonaislukuja. Ilmaus i = lukija.nextInt(); sanoo, että yritetään lukea kokonaisluku ja sijoittaa se kokonaislukumuuttujan i arvoksi.

Harjoitus: Kokeile ohjelmaa Kaiuta. Miten käy kun ohjelmalle syötetään kokonaisluku? Entä useita kokonaislukuja samalla rivillä? Entä jos annetaankin desimaaliluku tai merkkijono? Saako kokonaislukua ennen syöttää välilyöntejä tai rivinvaihtoja?

Muutamia Scanner-metodeita:

Kaikki nämä ns. palauttavat arvonaan luetun syöttötiedon. Niitä siis käytetään lausekkeina eli niillä itsellään on arvo:

    int luku1;
    double luku2;
    String mjono;

    luku1 = lukija.nextInt();
    luku2 = lukija.nextDouble();
    mjono = lukija.nextLine();

Huom: Nuo tyhjät sulkeet operaatioiden yhteydessä ovat välttämättömät. Operaatiot on toteutettu Scanner-luokassa ns. metodeina. Metodeista pian lisää.

Huom: Laitoksen Java-järjestelmässä metodi nextDouble() siis oletusarvoisesti vaatii syötettävässä desimaaliluvussa käytettäväksi desimaalipilkkua -- desimaalipiste johtaa virheeseen ja ohjelman suorituksen keskeytykseen. Tämä on sikäli kiusallista, että ohjelmatekstissä puolestaan ainoastaan desimaalipiste on käytettävissä! Jos halutaan sallia (ja vaatia) nimenomaan desimaalipiste käyttö, seuraavasti se saadaan aikaan:

import java.util.Scanner;  // tuodaan käyttöön luokka Scanner
import java.util.Locale;   // tuodaan käyttöön luokka Locale

public class LuokkaNimeltaLuokkaTms {

  private static Scanner lukija =
    new Scanner(System.in).useLocale(new Locale("en_GB"));

  public static void main(String[] args) {
    ...
    luku2 = lukija.nextDouble();  // nyt vaaditaan desimaalipiste
  }
}
Tällä kurssilla ero ei ole oleellinen, mutta on hyvä tietää tuo tapa vastaisen varalle! [Kiitokset menettelytavan esiin kaivamisesta 2009 touko-kesäkuun ohjelmointikurssin opiskelijalle Sakari Vaelmalle!]

Ensimmäiset oikeat ohjelmat

Tehdään ensin pikku kokeilu - eräänlainen tekoälyohjelma - lukemisvälineiden käytöstä (Viisas.java):
import java.util.Scanner;  // Viisas ohjelma, AW-2005

public class Viisas {

  private static Scanner lukija = new Scanner(System.in);

  public static void main(String[] args) {

    String syottörivi;
    int    kokonaisluku;
    double desimaaliluku;

    System.out.println("Mikä on nimesi?");
    syottörivi = lukija.nextLine();
    System.out.println("Mitä kokonaislukua ajattelet?");
    kokonaisluku = lukija.nextInt();
    System.out.println("Mitä desimaalilukua ajattelet?");
    desimaaliluku = lukija.nextDouble();

    System.out.print  ("Tiedän, että nimesi on "+syottörivi);
    System.out.println(" ja että ajattelit lukuja "+ 
                       kokonaisluku+" ja "+ desimaaliluku+".");
    System.out.println("Enkö olekin viisas!");
  }
}
Ohjelman suoritus voi näyttää vaikkapa seuraavalta:
Mikä on nimesi?
Arto
Mitä kokonaislukua ajattelet?
49
Mitä desimaalilukua ajattelet?
3,14
Tiedän, että nimesi on Arto ja että ajattelit lukuja 49 ja 3.14.
Enkö olekin viisas!
Huomaa miten ohjelmalle syötettiin desimaaliluku desimaalipilkkua käyttäen, mutta ohjelma tulosti luvun desimaalipisteellisenä! Myös ohjelmatekstissä saa käyttää vain desimaalipistettä. Kiusallista! Mutta edellä nähtiin, miten homma voidaan hoitaa paremmin. Kurssimateriaalissa ja harjoituksissa voidaan jatkossa tyytyä tähän "piirteeseen" -- kyse ei ole loogisesti kiinnostavasta asiasta. Joissakin esimerkeissä "pistekorjaus" kuitenkin tehdään.

Harjoitus: Kokeile ohjelmaa Viisas erilaisilla oikeilla ja virheellisillä syötteillä.

Laaditaan vähän järkevämpi sovellus kolmen luvun keskiarvon laskemiseen. Ohjelma on vuorovaikutteinen (interactive) eli se keskustelee käyttäjän kanssa. Tehdään esimerkin vuoksi tähän ohjelmaan tuo edellä nähty "desimaalipistekorjaus". (Kolmekarvo.java):

import java.util.Scanner;
import java.util.Locale;

public class KolmeKarvo {
  private static Scanner lukija =
       new Scanner(System.in).useLocale(new Locale("en_GB"));

  public static void main(String[] args) {

    double luku1, luku2, luku3;
    double summa, karvo;

    System.out.println("\n  **** Keskiarvo-ohjelma ****\n");

    System.out.print("Anna 1. luku: ");
    luku1 = lukija.nextDouble();
    System.out.print("Anna 2. luku: ");
    luku2 = lukija.nextDouble();
    System.out.print("Anna 3. luku: ");
    luku3 = lukija.nextDouble();

    summa = luku1 + luku2 + luku3;
    karvo = summa/3;

    System.out.println("Lukujen keskiarvo on " + karvo);
  }
}
Tämän ohjelman suoritus voi näyttää seuraavanlaiselta:
  **** Keskiarvo-ohjelma ****

Anna 1. luku: 123.4
Anna 2. luku: -567.8
Anna 3. luku: 9.0
Lukujen keskiarvo on -145.13333333333333

Huom: Kuten on nähty (jos asiaa on tullut kokeilluksi ;-) virheellisten syöttötietojen antaminen johtaa ohjelman suorituksen keskeytymiseen, "ohjelman kaatumiseen". Jos vaikkapa ohjelmalle KolmeKarvo antaa virheellisen syötteen, käy seuraavasti:

  **** Keskiarvo-ohjelma ****

Anna 1. luku: pii
Exception in thread "main" java.util.InputMismatchException
        at java.util.Scanner.throwFor(Scanner.java:840)
        at java.util.Scanner.next(Scanner.java:1461)
        at java.util.Scanner.nextDouble(Scanner.java:2387)
        at KolmeKarvo.main(KolmeKarvo.java:18)
Tässä Java-tulkki ei hyväksy merkkijonoa "pii" desimaaliluvuksi, ja ohjelman suoritus keskeytetään virheilmoitukseen, joka ei tavalliselle ohjelman käyttäjälle ole suureksikaan avuksi.

"Oikeissa" ohjelmissa noin ei tietenkään saa käydä, mutta toistaiseksi tyydymme tilanteeseen. Myöhemmin kurssilla opitaan virhetilanteiden käsittelyä erilaisin tavoin: Jo luvussa 2.4 nähdään yksi tapa. Lisää opitaan jatkokurssin puolella luvussa 5.


Takaisin luvun 2 sisällysluetteloon.