inhoudstafel en auteursrecht
* STER *


4b. Enkele bijzondere klassen

4b.1 Manipuleren van tekst met 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.

4b.2 Fouten opvangen met Exception

Professioneel 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:

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.

Voorbeeld

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.

Voorbeeld

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 {
}

Voorbeeld

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.

4b.3 Gegevensstromen

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:

Voorbeeld

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.

Voorbeeld

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.java
en 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:

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();

Voorbeeld

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.

Voorbeeld

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().

Oefening 4b.1

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:

  1. Schrijf een programma dat een gegeven invoerbestand, of de eerste 1000 regels ervan, opslaat in een rij-veranderlijke (basistype String). Het aantal gelezen regels moet eveneens onthouden worden.
    Deeloplossing
  2. Breid het programma uit zodat het daarna de 1000 regels wegschrijft naar het gevraagde uitvoerbestand.
    Deeloplossing
  3. Voeg tussenin code toe voor de alfabetische rangschikking. Je kan twee objecten van het type 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.

Oplossing


inhoudstafel en auteursrecht
* STER *

Valid HTML 4.0! Valid CSS!