inhoudstafel en auteursrecht
* STER *
String en StringBuffer
De klassen String en StringBuffer zijn ontworpen voor
het werken met tekenreeksen, d.w.z. tekst. De klasse String modelleert
een tekstobject waarvan de inhoud niet wijzigt in de loop van de levenscyclus van
het object. De klasse StringBuffer modelleert een tekstobject
waarvan de inhoud kan gewijzigd worden.
Beide klassen spelen een bijzondere rol omdat ze nauw verweven zijn
met de grammatica van Java. Zo is er de mogelijkheid, teksten
aan elkaar te plakken met behulp van de concatenatie-operator +
(het plusteken). In feite is dit een oproep van de methode append
van de klasse StringBuffer.
Objecten kunnen niet inhoudelijk met elkaar vergeleken worden
met behulp van de operator "is gelijk aan" (dubbel gelijkteken ==).
Strings en StringBuffers vormen daarop geen uitzondering. Het dubbel gelijkteken
test of de twee referenties verwijzen naar hetzelfde geheugenblok; dat
is iets anders dan testen of twee verschillende geheugenblokken dezelfde
reeks tekens bevatten. Voor dit laatste is de klasse String
uitgerust met een methode equals. Om te testen of de Strings
a en b inhoudelijk gelijk zijn, gebruiken
we bijvoorbeeld de volgende code
String a = "alles kits";
String b = "alles" + " kits";
if (a.equals(b))
System.out.println("Strings a en b zijn inhoudelijk identiek");
else
System.out.println("Strings a en b zijn inhoudelijk verschillend");
De methode compareTo bepaalt de onderlinge alfabetische
volgorde tussen Strings. Ze geeft een negatief of een positief geheel
getal terug om de rangschikking aan te geven, of 0 als beide Strings
dezelfde inhoud hebben. De variant compareToIgnoreCase
doet hetzelfde, maar houdt daarbij geen rekening met het onderscheid
tussen hoofdletters en kleine letters.
Om een String om te zetten in een StringBuffer
bestaat er een constructor van de klasse StringBuffer die
een String als parameter aanvaardt. De omgekeerde conversie
krijg je door de methode toString van de klass
StringBuffer
De belangrijkste methoden van StringBuffer zijn insert
(vele varianten), append (vele varianten) en delete
om respectievelijk tekens tussen te voegen, achteraan toe te voegen en
te verwijderen.
De klasse String heeft nog een aantal conversie-methoden,
zoals toUpperCase om alle kleine letters in hoofdletters
om te zetten. Op het eerste gezicht is dat in tegenspraak met ons
uitgangspunt dat Strings onveranderlijk zijn. Deze methoden wijzigen
echter niets aan de onderliggende String, ze
geven gewoon een nieuw Stringobject terug met de gewijzigde tekenreeks.
ExceptionProfessioneel programma-ontwerp vereist dat een programma gewapend is tegen een omgeving die zich niet gedraagt zoals het hoort. Met name netwerkverbindingen en input/output, de configuratie van de eigen bestanden, maar ook interne programmafouten kunnen een informatiesysteem grondig blokkeren.
Daarom zal een goed programmeur regelmatig tests inbouwen of de zojuist uitgevoerde opdrachten het verhoopte resultaat hebben gehad. Als de test positief uitvalt, gaat het programma verder met de normale afwikkeling; als de test negatief is, probeert het programma de fout te herstellen alvorens verder te gaan, of stopt het met een duidelijke foutmelding.
Om te vermijden dat dit soort kwaliteitszorg aanleiding geeft tot een
onoverzichtelijk kluwen van if/else-opdrachten die het
programma erg onleesbaar zouden maken, beschikken Java en enkele andere
talen over het mechanisme van uitzonderingen
of exceptions.
De Java-klasse Exception modelleert een algemene, niet-fatale
fout in de werking van een programma. Van deze klasse zijn tientallen andere
rechtstreeks of onrechtstreeks afgeleid om bijzondere soorten van fouten
te modelleren. Enkele voorbeelden:
ArithmeticException: een niet-toelaatbare rekenkundige bewerking, zoals het
delen van twee getallen waarbij het tweede nul blijkt te zijn (bij getallen met vlottende
komma genereert dit géén uitzondering, maar het bijzondere getal
'Not a Number' ofwel NaN)ArrayIndexOutOfBoundsException: verwijzen naar een element van
een rij met een index die negatief is, of groter dan of gelijk aan het totale
aantal elementenClassNotFoundException: een declaratie of opdracht verwijst naar een
klasse die niet beschikbaar is - deze exception treedt ook op als je een niet-bestaand
Java-programma probeert op te roepen, of als je een typfout maakt in de naam van de
klasseIOException: een fout bij het lezen of schrijven van een
extern informatiekanaal, bijvoorbeeld een bestandNullPointerException: gebruik maken van eigenschappen
of methoden van een veranderlijke
of parameter van een
referentietype die niet naar een object verwijst, bijvoorbeeld omdat er sinds de
declaratie geen waarde aan toegekend is
Bij het mechanisme van uitzonderingen horen vijf nieuwe Java-sleutelwoorden:
try, catch, finally, throws
en throw.
De eerste drie horen bij elkaar en dienen voor het opvangen en afhandelen van een fouttoestand. De algemene vorm luidt
try {
// normale opdrachten
}
catch (UitzonderingsType1 naam1) {
// afhandeling van fouten van type UitzonderingsType1
}
catch (UitzonderingsType2 naam2) {
// afhandeling van fouten van type UitzonderingsType2
}
// ...
catch (UitzonderingsTypeN naamN) {
// afhandeling van fouten van type UitzonderingsTypeN
}
finally {
// afsluitende opdrachten
}
De interpretatie is de volgende. De normale opdrachten worden in de
aangegeven volgorde uitgevoerd, totdat voor het eerst een uitzonderingstoestand optreedt.
Vanaf dat punt worden alle verder opdrachten in het blok normale opdrachten
genegeerd, en gaat het systeem op zoek naar het eerste van de rij uitzonderingstypes
dat overeenkomt met de optredende fout. Het bijbehorende afhandelingsblok wordt
uitgevoerd.
Als tijdens de normale opdrachten geen uitzondering optreedt, worden na
de afwerking van dit blok alle catch-gedeelten overgeslagen.
Of er nu een uitzondering is opgetreden of niet, de afsluitende opdrachten
in het finally-blok worden in elk geval uitgevoerd. Dit laatste
blok mag overigens ook worden weggelaten.
In het volgende programma komt ogenschijnlijk een oneindige lus voor: de voorwaarde
true van de while-opdracht zorgt ervoor dat deze
iteratie nooit op de normale wijze eindigt. Maar de deling door nul die na verloop van
tijd onvermijdelijk optreedt, zorgt ervoor dat het programma overgaat naar het
catch-gedeelte waarin de ArithmeticException wordt opgevangen.
class DelingDoorNul {
public static void main(String[] args) {
try {
int i = 10;
while (true) {
System.out.println("10 : " + i + " = " + 10/i);
i--;
}
}
catch (ArithmeticException e) {
System.out.println("rekenfoutje:" + e);
}
finally {
System.out.println("eind goed, al goed");
}
}
}
We krijgen de volgende uitvoer.
10 : 10 = 1 10 : 9 = 1 10 : 8 = 1 10 : 7 = 1 10 : 6 = 1 10 : 5 = 2 10 : 4 = 2 10 : 3 = 3 10 : 2 = 5 10 : 1 = 10 rekenfoutje:java.lang.ArithmeticException: / by zero eind goed, al goed
Het sleutelwoord throw wordt gevolgd door een uitdrukking van het
type Expression en doet het omgekeerde: het onderbreekt de lopende
code met een foutconditie, zodat de virtuele machine op zoek gaat naar het
eerstvolgende passende catch-blok. Als de throw-opdracht
zich niet uitdrukkelijk binnen een try-blok bevindt, wordt de
uitzondering doorgegeven aan de methode van waaruit de huidige methode werd aangeroepen.
Als geen enkele methode in de ketting van aanroepen vanaf main tot
de plaats van de throw-opdracht een passende catch formuleert
voor de optredende uitzondering, dan wordt de uitzondering doorgegeven aan de
runtime-omgeving. Deze vangt standaard alle uitzonderingen op
met het afbreken van het programma en het afdrukken van een reeks nogal
cryptische meldingen. Als we in bovenstaand programma geen
voorzorgsmaatregelen inbouwen:
class DelingDoorNulOnveilig {
public static void main(String[] args) {
int i = 10;
while (true) {
System.out.println("10 : " + i + " = " + 10/i);
i--;
}
}
}
dan valt dit nogal mee, de runtime-omgeving meldt ons
Exception in thread "main" java.lang.ArithmeticException: / by zero
at DelingDoorNulOnveilig.main(DelingDoorNulOnveilig.java:5)
en stopt.
Omdat een niet-opgevangen uitzondering zonder pardon doorgegeven
wordt aan de aanroepende methode, zouden programmeurs die andermans
werk gebruiken, voor onaangename verrassingen kunnen staan. Om dat
te vermijden is het verplicht dat elke uitzondering die binnen
een methode kan optreden, en die niet opgevangen wordt binnen
die methode, gesignaleerd wordt door het sleutelwoord
throws in de hoofding van de methode-declaratie.
De volgende methode waarschuwt dat ze uitzonderingen van het
type IOException en SQLException
kan veroorzaken, hetzij rechtstreeks, hetzij onrechtstreeks door
de aanroep van andere methoden.
public int registreerAantalDeelnemers throws SQLException, IOException {
//...
}
Deze verplichting heet de "catch or specify"-regel en wordt
effectief door de Java-compiler afgedwongen. Ze geldt niet
voor uitzonderingen die afgeleid zijn van het subtype
RuntimeException, omdat dit aanleiding zou geven
tot een al te groot aantal throws-clausules.
Zo is bijvoorbeeld een ArithmeticException een
bijzonder geval van een RuntimeException. Gelukkig
maar: anders zou elke methode waarin een deling tussen gehele
getallen voorkwam, een exception moeten opvangen of specificeren !
Als je zelf foutcondities wil signaleren aan andere programmagedeelten, dan creëer je beter een nieuw type uitzondering dan dat je gebruikmaakt van de bestaande types. De bestaande types slaan allemaal op welgedefinieerde soorten fouten.
Je kan een nieuw type uitzondering definiëren door gewoon een nieuwe
subklasse te maken van een bestaand type uitzondering. In het volgende
voorbeeld maken we een nieuw type, VerkeerdWachtwoord,
door het af te leiden van Exception.
class VerkeerdWachtwoord extends Exception {
}
Het programma MachtsRecursie uit hoofdstuk 3
was inherent onveilig. Bij een negatieve exponent zou het
oneindig lang blijven lopen (waarom ?)
We declareren hiervoor een speciaal uitzonderingstype en gebruiken het om eventuele fouten van dit type te signaleren aan het hoofdprogramma.
import java.io.*;
class NegatieveExponent extends Exception {
NegatieveExponent(String s) {
super(s);
}
}
class MachtsRecursieVeilig {
public static int machtsverheffing(int grondtal, int exponent)
throws NegatieveExponent {
if (exponent == 0)
return 1;
else if (exponent < 0)
throw new NegatieveExponent("" + exponent);
else
return grondtal * machtsverheffing(grondtal, exponent - 1);
}
public static void main(String[] args) throws IOException {
BufferedReader toetsenbord;
toetsenbord = new BufferedReader(new InputStreamReader(System.in));
System.out.println("grondtal: ");
int grondtal;
grondtal = Integer.parseInt(toetsenbord.readLine());
System.out.println("exponent: ");
int exponent;
exponent = Integer.parseInt(toetsenbord.readLine());
int macht;
try {
macht = machtsverheffing(grondtal, exponent);
System.out.println(grondtal + " tot de macht " + exponent
+ " is gelijk aan " + macht);
}
catch (NegatieveExponent fout) {
System.out.println("Exponent mag niet negatief zijn:" + fout);
}
}
}
We kunnen nu verklaren waarom het lezen van het toetsenbord
gepaard moest gaan met de clausule throws IOException.
Telkens wanneer we gebruik maken van de methode readLine()
kan namelijk een uitzondering van dat type optreden. In plaats daarvan
kunnen we ook de throws-clausule weglaten en de
readLine-opdrachten in een of meer
try-blokken plaatsen.
Elke bruikbare programmeeromgeving moet beschikken over een manier om de computerprogramma's te laten interageren met andere componenten van het systeem: bestanden, andere programma's, netwerken, databases,... Een groot voordeel van Java is de eenvormigheid waarmee dit gebeurt.
Niet alle input/output heeft te maken met bestanden. Moderne computerprogramma's zullen trouwens zelden rechtstreeks met bestanden interageren, maar hun langlevende informatie eerder opslaan in (al dan niet relationele) gegevensbanken. Toch ligt in deze paragraaf de nadruk op gegevensstromen van en naar bestanden: het is een handig model om op praktische wijze de omgang met algemene gegevensstromen aan te leren. De opgedane kennis kan achteraf met weinig aanpassingen toegepast worden op dynamische webpagina's, communicatie tussen verschillende programma's, enz.
Naargelang van de manier waarop computerprogramma's toegang hebben tot een bestand, spreekt men van sequentiële bestanden en van bestanden met willekeurige toegang. Het eerste model wordt gehanteerd door de meeste kantoortoepassingen, bijvoorbeeld ook door MS Excel en MS Word. Als je een Word-document opent, wordt het in zijn geheel van voor naar achter van de harde schijf gelezen en in het elektronische geheugen van de computer geladen. Als je het daarna, eventueel met kleine wijzigingen, bewaart, wordt het opnieuw integraal van voor naar achter naar de harde schijf weggeschreven.
Niet zo voor gegevensbanktoepassingen als MS Access. Als je in een Access-tabel een gegeven wijzigt, wordt die wijziging vrijwel onmiddellijk naar de harde schijf geschreven zonder dat je expliciet een 'Save'-opdracht moet geven. Ook wordt niet het hele bestand in het geheugen geladen (dat zou ook een onnodig zware last voor het geheugen zijn): het programma leest en schrijft slechts welbepaalde gedeelten. Dit impliceert dat het programma ook op de hoogte is van de interne organisatie en de positie van de informatie in het bestand. De toepassing MS Access is trouwen genoemd naar de Engelse term "Random Access File" (bestand met willekeurige toegang).
In Java worden random access files ontsloten door de klasse
RandomAccessFile van het pakket java.io;
we gaan er hier niet verder op in, maar verwijzen naar de online
documentatie van die klasse.
De meeste andere klassen in het pakket java.io hebben
op een of andere manier te maken met het sequentiële model
van informatie-overdracht.
De belangrijkste vier klassen in dit verband zijn Reader,
InputStream, Writer en OutputStream.
Het onderscheid tussen de eerste twee en de laatste twee is het onderscheid tussen lezen en schrijven. Essentieel voor het sequentiële model is dat er geen lees- en schrijfoperaties door elkaar kunnen voorkomen (wat bij een bestand met willekeurige toegang in principe geen probleem vormt).
Het onderscheid tussen enerzijds Reader en Writer en
anderzijds InputStream en OutputStream is een beetje
subtieler. Traditioneel wordt informatie in computers voorgesteld in bytes,
reeksen van 8 bits. Ook tekstbestanden werden vroeger als bytereeksen opgeslagen,
waarbij ieder karakter werd voorgesteld door een overeenkomstige byte volgens
bepaalde conversietabellen: ASCII, ANSI, EBCDIC,...
Ook Java hanteert een standaard-conversietabel voor karakters, maar dan a rato van één karakter per twee bytes, namelijk volgens het Unicode-alfabet.
De klassen InputStream en OutputStream modelleren
invoer- en uitvoerstromen die georganiseerd zijn volgens het traditionele
byte-model. Ook binaire gegevensstromen nemen deze vorm aan.
De klassen Reader en Writer modelleren invoer-
en uitvoerstromen die georganiseerd zijn volgens het Unicode-karaktermodel.
Deze stromen zijn uitsluitend van toepassing voor informatie die als
tekst is georganiseerd. Dat laatste is niet zo'n strenge beperking
als het lijkt: meer en meer computercommunicatie gebeurt in het tekstformaat
XML (extensible modular language).
Het onderscheid tussen (8 bits) bytestromen en (16 bits) karakterstromen is vooral binnen ons Java-programma van belang. Het kan zijn dat een bestand dat we via een karakterstroom aanspreken, door het operating system van de computer uiteindelijk tóch als een reeks bytes wordt opgeslagen.
De klassen Reader, Writer, InputStream
en Outputstream zijn abstract: dat wil zeggen dat ze
niet kunnen geïnstantieerd worden. De enige manier om er als programmeur
nuttig gebruik van te maken, is er subklassen van definiëren (en die
subklassen dan instantiëren tot concrete objecten). Je kan zelf een
klasse als abstract declareren door gebruik te maken van het sleutelwoord
abstract:
public abstract class NietConstrueerbaar {
// ... attributen en methoden ...
}
public class Construeerbaar extends NietConstrueerbaar {
// ... nog meer attributen en methoden ...
}
In het vervolg van deze paragraaf zullen we gebruik maken van de concrete
klassen FileReader, FileWriter, FileInputStream
en FileOutputStream die van hogergenoemde abstracte klassen zijn afgeleid.
De belangrijkste methode van de klasse Reader is de methode
read(), zonder parameters. Ze leest het volgende karakter
van de invoerstroom en geeft het resultaat terug als een geheel getal.
Om dat geheel getal (int) vervolgens om te zetten naar een char
moeten we gebruik maken van een typecast. Typecasten is: een uitdrukking
laten voorafgaan door de naam van een gewenst type, tussen haakjes. Omwille
van de type-controle laat de Java-omgeving typecasting slechts toe in de volgende
twee gevallen:
int en char)ClassCastException
als het om te zetten object niet tot de
subklasse behoort)
Gebruik de volgende programmacode om één karakter te
lezen van een Reader met de naam bestand:
char c = (char) bestand.read();
Opeenvolgende read-operaties lezen opeenvolgende karakters
van de invoerstroom. Als het laatste beschikbare karakter van de stroom
gelezen is, en het operating system het einde van de stroom vaststelt,
dan geven alle volgende aanroepen van read() voor die stroom
het gehele getal -1 terug. In feite hadden we dus moeten schrijven
int i = bestand.read(); char c; if (i >= 0) c = (char) i; else // ...
De klasse InputStream heeft precies zo'n methode read,
met dien verstande dat de teruggegeven getallen niet meer over het bereik
van -1 tot +65535 variëren, maar slechts van -1 tot +256.
De klasse Writer heeft een methode write die
een int-parameter neemt (het terugkeertype is void)
en die het overeenkomstige karakter naar de output schrijft. Ook hier is dus
een typecast nodig, maar dan in de andere richting, namelijk van char
naar int:
uitvoerbestand.write((int) c);
Ook de klasse OutputStream heeft een dergelijke methode, al wordt
hier natuurlijk verwacht dat het gegeven getal niet groter is dan 256.
Om een Reader te construeren die met een gegeven bestand
op de harde schijf overeenstemt, kun je de constructor van FileReader
gebruiken die een String-parameter aanvaardt, nl. de naam
of de padnaam van het bestand.
Reader r = new FileReader("in.txt");
Deze constructor kan een Exception genereren van het type
FileNotFoundException, dat is een subtype van IOException.
Analoog voor een uitvoerbestand, maar dat wordt dan wel gecreëerd als het
nog niet bestond.
Writer w = new FileWriter("out.txt");
De klassen FileInputStream en FileOutputStream beschikken
over gelijkaardige constructoren voor het modelleren van byte-georiënteerde
bestanden.
Het volgende programma telt het aantal karakters in een
gegeven tekstbestand. De naam van het bestand wordt door
de gebruiker meegegeven als parameter op het einde
van de opdrachtregel en komt dus het programma binnen
als het eerste ("nulde") element van de rij args.
Zo kan je het programma de lengte
van zijn eigen broncode laten meten met de opdracht
java TelKarakters TelKarakters.javaen dan krijg je als resultaat
616 karakters gelezen.
import java.io.*;
class TelKarakters {
public static void main(String[] args) {
try {
FileReader f = new FileReader(args[0]);
int teller = 0;
while (f.read() >= 0)
teller++;
f.close();
System.out.println(teller + " karakters gelezen.");
}
catch (FileNotFoundException e) {
System.out.println("Bestand niet gevonden.");
}
catch (IOException e) {
System.out.println("Algemene invoerfout.");
}
catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Geef een bestandsnaam mee als opdrachtregel-parameter.");
}
}
}
De opdracht f.close() dient om het invoerbestand netjes af te sluiten.
Bij het gebruik van externe hulpmiddelen zoals bestanden, netwerken en databases is
het belangrijk dat de middelen worden vrijgegeven zodra ons programma ze niet meer
nodig heeft. Op die manier lopen we minder risico dat andere programma's, die dezelfde
hulpbronnen nodig hebben, op ons moeten wachten.
Soms beschikken we over een InputStream, maar zouden we de binnenkomende
informatie liever als karakters dan als bytes interpreteren. Daartoe bestaat een hulpklasse
InputStreamReader met de volgende nuttige combinatie van eigenschappen:
Reader (dus elke InputStreamReader
is een ReaderInputStream
(en zelfs geen andere constructoren)
Om een InputStream om te zetten in een Reader construeer je
gewoon een object van het type InputStreamReader met in de constructor
een verwijzing naar de oorspronkelijke stroom.
Een dergelijke situatie doet zich voor bij het lezen van het toetsenbord. We krijgen
van de virtuele machine het object System.in cadeau, dat het toetsenbord
modelleert. Helaas is dit object van het type InputStream, terwijl we
de ingevoerde tekens natuurlijk liever als 16-bits karakters zouden interpreteren.
We doen dat door er een InputStreamReader omheen te wikkelen.
// situatie 1: lees een byte van het toetsenbord byte b = (byte) System.in.read(); // situatie 2: lees een char van het toetsenbord Reader r = new InputStreamReader(System.in); char c = (char) r.read();
Vaak is het handig als we tekstinvoer niet karakter per karakter, maar regel per
regel kunnen lezen. De klasse BufferedReader biedt hier de extra
functionaliteit die we missen via haar methode readLine(),
die een String teruggeeft (of null indien het
einde van de invoer is bereikt). Een BufferedReader wordt
geconstrueerd bovenop een bestaande Reader:
// situatie 3: lees een regel van het toetsenbord Reader r = new InputStreamReader(System.in); BufferedReader b = new BufferedReader(r); String regel = b.readLine();
We kunnen nu eindelijk de cryptische code van paragraaf 2.6 verklaren. Nemen we
bijvoorbeeld de volgende programmaregel uit de main-methode van de
klasse BTW.
BufferedReader toetsenbord = new BufferedReader(new InputStreamReader(System.in));
De BufferedReader wordt geconstrueerd aan de hand van een
InputStreamReader, die op zijn beurt gebruik maakt van het object
System.in van de klasse InputStream.
Latere leesopdrachten in hetzelfde programma maakten dan gebruik van de
methode readLine, bijvoorbeeld als volgt:
nettoTekst = toetsenbord.readLine();
Het volgende programma telt het aantal regels in een gegeven
invoerbestand. Voor het overige is het identiek aan het eerder gegeven
programma TelKarakters.
import java.io.*;
class TelRegels {
public static void main(String[] args) {
try {
FileReader f = new FileReader(args[0]);
BufferedReader b = new BufferedReader(f);
int teller = 0;
while (b.readLine() != null)
teller++;
f.close();
System.out.println(teller + " regels gelezen.");
}
catch (FileNotFoundException e) {
System.out.println("Bestand niet gevonden.");
}
catch (IOException e) {
System.out.println("Algemene invoerfout.");
}
catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Geef een bestandsnaam mee als opdrachtregel-parameter.");
}
}
}
Aan de output-kant bestaat iets analoogs: de klasse PrintWriter
kan geconstrueerd worden met een willekeurige Writer of OutputStream
in de constructor. Ze beschikt over methoden print en println
om veel verschillende datatypes als tekst naar de uitvoerstroom te sturen. De objecten
System.out en System.err, die normaal naar het scherm verwijzen,
zijn van de verwante klasse PrintStream.
Het verschil tussen de methoden print en println is dat de
tweede extra karakters toevoegt om naar het begin van de volgende regel te gaan.
Het volgende programma kopieert de inhoud van een gegeven tekstbestand regel per regel naar een ander bestand. De namen van de twee bestanden worden door de gebruiker gegeven als de eerste twee opdrachtregel-parameters.
import java.io.*;
class Kopieer {
public static void main(String[] args) {
try {
FileReader r = new FileReader(args[0]);
BufferedReader b = new BufferedReader(r);
FileWriter w = new FileWriter(args[1]);
PrintWriter p = new PrintWriter(w);
String regel = b.readLine();
while (regel != null) {
b.println(regel);
regel = b.readLine();
}
b.close();
p.close();
}
catch (FileNotFoundException e) {
System.out.println("Invoerbestand niet gevonden.");
}
catch (IOException e) {
System.out.println("Algemene invoer/uitvoerfout.");
}
catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Geef twee bestandsnamen mee als opdrachtregel-parameters.");
}
}
}
In dit geval is de oproep van de methode close van belang voor de goede
werking van ons programma: het sluiten van een uitvoerbestand heeft eveneens tot gevolg
dat de uitvoerbuffer leeggeschreven wordt. Als we dat niet doen, kan het zijn
dat een deel van de geschreven informatie niet in het bestand aankomt. Het leegschrijven
van de buffer kan je ook forceren zonder het bestand te sluiten, met behulp van de methode
flush().
Schrijf een programma dat een invoerbestand leest van maximaal 1000 regels, en dat de regels daarna in alfabetische volgorde naar een uitvoerbestand met gegeven naam schrijft. Je kan te werk gaan in de volgende stappen:
String). Het aantal
gelezen regels moet eveneens onthouden worden.
String alfabetisch vergelijken met de methode compareTo.
De uitdrukking
een.compareTo(twee)geeft een negatief getal als
een alfabetisch vóór twee
komt, nul als ze dezelfde inhoud hebben, en een positief getal als
een alfabetisch ná twee
komt.