4.
praktikum (Sõnetöötlus ja tekstiline
sisend/väljund)
Teemad
Klassid String ja StringBuilder, tekstifailidega
suhtlemine.
Pärast selle praktikumi läbimist
üliõpilane
- teab, et String
on viittüüp, mitte lihttüüp;
- tunneb klassi String
meetodeid ja
oskab
neid kasutada;
- tunneb klassi StringBuilder
meetodeid ja
oskab
neid kasutada;
- oskab teksti kirjutada faili ja sealt lugeda.
Sõned
Erinevalt arvutüüpidest
(täisarvutüübid int
,
byte
,
short
,
long
ja
reaalarvutüübid double
,
float
),
loogilisest
tüübist (boolean
)
ja
sümboltüübist (char
)
pole sõnetüüp keeles Java algtüüp, vaid iga sõne on klassi String
isend. Kui vaataksime näiteks Java
API-st, siis näeme, et klassi String
isendi loomiseks on võimalik kasutada üle
kümne
erineva konstruktori. Näiteks on nende hulgas konstruktor, mis
nõuab argumendiks sümbolite järjendit.
char[]
sümboliteJärjend = {'T','e','r','e','
','h','o','m','m','i','k','u','s','t'};
String teade = new String(sümboliteJärjend);
System.out.println(teade);
On aga ka lihtsam viis klassi String
isendi loomiseks, nimelt kiirloome sõneliteraali
abil:
String teade
= "Tere hommikust";
(Literaal
on konkreetse väärtuse üleskirjutus
programmis. Väärtuse tüüp on
määratud kirjakujuga, nt. 15 on int-tüüpi,
15L aga long-tüüpi.
Sõneliteraali tähistavad jutumärgid. Kui
sõneliteraali sees on vaja jutumärke kasutada, siis
saab seda teha langkriipsu abil, nt "\"".)
Edasi, kui
klassi String
isend on loodud, saab kasutada klassis String
defineeritud isendimeetodeid, näiteks length,
toLowerCase
jne. Nagu eelmises praktikumis õppisime, kasutatakse
isendimeetodit koos konkreetse isendiga. Isendi nimi ja
meetodi nimi ühendatakse punktiga, näiteks
sõne teade
pikkus leitakse järgmiselt:
teade.length()
Järgnev näiteprogramm demonstreerib klassi String
meetodeid. Pange tähele, et sõne sümbolite
nummerdamine algab nullist (analoogiliselt massiiviindeksiga). Leidke
nende meetodite täpsed kirjeldused Java
API-st.
Pöörake tähelepanu meetodi signatuurile
(eriti
parameetrite arvule ja nende tüüpidele) ning
tagastustüübile. Tutvuge API-s ka teiste klassi String
meetoditega.
class
TestString {
public static void main(String[] args) {
String nimi = "Mart Mardikas";
System.out.println("Sõne
pikkus on: " + nimi.length()); // 13
System.out.println(nimi.startsWith("Mart")); // true
System.out.println(nimi.endsWith("kas")); // true
System.out.println(nimi.endsWith("Mart")); // false
System.out.println("\'a\' esimene
positsioon: " +
nimi.indexOf('a')); // 1
int rIndex = nimi.indexOf('r');
System.out.println("\'r\' esimene
positsioon: " + rIndex); // 2
System.out.println("\'r\' jargmine
positsioon: " +
nimi.indexOf('r', rIndex + 1)); // 7
int aIndex = nimi.lastIndexOf('a');
System.out.println("\'a\' viimane
positsioon: " + aIndex); // 11
System.out.println("Alamsõne
\'Mardi\' algus: " +
nimi.indexOf("Mardi")); // 5
System.out.println("4. täht on
"+nimi.charAt(3)); // 't'
//Täpne võrdsuse
kontroll:
System.out.println(nimi.equals("Mart Mardikas")); // true
System.out.println(nimi.equals("mart
mardikas")); // false
//Suuri-väikesi tähti
mitteeristav võrdsuse kontroll:
System.out.println(nimi.equalsIgnoreCase("mart mardikas"));
// true
//Leksikograafiline
võrdlemine:
System.out.println(nimi.compareTo("Jaan
Jaaniste")); // >0
System.out.println(nimi.compareTo("Peeter Paan")); // <0
System.out.println(nimi.compareTo("Mart
Mardikas")); // =0
System.out.println(nimi.replace('M',
'P')); // 'Part Pardikas'
System.out.println(nimi.toUpperCase());
// 'MART MARDIKAS'
}
}Ülesanne
1
Kompileerige
ja käivitage klass TestString.Sõnede
tükeldamine
Üsna
sageli on vaja sõnesid tükeldada. Selleks on mitu
võimalust (nt. klass StringTokenizer).
Siin praktikumis kasutame tükeldamiseks klassi String
isendimeetodit split.
Selle meetodi tagastustüübiks on String[],
mis tähendab, et tulemuseks saadakse sõnede
massiiv.
Argumendiks on regulaaravaldis, mis määrab, milliseid
sümboleid lugeda eraldajateks. Järgmises
näites on
eraldajaks tühik
String[]
tükid = nimi.split(" ");
for
(int i = 0; i<tükid.length; i++)
System.out.println(tükid[i]);
Antud juhul saaksime siis ekraanile
Mart
Mardikas
Kui aga määrata eraldajaks täht "a"
String[]
tükid = nimi.split("a");,
siis saame ekraanile
M
rt
M
rdik
s
Regulaaravaldis võib olla ka keerulisem, näiteks "[art]"
korral loetakse eraldajaks nii tähte "a"
kui ka tähti "r"
ja "t".Ülesanne
2
Võtke
sõnena kasutusele üks lause, milles on
vähemalt 7
sõna. Koostage programm, mis leiab kõik lauses
esinevad
sõnad ja mõõdab, kui pikad need
on.
Ekraanile peab väljastatama alglause ning sõnad ja
nende
pikkused.Unicode
Java
kasutab tekstitöötlusel kooditabelit Unicode,
mis sisaldab
tähti jm. sümboleid erinevate tähestike
jaoks. Iga
sümbol on Javas esitatud 16-bitise märgita
täisarvuna,
seega võimaldab kooditabel esitada 216
= 65 536
erinevat sümbolit. Paljud teised programmeerimiskeeled
kasutavad
tekstitöötlusel ASCII kooditabelit, milles
sümbolite
kodeerimiseks kasutatakse 7-bitiseid arve (128 sümbolit).
Samas on
ASCII Unicode-i alamhulk – kooditabeli Unicode esimesed 128
sümbolit. Esimesed 256 moodustavad aga kooditabeli ISO-Latin-1
extended ASCII. Sõnede võrdlemisel,
näiteks
meetodiga compareTo
võrreldakse tegelikult paarikaupa nende sümbolite
arvulisi koode antud kooditabelis.Sõnede võrdlus
Ülaltoodud meetodite hulgas on mitmeid, mis kaht klassi String
isendit omavahel võrdlevad. Võrduse
kontrolliks saab kasutada meetodit equals. Miks
ei võiks kahe isendi võrdust aga kontrollida
märgipaari ==
abil nagu arvude võrduse kontrollimiseks? Tegelikult
ongi märgipaar ==
täiesti lubatud ja kompileerimisel veateadet ei tule. Mure
allikas on selles, et võrdusmärkide puhul
võrreldakse isendite viitasid, aga meetod equals arvestab
isendite sisu. Olukorra selgitamiseks loome kolm sõne,
esimese sümbolijärjendi ja kaks
ülejäänut sõneliteraali abil.
char[]
sümboliteJärjend = {'T','e','r','e'};
String
teade1 = new String(sümboliteJärjend);
String
teade2="Tere";
String
teade3="Tere";
Püüdke
ennustada, millised väärtused väljastatakse,
kui kasutame järgmisi võrdlusi.
System.out.println(teade1.equals(teade2));
System.out.println(teade2.equals(teade3));
System.out.println(teade1==teade2);
System.out.println(teade2==teade3);Ülesanne
3
Proovige
nüüd eeltoodud võrdlemise näited
läbi ja kontrollige oma ennustusi.
Täpsustavalt saab öelda, et kuna sõne on
Javas muutmatu ja sageli kasutatav, siis JVM (Java virtuaalmasin)
säästab mälu ja kasvatab jõudlust
sellega, et paneb kiirloome abil sama sõneliteraaliga loodud
klassi String
isendid
ühte. Seda sõne nimetatakse kanooniliseks sõneks.
Nii ongi viitade mõttes teade2
ja teade3
võrdsed, aga teade1
nendega mitte. Sisu mõttes on aga kõik kolm
võrdsed. Kuna tavaliselt on meil just sisu mõttes
võrdsust vaja kontrollida, siis kasutamegi meetodit equals.
Klass StringBuilder
Eespool
nägime palju meetodeid, mida saab rakendada klassi String
isendi puhul. Tähtis on aga silmas pidada, et need meetodid ei
muuda isendit ennast. Sõne on Javas muutumatu – tema sisu ei
saa muuta. Ka näiteks read
String
s = "Soome"
s
= "Poola";
ei muuda sõne s sisu.
Esimene rida loob isendi, mille sisuks on "Soome"
ja omistab selle viida s-ile.
Teine rida loob isendi, mille sisuks on "Poola"
ja omistab selle viida s-ile.
Esimene objekt jääb tegelikult alles (ega muutu), aga
tema poole ei saa enam pöörduda.
Võime öelda, et klass String
käsitleb sõne staatiliselt. Kui aga tahame
sõne dünaamiliselt käsitleda (nt. muuta),
siis on sobiv klass StringBuilder.
Klassis StringBuilder
(vt. API) on neli konstruktorit,
millest meie vaatleme kolme. Parameetriteta konstuktori abil
moodustub klassi StringBuilder isend, milles on kohti 16 sümboli jaoks, aga ühtegi sümbolit (esialgu) pole. Kui
isendi loomisel anda ette täisarv, siis
tekibki kohti niipaljude sümbolite jaoks. Kui argumendiks on
sõne, siis vastava sõne sümbolid "puhvrisse" pannaksegi.
StringBuilder()
StringBuilder(int)
StringBuilder(String)
Meetodeid
on mõnikümmend, mitmed neist on korduva nimega.
Meie jaoks olulisemad on
- append (lõppu
lisamine)
- capacity
(maht)
- charAt (sümbol
vastaval kohal)
- delete (kustutamine)
- insert (lisamine
vastavale kohale)
- length (pikkus)
- setCharAt
(sümboli
asendamine vastaval kohal)
- setLength
(pikkuse
muutmine)
- replace (asendamine)
- reverse (transponeerimine)
Toomegi
mõningad näited nende kasutamisest.
Kõigepealt mõned meetodid, mille
väärtuseks on täisarv ja mis veel isendi
sisu ei muuda.
StringBuilder sb = new StringBuilder("Suusalumi-suusalumi sadas
õhtust");
System.out.println(sb.capacity());
System.out.println(sb.length());
System.out.println(sb.indexOf("sada"));
Järgmine
meetod tagastab char-tüüpi
väärtuse.
System.out.println(sb.charAt(10));
Järgmised
meetodid on void-tüüpi
ja muudavad vastavalt sisu ja pikkust.
sb.setCharAt(10,
'p');
sb.setLength(20);
System.out.println(sb);
Järgmisedki meetodid muudavad
sisu, aga nad tagastavad ka viida isendile endale. Neid saab kasutada
kahel moel. Kui me pole tagastatavast viidast huvitatud, siis
võime neid meetodeid ilma omistamata kasutada.
sb.append("
ei sadanud");
sb.delete(10,18);
sb.insert(10,
"lum");
sb.replace(0,
5, "Uisu");
sb.reverse();
Seda omadust saab kasutada näiteks nii:
StringBuilder tervitusSB = new StringBuilder();
String tervitatav = "Andrus";
tervitusSB.append("Tere, ").append(tervitatav).append("!");Ülesanne
4
Katsetage
klassi StringBuilder meetodeid
ja vahetulemuste nägemiseks lisage sobivatesse kohtadesse rida
System.out.println(sb);
Tekstiline
sisend/väljund (I/O)
Päris
mitmes praktikumis oleme põgusalt vaadanud, kuidas saada
kasutajalt programmi töö alustamisel või
käigus informatsiooni. Vaatluse all on olnud
käsurealt programmi käivitamisel
sõnejärjendina saadavad argumendid (2. praktikum),
klassi Scanner
abil klaviatuurilt saadavad väärtused (1. praktikum)
ja klassi JOptionPane abil
korralduv dialoog (1. rühmatöö juhend). Käesolevas lõigus
püüame andmed saada kätte tekstifailist ja
tekstifaili ka kirjutada.
Failidega suhtlemise saab Javas korraldada mitmeti. Siinkohal toome
vaid ühe võimaluse. Kõigepealt
püüame määrata, millise failiga
suhtlemine käib. Selleks loome klassi java.io.File
isendi.
java.io.File
fail = new java.io.File("c:/temp/marsruut.txt");
Kasutada saab mitmesuguseid meetodeid (vt. API), millest hetkel vaatleme
meetodit exists, mille
abil saame teada, kas antud fail eksisteerib või mitte.
if
(fail.exists())
System.out.println("Fail on juba olemas");
else
System.out.println("Faili ei ole olemas");
Püüame nüüd sinna faili midagi
kirjutada. Selleks loome klassi java.io.PrintWriter
isendi. Selles klassis on mitmeid konstruktoreid (vt. API), meie kasutame seda, mille
argumendiks on fail. (Võite proovida ka varianti, kus
argumendiks on failinimi sõnena.)
java.io.PrintWriter
pw = new java.io.PrintWriter(fail);
Selle rea lisamisel tekib aga veateade, mis räägib
käsitlemata erindist (unhandled
exception). Erinditest tuleb hiljem eraldi praktikum,
siinkohal lahendame olukorra lihtsalt peameetodi päisele kahe
sõna lisamisega.
public
static void main(String[] args) throws Exception
Klassis PrintWriter
on mitmeid meetodeid, sealhulgas ka meile tuttavad print
ja println,
mida nüüd kasutamegi.
pw.print("Karl Ernst von Baeri ");
pw.println("tänav");
pw.print("Johann Wilhelm Friedrich Hezeli ");
pw.println("tänav");
pw.print("Juhan Liivi ");
pw.println("tänav");
Kui me enam juurde kirjutada ei taha, siis sulgeme voo.
pw.close();
Kas teate, kus need tänavad Tartus asuvad? Hezeli tänav sai oma nime alles 22. veebruaril 2012.Ülesanne
5
Püüdke
ülaltoodud programmilõikude abil vastav fail luua.
Vaadake mingi tekstiredaktoriga, kas fail tõesti
loodetud kujul täitus.
Failist lugemiseks kasutame klassi Scanner
(kasutasime seda tegelikult juba ka 1. praktikumi klaviatuurilt
lugemiseks). Isendi konstrueerimisel saab ette anda faili.
java.util.Scanner
sc = new java.util.Scanner(fail);
Kasutades meetodeid hasNextLine (mis kontrollib, kas on veel võtta ridu) ja nextLine (mis võtab järgmise rea) loeb järgmine programmilõik
failist andmeid ja väljastab need ekraanil nii, et eesnimed jäävad ära.
while (sc.hasNextLine()) {
String rida = sc.nextLine();
String[] tükid = rida.split(" ");
System.out.print(tükid[tükid.length-2]+" "+tükid[tükid.length-1]);
System.out.println();
}
Analoogilisi meetodeid on teisigi. Näiteks lekseemide (token) jaoks on hasNext
(mis kontrollib, kas on veel võtta lekseeme) ja next (mis
võtab järgmise lekseemi). Vt. ka API.
Pakettide nimede igakordse mainimise asemel võib ka vastavad
paketid importida, kirjutades programmi algusesse nt.
import java.util.Scanner;
java.io.PrintWriter;
java.io.File
Siis
võib
java.util.Scanner
sc = new java.util.Scanner(fail);
asemel
kirjutada
Scanner
sc = new Scanner(fail);
Sulgemiseks saab jälle kasutada meetodit close.Ülesanne
6 (kontroll)
Failis konverentsid.txt on
konverentside nimed, näiteks
International Conference on Computer Games 2007
Dark Side of the Moon 2004
Juulikuise lume uurimise konverents 1998
Eeldame, et iga nimi koosneb sõnadest, mille järel
on
neljakohaline aastaarv. Failis võib olla 1 kuni 30
konverentsinime.
Kirjutada programm, mis väljastab ekraanile kõigi
konverentsinimede lühendid, võttes igast
sõnast esitähe (suurtähtena) ja aastaarvu
kaks viimast
numbrit ning asetades nende vahele ülakoma.
Näiteks ülaltoodud faili puhul ilmub ekraanile
ICOCG'07
DSOTM'04
JLUK'98