Kurssilla on tähän saakka luettu syöttiedot ponnahdusikkunoita käyttäen. Tuloksia on ponnahdusikkunoiden liskäksi kirjoitettu standarditulosvirtaan System.out. Nyt on aika oppia lukemaan syöttötietoja myös standardisyöttövirrasta System.in. Oletusarvoisesti nämä "virrat" tarkoittavat kuvaruutua ja näppäimistöä, mutta ne voidaan ohjata tarkoittamaan myös tekstitiedostoja. Tästä lisää jatkokurssilla.
Javalla syöttötietoja voi lukea käyttämällä ns.
Scanner
-oliota.
Scanner
-luokalla on mm. sellainen konstruktori,
jolle annetaan parametriksi standardisyöttövirta
System.in. Luokka tarjoaa mm. seuraavat aksessorit:
Tuttu esimerkki ilman ponnahdusikkunoita:
import java.util.Scanner; // Scanner-luokka tuodaan käännösyksikköön public class Viisas { private static Scanner lukija = new Scanner(System.in); // luodaan Scanner-olio public static void main(String[] args) { String nimi; int ika; double pituus; System.out.println("Mikä on nimesi?"); nimi = lukija.nextLine(); // sovelletaan aksessoria nextLine(); System.out.print("Ja ikäsi: "); ika = lukija.nextInt(); // sovelletaan aksessoria nextInt(); System.out.print("Entä pituutesi? "); pituus = lukija.nextDouble(); // Syötteessä käytettävä desimaalipilkkua!! System.out.println("Moi " + nimi +"!"); System.out.print("Tiedän että olet " + ika + "-vuotias "); System.out.println("ja että olet " + pituus + " senttiä pitkä."); System.out.println("Enkö olekin viisas!"); } }
Syöttö tapahtuu siis nyt Java-ohjelman komentotulkki-ikkunassa, siinä samassa, jonne System.out.println tulostaa.
Laitoksen koneissa desimaaliluku on siis syötettävä desimaalipilkullisena vaikka ohjelma itse käyttääkin desimaalipistettä! Syynä on "oletuslokalisaatio". Se voi omassa koneessasi olla eri...
Mikä on nimesi? Violetta Ja ikäsi: 20 Entä pituutesi? 167,8 Moi Violetta! Tiedän että olet 20-vuotias ja että olet 167.8 senttiä pitkä. Enko olekin viisas?
Jos ohjelmalle syöttää väärän tyyppistä tietoa, ohjelman suoritus päättyy virheeseen. Myös desimaalipisteen käyttö siis kaataa ohjelman.
---> Lisätietoa desimaalipisteen sallimisesta
Javassa näppäimistöltä syötettävä teksti (samoin kuin tekstitiedostojen sisältö) muodostuu riveistä. Tietokoneen muistia ei tietenkään ole painetun tai kirjoitetun tekstin tavoin organisoitu riveittäin. Koneessa rivinvaihto muiden merkkien tapaan esitetään bittijonoksi koodattuna. Linuxissa/Unixissa rivin loppu ilmaistaan yhdellä merkillä, Windowsissa kahdella. Java-ohjelmoijan ei tarvitse tästä kantaa huolta – Java tietää, millaisessa ympäristössä sitä käytetään.
Kun tietoa kirjoitetaan näppäimistöltä, ohjelma saa kirjoitetun tekstin ns. syöttöpuskurissa, jonne viedään kaikki teksti seuraavaan rivinvaihtoon saakka (eli enter-näppäimen painallukseen saakka).
Jos kirjoitetaan vaikkapa kaksi välilyöntiä, merkit "2", "1", "3" ja vielä kolme välilyöntiä, syöttöpuskurissa on:
213 @ (loppumerkki) ^ (seuraavaksi luettava merkki)
Merkitään näissä erimerkeissä loppumerkkiä näin: @. Syöttöpuskurissa on myös osoitin, joka osoittaa ensimmäistä lukemantonta merkkiä. Ilmaistaan se tässä merkillä ^.
Kun tässä tilanteessa luetaan
int i = nextInt();
Muuttuja i saa arvokseen kokonaisluvun 213 ja syöttöpuskurin tilanne on seuraava:
213 @ ^
Jos samassa lähtötilanteessa
213 @ ^
luetaan merkkijo tyyliin
String jono = nextLine();puskuriin syntyy tilanne:
Eli puskuri on tyhjä ja muuttuja jono
on arvoltaan "__123___"
(välilyönnit on tässä merkattu alaviivalla).
Scanner-aksessori nextLine()
siis lukee myös loppumerkin, vaikkei
sisällytäkään sitä palauttamaansa merkkijonoon!
Kuten yllä opittiin, Scanner-luokka siis mm. lukuoperaatiot:
Näistä operaatioista kolme ensin lueteltua toimivat eri logiikalla kuin viimeksi mainittu:
nextInt()
ei lue puskurista pois rivin loppumerkkiä.
Operaatio nextLine() sen sijaan ei white space -merkkejä tunnista. Se palauttaa arvonaan merkkijonona kaiken "seuraavaksi luettavasta merkistä" aina seuraavaan rivinvaihtoon saakka (ilman rivinvaihtomerkkiä, jonka se kuitenkin "syö"):
Esimerkki: Olkoon lähtötilanne:
213@ ^
Operaation
int i = nextInt();
jälkeen puskurissa on
213@ ^
Eli seuraavaksi tarjolla on loppumerkki. Jos nyt luetaan
String jono = nextLine();
muuttuja jono
saa arvokseen tyhjän merkkijonon ""
ja
"ensimmäisen lukemattoman merkin" osoitin siirtyy seuraavan rivin alkuun,
joka vuorovaikutteisen ohjelman tapauksessa syntyy vasta, kun käyttäjä päättää
syöttää seuraavan rivin.
Tämä selittänee joidenkin varmasti havaitseman "synkronointiongelman" tietojen syötössä: Jos käytetään nextLine()-operaatiota noiden kolmen muun kanssa, loppumerkin käsittelyn kanssa on syytä olla tarkkana.
Virheellisen tyyppinen syöte johtaa ohjelman suorituksen keskeytymiseen:
Jos esimerkiksi nextDouble()
-operaatiolle syötetään desimaaliluvuksi
kelpaamaton arvo, kaikki päättyy ikävällä tavalla:
Exception in thread "main" java.util.InputMismatchException at java.util.Scanner.throwFor(Scanner.java:819) at java.util.Scanner.next(Scanner.java:1431) at java.util.Scanner.nextDouble(Scanner.java:2335) at KolmeKarvo.main(KolmeKarvo.java:14)
Tällainen ei tietenkään ole oikeissa ohjelmissa hyväksyttävää! Niissä syöttötietojen oikeellisuus pitää siis tarkistaa.
Scanner-luokassa on joukko totuusarvoisia eli boolean-tyyppisiä aksessoreita, joilla voi kysyä: "Jos lukisin, saisinko sen ja sen tyyppisen arvon". Metodit siis tavallaan "kurkistavat tulevaisuuteen". Oikeasti ne vain käyvät kurkkaamassa syöttöpuskurin sisältöä:
Näillä välineillä voidaan tarkistaa esimerkiksi, onko seuraava syöttöalkio kokonaisluku:
import java.util.Scanner; public class Tarkista1 { private static Scanner lukija = new Scanner(System.in); public static void main(String[] args) { int luku; System.out.println("Anna kokonaisluku."); if (lukija.hasNextInt()) { luku = lukija.nextInt(); System.out.println("Annoit luvun " + luku); } else { System.out.println("Et syöttänyt kokonaislukua!"); } } }
Toki voidaan myös vaatia kunnon kokonaisluku:
import java.util.Scanner; public class Tarkista2 { private static Scanner lukija = new Scanner(System.in); public static void main(String[] args) { int luku; while (true) { // ***** keskeytys breakilla! System.out.println("Anna kokonaisluku."); if (lukija.hasNextInt() ) { luku = lukija.nextInt(); // nyt uskaltaa lukea! break; // ***** keskeytys breakilla! } String virheellinen = lukija.next(); // ohitetaan kelvoton alkio System.out.print (virheellinen + " ei ole kokonaisluku! "); System.out.println("Yritä uudelleen!"); } System.out.println("Annoit luvun " + luku); } }
Koska tällä kurssilla harjoitellaan ohjelmoinnin perusvälineistöä, useinkaan ei ole syytä käyttää voimia ja ohjelmarivejä syöttötietojen tarkastamiseen, jottei opeteltava uusi asia hukkuisi muun ohjelmatekstin sisään. Jos ja kun harjoitus- ja koetehtävissä syötteiden tarkistamista vaaditaan, se sanotaan erikseen.
On kuitenkin syytä pitää mielessä se jo useampaan kertaan todettu sääntö, että "oikeissa" käyttäjille tarkoitetuissa ohjelmissa syöttötiedot tarkistetaan aina
Huom: "Kurkistus tulevaisuuteen" on luontevinta, kun luetaan tekstitiedostoja. Interaktiivisessa tietojen syöttämisessä "tulevaisuus" on käyttäjän aivoituksissa. Tällöinkin "tiedoston loppumisen" voi ilmaista: Linuxissa ctrl-d, Windowsissa ctrl-z. (Jos edehdyt Linuxissa syöttämään ctrl-z, ohjelma joutuu ns. "backgroundiin" ja uloskirjoittautuminen voi estyä. Pelastus on Linux-komento fg, joka palauttaa ohjelman näkyville, "foreground"!) Tiedostojen käsittelyä opetellaan jatkokurssilla.
Scanner-luokka on monipuolinen väline. Sen lisäksi, että sillä voidaan kehittynein välinein lukea syöttövirtaa, aivan samoin aksessorein voidaan tutkia myös yksittäisen merkkijonon sisältöä!
Olio-ohjelmointiterminologialla ilmaisten: Scanner-luokassa on myös konstruktori, jolla voidaan luoda yksittäistä String-arvoa "skannaava" Scanner-olio. Samat tutut metodit ovat tällöinkin käytettävissä.
Tämä tarjoaa yhden mahdollisuuden virheitä sietävän tietojen lukemisen
toteuttamiseen: Idea on, että luetaan syöttövirtaa rivi kerrallaan operaatiolla
nextLine()
.
Yksi kerrallaan jokaisesta rivistä (String) luodaan Scanner-olio, jonka
avulla rivin sisältöä sitten tutkitaan ja kaivetaan tiedot esiin.
Esimerkki:
import java.util.Scanner; public class RiviLuku { private static Scanner lukija = new Scanner(System.in); public static void main(String[] args) { System.out.println("Anna kokonaislukuja. Tyhjä rivi päättää."); String rivi = lukija.nextLine(); while (rivi.length() > 0) { Scanner rivinSisalto = new Scanner(rivi); // joka rivistä oma Scanner! if (rivinSisalto.hasNextInt()) { int luku = rivinSisalto.nextInt(); System.out.println("Luku on: " + luku); } else System.out.println("Virheellinen rivi: " + rivi); rivi = lukija.nextLine(); } } }