Korte inhoudsopgave. 1 Programmeren 5. 2 Java Tekenen en rekenen Nieuwe methoden Objecten en methoden Invloed van buiten 59

Maat: px
Weergave met pagina beginnen:

Download "Korte inhoudsopgave. 1 Programmeren 5. 2 Java Tekenen en rekenen Nieuwe methoden Objecten en methoden Invloed van buiten 59"

Transcriptie

1 1 Korte inhoudsopgave 1 Programmeren 5 2 Java 14 3 Tekenen en rekenen 23 4 Nieuwe methoden 31 5 Objecten en methoden 43 6 Invloed van buiten 59 7 Herhaling 71 8 Keuze 80 9 Objecten en klassen Overerving Strings en Arrays Ontwerp van programma s Objectgeoriënteerd ontwerp Algoritmen 205 A Gebruik van de compiler 226 B Programmeerprojecten 232 C Gereserveerde woorden 240 D Standaardklassen en -methoden 241 E Operatoren 251 F Syntax 252

2 2 Inhoudsopgave 1 Programmeren Computers en programma s Orde in de chaos Programmeerparadigma s Programmeertalen Vertalen van programma s Programmeren 11 2 Java Omgeving van het programma Opbouw van een programma Klasse-definitie Methode-definitie Opdrachten Methoden en parameters Naamgeving Bibliotheek-klassen Ontwikkelomgevingen 19 3 Tekenen en rekenen Graphics Variabelen Berekeningen Programma-layout 29 4 Nieuwe methoden Methode-definitie Op zoek naar parameters Methoden met een resultaat 36 5 Objecten en methoden Variabelen Typering Methoden Constanten Toepassing: Intro-scherm 55 6 Invloed van buiten Applets parametriseren Utility-klassen Interactie via objecten Interactie-componenten Interactie met de gebruiker 67

3 INHOUDSOPGAVE 3 7 Herhaling De while-opdracht Boolean waarden De for-opdracht Bijzondere herhalingen Toepassing: renteberekening 76 8 Keuze De if-opdracht Toepassingen Grafiek en nulpunten van een parabool Exceptions 91 9 Objecten en klassen Klasse: beschrijving van een object Toepassing: Bewegende deeltjes Animatie Klasse-ontwerp en -gebruik Klassen in de Java-libraries Overerving Subklassen Klasse-hiërarchieën Klasse-hiërarchieën in de Java-libraries Strings en Arrays Strings en characters Arrays Toepassing: Tekst-analyse met letterfrequentie Ontwerp van programma s Layout van de userinterface Toepassing: Rekenmachine Applications Menu s en WindowEvents Toepassing: een bitmap-editor Details van de bitmap-editor Objectgeoriënteerd ontwerp Abstracte klassen en interfaces Collections Uitbreidingen van AWT Toepassing: een schets-programma File input/output Non-window-programma s Algoritmen Toepassing: een zoekend programma Het zoekalgoritme Toepassing: automatische taalherkenning 210 A Gebruik van de compiler 226 A.1 Installatie van de software 226 A.2 Configuratie van de IDE 227 A.3 Een programma schrijven en uitvoeren 229 A.4 Documentatie raadplegen 231

4 4 INHOUDSOPGAVE B Programmeerprojecten 232 B.1 Kalender 232 B.2 Mandelbrot 233 B.3 Reversi-spel 235 B.4 Arkanoid-spel 237 B.5 Beeldverwerking 238 C Gereserveerde woorden 240 D Standaardklassen en -methoden 241 D.1 package java.lang 241 D.2 package java.util 242 D.3 package java.awt 244 D.4 package javax.swing 246 D.5 package java.awt.event 247 D.6 package java.net 248 D.7 package java.io 248 D.8 hoofdprogramma 250 D.9 primitieve types 250 E Operatoren 251 F Syntax 252

5 5 Hoofdstuk 1 Programmeren 1.1 Computers en programma s Computer: processor plus geheugen Een computer bestaat uit tientallen verschillende onderdelen, en het is een vak apart om dat allemaal te beschrijven. Maar als je het heel globaal aanpakt, kun je het eigenlijk met twee woorden zeggen: een computer bestaat uit een processor en uit geheugen. Dat geheugen kan allerlei vormen aannemen, voornamelijk verschillend in de snelheid van gegevensoverdracht en de toegangssnelheid. Sommig geheugen kun je lezen en schrijven, sommig geheugen alleen lezen of alleen met wat meer moeite beschrijven, en er is geheugen dat je alleen kunt beschrijven. Invoer- en uitvoer-apparatuur (toetsenbord, muis, monitor, printer enz.) lijken op het eerste gezicht buiten de categorieën processor en geheugen te vallen, maar als je ze maar abstract genoeg beschouwt vallen ze in de categorie geheugen : een toetsenbord is read only geheugen, en een monitor is write only geheugen. Ook het modem en de netwerkkaart, en met een beetje goede wil zelfs de geluidkaart, zijn een vorm van geheugen. De processor, daarentegen, is een wezenlijk ander onderdeel. Taak van de processor is het uitvoeren van opdrachten. Die opdrachten hebben als effect dat het geheugen wordt veranderd. Zeker met onze ruime definitie van geheugen verandert of inspecteert praktisch elke opdracht die de processor uitvoert het geheugen. Opdracht: voorschrift om geheugen te veranderen Een opdracht is dus een voorschrift om het geheugen te veranderen. De opdrachten staan zelf ook in het geheugen (eerst op een disk, en terwijl het wordt uitgevoerd ook in het RAM-geheugen). In principe zou het programma opdrachten kunnen bevatten om een ander deel van het programma te veranderen. Dat idee is een tijdje erg in de mode geweest (en de verwachtingen voor de kunstmatige intelligentie waren hooggespannen), maar dat soort programma s bleken wel erg lastig te schrijvenze veranderen waar je bij staat! We houden het er dus maar op dat het programma in een afzonderlijk deel van het geheugen staat, apart van het deel van het geheugen dat door het programma wordt veranderd. Het programma wordt, alvorens het uit te voeren, natuurlijk wel in het geheugen geplaatst. Dat is de taak van een gespecialiseerd programma, dat we een operating system noemen (of anders een virus). Programma: lange reeks opdrachten Ondertussen zijn we aan een definitie van een programma gekomen: een programma is een (lange) reeks opdrachten, die -als ze door de processor worden uitgevoerd- het doel hebben om het geheugen te veranderen. Programmeren is de activiteit om dat programma op te stellen. Dat vergt het nodige voorstellingsvermogen, want je moet je de hele tijd bewust zijn wat er met het geheugen zal gebeuren, later, als het programma zal worden uitgevoerd. Voorbeelden van programma s in het dagelijks leven zijn talloos, als je bereid bent om het begrip geheugen nog wat ruimer op te vatten: kookrecepten, breipatronen, routebeschrijvingen, ambtelijke procedures, het protocol voor de troonswisseling: het zijn allemaal reeksen opdrachten, die als ze worden uitgevoerd, een bepaald effect hebben. Programmeertaal: notatie voor programma s De opdrachten die samen het programma vormen moeten op een of andere manier geformuleerd. Dat zou met schema s of handbewegingen kunnen, maar in de praktijk gebeurt het vrijwel altijd

6 6 Programmeren door de opdrachten in tekst-vorm te coderen. Er zijn vele verschillende notaties in gebruik om het programma mee te formuleren. Zo n verzameling notatie-afspraken heet een programmeertaal. Daar zijn er in de recente geschiedenis nogal veel van bedacht, want telkens als iemand een nóg handigere notatie bedenkt om een bepaald soort opdrachten op te schrijven wordt dat al gauw een nieuwe programmeertaal. In een overzicht uit 1995 (ftp://wuarchive.wustl.edu/doc/misc/lang-list.txt) van programmeertalen die op zijn minst een naam hebben en waarover iets is gepubliceerd kwam de telling tot In feite is dat getal nogal arbitrair, want het ligt er maar aan wat je meetelt: als je alle kleine varianten of dialecten als aparte taal gaat beschouwen, kom je nog veel hoger uit. Het zijn er hoe dan ook erg veel. Het heeft weinig zin om die talen allemaal te gaan leren, en dat hoeft ook niet, want er is veel overeenkomst tussen talen. Wel is het zo dat er in de afgelopen 50 jaar een ontwikkeling heeft plaatsgevonden in programmeertalen. Ging het er eerst om om steeds meer nieuwe mogelijkheden van computers te gebruiken, tegenwoordig ligt de nadruk er op om een beetje orde te scheppen in de chaos die het programmeren anders dreigt te veroorzaken. 1.2 Orde in de chaos Omvang van het geheugen Weinig zaken hebben zo n spectaculaire groei doorgemaakt als de omvang van het geheugen van computers. In 1948 werd een voorstel van Alan Turing om een (één) computer te bouwen met een geheugencapaciteit van 6 kilobyte nog afgekeurd (te ambitieus, te duur!). Tegenwoordig zit dat geheugen al op de klantenkaart van de kruidenier. Maar ook recent is de groei er nog niet uit: tien jaar geleden had de modale PC een geheugen van 640 kilobyte, en niet van kilobyte zoals nu. Voor disks geldt een zelfde ontwikkeling: tien jaar geleden was 20 megabyte best acceptabel, nu is 2000 megabyte een instapmodel. En wat zouden we over tien jaar denken van onze huidige 640 megabyte CD tjes? Variabele: geheugenplaats met een naam Het geheugen is voor programma s aanspreekbaar in de vorm van variabelen. Een variabele is een plaats in het geheugen met een naam. Een opdracht in het programma kan dan zijn om bepaalde, bij naam genoemde, variabele te veranderen. Voor kleine programma s gaat dat prima: enkele tientallen variabelen zijn nog wel uit elkaar te houden. Maar als we al die nieuw verworven megabytes met aparte variabelen gaan vullen, worden dat er zoveel dat we daar het overzicht totaal over verliezen. In wat oudere programmeertalen is het om die reden dan ook vrijwel niet mogelijk te voldoen aan de eisen die tegenwoordig aan programmatuur wordt gesteld (windowinterface, geheel configureerbaar, what-you-see-is-what-you-get, gebruik van alle denkbare rand- en communicatieapparatuur, onafhankelijk van taal, cultuur en schriftsoort, geïntegreerde online help en zelfdenkende wizards voor alle klusjes... ). Object: groepje variabelen Er is een bekende oplossing die je kunt gebruiken als, door het grote aantal, dingen onoverzichtelijk dreigen te worden: groeperen, en de groepjes een naam geven. Dat werkt voor personen in verenigingen, verenigingen in bonden, en bonden in federaties; het werkt voor gemeenten in provincies, provincies in deelstaten, deelstaten in landen, en landen in unies; het werkt voor werknemers in afdelingen, afdelingen in divisies, divisies in bedrijven, bedrijven in holdings; het werkt voor universiteits-medewerkers in leerstoelgroepen, leerstoelgroepen in instituten, instituten in faculteiten, faculteiten in universiteiten, en universiteiten in regionale clusters. Dat moet voor variabelen ook kunnen werken. Een groepje variabelen die bij elkaar horen en als geheel met een naam kan worden aangeduid, staat bekend als een object. In de zogenaamde objectgeoriënteerde programmeertalen kunnen objecten ook weer in een variabele worden opgeslagen, en als zodanig deel uitmaken van grotere objecten. Zo kun je in programma s steeds grotere gehelen manipuleren, zonder dat je steeds met een overweldigende hoeveelheid details wordt geconfronteerd. Omvang van programma s Programma s staan ook in het geheugen, en omdat daar zo veel van beschikbaar is, worden programma s steeds groter. Vijftien jaar geleden pasten operating system, programmeertaal en tekst-

7 1.3 Programmeerparadigma s 7 verwerker samen in een ROM van 16 kilobyte; de nieuwste tekstverwerkers worden geleverd op meerdere CD s à 640 megabyte. In een programma staan een enorme hoeveelheid opdrachten, en het is voor één persoon totaal niet meer te bevatten wat die opdrachten precies doen. Erger is, dat ook met een team er moeilijk uit te komen is: steeds moet zo n team weer vergaderen over de precieze taakverdeling. Methode: groepje opdrachten met een naam Het recept is bekend: we moeten wat orde in de chaos scheppen door de opdrachten te groeperen, en van een naam te voorzien. We kunnen dan door het noemen van de naam nonchalant grote hoeveelheden opdrachten aanduiden, zonder ons steeds in alle details te verdiepen. Dat is de enige manier om de complexiteit van grote programma s nog te kunnen overzien. Dit principe is al vrij oud, al wordt zo n groepje opdrachten door de geschiedenis heen steeds anders genoemd (de naam van elk apart groepje wordt uiteraard door de programmeur bepaald, maar het gaat hier om de naam van de naamgevings-activiteit... ). In de vijftiger jaren van de vorige eeuw heette een van naam voorzien groepje opdrachten een subroutine. In de zestiger jaren ging men spreken van een procedure. In de tachtiger jaren was de functie in de mode, en in de jaren negentig moest je van een methode spreken om er nog bij te horen. We houden het nog steeds maar op methode, maar hoe je het ook noemt: het gaat er om dat de complexiteit van lange reeksen opdrachten nog een beetje te beheersen blijft door ze in groepjes in te delen, en het groepje van een naam te voorzien. Klasse: groepje methoden met een naam Decennia lang kon men heel redelijk uit de voeten met hun procedures. Maar met de steeds maar groeiende programma s onstond er een nieuw probleem: het grote aantal procedures werd te onoverzichtelijk om nog goed hanteerbaar te zijn. Het recept is bekend: zet de procedures in samenhangende groepjes bij elkaar en behandel ze waar mogelijk als één geheel. Zo n groepje heet een klasse. En als om de overgang naar deze nieuwe zienswijze te benadrukken ging men, sinds de groepjes opdrachten in klassen gebundeld werden, de groepjes opdrachten in de bundel methoden noemen. 1.3 Programmeerparadigma s Imperatief programmeren: gebaseerd op opdrachten Ook in de wereld van de programmeertalen kunnen we wel wat orde in de chaos gebruiken. Programmeertalen die bepaalde eigenschappen gemeen hebben behoren tot hetzelfde programmeerparadigma. (Het woord paradigma is gestolen van de wetenschapsfilosofie, waar het een gemeenschappelijk kader van theorievorming in een bepaalde periode aanduidt; heel toepasselijk dus.) Een grote groep programmeertalen behoort tot het imperatieve paradigma; dit zijn dus imperatieve programmeertalen. In het woord imperatief herken je de gebiedende wijs ; imperatieve programmeertalen zijn dan ook talen die gebaseerd zijn op opdrachten om het geheugen te veranderen. Imperatieve talen sluiten dus direct aan op het besproken computermodel met processor en geheugen. In deze cursus staat een imperatieve programmeertaal centraal, en dat verklaart dan ook de naam van de cursus. Declaratief programmeren: gebaseerd op functies Het feit dat we de moeite nemen om de imperatieve talen als zodanig te benoemen doet vermoeden dat er nog andere paradigma s zijn, waarin geen opdrachten gebruikt worden. Kan dat dan? Wat doet de processor, als hij geen opdrachten uitvoert? Het antwoord is, dat de processor weliswaar altijd opdrachten uitvoert, maar dat je dat in de programmeertaal niet noodzakelijk hoeft terug te zien. Denk bijvoorbeeld aan het maken van een ingewikkeld spreadsheet, waarbij je allerlei verbanden legt tussen de cellen op het werkblad. Dit is een activiteit die je programmeren kunt noemen, en het nog-niet-ingevulde spreadsheet is het programma, klaar om actuele gegevens te verwerken. Het programma is niet op het geven van opdrachten gebaseerd, maar veeleer op het leggen functionele verbanden tussen de diverse cellen. Naast dit soort functionele programmeertalen zijn er nog talen die op de propositielogica zijn gebaseerd: de logische programmeertalen. Samen staan deze bekend als het declaratieve paradigma. Maar daar gaat deze cursus dus niet over.

8 8 Programmeren Figuur 1: Programmeerparadigma s Procedureel programmeren: imperatief + methoden Programmeertalen waarin procedures (of methoden, zoals we tegenwoordig zouden zeggen) een prominente rol spelen, behoren tot het procedurele paradigma. Alle procedurele talen zijn bovendien imperatief: in die procedures staan immers opdrachten, en de aanwezigheid daarvan maakt een taal imperatief. Object-georiënteerd programmeren: procedureel + objecten Weer een uitbreiding van procedurele talen vormen de object-georiënteerde talen. Hierin kunnen niet alleen opdrachten gebundeld worden in procedures (of liever: methoden), maar kunnen bovendien variabelen gebundeld worden in objecten. Je ziet het procedurele en het objectieve paradigma wel eens als contrast gepresenteerd: programmeer jij procedureel of object-georiënteerd?. Zo n vraag berust op een misverstand; het moet zijn: programmeer jij procedureel, of ook object-georiënteerd?. 1.4 Programmeertalen Imperatieve talen: Assembler, Fortran, Basic De allereerste computers werden geprogrammeerd door de instructies voor de processor direct, in getalvorm, in het geheugen neer te zetten. Al snel kreeg men door dat het handig was om voor die instructies gemakkelijk te onthouden afkortingen te gebruiken, in plaats van getallen. Daarmee was rond 1950 de eerste echte programmeertaal ontstaan, die Assembler werd genoemd, omdat je er gemakkelijk programma s mee kon bouwen ( to assemble ). Elke processor heeft echter zijn eigen instructies, dus een programma in Assembler is specifiek voor een bepaalde processor. Je kunt dus eigenlijk niet spreken van de taal Assembler, maar moet liever spreken van Assembler-talen. Dat was natuurlijk niet handig, want als er een nieuwe type processor wordt ontwikkeld zijn al je oude programma s waardeloos geworden. Een nieuwe doorbraak was rond 1955 de taal Fortran (een afkorting van formula translator ). De opdrachten in deze taal waren niet specifiek geënt op een bepaalde processor, maar konden (met een speciaal programma) worden vertaald naar diverse processoren. De taal werd veel gebruikt voor technisch-wetenschappelijke toepassingen. Nog steeds trouwens; niet dat modernere talen daar niet geschikt voor zouden zijn, maar omdat er in de loop der jaren nu eenmaal veel programmatuur is ontwikkeld, en ook omdat mensen niet zo gemakkelijk van een eenmaal aangeleerde taal afstappen. Voor beginners was Fortran een niet zo toegankelijke taal. Dat was aanvankelijk niet zo erg, want zo n dure computer gaf je natuurlijk niet in handen van beginners. Maar na verloop van tijd (omstreeks 1965) kwam er toch de behoefte aan een taal die wat gemakkelijker in gebruik was, en zo ontstond Basic ( Beginner s All-purpose Symbolic Instruction Code ). De taal is later vooral populair geworden doordat het de standaard-taal werd van personal computers: de Apple II in 1978, de IBM-PC in 1979, en al hun opvolgers. Helaas was de taal niet gestandaardiseerd, zodat op elk merk computer een apart dialect werd gebruikt, dat niet uitwisselbaar was met de andere dialecten.

9 1.4 Programmeertalen 9 Procedurele talen: Algol, Pascal, C Ondertussen was het inzicht doorgebroken dat voor wat grotere programma s het gebruik van procedures onontbeerlijk was. De eerste echte procedurele taal was Algol (een wat merkwaardige afkorting van Algorithmic Language ). De taal werd in 1960 gelanceerd, met als bijzonderheid dat de taal een officiële definitie had, wat voor de uitwisselbaarheid van programma s erg belangrijk was. Er werd voor de gelegenheid zelfs een speciale notatie (BNF) gebruikt om de opbouw van programma s te beschrijven, die (anders dan Algol zelf) nog steeds gebruikt wordt. In het vooruitgangsgeloof van de zestiger jaren was in 1968 de tijd rijp voor een nieuwe versie: Algol68. Een grote commissie ging er eens goed voor zitten en voorzag de taal van allerlei nieuwe ideeën. Zo veel ideeën dat het erg lastig was om vertalers te maken voor Algol68-programma s. Die kwamen er dan ook nauwelijks, en dat maakt dat Algol68 de dinosauriërs achterna is gegaan: uitgestorven vanwege zijn complexiteit. Het was wel een leerzame ervaring voor taal-ontwerpers: je moest niet willen streven naar een taal met eindeloos veel toeters en bellen, maar juist naar een compact en simpel taaltje. De eerste simpele, maar wel procedurele, taal werd als éénmansactie bedacht in 1971: Pascal (geen afkorting, maar een vernoeming naar de filosoof Blaise Pascal). Voornaamste doel van ontwerper Wirth was het onderwijs aan de universiteit van Zürich te voorzien van een gemakkelijk te leren, maar toch verantwoorde (procedurele) taal. Al gauw werd de taal ook voor serieuze toepassingen gebruikt; allicht, want mensen stappen niet zo gauw af van een eenmaal aangeleerde taal. Voor echt grote projecten was Pascal echter toch te beperkt. Zo n groot project was de ontwikkeling van het operating system Unix eind jaren zeventig bij Bell Labs. Het was sowieso nieuw om een operating system in een procedurele taal te schrijven (tot die tijd gebeurde dat in Assembler-talen), en voor deze gelegenheid werd een nieuwe taal ontworpen: C (geen afkorting, maar de opvolger van eerdere prototypes genaamd A en B). Het paste in de filosofie van Unix dat iedereen zijn eigen uitbreidingen kon schrijven (nieuwe editors en dergelijke). Het lag voor de hand dat die programma s ook in C werden geschreven, en zo werd C de belangrijkste imperatieve taal van de jaren tachtig, ook buiten de Unix-wereld. Object-georiënteerde talen: Simula, Smalltalk, C++, Java In 1975 was in Bergen (Noorwegen) een zekere Ole-Johan Dahl geïnteresseerd in programma s die simulaties uit konden voeren (van het gedrag van rijen in een postkantoor, de doorstroming van verkeer, enz.). Het was in die tijd al niet zo raar meer om je eigen taal te ontwerpen, en zo ontstond de taal Simula als een uitbreiding van Algol60. Een van die uitbreidingen was het object als zelfstandige eenheid. Dat kwam handig uit, want een persoon in het postkantoor of een auto in het verkeer kon dan mooi als object worden beschreven. Simula was daarmee de eerste object-georiënteerde taal. Simula zelf leidde een marginaal bestaan, maar het object-idee werd opgepikt door onderzoekers van Xerox in Palo Alto, die (eerder dan Apple en Microsoft) experimenteerden met window-systemen en een heuse muis. Hun taaltje (genaamd Smalltalk ) gebruikte objecten voor het modelleren van windows, buttons, scrollbars en dergelijke: allemaal min of meer zelfstandige objecten. Maar Smalltalk was wel erg apart: werkelijk alles moest een object worden, tot aan getallen toe. Dat werd niet geaccepteerd door de massa. Toch was duidelijk dat objecten op zich wel handig waren. Er zou dus een C-achtige taal moeten komen, waarin objecten gebruikt konden worden. Die taal werd C++ (de twee plustekens betekenen in C de opvolger van, en elke C-programmeur begreep dus dat C++ bedoeld was als opvolger van de taal C). De eerste versie is van 1978, en de officiële standaard verscheen in De taal is erg geschikt voor het schrijven van window-gebaseerde programma s, en dat begon in die tijd net populair te worden. Maar het succes van C++ is ook toe te schrijven aan het feit dat het echt een uitbreiding is van C: de oude constructies uit C bleven bruikbaar. Dat kwam goed uit, want mensen stappen nu eenmaal niet zo gemakkelijk af van een eenmaal aangeleerde taal. De taal C++ is weliswaar standaard, maar de methode-bibliotheken die nodig zijn om windowsystemen te maken zijn dat niet. Het programmeren van een window op een Apple-computer, een Windows-computer of een Unix-computer met X-windows moet dan ook totaal verschillend worden aangepakt, en dat maakt de interessantere C++-programma s niet uitwisselbaar met andere operating systems. In eerste instantie vond men dat nog niet eens zo heel erg, maar dat werd anders toen midden jaren negentig het Internet populair werd: het was toch jammer dat de programma s die je via het Internet verspreidde slechts door een deel van het publiek gebruikt kon

10 10 Programmeren worden (mensen met hetzelfde operating system als jij). Tijd dus voor een nieuwe programmeertaal, ditmaal eentje die gestandaardiseerd is voor gebruik onder diverse operating systems. De taal zou moeten lijken op C++, want mensen stappen nu eenmaal niet zo gemakkelijk af van een eenmaal aangeleerde taal, maar het zou een mooie gelegenheid zijn om de nog uit C afkomstige en minder handige ideeën overboord te zetten. De taal Java vervult deze rol (geen afkorting, geen filosoof, maar de naam van het favoriete koffiemerk van de ontwerpers; de naam is blijkbaar bedacht tijdens een rondje brainstormen in de coffeeshop). Internet-gebruikers zijn gewend dat alles gratis is, en wilde de taal een kans maken dat zou hij ook gratis gebruikt moeten kunnen worden. Welk bedrijf zou daarin willen investeren? Hardwarefabrikant Sun is zo aardig geweest, natuurlijk niet zonder eigenbelang: een taal die onafhankelijk is van het operating system zou het dreigende monopolie van concurrent Microsoft kunnen voorkomen. Het is in dat verband wel leuk om de kleine lettertjes van de Java-licentie te lezen. Anders dan bij de meeste andere software is zowat alles toegestaan: gebruiken, kopiëren, verspreiden; en dat allemaal gratis. Slechts één ding is hevig verboden: het toevoegen van extra tjes aan de taal die specifiek zijn voor een bepaald operating system. Dat laatste vindt Microsoft natuurlijk weer niet leuk, en die heeft daarom een eigen object-georiënteerde opvolger-van-c++ ontworpen: de taal C# (uit te spreken als C-sharp ). Waar dit alles toe moet leiden is lastig te voorspellen. Wordt Java de standaardtaal van dit decennium of gaat C# winnen? Zal C++ helemaal verdrongen worden, of blijft dat ook bestaan? Blijft de taal standaard, of komen er toch weer dialecten? Zal de snelheid van C++ nog eens geëvenaard gaan worden? In ieder geval is Java eenvoudiger te leren dan C++ (dat door de compatibiliteit met C een nogal complexe taal is), en je kunt er dus sneller interessante programma s mee schrijven. Objectgeorieënteerde ideeën zijn in Java prominent aanwezig, en het kan zeker geen kwaad om die te leren. Als imperatieve en procedurele taal is Java zeker niet slechter dan bijvoorbeeld Pascal. Andere object-georiënteerde talen (C++, C#, of nog weer andere) zijn, met Java als basiskennis, relatief gemakkelijk bij te leren. En dat kan nooit kwaad, want er is geen enkele reden nooit meer af te stappen van een eenmaal geleerde taal Vertalen van programma s Assembler Een computerprogramma wordt door een speciaal programma vertaald voor gebruik op een bepaalde computer. Afhankelijk van de omstandigheden heet zo n vertaalprogramma een assembler, een compiler, of een interpreter. Een assembler wordt gebruikt voor het vertalen van Assembler-programma s naar machinecode. Omdat een Assembler-programma specifiek is voor een bepaalde processor, heb je voor verschillende computers verschillende programma s nodig, die elk door een overeenkomstige assembler worden vertaald. Compiler Het voordeel van alle talen behalve Assembler is dat ze, in principe althans, geschreven kunnen worden onafhankelijk van de computer. Er is dus maar één programma nodig, dat op een computer naar keuze kan worden vertaald naar de betreffende machinecode. Zo n vertaalprogramma heet een compiler. De compiler zelf is wel machine-specifiek; die moet immers de machinecode van de betreffende computer kennen. Het door de programmeur geschreven programma (de source code, of kortweg source, of in het Nederlands: broncode) is echter machine-onafhankelijk. Vertalen met behulp van een compiler is gebruikelijk voor de meeste procedurele talen, zoals Pascal, C en C++. Interpreter Een directere manier om programma s te vertalen is met behulp van een interpreter. Dat is een programma dat de broncode leest, en de opdrachten daarin direct uitvoert, dus zonder deze eerst te vertalen naar machinecode. De interpreter is specifiek voor de machine, maar de broncode is machine-onafhankelijk. Het woord interpreter betekent letterlijk tolk, dit naar analogie van het vertalen van mensentaal: een compiler kan worden vergeleken met schriftelijk vertalen van een tekst, een interpreter

11 1.6 Programmeren 11 vertaalt de uitgesproken zinnen direct mondeling. Het voordeel van een interpreter boven een compiler is dat er geen aparte vertaalslag nodig is. Het nadeel is echter dat het uitvoeren van het programma langzamer gaat, en dat eventuele fouten in het programma niet in een vroeg stadium door de compiler gemeld kunnen worden. Vertalen met behulp van een interpreter is gebruikelijk voor de wat eenvoudigere talen, zoals Basic, maar ook bijvoorbeeld voor html en de daarin ingebedde taal Javascript (niet te verwarren met Java). Compiler+interpreter Bij Java is voor een gemengde aanpak gekozen. Java-programma s zijn bedoeld om via het Internet te verspreiden. Het verspreiden van de gecompileerde versie van het programma is echter niet handig: de machinecode is immers machine-specifiek, en dan zou je voor elke denkbare computer aparte versies moeten verspreiden. Maar het verspreiden van broncode is ook niet altijd wenselijk; dan ligt de tekst van het programma immers voor het oprapen, en dat is om redenen van auteursrecht niet altijd de bedoeling. Het komt veel voor dat gebruikers het programma wel mogen gebruiken, maar niet mogen inzien of wijzigen; machinecode is voor dat doel heel geschikt. De aanpak die daarom voor Java wordt gehanteerd is een compiler die de broncode vertaalt: maar niet naar machinecode, maar naar een nog machine-onafhankelijke tussenliggende taal, die bytecode wordt genoemd. Die bytecode kan via het Internet worden verspreid, en wordt op de computer van de gebruiker vervolgens met behulp van een interpreter uitgevoerd. De bytecode is dusdanig eenvoudig, dat de interpreter erg simpel kan zijn; interpreters kunnen dan eenvoudig worden ingebouwd in Internet-browsers. Omdat het meeste vertaalwerk al door de compiler is gedaan, kan het interpreteren van de bytecode relatief snel gebeuren, al zal een naar echte machinecode gecompileerd programma altijd sneller kunnen worden uitgevoerd. 1.6 Programmeren In het klein: Edit-Compile-Run Omdat een programma een tekst is, begint het implementeren over het algemeen met het tikken van de programmatekst met behulp van een editor. Is het programma compleet, dan wordt het bestand met de broncode aangeboden aan de compiler. Als het goed is, maakt de compiler de bijbehorende bytecode, die we vervolgens met een interpreter kunnen uitvoeren. Zo ideaal verloopt het meestal echter niet. Het bestand dat je aan de compiler aanbiedt, moet wel geldige Java-code bevatten: je kunt moeilijk verwachten dat de compiler van willekeurige onzin naar zinvolle bytecode kan vertalen. De compiler controleert dan ook of de broncode aan de vereisten voldoet; zo niet, dan volgt er een foutmelding, en weigert de compiler om bytecode te maken. Nu doe je over het algemeen wel je best om een echt Java-programma te compileren, maar een tikfout is snel gemaakt, en de vorm-vereisten voor programma s zijn nogal streng. Reken er dus maar op dat je een paar keer door de compiler wordt terugverwezen naar de editor. Vroeg of laat zal de compiler echter wel tevreden zijn, en bytecode produceren. Dan kun je de volgende fase in: het uitvoeren van het programma, in het Engels run of execute genoemd, en in het Nederlands dus ook wel runnen of executeren. In veel gevallen merk je dan, dat het programma toch net niet (of helemaal niet) doet wat je bedoeld had. Natuurlijk heb je je best gedaan om de bedoeling goed te formuleren, maar een denkfout is snel gemaakt. Er zit dan niets anders op om weer terug te keren naar de editor, en het programma te veranderen. Dan weer compileren (en hopen dat je geen nieuwe tikfouten gemaakt hebt), en dan weer runnen. Om tot de conclusie te komen dat er nu wel iets anders gebeurt, maar toch nét niet wat je bedoelde. Terug naar de editor... In het groot: Modelleer-Specificeer-Implementeer Zodra de doelstelling van een programma iets ambitieuzer wordt, kun je niet direct achter de editor plaatsnemen en het programma beginnen te tikken. Aan het implementeren (het daadwerkelijk schrijven en testen van het programma) gaan nog twee fasen vooraf. Als eerste zul je een praktijkprobleem dat je met behulp van een computer wilt oplossen moeten formuleren in termen van een programma dat invoer krijgt van een gebruiker en bepaalde resultaten te zien zal geven. Deze fase, het modelleren van het probleem, is misschien nog wel het moeilijkste. Is het eenmaal duidelijk wat de taken zijn die het programma moet uitvoeren, dan is de volgende stap om een overzicht te maken van de klassen die er nodig zijn, en de methoden die daarin

12 12 Programmeren Figuur 2: Vier manieren om een programma te vertalen

13 1.6 Programmeren 13 ondergebracht gaan worden. In deze fase hoeft van de methoden alleen maar beschreven te worden wat ze moeten doen, nog niet hoe dat precies gebeurt. Bij dit specificeren zul je wel in de gaten moeten houden dat je niet het onmogelijke van de methoden verwacht: ze zullen later immers geïmplementeerd moeten worden. Als de specificatie van de methoden duidelijk is, kun je beginnen met het implementeren. Daarbij zal de hierboven genoemde edit-compile-run cyclus waarschijnlijk meermalen doorlopen worden. Is dat allemaal af, dan kun je het programma overdragen aan de opdrachtgever. In veel gevallen zal die dan opmerken dat het weliswaar een interessant programma is, maar dat er toch eigenlijk een net iets ander probleem opgelost moest worden. Dan begint het weer van voren af aan met het herzien van de modellering, gevolgd door aanpassing van de specificatie en een nieuwe implementatie, en dan... Opgaven 1.1 Assembler en compiler a. Wat is het verschil tussen een assembler en een compiler? b. Wat is het voordeel van een compiler boven een assembler? c. Waarom wordt voor sommige dingen toch, ook nu nog, een assembler gebruikt? d. Voor wat voor soort dingen is dat dan? 1.2 Broncode en objectcode a. Is het mogelijk om een eenmaal gecompileerd programma nog te ont-compileren? Waarom wel of niet? b. Kunnen twee verschillende bron-programma s in dezelfde objectcode worden vertaald? Is er voor elke mogelijk objectcode een broncode te bedenken die daarin wordt vertaald? 1.3 Compiler als programma Een compiler is zelf ook een programma. a. Kan de compiler zelf in een hogere programmeertaal zijn geschreven? b. Zo ja, kan dat ook zijn eigen taal zijn? c. Zo ja, kan hij dan ook zichzelf compileren? 1.4 Cross-compiling a. Een compiler genereert objectcode. Kan dat ook code zijn voor een andere machine dan waarop de compiler zelf runt? b. Zo nee, waarom niet; zo ja, is dat zinvol? 1.5 The future is now a. Een bekend science-fiction motief is dat computers hun eigen programma schrijven. Is dat mogelijk? Gebeurt het al? Zijn er ethische bezwaren aan verbonden? b. In 1950 schreef Alan Turing dat, om kunstmatige intelligentie te verwezenlijken, er meer geheugen nodig zou zijn dan het toenmalige beschikbare (hij waagde zich zelfs aan een voorspelling: 109 bits, dus circa 100 megabyte). Om die te kunnen gebruiken zouden programmeurs wel efficiënter moeten kunnen werken dan de 1000 cijfers per dag die hij zelf kon schrijven. Kunnen we dat inmiddels? 1.6 Productiviteit van programmeurs a. In welke eenheid zou je de productiviteit van een programmeur kunnen meten? b. In die eenheid gemeten, hoeveel programma kan een programmeur per jaar ongeveer schrijven? 1.7 Programmeerparadigma s Waar of niet waar (en waarom?) a. Alle imperatieve talen zijn object-georiënteerd. b. Er zijn object-georiënteerde talen die niet procedureel zijn. c. Procedurele talen moeten worden gecompileerd. d. Declaratieve talen kunnen niet op dezelfde processor runnen als imperatieve, omdat die processor een toekenningsopdracht kan uitvoeren, die in declaratieve talen niet bestaat.

14 14 Hoofdstuk 2 Java 2.1 Omgeving van het programma Java-applets Een van de bestaansredenen van Java is het verspreiden van programma s via het Internet. De meest directe manier om dat te doen is om programma s direct vanuit de Internet-browser te starten. Het programma krijgt dan een deel van het browser-window toegewezen, en kan dat gebruiken om met de gebruiker te communiceren. Zo n Java-programma heet een applet; dit is een voor de gelegenheid bedachte samenstelling van application ( toepassing ) en het achtervoegsel -plet ( -tje ). Een applet is dus een toepassingetje. In de opmaaktaal voor web-pagina s html is het mogelijk om aan te geven dat er een applet in een pagina moet worden opgenomen. Op dezelfde manier waarop je met de <IMG>-tag een plaatje kunt neerzetten op een pagina, kun je met de <APPLET>-tag een applet neerzetten. De eigenlijke bytecode van het programma maakt geen deel uit van de html-tekst, maar wordt in een apart bestand geplaatst, op dezelfde manier als een plaatje apart in een gif- of jpeg-bestand staat opgeslagen. Java-applications Omdat Java een complete, general purpose programmeertaal is, is het ook mogelijk om vrijstaande programma s te maken, dus programma s die los van een Internet-browser kunnen worden gebruikt. Zo n programma wordt, in contrast met een applet, een application genoemd. De opzet van een programma dat als application moet kunnen werken is een beetje anders dan dat van een applet. Zo moet een application zijn eigen window maken, terwijl een applet het browserwindow kan gebruiken. Een application kan, als dat nodig is, meer dan één window creëren, en dat is in een applet juist weer niet mogelijk. Verder kan een application de files op de disk van de computer gebruiken, iets wat in een applet om veiligheidsredenen niet is toegestaan (wel kunnen zowel applets als applications files van het internet lezen). Voor het grootste deel van het programma maakt het echter niet uit of het als applet of als application gebruikt zal worden. Een applet is heel eenvoudig om te bouwen tot application (door een paar opdrachten toe te voegen die een window maken, en daarbinnen vervolgens de applet tonen). Omgekeerd kan het wat lastiger zijn, omdat de application zich mogelijk niet houdt aan de beperkingen die voor applets gelden. Javascript is geen Java Het is een beetje verwarrend dat er nog een andere programmeertaal is die bedoeld is voor gebruik in combinatie met het Internet. Deze taal heet Javascript, en hoewel de naam er op lijkt, is dat echt heel wat anders dan Java. Behalve de naam zijn de overeenkomsten tussen Javascript en Java dat beiden imperatieve machineonafhankelijke talen zijn, gebruikt kunnen worden via een Internet-browser, en dat op een veilige manier, zonder dat de gebruiker bang hoeft te zijn dat er met de files op de disk gerommeld gaat worden. Er zijn echter behoorlijk wat verschillen: Java is object-georiënteerd, Javascript is slechts procedureel; Java wordt gecompileerd (en daarna de bytecode geïnterpreteerd), Javascript wordt direct geïnterpreteerd; Java bytecode staat in een apart bestand, Javascript-broncode staat direct in het htmlbestand;

15 2.2 Opbouw van een programma 15 import java.awt.graphics; import java.applet.applet; public class Hallo extends Applet 5 { public void paint(graphics g) { g.drawstring("hallo!", 30, 20); 10 Listing 1: Hallo/Hallo.java Java-applets werken geheel binnen het hun toegewezen deel van het browser-window, Javascriptprogramma s kunnen zich met de rest van de browser bemoeien (kleur veranderen, naar een andere pagina surfen, enz.); Java-programma s hebben de beschikking over een uitgebreide bibliotheek met klassen en methoden, Javascript-programma s alleen over een beperkt aantal ingebouwde methoden; In de praktijk komt het er op neer dat Java vooral wordt gebruikt door programmeurs, en Javascript vooral door web-ontwerpers. In deze cursus hebben we het verder alleen over Java, omdat dat op de lange termijn meer inzicht geeft in object-georiënteerde programmeertalen. En het is tenslotte niet de bedoeling dat je nooit meer afstapt van een eenmaal geleerde taal. (Dit neemt niet weg dat we wel eens jaloers zullen worden op de flitsende grafische effecten die met Javascript relatief eenvoudig te maken zijn). 2.2 Opbouw van een programma Programma: opsomming van klassen Omdat Java een imperatieve taal is, bestaat een programma vooral uit opdrachten die uitgevoerd moeten worden. Omdat Java bovendien een procedurele taal is, zijn die opdrachten gebundeld in methoden. En omdat Java bovendien een object-georiënteerde taal is, zijn die methoden op hun beurt gebundeld in klassen. Een programma bestaat aldus uit een opsomming van alle daarin benodigde klassen (met daarin de methoden (met daarin de opdrachten)). In listing 1 staat een van de kortst mogelijke Java-programma s. Het is een applet die de tekst blz. 15 Hallo! op het scherm schrijft. Het bestaat uit slechts één opdracht. Die ene opdracht moet desondanks in een methode worden verpakt, en die ene methode vervolgens weer in een klasse. Elke klasse heeft een naam, die door de programmeur wordt bedacht. In het voorbeeld heet de klasse Hallo. Klasse: opsomming van methoden Een klasse bestaat grotendeels uit een opsomming van de methoden die er deel van uitmaken. In het voorbeeld is dat er maar één. Net als de klasse krijgen ook de methoden een door de programmeur bedachte naam. In het voorbeeld is dat paint. Methode: opsomming van opdrachten In de methoden zijn de eigenlijke opdrachten ondergebracht. In het voorbeeld-programma is dat er maar één, namelijk een de opdracht om op een bepaalde plaats op het scherm een bepaalde tekst te tekenen. We zullen het voorbeeld nu wat gedetailleerder bekijken. 2.3 Klasse-definitie Klasse-header en -body Elke definitie van een klasse bestaat uit een header (een kopregel ) en een body. De header van de klasse Hallo in het voorbeeldprogramma is public class Hallo extends Applet

16 16 Java De body die daaronder volgt moet in zijn geheel tussen accolades { en staan: daarmee wordt aangegeven welke methoden er allemaal bij deze klasse horen. Het is gebruikelijk om deze accolades recht onder elkaar te zetten, op een aparte regel. Alle tekst die daartussen staat wordt een stukje ingesprongen. Zo kun je gemakkelijk zien waar de klasse eindigt. public: bruikbaar voor de omgeving In de header van de klasse-definitie staat in ieder geval het woord class, om aan te geven dat het een klasse-definitie betreft. Direct daarachter staat de door de programmeur gekozen naam, in dit geval Hallo. Nog vóór het woord class kan het woord public staan. Dat geeft aan dat de klasse van buitenaf gebruikt mag worden. Omdat het voorbeeldprogramma een applet is, moet deze klasse inderdaad public gemaakt worden, omdat de klasse door de browser gebruikt wordt. In plaats van het woord public mag er in de klasse-header ook het woord private staan. De klasse is dan alleen voor intern gebruik door andere klassen in het programma. In het voorbeeld is dat dus geen goed idee (er zijn niet eens andere klassen!). Als je het woord public of private helemaal weglaat (en de klasse-header dus direct met het woord class begint), maakt de compiler de klasse automatisch private. extends: voortbouwen op eerder werk Achter de naam van de klasse mag in de header het woord extends staan, gevolgd door de naam van een andere, al bestaande klasse. Daarmee kunnen we aangeven dat deze klasse niet van de grond af aan wordt opgebouwd, maar een uitbreiding is van de bestaande klasse. Omdat het hallo-programma uit het voorbeeld een applet moet worden, zorgen we er voor dat de klasse een uitbreiding is van de bestaande klasse Applet. Daardoor hoeven we alleen nog maar aan te geven in hoeverre ons programma afwijkt van een blanco applet. 2.4 Methode-definitie Methode-header en -body De body van een klasse bestaat uit definities van methoden die bij die klasse horen. In het voorbeeldprogramma is dat er maar één: een methode genaamd paint. Net als een klasse heeft een methode een header en een body. In de header staat ditmaal natuurlijk niet het woord class. De header van de methode in het voorbeeld is: public void paint(graphics g) De body van de methode moet in zijn geheel tussen accolades staan. Daarmee wordt aangegeven welke opdrachten allemaal bij deze methode horen. In dit geval is dat er overigens maar één. Voor de duidelijkheid zetten we de accolades weer op aparte regels, recht onder elkaar, en springen we de opdracht in de body weer een stukje verder in. public: bruikbaar voor de omgeving Een methode-header begint, net als de klasse-header, met een aanduiding van de protectie: het woord public of private. De methode in het voorbeeld moet door de browser gebruikt mogen worden, en is dus public. void: doet dingen, maar berekent niets Het tweede woord van de methode-header geeft het type van de methode. Sommige methoden kunnen een waarde berekenen, en dan geeft het type aan wat voor soort waarde dat is. Maar er zijn ook methoden die er alleen maar voor dienen om een aantal opdrachten uit te voeren; de methode in het voorbeeld is er zo een. In dat geval moet als type het woord void worden gebruikt. Letterlijk betekent dit leeg, en dat klopt, want de resultaatwaarde van deze methode is leeg. Naam van de methode Het derde woord in de methode-header is de naam van de methode. De naam van de methode mag in principe door de programmeur bedacht worden. Het is echter zo, dat de browser op het moment dat een applet in beeld gebracht wordt, de opdrachten begint uit te voeren die in de methode paint staan. Willen we dus dat onze methode daadwerkelijk door de browser uitgevoerd zal worden, dan is er eigenlijk geen keuze en moeten we de methode paint noemen.

17 2.5 Opdrachten 17 Parameters van de methode Achter de naam van de methode volgt een opsomming van de zogenaamde parameters van de methode. Die staan tussen haakjes; gewone, ronde haakjes en dus geen accolades. De programmeur mag kiezen hoeveel parameters er zijn: nul, één, twee of meer. Maar zelfs als er nul parameters zijn moeten de haakjes er staan, met niets er tussen. In het geval van de methode paint is die keuzevrijheid er echter niet, omdat er over deze methode al afspraken bestaan met de browser. De methode paint moet daarom precies één parameter krijgen. De parameters geven aan wat het type is van de variabelen die bij de start van de methoden beschikbaar zijn voor gebruik in die methode. In het geval van paint is er geen keuze mogelijk: die ene parameter moet per se een Graphics-object zijn. Wel mogen we zelf een naam voor deze parameter verzinnen. In het voorbeeld is voor de parameter de naam g gekozen. Waar die parameters voor dienen zal spoedig duidelijk worden, maar eerst gaan we de body van de methode bekijken. 2.5 Opdrachten Methode-aanroep Er zijn in Java een tiental verschillende soorten opdrachten mogelijk. We beginnen met een van de belangrijkste: de aanroep van een methode. Als zo n methode-aanroep door de processor wordt uitgevoerd, dan zal de processor op dat moment de opdrachten gaan uitvoeren die in de body van die methode staan. Pas als die allemaal zijn uitgevoerd, gaat de processor verder met de opdracht die volgt op de methode-aanroep. Het aardige is dat de opdrachten in de body van de aangeroepen methode ook weer methodeaanroepen mogen zijn, van weer andere methoden. Beschouw het maar als een soort delegeren van werk aan anderen: als een methode geen zin heeft om het werk zelf uit te voeren, wordt een andere methode aangeroepen om het vuile werk op te knappen. In het voorbeeldprogramma is de enige opdracht in de body van de methode paint zo n methodeaanroep: g.drawstring("hallo!", 30, 20); We zullen deze opdracht nu in detail ontleden. Object waarop de methode werkt Elke methode die wordt aangeroepen neemt een bepaald object onder handen. In de methodeaanroep wordt dat object als eerste genoemd het paradigma heet niet voor niets objectgeoriënteerd programmeren! In het voorbeeld gaat het object g, dat we als parameter hebben gekregen, dienen als object waar de methode-aanroep op werkt. Naam van de methode Achter de naam van het object volgt een punt, en daarna de naam van de methode die we willen aanroepen. In het voorbeeld willen we dat het object g iets voor ons gaat tekenen, en daarom roepen we de methode drawstring aan. Parameters van de methode Bij de aanroep van de methode drawstring moeten we nog wat details doorgeven. Deze methode verwacht namelijk een drietal parameters: welke tekst er getekend moet worden, en op welke plaats dat moet gebeuren. De tekst in kwestie is "Hallo!", de positie op het scherm 30 beeldpunten van de linkerrand en 20 beelpunten van de bovenrand van het window verwijderd. De parameters moeten tussen haakjes (gewone ronde) worden geschreven. Tenslotte sluit een puntkomma de methode-aanroep af. De tekst staat tussen aanhalingstekens, om aan te geven dat deze tekst echt letterlijk op het scherm gezet moet worden. Zonder de aanhalingstekens zou de compiler gaan klagen dat er geen tekst genaamd Hallo bestaat (wel een klasse, maar die kan weer niet getekend worden), en dat zo n los uitroepteken ook geen geldige Java is. Tussen de aanhalingstekens accepteert de compiler echter alles. De aanhalingstekens bakenen de grenzen van de tekst af, en de tussenliggende symbolen worden letterlijk overgenomen.

18 18 Java 2.6 Methoden en parameters Parameters in een methode-definitie Parameters zijn de manier waarop methoden met elkaar communiceren: in een methode-header kun je aangeven dat een methode parameters verwacht, bij een methode-aanroep moet je aangeven wat de waarden van de parameters dan wel zijn. De methode-header van paint luidde: public void paint(graphics g) Hiermee geven we aan dat we van degene die de methode aanroept eisen dat hij bij aanroep van de methode een object van type Graphics verschaft. Parameters in een methode-aanroep Bij de aanroep van een methode moeten de eisen worden ingewilligd. Nu is het zo dat in de header van de methode drawstring is aangegeven dat deze methode een tekst en twee getallen verwacht. Bij aanroep van de methode zijn we dus verplicht om inderdaad met een tekst en twee getallen op de proppen te komen. De parameters staan, zowel in de header van de methode als bij aanroep, tussen haakjes. Maar wat er tussen de haakjes staat, verschilt: in de header staat er het type van de parameter en een zelfbedachte naam, bij de aanroep moet er alleen een waarde voor de betreffende parameter worden vermeld. Objecten van type Graphics Wat moeten we ons nu voorstellen bij dat Graphics-object, dat de methode paint als parameter ontvangt? Een Graphics-object is het beste te beschouwen als een teken-apparaat. Het is een object dat op verzoek teksten (en andere dingen) kan tekenen, en wel door de methode drawstring aan te roepen met het object vóór de punt. De web-browser is magischerwijze in het gelukkige bezit van zo n teken-apparaat, en bij aanroep van paint is hij zo aardig om dat object als parameter mee te geven. In de methode-header van paint nemen we het object in ontvangst, en geven het de naam g. In de body van paint kunnen we van het verworven object g gebruik maken, door het object in een methode-aanroep te gebruiken vóór het punt-teken. 2.7 Naamgeving Eisen voor de naamgeving Veel zaken in Java worden aangeduid door een naam: klassen, methoden, object-typen, parameters. Soms is dat een naam die door de programmeur zelf wordt bedacht, soms is het de naam van een al bestaande klasse of methode. De naam moet voldoen aan de volgende eisen: de naam bestaat uit één of meer letters, cijfertekens, en/of onderstrepings- en dollar-tekens; het eerste teken is in ieder geval een letter; een vijftigtal woorden is taboe, omdat die een speciale betekenis hebben in Java (voorbeelden hiervan zijn public, class, extends, en void). Hoofdletters en kleine letters worden als verschillende symbolen beschouwd; de methode in het voorbeeld moet dus echt paint heten, en niet Paint of PAINT. Deze regels zijn bindend; als je ertegen zondigt zal de compiler bezwaar maken. Aanbevelingen voor de naamgeving Daarnaast doen de ontwerpers van Java een paar aanbevelingen voor de naamgeving. Deze regels zijn niet bindend in de zin dat ze door de compiler worden afgedwongen, maar het is een goede gewoonte om je er aan te houden. Als iedereen dat doet komen programma s die door een team worden geschreven er mooi uniform uit te zien. Deze stijlregels luiden: namen van klassen beginnen met een hoofdletter (bijvoorbeeld onze eigen klasse Hallo, en de al bestaande klasse Applet), en namen van object-typen ook (bijvoorbeeld Graphics); namen van methoden beginnen met een kleine letter (bijvoorbeeld paint en drawstring) en namen van parameters ook (bijvoorbeeld g); als een naam uit meerdere woorden bestaat, beginnen het tweede en de verdere woorden met een hoofdletter (zoals in drawstring);

19 2.8 Bibliotheek-klassen 19 rare afkortingen moeten worden vermeden-gebruik liever een naam met meerdere woorden (dus niet emn maar eenmooienaam). 2.8 Bibliotheek-klassen Importeren van klassen In het voorbeeldprogramma worden twee klassen gebruikt uit een bibliotheek van Javastandaardklassen: Applet, omdat onze klasse Hallo daar een uitbreiding van is Graphics, omdat dat het objecttype is van de parameter van paint Om deze klassen te mogen gebruiken, moeten we aan het begin van het programma aangeven dat deze klassen worden geïmporteerd. Dit gebeurt met een speciale import-aanwijzing aan het begin van het programma: import java.awt.graphics; import java.applet.applet; Dit zijn, in tegenstelling tot de methode-aanroep verderop in het programma, geen opdrachten; ze hoeven immers niet door de processor worden uitgevoerd tijdens het runnen van het programma. Het zijn veeleer aanwijzingen voor de compiler dat de bewuste klassen in het programma gebruikt gaan worden. Dat is de reden dat we ze niet import-opdrachten noemen. Package: bundeling van klassen De klassen in de Java-bibliotheek zijn georganiseerd in zogenaamde packages. (Informatici hebben iets met hiërarchische ordeningen). In de import-aanwijzingen moet de naam van de package vermeld worden. De klasse Applet zit in package java.applet (waarin naast Applet zelf nog een paar klassen zitten die iets met applets te maken hebben). De klasse Graphics zit in package java.awt. Dit is een package waarin alle klassen zitten die behoren tot de abstract window toolkit. Deze toolkit heet abstract omdat de klassen niet specifiek bedoeld zijn voor een bepaald operating system. Je kunt er dus window-gebaseerde programma s mee maken die op meerdere computersystemen werken. 2.9 Ontwikkelomgevingen Java-applets op een web-pagina Java-applets maken onderdeel uit van een web-pagina. Om een applet te kunnen uittesten, moet je dus eerst een html-bestand maken met een verwijzing naar de applet. In listing 2 staat een blz. 20 voorbeeld van een html-bestand waarin de Hallo-applet is opgenomen. Dit gebeurt met een <APPLET>-tag, waarbij de naam van de file met de bytecode, en de breedte en hoogte die voor het applet moet worden gereserveerd, is aangegeven. Er is ook een bijbehorende eind-tag </APPLET>. Tekst die tussen de begin- en eindtag staat wordt alleen maar weergegeven als de browser onverhoopt niet in staat is om applets te runnen. Source code, bytecode en html-code De naam van het bestand waarin de source code staat moet dezelfde naam hebben als de klasse die daarin wordt beschreven, met de extensie.java. Het programma met de klasse Hallo staat dus in het bestand Hallo.java. Niet alle compilers zijn even strikt in het afdwingen van deze regel, maar het is sowieso wel een handig systeem om je klassen later zelf te kunnen terugvinden. Als je de source code compileert, maakt de compiler een bestand aan met de extensie.class, in dit geval dus Hallo.class. Dat is een beetje verwarrend, want de tekst van de class staat dus in de java-file, terwijl de class-file bytecode bevat... Het bytecode-bestand is niet leesbaar met een editor. Dat hoeft ook niet, want het bestand is bestemd voor de interpreter die in de web-browser is ingebouwd. Het is overigens wel aardig om de class-file eens met een hexadecimale editor te bekijken. De.class-file en de.html-file moeten op de web-server worden geplaatst. De.java-file hoef je niet op de server te zetten; die kun je dus desgewenst geheim houden. Als je vervolgens met de browser naar de.html-file surft, begint de applet te werken (de browser roept namelijk de methode paint aan). Het resultaat is te zien in figuur 3.

20 20 Java <HTML> Hier is een <B>simpel</B> applet: <BR> <APPLET code=hallo.class width=100 height=50> </APPLET> 5 </HTML> Listing 2: Hallo/Hallo.html Figuur 3: Source code, byte code en html code

21 2.9 Ontwikkelomgevingen 21 De Java2 Software Development Kit (SDK) In de door Sun uitgebrachte Java Software Development Kit zitten onder andere de volgende programma s: javac, een compiler die Java source code vertaalt naar bytecode java, een interpreter voor stand alone Java applications appletviewer, een interpreter voor applets, waarmee je ook zonder browser je applets kunt testen. Vroeger heette dit de Java Development Kit (JDK), waarvan versies 1.0 en 1.1 hebben bestaan. Toen men aan versie 1.2 toe was werd de naam gewijzigd in SDK, en om marketingredenen werd het pakket Java2 genoemd. De software evolueerde verder naar versie 1.4, die dus voluit eigenlijk Java2 SDK, version 1.4, Standard Edition genoemd moet worden. De taal is sinds versie 1.2 stabiel, maar bij elke versie wordt de bibliotheek met standaardklassen uitgebreid. Edit-compile-run met de SDK In de SDK zit geen aparte editor. Je kunt de source code en de html-code echter gewoon intikken met je favoriete editor. Een simpele editor zoals notepad of pfe volstaat. De compiler moet je starten vanuit een dos-box, met het commando javac Hallo.java Eventuele foutmeldingen verschijnen ook in de dos-box. De compiler geeft het regelnummer van de fout aan; die regel kun je in de editor opzoeken, om de fout in het programma te verbeteren. Als de compiler niets meer te klagen heeft, is er als het goed is een.class-file ontstaan. Je kunt de applet dan testen met het commando appletviewer Hallo.html waarbij Hallo.html de html-file is waarin de <APPLET>-tag met de verwijzing naar Hallo.class is opgenomen. Als het programma geen applet is maar een application, dan kun je de bytecode interpreteren met het commando java Startklasse Waarbij Startklasse de naam van de klasse is (dus niet de naam van een file!) waarin het hoofdwindow wordt opgebouwd. Voorlopig zullen we dat echter in deze cursus niet gebruiken, omdat de programma s in de komende hoofdstukken allemaal applets zijn. Geïntegreerde ontwikkelomgevingen Hoewel de SDK in principe genoeg is om Java-programma s te ontwikkelen, is het gebruiksgemak nogal beperkt. Meer service wordt geboden door zogenaamde geïntegreerde ontwikkelomgevingen, waarin de compiler en interpreter, maar ook de editor, zijn samengevoegd tot één geheel. Zo n ontwikkelomgeving biedt meestal de volgende faciliteiten: je kunt de foutmeldingen van de compiler aanklikken, waarna de editor direct naar de bewuste regel springt; de editor kent Java, en geeft bijvoorbeeld bijzondere woorden zoals class en void weer in een aparte kleur; de editor ondersteunt typische Java-constructies, zoals paren accolades met een ingesprongen body daartussen; de editor geeft een overzicht van klassen en methoden in het programma; met één druk op de knop kun je het programma compileren en/of runnen; als de cursor op een klasse- of methode-naam uit de bibliotheek staat, kun je met een druk op de help-toets direct de documentatie daarvan raadplegen. Er zijn diverse geïntegreerde ontwikkelomgevingen (Integrated Development Environments of IDE) voor Java op de markt, waaronder Webgain Visual Cafe, Inprise JBuilder, Microsoft J++, IBM Visual Age, Oracle Jdeveloper, Metroworks CodeWarrior, and Tek-tools Kawa. De meeste IDE s hebben geen ingebouwde compiler, maar gebruiken achter de schermen de compiler van de SDK. De IDE vormt dus alleen een schil rond de SDK. Dit heeft als voordeel dat je dezelfde IDE kunt blijven gebruiken als er een nieuwe versie van de SDK uitkomt. In appendix A beschrijven we het gebruik van een freeware IDE genaamd JCreator van Xinox software. Deze heeft vrijwel dezelfde mogelijkheden als de hierboven genoemde commerciële producten, en heeft als bijkomend voordeel dat het gratis is te gebruiken.

22 22 Java Opgaven 2.1 Veiligheid van applets a. Al rondsurfend over het web kun je applets tegenkomen van andere programmeurs. Wat zou je in ieder geval niet willen dat zo n applet kan doen? b. Tot dingen die een applet niet mogen doen (omdat dat een potentieel gevaar is voor onnozele surfers) is het creëren van een nieuw window. Waarom wordt dat als veiligheidsrisico beschouwd? 2.2 Namen veranderen a. De klasse in het voorbeeldprogramma in hoofdstuk 2 heet Hallo. Wat moet er allemaal veranderen als we dat willen veranderen in Hoi? Wat hoeft er strikt genomen niet te veranderen, maar zou logisch zijn om ook te veranderen? b. De parameter van de methode paint in dit programma heet g. Wat moet er allemaal veranderen als we dat willen veranderen in gr? 2.3 Naamgeving In de wiskunde en de natuurkunde worden vaak vaste variabele- en constantennamen gebruikt in een vaste betekenis. Welke bijvoorbeeld? Is dat handig? 2.4 Edit-compile-run a. Tik het programma Hallo in (of een variatie daarop). b. Compileer en run het programma in de JCreator omgeving. c. Run het programma vanuit de www-browser d. Zet de applet op je homepagina op de www-server, en surf erheen. e. Verander iets aan het programma en run het opnieuw. 5. Multiple window a. Maak het window voor de foutmeldingen van de compiler groter. b. Hoe kun je de inhoud van twee files (bijvoorbeeld Java- en html-files) bekijken? 2.5 Compiler foutmeldingen Introduceer steeds één van de hieronderstaande fouten (als je dat al niet per ongeluk had gedaan... ) en verklaar de reactie van de compiler: a. vergeet een import-aanwijzing b. vergeet het woord public in de klasse- en/of methode-header c. vergeet de hoofdletter in de klassenaam d. vergeet het aanhalingsteken sluiten e. vergeet de puntkomma achter de opdracht f. vergeet de accolade sluiten achter de methode en/of klasse 2.6 Help-pagina s a. Zoek de help-pagina van de methode drawstring. b. Welke methoden zitten er o.a. nog meer in deze klasse? c. Probeer een van die methoden te gebruiken in de applet.

23 23 Hoofdstuk 3 Tekenen en rekenen 3.1 Graphics Grafische uitvoer Een programma dat alleen maar "Hallo!" op het scherm schrijft is niet geweldig interessant (het had zelfs zonder applet gekund, door de tekst "Hallo!" direct in de html-file te zetten... ). Gelukkig kennen objecten van type Graphics nog andere methoden dan drawstring. Door in de body van de paint-methode van een applet meerdere methoden aan te roepen, kunnen we een gecompliceerde tekening opbouwen. Het programma in listing 3 bijvoorbeeld, maakt op deze blz. 24 manier een schilderij in de Stijl van Mondriaans compositie met rood en blauw. In figuur 4 is deze applet in werking te zien. Methoden in de klasse Graphics Objecten van het type Graphics, zoals we aangeleverd krijgen als parameter van de methode paint, kunnen worden gebruikt met onder andere de volgende methoden: drawstring(t, x, y) tekent tekst t op positie (x,y) drawline(x1, y1, x2, y2) tekent een lijn van positie (x1,y1) naar (x2,y2) drawrect(x, y, b, h) tekent een rechthoek met breedte b en hoogte h drawoval(x, y, b, h) tekent een ovaal binnen de genoemde rechthoek fillrect(x, y, b, h) als drawrect, maar nu helemaal ingekleurd setcolor(c) gebruikt kleur c in volgende teken-acties Alle afmetingen en posities worden geteld in beelscherm-punten, en worden gerekend vanaf de linkerbovenhoek. De x-coördinaat loopt dus gewoon van links naar rechts, maar de y-coördinaat loopt van boven naar beneden (net andersom als in wiskunde-grafieken); zie figuur 5. In listing 3 gebruiken we de methode fillrect om een aantal rechthoeken in te kleuren. Voor de blz. 24 gekleurde rechthoeken roepen we eerst setcolor aan. Klassen beschrijven de mogelijkheden van objecten Alle methoden uit de klasse Graphics kun je aanroepen, als je tenminste de beschikking hebt over een object met object-type Graphics. Dat is in de body van de methode paint geen probleem, want die methode heeft een Graphics-object als parameter, die in de body gebruikt mag worden. Dit illustreert de rol van klasse-definities. Het is niet zomaar een opsomming van methoden: de methoden kunnen gebruikt worden om een object uit die klasse te bewerken. In zekere zin beschrijft de lijst van methoden de mogelijkheden van een object: een Graphics-object kan teksten, lijnen, rechthoeken en ovalen tekenen. Je kunt zien dat objecten geheugen hebben. Na aanroep van de methode setcolor onthoudt het object immers welke kleur gebruikt moet worden als er later nog aanroepen van een van de draw-methoden zouden volgen. Dat klopt ook wel met de manier waarop in sectie 1.2 over objecten werd gesproken: een object is een groepje variabelen. Inmiddels hebben we gezien dat een klasse (sectie 1.2: groepje methoden met een naam) beschrijft wat je met zo n object kunt doen. Het gedrag dat het object door aanroep van de methoden kan vertonen is veel interessanter dan een beschrijving van welke variabelen nou precies deel uitmaken van een object. Je ziet dit duidelijk aan de manier waarop we het Graphics-object gebruiken: uit welke variabelen het object precies is opgebouwd hoeven we helemaal niet te weten, als we maar weten welke methoden aangeroepen kunnen worden. Het gebruik van bibliotheek-klassen gebeurt onder het motto: vraag niet hoe het kan, maar profiteer ervan!.

24 24 Tekenen en rekenen import java.awt.graphics; import java.awt.color; import java.applet.applet; 5 /* Deze applet tekent een Mondriaan-achtige "compositie met rood en blauw" */ public class Mondri extends Applet 10 { public void paint(graphics g) { int breedte, hoogte, balk, x1, x2, x3, y1, y2; 15 // posities van de lijnen breedte = 200; x1 = 10; x2 = 50; x3 = 90; hoogte = 100; y1 = 40; y2 = 70; balk = 10; 20 // achtergrond g.setcolor(color.white); g.fillrect(0, 0, breedte, hoogte); // zwarte balken 25 g.setcolor(color.black); g.fillrect(x1, 0, balk, hoogte); g.fillrect(x2, 0, balk, hoogte); g.fillrect(x3, 0, balk, hoogte); g.fillrect(0, y1, breedte, balk); 30 g.fillrect(0, y2, breedte, balk); // gekleurde vlakken g.setcolor(color.blue); g.fillrect(0, y1+balk, x1, y2-(y1+balk) ); 35 g.setcolor(color.red); g.fillrect(x3+balk, 0, breedte-(x3+balk), y1); Listing 3: Mondri/Mondri.java Figuur 4: De applet Mondri in werking

25 3.2 Variabelen 25 Figuur 5: Enkele methoden uit de klasse Graphics De klasse Color De methode setcolor in de klasse Graphics behoeft nog wat nadere aandacht. Bij aanroep van deze methode worden we geacht een kleur als parameter mee te geven. Zo n kleur is in Java een object op zichzelf, en de vraag is: hoe komen we aan kleur-objecten? Er is een bibliotheek-klasse Color, waarin een aantal methoden staan die gebruikt kunnen worden met een Color-object onder handen. Maar zo n object hebben we nou juist nog niet... Nu is het zo, dat in klassen nog meer te halen valt dan alleen methoden. In sommige klassen zitten ook constanten die je in je programma s kunt gebruiken. Zo ook in de klasse Color: hierin zitten een aantal kant-en-klare Color-objecten. Deze constanten kun je pakken door de naam van de klasse en de naam van de constante te noemen, gescheiden door een punt, bijvoorbeeld: Color.blue Ditmaal staat er dus voor de punt niet de naam van een object (zoals bij methode-aanroepen), maar de naam van de klasse. Het aldus verkregen Color-object kunnen we gebruiken als parameter bij de aanroep van de methode setcolor: g.setcolor( Color.blue ); waarbij g het Graphics-object is dat voortaan in blauw moet gaan tekenen. In de klasse Color zijn de volgende Color-constanten beschikbaar: black darkgray red green blue gray white lightgray cyan magenta yellow orange pink Cyan is de complementaire kleur van rood: de blauw/groene kleur van cyanidezouten. Magenta is de complementaire kleur van groen: lichtroze. Als de klasse Color in een programma wordt gebruikt, moet deze aan het begin van het programma worden geïmporteerd. Net als de klasse Graphics zit de klasse Color in het package java.awt, dus de import-aanwijzing luidt: import java.awt.color; 3.2 Variabelen Opslag in het geheugen In het voorbeeldprogramma worden drie vertikale zwarte balken getekend. Dat had gekund met de volgende opdrachten:

26 26 Tekenen en rekenen g.fillrect(10, 0, 10, 100); g.fillrect(50, 0, 10, 100); g.fillrect(90, 0, 10, 100); De eerste twee parameters geven de plaats aan van de balken: 10, 50 en 90 beeldpunten vanaf de linkerrand, tegen de bovenrand aan. De derde en vierde parameter stellen de breedte (10) en hoogte (100) voor van de balken. Nu zou het kunnen zijn dat we er na enig experimenteren achter komen dat het mooier is als de breedte van de balken niet 10, maar 12 is. Bij dat experimenteren moeten we dan in alle aanroepen de 10 vervangen door de 12. Het vervelende is, dat dat niet met zoek- en vervang-opdracht van de editor kan, want dan wordt ook de x-coördinaat van de eerste balk veranderd, en verandert de 100 in 120. (Wie denkt dat de drie getalletjes ook wel met de hand veranderd kunnen worden, stelle zich een experiment met de Victory Boogie-Woogie voor). Een oplossing is het gebruik van variabelen. In plaats van de constanten 10 en 100 gebruiken we twee variabelen, laten we zeggen balk en hoogte: g.fillrect(10, 0, balk, hoogte); g.fillrect(50, 0, balk, hoogte); g.fillrect(90, 0, balk, hoogte); We gaan er voor zorgen dat die variabelen de waarden 10 en 100 hebben, maar mochten we daar later niet tevreden mee zijn, zijn ze eenvoudig te wijzigen. Toekenningsopdracht Je kunt variabelen in Java een waarde geven met een zogenaamde toekennings-opdracht (engels: assignment statement). Dat gebeurt als volgt: balk = 10; hoogte = 100; Een toekenningsopdracht bestaat dus uit: de naam van de variabele die een waarde moet krijgen; het teken =, uit te spreken als wordt ; de nieuwe waarde van de variabele; een puntkomma. Als de variabele al eerder (met een andere toekenningsopdracht) een waarde had gekregen, dan gaat de oude waarde verloren. En omgekeerd: de nieuwe waarde blijft behouden tot de volgende toekenningsopdracht aan dezelfde variabele. Dat is ook de reden dat je het =-teken maar beter niet kunt uitspreken als is, maar liever als wordt : de waarde van de variabele balk is nog niet 10, althans niet noodzakelijkerwijs, maar hij wordt het door uitvoeren van de toekenningsopdracht. Na de methode-aanroep in sectie 2.5 is de toekenningsopdracht de tweede vorm van Javaopdrachten die we zijn tegengekomen. Declaratie van variabelen Je mag niet in het wilde weg variabelen gebruiken in een programma. De variabelen die je nodig hebt, moeten van tevoren worden aangekondigd. Dat gebeurt met de declaratie van variabelen; de variabelen moeten worden gedeclareerd. De declaratie van de variabelen balk en hoogte ziet er als volgt uit: int balk, hoogte; Een declaratie bestaat dus uit: het type dat de variabelen moeten krijgen; de naam of namen van de variabele(n), gescheiden door komma s; een puntkomma. Een declaratie is geen opdracht: er valt immers, tijdens het runnen, niets uit te voeren aan een declaratie. Een declaratie is veeleer een aanwijzing voor de compiler dat bepaalde variabelen gebruikt gaan worden en van een bepaald type zijn. De plaats van declaraties Declaraties van variabelen staan in de body van de methode waarin de variabelen nodig zijn, dus na de accolade-openen van de methode.

27 3.2 Variabelen 27 In principe mogen ze overal in de body staan, dus afgewisseld met de opdrachten. In de praktijk staan ze vaak aan het begin van de methode, op dezelfde manier waarop een recept begint met een opsomming van ingrediënten. Na de declaratie volgen toekenningsopdrachten die de variabelen van een waarde voorzien, en daarna kunnen de variabelen worden gebruikt: public void paint(graphics g) { int balk, hoogte; balk = 10; hoogte = 100; g.fillrect(10, 0, balk, hoogte); g.fillrect(50, 0, balk, hoogte); g.fillrect(90, 0, balk, hoogte); Declaraties van variabelen lijken veel op de parameters, die in de methode-header zijn opgesomd. In feite zijn dat óók declaraties. Maar er zijn een paar belangrijke verschillen: variabelen worden gedeclareerd in de body van de methode, parameters worden gedeclareerd tussen de haakjes in de methode-header; variabelen krijgen een waarde door een toekennings-opdracht, parameters krijgen automatisch een waarde bij de aanroep van de methode; in een variabele-declaratie kun je meerdere variabelen tegelijk declareren en het type maar één keer opschrijven, in parameter-declaraties moet bij elke parameter opnieuw het type worden opgeschreven (zelfs als dat hetzelfde is); variabele-declaraties eindigen met een puntkomma, parameter-declaraties niet. Het type int Door middel van declaraties (zowel variabele-declaraties in de body van een methode, als parameter-declaraties in de header van een methode) hecht je een type aan de variabele-naam, respectievelijk parameter-naam. In veel gevallen is dat een object-type: de parameter van de methode paint die we in de parameterdeclaratie g genoemd hebben is bijvoorbeeld een Graphics-object. De variabelen balk en hoogte in de variabele-declaratie hebben het type int. Dit type is geen object-type, en balk en hoogte zijn dan ook geen objecten; het zijn getallen, en dat zijn in Java primitieve waarden. Dat wil zeggen dat dit soort waarden in Java ingebouwd zijn, zonder dat er een klasse is waarin hun eigenschappen liggen vastgelegd. Primitieve waarden zijn geen objecten, en je kunt er dan ook geen methoden van aanroepen. Wel kun je ze gebruiken als parameter van methoden, zoals in het voorbeeld gebeurt bij de aanroep van fillrect. De betreffende parameter-positie moet, bij de definitie van de methode, dan wel als int zijn gedeclareerd. Primitieve waarden zijn geen objecten, en de primitieve types zijn dan ook geen klassen. Daarom wordt het type int, in tegenstelling tot object-typen, niet met een hoofdletter geschreven. Het type int is ingebouwd in de taal, en hoeft dus ook niet geïmporteerd te worden. Het woord int is niet eens een naam, zoals de klassenamen die object-typen aanduiden dat wel zijn. Het woord int, en de andere primitieve typen, hebben de status van gereserveerd woord, net zoals woorden als public en extends. Variabelen (en parameters) met het type int zijn getallen. Hun waarde moet geheel zijn; er kunnen in int-waarden dus geen cijfers achter de komma staan. De waarde kan positief of negatief zijn. De grootst mogelijk int-waarde is , en de kleinst mogelijke waarde is ; het bereik ligt dus ruwweg tussen min en plus twee miljard. Er zijn maar een handjevol primitieve typen. Andere primitieve typen die we nog zullen tegenkomen zijn double (getallen die wel cijfers achter de komma kunnen hebben), char (lettertekens) en boolean (waarheidswaarden). Alle andere typen zijn object-typen; hun mogelijkheden worden wèl beschreven in een klasse. Nut van declaraties Declaraties zijn nuttig om meerdere redenen: de compiler weet door de declaraties van elke variabele wat het type is; daardoor kan de compiler controleren of methode-aanroepen wel zinvol zijn (aanroep van fillrect is zinvol

28 28 Tekenen en rekenen met Graphics-objecten, maar onmogelijk met waarden van andere object-typen of met intwaarden); de compiler kan bij aanroep van methoden controleren of de parameters wel van het juiste type zijn; zou je bijvoorbeeld bij aanroep van drawstring de tekst en de positie omwisselen, dan kan de compiler daarvoor waarschuwen; als je een tikfout maakt in de naam van een variabele (bijvoorbeeld hootge in plaats van hoogte), dan komt dat aan het licht doordat de compiler klaagt dat deze variabele niet is gedeclareerd blz Berekeningen Expressies met een int-waarde Op verschillende plaatsen in het programma kan het nodig zijn om een int-waarde op te schrijven, bijvoorbeeld: als parameter in een methode-aanroep van een methode met int-parameters aan de rechterkant van een toekenningsopdracht aan een int-variabele Op deze plaatsen kun je een constante getalwaarde schrijven, zoals 37, of de naam van een intvariabele, zoals hoogte. Maar het is ook mogelijk om op deze plaats een formule te schrijven waarin bijvoorbeeld optelling en vermenigvuldiging een rol spelen, bijvoorbeeld hoogte+5. In dat geval wordt, op het moment dat de opdracht waarin de formule staat wordt uitgevoerd, de waarde uitgerekend (gebruikmakend van de op dat moment geldende waarden van variabelen). De uitkomst wordt gebruikt in de opdracht. Zo n formule wordt een expressie genoemd: het is een uitdrukking waarvan de waarde kan worden bepaald. Gebruik van variabelen en expressies In het voorbeeldprogramma in listing 3 komen variabelen en expressies goed van pas. Om het programma gemakkelijk aanpasbaar te maken, zijn er niet alleen variabelen gebruikt voor de breedte en hoogte van het schilderij, en voor de breedte van de zwarte balken daarin, maar ook voor de positie van de zwarte balken. De x-positie van de drie verticale balken wordt opgeslagen in de drie variabelen x1, x2 en x3, en de y-positie van de twee horizontale balken in de twee variabelen y1 en y2 (er mogen cijfers voorkomen in variabele-namen, als die maar met een letter begint). Met toekenningsopdrachten krijgen deze variabelen een waarde toegekend: breedte = 200; x1 = 10; x2 = 50; x3 = 90; enzovoorts. Bij het tekenen van de balken komt er, behalve het getal 0, geen enkele constante meer aan te pas: g.fillrect(x1, 0, balk, hoogte); g.fillrect(0, y1, breedte, balk); Met behulp van expressies kunnen we ook de positie van de gekleurde vlakken in termen van deze variabelen aanduiden. Het blauwe vlak aan de linkerkant ligt direct onder de eerste zwarte balk; dit vlak heeft dus een y-coordinaat die één balkbreedte groter is dan de y-coordinaat van de eerste balk: g.setcolor(color.blue); g.fillrect(0, y1+balk, iets, iets ); Omdat het vlak tegen de linkerkant aanligt, is de breedte van het vlak gelijk aan de x-positie van de eerste verticale balk. Zelfs de hoogte van het vlak kunnen we met een expressie aangeven: het is het verschil van de y-posities van de twee horizontale balken, minus één extra balkbreedte. Het vlak kan dus getekend worden met de methode-aanroep: g.fillrect(0, y1+balk, x1, y2-(y1+balk) ); Ook het rode vlak tegen de bovenrand kan op zo n manier beschreven worden. Operatoren In int-expressies kun je de volgende rekenkundige operatoren gebruiken:

29 3.4 Programma-layout 29 + optellen - aftrekken * vermenigvuldigen / delen % bepalen van de rest bij deling Voor vermenigvuldigen wordt een sterretje gebruikt, omdat de in de wiskunde gebruikelijke tekens ( of ) nou eenmaal niet op het toetsenbord zitten. Helemaal weglaten van de operator, zoals in de wiskunde ook wel wordt gedaan is niet toegestaan, omdat dat verwarring zou geven met meer-letterige variabelen. Bij gebruik van de delings-operator / wordt het resultaat afgerond, omdat het resultaat van een bewerking van twee int-waarden in Java weer een int-waarde oplevert. De afronding gebeurt door de cijfers achter de komma weg te laten; positieve waarden worden dus nooit naar boven afgerond (en negatieve waarden nooit naar beneden ). De uitkomst van de expressie 14/3 is dus 4. De bijzondere operator % geeft de rest die overblijft bij de deling. De uitkomst van 14%3 is bijvoorbeeld 2, en de uitkomst van 456%10 is 6. De uitkomst zal altijd liggen tussen 0 en de waarde rechts van de operator. De uitkomst is 0 als de deling precies op gaat. Prioriteit van operatoren Als er in één expressie meerdere operatoren voorkomen, dan geldt de gebruikelijke prioriteit van de operatoren: vermenigvuldigen gaat voor optellen. De uitkomst van 1+2*3 is dus 7, en niet 9. Optellen en aftrekken hebben onderling dezelfde prioriteit, en vermenigvuldigen en de twee delings-operatoren ook. Komen in een expressie operatoren van dezelfde prioriteit naaast elkaar voor, dan wordt de expressie van links naar rechts uitgerekend. De uitkomst van is dus 3, en niet 7. Als je wilt afwijken van deze twee prioriteitsregels, dan kun je haakjes gebruiken in een expressie, zoals in (1+2)*3 en 3+(6-5). In de praktijk komen in dit soort expressies natuurlijk variabelen voor, anders had je de waarde (9 en 4) meteen zelf wel kunnen uitrekenen. Een overbodig extra paar haakjes is niet verboden: 1+(2*3), en wat de compiler betreft mag je naar hartelust overdrijven: ((1)+(((2)*3))). Dat laatste maakt het programma er voor de menselijke lezer echter niet duidelijker op. 3.4 Programma-layout Commentaar Voor de menselijke lezer van een programma (een collega-programmeur, of jijzelf over een paar maanden, als je de details van de werking van het programma vergeten bent) is het heel nuttig als er wat toelichting bij het programma staat geschreven. Dit zogenaamde commentaar wordt door de compiler geheel genegeerd, maar zorgt ervoor dat het programma beter te begrijpen is. Er zijn in Java twee manieren om commentaar te markeren: alles tussen de tekencombinatie /* en de eerstvolgende teken-combinatie */ (mogelijk pas een paar regels verderop) alles tussen de tekencombinatie // en het einde van de regel Dingen waarbij het zinvol is om commentaar te zetten zijn: groepjes opdrachten die bij elkaar horen, methoden en de betekenis van de parameters daarvan, en complete klassen. Het is de kunst om in het commentaar niet de opdracht nog eens in woorden weer te geven; je mag er van uitgaan dat de lezer Java kent. In het voorbeeld-programma staat daarom bijvoorbeeld het commentaar en niet // posities van de lijnen x1 = 10; x2 = 50; // maak de variabele x1 gelijk aan 10, en x2 aan 50 x1 = 10; x2 = 50; Tijdens het testen van het programma kunnen de commentaar-tekens ook gebruikt worden om een of meerdere opdrachten tijdelijk uit te schakelen. Het staat echter niet zo verzorgd om dat soort uitgecommentarieerde opdrachten in het definitieve programma te laten staan.

30 30 Tekenen en rekenen Regel-indeling Er zijn geen voorschriften voor de verdeling van de tekst van een Java-programma over de regels van de file. Hoewel het gebruikelijk is om elke opdracht op een aparte regel te schrijven, worden hier door de compiler geen eisen aan gesteld. Als dat de overzichtelijkheid van het programma ten goede komt, kan een programmeur dus meerdere opdrachten op één regel schrijven (in het voorbeeldprogramma is dat gedaan met de relatief korte toekenningsopdrachten). Bij hele lange opdrachten (bijvoorbeeld methode-aanroepen met veel of ingewikkelde parameters) is het een goed idee om de tekst over meerdere regels te verspreiden. Verder is het een goed idee om af en toe een regel over te slaan: tussen verschillende methoden, en tussen groepjes opdrachten (en het bijbehorende commentaar) die bij elkaar horen. Witruimte Ook voor de plaatsing van spaties zijn er nauwelijks voorschriften. De enige plaats waar spaties vanzelfsprekend werkelijk van belang zijn, is tussen afzonderlijke woorden: public void paint mag niet worden geschreven als publicvoidpaint. Omgekeerd, midden in een woord mag geen extra spatie worden toegevoegd. In een tekst die letterlijk genomen wordt omdat er aanhalingstekens omheen staan, worden ook de spaties letterlijk genomen. Er is dus een verschil tussen en g.drawstring("hallo", 10, 10); g.drawstring("h a l l o ", 10, 10 ); Maar voor het overige zijn extra spaties overal toegestaan. Goede plaatsen om extra spaties te schrijven zijn: achter elke komma en puntkomma (maar niet ervoor) links en rechts van het = teken in een toekenningsopdracht aan het begin van regels, zodat de body van methoden en klassen wordt ingesprongen (4 posities is gebruikelijk) ten opzichte van de accolades die de body begrenzen. Opgaven 3.1 Declaratie, opdracht, expressie Wat is het verschil tussen een declaratie, een opdracht en een expressie? 3.2 Kleuren mengen Welke kleuren hebben de beeldpunten van een kleuren-tv? Welke kleuren hebben de flesjes inkt van een kleuren-printer? Kun je het verschil verklaren? 3.3 Engels jargon Het Engelse woord voor opdracht in een programmeertaal is statement. Dat woord is eigenlijk erg ongelukkig gekozen. Waarom? 3.4 Smiley Schrijf een applet dat een smiley op het scherm tekent: een grote cirkel, met twee kleine cirkels als ogen, en een cirkelboog als mond. Zoek in de help-pagina s van de klasse Graphics op hoe je een cirkelboog kunt tekenen. Gebruik variabelen in het programma, zodat je met het wijzigen van één toekenningsopdracht de afmeting van de smiley kunt aanpassen.

31 31 Hoofdstuk 4 Nieuwe methoden 4.1 Methode-definitie Orde in de chaos Als je een vierkant tekent met twee schuine lijntjes erbovenop heb je een simpel huisje getekend. Het voorbeeld-applet in dit hoofdstuk tekent drie huisjes. Het complete programma staat in listing 4; blz. 32 in figuur 6 is het resultaat te zien. Deze drie huisjes zouden getekend kunnen worden met de volgende paint-methode: public void paint(graphics g) { // kleine huisje links g.drawrect(20,60,40,40); g.drawline(20,60,40,40); g.drawline(40,40,60,60); // kleine huisje midden g.drawrect(70,60,40,40); g.drawline(70,60,90,40); g.drawline(90,40,110,60); // grote huis rechts g.drawrect(120,40,60,60); g.drawline(120,40,150,10); g.drawline(150,10,180,40); Ondanks het commentaar begint dit nogal onoverzichtelijk te worden. Wat zou je bijvoorbeeld in dit programma moeten veranderen als bij nader inzien niet het rechter, maar juist het linker huis groot getekend moet worden? Om het programma op die manier aan te passen zou je alle parameters van alle opdrachten weer moeten napuzzelen, en als je dat niet nauwkeurig doet loop je een goede kans dat in de nieuwe versie van het programma een van de daken in de lucht getekend wordt. En dan is dit nog maar een programma dat drie huisjes tekent; dit programma uitbreiden zodat het niet drie maar tien huisjes tekent is ronduit vervelend. We gaan wat orde scheppen in deze chaos met behulp van methoden. Nieuwe methoden Methoden zijn bedoeld om groepjes opdrachten die bij elkaar horen als één geheel te kunnen behandelen. Op het moment dat het groepje opdrachten moet worden uitgevoerd, kun je de dat laten gebeuren door de methode aan te roepen. In het voorbeeld horen duidelijk steeds drie opdrachten bij elkaar die samen één huisje tekenen (de aanroep van drawrect en de twee aanroepen van drawline). Die drie opdrachten zijn dus een goede kandidaat om in een methode te zetten; in de methode paint komen dan alleen nog maar drie aanroepen van deze nieuwe methode te staan. De opzet van het programma wordt dus als volgt:

32 32 Nieuwe methoden 5 import java.awt.graphics; import java.applet.applet; // Deze applet tekent drie huizen van divers formaat public class Huizen extends Applet { // Deze methode tekent een huis // met linkeronderhoek (x,y) en breedte br 10 private void tekenhuis(graphics gr, int x, int y, int br) { int topx, topy; topx = x + br/2; topy = y - 3*br / 2; 15 gr.drawrect(x, y-br, br, br); gr.drawline(x, y-br, topx, topy); gr.drawline(topx, topy, x+br, y-br); 20 public void paint(graphics g) { this.tekenhuis(g, 20, 100, 40); this.tekenhuis(g, 70, 100, 40); this.tekenhuis(g, 120, 100, 60); 25 Listing 4: Huizen/Huizen.java Figuur 6: De applet Huizen in werking

33 4.1 Methode-definitie 33 public class Huizen extends Applet { private void tekenhuis( iets ) { iets.drawrect( iets ); iets.drawline( iets ); iets.drawline( iets ); public void paint(graphics g) { iets.tekenhuis( iets ); iets.tekenhuis( iets ); iets.tekenhuis( iets ); Er zijn dus twee methoden: naast de gebruikelijke methode paint is er een tweede methode die één huis tekent, en die we daarom tekenhuis noemen. De naam mag vrij worden gekozen, en het is een goed idee om die naam de taak van de methode een beetje te laten beschrijven. Als de browser het applet wil tekenen, roept hij de methode paint aan. Het maakt niet uit dat deze methode niet aan het begin van het programma staat: wat er verder ook allemaal in de klasse zit, de methode paint zal als eerste worden aangeroepen. Pas als de methode paint een aanroep doet van de methode tekenhuis, worden de opdrachten in de body van de methode tekenhuis uitgevoerd. Als dat klaar is, gaat paint weer verder met de volgende opdracht. In dit geval is dat toevallig weer een aanroep van tekenhuis, dus wordt er een tweede huis getekend. Ook bij de derde aanroep in paint wordt er een huisje getekend, en als daarna gaat het weer verder op de plaats van waaruit paint zelf werd aangeroepen. Methoden nemen een object onder handen Het raamwerk van het programma is nu klaar, maar er zijn nog de nodige details die ingevuld moeten worden (in het raamwerk aangegeven met iets). Als eerste bekijken we de vraag: wat komt er vóór de punt te staan bij de aanroep van de methode drawrect in de body van tekenhuis? Elke methode die je aanroept, krijgt een object onder handen ; dit is het object dat je voor de punt in de methode-aanroep aangeeft. De methode drawrect bijvoorbeeld, krijgt een Graphics-object onder handen. Tot nu toe hebben we daar het Graphics-object voor gebruikt, dat als parameter beschikbaar was in de methode paint (en dat we steeds g noemden). De parameter van de methode paint is echter niet zomaar beschikbaar in de body van de methode tekenhuis. Parameters van methoden We moeten er dus voor zorgen dat ook in de body van tekenhuis een Graphics-object beschikbaar is, en dat kunnen we doen door ook tekenhuis een Graphics-object als parameter te geven. In de body van tekenhuis kunnen we die parameter dan mooi gebruiken voor de punt in de aanroep van drawrect en drawline: private void tekenhuis(graphics gr, iets ) { gr.drawrect( iets ); gr.drawline( iets ); gr.drawline( iets ); Voor de afwisseling hebben we de parameter nu eens gr genoemd in plaats van g. Je mag als programmeur de naam van de parameter immers vrij kiezen. In de body van de methode moet je, als je de parameter wilt gebruiken, wel diezelfde naam gebruiken, dus ook daar gebruiken we nu het Graphics-object gr. De naam van het type van de parameter mag je niet zomaar kiezen: het object-type Graphics is een bestaande bibliotheek-klasse, en die mogen we niet ineens Grafiek of iets dergelijks gaan noemen. Nu we in de header van de methode tekenhuis gespecificeerd hebben dat de eerste parameter een Graphics-object is, moeten we er voor zorgen dat bij aanroep van tekenhuis ook inderdaad een Graphics-object wordt meegegeven. De aanroep van tekenhuis vindt plaats vanuit de methode paint, en daar hebben we gelukkig een Graphics-object beschikbaar: het object dat door de browser als parameter aan paint is meegegeven. De aanroepen van tekenhuis komen er dus als

34 34 Nieuwe methoden volgt uit te zien: public void paint(graphics g) { iets.tekenhuis(g, iets ); iets.tekenhuis(g, iets ); iets.tekenhuis(g, iets ); De methode tekenhuis wordt alleen maar door paint aangeroepen, en niet door de browser (althans niet direct). De methode tekenhuis is daarom als een private methode gedeclareerd: hij is alleen voor intern gebruik. Let er op dat er één Graphics-object is, dat door de verschillende methoden verschillend wordt genoemd: in paint heet het object g, en in tekenhuis heet het gr. En waarschijnlijk noemde de browser dit object, alvorens het aan paint mee te geven, weer anders. Of misschien ook wel niet het punt is dat dat er niet toe doet. Hoe methoden hun eigen parameters noemen is hun zaak, en degene die de methode aanroept hoeft niet te weten wat die naam is. De aanroeper heeft er alleen voor te zorgen dat op de betreffende parameterplaats een waarde van het juiste type wordt meegegeven. Het object this Een volgend detail dat we nog moeten invullen in het programma is het object vóór de punt bij de aanroep van tekenhuis. Welk object krijgt tekenhuis eigenlijk onder handen? En welk object heeft paint zelf eigenlijk onder handen? Het is in ieder geval niet dat Graphics-object dat in de vorige paragraaf centraal stond: Graphicsobjecten kennen immers alleen maar draw... - en fill... -methoden, en geen methode paint en al zeker niet een methode tekenhuis. Het object dat door methoden onder handen wordt genomen, is van het object-type zoals dat in de klasse-header staat waarin de methode staat. De methode drawrect heeft een Graphics-object onder handen, omdat drawrect in de klasse Graphics staat. Welnu, de methoden paint en tekenhuis staan in de klasse Huizen, en hebben dus blijkbaar een Huizen-object onder handen. Zo n Huizen-object is door de browser gecreëerd, waarna de methode paint werd aangeroepen met dat object onder handen. In de body van paint zouden we datzelfde object wel willen gebruiken om door tekenhuis onder handen genomen te laten worden. Maar hoe moeten we het object dat we onder handen hebben, aanduiden? Dit object is immers geen parameter, dus we hebben het in de methode-header geen naam kunnen geven. De oplossing van dit probleem is dat in Java het object dat een methode onder handen heeft gekregen, kan worden aangeduid met het woord this. Dit woord kan dus worden geschreven op elke plaats waar het object nodig is. Nu komt het dus goed van pas om in de body van de methode paint aan te geven dat bij de aanroep van tekenhuis hetzelfde object onder handen genomen moet worden als dat paint zelf al onder handen heeft: public void paint(graphics g) { this.tekenhuis(g, iets ); this.tekenhuis(g, iets ); this.tekenhuis(g, iets ); Het woord this is in Java een voor dit speciale doel gereserveerd woord. Je mag het dus (evenmin als class, extends, void, public, private en int) gebruiken als naam van een variabele of iets dergelijks. In elke methode duidt this een object aan. Dit object heeft als object-type dat wat in de header van de klasse staat waarin de methode is gedefinieerd. 4.2 Op zoek naar parameters Parameters maken methoden flexibeler Het administratieve werk zorgen dat alle methoden over de benodigde Graphics- en Huizenobject kunnen beschikken is nu gedaan, en het leuke werk kan beginnen: de jacht op de overige parameters. Tot nu toe hebben we voor het gemak gezegd dat de huis-tekenende opdrachten (drawrect en tweemaal drawline) in alle drie gevallen hetzelfde is, en dat ze daarom met drie aanroepen van

35 4.2 Op zoek naar parameters 35 tekenhuis kunnen worden uitgevoerd. Maar de opdrachten die de drie huizen tekenen zijn niet precies hetzelfde: per huisje verschillen de getallen die als parameter worden meegegeven aan drawrect en drawline. We kijken eerst maar eens naar de aanroepen van drawrect in de oorspronkelijke (chaotische) versie van het programma: g.drawrect( 20, 60, 40, 40); g.drawrect( 70, 60, 40, 40); g.drawrect(120, 40, 60, 60); De eerste twee getallen zijn de coördinaten van de linkerbovenhoek van de rechthoek, de laatste twee getallen zijn de breedte en hoogte van de rechthoek. Omdat we vierkanten tekenen zijn de breedte en hoogte gelijk: 40 voor de kleine huisjes, en 60 voor het grote. De breedte (tevens hoogte) is niet in alle gevallen dezelfde. Als we de gewenste breedte echter door een parameter aangeven, dan kunnen we bij elke aanroep een andere breedte specificeren. Wat betreft de coördinaten geldt hetzelfde: aangezien deze verschillend zijn bij alle drie de aanroepen, laten we de aanroeper van tekenhuis ook deze waarden specificeren. Voor de aanroeper is het waarschijnlijk gemakkelijker om de coördinaten van de linker-onderhoek te specificeren: de coördinaten van de bovenhoek zijn verschillend voor huizen van verschillende grootte, terwijl de y-coördinaat van de onderhoek voor huizen op één rij hetzelfde zijn. Ook dit kan geregeld worden: we spreken af dat de y-coördinaat-parameter van de methode tekenhuis de basislijn van de huizen voorstelt, en de y-coördinaat van de bovenhoek berekenen we met een expressie: private void tekenhuis(graphics gr, int x, int y, int br) { gr.drawrect(x, y-br, br, br); gr.drawline( iets ); gr.drawline( iets ); public void paint(graphics g) { this.tekenhuis(g, 20, 100, 40); this.tekenhuis(g, 70, 100, 40); this.tekenhuis(g, 120, 100, 60); De parameters van de twee aanroepen van drawline (de coördinaten van begin- en eindpunt van de lijnen die het dak van het huis vormen) zijn ook in alle gevallen verschillend. Het is echter niet nodig om die apart als parameter aan tekenhuis mee te geven; deze coördinaten kunnen namelijk worden berekend uit de positie en de breedte van het vierkant, en die hebben we al als parameter. De coördinaten van de top van het dak zijn twee maal nodig: als het eindpunt van de eerste lijn, en als beginpunt van de tweede. Om de berekening van dit punt niet twee maal te hoeven doen, gebruiken we twee variabelen om deze coördinaten tijdelijk op te slaan. Deze variabelen zijn nodig in de methode tekenhuis, en worden dan ook in die methode gedeclareerd: private void tekenhuis(graphics gr, int x, int y, int br) { int topx, topy; topx = x + br/2; topy = y - 3*br / 2; gr.drawrect(x, y-br, br, br); gr.drawline(x, y-br, topx, topy); gr.drawline(topx, topy, x+br, y-br); Grenzen aan de flexibiliteit Nu we besloten hebben om de linkeronderhoek van het huisje te specificeren (en niet de linkerbovenhoek van de gevel), blijkt de y-coördinaat in alle drie de aanroepen van tekenhuis hetzelfde te zijn (namelijk 100). Achteraf gezien was deze parameter dus niet nodig geweest: we hadden de waarde 100 in de body van tekenhuis kunnen schrijven op alle plaatsen waar nu een y staat. Kwaad kan het echter ook niet om te veel parameters te gebruiken. Wie weet willen we later nog wel eens huisjes tekenen op een andere hoogte dan 100, en dan is onze methode er alvast maar op voorbereid. De vraag is wel hoe ver je moet gaan in het flexibeler maken van methoden, door het toevoegen van extra parameters. De methode tekenhuis zoals we die nu hebben geschreven kan alleen maar

36 36 Nieuwe methoden huisjes met een vierkante gevel tekenen. Het is ook denkbaar om de breedte en de hoogte apart als parameter mee te geven, want wie weet willen we later nog wel eens een niet-vierkant huisje tekenen, en dan is de methode er alvast maar op voorbereid. En je zou de hoogte van het dak apart als parameter mee kunnen geven, want wie weet willen we later nog wel eens een huisje met een extra schuin of extra plat dak tekenen. En je zou nog een kleur-object apart als parameter kunnen meegeven, want wie weet willen we later nog wel eens een gekleurd huisje tekenen. Of twee kleur-objecten, zodat het dak een andere kleur kan krijgen dan de gevel... Al die extra parameters hebben wel een prijs, want bij de aanroep moeten ze steeds maar meegegeven worden. En als de aanroeper helemaal niet van plan is om al die variatie te gaan gebruiken, zijn die overbodige parameters maar tot last. De kunst is om een afweging te maken tussen de moeite die het kost om extra parameters te gebruiken (zowel voor de programmeur van de methode als voor de programmeur die de aanroepen schrijft) en de kans dat de extra flexibiliteit in de toekomst ooit nodig zal zijn. Flexibiliteit in het groot Hetzelfde dilemma doet zich voor bij programma s als geheel. Gebruikers willen graag flexibele software, die ze naar hun eigen wensen kunnen configureren. Maar ze zijn weer ontevreden als ze eindeloze lijsten met opties moeten instellen voordat ze aan het werk kunnen, en onnodige opties maken een programma maar complex en (daardoor) duur. Achteraf heb je makkelijk praten, maar had men in het verleden kunnen voorzien dat er ooit behoefte zou ontstaan aan een 4-cijferig jaartal in plaats van een 2-cijferig? (Ja.) Maar moeten we er nu al rekening mee houden dat in de toekomst de jaarkalender misschien een dertiende maand krijgt, en alle maanden 28 dagen? (Nou, nee). Moet de gebruiker van financiële software zelf kunnen instellen wat het geldende BTW-tarief is? Of moet de gebruiker, als het tarief ooit zal veranderen, maar een nieuwe versie van het programma kopen? En moet de software er nu al in voorzien dat er behalve een laag en een hoog BTW-tarief ook een midden-tarief komt? En dat de munteenheid verandert? En het symbool daarvoor? Moet de gebruiker van een programma waarin tijden een rol spelen zelf kunnen instellen op welke datum de zomertijd eindigt? Of is het beter als de regel daarvoor ( laatste zondag van oktober ) in het programma is ingebouwd? En als de regel dan veranderd wordt? Of moet de gebruiker zelf de regel kunnen specificeren? En mag hij dan eerst kiezen in welke taal hij oktober mag spellen? 4.3 Methoden met een resultaat Numerieke typen Een variabele met het type int kan alleen maar gehele getallen bevatten. Om getallen met cijfers achter de komma te kunnen opslaan in een variabele, moet je een ander type gebruiken in de declaratie. Meestal wordt daarvoor het type double gebruikt (een afkorting van double precision floating point number ). Na de declaratie double d; Kun je de variabele een waarde geven met een toekenningsopdracht d = ; Overeenkomstig de angelsaksische gewoonte wordt in dit soort getallen een decimale punt gebruikt, en dus niet zoals in Nederland een decimale komma. Hoewel je in een double variabele dus ook gehele getallen kunt opslaan, is het toch zinvol om het onderscheid met int te maken, en daar waar je zeker weet dat er alleen maar gehele getallen nodig zijn een int te declareren. De voordelen daarvan zijn: een int kost minder opslagruimte dan een double berekeningen met een int gaan (iets) sneller dan met een double waarden worden altijd exact opgeslagen door het gebruik van een passend type documenteer je impliciet de bedoeling die je als programmeur met een bepaalde variabele hebt. Berekenen van functies Door middel van parameters kan de methode-aanroeper waarden (int-waarden, double-waarden, en zelfs waarden met een object-type zoals Graphics) doorspelen aan een methode, zodat die

37 4.3 Methoden met een resultaat 37 waarden in de body van de methode gebruikt kunnen worden. Dit is echter éénrichtingverkeer: de methode kan niets terugzeggen tegen de aanroeper. Toch is dat vaak wel gewenst. Vergelijk het maar met het berekenen van functies in de wiskunde. Je kunt een functie, zoals de functie kwadraat, als het ware aanroepen door hem van een parameter te voorzien: kwadraat(5). Zo n functie berekent een resultaat; in dit geval is dat 25. Dat antwoord is beschikbaar voor degene die de functie aanroept. In Java kun je methoden, net als wiskundige functies, ook een resultaat laten berekenen, dat door de aanroeper kan worden gebruikt. En net als in de wiskundige functies kan een methode meerdere parameters krijgen, maar heeft hij maar één resultaat. Zo n resultaatwaarde kan een int-waarde zijn, maar je kunt methoden ook een object-waarde laten teruggeven aan de aanroeper. Het resultaattype van een methode Net als de parameters, heeft de resultaat-waarde van een methode een type. Dat type kan int zijn als je een geheel getal wilt teruggeven, maar ook bijvoorbeeld het object-type Color, als je de methode een kleur wilt laten teruggeven. Het resultaattype van de methode moet in de header van de methode worden gespecificeerd, direct vóór de naam van de methode. De header van de kwadraat-methode kan er dus zo uitzien: private double kwadraat(double x) Als de methode geen resultaat heeft, staat op de plaats van het resultaattype het woord void. De return-opdracht In de body van een methode met een resultaat moet op de een of andere manier worden aangegeven wat het resultaat dan wel is. Voor dit doel is er in Java een speciale opdracht beschikbaar: de return-opdracht. In de body van de kwadraat-methode staat als enige opdracht zo n returnopdracht: private double kwadraat(double x) { return x*x; Een return-opdracht bestaat uit het, speciaal voor dit doel gereserveerde, woord return, gevolgd door een expressie. Net als de toekennings-opdracht en de methode-aanroep wordt de returnopdracht afgesloten met een puntkomma. Op het moment dat de return-opdracht wordt uitgevoerd, wordt de expressie uitgerekend, gebruik makend van de op dat moment geldende waarden van variabelen en parameters. Die waarde wordt dan als resultaatwaarde opgeleverd aan de aanroeper van de methode. Aanroep van methoden Methoden met een resultaatwaarde moeten op een andere manier worden aangeroepen dan methoden met void als resultaat-type; de resultaatwaarde die door de return-opdracht in de methode wordt teruggegeven moet immers op de een of andere manier worden geaccepteerd. Aanroepen van void-methoden hebben de status van een zelfstandige opdracht. Bijvoorbeeld: g.drawstring("hallo", 10, 10); Aanroep van een niet-void-methode heeft echter de status van een expressie. Die expressie staat niet op zichzelf, maar kan gebruikt worden in een opdracht naar keuze, bijvoorbeeld als rechterhelft van een toekenningsopdracht: double k; k = this.kwadraat(5); Maar de aanroep van de methode kwadraat kan ook op andere plaatsen worden gebruikt waar double-waarden nodig zijn, bijvoorbeeld als onderdeel van een grotere expressie, of als parameter van een andere aanroep: double oppervlakte, vierdemacht; oppervlakte = * this.kwadraat(5); vierdemacht = this.kwadraat( this.kwadraat(5) ); Uiteindelijk zal je die grotere expressie dan toch weer moeten gebruiken in een opdracht (hetzij een toekenningsopdracht, hetzij een aanroep van een void-methode).

38 38 Nieuwe methoden import java.awt.graphics; import java.applet.applet; public class Opper extends Applet 5 { private double kwadraat(double x) { return x*x; private double opper(double r) { return * this.kwadraat(r); public void paint(graphics g) { g.drawstring("straal", 2, 10 ); 20 g.drawstring("oppervlakte", 60, 10 ); g.drawstring("25 " + this.opper(25), 10,35); g.drawstring("37 " + this.opper(37), 10,55); g.drawstring("50 " + this.opper(50), 10,75); 25 g.drawstring("99 " + this.opper(99), 10,95); 30 g.drawline(0, 15, 170, 15); g.drawline(40, 0, 40, 100); Listing 5: Opper/Opper.java blz. 38 Methodes die elkaar aanroepen Methodes kunnen elkaar aanroepen. De methode kwadraat bijvoorbeeld, komt goed van pas in de methode die de oppervlakte van een cirkel kan uitrekenen: private double opper(double x) { return * this.kwadraat(x); Omdat de methoden opper en kwadraat zich in dezelfde klasse bevinden, kunnen ze elkaar aanroepen met this als betrokken object. Geen van beide methoden zet een resultaat op het scherm. Dat kunnen ze niet eens, want ze hebben niet de beschikking over een Graphics-object. Het opleveren van een resultaatwaarde betekent nog niet automatisch dat die waarde op het scherm verschijnt! Dat moet expliciet gebeuren, bijvoorbeeld door in de methode paint het resultaat van de methode op het scherm te tekenen: public void paint(graphics g) { g.drawstring("oppervlakte: " + this.opper(5) ); In listing 5 en figuur 7 staat het programma Opper, dat een tabelletje tekent met de straal en de bijbehorende oppervlaktes van een aantal cirkels. De berekening wordt gedaan met behulp van de extra methoden kwadraat en opper.

39 4.3 Methoden met een resultaat 39 Figuur 7: De applet Opper in werking De plaats van de return-opdracht Voorafgaand aan de return-opdracht kunnen nog andere opdrachten staan, die wat voorbereidende berekeningen uitvoeren. Onderstaande methode berekent bij een parameter x de waarde van (x + 1) 3 : private int derdemachtvanopvolger(int x) { int v; v = x+1; return v*v*v; Een return-opdracht zou in theorie ook halverwege een methode kunnen staan, en je zou zelfs twee return-opdrachten in één methode kunnen zetten: private int raar(int x) { return x*x; return x*x*x; Maar wat levert deze methode nou op: het kwadraat of de derdemacht van de parameter? Bij de eerste return-opdracht wordt de waarde uitgerekend en teruggegeven aan de aanroeper, en daarmee is de methode-aanroep ook meteen beëindigd. Aan het uitrekenen van de derdemacht komt deze methode raar helemaal nooit toe. Hoewel het dus in principe is toegestaan om een return-opdracht op een andere plaats dan als laatste in een methode te zetten, is dat in de praktijk zinloos: opdrachten na de return-opdracht kunnen nooit worden uitgevoerd. De compiler zal in zo n geval dan ook waarschuwen dat de methode unreachable code bevat. Het woord return kun je op twee manieren vertalen: teruggeven : de waarde van de expressie in de return-opdracht wordt teruggegeven aan de aanroeper; terugkeren : na het uitvoeren van de return-opdracht keert de processor terug naar de aanroeper, ongeacht of er nog meer opdrachten staan na de return-opdracht. Beide vertalingen zijn een adequate beschrijving van de return-opdracht. Opgaven 4.1 Vermenigvuldigen en delen Is er verschil tussen de opdrachten (uit het programma Huizen): topy = y - 3*br / 2; topy = y - 3/2 * br; topy = y - br/2 * 3; 4.2 Uren, minuten, seconden Stel dat de variabele tijd een (mogelijk groot) aantal seconden bevat. Schrijf een aantal opdrach-

40 40 Nieuwe methoden ten, waardoor de variabelen uren, minuten en seconden een daarmee overeenkomende waarde krijgen, waarbij de waarden van minuten en seconden kleiner dan 60 moeten zijn. Schrijf bovendien een methode die, het omgekeerde probleem, uit drie parameters uren, minuten en seconden de totale hoeveelheid seconden berekent. 4.3 Methoden en parameters Schrijf, gebruikmakend van de methode tekenhuis, een methode tekenstraat, die vier huizen naast elkaar tekent. Wat zijn geschikte parameters voor tekenstraat? 4.4 Expressie versus opdracht Wat is het verschil tussen een expressie en een opdracht? De aanroep van een methode vormt soms een expressie, en soms een opdracht; waardoor wordt dit onderscheid gemaakt? 4.5 Schubert In figuur 8 is het beginstuk weergegeven van de melodie van een muziekstuk (het Impromptu opus 90 nummer 4 van Schubert). Schrik niet, je hoeft geen noten te kunnen lezen om deze opgave te maken; we leggen het hier even uit (als informaticus moet je je tenslotte snel kunnen inwerken in de eigenaardigheden en bijzondere notaties van een klant): Elke noot heeft een toonhoogte; hoe hoger hij in de balk staat, hoe hoger de noot klinkt. Voor het gemak van de niet-musici staat er een getal bij de noten geschreven: hoe groter, hoe hoger. Niet alle noten duren even lang. Noten met een dubbele dwarsbalk (de groepjes van 4) duren ieder 1 tijdseenheid; noten met een enkele vlag er aan (bijvoorbeeld nummer 70 op de eerste regel) duren twee tijdseenheden, en noten met alleen een verticale streep eraan (bijvoorbeeld de zes halverwege de tweede regel) duren 4 tijdseenheden. Het speciale symbool 0 op de eerste regel is een stilte van twee tijdseenheden. Voor de leesbaarheid staat er na elke 12 tijdseenheden (een maat) een verticale streep in de notenbalk. Veronderstel dat we in een klasse zitten, waar een methode play bestaat met twee parameters: een getal dat de toonhoogte voorstelt, en een getal dat de lengte voorstelt. We zouden nu een nieuwe methode schubert kunnen schrijven, die het hele muziekstuk afspeelt: public void schubert( ) { this.play(83,1); this.play(87,1); this.play(83,1); this.play(80,1); this.play(80,1); this.play(83,1); en zo verder alle andere 300 noten in dit fragment. Maar dat wordt wel een erg saai programma. Het kan handiger, door gebruik te maken van extra methoden. Stukken die twee keer voorkomen kun je in een methode onderbrengen, en twee keer aanroepen. Maar door handig gebruik te maken van parameters, kun je ook stukken die niet exact hetzelfde zijn, in één methode onderbrengen, en aanroepen met verschillende parameters. De opgave luidt nu: schrijf een programma dat alle 306 noten afspeelt, waarbij je zo min mogelijk aparte aanroepen van play verwerkt. Buit dus alle aanwezige structuur uit in extra methoden: de regelmaat van groepjes van 4, maar ook de grotere structuren van 2 of 6 maten. In de practicum-directory met voorbeeldprogramma s staat een programma MidiMaker, dat inderdaad zo n methode play bevat. Er zit ook een methode score in, die de aanroepen naar play bevat (vergelijk paint in een gewone applet). Je kunt deze methode score vervangen door je eigen methode, om deze opgave te testen. Als je het programma runt, wordt de muziek afgespeeld. 4.6 Fractale geometrie Tekenen met behulp van Graphics is wel handig, maar soms is het een nadeel dat je alle coordinaten van de lijnen die je wilt tekenen als precieze waarde moet opgeven. Soms is het handiger om de tekening relatief te specificeren, dus niet trek een lijn van A naar B, maar trek een lijn door vanaf het huidige punt S stappen naar voren te gaan. Om ook andere dan rechte lijnen te kunnen trekken heb je dan ook nodig: draai je neus G graden rond. Om ook niet-aaneengesloten figuren

41 4.3 Methoden met een resultaat 41 Figuur 8: Schubert, Impromptu op.90 nr.4

42 42 Nieuwe methoden te kunnen tekenen heb je verder nog nodig: zet de pen neer en trek de pen op. Tekenen op deze manier staat bekend als turtle graphics, omdat je als het ware een schildpad opdrachten kunt geven om te lopen, te draaien en een aan zijn schild bevestigde pen neer te zetten. In de practicum-directory staat een klasse TurtleApplet die het makkelijk maakt om zo n turtletekening te maken. Om hem te gebruiken hoef je (nog) niet precies te begrijpen wat er in deze klasse gebeurt. Hoofdzaak is dat er een methode makedrawing is, die door jou moet worden ingevuld. Het is de bedoeling dat er in die methode aanroepen komen te staan naar de andere methoden die er in de klasse aanwezig zijn: move, turn, en penup en pendown. Je zou bijvoorbeeld een vierkant kunnen tekenen door eerst this.move(100); aan te roepen, dan this.turn(90);, en dat vier keer te herhalen. a. Maak een nieuw project aan, en kopieer de tekst van TurtleApplet in plaats van de Hallo-tekst die de JCreator-wizard voor je aan heeft gemaakt. Vul de methode makedrawing in zodat er een vierkant wordt getekend. Compileer en run het programma. Nog chiquer is het om een tweede Java-file te maken, die op zijn beurt een extensie is van de klasse TurtleApplet. Daarin komt dan alleen de methode makedrawing te staan, plus eventuele extra methoden die je nog gaat schrijven: public class MijnTurtle extends TurtleApplet { public void makedrawing() { // to be defined b. Maak een nieuwe methode vierkant die een vierkant tekent. Roep de methode aan vanuit makedrawing. Maak een tweede methode driehoek, en roep m aan. Experimenteer ook wat met andere aantallen lijnen en hoeken bij de aanroep van turn. c. Schrijf een methode koch met als parameter een lengte (ter ere van de wiskundige Helge von Koch, die in 1904 deze constructie bedacht). Laat m een lijn tekenen van die lengte, maar dan zodanig dat het middelste een-derde deel van die lijn ontbreekt, en wordt vervangen door twee lijnstukjes die een omtrekkende beweging maken, zoals in de figuur hier linksonder: d. Schrijf nu een methode koch2, met weer dezelfde parameter. Deze methode doet vrijwel hetzelfde als koch, echter waar koch de methode move aanroept, roept koch2 de methode koch aan; het resultaat is de figuur hier rechtsboven e. Schrijf ook een methode koch3, die viermaal koch2 aanroept, een methode koch4 die viermaal koch3 aanroept, en koch5 die viermaal koch4 aanroept. Test de methode, door ze vanuit makedrawing alledrie aan te roepen met gebruikmaking van verschillende kleuren. f. Schrijf een methode vierkant5, die voor zijn vier zijden in plaats van move de methode koch5 aanroept. g. Maak een nieuwe versie van de koch-familie, met een extra parameter hoek. Deze parameter geeft aan over welke hoek er gedraaid wordt tussen de lijnsegmenten. Ook de waarde waardoor de lengte van het totaal gedeeld moet worden om de lengte van een van de lijnsegmenten te krijgen (in de vorige versie was dat dus 3) kan gevarieerd worden. De beste resultaten krijg je echter door te delen door 2 (1 + cos(hoek)). Let op dat de hoek hier niet in graden maar in radialen moet worden opgegeven. h. Maak een kerstkaart, door de methode vierkant5 aan te roepen, in verschillende kleuren, met verschillende waarden van hoek.

43 43 Hoofdstuk 5 Objecten en methoden 5.1 Variabelen Programmeren is groeperen Om in grote en ingewikkelde programma s het overzicht te kunnen blijven behouden is het belangrijk om dingen die bij elkaar horen te groeperen en als één geheel te beschouwen. We hebben daar al een aantal voorbeelden van gezien: Een groepje opdrachten die bij elkaar horen vormen een methode. Een aantal methoden die bij elkaar horen vormen een klasse. Een aantal klassen die bij elkaar horen zijn in de bibliotheek van standaardklassen ondergebracht in een package. Ook bij het werken met de JCreator programmeeromgeving wordt er op allerlei nivo s gegroepeerd: De files met broncode van zelf-geschreven klassen, en de bijbehorende html-files die voor een bepaald programma nodig zijn vormen een project. Meerdere projecten waar je tegelijk aan bezig bent zijn ondergebracht in een workspace. Java is een object-georiënteerde programmeertaal. Natuurlijk spelen in zo n taal objecten een belangrijke rol. Ook objecten zijn het resultaat van groepering van samenhangende zaken: een object is een groepje variabelen dat bij elkaar hoort. Object: groepje variabelen dat bij elkaar hoort In sectie 3.2 zijn variabelen geïntroduceerd: een variabele is een geheugenplaats met een naam, die je kunt veranderen met een toekenningsopdracht. Een variabele x kan bijvoorbeeld op een bepaald moment de waarde 7 bevatten, en een tijdje later de waarde 12. In veel situaties is het handig om meerdere variabelen te groeperen en als één geheel te behandelen. Bijvoorbeeld, met twee variabelen, laten we zeggen x en y, kun je de positie van een punt in het platte vlak beschrijven. Die twee variabelen zou je dan samen als één positie-object kunnen beschouwen. Met drie getallen kun je een kleur beschrijven: de hoeveelheid rood, groen en blauw licht die in de kleur gemengd zijn. Drie variabelen die ieder een getal bevatten zijn dus samen als één kleur-object te beschouwen. Voor het beschrijven van complexere zaken zijn veel meer variabelen nodig. Voor het beheer van een window op het scherm zijn variabelen nodig om de positie van het window te bepalen, de afmetingen, misschien de achtergrondkleur en de naam dit in de titelbalk staat, het aanwezig zijn van scrollers en als die er zijn de positie daarvan, om nog maar niet te spreken van de inhoud van het window. Het is duidelijk dat het erg gemakkelijk is om in een programma een window in z n geheel te kunnen manipuleren, in plaats van steeds opnieuw met al die losse variabelen te worden geconfronteerd. Het is lang niet altijd nodig om precies te weten uit welke variabelen een bepaald object is opgebouwd. Het kan handig zijn om je er ongeveer een voorstelling van te maken, maar strikt noodzakelijk is dat niet. Om je een voorstelling te maken van een kleur-object kun je aan een groepje van drie variabelen denken, maar ook zonder die kennis zou je een kleur-object kunnen manipuleren. We hebben dat in hoofdstuk 3 gedaan: door het meegeven van een Color-object aan blz. 23 de methode setcolor werd de kleur van de vlakken in het Mondriaan-schilderij bepaald. Dat kan, zonder dat we hoefden te weten dat een kleur-object in feite een groepje van drie variabelen is. Het is eerder regel dan uitzondering dat je niet precies weet hoe een object is opgebouwd. In programma s worden windows, buttons, files en allerlei andere objecten gebruikt, zonder dat de

44 44 Objecten en methoden blz. 94 programmeur de opbouw van die objecten in detail kent. Die details worden (gelukkig) afgeschermd in de bibliotheek-klasssen. Van de meeste standaard-objecten (windows, buttons enz.) is het zelfs zo dat je de opbouw niet te weten kan komen, zelfs als je dat uit nieuwsgierigheid zou willen. Dat is geen pesterij: de opbouw van objecten wordt geheim gehouden om de auteur van de standaard-bibliotheek de vrijheid te geven om in de toekomst een andere opbouw te kiezen (bijvoorbeeld omdat die efficiënter is), zonder dat bestaande programma s daaronder te leiden hebben. Als je zelf nieuwe object-typen samenstelt (we gaan dat doen in hoofdstuk 9) dan moet je natuurlijk wel weten hoe zo n object is opgebouwd. Maar zelfs dan kan het een goed idee zijn om dat zo snel mogelijk weer te vergeten, en je eigen objecten waar mogelijk als ondeelbaar geheel te behandelen. Voorlopig gebruiken we alleen standaardobjecten, zoals kleur-objecten en tekst-objecten. Om die te kunnen manipuleren is het nodig om eerst iets meer te weten over declaraties. Declaratie: aangifte van het type van een variabele In sectie 3.2 hebben we gezien dat je de variabelen die je in je programma wilt gebruiken moet declareren. Dat gebeurt door middel van een zogeheten declaratie, waarin je de namen van de variabelen opsomt en hun type aangeeft. Een voorbeeld van een declaratie is int x, y; Je maakt daarmee ruimte in het geheugen voor twee variabelen, genaamd x en y, en geeft aan dat het type daarvan int is. Het type int staat voor integer number, oftewel geheel getal. Je kunt je de situatie in het geheugen als volgt voorstellen: De geheugenplaatsen zijn beschikbaar (in de tekening gesymboliseerd door het hok), maar ze hebben nog geen waarde. Een variabele krijgt een waarde door middel van een toekennigsopdracht, zoals x = 20; De situatie in het geheugen wordt daarmee: Met een tweede toekenningsopdracht kan ook aan de variabele y een waarde worden toegekend. In de expressie aan de rechterkant van het =-teken kan de variabele x worden gebruikt, omdat die inmiddels een waarde heeft. Bijvoorbeeld: y = x+5; Na het uitvoeren van deze opdracht is de situatie als volgt: Het kan gebeuren dat later een andere waarde aan een variabele wordt toegekend, bijvoorbeeld met x = y*2; De variabele x krijgt daarmee een nieuwe waarde, en de oude waarde gaat voor altijd verloren. De situatie die daardoor ontstaat is als volgt: Merk op dat met toekenningsopdrachten de waarde van een variabele kan veranderen. De naam wordt echter met de declaratie definitief vastgelegd. Om te zien wat er in ingewikkelde situaties gebeurt, kun je de situatie op papier naspelen. Teken daartoe voor elke declaratie met pen een hok met bijbehorende naam. De toekenningsopdrachten voer je uit door het hok van de variabelen met potlood in te vullen, waarbij je een eventuele oude inhoud van het hok eerst uitgumt. blz. 31 Numerieke typen In hoofdstuk 4 zagen we het type double. Variabelen van dat type kunnen getallen met cijfers achter de decimale punt bevatten. Na de declaratie double d;

45 5.1 Variabelen 45 Kun je de variabele een waarde geven met een toekenningsopdracht d = ; Overeenkomstig de angelsaksische gewoonte wordt in dit soort getallen een decimale punt gebruikt, en dus niet zoals in Nederland een decimale komma. Variabelen van het type double kunnen ook gehele getallen bevatten; er komt dan automatisch 0 achter de decimale punt te staan: d = 10; Anders dan bij het type int, treden er bij deling van double variabelen slechts kleine afrondingsfouten op: d = d / 3; Naast int en double zijn er in Java nog vier andere types voor numerieke variabelen. Vier van de zes numerieke types kunnen worden gebruikt voor gehele getallen. Het verschil is het bereik van de waarden die kunnen worden gerepresenteerd: type kleinst mogelijke waarde grootst mogelijke waarde byte short int long Het type long is alleen maar nodig als je van plan bent om extreem grote of kleine waarden te gebruiken. De types byte en short worden gebruikt als het bereik van de waarden beperkt blijft. De besparing in geheugengebruik die dit oplevert is eigenlijk alleen de moeite waard als er erg veel (duizenden of zelfs miljoenen) van dit soort variabelen nodig zijn. Ook voor getallen met cijfers achter de komma zijn er twee verschillende types beschikbaar. Ze verschillen behalve in de maximale waarde die kan worden opgeslagen ook in het aantal significante cijfers dat beschikbaar is. type significante cijfers grootst mogelijke waarde float circa double circa Hier is het type float het zuinigst met geheugenruimte; desondanks wordt het over het algemeen het type double gebruikt, ook in de standaardmethoden. Objectverwijzingen In sectie 1.2 hebben we een object gedefinieerd als een groepje variabelen dat bij elkaar hoort. Een object kan als één geheel behandeld worden, bijvoorbeeld door het als parameter aan een methode mee te geven. Een voorbeeld van een object is een kleur. (Bijna) elke kleur kan namelijk door middel van drie getallen worden weergegeven: de hoeveelheid rood, de hoeveelheid groen en de hoeveelheid blauw licht die moeten worden gemengd om de kleur te verkrijgen. In Java (en de meeste andere object-georiënteerde talen) heeft elk object een type. Een kleur is iets anders dan (bijvoorbeeld) een lettertype, en daarom bestaan er Color-objecten en Font-objecten. In de standaardbibliotheken is een groot aantal object-typen beschikbaar. Je kunt ook zelf nieuwe object-typen definiëren. Net als voor waarden van numerieke typen, kun je variabelen declareren om waarden van objecttypen op te slaan. De declaratie bestaat, net als bij declaraties van numerieke variabelen, uit het type en een of meer zelfbedachte namen:

46 46 Objecten en methoden Color geel; In zo n variabele met een object-type kan een verwijzing naar een object worden opgeslagen, dat op zijn beurt weer uit variabelen bestaat. Met puur de declaratie wijst de variabele echter nog niet naar een bepaald object. Net als bij numerieke variabelen is er een toekenningsopdracht nodig om de variabele een waarde te geven. In dit geval is de waarde een verwijzing, die je je kunt voorstellen als een pijl naar het eigenlijke object: geel = Color.yellow; Een Color-object is een groepje van drie variabelen, die als één geheel behandeld worden. De pijl wijst dus naar het hele groepje. Het is niet het object zelf dat geel heet, maar de verwijzingsvariabele. Dat dat onderscheid belangrijk is, blijkt als je een tweede variabele declareert, en die met een toekenningsopdracht gelijk maakt aan de eerste: Color gelb; gelb = geel; De waarde van de variabele gelb is gelijk gemaakt aan die van de variabele geel. Dat wil zeggen dat gelb nu dezelfde verwijzing bevat als geel, en dus naar hetzelfde object verwijst. Het is de pijl die is gekopieerd, niet het object zelf. Dat maakt dat een toekenning aan objectvariabelen altijd snel kan gebeuren, ook als het om hele grote objecten gaat. De geheugenruimte die nodig is om verwijzingen op te slaan is gelijk aan die van een int: 4 bytes. Voorbeelden van objectverwijzingstypen In de standaardbibliotheken zijn een groot aantal typen objecten gedefinieerd. De verzameling wordt met elke versie van Java verder uitgebreid (en soms komen er ook te vervallen als er een beter alternatief beschikbaar komt). Om een idee te geven van welke object-typen er bestaan volgt hier een kleine selectie: typen van objecten waarin heel duidelijk een klein groepje variabelen is te herkennen: Dimension (twee gegroepeerde variabelen: een lengte en een breedte), Color (drie variabelen: rood, groen en blauw), Calendar (een groepje variabelen die een datum en een tijd kunnen bevatten). typen van objecten met een wat complexere structuur, die een zeer natuurlijke eenheid vormen: String (een tekst bestaande uit nul of meer lettertekens), Font (een lettertype), BufferedImage (een afbeelding) typen van objecten die nodig zijn om een interactieve interface te maken: Button (een drukknop op het scherm), Scrollbar (een schuifregelaar), TextField (een invulveld voor de gebruiker), maar ook samengestelde componenten zoals Frame en zelfs Applet typen van objecten waarin de details van een bepaalde gebeurtenis kunnen worden weergegeven, zoals MouseMotionEvent en KeyEvent typen van objecten die een bepaald kunstje kunnen uitvoeren: Graphics (om een tekening te maken), StringTokenizer (om een tekst in stukjes te splitsen)

47 5.2 Typering 47 Figuur 9: De twee rollen van het begrip klasse typen van objecten waarmee files en internet-verbindingen gemanipuleerd kunnen worden: File, URL, FileReader, FileWriter en vele anderen. Van al deze objecttypen kunnen variabelen worden gedeclareerd. De variabelen kunnen een verwijzing bevatten naar een object van het betreffende type. Klasse: groepje methoden èn type van object In sectie 1.2 hebben we een klasse gedefinieerd als een groepje methoden met een naam. Als je zelf methoden schrijft moet je die onderbrengen in een klasse: zo was de methode tekenhuis in hoofdstuk 3 een methode in de klasse Huizen. Ook de standaardmethoden zijn ondergebracht in blz. 23 een klasse: zo is de methode drawrect bijvoorbeeld beschikbaar in de klasse Graphics. Maar klassen spelen nog een andere rol: ze zijn het type van objecten. De naam van de klasse kan op de plaats staan van het type bij de declaratie van een variabele, net zoals numerieke basistypen dat kunnen. Vergelijk: int x; Color geel; De twee rollen die een klasse kan spelen zijn sterk met elkaar verbonden. Methoden hebben immers een object onder handen (het object dat voor de punt staat in de methode-aanroep). Dat object bestaat uit variabelen, die kunnen worden veranderd door de opdrachten in de methode. Objecten die een bepaalde klasse als type hebben, kunnen onder handen worden genomen door de methoden uit die klasse. Of anders gezegd: de methoden van een klasse kunnen objecten onder handen nemen, die die klasse als type hebben. In figuur 9 staat de samenhang tussen de begrippen opdracht, variabele, methode, object en klasse, waarbij de dubbele rol van klassen duidelijk naar voren komt. 5.2 Typering Typering voorkomt fouten Elke variabele heeft een type, die door de declaratie van de variabele is vastgelegd. Het type kan een van de zes numerieke basistypen zijn (de variabele kan dan een getal van dat type bevatten) of een klasse (de variabele kan dan een verwijzing naar een object van die klasse bevatten). Declaraties worden verwerkt door de compiler. Dat is wat ze onderscheidt van opdrachten, die tijdens het runnen van het programma worden uitgevoerd. Door de declaraties kent de compiler de typen van alle variabelen. De compiler is daardoor in staat om te controleren of aanroepen van methoden wel zinvol zijn. Methoden uit een bepaalde klasse kunnen immers alleen worden aangeroepen met een object onder handen dat die klasse als type heeft. Klopt de typering niet, dan geeft de compiler een foutmelding. De compiler maakt dan geen bytecode aan, en het programma kan dus niet worden uitgevoerd. Hoewel het in de praktijk een heel gedoe kan zijn om de compiler helemaal tevreden te stellen wat betreft de typering van het programma, is dat verre te prefereren boven de situatie waar

48 48 Objecten en methoden vergissingen met de typering pas aan het licht zouden komen bij het uitvoeren van het programma. In programmeertalen waarin geen of een minder strenge vorm van typering wordt gebruikt kunnen er verborgen fouten in een programma zitten. Als de bewuste opdrachten bij het testen toevallig niet aan bod zijn gekomen, blijft de fout in de code sluimeren totdat een ongelukkige gebruiker in een onwaarschijnlijke samenloop van omstandigheden de foute opdracht eens tegenkomt. Voor de programmeur is het een onrustbarende gedachte dat dat zou kunnen gebeuren daarom is het goed dat voor Java de compiler de typering zo streng controleert. Als de compiler geen foutmeldingen meer geeft, betekent dat niet dat het programma ook gegarandeerd foutvrij is. Een compiler kan natuurlijk niet de bedoeling van de programmeur raden, en waarschuwen voor het feit dat er een rode cirkel wordt getekend in plaats van een groene. Wel kan de compiler weten dat groen als diameter van een cirkel nooit kan kloppen, omdat groen een kleur is en de diameter een getal moet zijn. De compiler controleert de typen van objecten die door een methode onder handen worden genomen, en ook van alle parameters van een methode. Ook bij het gebruik van rekenkundige operatoren worden de types van de twee argumenten gecontroleerd, zodat bijvoorbeeld niet twee kleuren vermenigvuldigd kunnen worden, maar alleen getallen van een van de zes numerieke typen. Conversie van numerieke typen Waarden van numerieke typen zijn in sommige situaties uitwisselbaar. Zo is de waarde 12 in principe van het type int, maar het is ook acceptabel als rechterkant van een toekenningsopdracht aan een variabele van type double. Bijvoorbeeld, na de declaraties int i; double d; Zijn de volgende toekenningen acceptabel: i = 12; d = 12; d = i; // waarde wordt automatisch geconverteerd // waarde wordt automatisch geconverteerd Bij de toekenningen van een int-waarde aan de double variabele, of dat nu een constante is of de waarde van een int-variabele, wordt de waarde automatisch geconverteerd. Omgekeerd is toekenning van een double-waarde aan een int-variabele niet mogelijk, omdat er in een int-variabele geen ruimte is voor cijfers achter de decimale punt. De controle wordt uitgevoerd door de compiler op grond van de typen. Een double-expressie is nooit acceptabel als waarde voor een int-variabele, zelfs niet als de waarde toevallig een nul achter de decimale punt heeft. De compiler kan dat namelijk niet weten, omdat de uitkomst van berekeningen kan afhangen van de situatie tijdens het runnen. De controle gebeurt puur op grond van het type, en daarom zijn zelfs toekenningen van constanten met 0 achter de decimale punt aan een int-variabele verboden. d = 2.5; i = 2.5; i = d; i = 2*d; i = 5.0; i = 5; // dit is goed // FOUT: double-waarde past niet in een int // FOUT: double-waarde past niet in een int // FOUT: typecontrole doet geen berekeningen // FOUT: 5.0 blijft een double // dit mag natuurlijk wel Het kan natuurlijk gebeuren dat je als programmeur zeker weet dat de conversie van double naar int in een bepaalde situatie wèl verantwoord is. Je kunt dat aan de compiler aangeven door vóór de expressie tussen haakjes het gewenste type te zetten, dus bijvoorbeeld: i = (int) d; i = (int) (2*d); // cast converteert double naar int // cast van een double-expressie De compiler accepteert de toekenningen, en converteert de double-waarden naar int-waarden door het gedeelte achter de decimale punt weg te gooien. Als er 0 achter de decimale punt staat is dat natuurlijk geen probleem; anders gaat er enige informatie verloren. Als programmeur geef je door het expliciet vermelden van (int) aan dat je dat geen probleem vindt. De conversie is een ruwe manier van afronden: 2.3 wordt geconverteerd naar 2, maar ook 2.9 wordt 2. De cijfers achter de decimale punt worden zonder meer weggegooid, er wordt niet afgerond naar de dichtstbijzijnde integer. Deze notatie, waarmee expressies van een bepaald type kunnen worden geconverteerd naar een ander type, staat bekend als een cast. Letterlijk is de betekenis daarvan (althans een van de vele)

49 5.2 Typering 49 een gietvorm ; door middel van de cast wordt de double-expressie als het ware in de gietvorm van een int geperst. Behalve voor conversie van double naar int, kan de cast-notatie ook worden gebruikt om conversies af te dwingen van long naar int, van int naar short, van short naar byte, en van double naar float; kortom in alle gevallen waarin de compiler het onverantwoord acht om een grote waarde in een kleine variabele te stoppen, maar waarin je als programmeur kan beslissen om de toekenning toch te laten plaatsvinden. Voor conversie van klein naar groot is een cast niet nodig, omdat daarbij nooit informatie verloren kan gaan. Operatoren en typering Bij het gebruik van rekenkundige operatoren hangt het van de typen van de argumenten af, op welke manier de operatie wordt uitgevoerd: zijn beide argumenten een int, dan is het resultaat ook een int; bijvoorbeeld: het resultaat van 2*3 is 6, en het type daarvan is int. zijn beide argumenten een double, dan is het resultaat ook een double; bijvoorbeeld: het resultaat van 2.5*1.5 is 3.75, en het type daarvan is double. is één van de argumenten een int en de andere een double, dan wordt eerst de int geconverteerd naar double, waarna de berekening op doubles wordt uitgevoerd; het resultaat is dan ook een double. Bijvoorbeeld: het resultaat van 10.0/4 is 2.5, en het type daarvan is double. Vooral bij een deling is dit van belang: bij een deling tussen twee integers wordt het resultaat naar beneden afgerond. Bijvoorbeeld: het resultaat van 10/4 is 2, met als type int. Als het resultaat daarna in een double variabele wordt opgeslagen, bijvoorbeeld met de toekenningsopdracht d=10/4; dan wordt de int 2 weer teruggeconverteerd naar de double 2.0, maar dan is het kwaad al geschied. Een dergelijke regel geldt voor alle expressies waar een operator wordt toegepast op verschillende numerieke typen, bijvoorbeeld een int en een long: eerst wordt het kleine type geconverteerd naar het grote type, daarna wordt de operatie uitgevoerd, en het resultaat is het grote type. De rekenkundige operatoren -,*, / en % kunnen alleen worden toegepast op numerieke basistypen. Een uitzondering is de operator +. Deze kan behalve op numerieke typen ook worden toegepast op twee String-objecten. De betekenis van het optellen van twee strings is dat de twee teksten aan elkaar worden geplakt; het resultaat is dus ook weer een String. Bijvoorbeeld, na String s, t; s = "Jeroen"; t = "Hallo " + s; bevat de variabele t een verwijzing naar de string "Hallo Jeroen". Is één van de argumenten een String, en de andere een numerieke waarde, dan wordt het getal eerst geconverteerd naar een String, en worden de strings daarna aan elkaar geplakt. Je kunt dat goed gebruiken om in de methode paint het resultaat van een berekening op het scherm te zetten, compleet met toelichting: double x; x = * 5 * 5; g.drawstring("oppervlakte is " + x, 10, 10); Wil je de een numerieke waarde converteren naar een String, dan kun je dit mechanisme gebruiken door het getal samen te voegen met een lege String: de conversie vindt dan wel plaats, maar er wordt geen extra tekst toegevoegd: g.drawstring("" + x, 10, 10);

50 50 Objecten en methoden blz. 23 blz. 31 blz Methoden Methoden hebben een object onder handen Methoden hebben een object onder handen. Anders gezegd: methoden bewerken een object. Het type van dat object moet de klasse zijn waarin de methode gedefinieerd is. Het object dat door de methode wordt bewerkt, wordt in de methode-aanroep vermeld vóór de punt. Een voorbeeld is g.drawline(10, 10, 30, 50); Het object dat door drawline onder handen wordt genomen is het object waar de variabele g naar verwijst. Die variabele moet dan wel zijn gedeclareerd met Graphics g, want drawline is een methode in de klasse Graphics. In de voorbeelden in hoofdstuk 3 en hoofdstuk 4 was dat inderdaad het geval: de Graphics-variabele was daar gedeclareerd als parameter van de methode paint. De methoden kunnen de waarde van variabelen waaruit het object is opgebouwd inspecteren, om ze te gebruiken in bijvoorbeeld een berekening of handeling. De aanroep van drawline is daarvan een voorbeeld: het Graphics-object waar g naar wijst is wel nodig bij het tekenen van de lijn, maar het object wordt er niet door veranderd. Het is ook mogelijk dat methoden een object veranderen, door toekenningen te doen aan een of meer variabelen die deel uitmaken van het object. Een voorbeeld daarvan is de methode setcolor. Immers, na een aanroep als g.setcolor(color.green); weet het object waar g naar wijst dat in de toekomst de kleur groen gebruikt moet worden bij teken-activiteiten. Blijkbaar is deze kleur opgeslagen in een van de variabelen van het object. Resultaatwaarde van een methode Methoden kunnen een waarde als resultaat opleveren. De (meestal laatste) opdracht van de methode-body moet dan een return-opdracht zijn, zoals in sectie 4.3 is besproken. Het resultaat van de methode heeft, zoals elke expressie, een type. In de header van de methode is aangegeven wat het type van het resultaat van de methode is. Een voorbeeld van een methode met een int als resultaat is de methode length in de klasse String. Met deze methode kan de lengte, dat wil zeggen het aantal lettertekens van een String bepaald worden. Bijvoorbeeld, als je een String hebt gedeclareerd en van een waarde hebt voorzien met String s; s = "deze zin bevat vierendertig tekens"; dan kun je de lengte van de string bepalen met int x; x = s.length(); Het resultaat van de aanroep van methode length is een int-waarde. Die moet dus gebruikt worden in een context waarin een int-waarde zinvol is. Dat kan de rechterkant van een toekenningsopdracht aan een int-variabele zijn, zoals in het voorbeeld hierboven. Maar er zijn vele andere contexten denkbaar voor een int-waarde, bijvoorbeeld als parameter van een methode die een int-waarde verwacht, of in een berekening (waarvan het resultaat dan weer in een context gebruikt moet worden): g.drawstring(s, 10, s.length() ); // hoe langer de string, hoe lager op het scherm g.drawstring("aantal tekens: " + s.length(), 5, 5 ); // toon de lengte van de string In plaats van een getal kan een methode ook een object als resultaat hebben. In de methodeheader staat dan een klasse als resultaattype. Bijvoorbeeld, een TextComponent-object kan bewerkt worden door de methode gettext, met een String als resultaat. (Een TextComponent is een veld op het scherm waar de gebruiker tekst kan invullen. Met de methode gettext kan de tekst worden opgevraagd die door de gebruiker op een bepaald moment is ingevuld. Meer hierover in hoofdstuk 7). TextComponent tc; String s;

51 5.3 Methoden en verderop in de methode... s = tc.gettext(); Ook hier geldt weer dat het resultaat van de aanroep van gettext in een context gebruikt moet worden waar een String-waarde nodig is. Dat kan een toekenningsopdracht aan een Stringvariabele zijn, zoals in het voorbeeld hierboven. Maar ook zou je de zojuist bepaalde string meteen weer onder handen kunnen laten nemen door een methode uit de klasse String, bijvoorbeeld de methode length: x = tc.gettext().length(); Het resultaattype van een methode kan toevallig hetzelfde zijn als de klasse waar de methode deel van uitmaakt; met andere woorden: het resultaattype kan hetzelfde zijn als het type van het object dat onder handen wordt genomen. Een voorbeeld is de methode substring in de klasse String. Het resultaat daarvan is een deel van de oorspronkelijke string, waarvan een nader op te geven aantal tekens (vanaf het begin) komt te vervallen. Bijvoorbeeld: staartstuk = s.substring(10); tweedehelft = s.substring( s.length()/2 ); Een ander voorbeeld is de methode touppercase, die een naar hoofdletters omgezette kopie van de string oplevert: hoofdletterversie = s.touppercase(); Let op dat zowel substring als touppercase de string die onder handen wordt genomen onveranderd laat. Het resultaat is een nieuwe string. Je kunt natuurlijk wel besluiten om de oorspronkelijke variabele naar die nieuwe string te laten wijzen: s = s.touppercase(); Iets dergelijks geldt voor de methode darken in de klasse Color. Het onder handen genomen Colorobject blijft onveranderd; de methode levert een nieuw Color-object op, die een iets donkerdere kleur (in dezelfde tint) van de oorspronkelijke kleur bevat. Bijvoorbeeld: Color geel, donkergeel; geel = Color.yellow; donkergeel = geel.darken(); Het is aan de naam van de methode niet altijd goed te zien of het onder handen genomen object wordt veranderd, of dat er een gewijzigde kopie van het object wordt opgeleverd. Dat is dus iets om goed op te letten bij het lezen van de beschrijving van een methode in de handleiding. Statische methoden hebben geen object onder handen In uitzonderlijke gevallen is het niet zinvol dat een methode een object onder handen neemt. Een voorbeeld is de methode sqrt, die de vierkantswortel (square root) van een getal berekent. Als parameter gaat er een double-waarde in, en als resultaat komt er een double-waarde uit, maar er is geen relevant object dat onder handen genomen wordt door de methode. Dit soort methoden heten statische methoden (Engels: static methods). In de header van de methode wordt, vóór het resultaattype door middel van het speciale woord static aangegeven dat een methode statisch is. Bij de aanroep van een statische methode hoeft er voor de punt geen object te staan; er is immers geen object dan onder handen genomen wordt. In plaats daarvan staat er in de aanroep voor

52 52 Objecten en methoden de punt de naam van de klasse waarin de methode zich bevindt. De methode sqrt bevindt zich bijvoorbeeld in de klasse Math, en kan dus als volgt worden aangeroepen: double worteltwee; worteltwee = Math.sqrt(2.0); In de klasse Math zijn behalve sqrt nog veel meer statische methoden gedefinieerd voor allerlei wiskundige functies: logaritme, sinus, absolute waarde, machtsverheffen, minimum, random waarde, enzovoort. Manieren om objecten te verkrijgen We inventariseren nog eens de verschillende manieren die er zijn om een object te pakken te krijgen. Tot nu toe zijn we er drie tegengekomen: gebruik van een object-parameter van een methode (bijvoorbeeld de Graphics-parameter van de methode paint); aanroep van een methode met een object als resultaatwaarde (bijvoorbeeld de methode substring, die een String-object oplevert); aanroep van een operator met een object als resultaatwaarde (bijvoorbeeld de operator + die, als je er twee strings instopt, een nieuw String-object oplevert. Er is echter nog een vierde manier: creatie van een nieuw object vanuit het niets. Constructormethoden creëren nieuwe objecten Voor het maken van gloednieuwe objecten, dus zonder daarbij andere, reeds bestaande objecten te gebruiken, is in Java een speciaal mechanisme beschikbaar. In bijna alle klassen is er een zogenaamde constructormethode aanwezig, die je kunt gebruiken om een nieuw object van die klasse te creëren. Bij aanroep van een constructormethode gebeurt er het volgende: ergens in het geheugen wordt een stukje ruimte gereserveerd voor een nieuw object; het nieuwe object wordt door de constructormethode meteen even onder handen genomen, zodat het object in een zinvolle begintoestand komt; het nieuwe object (althans een verwijzing daarnaar) wordt als resultaat van de methode opgeleverd. De constructormethode heeft altijd dezelfde naam als de klasse (en deze naam begint dus, bij wijze van uitzondering voor methode-namen, ook met een hoofdletter). Bijvoorbeeld: de constructormethode waarmee een nieuw Color-object gemaakt kan worden heet Color, en de constructormethode waarmee een nieuw Button-object gemaakt kan worden heet Button. Aanroep van een constructor-methode gebeurt op een speciale manier. Er is immers nog geen object dat onder handen genomen kan worden (dat willen we juist construeren), dus de normale vorm van methode-aanroep (object-punt-methodenaam) werkt niet. Om een constructormethode aan te roepen schrijf je het woord new, gevolgd door de naam van constructormethode, gevolgd door eventuele parameters. Let op: er staat geen punt tussen het woord new en de methodenaam! Het woord new is weer zo n gereserveerd woord, dat alleen gebruikt kan worden voor dit speciale doel. Hier zijn twee voorbeelden van aanroepen van constructormethoden: new Color(178,255,152) new Button("druk hier") De new-constructie is een expressie De aanroep van een constructormethode met behulp van new heeft de status van een expressie. De twee voorbeelden hierboven zijn dus geen complete opdrachten (er stond dan ook geen puntkomma achter!). De aanroep is een expressie, omdat de methode een resultaatwaarde heeft: een (verwijzing naar) een nieuw object. Die resultaatwaarde kan ergens worden opgeslagen met een toekenningsopdracht, en het ligt voor de hand om daar eerst een variabele voor te declareren: Color lichtgroen; Button bevestig; lichtgroen = new Color(178,255,152); bevestig = new Button("druk hier");

53 5.4 Constanten 53 Maar het is ook mogelijk om het zojuist geconstrueerde object in een andere context te gebruiken waar een Color-object nodig is, bijvoorbeeld als parameter van een methode die een object van dat type verwacht: g.setcolor( new Color(178,255,152) ); 5.4 Constanten Numerieke constanten Constanten van het type int kun je in het programma gewoon opschrijven als een rijtje cijfers. Dat ligt zo voor de hand dat we het al vele malen hebben gebruikt. Om negatieve getallen aan te geven zet je een minteken voor de cijfers. Dit zijn een paar voorbeelden van int-constanten: De grootst mogelijke int-waarde is ruim 2 miljard; ga daar niet overheen! Wil je een long-getal groter dan 2 miljard aanduiden, zet dan de letter L achter het getal: long wereldpopulatie; wereldpopulatie = L; In bijzondere gevallen wil je een getal misschien in de 16-tallige (hexadecimale) notatie aangeven. Dat kan; je begint het getal dan met 0x, waarachter behalve de cijfers 0 tot en met 9 ook de cijfers a tot en met f mogen volgen. Voorbeelden: 0x10 (waarde 16) 0xa0 (waarde 160) 0xff (waarde 255) 0x100(waarde 256) Ook het 8-tallige (octale) stelsel kun je gebruiken. Je begint het getal dan met een 0, waarachter alleen de cijfers 0 tot en met 7 mogen volgen. Het octale stelsel is een beetje in onbruik geraakt, en de enige reden om dit feit te weten is eigenlijk om niet per ongeluk een getal met een 0 te laten beginnen: het zou als octaal getal worden beschouwd! Voorbeelden: 010 (waarde 8) 0377 (waarde 255) 0400 (waarde 256) Constanten zijn van type double zodra er een decimale punt in voorkomt. Een nul voor de punt mag je weglaten (maar waarom zou je?). Voorbeelden van double-waarden zijn: Voor hele grote, of hele kleine getallen kun je de wetenschappelijke notatie gebruiken, bekend van de rekenmachine: E3 betekent: , oftewel E23 is het getal van Avogadro: E-11 is de straal van een waterstofatoom: meter Net als op een rekenmachine worden hele grote getallen niet meer exact opgeslagen. Er zijn circa 15 significante cijfers beschikbaar. Behalve gewone getallen zijn er speciale waarden voor plus en min oneindig, en een waarde genaamd NaN (voor Not a Number ), die als resultaat gebruikt wordt bij onmogelijke berekeningen.

54 54 Objecten en methoden String constanten Letterlijke teksten in een programma zijn constanten van het type String. Ook die hebben we al de nodige keren gebruikt. Je moet de tekst tussen dubbele aanhalingstekens zetten. Daartussen kun je vrijwel alle symbolen die op het toetsenbord zitten neerzetten. Voorbeelden: "hallo" een gewone tekst "h o i!" spaties tellen ook als symbool "Grr#$%]&*{" in een tekst mag alles... "1234" dit is ook een String, geen int "" een String met nul symbolen Alleen een aanhalingsteken in een String zou problemen geven, omdat de compiler dat zou beschouwen als het einde van de string. Daarom moet je, als je toch een aanhalingsteken in een string wilt zetten, daar een backslash-symbool (omgekeerde schuine streep) vóór zetten. Dat roept een nieuw probleem op: hoe zet je het backslash-symbool zelf dan in een string? Antwoord: zet daar een extra backslash-symbool voor, dus verdubbel de backslash. Voorbeelden: "Ze zei \"meneer\" tegen me!" "gewone slash: / backslash: \ hekje: # " Met behulp van de backslash kunnen nog een aantal andere bijzondere tekens worden aangeduid: een regelovergang door \n, een tabulatie door \t en het symbool met Unicode-nummer (hexadecimaal) 12ab door \u12ab. Dat laatste is vooral handig om symbolen die niet op het toetsenbord voorkomen in een string te zetten. Final variabelen Variabelen kunnen veranderen het woord zegt het al. De waarde verandert bij elke toekenningsopdracht aan die variabele. Soms is het handig om een bepaalde waarde een naam te geven, als die waarde veel in een programma voorkomt. In een programma met veel wiskundige berekeningen is het bijvoorbeeld handig om eenmalig te schrijven: double pi; pi = ; Daarna kun je waar nodig de variabele pi gebruiken, in plaats van elke keer dat hele getal uit te schrijven. Het is in dit geval niet de bedoeling dat de variabele later in het programma nog wijzigt echt variabel is deze variabele dus niet. Om er voor te zorgen dat dat niet per ongeluk zal gebeuren (bijvoorbeeld door een tikfout bij het intikken van het programma), kun je bij de declaratie met het keywoord final aangeven dat de variabele meteen zijn definitieve waarde krijgt. De toekenning moet dan meteen bij de declaratie plaatsvinden. De declaratie en toekenning worden dus gecombineerd zoals in: final double pi = ; Zo n variabele heet een final variabele; raar woord eigenlijk, want deze variabele kan dus niet variëren. Constante zou een beter woord zijn, maar dat woord hebben we al gebruikt voor getallen, en strings tussen aanhalingstekens. Dat soort constanten worden ter onderscheid in het Engels ook wel literal genoemd. In het Nederlands zou je letterlijke waarde kunnen zeggen. Statische final variabelen in klassen Behalve methoden zitten er in sommige klassen ook final variabelen. Meestal zijn die bovendien static, dat wil zeggen dat ze bij de klasse als geheel horen, en niet bij elk object uit die klasse afzonderlijk. Bijvoorbeeld, de waarde van π is beschikbaar in de klasse Math. Je kunt de waarde gebruiken met de punt-notatie: Math.PI is een expressie met double als type. Let op de hoofdletters: de gewoonte wil dat numerieke constanten geheel in hoofdletters worden geschreven. Ook in andere klassen zijn numerieke constanten beschikbaar. Bijvoorbeeld, de klasse Font bevat een constante Font.ITALIC, die gebruikt kan worden om een cursief lettertype te maken (meer daarover in sectie 5.5). Constanten hoeven niet altijd numeriek te zijn; het kunnen ook objectverwijzingen zijn. Zo zijn er een aantal veel gebruikte kleuren beschikbaar als constante in de klasse Color. Bijvoorbeeld:

55 5.5 Toepassing: Intro-scherm 55 Color.red, Color.green, Color.blue, Color.yellow en zo nog een aantal meer. Let op de dubbele rol die Color hier speelt: deze constanten zijn zelf Colors, en ze zijn bovendien ondergebracht in de klasse Color. Vergelijk dat met Math.PI: dat is een double, die is ondergebracht in de klasse Math. 5.5 Toepassing: Intro-scherm Intro-scherm: strings en fonts We gaan de besproken concepten gebruiken in een klein programma. Het programma heet Intro, omdat het applet gebruikt zou kunnen worden om een intro-scherm te maken bij een website, waarop de woorden van een tekst op speelse wijze in verschillende kleuren en lettertypes door elkaar heen verschijnen. Behalve objecten van type Color en Font, gaan we gebruik maken van een object van type StringTokenizer, om een String-object in onderdelen te splitsen. Door het gebruik van de wiskundige functie random is het effect elke keer dat we het programma gebruiken een beetje anders. Zie listing 6 voor de tekst van het programma, en figuur 10 voor twee snapshots. blz. 56 Objecten van type StringTokenizer Het programma bestaat uit een methode paint, die zoals altijd automatisch door de browser wordt aangeroepen om het hele plaatje te tekenen. Voor het tekenen van de aparte woorden is er in dit programma een extra methode woord gemaakt, die vanuit de methode paint meermalen wordt aangeroepen. De methode woord heeft een Graphics-object als parameter, omdat die nodig is om überhaupt te kunnen tekenen. Verder krijgt de methode een String s als parameter die het woord bevat dat getekend moet worden, een kleur c die bij het tekenen gebruikt moet worden, de verticale positie y, en de hoogte h van het lettertype dat gebruikt moet worden. De methode woord wordt vanuit paint negen keer aangeroepen, steeds met andere parameters. Op de plaats van de vierde parameter wordt steeds een ander getal gebruikt, zodat elk woord op een andere verticale positie getekend wordt. Op de plaats van de tweede parameter hadden we als letterlijke strings de respectievelijke woorden kunnen schrijven. In plaats daarvan is echter een meer flexibele aanpak gekozen, die het gemakkelijker maakt om later nog eens een andere tekst te kiezen. De tekst die in het programma getoond wordt, wordt eenmalig opgeslagen in de variabele zin. Daarna wordt een zogeheten StringTokenizer-object gemaakt, door met behulp van het speciale keyword new de constructormethode daarvan aan te roepen. Een StringTokenizer-object kent de methode nexttoken. Die methode levert het eerstvolgende woord op van de tekst, die als parameter bij de constructie van het StringTokenizer-object werd aangeboden. Een StringTokenizer kan dus zinnen in woorden splitsen, en dat is de reden dat we het object splitser hebben genoemd. De tweede parameter van de constructor van StringTokenizer geeft aan op welk symbo(o)l(en) de tekst gesplitst moet worden. Omdat we hier een spatie hebben opgegeven, wordt de tekst inderdaad in woorden gesplitst. Elke keer als je nexttoken aanroept, met het object splitser onder handen, krijg je het volgende woord. De splitser onthoudt dus waar je bent gebleven. Daar worden de variabelen, waaruit het splitser-object bestaat (elk object bestaat uit variabelen) blijkbaar voor gebruikt. Het resultaat van aanroep van de methode nexttoken is een String. Die kan direct worden meegegeven als tweede parameter van woord, die immers op die positie een String-object verwacht. Een alternatief wordt ook in het programma getoond: twee resultaten van aanroepen van nexttoken worden met de + operator aan elkaar geplakt, waarna de variabele woorden naar het resultaat gaat wijzen. Die variabele wordt vervolgens gebruikt als parameter van woord, zodat er twee woorden tegelijk getekend worden. Objecten van type Color Voor de kleur die wordt meegegeven aan de methode woord, gebruikt het programma ook diverse manieren (meer om de mogelijkheden te demonstreren, dan dat dat nou erg nuttig is... ) Als eerste wordt door aanroep van de constructor van Color een geheel nieuwe mengkleur gemaakt: veel rood, een beetje groen en helemaal geen blauw levert een oranje-achtige kleur op. Dat nieuwe kleurobject wordt opgeslagen in de variabele kleur, of preciezer gezegd: de objectverwijzing kleur gaat naar het nieuwe object wijzen. Vervolgens wordt dat object als parameter meegegeven aan

56 56 Objecten en methoden import java.awt.graphics; import java.awt.color; import java.awt.font; import java.applet.applet; 5 import java.util.stringtokenizer; public class Intro extends Applet { 10 private void woord(graphics g, String s, Color c, int y, int h) { double plek; plek = * Math.random(); 15 int x; x = (int) plek; g.setcolor(c); g.setfont(new Font("Tahoma", Font.BOLD, h)); 20 g.drawstring(s, x, y); public void paint(graphics g) { 25 String zin, woorden; StringTokenizer splitser; Color kleur; zin = "Een object is een groepje variabelen die je kunt bewerken met methoden"; 30 splitser = new StringTokenizer(zin, " "); kleur = new Color(255,164,0); this.woord(g, splitser.nexttoken(), kleur, 10, 10); kleur = kleur.darker(); 35 this.woord(g, splitser.nexttoken(), kleur, 25, 20); kleur = kleur.darker(); woorden = splitser.nexttoken() + splitser.nexttoken(); this.woord(g, woorden, kleur, 40, 10); this.woord(g, splitser.nexttoken(), Color.red, 55, 10); 40 this.woord(g, splitser.nexttoken(), Color.blue, 70, 15); woorden = splitser.nexttoken() + splitser.nexttoken() + splitser.nexttoken(); this.woord(g, woorden, new Color(128,128,128), 85, 10); this.woord(g, splitser.nexttoken(), Color.blue, 100, 15); this.woord(g, splitser.nexttoken(), Color.blue, 115, 10); 45 this.woord(g, splitser.nexttoken(), Color.blue, 130, 20); Listing 6: Intro/Intro.java

57 5.5 Toepassing: Intro-scherm 57 Figuur 10: Twee snapshots van de applet Intro de methode woord. Daarna wordt door aanroep van Color s methode darken een nieuw kleur-object gemaakt, en met een toekenning gaat kleur naar dat nieuwe object wijzen. De tweede maal dat kleur wordt meegegeven aan woord, ontvangt die methode dus een donkerder oranje kleur-object. De derde aanroep van woord ontvangt een nog donkerder oranje kleur-object. (De oude oranjes zijn inmiddels onbereikbaar geworden, want daar wijst geen enkele variabele meer naar!) In plaats van al dat gedoe kun je natuurlijk ook gewoon een constante, zoals Color.red of Color.blue meegeven als derde parameter van woord. Nog een andere mogelijkheid is om ter plaatse een new Color te maken, en die meteen mee te geven (dus zonder hem eerst op te slaan in een variabele). Objecten van type Font Richten we nu onze aandacht op de body van methode woord. De aanroep van Math.random levert een willekeurige double-waarde tussen 0.0 en 1.0 op. Door die met 50 te vermenigvuldigen, onstaat een getal tussen 0.0 en Door daar nog eens 10 bij op te tellen, onstaat een getal tussen 10.0 en Die wordt opgeslagen in de variabele plek, welke daarna met de cast-notatie wordt afgerond om in een int-variabele x opgeslagen te kunnen worden. De variabele x wordt gebruikt als x-coordinaat in de aanroep van drawstring, en daardoor verschijnen de woorden met een gezellig rommelige kantlijn op het scherm. Let op het verschil tussen de declaratie van de variabele y en de variabele x. De variabele y is een parameter van de methode, want hij wordt gedeclareerd in de header van de methode. De waarde ervan wordt dus bepaald door de waarde op de overeenkomstige plaats in de aanroep: de eerste keer is die 10, de tweede keer 25, de derde keer 40, enzovoort. In de body van woord is dus geen toekenning aan y nodig: parameters krijgen bij aanroep van de methode hun waarde. De variabele x daarentegen wordt in de body van de methode woord gedeclareerd. Het is dus een extra variabele voor intern gebruik binnen de methode, die niet een waarde krijgt door aanroep van de methode. Daarom staat er in de body een toekenningsopdracht aan x, alvorens de waarde van x gebruikt kan worden in de aanroep van drawstring. Voordat het woord getekend wordt met drawstring, wordt de gewenste kleur gekozen met een aanroep van setcolor. Op een dergelijke manier kan het gewenste lettertype worden gekozen met een aanroep van setfont. Waar setcolor een Color-object als parameter nodig heeft, heeft setfont een Font-object nodig. Die wordt ter plaatse new gecreëerd. De constructor van Font heeft drie parameters nodig: de naam van het lettertype, de stijl, en de grootte. De stijl is een int die aangeeft of het font bold en/of cursief is. De getallen die die eigenschappen coderen hoef je niet uit je hoofd te kennen, want ze zijn als constante beschikbaar in de klasse Font: Font.PLAIN voor normale letters, Font.BOLD voor vette letters en Font.ITALIC voor cursieve letters. Wil je vette èn cursieve letters, dan kun je Font.BOLD en Font.ITALIC optellen: het zijn immers getallen! Welke getallen precies gebruikt worden in de codering hoef je niet te weten. Je kunt ze natuurlijk

58 58 Objecten en methoden eens afdrukken (je ziet dan de waardes 0, 1 en 2). Toch is het niet slim om de volgende keer dan maar de waarde 2 te gebruiken in plaats van Font.BOLD. Als in een volgende versie van Java de codering anders zou worden (en die vrijheid heeft de auteur van een package!), dan zou je programma zich ineens anders gaan gedragen. Gebruik je netjes de daarvoor bedoelde constanten, dan hoef je het niet eens te merken als de codering zou zijn veranderd. Het is net als in de spionage: hoe minder informatie je weet, des te minder informatie kun je per ongeluk verkeerd gebruiken... Nieuwe Color-objecten Zoals uit de voorbeelden hierboven blijkt, heeft de constructor-methode Color drie int-waarden als parameter. De drie waarden moeten tussen 0 en 255 liggen. Ze staan voor de hoeveelheid rood, groen en blauw licht die in de nieuwe kleur aanwezig zijn. Je moet je daarbij voorstellen dat de kleur gemaakt wordt door het licht van drie gekleurde spots te mengen (en dat werkt net andersom dan je zou verwachten als je aan mengen van verf denkt). Een paar voorbeelden van mengkleuren zijn: new Color( 0, 0, 0) geen licht, dus zwart new Color(255, 0, 0) intens rood new Color( 0,255, 0) intens groen new Color( 0, 0,255) intens blauw new Color(128, 0, 0) tussen rood en zwart, dus donkerrood new Color(255,255, 0) rood plus groen licht geeft geel new Color(255,128, 0) tussen rood en geel, dus oranje new Color(255,255,255) alle licht aan is wit new Color(128,128,128) tussen zwart en wit is grijs new Color(255,128,128) tussen rood en wit is flets rood new Color(123, 37, 95) probeer maar... Als je dus niet uit de voeten kunt met de dertien standaardkleuren die in de klasse Color als constante aanwezig zijn (Color.blue enzovoorts) dan kun je door het aanroepen van de constructormethode Color elke tint mengen die je wilt. Er zijn in totaal =ruim 16 miljoen kleuren mogelijk. (In hardware-kringen heet dat true color ). Opgaven 5.1 This a. Wat wordt er in een Java-programma aangeduid door het woord this? b. In welke context kun je het woord this niet gebruiken, en waarom niet?

59 59 Hoofdstuk 6 Invloed van buiten 6.1 Applets parametriseren Methode paint heeft één parameter De applets die we tot nu toe hebben bekeken doen, elke keer dat ze worden gerund, steeds hetzelfde. Op deze manier hebben ze weinig meerwaarde boven een plaatje, dat je in de html-pagina kunt opnemen met een <IMG>-tag. Het zou wel leuk zijn als je de applet als het ware van parameters kunt voorzien. Je zou dan dezelfde applet tweemaal kunnen starten, en in de twee gevallen van verschillende parameters voorzien. Je kunt echter niet zomaar de methode paint van extra parameters voorzien. De browser gaat er namelijk van uit dat paint precies één parameter heeft, namelijk een object van het type Graphics. Als de header van de methode paint niet precies is zoals door de browser wordt verwacht (public, resultaattype void, één Graphics-parameter) dan herkent de browser de methode niet, en gebeurt er dus helemaal niets. Toch is het mogelijk om informatie door te spelen vanuit de html-file naar de applet. We gaan weer een applet maken die Hallo! op het scherm zet, maar ditmaal moet de tekst van de begroeting toegesneden kunnen worden op de naam van een persoon, dus bijvoorbeeld Hallo, Jeroen!. De te gebruiken naam zal in de html-file worden gespecificeerd, en daarmee is dit een mooi voorbeeld van het doorgeven van parameters aan een applet. Klasse-extensies erven methoden We moeten, om te begrijpen hoe je een applet van parameters kunt voorzien, eerst even kijken naar de betekenis van extends in de klasse-header. Een klasse-header zoals class Huizen extends Applet betekent dat er een aantal methoden volgen die een Huizen-object onder handen kunnen nemen. De aanduiding extends Applet betekent dat een Huizen-object bovendien alle methoden kent die in de klasse Applet al zijn gedefinieerd. Met extends in de klasse-header kun je dus een bestaande klasse uitbreiden: elk Huizen-object is ook een Applet-object, en wel een heel bijzonder Applet-object dat nog een paar extra kunstjes kent. Methoden in de klasse Applet Het is dus wel de moeite waard om de methoden in de klasse Applet te leren kennen; die kunnen namelijk vanuit iedere methode in een extensie-klasse van Applet worden aangeroepen, gebruik makend van het object this. Eén van de methoden uit de klasse Applet is van belang voor het doorgeven van informatie uit de html-file aan een applet: de methode getparameter. Er kunnen meerdere parameters worden doorgegeven aan de applet, en door aanroep van de methode getparameter kun je er steeds één tegelijk te pakken krijgen. De methode getparameter heeft als (methode-)parameter de naam van de (programma-)parameter die je wilt hebben. Als methode-resultaat levert getparameter dan de waarde van de gevraagde programma-parameter. Zowel de naam als de waarde van programma-parameters zijn teksten. Teksten spelen een belangrijke rol in veel Java-programma s, en er is dan ook een bibliotheek-klasse waarin vele methoden zijn ondergebracht die iets met tekst te maken hebben. Deze klasse heet String; een String-object stelt dus een tekst voor. Zowel parameter als resultaat van de methode getparameter zijn dus een String. De String

60 60 Invloed van buiten die als resultaat wordt opgeleverd, kunnen we tijdelijk opslaan in een variabele, die daarvoor gedeclareerd moet worden met String als object-type. Deze declaratie en de daaropvolgende aanroep kunnen er bijvoorbeeld als volgt uitzien: String persoon; persoon = this.getparameter("voornaam"); Nu moeten we nog zorgen dat in de html-file inderdaad een programma-parameter wordt aangeboden met die naam. Voor dit doel bestaat er een speciale html-tag <PARAM>, die tussen de <APPLET>-tag en de </APPLET>-tag moet staan, als volgt: <APPLET code=groet.class width=200 heigth=50> <PARAM name="voornaam" value="jeroen"> </APPLET> De naam die we in de html-file voor de programma-parameter kiezen (in dit voorbeeld: voornaam), moet in het Java-programma als String worden meegegeven aan de methode getparameter, dus met aanhalingstekens: "voornaam". De waarde die in de html-file achter value= staat gespecificeerd (in het voorbeeld: Jeroen) is in het Java-programma beschikbaar als methode-resultaat van getparameter. De situatie is een beetje verwarrend doordat er twee soorten parameters een rol spelen: methodeparameter en programma-parameters. Let speciaal op het feit dat de naam van de programmaparameter (in het voorbeeld: voornaam) geen Java-variabele of -parameter is, en dus in het programma ook niet gedeclareerd is. De naam van een programma-parameter wordt in het Java-programma als tekst behandeld, vandaar de aanhalingstekens in "voornaam" in het Javaprogramma. blz. 61 blz Utility-klassen Rekenen aan rechthoeken Als een iets uitgebreider voorbeeld van programma-parameters bekijken we een ander applet. Het programma heet Rechthoek, en berekent een paar eigenschappen van een rechthoek (omtrek, oppervlakte en lengte van de diagonaal). De afmetingen van de rechthoek worden als programmaparameter aan de applet meegegeven. Dit programma demonstreert bovendien het gebruik van een paar belangrijke bibliotheek-klassen. Het complete programma staat in listing 7; de bijbehorende html-file staat in listing 8. In figuur 11 is het programma in werking te zien. De klasse Integer Strings en int-waarden zijn van een verschillend type. Er is een wezenlijk verschil tussen het getal 37 (een int-waarde) en de tekst "37" (een String-waarde, die toevallig alleen maar cijfertekens bevat). Het verschil merk je als je ermee probeert te rekenen: 37+1 is 38, maar "37"+"1" is "371". In het voorbeeldprogramma krijgen we de lengte en breedte van de rechthoek als programmaparameter binnen, en dus in de vorm van een String-object. Alvorens we met deze waarden kunnen gaan rekenen (de oppervlakte, bijvoorbeeld, is de lengte maal de breedte), zullen we de Stringobjecten dus moeten converteren naar int-waarden. Gelukkig is er een bibliotheek-methode die deze conversie uitvoert. De methode heet parseint (to parse betekent ontleden ), en is te vinden in de klasse Integer. Die bevindt zich in de package java.lang, maar deze klasse hoeft niet apart geïmporteerd te worden, omdat alle klassen in java.lang automatisch zijn geïmporteerd. Ook de klasse String bevindt zich in deze package, en hoeft dus niet te worden geïmporteerd. De methode parseint uit de klasse Integer is een static methode. Hiervan zou een aanroep kunnen zijn: String lengtealstekst; int lengtealsgetal; lengtealstekst = this.getparameter("lengte"); lengtealsgetal = Integer.parseInt(lengteAlsTekst); De klasse Math Voor het zwaardere rekenwerk is er een klasse Math, vol met methoden die allerlei wiskundige functies berekenen. In deze klasse zijn onder andere de volgende methoden te vinden: abs absolute waarde, d.w.z. laat het eventuele minteken weg sqrt wortel (square root)

61 6.2 Utility-klassen 61 import java.awt.graphics; import java.applet.applet; public class Rechthoek extends Applet 5 { public void paint(graphics g) { String lengtetekst, breedtetekst; int lengte, breedte, omtrek, oppervlakte; 10 double diagonaal; lengtetekst = this.getparameter("lengte"); breedtetekst = this.getparameter("breedte"); 15 lengte = Integer.parseInt(lengteTekst); breedte = Integer.parseInt(breedteTekst); omtrek = 2*(lengte+breedte); oppervlakte = lengte*breedte; 20 diagonaal = Math.sqrt(lengte*lengte + breedte*breedte); g.drawstring("rechthoek", 20, 20); g.drawstring("afmetingen " + lengte + " bij " + breedte, 20, 40); g.drawstring("omtrek is " + omtrek, 20, 60); 25 g.drawstring("oppervlakte is " + oppervlakte, 20, 80); g.drawstring("diagonaal is " + diagonaal, 20, 100); Listing 7: Rechthoek/Rechthoek.java <HTML> Hier is een applet dat de eigenschappen van een rechthoek uitrekent: <BR> 5 <APPLET code=rechthoek.class width=200 height=120> <PARAM name=lengte value=12> <PARAM name=breedte value=8> </APPLET> <HR> 10 Hier is het applet nog eens, maar nu voor een andere rechthoek: <BR> <APPLET code=rechthoek.class width=200 height=120> <PARAM name=lengte value=7> 15 <PARAM name=breedte value=3> </APPLET> </HTML> Listing 8: Rechthoek/Rechthoek.html

62 62 Invloed van buiten Figuur 11: Twee versies van het Rechthoek-applet in éé n html-file sin sinus exp e-tot-de-macht pow machtsverheffen, met als parameters grondtal en exponent log logaritme met grondtal e al deze methoden zijn ook static, en hebben dus niet betrekking op een bepaald object. Rekenen aan rechthoeken Het rechthoeken-programma spreekt nu verder voor zich. Als eerste worden de programmaparameters lengte en breedte opgehaald door aanroep van getparameter. Omdat deze methode de waardes oplevert in String-vorm, moeten deze met parseint eerst worden omgezet naar de overeenkomstige getalwaarde. Met die getallen kunnen vervolgens de berekeningen worden uitgevoerd. De sqrt-methode wordt daarbij gebruikt om, conform de stelling van Pythagoras, de diagonaal van de rechthoek te berekenen. De resultaten kunnen tenslotte met de methode drawstring op het scherm worden getekend. De strings die getekend moeten worden, worden opgebouwd met de operator +, die hierbij de betekenis strings-samenplakken heeft. Omdat als één van de twee operanden van + een int-waarde is, worden er strings samengeplakt, zoals in de expressie "Afmetingen " + lengte Het getal wordt zo eerst automatisch geconverteerd naar een String-waarde. 6.3 Interactie via objecten Een kleuren-mixer In de vorige sectie voorzagen we het programma vanuit de html-file van parameters, zodat het gedrag van het programma van buiten af was te beïnvloeden. Eenmaal gestarte programma s gingen echter gewoon hun gang; de gebruiker van het programma kon niets anders doen dan toekijken. In de meeste programma s wordt de gebruiker echter in staat gesteld om actief in te grijpen in het programmaverloop. Daartoe staan er allerlei interactie-componenten op het scherm, zoals buttons, schuifregelaars, invulbare teksten, menu s, enzovoorts. In dit hoofdstuk bekijken we een programma waarin de gebruiker met drie schuifregelaars de kleur van een vlak kan bepalen: het is dus een kleurenmixer. Met een druk op een speciale button op

63 6.3 Interactie via objecten 63 het scherm kan de gebruiker bovendien de drie schuifregelaars in één klap weer in de stand zwart zetten. Het programma is niet zo lang, maar er komen veel principes aan de orde. De technieken die in dit programma worden toegepast, zijn nodig in elk programma dat interactie met een gebruiker pleegt. Het complete programma staat in listing 9. blz. 64 Alle interactie-componenten (de drie schuifregelaars en de button) worden in het programma gemodelleerd als aparte objecten. In de Java-bibliotheek zitten aparte klassen voor alle standaard interactie-componenten, dus met die klassen en de methoden daarvan krijgen we in ieder geval te maken. Her-definitie van de methode paint Er zijn in de klasse Applet een aantal methoden die van belang zijn voor interactie met de gebruiker. Om te beginnen is er in Applet een methode paint, met een Graphics-object als parameter. In de klasse Applet is deze methode weliswaar gedefinieerd, maar staat er geen enkele opdracht in de body. Als de methode wordt aangeroepen gebeurt er dus helemaal niets. Als je een applet wilt maken dat wél wat tekent, dan moet je een extensie maken van de klasse Applet, en daarin de methode opnieuw definiëren. Dat hebben we in alle programma s in de eerdere hoofdstukken dan ook gedaan. Bedenk dat je in zo n uitgebreiding van een klasse nieuwe methoden kunt toevoegen (zoals tekenhuis in de klasse Huizen), maar dat je ook alle methoden erft van de oorspronkelijke klasse (zie sectie 6.1). Je kunt in een extensie van een klasse ook een methode die je eigenlijk al had geërfd opnieuw definiëren. Bij aanroep van de methoden worden dan de opdrachten in de body van deze herdefinitie uitgevoerd. Herdefinitie van de methode paint in een extensie-klasse van Applet is een slimme truc. De browser roept immers in goed vertrouwen de methode paint aan, en als die is hergedefinieerd hebben we hem dus mooi in de val laten lopen om de macht over te dragen aan ons programma... Indirecte aanroep van paint via repaint De methode paint (de herdefinitie, of als die er niet is, de lege staandaard-versie die in Applet al bestond) wordt door de browser automatisch aangeroepen op elk moment dat het scherm getekend moet worden. Dat is in ieder geval aan het begin, maar bijvoorbeeld ook als het window uit beeld is geweest en nu weer in zicht raakt. Het komt regelmatig voor dat we vanuit het programma er voor willen zorgen dat het scherm opnieuw getekend wordt. Bijvoorbeeld, als de gebruiker een van de interactie-componenten heeft gebruikt, en als reactie daarop de afbeelding op het scherm veranderd moet worden. Je zou in die situatie de methode paint wel zelf willen aanroepen. Maar dat kan niet, want paint heeft een Graphics-object als parameter nodig, en waar haal je die zo ineens vandaan? Gelukkig is het toch mogelijk om vanuit het programma er voor te zorgen dat paint nog een keer extra wordt aangeroepen. Je kunt dat doen door aanroep van de methode repaint, die in Applet beschikbaar is. Deze methode tovert ergens een Graphics-object vandaan (vraag niet hoe dat kan, maar profiteer ervan) en roept vervolgens de methode paint aan met dat Graphicsobject als parameter. Als we de paint-methode hadden hergedefinieerd, dan roept repaint de hergedefinieerde versie aan, en dat is precies het effect dat we willen bereiken. Her-definitie van de methode init Naast paint is er nog een Applet-methode die wordt aangeroepen door de browser. Dit is de methode init: een void-methode zonder parameters. Net als paint heeft init bij de definitie in Applet een lege body. De methode is dan ook, net als paint, bedoeld om hergedefinieerd te worden in een extensie-klasse van Applet. Op die manier kunnen we nóg een valkuil graven waar de browser op een goed moment intrapt. De methode init wordt door de browser eenmalig aangeroepen op het moment dat de applet voor het eerst in beeld wordt gebracht, dus nog vóór de eerste aanroep van paint. Dat maakt de methode init de ideale plaats om opdrachten neer te zetten die niet elke keer dat het window aan het licht komt opnieuw uitgevoerd moeten worden, maar die slechts eenmalig nodig zijn. Het creëren van nieuwe objecten voor de interactie-componenten (door aanroep van new Button en dergelijke) is iets wat typisch in de methode init thuishoort. Aanroep van de methode add In een programma waarin een button nodig is, kan de methode init als volgt hergedefinieerd worden:

64 64 Invloed van buiten import java.awt.*; import java.awt.event.*; import java.applet.applet; 5 public class Mixer extends Applet implements AdjustmentListener, ActionListener { private Scrollbar rood, groen, blauw; private Button zwart; 10 public void init() { rood = new Scrollbar(Scrollbar.HORIZONTAL, 0, 1, 0, 255); groen = new Scrollbar(Scrollbar.HORIZONTAL, 0, 1, 0, 255); 15 blauw = new Scrollbar(Scrollbar.HORIZONTAL, 0, 1, 0, 255); zwart = new Button("Zwart"); this.add(rood); this.add(groen); this.add(blauw); 20 this.add(zwart); rood.addadjustmentlistener(this); groen.addadjustmentlistener(this); blauw.addadjustmentlistener(this); zwart.addactionlistener(this); 25 public void paint(graphics g) { int rw, gw, bw; 30 rw = rood.getvalue(); gw = groen.getvalue(); bw = blauw.getvalue(); g.drawstring("r="+rw+" G="+gw+" B="+bw, 20, 40 ); g.setcolor(new Color(rw, gw, bw)); 35 g.fillrect(20, 60, 260, 220); public void adjustmentvaluechanged(adjustmentevent e) { 40 repaint(); public void actionperformed(actionevent e) { 45 rood.setvalue(0); groen.setvalue(0); blauw.setvalue(0); repaint(); 50 Listing 9: Mixer/Mixer.java

65 6.4 Interactie-componenten 65 Figuur 12: De Mixer-applet in werking public void init( ) { Button b; b = new Button("druk hier"); Maar dat is nog niet genoeg. De nieuwe button is nu wel ergens in het geheugen als object aanwezig (en bereikbaar via de object-referentie b), maar dat wil nog niet zeggen dat de button al direct op het scherm zichtbaar is. Om dat voor elkaar te krijgen, moet je ook nog de methode add aanroepen. Dat is een methode uit de klasse Applet, en omdat init dat ook is, kan add vanuit init worden aangeroepen met this als object dat onder handen wordt genomen. Als parameter krijgt add de interactie-component die zichtbaar gemaakt moet worden: this.add( b ); Interactie-componenten die aldus aan de applet zijn toegevoegd worden automatisch in beeld gebracht; je hoeft ze dus niet zelf in paint te tekenen. 6.4 Interactie-componenten De klasse Button In de package java.awt ( abstracte window toolkit ) zitten een aantal klassen die interactiecomponenten beschrijven. De klasse Button is een beschrijving van drukknoppen die op het scherm worden afgebeeld. Het programma kan zodanig ingericht worden dat er bepaalde opdrachten worden uitgevoerd als de gebruiker op de button drukt. Op deze manier kan het programma dus reageren op de acties van de gebruiker. Zoals in de vorige sectie werd beschreven, kun je door aanroep van de constructor-methode, die ook Button heet, nieuwe objecten maken voor elke drukknop die je in het programma wilt gebruiken. Twee methoden uit de klasse Button zijn met name van belang: Button: de constructormethode setlabel: om de tekst op de button te veranderen Beide methoden hebben een string als parameter, die op de button getoond wordt zodra die met add aan de applet wordt toegevoegd. De klasse Scrollbar Een tweede interactie-component die we in het voorbeeld-programma in dit hoofdstuk zullen gebruiken is Scrollbar. Scrollbars worden vaak langs de randen van windows gebruikt om de ligging van het window te verschuiven, maar je kunt een scrollbar ook los gebruiken. Het is dan een soort schuifregelaar, waarmee de gebruiker een waarde kan instellen. Drie methoden uit de klasse Scrollbar zijn met name van belang: Scrollbar: de constructormethode, met maar liefst vijf int-parameters: De ligging van de scrollbar: horizontaal of verticaal. Je kunt de gewenste ligging aangeven door voor deze parameter één van de twee constanten te gebruiken die voor dit doel in de klasse Scrollbar aanwezig zijn: Scrollbar.HORIZONTAL of Scrollbar.VERTICAL.

66 66 Invloed van buiten De beginwaarde van de schuifregelaar De afstand die de schuifregelaar verschuift als de gebruiker naast het schuivertje klikt De minimale waarde (schuivertje helemaal naar links) De maximale waarde (schuivertje helemaal naar rechts) setvalue: een methode om de waarde van de schuifregelaar, en dus de positite van het schuivertje, te veranderen. De methode heeft één parameter van type int, waarmee de gewenste waarde kan worden aangegeven. Die waarde moet natuurlijk tussen de minimale en maximale waarde liggen zoals die bij de constructormethode werd gespecificeerd. getvalue: een methode om de huidige waarde van de schuifregelaar op te vragen. De methode heeft geen parameters, maar wel een int als resultaatwaarde. Alles importeren Programma s gebruiken vaak vrij veel klassen uit de awt-bibliotheek. allemaal geïmporteerd worden: import java.awt.button; import java.awt.scrollbar; Die moeten in principe enzovoorts. Dat kan in de praktijk een lange opsomming worden. Daarom is het toegestaan om in één klap alle klassen uit een library te importeren: import java.awt.*; Je krijgt dan ook de klassen die je niet nodig hebt, maar daar heb je over het algemeen geen last van. Constructie en gebruik van Scollbar-objecten In het voorbeeldprogramma gaan we drie nieuwe scrollbar-objecten maken, waarmee de gebruiker de hoeveelheid rood, groen en blauw licht kan aanmaken. We declareren daarom drie variabelen van het juiste object-type: Scrollbar rood, groen, blauw; In de methode init worden deze variabelen gebruikt om de verwijzing naar drie nieuw gecreëerde Scollbar-objecten te bewaren: rood = new Scrollbar(Scrollbar.HORIZONTAL, 0, 1, 0, 255); groen = new Scrollbar(Scrollbar.HORIZONTAL, 0, 1, 0, 255); blauw = new Scrollbar(Scrollbar.HORIZONTAL, 0, 1, 0, 255); Door drie aanroepen van add worden de objecten zichtbaar gemaakt in de applet: this.add(rood); this.add(groen); this.add(blauw); In de methode paint gaan we een gekleurd vlak tekenen met een kleur zoals die door de drie schuifregelaars wordt aangegeven. Om te weten op welke positie de schuivertjes zich bevinden, kunnen we de methode getvalue aanroepen: int rw, bw, gw; rw = rood.getvalue(); gw = groen.getvalue(); bw = blauw.getvalue(); Met de drie verkregen int-waarden (rood-waarde, groen-waarde en blauw-waarde) maken we een nieuw Color-object aan, om de kleur van het daarna te tekenen vlak te bepalen: Color c; c = new Color(rw, gw, bw); gr.setcolor(c); gr.fillrect(0,0,100,100); Object-variabelen Er is echter een probleem met de hierboven geschetste aanpak: waar zetten we de declaraties van de drie Scrollbar-objecten rood, groen en blauw?

67 6.5 Interactie met de gebruiker 67 Deze variabelen zijn nodig in de methode init (in de toekenningsopdrachten en als parameter bij de aanroepen van add). Maar ze zijn ook nodig in de methode paint (om de huidige waarde van de schuivertjes op te vragen). Als we de variabelen declareren in één van beide methoden, zal de compiler klagen dat er in de andere methode variabelen worden gebruikt die niet zijn gedeclareerd: methoden mogen immers alleen maar hun eigen variabelen (en parameters) gebruiken. Maar ook het declareren van de variabelen in beide methoden biedt geen uitweg: variabelen zijn lokaal geldig binnen een methode, een als ze toevallig dezelfde naam hebben als de lokale variabelen van een andere methode, blijft het toch om verschillende variabelen gaan. De oplossing van het probleem is om de variabelen dan maar in geen van beide methoden te declareren. Variabelen kunnen namelijk ook buiten de methoden worden gedeclareerd, dus los in de klasse. Dat soort variabelen maken permanent deel uit van het object: sterker nog, ze zijn het object. Ze worden dan ook object-variabelen genoemd. De opzet van het programma wordt dus als volgt: Class Mixer extends Applet { Scrollbar rood, groen, blauw; // object-variabelen public void init() { rood = new Scrollbar(iets,iets,iets,iets,iets ); this.add(rood); enzovoort public void paint(graphics gr) { int rw; rw = rood.getvalue(); enzovoort Object-variabelen kunnen door alle methoden worden bekeken en veranderd. Dat is iets wat we tot nu toe een beetje vaag genoemd hebben: het object wordt door de methoden onder handen genomen. Uitzondering hierop zijn de statische methoden; die hebben geen object onder handen, of meer precies: ze mogen de object-variabelen niet gebruiken. Elk object heeft zijn eigen setje object-variabelen. Die worden gemaakt op het moment dat het object gecreëerd wordt, en blijven net zo lang bestaan als ze nog bereikbaar zijn via variabelen. Het is een goede gewoonte om het aantal object-variabelen zoveel mogelijk te beperken. Als dat mogelijk is, kunnen variabelen beter lokaal in een methode gedeclareerd worden. Op die manier weet je zeker dat methoden niet met elkaars variabelen gaan rommelen. 6.5 Interactie met de gebruiker Event: actie van de gebruiker Met de tot dusver besproken opdrachten in init en paint staat het programma helemaal klaar voor de gebruiker, alleen reageert het programma nog niet als de gebruiker een schuifregelaar bedient of op een button drukt. Zo n handeling van de gebruiker (of een ander van buiten komende actie) heet een event. We moeten het programma nu zo inrichten dat het aan event-handling (afhandelen van gebruikers-acties) gaat doen. Event-listener: object dat een seintje krijgt De gebruiker genereert events door een interactie-component te gebruiken. Om daarop te kunnen reageren, moet je in het programma een event-listener koppelen aan de interactie-component. Een event-listener is een object dat een seintje krijgt als er wat met de interactie-component gebeurt. Het koppelen van een event-listener aan een interactie-component gebeurt door aanroep van een methode van de betreffende component. De naam van die methode verschilt per component-type: in de klasse Button zit een methode addactionlistener in de klasse Scrollbar zit een methode addadjustmentlistener Deze methoden krijgen als parameter de event-listener die op de hoogte gesteld moet worden van het feit dat de gebruiker op de button drukt, respectievelijk de schuifregelaar verplaatst.

68 68 Invloed van buiten De methode init komt er dus als volgt uit te zien (er van uitgaande dat er bij de object-variabelen een Button zwart en een Scrollbar rood is gedeclareerd): public void init() { zwart = new Button("terug naar zwart"); rood = new Scrollbar(iets,iets,iets,iets,iets ); this.add(zwart); this.add(rood); zwart.addactionlistener(iets ); rood.addadjustmentlistener(iets ); De vraag is nu alleen nog welk object gaat dienen als event-listener, en dus als parameter moet worden meegegeven aan deze twee methoden. Event-listeners krijgen seintje via methode-aanroep Interactie-componenten, zoals buttons en scrollbars, geven de event-listener die er aan gekoppeld is een seintje zodra de gebruiker daar aanleiding toe geeft. Dat seintje wordt gegeven door een speciale methode van de event-listener aan te roepen: Een button roept de methode actionperformed aan van zijn action-listener Een scrollbar roept de methode adjustmentvaluechanged aan van zijn adjustmentlistener. Ze moeten er dan wel op kunnen vertrouwen dat die event-listener inderdaad de bewuste methode heeft. Hoe kunnen we dat garanderen? Methodes beloven met implements Groepjes methoden kun je bundelen. Dat is niets nieuws: zo n bundel methoden heet een klasse. Wel nieuw is dat je ook groepjes methode-headers kunt bundelen. Dat staat bekend als een interface. Dit begrip is nodig om precies te omschrijven wat een action-listener nu eigenlijk is. Dat gebeurt in de Java-bibliotheek: interface ActionListener { public void actionperformed(actionevent e); oftewel: een ActionListener moet de methode actionperformed kennen zoals beschreven in de interface-definitie. In de header van een klasse kun je nu beloven dat deze klasse inderdaad zo n methode zal gaan definiëren. Dat gebeurt door in de header de tekst implements ActionListener op te nemen. In het voorbeeld-programma gaan we dat doen met de klasse die we toch al aan het definiëren zijn. (Het wordt een lange header, want daar stond ook al extends Applet, maar dat is helemaal niet erg; desnoods splits je hem in twee regels): public class Mixer extends Applet implements ActionListener { Met deze header doen we de belofte dat in de klasse Mixer alle methoden uit de interface ActionListener zullen worden geïmplementeerd. Dat is er trouwens maar ééntje, namelijk de methode actionperformed. De implements-belofte nakomen De gedane belofte moet natuurlijk wel worden nagekomen. In de body van de klasse Mixer definiëren we daarom inderdaad de beloofde methode: public void actionperformed(actionevent e) { Deze methode wordt aangeroepen als de gebruiker op de button drukt. Hoe moeten we daar ook alweer op reageren? O ja, de schuifregelaars in de uitgangspositie zetten: rood.setvalue(0); groen.setvalue(0); blauw.setvalue(0);

69 6.5 Interactie met de gebruiker 69 De nieuwe situatie moet ook nog tot uiting komen op het scherm, en dat doen we door een aanroep van paint te forceren. We kunnen paint niet zelf aanroepen, maar wel indirect door een aanroep van repaint (zie sectie 6.3). Daarmee is de afhandeling van het indrukken van de button dan voltooid. this.repaint(); Profiteren van de implements-belofte Met het definiëren van de methode actionperformed is de klasse Mixer de belofte om ActionListener te implementeren, nagekomen. Dit betekent dat elk Mixer-object mag dienen als action-listener. Nu hadden we net een action-listener nodig in de methode init, als parameter van addactionlistener: public void init() { enzovoort zwart.addactionlistener(iets ); De methode init is zelf een methode in de klasse Mixer, en dat betekent dat init een Mixer-object onder handen heeft. Dat object kan met het woord this worden aangeduid. In deze context is this een Mixer-object, en daarom mag this dus dienen als action-listener. Daarmee is de puzzel compleet: de creatie van de button, het tonen op het scherm en het koppelen van een action-listener daar aan kan als volgt gebeuren: public void init() { enzovoort zwart = new Button("terug naar zwart"); this.add(zwart); zwart.addactionlistener(this); Reageren op een Scrollbar Het reageren op events die de gebruiker met een van de drie scrollbars laat gebeuren, verloopt langs dezelfde lijnen als het afhandelen van het indrukken van de button: We koppelen in de methode init het Mixer-object this als event-listener aan de scrollbars: rood.addadjustmentlistener(this); groen.addadjustmentlistener(this); blauw.addadjustmentlistener(this); Dat mag alleen maar als Mixer-objecten zich als echte adjustment-listener kunnen gedragen, dus dat beloven we in de header van de klasse Mixer: public class Mixer extends Applet implements ActionListener, AdjustmentListener Om die belofte na te komen definiëren we de in de interface AdjustmentListener gevraagde methode, te weten adjustmentvaluechanged: public void adjustmentvaluechanged (AdjustmentEvent e) { this.repaint(); Het afhandelen van het schuiven aan de scrollbar bestaat alleen maar uit het aanroepen van repaint. Die roept op zijn beurt paint aan, en daarin wordt met getvalue de huidige waarde van de scrollbars opgevraagd en gebruikt. De term interface Het begrip interface wordt op nogal wat plaatsen in de informatica gebruikt. Op het eerste gezicht in totaal verschillende betekenissen: In hardware-kringen: de stekkertjes aan de achterkant van de computer vormen samen de interface. Zo kan er een printer worden aangesloten op de parallelle interface, en een muis op de seriële interface. De interface is als het ware het gezicht dat de computer aan de randapparatuur vertoont.

70 70 Invloed van buiten In ontwerpers-kringen: de windows, knopjes, dialogen, menu s, kortom: de interactiecomponenten waarmee de gebruiker met het programma communiceert vormen samen de grafische user interface, ook wel GUI (spreek uit: koewie ) genoemd. De interface is als het ware het gezicht dat het programma aan de gebruiker vertoont. In kringen van Java-programmeurs: de methoden die van een bepaald object aangeroepen kunnen worden vormen samen de interface, ook wel API genoemd (application programmer s interface). De interface is als het ware het gezicht dat de klasse aan de programmeur die de klasse gebruikt, vertoont. Omdat er in alle gevallen sprake is van een gezicht dat [de computer / het programma / de klasse] vertoont aan de buitenwereld, is het bij nader inzien niet verwonderlijk dat daar dezelfde term voor wordt gebruikt: interface. Wel even goed uitkijken in welke kringen je je beweegt. Opgaven 6.1 Smiley flexibel Pas het smiley-programma uit opgave 3.4 aan, zodat de gebruiker de afmeting van de smiley met een schuifregelaar kan instellen. Maak een tweede schuifregelaar voor de grootte van de ogen, en eventueel een derde voor de vrolijkheid van de mond. 6.2 Eyes Om op een button re reageren, maak je een methode actionperformed. Daarmee heb je voldaan aan de belofte om een ActionListener te implementeren. Je kunt dan in init de methode addactionlistener aanroepen, met de button als object voor de punt. Als parameter geef je daarbij this mee, die immers een geldige ActionListener is, omdat hij een methode actionperformed kent. Het reageren op beweging van de muis gebeurt op een soortgelijke manier: in plaats van de methode actionperformed schrijf je de methoden mousemoved en mousedragged (zie het overzicht in de appendix) je hebt daarmee niet een ActionListener geïmplementeerd, maar een MouseMotionListener je kunt dan in init de methode addmousemotionlistener aanroepen, ditmaal met de hele applet als betrokken object. Maak met deze techniek een applet genaamd eyes. De applet tekent twee grote stripfiguur-ogen: een grote open cirkel met een zwarte cirkel als pupil. De pupillen moeten allebei de bewegingen van de muis volgen: ze kijken naar rechtsonder als de muis rechtsonder staat, enzovoorts. Je kunt de ogen scheel laten kijken door de muis ertussenin te bewegen. Hint: de methode mousemoved heeft een MouseEvent-object als parameter. Zoek op wat je daarmee kunt doen, en verzin een manier om die informatie door te spelen aan paint. Maak een hulpmethode die één oog tekent, en roep die tweemaal aan. Voor de positie van de pupillen: stel dat (cx, cy) het centrum van het oog is, r de straal van de cirkel, en (mx, my) de positie van de muis. Dan geldt voor de positie van de pupil: px = cx + r/d dx, waarbij d 2 = dx 2 + dy 2 en dx = mx cx.

71 71 Hoofdstuk 7 Herhaling Dit hoofdstuk is niet een herhaling, maar gaat over herhaling in Java, en is dus nieuw! 7.1 De while-opdracht Opdrachten herhalen Met behulp van event-listeners kunnen we het programma nu weliswaar laten reageren op de gebruiker, maar nadat de afhandelings-methode is afgelopen, staat het programma toch weer stil (totdat de gebruiker een nieuw event genereert). Om het programma gedurende langere tijd bezig te houden, zijn erg veel opdrachten nodig. Òf: we moeten er voor zorgen dat één (of een paar) opdrachten steeds opnieuw worden uitgevoerd. Dit is mogelijk met een speciale opdracht: de while-opdracht. Een voorbeeld van het gebruik van zo n while-opdracht is het volgende programma-fragment: public void paint(graphics gr) { int x; x = 1; while (x<1000) x = 2*x; gr.drawstring("eindwaarde: " + x, 10, 10); In deze methode staan een declaratie, een toekenningsopdracht, dan zo n while-opdracht, en tenslotte een aanroep van de methode drawstring. De programma-tekst while (x<1000) x = 2*x; is dus één opdracht, bestaande uit een soort header: while (x<1000) en een body: x=x*2;. De header bestaat uit het woord while gevolgd door een voorwaarde tussen haakjes; de body is zélf een opdracht: hier een toekenningsopdracht, maar dat had ook bijvoorbeeld een methode-aanroep kunnen zijn. Bij het uitvoeren van zo n while-opdracht wordt de body steeds opnieuw uitgevoerd. Dit blijft net zolang doorgaan als dat de voorwaarde in de header geldig is. Daarom heet het ook een while-opdracht: de body wordt steeds opnieuw uitgevoerd while de voorwaarde geldt. In het voorbeeld krijgt de variabele x aan het begin de waarde 1. Dat is zeker kleiner dan 1000, dus wordt de body uitgevoerd. In die body wordt de waarde van x veranderd in zijn eigen dubbele; de waarde van x wordt daarmee gelijk aan 2. Dat is nog steeds kleiner dan 1000, en dus wordt de body nogmaals uitgevoerd, waardoor x de waarde 4 krijgt. Ook dat is kleiner dan 1000, dus nogmaals wordt de waarde van x verdubbeld tot 8. Dat is kleiner dan 1000, en zo doorgaand krijgt x daarna nog de waarden 16, 32, 64, 128, 256 en 512. Dat is kleiner dan 1000, en dus wordt de body weer opnieuw uitgevoerd, waarmee x de waarde 1024 krijgt. En dat is niet kleiner dan 1000, waarmee de herhaling eindelijk tot een eind komt. Pas dan wordt de volgende opdracht uitgevoerd: de aanroep van drawstring. Met die opdracht wordt de waarde die x na al dat verdubbelen heeft gekregen (1024) op het scherm getekend. Groepjes opdrachten herhalen Je kunt ook meerdere opdrachten herhalen met behulp van een while-opdracht. Je zet de opdrachten dan tussen accolades, en maakt het hele bundeltje tot body van de while-opdracht. Het

72 72 Herhaling volgende programmafragment bijvoorbeeld, bepaalt hoe vaak je het getal 1 kunt verdubbelen totdat het groter dan 1000 wordt: int x, n; x = 1; n = 0; while (x<1000) { x = 2*x; n = n+1; gr.drawstring(n + " keer verdubbeld", 10, 10); We gebruiken hier een variabele n om het aantal herhalingen te tellen. Voorafgaand aan de whileopdracht is er nog niets herhaald, en daarom maken we n gelijk aan 0. Elke keer dat de waarde van x in de body verdubbeld wordt, verhogen we ook de waarde van n, zodat die variabele inderdaad de tel bijhoudt. Na afloop van de while-opdracht bevat de variabele n dan het aantal uitgevoerde herhalingen. Twee dingen vallen op aan deze programmafragmenten: De variabelen die in de body gebruikt worden, moeten voorafgaand aan de herhaling een beginwaarde hebben gekregen De voorwaarde die de herhaling controleert moet een variabele gebruiken die in de body wordt veranderd (zo niet, dan is de herhaling òf direct, òf helemaal nooit afgelopen). Herhalen met een teller Variabelen die het aantal herhalingen tellen zijn ook heel geschikt om het verdergaan van de herhaling te controleren. Je kunt met zo n teller een opdracht bijvoorbeeld precies tien keer laten uitvoeren. Dit fragment (nog steeds een stukje body van paint) tekent 10 smiley s onder elkaar op het scherm: int n; n = 0; while (n<10) { gr.drawstring( ":-)", 0, 20*n ); n = n+1; Behalve om het aantal herhalingen tellen, komt de teller n hier ook goed van pas om de positie te bepalen waar de n-de smiley moet worden getekend: de y-coördinaten 0, 20, 40, 60 enzovoorts kunnen eenvoudig uit n worden berekend. Opbouw van een resultaat Bij een while-opdracht wordt er vaak gedurende de herhaling een resultaat opgebouwd. Een voorbeeld hiervan is de volgende methode, die de faculteit berekent van een getal, dat als parameter wordt meegegeven. (De faculteit van een getal is het product van alle getallen tussen 1 en dat getal; bijvoorbeeld: de faculteit van 4 is 1*2*3*4=24.) Behalve een teller gebruikt deze methode een variabele result, waarin het resultaat langzaam wordt opgebouwd: private static int faculteit(int x) { int n, result; n=1; result=1; while (n<=x) { result = result*n; n = n+1; return result; 7.2 Boolean waarden Vergelijkings-operatoren De voorwaarde in de header van de while-opdracht is een expressie, die na berekening een waarheidswaarde oplevert: ja of nee. De herhaling wordt voortgezet zolang de uitkomst van de berekening ja is.

73 7.2 Boolean waarden 73 In voorwaarden kun je vergelijkings-operatoren gebruiken. De volgende operatoren zijn beschikbaar: < kleiner dan <= kleiner dan of gelijk aan > groter dan >= groter dan of gelijk aan == gelijk aan!= ongelijk aan Deze operatoren kunnen worden gebruikt tussen twee getallen, zowel int-waarden als doublewaarden. Links en rechts van de operator mogen constante getallen staan, variabelen, of complete expressies met optellingen en vermenigvuldigingen en dergelijke. Let er op dat de gelijkheids-test met een dubbel is-teken wordt geschreven. Dit moet, omdat het enkele is-teken al in gebruik is als toekenningsopdracht. Het verschil tussen = en == is erg belangrijk: x=5; opdracht maak x gelijk aan 5! x==5 expressie is x op dit moment gelijk aan 5? Logische operatoren Een voorwaarde is wat in de logica een predicaat wordt genoemd. De operatoren die in de logica gebruikt worden om predicaten te verbinden ( en, of en niet ) kunnen ook in Java gebruikt worden. De mooie symbolen die de logica ervoor gebruikt zitten helaas niet op het toetsenbord, dus we zullen het moeten doen met een ander symbool: && is de logische en is de logische of! is de logische niet Het type boolean Expressies waarin de vergelijkingsoperatoren gebruikt worden, of waarin vergelijkingen met logische operatoren worden gekoppeld, hebben evengoed een type als expressies waarin rekenkundige operatoren gebruikt worden. De uitkomst van zo n expressie is immers een waarde: één van de twee waarheidswaarden ja of nee. Logici noemen deze waarden waar en onwaar ; de gangbare Engelse benamingen zijn true en false. Behalve gebruiken als voorwaarde in een while-opdracht, kun je allerlei andere dingen doen met logische expressies. Een logische expressie is namelijk net zoiets als een rekenkundige expressie, alleen van een ander type. Je kunt de uitkomst van een logische expressie bijvoorbeeld opslaan in een variabele, of als resultaat laten opleveren van een methode. Het type van logische waarden heet boolean. Dit is een van de primitieve typen van Java, het is dus niet een object-type. Het type is genoemd naar de Engelse logicus George Boole (zie figuur 13). Een voorbeeld van een declaratie van en toekenning aan een boolean variabele: boolean test; test = x>3 && y<5; Wat nuttiger lijkt een methode met een boolean waarde als resultaat. Bijvoorbeeld een methode die het antwoord oplevert op de vraag of een getal een zevenvoud is: private static boolean iszevenvoud(int x) { return x%7==0; Een getal is een zevenvoud als de rest bij deling door zeven nul is. De uitkomst van de boolean expressie die dat test is het resultaat van de methode. De methode kan vervolgens worden gebruikt als voorwaarde in een while-opdracht, om noem ns wat het eerste 7-voud groter dan 1000 te vinden: n = 1000; while (! iszevenvoud(n) ) n = n+1; Dit voorbeeld maakt duidelijk dat de voorwaarde in een while-opdracht niet altijd een vergelijking hoeft te zijn, maar ook een andere boolean expressie mag zijn; omgekeerd zijn voorwaarden van

74 74 Herhaling Figuur 13: George Boole ( ) while-opdrachten niet de enige plaats waar vergelijkingen een rol spelen: dit kan ook op andere plaatsen waar een boolean expressie nodig is. 7.3 De for-opdracht Verkorte notatie van teller-ophoging In de bodies van veel while-opdrachten, vooral die waarin een teller wordt gebruikt, komen opdrachten voor waarin een variabele wordt opgehoogd. Dit kan door middel van de opdracht n = n+1; (Even terzijde: alleen al vanwege dit soort opdrachten is het onverstandig om de toekenning uit te spreken als is. De waarde van n is namelijk niet gelijk aan n+1, maar de waarde van n wordt gelijk aan de (oude) waarde van n+1). Opdrachten zoals deze komen zo vaak voor, dat er een speciale, verkorte notatie voor bestaat: n++; Een adequate uitspraak voor ++ is wordt opgehoogd. Voor ophoging met meer dan 1 is er nog een andere notatie: n += 2; betekent hetzelfde als n = n+2; Automatisch tellen Veel while-opdrachten gebruiken een tellende variabele, en hebben dus de volgende structuur: int n; n = beginwaarde ; while (n < eindwaarde ) { doe iets nuttigs gebruikmakend van n n++; Omdat zo n tellende herhaling zo vaak voorkomt, is er een aparte notatie voor beschikbaar: int n; for (n=beginwaarde; n<eindwaarde; n++) { doe iets nuttigs gebruikmakend van n De betekenis van deze for-opdracht is precies dezelfde als die van de hierboven genoemde whileopdracht. Het voordeel is echter dat de drie dingen die met de teller te maken hebben (de beginwaarde, de eindwaarde en het ophogen) netjes bij elkaar staan in de header. Dat maakt de kans veel kleiner dat je het ophogen van de teller vergeet op te schrijven.

75 7.4 Bijzondere herhalingen 75 In die gevallen waar doe iets nuttigs uit maar één opdracht bestaat, kun je de accolades ook nog weglaten, wat de notatie nog iets compacter maakt. 7.4 Bijzondere herhalingen Niet-uitgevoerde herhaling Het kan gebeuren dat de voorwaarde in de header van een while-opdracht meteen aan het begin al onwaar is. Dit is het geval in de volgende opdracht: x=1; y=0; while (x<y) x++; In deze situatie wordt de body van de while-opdracht helemaal niet uitgevoerd, zelfs niet één keer. In het voorbeeld blijft x dus gewoon de waarde 1 houden. Oneindige herhaling Een gevaar van while-opdrachten is dat er soms nooit een einde aan komt (qua uitvoeren dan, niet qua programmatekst!). Zo n opdracht is gemakkelijk te schrijven. Met while (1==1) x = x+1; wordt de waarde van x steeds maar verhoogd. De voorwaarde 1==1 blijft namelijk altijd waar, zodat de opdracht steeds opnieuw uitgevoerd wordt. In dit programma was die oneindige herhaling wellicht de bedoeling, maar vaak slaat een whileopdracht ook op hol als gevolg van een programmeerfout. Bijvoorbeeld in: x = 1; aantal = 0; while (aantal<10) x = x*2; aantal = aantal+1; // fout! Het is de bedoeling dat de waarde van x tienmaal wordt verdubbeld. Helaas heeft de programmeur vergeten om de twee opdrachten van de body tussen accolades te zetten. De bedoeling wordt wel gesuggereerd door de lay-out, maar daar heeft de compiler geen boodschap aan. Daardoor wordt alleen de opdracht x=x*2; herhaald, en op die manier wordt de waarde van aantal natuurlijk nooit groter of gelijk aan 10. Na afloop van de while-opdracht zou de opdracht aantal=aantal+1; éénmaal worden uitgevoerd, maar daaraan komt de computer niet eens meer toe. De bedoeling van de programmeur was natuurlijk: while (aantal<10) { x = x*2; aantal = aantal+1; // goed. Het zou jammer zijn als je, na een computer met een vergeten accolade in coma gebracht te hebben, het dure apparaat weg zou moeten gooien omdat hij steeds maar bezig blijft met dat ene programma. Gelukkig is er een manier om met geweld de uitvoering van het programma te beëindigen, ook al is het nog niet voltooid. De manier waarop dat gaat verschilt per computersysteem. Als je je programma uittest met appletviewer kun je het window sluiten. Het programma wordt dan direct gestopt, en je kunt de oorzaak van het hangen van het programma gaan zoeken. In het algemeen moet je, als het programma bij het uittesten niets lijkt te doen, de while-opdrachten in je programma nog eens kritisch bekijken. Een beruchte fout is het vergeten van het ophogen van de tellende variabele, waardoor de bovengrens van de telling nooit wordt bereikt, en de herhaling dus steeds maar doorgaat. Herhaalde herhaling De body van een while-opdracht en van een for-opdracht is zelf óók weer een opdracht. Dat kan een toekenningsopdracht zijn, of een methode-aanroep, of een met accolades gebouwde samengestelde opdracht. Maar de body kan ook zelf weer een while- of for-opdracht zijn. Bijvoorbeeld:

76 76 Herhaling int x, y; for (y=0; y<10; y++) for (x=0; x<y; x++) gr.drawstring("+", 20*x, 20*y); In dit fragment telt de variabele y van 0 tot 10. Voor elk van die waarden van y wordt de body uitgevoerd, en die bestaat zelf uit een herhaling, gecontroleerd door de teller x. Deze teller heeft als bovengrens de waarde van y. Daardoor zal de binnenste herhaling, naarmate y groter wordt, steeds langer doorgaan. De opdracht die herhaald herhaald wordt, is het tekenen van een plus-symbool op posities evenredig met x en y. Het resultaat is een driehoek-vormig tableau van plus-tekens: Op de bovenste rij in dit tableau staan nul plus-tekens. De waarde van y is op dat moment nog 0, en de eerste keer dat de for-x-opdracht wordt uitgevoerd, betreft het een herhaling die nul keer wordt uitgevoerd. Zo n niet-uitgevoerde herhaling past hier prima in de regelmaat van het schema. blz Toepassing: renteberekening Rente op rente Een aantal besproken ideeën komt samen in de applet die te zien is in listing 10 en figuur 14 (voor respectievelijk de programmatekst en het runnende programma). Deze applet laat de gebruiker een bedrag en een rentepercentage invoeren, en toont dan de ontwikkeling van het kapitaal (of als je wilt de schuld...) in de komende tien jaren. Door het effect van rente op rente komt er niet elk jaar een vast bedrag bij, maar stijgt het kapitaal/de schuld steeds sterker. De vermeerdering van het kapitaal wordt beschreven door de opdracht kapitaal *= ( *rente); De hierin gebruikte operator *= heeft de betekenis wordt vermenigvuldigd met, net zoals += de betekenis heeft wordt vermeerderd met. Deze opdracht is een verkorte schrijfwijze voor kapitaal = kapitaal * ( *rente); Bij een rentepercentage van 5 wordt het kapitaal door middel van deze opdracht vermenigvuldigd met In een for-opdracht wordt de opdracht elfmaal uitgevoerd, en daaraan voorafgaand wordt steeds het tussenresultaat op het scherm getekend. Invoer via TextFields De gebruiker kan zelf het te gebruiken startbedrag en het rentepercentage invullen. Dat is mogelijk met invoercomponenten van het type TextField. In de methode init worden daarom twee TextField-objecten gecreëerd. Omdat deze objecten ook in de methode paint nodig zijn, zijn ze gedeclareerd als object-variabelen. Parameters van de constructor-methode zijn de tekst die aan het begin in de tekstvelden wordt getoond, en de breedte van het tekstveld. Net als aan een button kan aan een tekstveld een action-listener worden gekoppeld. Op de manier beschreven in het vorige hoofdstuk gebruiken we het applet als action-listener. De gebruiker genereert events door in het tekstveld op de Enter-toets te drukken. In de methode actionperformed hebben we gespecificeerd dat op dat moment (via repaint) de methode paint aangeroepen wordt. In die methode pakken we met een aanroep van gettext de tekst uit de tekstvelden, en converteren de daarmee verkregen Strings meteen (zonder ze eerst in variabelen op te slaan!) met de methode parseint tot getallen. Met die getallen gaat de for-opdracht vervolgens aan de slag.

77 7.5 Toepassing: renteberekening 77 import java.applet.applet; import java.awt.*; import java.awt.event.*; 5 public class Rente extends Applet implements ActionListener { TextField starttekst, rentetekst; public void init() 10 { starttekst = new TextField("100", 8); rentetekst = new TextField("5", 4); this.add(starttekst); this.add(rentetekst); 15 starttekst.addactionlistener(this); rentetekst.addactionlistener(this); public void actionperformed(actionevent e) 20 { this.repaint(); public void paint(graphics gr) 25 { int start, rente, jaar; double kapitaal; start = Integer.parseInt( starttekst.gettext() ); rente = Integer.parseInt( rentetekst.gettext() ); 30 kapitaal = start; for (jaar=0; jaar<=10; jaar++) { gr.drawstring( "Na " + jaar + " jaar: " + kapitaal, 10, 50+15*jaar); 35 kapitaal *= ( *rente); Listing 10: Rente/Rente.java Figuur 14: De applet Rente in werking

78 78 Herhaling Opgaven 7.1 Muur Schrijf een applet die een bakstenen-muur tekent zoals in de figuur hieronder 7.2 Driehoek Schrijf de methode paint van een applet met als output een driehoek met vierkantjes zoals in de figuur hierboven. Gebruik daarbij geen while-, maar for-opdrachten. 7.3 Driehoek van Pascal Het aantal manieren waarop je k elementen kunt kiezen uit een verzameling van n elementen staat in de wiskunde bekend als n boven k. Je kunt dit aantal bepalen door n! te delen door k!(n k)!, waarbij n! staat voor de faculteit van n, d.w.z. alle getallen van 1 t/m n vermenigvuldigd. De driehoek van Pascal toont op de n-de rij de waarden van n boven k, voor k tussen 0 en n: Schrijf een applet dat de eerste twintig rijen van de driehoek van Pascal toont. 7.4 Wortel berekenen Er is een wiskundige stelling die zegt: als y een benadering is voor de wortel van x, dan is het gemiddelde van y en x/y een betere benadering. Gebruik deze eigenschap om een eigen sqrtmethode te schrijven. Gebruik 1 als eerste benadering, en pas dan net zolang deze eigenschap toe, totdat y 2 nog maar erg weinig afwijkt van x. 7.5 Haakjes uitwerken Als je de haakjes uitwerkt in (1 + x) 2 krijg je x 2 + 2x + 1, en (1 + x) 4 wordt x 4 + 4x 3 + 6x 2 + 4x + 1. In de coëfficiënten kun je de getallen uit de driehoek van Pascal herkennen. Schrijf een methode met een getal n als parameter, die als methode-resultaat een String oplevert met de uitgewerkte versie van (1 + x) n. Bijvoorbeeld, als n de waarde 4 heeft, is het resultaat "x^4 + 4x^3 + 6x^2 + 4x +1". Hint: de operator += werkt ook op Strings! 7.6 Tafels van vermenigvuldiging Schrijf een applet waarbij de gebruiker in een tekstveld een getal kan invoeren, waarna de tafel van vermenigvuldiging (van 1 t/m 10) van dat getal wordt getoond. 7.7 Uitvoer voorspellen Bepaal de waarde van a na uitvoeren van het volgende programmafragment: a=0; b=1; c=6; for (k=0; k<100; k++) { a += b; b += c; c += 6; Hint: voer een paar stappen met de hand uit en probeer de regelmaat te ontdekken. Extrapoleer

79 7.5 Toepassing: renteberekening 79 dan naar het eind van de for-opdracht. 7.8 Boolean methoden Schrijf een methode deelbaar die bepaalt of een getal deelbaar is door een ander getal. Het resultaat moet als boolean waarde worden opgeleverd. Schrijf vervolgens een methode die bepaalt of een getal een priemgetal is (een priemgetal is een getal dat alleen maar deelbaar is door 1 en zichzelf). Gebruik deze methoden in een methode paint die de eerste honderd priemgetallen in 10 rijtjes van 10 op het scherm zet. 7.9 Random tekst Schrijf een applet dat een bepaalde tekst vele malen op het scherm weergeeft. Welke tekst wordt getoond, moet in de html-file kunnen worden gespecificeerd, zodat deze applet op diverse webpagina s kan worden gebruikt. Ook het aantal keren dat de tekst wordt weergegeven, moet in de html-file kunnen worden gespecificeerd. De positie van de teksten is kriskras door elkaar, dat wil zeggen op random posities. Sommige teksten worden klein weergegeven, andere groot: varieer tussen 5- en 30-punts letters. Ook deze keuze moet random geschieden. Als je wilt kun je ook de kleur van de teksten variëren. Breid nu de applet uit, zodat ook een button met het opschrift shuffle wordt weergegeven. Elke keer als de gebruiker de button indrukt worden de posities en groottes van de teksten opnieuw bepaald.

80 80 Hoofdstuk 8 Keuze 8.1 De if-opdracht Opdrachten voorwaardelijk uitvoeren Opdrachten in een programma worden normaal gesproken de één na de ander uitgevoerd. Met een while-opdracht kun je er eens een paar herhalen, maar daarna gaat het (als de herhaling tenminste niet oneindig lang doorgaat) onverbiddelijk verder. Niet altijd is dat gewenst: soms moeten opdrachten alleen maar onder bepaalde omstandigheden worden uitgevoerd. Die omstandigheden kunnen afhangen van wat er voorafgaand in het programma is gebeurd, en hangen uiteindelijk af van invoer die de gebruiker heeft verstrekt. Stel bijvoorbeeld dat in het programma de variabele temperatuur een waarde heeft gekregen, bijvoorbeeld doordat de gebruiker dat via een tekstveld heeft ingevoerd. Met een speciale opdrachtvorm kunnen we het programma nu een opmerking over het weer laten maken, maar dan alleen als dat toepasselijk is: if (temperatuur<0) gr.drawstring("het vriest!", 10, 10); De opbouw van deze if-opdracht lijkt op die van een while-opdracht: er is een header met een voorwaarde, en een opdracht die de body vormt. De opdracht in de body wordt alleen uitgevoerd als de voorwaarde waar is, anders wordt-ie overgeslagen. Een tweede alternatief achter else Mocht het nodig zijn om in het andere geval, dus als de voorwaarde in de header juist niet waar is, een andere opdracht uit te voeren, dan kun je de if-opdracht uitbreiden met een else-gedeelte. Bijvoorbeeld: if (temperatuur<0) gr.drawstring("het vriest!", 10, 10); else gr.drawstring("het dooit.", 10, 10); Let wel, het geheel if+voorwaarde+opdracht+else+opdracht heeft zelf de status van één opdracht. Het geheel, met of zonder else-gedeelte, kan dus zelf optreden als bijvoorbeeld de body van een for-opdracht, zonder dat er accolades nodig zijn: for (n=1; n<20; n++) if (n%3==0) gr.drawstring(n + " is deelbaar door 3",iets,iets ); else gr.drawstring(n + " is niet deelbaar door 3",iets,iets ); Groepjes opdrachten voorwaardelijk uitvoeren Als je meerdere opdrachten voorwaardelijk wilt uitvoeren, dan kun je die net als bij de whileopdracht groeperen met accolades. Bijvoorbeeld: if (temperatuur<0) { gr.drawstring("het vriest,", 10, 10); gr.drawstring("koud he!", 10, 25); Ook de opdracht achter else kan een met accolades samengebundelde groep opdrachten zijn.

81 8.2 Toepassingen 81 Een reeks alternatieven Als er meerdere categorieën van waarden zijn, dan kun je met if-opdrachten uittesten welk geval zich voordoet. De tweede test komt te staan achter de else van de eerste test, zodat de tweede test alleen maar wordt uitgevoerd als de eerste test mislukt is. Een eventuele derde test komt te staan achter de else van de tweede test. Het volgende fragment bijvoorbeeld bepaalt tot welke tarief-categorie iemand met een bepaalde leeftijd behoort. We gaan er van uit dat de variabele leeftijd al een waarde heeft. Het resultaat wordt voor de verandering in een tekstveld neergezet, die als objectvariabele tf beschikbaar moet zijn: if (leeftijd<4) tf.settext("gratis"); else if (leeftijd<12) tf.settext("railrunner"); else if (leeftijd<65) tf.settext("vol tarief"); else tf.settext("seniorenkaart"); Achter elke else (behalve de laatste) staat opnieuw een if-opdracht. Voor babies wordt de tekst Gratis getoond, en wordt de hele rest overgeslagen (die staat immers achter de else). Bejaarden daarentegen, doorlopen alle tests (kleiner dan 4? kleinder dan 12? kleiner dan 65?) voordat we tot een Seniorenkaart concluderen. In het programma is met inspringen duidelijk aangegeven welke else bij welke if hoort. Bij lange reeksen tests gaat de tekst van het programma dan wel erg naar rechts hangen. Als uitzondering op onze gewoonte om deel-opdrachten achter else naar rechts in te springen, zullen we bij zo n herhaalde if-opdracht de lay-out iets simpeler houden: if (leeftijd<4) tf.settext("gratis"); else if (leeftijd<12) tf.settext("railrunner"); else if (leeftijd<65) tf.settext("vol tarief"); else tf.settext("seniorenkaart"); Dat is ook wel mooi, want dan zie je alle alternatieven netjes op een rijtje staan. Stop als gevonden De if-opdracht kan natuurlijk ook in een methode staan. Je kunt dan de return-opdracht, waarmee een methode zijn resultaat bekendmaakt, voorwaardelijk maken. Hier is het treintarief-voorbeeld nog eens, maar nu in methode-vorm: private static String tarief(int leeftijd) { if (leeftijd<4) return "Gratis"; if (leeftijd<12) return "Railrunner"; if (leeftijd<65) return "Vol tarief"; return "Seniorenkaart"; Omdat de return-opdracht direct terugkeert naar de aanroeper van de methode, is het hier niet eens nodig om de tweede test achter else te schrijven. Als de eerste test waar is, dan komt de methode dus niet eens toe aan de tweede test. In sectie 4.3 noemden we het nog zinloos om meer dan één return-opdracht in een methode te schrijven, omdat de tekst achter de eerste return-opdracht unreachable code zou zijn. In dit geval is het echter wel zinvol, omdat de eerste return-opdracht niet altijd wordt uitgevoerd, en de tekst erachter dus onder sommige omstandigheden wel reachable is. 8.2 Toepassingen Onderscheiden van Buttons In listing 11 en figuur 15 staan (tekst en snapshot) van een programma dat een groene cirkel op blz. 82

82 82 Keuze import java.awt.*; import java.awt.event.*; import java.applet.applet; 5 public class Cirkel extends Applet implements ActionListener { Button kleiner, groter; int straal; 10 public void init() { kleiner = new Button("Kleiner"); groter = new Button("Groter"); this.setlayout(new FlowLayout()); this.add(kleiner); 15 this.add(groter); kleiner.addactionlistener(this); groter.addactionlistener(this); straal = 100; 20 public void actionperformed(actionevent e) { if (e.getsource()==kleiner && straal>10) straal -= 10; 25 if (e.getsource()==groter && straal<150) straal += 10; this.repaint(); 30 public void paint(graphics gr) { gr.setcolor(color.green); gr.filloval(150-straal, 150-straal, 2*straal, 2*straal); 35 Listing 11: Cirkel/Cirkel.java het scherm tekent. Er zijn twee buttons met opschrift kleiner en groter. Met deze knoppen kan de gebruiker de cirkel kleiner en groter maken, mits hij niet onzichtbaar wordt of buiten het window gaat vallen. De opzet van het programma is zoals ieder interactief programma: de methode init creëert de nodige interactie-componenten, er is een action-listener die een aanroep van paint forceert, en een methode paint die de tekening verzorgt. Variabelen voor de twee buttons zijn als object-variabelen gedeclareerd, omdat deze variabelen zowel in init als in actionperformed nodig zijn. Ditmaal is er bovendien een int-variabele (straal) als object-variabele gedeclareerd. Deze variabele is in alle drie de methoden nodig: in init krijgt straal zijn beginwaarde in paint wordt straal gebruikt om de grootte van de cirkel te bepalen in actionperformed wordt straal groter of kleiner gemaakt, afhankelijk van welke button is ingedrukt. Voor dat laatste komt een if-opdracht goed van pas. We gebruiken bovendien iets nieuws: de parameter van actionperformed. Dat is een object van het type ActionEvent, waarmee allerlei informatie over de gebeurtenis die is opgetreden is opgeslagen. Het belangrijkste is wellicht met welke interactie-component de gebruiker de gebeurtenis heeft veroorzaakt. Dat is aan zo n

83 8.2 Toepassingen 83 Figuur 15: De applet Cirkel in werking ActionEvent te vragen door aanroep van de methode getsource. Met behulp van twee if-opdrachten wordt getest of het bewuste object de groter - of de kleiner - button is. Door middel van een tweede voorwaarde, achter de logische and -operator, wordt bovendien gecontroleerd dat de straal niet té groot, respectievelijk klein wordt. Je ziet dat je met de == operator behalve getallen ook objecten, of liever gezegd object-verwijzingen, kunt testen op gelijkheid. Ook de ongelijkheids-operator!= werkt op objecten. Je kunt objecten echter niet ordenen met operatoren zoals < en >=. Controle van passwords In listing 12 staat de tekst van een programma dat een mooie tekening maakt, maar alleen als de blz. 84 gebruiker eerst het juiste password intikt in een daarvoor bestemd tekstveld. Als object-variabelen hebben we behalve een variabele voor het tekstveld nog twee variabelen. Er is een string waarin de sleutel tot de toegang wordt bewaard, en er is een boolean open, die aangeeft of het slot al is opengemaakt. In de methode init krijgt de sleutel-string zijn waarde (maar die is geheim, dus die verklappen we hier niet), en krijgt de variabele open de waarde false, omdat het slot initieel nog niet is opengemaakt. De methode actionperformed wordt aangeroepen als de gebruiker op de Enter-toets drukt in het tekstveld, hopelijk nadat hij een password heeft ingetikt. Dat gaan we dus controleren in actionperformed. Als de gebruiker het password goed heeft geraden, geven we de variabele open de waarde true, om aan te geven dat het slot nu is opengemaakt. In de methode paint, die door een aanroep van repaint aan het werk wordt gezet, controleren we de waarde van de variabele open. Alleen als die true is, wordt de tekening gemaakt. Omdat open een boolean variabele is, kan die direct dienen als voorwaarde in een if-opdracht: die voorwaarde moet immers een boolean expressie zijn. Als de boolean variabele de waarde true heeft, wordt de opdracht in de body uitgevoerd, als hij de waarde false heeft, wordt de opdracht achter else uitgevoerd. Speciale aandacht behoeft het vergelijken van het ingetikte password met de sleutel. Het ingetikte password kunnen we uit het tekstveld halen met de expressie password.gettext(), en de sleutel is beschikbaar in de variabele sleutel. Deze twee strings worden echter niet vergeleken met maar met if ( password.gettext() == sleutel ) if (password.gettext().equals(sleutel) ) // fout! // goed. Zouden we de vergelijking uitvoeren met ==, dan testen we of deze twee string-expressies hetzelfde object aanduiden (dezelfde plaats in het geheugen). Dat is hier echter zeker niet het geval: sleutel duidt een constante string aan, en het resultaat van gettext is een speciaal door gettext nieuw gecreëerd string-object. Wat we willen weten is of de inhoud van de twee strings hetzelfde is (d.w.z.

84 84 Keuze import java.awt.*; import java.awt.event.*; import java.applet.applet; 5 public class Geheim extends Applet implements ActionListener { TextField password; boolean open; String sleutel; 10 public void init() { password = new TextField(20); this.add(password); password.addactionlistener(this); 15 password.setechochar( * ); sleutel = "geheim"; open = false; 20 public void actionperformed(actionevent e) { if ( password.gettext().equals(sleutel) ) { open = true; password.setvisible(false); 25 this.repaint(); else password.settext(""); 30 public void paint(graphics gr) { if (open) { gr.setcolor(color.green); gr.filloval(50,50,100,100); 35 gr.setcolor(color.blue); gr.filloval(81,85,8,8); gr.filloval(111,85,8,8); gr.drawarc(75,75,50,50,225,90); 40 else gr.drawstring("please enter password", 50, 50 ); Listing 12: Geheim/Geheim.java

85 8.3 Grafiek en nulpunten van een parabool 85 of de twee teksten toevallig allebei dezelfde letters bevatten). Die test kan worden uitgevoerd met behulp van de methode equals uit de klasse String. Deze methode heeft één string onder handen, en krijgt een tweede string als parameter. Het resultaat van equals is een boolean waarde. In het programma is handig gebruik gemaakt van het resultaat van de aanroep van gettext. We hadden stapsgewijs eerst de ingetikte string kunnen ophalen, die vervolgens met de sleutel kunnen vergelijken, en tenslotte het boolean resultaat in de if-opdracht kunnen schrijven: String ingetikt; boolean klopt; ingetikt = password.gettext(); // dit is... klopt = ingetikt.equals(sleutel); // wel erg... if (klopt) // omslachtig! Maar al die extra variabelen maken het wel erg omslachtig. Het is veel handiger om de resultaatstring van gettext direct onder handen te laten nemen door de methode equals. De boolean waarde die daar het resultaat van is, kan direct worden gebruikt als voorwaarde in de if-opdracht if (password.gettext().equals(sleutel) ) // korter. Als finishing touch maken we van het tekstveld een echt password-veld. Door de aanroep van setechochar kunnen we er voor zorgen dat er tijdens het intikken alleen sterretjes in beeld verschijnen, zoals gebruikelijk bij password-velden. Is de sleutel eenmaal geraden, dan halen we het tekstveld weg door een aanroep van de methode setvisible. Minimum/maximum thermometer In listing 13 staat de tekst van een programma dat het gedrag vertoont van een blz. 86 maximum/minimum-thermometer. Met een scrollbar kan de gebruiker de temperatuur instellen. De maximale en minimale waarde ooit behaald wordt op het scherm getoond. Als de gebruiker op de reset -toets drukt, worden maximum en minimum weer gelijk aan de huidige temperatuur, en kunnen we op nieuwe records gaan jagen. Kern van dit programma is de if-opdracht in adjustmentvaluechanged. Is de huidige waarde groter dan het maximum-tot-nu-toe? Dan moet het maximum worden aangepast! De variabelen minimum en maximum zijn in alle methoden nodig, en zijn dus gedeclareerd als objectvariabelen. Ook de scrollbar is in meerdere methoden nodig, en is dus een object-variabele. De button-variabele is echter alleen in de methode init nodig, en kan dus locaal in init worden gedeclareerd. 8.3 Grafiek en nulpunten van een parabool Beschrijving van de casus We gaan een wat groter programma maken, waarin zowel keuze als herhaling een belangrijke rol spelen. Maar bovendien interactiecomponenten met event-listeners, methoden en parameters, locale declaraties en object-variabelen. Ook komen enkele typische aspecten van het werken met double-waarden aan de orde. Het programma tekent een parabool. Een parabool is de grafiek van een wiskundige functie met het voorschrift y = a x 2 + b x + c, waarbij a, b en c nog nader te bepalen constanten zijn. In de applet kan de gebruiker de waarden van a, b en c invoeren in tekstvelden, om dan meteen de grafiek te zien veranderen. Een klassiek onderwerp in de middelbare-schoolwiskunde is het bepalen van de nulpunten van een parabool. Die kunnen gemakkelijk worden bepaald met de zogenaamde abc-formule (genoemd naar de constanten a, b en c die er in gebruikt worden). De applet gaat deze formule gebruiken om naast de grafiek ook de waarden van de nulpunten aan de gebruiker te tonen. Structuur van het programma Er zijn objectvariabelen voor drie tekstvelden, waarin de gebruiker de waarden van a, b en c in kan vullen. In de methode init worden de tekstveld-objecten gecreëerd. Bovendien zijn er de waarden a, b en c zelf, die in de methode init alvast een waarde krijgen, zodat de gebruiker al een parabool kan aanschouwen zonder eerst waarden te hoeven invullen. We gaan er voor zorgen dat de doublewaarden variabelen a, b, en c altijd overeenkomen met de ingevulde teksten in de drie tekstvelden. In init geven we de tekstvelden om te beginnen als startwaarde de (naar string geconverteerde) waarde van a, b, en c.

86 86 Keuze import java.awt.*; import java.awt.event.*; import java.applet.applet; 5 public class Thermo extends Applet implements ActionListener, AdjustmentListener { Scrollbar meter; int maximum, minimum; 10 public void init() { this.setlayout(new BorderLayout()); Button reset; reset = new Button("Reset"); 15 meter = new Scrollbar(Scrollbar.HORIZONTAL, 0, 1, -50, 50); this.add(reset, BorderLayout.SOUTH); this.add(meter, BorderLayout.NORTH); reset.addactionlistener(this); meter.addadjustmentlistener(this); 20 maximum = 0; minimum = 0; public void adjustmentvaluechanged(adjustmentevent e) 25 { int waarde; waarde = meter.getvalue(); if (waarde>maximum) maximum = waarde; if (waarde<minimum) 30 minimum = waarde; this.repaint(); public void actionperformed(actionevent e) 35 { maximum = meter.getvalue(); minimum = maximum; this.repaint(); public void paint(graphics gr) 40 { gr.drawstring("hoogste: " + maximum, 50, 90); gr.drawstring("laagste: " + minimum, 50,110); int w = this.getwidth(); double schaal = (w-32)/100.0; 45 gr.setcolor(color.blue); gr.fillrect(0, 30, (int)(16+schaal*(minimum+50)), 20); gr.setcolor(color.red); gr.fillrect((int)(w-16+schaal*(maximum-50)), 30, w, 20); 50 Listing 13: Thermo/Thermo.java

87 8.3 Grafiek en nulpunten van een parabool 87 import java.awt.*; import java.awt.event.*; import java.applet.applet; 5 public class Parabool extends Applet implements ActionListener { TextField abox, bbox, cbox; double a, b, c; 10 public void init() { a = 0.5; b = 2.0; c = -4.0; 15 abox = new TextField(""+a, 8); bbox = new TextField(""+b, 8); cbox = new TextField(""+c, 8); this.add(abox); 20 this.add(bbox); this.add(cbox); abox.addactionlistener(this); bbox.addactionlistener(this); cbox.addactionlistener(this); 25 public void actionperformed(actionevent e) { a = Double.parseDouble(abox.getText()); 30 b = Double.parseDouble(bbox.getText()); c = Double.parseDouble(cbox.getText()); this.repaint(); 35 public void paint(graphics gr) { this.oplossingen(gr); gr.setcolor(color.red); this.assen(gr); 40 gr.setcolor(color.blue); this.grafiek(gr); private double parabool(double x) 45 { return a*x*x + b*x + c; Listing 14: Parabool/Parabool.java, deel 1 van 2

88 88 Keuze private void oplossingen(graphics gr) 50 { double discriminant, noemer, wortel; discriminant = b*b-4*a*c; noemer = 2*a; 55 if (noemer==0) gr.drawstring("rechte lijn!", 50,50); else if (discriminant<0) gr.drawstring("geen nulpunten", 50, 50); else if (discriminant==0) 60 gr.drawstring("een nulpunt: " + -b/noemer, 50, 50); else { wortel = Math.sqrt(discriminant); gr.drawstring("twee nulpunten: " + + (-b-wortel)/noemer + " en " 65 + (-b+wortel)/noemer, 50, 50); private void assen(graphics gr) 70 { gr.drawline(0,250,500,250); gr.drawline(250,0,250,500); 75 private void grafiek(graphics gr) { int xpixel, ypixel, oldy; double xwaarde, ywaarde, schaal; 80 schaal = 0.03; oldy = 0; for (xpixel=-1; xpixel<500; xpixel++) { xwaarde = (xpixel-250)*schaal; 85 ywaarde = parabool(xwaarde); ypixel = (int) (250-(ywaarde/schaal)); if (xpixel>=0) gr.drawline(xpixel-1, oldy, xpixel, ypixel); 90 oldy = ypixel; Listing 15: Parabool/Parabool.java, deel 2 van 2

89 8.3 Grafiek en nulpunten van een parabool 89 Figuur 16: De applet Parabool in werking

90 90 Keuze Elke keer als de gebruiker in een van de tekstvelden op Enter drukt, wordt de methode actionperformed aangeroepen (omdat actionperformed is gedefinieerd in de klasse Parabool, die dat beloofd had met implements ActionListener, en waardoor elk Parabool-object zich dus als action-listener kan gedragen; zo ook het object this, wat immers een Parabool is, en die daarom dus als action-listener kan worden gekoppeld aan de tekstvelden). Als reactie daarop maken we de waarde van a, b en c weer gelijk aan de double-versie van de, inmiddels mogelijk gewijzigde inhoud van de tekstvelden. Nadat actionperformed de waarden van a, b en c weer up-to-date heeft gemaakt, roept hij repaint aan, die een aanroep van paint forceert. In de methode paint moet het eigenlijke werk gaan gebeuren: het bepalen van de nulpunten, en het tekenen van de grafiek. Omdat dat hele verschillende taken zijn, maken we aparte methoden voor deze twee dingen, en een derde methode voor het tekenen van de x-as en de y-as. Daardoor blijft de methode paint mooi overzichtelijk: het enige wat hier gebeurt is het kiezen van de kleuren, en het aanroepen van de drie methoden die het eigenlijke werk doen. Deze methoden krijgen het Graphics-object dat in paint beschikbaar is als parameter mee, zodat ze zelf dingen op het scherm kunnen tekenen. De nulpunten In de methode nulpunten kunnen we ons nu helemaal op het uitrekenen van de nulpunten concentreren, zonder gedoe met tekstvelden. We kunnen er op vertrouwen dat de variabelen a, b en c de benodigde constanten bevatten. De nulpunten kunnen we uitrekenen met behulp van de abc-formule, maar dan moeten er natuurlijk wel nulpunten zijn. In de abc-formule wordt de wortel getrokken uit b 2 4ac, dus als die waarde negatief is, zijn er geen oplossingen. Bovendien wordt er gedeeld door 2a, dus als die waarde 0 is moet er ook speciale actie ondernomen worden. Met if-opdrachten worden al deze gevallen uitgesplitst, waarna in elk geval een toepasselijke melding op het scherm kan worden gezet. De grafiek Basisidee van het tekenen van de grafiek is dat we voor alle mogelijke x-waarden de bijbehorende y-waarde berekenen. Op dat punt zouden we een stipje kunnen zetten: for (xpixel=0; xpixel<500; xpixel++) { ypixel = this.parabool(xpixel); gr.fillrect(xpixel, ypixel, 1, 1); Maar als de grafiek erg steil loopt, en y-waarden voor aangrenzende x-waarden meer verschillen dan de dikte van de stippen, dan komen de stippen los te staan. Beter is het daarom, om in plaats van een stip een lijn te trekken vanaf het vorige berekende punt. De y-waarde van het punt wordt daarom na gebruik bewaard in de variabele oldy voor de volgende ronde. Met een if-opdracht zorgen we ervoor om de eerste keer geen lijn te trekken (want dan was er geen vorige ronde, en heeft oldy dus nog geen zinvolle waarde). De oude x-waarde hoeven we niet apart te bewaren, want die is natuurlijk x 1. for (xpixel=-1; xpixel<500; xpixel++) { ypixel = this.parabool(xpixel); if (xpixel>=0) gr.drawline(xpixel-1, oldy, xpixel, ypixel); oldy = ypixel; De compiler is nogal streng, en blijft zeuren dat variable oldy may not have a value when used. Dat is omdat de compiler wel ziet dat in de while-body oldy gebruikt lijkt te worden voordat hij een waarde krijgt, maar niet slim genoeg is om te doorzien dat die if-opdracht dat nou juist voorkomt. Om de compiler tevreden te stellen geven we oldy voorafgaand aan de while-opdracht zomaar een waarde. Schaling Het interval [0, 500] is niet het interessantste gebied om een parabool te bekijken, althans niet voor de waarden van a, b en c die je als eerste te binnen schieten. Interessanter is bijvoorbeeld het interval [ 7, 7]. Daarom wordt de berekening van de functiewaarde niet gedaan met de waarde van xpixel, maar met de verschoven en opgeschaalde versie xwaarde daarvan. De verkregen

91 8.4 Exceptions 91 ywaarde wordt teruggeschaald, teruggeschoven, en er wordt bovendien gecorrigeerd voor het Javacoördinatensysteem dat de verkeerde kant oploopt. t Is een beetje lastig onder woorden te brengen; bekijk de formules in listing 14 en reken maar na. blz Exceptions Het afhandelen van fouten Bij het uitvoeren van methodes kunnen er uitzonderlijke omstandigheden zijn die het onmogelijk maken dat de methode compleet wordt uitgevoerd. In dat geval is er sprake van een exception. Zo n exception wordt door de methode opgeworpen, en het is dan aan de aanroeper om daar een oplossing voor te vinden. Een voorbeeld van een methode die een exception opwerpt, is de methode parseint. Deze werpt een exception op als de aangeboden string iets anders dan cijfers bevat. In dat geval kan het programma geen normale verdere doorgang vinden. De try-catch opdracht Je zou kunnen vermijden dat er exceptions ontstaan, door vooraf te controleren of aan alle voorwaarden is voldaan (in het geval van parseint: of de aangeboden string uitsluitend cijfer-tekens bevat). Maar dan doe je dubbel werk, want parseint doet die controle nogmaals. Beter is het om te reageren op het optreden van de exception. Dat gebeurt met de speciale try-catch-opdracht. Je kunt de aanroep die mogelijkerwijs een exception zal opwerpen in de body van een try-opdracht zetten. In het geval dat er inderdaad een exception optreedt, gaat het dan verder in de body van het catch-gedeelte. Gaat echter alles goed, dan wordt het catch-gedeelte overgeslagen. Bijvoorbeeld: try { n = Integer.parseInt(s); uitvoer.settext("kwadraat van" + n + "is" + n*n); catch (Exception e) { uitvoer.settext( s + " is geen getal"); In het catch-gedeelte wordt de opgeworpen exception als het ware opgevangen. Achter het woord catch moet een soort parameter worden gedeclareerd. Via deze parameter is te achterhalen wat er precies fout is gegaan. In het geval van parseint is dat zo ook wel duidelijk, maar de parameter moet toch gedeclareerd worden, ook als we hem niet gebruiken. In de body van try kunnen meerdere opdrachten staan. Bij de eerste exception gaat het echter verder bij catch. De rest van de opdrachten achter try mag er dus van uitgaan dat er geen exception is opgetreden. Let op dat de bodies van het try- en het catch-gedeelte tussen accolades moeten staan, zelfs als er maar één opdracht in staat. (Dat is wel onlogisch, want bij opdrachten zoals if en while mogen in die situatie de accolades worden weggelaten.) Het type Exception, waarvan achter catch een variabele wordt gedeclareerd, is een klasse met allerlei subklassen: NumberFormatException, NullPointerException, InterruptedException enzovoorts. Deze verschillen in het soort details dat je over de exception kunt opvragen (door het aanroepen van methodes van het exception-object). Ben je niet geïnteresseerd in de details, dan kun je ruwweg een Exception-object declareren, maar anders kun je het object van het juiste type declareren. Het is toegestaan om bij één try-opdracht meerdere catch-gedeeltes te plaatsen. Die kun je dan parameters van verschillend (sub-)type geven. Bij het optreden van een exception wordt, afhankelijk van het type exception, de juiste afhandeling gekozen. Een voorbeeld vormt het lezen van files (dit wordt uitgebreider besproken in sectie Fouten die daarbij kunnen optreden zijn dat de file in het geheel niet bestaat (FileNotFoundException), of dat de file weliswaar bestaat, maar toch niet gelezen kan worden (IOException). Met één try-catch-catch opdracht kun je op beide mogelijkheden anticiperen: try { /* opdrachten om de file te lezen */ catch (FileNotFoundException fnfe) { uitvoer.settext("de file bestaat niet"); catch (IOException ioe) { uitvoer.settext("de file is onleesbaar"); Bij sommige typen exception, zoals NumberFormatException en NullPointerException, is het

92 92 Keuze opvangen vrijwillig. Bij andere typen is het verplicht, en controleert de compiler dat je dit niet vergeet. Als je desondanks geen speciale actie wilt ondernemen als reactie op de exception, kun je de body van catch leeg laten, maar de try-catch moet er wel staan. Opgaven 8.1 Teller-applet a. Schrijf een applet dat bestaat uit een button met het opschrift meer, en daaronder de waarde van een teller. Aan het begin heeft de teller de waarde 0. Elke keer als de gebruiker op de button drukt, wordt de waarde van de teller opgehoogd. b. Verander het programma nu zo, dat de teller niet met drawstring wordt getekend, maar dat de teller een TextField-object is. c. De gebruiker kan het TextField-object ook zelf veranderen, door daar een ander getal in te tikken (en daarna op de Enter-toets te drukken). Maak het programma nu zo, dat de applet bij het ingevoerde getal verder begint te tellen als de meer-button weer wordt gebruikt. 8.2 Parabool Breid het programma parabool zo uit, dat de waarden van de nulpunten ook worden getoond in de gevallen dat a en/of b en/of c de waarde 0 hebben. 8.3 Foutmelding else without if Bij het compileren van onderstaand programma geeft de compiler de foutmelding IfDemo.java:7: else without if. Hoe komt dat? class IfDemo extends Applet { public void paint(graphics gr) { int getal; getal = 5; if (getal<0); gr.drawstring("negatief", 10, 10); else gr.drawstring("positief", 10, 10); 8.4 Range-test Iemand wil testen of een variabele x tussen 10 en 20 ligt, en schrijft: blz. 82 blz. 86 if (10 <= x <= 20) gr.drawstring("ja", 10, 10); De compiler geeft echter een foutmelding. Waarom? Hoe moet het dan wel? 8.5 Equals en == Welk van de volgende uitspraken is waar: a. Als s==t geldt, dan geldt ook s.equals(t) b. Als s.equals(t) geldt, dan geldt ook s==t 8.6 Opdrachten uitsparen Iemand schrijft de volgende opdrachten in een programma: s1 = invoer1.gettext(); s2 = invoer2.gettext(); n1 = Integer.parseInt(s1); n2 = Integer.parseInt(s2); r = n1*n2; uitvoer.settext("product: " + r ); Kun je hetzelfde ook met één opdracht doen? Zo nee, waarom niet; zo ja, hoe dan? 8.7 Objectvariabelen uitsparen In het programma Cirkel in listing 11 wordt de variabele kleiner die de verwijzing naar het Button-object gedeclareerd als object-variabele. In het programma Thermo in listing 13 is de

93 8.4 Exceptions 93 Button-variabele reset echter een lokale variabele in de methode init. Wat is de reden voor dit verschil? 8.8 Gelijkheid aan true Bekijk het programma Geheim in listing 12. blz. 84 Mag je in plaats van if (open) ook schrijven: if (open==true)? Zo nee, waarom niet; zo ja, is dat beter? 8.9 Pyramide Schrijf een applet dat een pyramide van cirkeltjes weergeeft. Er zijn bovendien twee knoppen: indrukken van less verwijdert een rij, indrukken van more voegt een rij toe. Initieel zijn er 4 rijen, minimaal 1 en maximaal 10.

94 94 Hoofdstuk 9 Objecten en klassen blz. 61 blz Klasse: beschrijving van een object Object: groepje variabelen met methoden Een object is een groepje variabelen dat bij elkaar hoort. Je realiseert je dat niet voortdurend, maar iedere keer als je een object creëert met new, maak je een nieuw groepje variabelen. Bijvoorbeeld, als je een nieuwe button maakt met de expressie new Button("druk hier"); dan ontstaat er een groepje variabelen waarin alle noodzakelijke administratie voor een button wordt bijgehouden: de afmetingen, de positie op het scherm, de kleur, de status (ingedrukt of niet), de bijbehorende action-listener, het opschrift, enzovoorts. Met die variabelen heb je echter niet direct te maken: om een button-object te kunnen gebruiken in je programma hoef je niet eens te weten welke variabelen er precies in een button-object zitten, en hoe die heten. Wel is het van belang om te weten welke methoden er zijn, die het object kunnen manipuleren. Voor een button-object zijn dat onder ander de methode setlabel (om het opschrift te veranderen) en de methode addactionlistener. Via deze methoden verander je indirect de variabelen die deel uitmaken van het object, zonder daar echter direct een toekenningsopdracht aan te doen. Klasse: declaratie van variabelen plus definitie van methoden De klasse-definitie is een beschrijving van de objecten van die klasse. In de klasse-definitie staan daarom: een declaratie van de variabelen waaruit het object bestaat een definitie van de methoden waarmee het object gemanipuleerd kan worden Behalve voor de klassen in de Java-bibliotheek geldt dit ook voor de klassen die je zelf schrijft. Bijvoorbeeld, als je een klasse Thermo hebt gedefinieerd die begint met: class Thermo extends Applet { private Scrollbar meter; private int min, max; dan bestaat elk Thermo-object dat daarna wordt gemaakt uit drie variabelen: een verwijzing naar een scrollbar en twee gehele getallen. Bovendien bevat elk Thermo-object, omdat de klasse een extensie is van Applet, alle variabelen die in de klasse pplet al waren gedeclareerd. Zo n Thermoobject wordt aangemaakt door de browser, als deze een webpagina tegenkomt waarin een applet is opgenomen met de (bytecode van) deze klasse als code. Terwijl je het programma schrijft heb je misschien het gevoel dat van elk van de variabelen die bovenin de klasse zijn gedeclareerd, er maar één aanwezig is. Dat is ook wel zo binnen één object, maar het kan best zo zijn dat de browser twee exemplaren van het applet aanmaakt. Dat gebeurt als er tweemaal een <APPLET>-tag in de html-file staat met dezelfde code; we hebben daarvan een voorbeeld gezien in listing 7, waar met één html-file twee Rechthoek-applets werden aangemaakt. Elk van de aangemaakte applets heeft zijn eigen setje variabelen. Objecten en object-verwijzingen Om een idee te krijgen van hoe objecten in het geheugen worden opgebouwd, bekijken we in detail wat er gebeurt bij het runnen van het programma Thermo (de minimum/maximum-thermometer in listing 13). Bij het verwerken van de <APPLET>-tag creëert de browser een Thermo-object. De browser beheert een verwijzing, en die gaat naar het nieuwe object wijzen. Het Thermo-object bestaat uit de

95 9.1 Klasse: beschrijving van een object 95 variabelen die in de klasse zijn gedeclareerd: twee int-variabelen min en max, en een verwijzing naar een Scrollbar-object, genaamd meter. Maar omdat de klasse Thermo een extensie is van de klasse Applet, bestaat het Thermo-object voor een deel ook uit de variabelen die al in de klasse Applet waren gedeclareerd. Dan roept de browser de methode init aan. In die methode is een locale variabele gedeclareerd: een verwijzing naar een Button-object, genaamd reset. Lokale variabelen staan ook in het geheugen, maar maken geen deel uit van een object. De situatie in het geheugen kun je je nu voorstellen als: De methode init heeft een Thermo-object onder handen gekregen, die te bereiken is via de naam this. Je kunt this beschouwen als een soort variabele (alleen mag je er geen nieuwe waarde aan toekennen). In de schets is niet verder uitgewerkt welke variabelen precies deel uitmaken van het van Applet geërfde deel van het Thermo-object. Dat kan je en hoef je niet te weten bij het gebruiken van bibliotheek-klassen. Let op dat de lokale variabele reset en de variabele meter die deel uitmaakt van het Thermo-object verwijzingen naar objecten zijn, maar op dit moment nog nergens naar wijzen. Dat gebeurt pas als de toekenningsopdrachten in de body van de methode init zijn uitgevoerd: reset = new Button("Reset"); meter = new Scrollbar(Scrollbar.HORIZONTAL,0,1,-50,50); max = 0; min = 0; Hiermee onstaat de volgende situatie: De vier variabelen waaraan een toekenning is gedaan hebben nu een waarde. In het geval van min en max is dat een int-waarde, in het geval van reset en meter is het een object-verwijzing naar een nieuw gecreëerd object. Die nieuwe objecten bevatten de variabelen die zijn gedeclareerd in respectievelijk de klasse Button en Scrollbar, maar dat is in de schets niet verder uitgewerkt. In een methode mogen zowel de lokale variabelen worden gebruikt (hier is dat reset), als de object-variabelen van het object onder handen (hier: meter, min en max in het object waar this naar wijst). In het geval dat een lokale variabele hetzelfde zou heten als een object-variabele, heeft de lokale variabele voorrang. In de volgende twee opdrachten wordt de methode add aangeroepen, met de twee zojuist gecreëerde objecten als parameter: this.add(reset);

96 96 Objecten en klassen this.add(meter); De methode add is een methode van Applet. De verwijzing this wijst weliswaar naar een Thermoobject, maar een deel van dat object (het geërfde deel) is een Applet-object. Het is dat (deel van het) object dat door add onder handen wordt genomen. We hebben add niet zelf geschreven, en het is voor ons als applicatie-programmeur niet precies bekend welke variabelen in het Applet-object door add worden veranderd, omdat we die variabelen niet kennen. Wat er precies gebeurt is dus een beetje giswerk. Je kunt je voorstellen dat het Appletobject een kopie bewaart van de als parameter meegegeven object-verwijzingen, zodat altijd alle met add toegevoegde componenten beschikbaar zijn voor later gebruik. De situatie wordt dus ongeveer als volgt. (Het rijtje variabelen heet in het echt vast niet added, maar we kunnen, hoeven en willen niet weten wat de naam dan wel is). In de laatste twee opdrachten in de body van init worden er event-listeners gekoppeld aan de button- en scrollbar-objecten: reset.addactionlistener(this); meter.addadjustmentlistener(this); Ditmaal wordt dus niet het this object, maar de twee andere objecten onder handen genomen. Het is alweer giswerk wat hier precies gebeurt. Het is echter redelijk om te verwachten dat in beide gevallen een kopie van de aangeboden parameter wordt bewaard voor later gebruik. Als in de toekomst de button ingedrukt zou worden, dan weet het button-object op die manier nog, van welk object de methode actionperformed aangeroepen moet worden. Omdat dit de laatste opdrachten in de body van init waren, worden nu de lokale variabelen opgeruimd (de variabele reset, en ook de pseudo-variabele this verliest zijn betekenis). De situatie die na afloop van het uitvoeren van init is ontstaan, ziet er als volgt uit: De lokale variabelen zijn weliswaar opgeruimd, maar dat waren alleen maar verwijzingen. De objecten waar die verwijzingen naar verwezen zijn nog wel degelijk beschikbaar: ze zijn via-via te bereiken, omdat er door de aanroep van add een extra verwijzing naar bewaard is gebleven.

97 9.2 Toepassing: Bewegende deeltjes 97 Programma s met meerdere klassen Tot nu toe hebben we steeds programma s geschreven die uit één klasse bestonden; steeds was de enige klasse een extensie van Applet, en verwachtten we dat de browser één object van die klasse zou creëren. In de programma s speelden weliswaar naast dat object ook nog andere objecten een rol, maar het type van die objecten was steeds een bibliotheek-klasse (Button, Scrollbar, Graphics, String enzovoorts). In de nu volgende toepassing verandert dat. We gaan behalve de welbekende extensie van Applet nog meer klassen maken, en in het programma gaan we objecten gebruiken die die klassen als type hebben. Op deze manier kunnen we zelfgemaakte objecten naar eigen ontwerp in het programma gebruiken. 9.2 Toepassing: Bewegende deeltjes Beschrijving van de casus Het programma dat we in deze sectie zullen ontwikkelen is een simulatie van bewegende deeltjes in een begrensde ruimte. Je kunt denken aan moleculen in een afgesloten vat, of aan biljartballen op een (wrijvingsloze) tafel, of aan ratten in een val. Voor het gemak zullen we de deeltjes afbeelden als kleine gekleurde cirkels, en de ruimte als grote rechthoek. In het runnende programma zullen drie ruimtes zichtbaar zijn, ieder met verschillende afmetingen. In iedere ruimte bevinden zich drie deeltjes, ieder met een verschillende kleur. De deeltjes kunnen bewegen. Ze doen één stap als de gebruiker op de button met het opschrift stap drukt. Er is bovendien een button met het opschrift start. Als de gebruiker daarop drukt, blijven de deeltjes bewegen, en ziet de gebruiker dus een animatie. Het opschrift van deze button verandert op dat moment ook in stop, en met nog een druk op deze knop kan de gebruiker de animatie weer stopzetten. In figuur 17 is een snapshot van het programma te zien. De klasse Ruimte In het window van de simulatie (zie figuur 17) zijn vijf dingen te zien: drie ruimtes met deeltjes, en twee buttons. Het zou het gemakkelijkste zijn als we deze vijf dingen op dezelfde manier konden maken: creëren van een nieuw object met new, en deze aan de display toevoegen met add. Voor de buttons is dat geen probleem, maar er bestaat natuurlijk geen bibliotheek-klasse Ruimte. Toch is dat geen bezwaar, want we kunnen zo n klasse zelf maken. We hoeven die klasse gelukkig niet van de grond af aan op te bouwen. We kunnen namelijk voortborduren op een al wel bestaande klasse: Canvas. Een Canvas is een interactie-component, net zoals Button en Scrollbar. Het kan daarom met add aan de applet worden toegevoegd. Een Canvas is een soort schilderslinnen, waarop je een tekening kunt maken. Een canvas heeft afmetingen en een achtergrondkleur, dus daar hoeven we ons niet meer druk om te maken. In de klasse Ruimte, die een extensie wordt van Canvas, hoeven we alleen maar extra variabelen toe te voegen die de gekleurde deeltjes beschrijven, en methoden die die deeltjes tekenen en laten bewegen. De klasse Deeltje Elk bewegend deeltje dat in dit programma een rol speelt, heeft een aantal eigenschappen: een kleur, een positie binnen de ruimte, en een bewegingsrichting. Voor elk van deze eigenschappen kunnen we variabelen declareren. De kleur is een Color-verwijzing, de positie bestaat uit twee int-waarden x en y, en de bewegingsrichting kunnen we beschrijven met de afstand dx en dy die het deeltje bij elke stap moet bewegen. Elk deeltje moet zijn eigen kleur, positie en richting krijgen. We maken daarom een aparte klasse Deeltje, waarin deze variabelen zijn gedeclareerd. In het programma kunnen we dan voor elk deeltje een Deeltje-object creëren, waarin de benodigde variabelen gebundeld zijn. De klasse Deeltje is geen extensie van een al bestaande klasse; we maken hem puur vanuit het niets, en een Deeltje-object bestaat dan ook alleen uit de variabelen die we zelf in de klasse declareren. Opzet van de klassen Het programma zal dus gaan bestaan uit drie klassen: de extensie van Applet die we ditmaal Simulatie zullen noemen, en de extra klassen Ruimte en Deeltje. We bekijken nu eerst de opzet van de klassen voor wat betreft de declaraties van object-variabelen; de methoden volgen later. class Simulatie extends Applet { Button stap, auto;

98 98 Objecten en klassen Ruimte r1, r2, r3; // methoden nog toe te voegen class Ruimte extends Canvas { Deeltje d1, d2, d3; // methoden nog toe te voegen class Deeltje { Color kleur; int x, y, dx, dy; // methoden nog toe te voegen Er moeten nu objecten gemaakt worden met deze klassen als type, zodat er een heel netwerk van verwijzingen ontstaat. De gewenste situatie kun je je als volgt voorstellen: De browser creëert een Simulatie-object. Dit bestaat voor een deel uit de geërfde variabelen van Applet, en voor een deel uit de vijf in de klasse Simulatie extra gedeclareerde variabelen: stap, auto, r1, r2 en r3. Het is de taak van de methode init om deze variabelen naar nieuw te maken objecten te laten wijzen. Dat gebeurt zoals gewoonlijk door middel van toekenningsopdrachten: stap = new Button("Stap"); auto = new Button("Start"); r1 = new Ruimte(iets); r2 = new Ruimte(iets); r3 = new Ruimte(iets); Hierdoor gaan stap en auto naar nieuw gecreëerde Button-objecten wijzen, en r1, r2 en r3 naar nieuw gecreëerde Ruimte-objecten. De Ruimte-objecten bestaan voor een deel uit de geërfde variabelen van Canvas, en voor een deel uit de drie in de klasse Ruimte extra gedeclareerde variabelen: d1, d2 en d3. Deze wijzen echter nog niet naar objecten. De constructormethode van Ruimte Bij het evalueren van een new-expressie gebeuren er twee dingen: er wordt ruimte aangemaakt voor het nieuwe object; de constructormethode, zoals gedefinieerd in de bijbehorende klasse, wordt aangeroepen. Alle opdrachten die in de constructormethode staan, worden dus automatisch uitgevoerd op het moment dat het object gecreërd wordt. Dat maakt de constructormethode de ideale plaats om de object-variabelen een beginwaarde te geven, en eventuele andere voorbereidingen te treffen. De constructormethode onderscheidt zich op twee manieren van de andere methoden die in een klasse worden gedefinieerd: de naam van de constructormethode is hetzelfde als de naam van de klasse, en begint dus

99 9.2 Toepassing: Bewegende deeltjes 99 volgens de stijl-conventies voor variabele-namen bij wijze van uitzondering met een hoofdletter; de constructormethode heeft geen resultaattype, zelfs niet void; de constructormethode kan immers alleen maar via de speciale new-expressie worden aangeroepen, en die heeft automatisch het nieuw gecreëerde object als resultaatwaarde. In onze nieuwe klasse Ruimte gaan we ook een constructormethode schrijven, waarin het Ruimteobject, direct nadat het is gecreëerd, wordt klaargezet voor gebruikt. Eerste taak van de constructormethode is om de afmetingen van het Ruimte-object vast te stellen. Dat gebeurt door een aanroep van setsize, die is geërfd van de klasse Canvas (de klasse Ruimte is een extensie van Canvas). Bij die aanroep moeten de gewenste breedte en hoogte worden opgegeven. Die willen we echter in de drie verschillende ruimtes verschillende waarden geven. Daarom geven we de constructormethode van Ruimte twee parameters, waarmee de breedte en hoogte kunnen worden gespecificeerd. Bij de creatie van de ruimte-objecten (in de init-methode van de klasse Simulatie) kan de gewenste grootte dan per object verschillend worden gekozen. De constructormethode van Ruimte begint dus als volgt (let op: er staat in constructormethodes geen resultaattype tussen public en de methode-naam): public Ruimte(int breed, int hoog) { this.setsize(breed, hoog); Volgende taak is het instellen van de achtergrondkleur. Ook dit kan via een van Canvas geërfde methode: setbackground. We maken de achtergrond van elk ruimte-object hetzelfde, namelijk een zelfgemengde lichtgele kleur: this.setbackground(new Color(255,255,128)); Nu wordt het tijd om de object-variabelen van Ruimte een waarde te geven: de verwijzingen naar Deeltje-objecten d1, d2 en d3. Deze verwijzingen wijzen uit zichzelf nog nergens naar, dus de Deeltje-objecten moeten nog worden gecreëerd: d1 = new Deeltje(iets ); d2 = new Deeltje(iets ); d3 = new Deeltje(iets ); Direct bij de creatie van een Ruimte-object, worden dus ook de drie bijbehorende Deeltje-objecten gemaakt. De klasse Deeltje heeft ook een constructormethode, waarin de variabelen van het nieuwe Deeltje-object een waarde krijgen. We verplaatsen onze aandacht daarom nu eerst even naar de methoden in de klasse Deeltje. De methoden van Deeltje De klasse Deeltje is nergens een extensie van. Een Deeltje-object bestaat dus alleen maar uit de variabelen die in de klasse Deeltje zijn gedeclareerd: x, y, dx, dy en kleur. De vraag die zich nu opdringt is wat we met zo n Deeltje-object eigenlijk willen doen; met andere woorden: welke methoden zijn er nodig in de klasse Deeltje? Het is niet zo moeilijk om een paar nuttige handelingen met Deeltje-objecten te bedenken: een constructormethode, die de variabelen een beginwaarde geeft een methode doestap, die de plaats van het deeltje zodanig verandert, dat het deeltje een stap doet in de door de richtingsvector aangegeven richting een methode teken, die het deeltje intekent op een als parameter te specificeren Graphicsobject. De methode teken krijgt een Graphics-object als parameter, zodat hij grafische methoden kan aanroepen. We gaan deze methode later aanroepen op het moment dat een deeltje getekend moet worden, maar dat is van later zorg. Voor het eigenlijke tekenen van het deeltje zijn alleen zijn eigen kleur en positie van belang; die worden in de body van teken dan ook gebruikt. public void teken(graphics gr) { gr.setcolor(kleur); gr.filloval(x-3, y-3, 7, 7); Die variabelen moeten dan natuurlijk wel een waarde hebben. De ideale plaats om dat te doen is de constructormethode. Door de gewenste kleur en startpositie als parameter van de constructor-

100 100 Objecten en klassen blz. 108 methode mee te geven, kan elk deeltje zijn eigen kleur en positie krijgen. Ook de snelheid van het deeltje wordt bij de constructie vastgelegd: public Deeltje(Color k, int x0, int y0, int dx0, int dy0 ) { kleur = k; x = x0; y = y0; dx = dx0; dy = dy0; De methode doestap is het interessantste. Door aanroep van deze methode gaat het deeltje bewegen. Omdat zowel de huidige plaats als de bewegingsrichting in het Deeltje-object zijn opgeslagen, hoeft deze methode geen parameters te krijgen. De beweging vindt in principe plaats door de variabelen x en y te vermeerderen met de bewegings-waarden dx en dy: public void doestap(iets ) { x += dx; y += dy; Het kan echter gebeuren (bij negatieve bewegingsrichting) dat de coördinaten negatief worden. In dat geval is het gewenst dat het deeltje terugkaatst tegen de muur. De coördinaat wordt dan juist zo positief als hij negatief geworden was. Maar door het kaatsen verandert ook de bewegingsrichting: bij het kaatsen tegen de zij-muren klapt het teken van de bewegingsrichting om: beweging naar links wordt beweging naar rechts, en omgekeerd. Het kaatsen tegen de linker- en bovenmuur wordt behandeld door de volgende opdrachten: if (x<0) { x = -x; dx = -dx; if (y<0) { y = -y; dy = -dy; Het deeltje kan echter ook bij de rechter- en ondermuur dreigen uit beeld te raken, en ook hier zouden we het deeltje willen laten terugkaatsen. Om dat te kunnen testen, moet het deeltje echter weten hoe groot de ruimte is waarin hij beweegt, en aan de object-variabelen x, y, dx, dy en kleur is dat niet te zien! Voor dat doel gaan we aan doestap een parameter meegeven die de afmetingen van de ruimte waarin het deeltje beweegt aangeeft. Een Dimension-object is precies wat we nodig hebben, al moeten de zich daarin bevindende breedte en hoogte nog met een cast naar int geconverteerd worden: public void doestap(dimension hok) { int maxx, maxy; maxx = (int)hok.getwidth(); maxy = (int)hok.getheight(); De aldus verkregen waarde van maxx kunnen we nu gebruiken in een derde test in de methode doestap: if (x>=maxx) iets Het is wel leuk om zelf even na te denken hoe deze situatie moet worden opgevangen (de oplossing staat in listing 18). De methoden van Simulatie De klasse Simulatie wordt alleen maar gebruikt voor de creatie van één object, maar is evengoed belangrijk. Het object modelleert het totale applet, en heeft dezelfde opbouw als alle eerdere programma s: een methode init om de interactie-componenten te creëren, en een event-listener om de acties van de gebruiker af te handelen. Een methode paint is ditmaal niet nodig, omdat er niets op de achtergrond van het applet wordt getekend. De twee buttons en de drie canvas-objecten tekenen zichzelf namelijk automatisch. Een deel van de methode init hebben we hierboven al besproken: creatie van de vijf objecten waar

101 9.2 Toepassing: Bewegende deeltjes 101 de object-variabelen naar toe moeten wijzen. public void init() { stap = new Button("Stap"); auto = new Button("Start"); r1 = new Ruimte(100,196); r2 = new Ruimte(196,150); r3 = new Ruimte(60,75); Bij de creatie van de Ruimte-objecten geven we de breedte en de hoogte van de ruimte mee, conform de definitie van de constructormethode van Ruimte zoals we die hebben gedefinieerd. Zoals gewoonlijk moeten de objecten ook aan de applet worden toegevoegd this.add(stap); this.add(auto); this.add(r1); this.add(r2); this.add(r3); De twee buttons krijgen omgekeerd het totale applet-object als action-listener toegewezen: stap.addactionlistener(this); auto.addactionlistener(this); De action-listener moet natuurlijk wel beloofd worden (met implements ActionListener in de klasse-header), en de belofte moet worden nagekomen. Bij de afhandeling van het indrukken van de button testen we welke van de twee buttons is ingedrukt. public void actionperformed(actionevent e) { if (e.getsource()==stap) this.doestap(); else iets Voor het gemak zetten we de eigenlijke afhandeling van het indrukken van de stap-button in een aparte methode. Die methode noemen we doestap (niet te verwarren met de gelijknamige methode in de klasse Deeltje). In de methode doestap van Simulatie zou de methode doestap van alle Deeltje-objecten kunnen worden aangeroepen. We gaan dat echter niet voor alle negen Deeltjeobjecten apart doen. In plaats daarvan delegeren we het werk aan de drie Ruimte-objecten, door daarvan een methode aan te roepen, die we (alweer!) doestap noemen. Niet vergeten dat we die straks nog moeten schrijven... private void doestap() { r1.doestap(); r2.doestap(); r3.doestap(); Deze methode is private gemaakt, omdat hij alleen maar door een van zijn collega-methoden wordt aangeroepen, en niet direct van buitenaf. Nadat de drie Ruimte-objecten aldus een stap hebben gedaan, moeten we ervoor zorgen dat het resultaat zichtbaar wordt. Daarom roepen we voor elk van de drie ruimtes de methode repaint aan, die de Ruimte-objecten hebben geërfd van Canvas. r1.repaint(); r2.repaint(); r3.repaint(); De methoden van Ruimte Het enige wat ons nu nog te doen staat is het schrijven van de methoden van de klasse Ruimte. Met de constructormethode van Ruimte hadden we al een begin gemaakt: public Ruimte(int breed, int hoog) { this.setsize(breed, hoog); this.setbackground(new Color(255,255,128)); Hier worden ook de drie Deeltje-objecten van het Ruimte-object gecreëerd. Inmiddels weten we

102 102 Objecten en klassen nu ook wat daarbij als parameter meegegeven moet worden: een Color-object, en vier int s voor de startpositie en -snelheid. d1 = new Deeltje(Color.red, 30, 40, 10, 10); d2 = new Deeltje(Color.green, 100, 80, 5,-10); d3 = new Deeltje(Color.blue, 200, 60, 8, 2); We moesten niet vergeten om in de klasse Ruimte ook een methode doestap klaar te zetten. die hadden we immers aangeroepen vanuit de methode doestap van de klasse Simulatie. Het schrijven van deze methode is heel simpel: we zetten gewoon onze drie deeltjes aan het werk om een stap te doen. De deeltjes wilden de afmeting van de ruimte waarin ze zich bevinden als parameter meekrijgen. Die kunnen ze krijgen: de afmetingen van een Canvas (en dus ook van een Ruimte) zijn eenvoudig op te vragen met de methode getsize: public void doestap() { d1.doestap(this.getsize()); d2.doestap(this.getsize()); d3.doestap(this.getsize()); De laatste methode van de klasse Ruimte is paint. Het gaat om een herdefinitie van de gelijknamige methode van Canvas. Het doel hiervan is hetzelfde als we gewend zijn bij Applet: de methode paint wordt aangeroepen als het canvas getekend moet worden. Aanroep van paint kan bovendien worden afgedwongen door het aanroepen van repaint, iets wat we in Simulatie s doestap inderdaad hebben gedaan. Het tekenen van het schilderij op het canvas is ook al heel gemakkelijk: we laten onze drie deeltjes zichzelf tekenen, door aanroep van de methode teken die we daarvoor hadden klaargezet in de klasse Deeltje. public void paint(graphics gr) { d1.teken(gr); d2.teken(gr); d3.teken(gr); Hiermee is het programma bijna voltooid. Wat rest is de afhandeling van het indrukken van de tweede button. Maar dat is een onderwerp apart. 9.3 Animatie Automatische actie Door steeds maar op de Stap -button te blijven drukken, kunnen we de deeltjes laten blijven bewegen. Maar dat wordt op den duur vervelend; leuker zou het zijn als we lui achterover kunnen leunen, en de deeltjes automatisch blijven bewegen. Met andere woorden: als het programma zich als een tekenfilm, oftewel een animatie zou kunnen gedragen. Het leuke van Java is dat dat veel gemakkelijker kan dan in eerdere talen zoals Basic, Pascal, C en C++. Het kost eigenlijk maar een paar regels in het programma om het tot leven te wekken. Het mechanisme zit echter wel doortrapt in elkaar, dus let op dat je de draad niet kwijtraakt. De klasse Thread Kern van het animatie-mechanisme is de klasse Thread in package java.util. Als je een animatie wilt maken, moet je een object van deze klasse creëren; een Thread-object dus. Als parameter moet je daarbij een object meegeven; this is meestal een goede keuze. Je gaat dus als volgt te werk: Thread animatie; animatie = new Thread(this); Nadat het Thread-object gecreëerd is, kun je de animatie starten door de methode start aan te roepen: animatie.start(); Het Thread-object reageert daarop door op zijn beurt de methode run aan te roepen van het object dat bij creatie als parameter was meegegeven. Hoe weet het object zo zeker dat het meegegeven object een methode run kent? Wel, de compiler controleert dat het meegegeven object

103 9.3 Animatie 103 als type een Runnable klasse heeft. Daar zorgen we dus voor door in de klasse-header te beloven: implements Runnable, en die belofte na te komen door inderdaad een methode run te definiëren. Dit lijkt allemaal maar omslachtig. Waarom al die moeilijkdoenerij om de methode run aan te roepen? Dat kan toch ook direct door this.run(); te schrijven? Dat kan inderdaad, maar er is één verschil: het Thread-object roept de methode run aan, maar wacht niet totdat dat afgelopen is. De methode start zet de methode run aan het werk, en keert direct daarna terug naar de aanroeper. Vanaf dat moment gebeuren er dus twee dingen tegelijk: de methode run begint, maar de rest van het programma draait ook weer verder! Daar gaan we gebruik van maken, door in de methode run een opdracht steeds opnieuw te laten uitvoeren. De opdracht staat daarom in de body van een while-opdracht, met als voorwaarde iets wat altijd waar blijft: public void run() { while (1==1) this.doestap(); (In plaats van de altijd-ware voorwaarde 1==1 hadden we ook de boolean constante true kunnen gebruiken). Hiermee wordt de methode die vroeger werd aangeroepen als reactie op het indrukken van Stap automatisch steeds opnieuw aangeroepen. De methode sleep Maar wacht even, dat gaat wel erg snel: nu is het stuiteren van de deeltjes bijna niet meer te volgen. Na elke stap moet eigenlijk een korte pauze worden ingelast. Dat kan, en wel door aanroep van de statische methode sleep uit de klasse Thread. Als parameter krijgt deze methode het aantal milliseconden dat de pauze moet duren. We herzien dus de methode run: public void run() { while (true) { this.doestap(); Thread.sleep(50); De pauze van 50 milliseconden zorgt ervoor dat er 20 keer per seconde een stap wordt uitgevoerd, wat een mooie vloeiende animatie oplevert. Aanroep van sleep moet in een try-catch opdracht De aanroep van sleep, als die precies zo wordt gedaan als in de vorige paragraaf, geeft echter een foutmelding van de compiler: exception must be caught. Het is namelijk zo dat in uitzonderlijke omstandigheden het (haze-)slaapje ook onderbroken kan worden voordat de slaaptijd is verstreken. In dit programma zal dat niet gebeuren, omdat we die mogelijkheid helemaal niet gebruiken. Evengoed eist de compiler dat we toch rekening houden met de mogelijkheid. Het opvangen van dit soort uitzonderlijke omstandigheden (exceptions) moet gebeuren met een try-catch-opdracht, zoals besproken in sectie 8.4. try { Thread.sleep(50); catch (Exception e) { Controleren van de animatie Het Thread-mechanisme kunnen we gebruiken om het bewegen van de deeltjes automatisch te laten verlopen na het indrukken van de start-button. Het Thread-object wordt gecreëerd en gestart als reactie op het indrukken van de button: public void actionperformed(actionevent e) { if (e.getsource()==auto) { animatie = new Thread(this); animatie.start();

104 104 Objecten en klassen Om de animatie ook weer te kunnen stoppen, veranderen we op dat moment het opschrift van de button: auto.setlabel("stop"); De afhandeling van de auto-button moet, nu hij het opschrift Stop heeft, natuurlijk anders verlopen dan voorheen. Dat betekent dat we de body van actionperformed weer even moeten herzien. We gebruiken een boolean variabele beweging, en we zorgen ervoor dat die de waarde true heeft zolang de animatie loopt. Deze variabele wordt in de klasse gedeclareerd, en krijgt in init de waarde false. De afhandeling van het indrukken van de auto-button verloopt nu zo: public void actionperformed(actionevent e) { if (e.getsource()==auto) { if (beweging) { // stop de animatie beweging = false; auto.setlabel("start"); else { // start de animatie beweging = true; animatie = new Thread(this); animatie.start(); auto.setlabel("stop"); else enzovoort De animatie stopt natuurlijk niet door alleen maar een variabele de waarde false te geven. Maar we kunnen wel de methode run aanpassen. In plaats van de while-opdracht eeuwig te laten duren, kunnen we in de voorwaarde van de while-opdracht de boolean variabele beweging nauwlettend in de gaten houden: public void run() { while (beweging) { this.doestap(); try {Thread.sleep(50); catch (Exception e){ Normaal gesproken zal de voorwaarde van een while-opdracht niet veranderen als er in de body geen toekenningen aan worden gedaan. Maar we zitten nu een een situatie met twee parallel verlopende processen, die elkaar beïnvloeden: terwijl de animatie in run bezig is, kan de gebruiker op een knop drukken, en bij het afhandelen daarvan wordt de waarde van de variabele beweging gelijk aan false gemaakt. De waarde null Met een toekenningsopdracht kun je een object-verwijzing naar een object laten wijzen. Het komt soms voor dat je de verwijzing ongedaan wilt maken, dus dat de verwijzing juist niet naar een object moet wijzen. Voor dit doel is er een speciale constante: null. Dit is als het ware de nulwaarde voor verwijzingen; dit is de waarde die verwijzingen hebben als er nog nooit een toekenning aan is gedaan. Het aardige is dat je met een if-opdracht kunt testen of variabelen de waarde null hebben. Gebruikmakend daarvan, kunnen we het programma in de vorige paragraaf herschrijven, zo dat de boolean variabele beweging niet meer nodig is. In plaats daarvan gebruiken we de variabele animatie. Dat is een verwijzing naar het Thread-object. We gaan nu deze verwijzing naar null laten wijzen op het moment dat de gebruiker de Stop-knop indrukt. Om te weten of de animatie nog beweegt, kunnen we testen of de variabele animatie een andere waarde dan null heeft (d.w.z. daadwerkelijk naar een Thread-object wijst). Daarom nog één keer de button-afhandelingsmethode: public void actionperformed(actionevent e)

105 9.3 Animatie 105 Figuur 17: De applet Simulatie in werking { if (e.getsource()==auto) { if (animatie!=null) { animatie = null; auto.setlabel("start"); else { animatie = new Thread(this); animatie.start(); auto.setlabel("stop"); else this.doestap(); De methode run gebruikt nu als voorwaarde om door te gaan dat de waarde van deze verwijzing nog niet null gemaakt is; zie listing 16. blz. 106

106 106 Objecten en klassen import java.awt.*; import java.awt.event.*; import java.applet.applet; 5 public class Simulatie extends Applet implements ActionListener, Runnable { Ruimte r1, r2, r3; Thread animatie; Button stap, auto; 10 public void init() { r1 = new Ruimte(100,196); r2 = new Ruimte(196,150); r3 = new Ruimte( 60, 75); stap = new Button("Stap"); 15 auto = new Button("Start"); animatie = null; this.add(r1); this.add(r2); this.add(r3); this.add(stap); this.add(auto); 20 stap.addactionlistener(this); auto.addactionlistener(this); private void doestap() 25 { r1.doestap(); r2.doestap(); r3.doestap(); r1.repaint(); r2.repaint(); r3.repaint(); public void actionperformed(actionevent e) 30 { if (e.getsource()==stap) this.doestap(); else if (e.getsource()==auto) { if (animatie==null) { animatie = new Thread(this); 35 animatie.start(); auto.setlabel("stop"); else { animatie = null; 40 auto.setlabel("start"); 45 public void run() { while (animatie!=null) { this.doestap(); try{ Thread.sleep(50); catch (Exception e) { 50 Listing 16: Simulatie/Simulatie.java

107 9.3 Animatie 107 import java.awt.*; class Ruimte extends Canvas { 5 Deeltje d1, d2, d3; public Ruimte(int b0, int h0) { this.setsize(b0, h0); this.setbackground(new Color(255,255,128)); 10 d1 = new Deeltje(Color.red, 30, 40, 10, 10); d2 = new Deeltje(Color.green, 100, 80, 5,-10); d3 = new Deeltje(Color.blue, 200, 60, 8, 2); 15 public void doestap() { d1.doestap(this.getsize()); d2.doestap(this.getsize()); d3.doestap(this.getsize()); 20 public void paint(graphics gr) { d1.teken(gr); d2.teken(gr); d3.teken(gr); 25 Listing 17: Simulatie/Ruimte.java

108 108 Objecten en klassen import java.awt.*; class Deeltje { 5 int x, y, dx, dy; Color kleur; public Deeltje(Color k, int x0, int y0, int dx0, int dy0) { kleur = k; 10 x = x0; y = y0; dx = dx0; dy = dy0; 15 public void doestap(dimension hok) { int maxx, maxy; maxx = (int)hok.getwidth(); 20 maxy = (int)hok.getheight(); x += dx; y += dy; 25 if (x >= maxx) { x = 2*maxX-x; dx = -dx; else if (x<0) 30 { x = -x; dx = -dx; if (y >= maxy) 35 { y = 2*maxY-y; dy = -dy; else if (y<0) { y = -y; 40 dy = -dy; public void teken(graphics gr) 45 { gr.setcolor(kleur); gr.filloval(x-4, y-4, 9, 9); Listing 18: Simulatie/Deeltje.java

109 9.4 Klasse-ontwerp en -gebruik Klasse-ontwerp en -gebruik Ontwerp: wat is en wat kan het object, en hoe? Als je een nieuwe klasse gaat schrijven, moet je je drie dingen afvragen: Wat is het object dat door de klasse wordt beschreven? (Het antwoord op deze vraag schrijf je in de variabele-declaraties bovenin de klasse.) Wat kun je doen met het object dat door de klasse wordt beschreven? (Het antwoord op deze vraag zijn de headers van de methoden in de klasse.) Hoe kun je dingen met het object doen? (Het antwoord op deze vraag zijn de bodies van de methoden in de klasse.) In dit hoofdstuk hebben we voor het eerst meerdere klassen geschreven. Het is nu dus voor het eerst dat al deze vragen aan de orde zijn gekomen. Gebruik: wat kan het object? Als je een klasse wilt gebruiken die iemand anders heeft geschreven, bijvoorbeeld een bibliotheekklasse, dan hoef je eigenlijk maar één ding te weten: Wat kun je doen met het object dat door de klasse wordt beschreven? Voor het gebruik van de klasse is het niet nodig om precies te weten uit welke variabelen het object zoal bestaat. Ook de bodies van de methoden hoef je niet te kennen om de methoden te kunnen aanroepen. Het is natuurlijk wel handig als je een informeel begrip hebt van wat de methoden doen, maar daarvoor is het niet nodig om te weten hoe dat precies gebeurt. Top-down versus bottom-up ontwerp Bij het schrijven van klassen heb je een grote vrijheid. Je kunt zelf bedenken welke methoden je gaat schrijven, en welke parameters deze zullen krijgen. Dit natuurlijk wel op voorwaarde dat je ze ook op een overeenkomstige manier aanroept; als je in de header van een methode schrijft dat deze een int als parameter krijgt, dan moet je bij aanroep ook een getal als parameter meegeven. Bij het bedenken van al die methoden in al die klassen, kun je ruwweg op twee manieren te werk gaan: Top-down ontwerp. Je begint met de meest veelomvattende klasse, d.w.z. de klasse die een extensie is van Applet. Bij het schrijven van init, paint en de event-listeners merk je vanzelf dat je allerlei objecten nodig hebt. Voor alles wat maar een beetje ingewikkeld is, bedenk je een naam voor een methode die je graag zou willen hebben. Daarna ga je die methoden één voor één schrijven, waarbij je alvast weer methoden aanroept, ook al heb je die nog niet geschreven. Daar ga je mee door tot de methoden die je nog moet schrijven zo simpel zijn dat je ze direct kunt schrijven. Sleutelzinnetje: niet vergeten om deze methode straks nog te schrijven. Bottom-up ontwerp. Je begint met de simpelste klasse (in de casus in dit hoofdstuk is dat Deeltje), en schrijft zo veel mogelijk methoden waarvan je denkt dat ze nog wel eens nuttig zullen zijn (zoals doestap). Daarna ga je verder met de wat complexere klassen (in het voorbeeld: Ruimte), waarbij je de zojuist geschreven klasse goed kunt gebruiken. Zo ga je verder, totdat je de allesomvattende extensie van Applet kunt schrijven. Sleutelzinnetje: wat zou je met dit soort objecten willen kunnen doen? Bij het behandelen van de casus hebben we beide strategieën door elkaar heen gebruikt (en zo gaat het in de praktijk ook vaak). We zijn top-down begonnen met de klasse Simulatie. Maar halverwege zijn we overgeschakeld op bottom-up ontwerp: de methoden van Deeltje hebben we geschreven voordat we ze nodig hadden in de methoden van Ruimte. 9.5 Klassen in de Java-libraries Niet het wiel opnieuw uitvinden Er zijn in de Java-bibliotheken een heleboel klassen beschikbaar voor gebruik. Voordat je zelf een klasse begint te schrijven, is het handig om je eerst te oriënteren of iets dergelijks al bestaat; het kan je een boel werk schelen. We geven een overzicht van een aantal klassen in de bibliotheken, met steeds enkele methoden die in die klasse gedefinieerd zijn. Het overzicht is verre van volledig, want dat zou een wel erg saaie opsomming worden. Voor een totaal-overzicht kun je beter de online help raadplegen.

110 110 Objecten en klassen Klassen van echte objecten Om te beginnen zijn er een aantal klassen die concrete objecten in de echte wereld beschrijven. Bijvoorbeeld: String: een tekst, bestaande uit lettertekens en/of andere symbolen zoals die op het toetsenbord te vinden zijn. int length(): bepaalt de lengte van de string String substring(int x,int y): selecteert een deel van de string, aangegeven door twee posities, en levert die op als resultaat String concat(string s): plakt een tweede string erachter, en levert dat op als resultaat char charat(int n): bepaalt welk symbool er op een bepaalde posititie staat boolean equals(string s): vergelijkt de string letter-voor-letter met een andere string GregorianCalendar: een tijdstip in geschiedenis of toekomst, tot op de seconde precies (de naam is een beetje misleidend, want een object met type GegorianCalendar is niet een kalender, maar een tijdstip volgens die kalender). void set(int j, int m, int d): zet de datum op de gespecificeerde jaar, maand en dag. int get(int x): vraag de waarde van het gespecificeerde item. Je kunt het gewenste item aanduiden met constanten als Calendar.YEAR, Calendar.DATE enzovoorts. Point: een punt in de twee-dimensionale ruimte void setlocation(int x, int y): verplaats het punt double getx(): kijk wat de x-coördinaat is Klassen van computer-gerelateerde objecten Veel klassen beschrijven een object dat iets te maken heeft met de computer. Al naar gelang je smaak kun je dit natuurlijk ook objecten in de echte wereld noemen. BufferedImage: een plaatje void setrgb(int x, int y, int k): geef een punt van het plaatje een kleur int getrgb(int x, int y): kijk welke kleur een punt heeft Graphics getgraphics(): lever een Graphics-object, waarmee je in het plaatje kunt tekenen, gebruikmakend van de methoden die Graphics biedt AudioClip: een geluidfragment void play(): speel het geluid af void loop(): speel het geluid steeds opnieuw af void stop(): stop met afspelen File: een al of niet bestaande file of directory, aangeduid door een complete padnaam String getname(): levert alleen de naam, zonder directory File getparentfile(): levert de directory waar de file in zit boolean exists(): kijk of de file echt bestaat void delete(): zorg dat hij hierna zeker niet meer bestaat Klassen van interactiecomponenten Bij het opbouwen van een grafische userinterface kun je gebruik maken van een aantal klassen die interactie-componenten beschrijven. Objecten met deze klassen als type kun je toevoegen aan een applet door aanroep van add. Button: een knop die de gebruiker kan indrukken TextField: een veld waar de gebruiker tekst kan invullen TextArea: een meer-regelig invoerveld Scrollbar: een door de gebruiker verschuifbare regelaar Label: een door de gebruiker niet te wijzigen tekst Checkbox: een vakje dat de gebruiker kan aankruisen Sommige klassen zijn niet in eerste instantie bedoeld om objecten van te creëren, maar eerder om een extensie van te maken, en daarvan vervolgens objecten te creëren. In de extensie is het de bedoeling dat je enkele methoden herdefinieert, zodat het object zich naar jouw wensen gaat gedragen. Voorbeelden zijn:

111 9.5 Klassen in de Java-libraries 111 Applet: een compleet toepassingetje, voor gebruik in een web-browser. Herdefinieer de methoden init en paint. Canvas: een schilderslinnen, te gebruiken als interactie-component. Herdefinieer paint. Klassen van objecten die iets voor je doen Sommige klassen beschrijven objecten waar je je niet direct iets bij kunt voorstellen, maar die wel handige methoden kennen die je goed kunt gebruiken in programma s. Beschouw dit soort objecten maar als apparaatjes, die een kunstje voor je kunnen vertonen. Voorbeelden zijn: Graphics: een tekendoos met handige methoden zoals void drawline(int x0, int y0, int x1, int y1): trek een lijn tussen twee punten void fillrect(int x, int y, int b, int h): vul de rechthoek met de gespecificeerde positie, breedte en hoogte StringTokenizer: een snijapparaat om een String in stukjes te snijden op aangegeven punten (bijvoorbeeld bij de spaties) String nexttoken(): geef het eerstvolgende plakje boolean hasmoretokens(): kijk of er nog plakjes zijn Opgaven 9.1 Vergeten add Bekijk de situatie-schetsen in sectie 9.1. Wat kun je opmerken over het Button-object als de aanroep this.add(reset) per ongeluk uit het programma is weggelaten? 9.2 Lichtkrant Schrijf een applet genaamd Lichtkrant. Deze laat een tekst langzaam door het beeld bewegen, van rechts naar links. Als de tekst helemaal uit beeld is verdwenen, begint hij weer opnieuw. De lengte van een String kun je bepalen door aanroep van length. Ga ervan uit dat elke letter gemiddeld 10 beeldpunten breed is. Gebruik een Thread-object voor de animatie! De tekst die getoond wordt, moet als parameter in de html-file kunnen worden gespecificeerd. (Je kunt de applet dan voor allerlei verschillende teksten gebruiken). 9.3 Simulatie-programma a. Bij het creëren van een nieuw Thread-object kun je ook iets anders dan this meegeven. Welk object zou je in het Simulatie-programma nog meer kunnen gebruiken? Wat zijn daarbij de voorwaarden? In welke situatie zou dat handig zijn? b. De Ruimtes in de Simulatie zijn wrijvingsloos: eenmaal bewegende deeltjes blijven onbeperkt door bewegen. Wat zou je moeten veranderen om de aanwezigheid van wrijving te modelleren?

112 112 Hoofdstuk 10 Overerving 10.1 Subklassen Subklasse: toevoegen van nieuwe variabelen/methoden Door gebruik te maken van klasse-bibliotheken kun je je veel werk besparen. Des te jammerder is het dan ook, als er in de bibliotheek een klasse zit die bijna doet wat je wilt, maar net niet helemaal. Of je hebt een vergelijkbare klasse al eens eerder geschreven, maar ditmaal wil je een kleine uitbreiding aan de klasse maken. Wat te doen? Een oplossing die vroeger, dat wil zeggen in de tijd van voor de object-georiënteerde talen (Pascal, C) maakte men in zo n geval een kopie van het eerdere programma, om daarin vervolgens aan te passen wat er aangepast moest worden. Dat scheelde in ieder geval een boel tikwerk. Toch had deze knip-en-plak -strategie een belangrijk nadeel: als het oorspronkelijke programma later verbeterd werd (fouten hersteld, of de snelheid verhoogd), dan moest die verbetering in de gewijzigde kopie apart doorgevoerd worden. En als die kopie ook alweer gebruikt was als uitgangspunt voor weer een andere versie, dan moest ook die versie weer aangepast worden. Er ontstaat, kortom een versie-probleem: verschillende versies groeien uit elkaar en kunnen niet gemakkelijk met elkaar in overeenstemming worden gebracht. Een kleine verbetering in het oorspronkelijke programma (bijvoorbeeld: een jaartal opslaan met vier cijfers in plaats van twee) kan met zich mee brengen dat vele duizenden zoveelste-generatie gewijzigde kopieën apart ook gewijzigd moeten worden. Zoals bekend kunnen de kosten daarvan aardig oplopen... Hoe moet het dan wel? Als je een uitbreiding wilt maken van een eerder geschreven klasse, dan moet je er niet een kopie van maken en die veranderen, maar kun je beter de verandering beschrijven in een extensie van de klasse. Dat gebeurt door in de klasse-header het woord extends te schrijven, met daarachter de naam van de klasse waarvan je een uitbreiding wilt maken. We hebben daar al veel voorbeelden van gezien: class Hallo extends Applet {... class Simulatie extends Applet {... class Ruimte extends Canvas {... Met extends maak je subklassen De uitgebreide klasse wordt ook wel een subklasse van de oorspronkelijke klasse genoemd. De oorspronkelijke klasse heet omgekeerd, de superklasse van de extensie. Een klasse kan maar één (directe) superklasse hebben, maar een klasse kan meerdere subklassen hebben. Bijvoorbeeld, de klasse Applet heeft zowel de klasse Hallo als de klasse Simulatie als subklassen, maar Hallo heeft maar één superklasse (namelijk Applet). Het is wel zo, dat je van de subklasse opnieuw weer een uitbreiding kunt maken: een sub-subklasse dus van de oorspronkelijke klasse. Die sub-subklasse heeft één directe superklasse, maar is indirect ook een subklasse van de super-superklasse. Er kan dus een hele stamboom ontstaan van klasseafstammingen. De objecten van een subklasse kunnen tevens worden opgevat als object van de superklasse. Een Ruimte-object bijvoorbeeld (zoals gedefinieerd in het vorige hoofdstuk) is ook een Canvas-object. Maar het is niet zomaar een Canvas-object, nee, het is een heel bijzonder Canvas-object; eentje namelijk met gekleurde bolletjes erop. Het wiskundige jargon voor dit wat overdreven geformuleerde zinnetje is: een Ruimte-object is een bijzonder geval van een Canvas-object.

113 10.1 Subklassen 113 Overerving van methoden en variabelen Objecten van de subklasse mogen de methoden gebruiken van de superklasse, en van de eventuele superklasse daar weer van, enzovoorts. Die methoden worden, zoals dat heet, geërfd van de superklasse. Hetzelfde geldt voor de variabelen die in de superklasse zijn gedeclareerd: die zijn ook in elk object van de subklasse aanwezig. Zo kan het gebeuren dat je in subklassen van Applet gewoon de methode add kunt aanroepen, ook al is die helemaal niet apart gedefinieerd in de subklasse. De methode is namelijk wel bekend in de klasse Applet, en wordt daar door de subklassen dus van geërfd. Bij het opzoeken van methoden in de handleiding is dit wel iets om op te letten: zie je een methode niet staan in een overzicht van methoden bij de klasse waar je hem had verwacht, dan betreft het waarschijnlijk een geërfde methode van een superklasse. De methode add bijvoorbeeld, zul je in de klasse Applet tevergeefs zoeken: hij wordt gedefinieerd in de super-superklasse van Applet, dat is Container. Een ander voorbeeld waar overerving een rol kan spelen, nu eens niet in verband met Applet en dergelijke, maar gewoon in je eigen klassen. Stel dat je een klasse Bolletje hebt gemaakt. Zo n Bolletje heeft een positie en een doorsnede. Er is een methode zetplaats, een methode groei en een methode teken gedefinieerd in de klasse: class Bolletje { int x, y, diam; void zetplaats(int x0, int y0) { x = x0; y = y0; void groei() { diam++; void teken(graphics gr) { gr.filloval(x,y,diam,diam); Later kun je een subklasse KleurBol maken, waarin de klasse Bolletje wordt uitgebreid met een kleur, en een methode zetkleur om de kleur vast te leggen: class KleurBol extends Bolletje { Color kleur; void zetkleur(color k) { kleur = k; Heb je nu een object van type KleurBol, dan kun je daar de nieuwe methode zetkleur van aanroepen, maar ook nog steeds de methode zetplaats en groei. Een KleurBol-object is tenslotte nog steeds een Bolletje, en voor het groeien en plaatsen van een bolletje maakt de eventuele kleur niet uit. Omgekeerd kan het natuurlijk niet: je kunt niet met zomaar een bolletje de methode zetkleur aanroepen, want zo n bolletje heeft geen kleur. (Tenzij het toevallig een heel bijzonder bolletje is, namelijk een gekleurd bolletje, maar daar kun je niet zomaar op rekenen). Herdefinitie van methoden Bij het erven van de methode teken door de klasse KleurBol ontstaat er toch een probleem. Een gekleurd bolletje moet namelijk op een andere manier getekend worden dan een gewoon bolletje: bij het tekenen speelt de kleur immers een rol. Voor dit soort situaties is het toegestaan om in subklassen een methode een andere definitie te geven dan de oorspronkelijk, dus om de methode te herdefiniëren. In de klasse KleurBol kunnen we de volgende herdefinitie van teken zetten: void teken(graphics gr) { gr.setcolor(kleur); gr.filloval(x,y,diam,diam);

114 114 Overerving Het nog-niet-uitgebreide object super Het is wel jammer dat je, zodra je besluit om een methode te herdefiniëren, je de hele body opnieuw moet schrijven. In de hergedefinieerde versie van teken moest de aanroep van filloval bijvoorbeeld opnieuw worden opgeschreven. Nou valt dat hier nog wel mee, maar als het tekenen ingewikkelder was geweest (bijvoorbeeld gekleurde huisjes in plaats van bolletjes) is dat toch zonde van het werk, en wat erger is: gevoelig voor versie-problematiek. Je zou eigenlijk bij de herdefinitie van de methode teken de oorspronkelijke methode willen kunnen aanroepen. Dat kan echter niet met this.teken(gr); want dan roep je de eigen methode aan, die dan weer zichzelf aanroept, die dan weer zichzelf aanroept, die dan weer zichzelf aanroept... Nee, dat willen we niet. Als uitweg in deze situatie is er in Java een speciale constante beschikbaar, genaamd super. Dit is net zoiets als this: het is een verwijzing naar het huidige object. Maar met dit verschil: super wijst naar het huidige object, alsof hij het type van de superklasse heeft. De herdefinitie van teken kan dus als volgt verlopen: void teken(graphics gr) { gr.setcolor(kleur); super.teken(gr); Polymorfie Objecten van een subklasse zijn acceptabel op plaatsen waar een object van die superklasse wordt verwacht. Dat geldt bijvoorbeeld voor parameters. Stel dat er een methode bestaat die een Bolletje als parameter wil hebben misschien omdat hij hem wil laten groeien: void laatgroeien(bolletje bol) { bol.groei(); In een ander deel van het programma hebben we zowel een Bolletje als een KleurBol beschikbaar: Bolletje saai; KleurBol mooi; saai = new Bolletje(); mooi = new KleurBol(); We kunnen nu laatgroeien aanroepen met elk van beide objecten: laatgroeien(saai); laatgroeien(mooi); Omgekeerd is het niet toegestaan. Een methode als void maakrood(kleurbol kb) { kb.zetkleur(color.red); kan niet worden aangeroepen met zomaar een bolletje (die immers niet gekleurd kan worden), maar natuurlijk wel met een KleurBol (die dat wel kan): maakrood(saai); maakrood(mooi); // fout // goed De leukste situatie is, als er een hergedefinieerde methode wordt aangeroepen. Bekijk de volgende variant op laatgroeien: void tekeneenbolletje(bolletje bol) { bol.teken(ergens); die we aanroepen met zowel het gewone bolletje als met de kleurbol: tekeneenbolletje(saai); tekeneenbolletje(mooi); Het gewone bolletje wordt gewoon getekend, maar de KleurBol wordt netjes gekleurd getekend! Daar is die methode tekeneenbolletje mooi ingetrapt. Hij denkt van zomaar een Bolletje de

115 10.2 Klasse-hiërarchieën 115 teken-methode aan te roepen, maar als die Bolletje-parameter een KleurBol blijkt te zijn (wat mag, want dat is een subklasse van Bolletje), dan wordt daar wel de hergedefinieerde versie van teken voor aangeroepen. En dat komt in de meeste gevallen goed uit. Dit verschijnsel heet polymorfie. De parameter kan letterlijk vele vormen aannemen (poly=veel, morf=vorm), en in elk van de gevallen wordt precies de juiste versie van de methode teken aangeroepen Klasse-hiërarchieën Extends: is een Met subklassen kun je de objecten in de wereld proberen te ordenen. Als je het woord extends daarbij leest als is een, kun je controleren of je geen denkfouten hebt gemaakt. Hier volgt een mogelijkheid om vervoermiddel-objecten te modelleren met behulp van allerlei klassen, die elkaars subklassen zijn. class Vervoermiddel class Voertuig extends Vervoermiddel class Vliegtuig extends Vervoermiddel class Boot extends Vervoermiddel class MotorVoertuig extends Voertuig class Fiets extends Voertuig class MotorBoot extends Boot class ZeilBoot extends Boot class StoomBoot extends MotorBoot class Auto extends MotorVoertuig class VrachtWagen extends Auto Met dit soort klasse-definities ontstaat een hele hiërarchie van klassen. Bij elk van de subklassen kunnen attributen en/of methoden worden toegevoegd, die alleen maar relevant zijn voor de betreffende klasse, en de subklassen daarvan. Bijvoorbeeld, de variabele aantalwielen hoort typisch thuis in Voertuig en niet in Vervoermiddel, want boten hebben geen wielen. De variabele vlieghoogte hoort thuis in Vliegtuig, en de boolean variabele belwerkt in de klasse Fiets. Zo n hiërarchie van klassen kun je overzichtelijk in een schema weergeven: Bij het maken van een indeling in subklassen moet je als ontwerper allerlei beslissingen nemen. Er is niet één goede onderverdeling, maar afhankelijk van de toepassing kan een bepaalde onderverdeling handiger uitpakken dan een andere. In het voorbeeld hebben we bijvoorbeeld gekozen om de klasse Vervoermiddel eerst onder te verdelen naar het medium waarover het zich voortbeweegt: land, lucht of water. Pas daarna worden de klassen in verdere subklassen onderverdeeld: gemotoriseerd of niet. Dat had ook andersom gekund. Voor sommige klassen is het ook niet altijd even duidelijk op welke plaats ze in de hiërarchie thuishoren: is een MotorFiets een bijzonder soort Fiets (namelijk met een motor), of een bijzonder soort MotorVoertuig (namelijk met weinig wielen)?

116 116 Overerving De onderlinge ligging van de klassen is echter wel altijd duidelijk. Een VrachtWagen is een Auto, maar een Auto is (lang niet altijd) een VrachtWagen. Een Fiets is een Voertuig, maar niet elk Voertuig is een Fiets. Object-variabelen: heeft een Werkend met diagrammen moet je wel uitkijken waar het eigenlijk over gaat. Er is nog een ander soort diagram dat wel eens getekend wordt in verband met objecten. Het gaat dan om welke objecten onderdeel uitmaken van andere objecten. Bijvoorbeeld in het Simulatie-programma in het vorige hoofdstuk: Dit diagram moet je heel anders interpreteren. De lijnen moet je ditmaal van boven naar beneden lezen, met de uitspraak heeft een. Het diagram is heel bruikbaar om een overzicht te krijgen van de samenhang tussen de object-variabelen in een bepaald programma, maar het is geen klassehiërarchie. Een Deeltje is namelijk helemaal geen Ruimte (maar het maakt er onderdeel van uit). In dit diagram stellen de vakjes dan ook objecten voor, en geen klassen. Dat maakt ook dat (objecten met dezelfde) klassen meermalen kunnen voorkomen in het diagram, iets wat in een klasse-hiërarchie ook al onmogelijk is Klasse-hiërarchieën in de Java-libraries Interface-componenten Veel klassen uit de Java-library zijn geordend in hiërarchieën, vergelijkbaar met de hiërarchie van vervoermiddellen uit de vorige sectie. Alle interactie-componenten, waaruit een grafische userinterface is opgebouwd, zijn bijvoorbeeld direct of indirect subklasse van de klasse Component. Methoden die op alle interactie-componenten van toepassing zijn, worden gedefinieerd in de klasse Container. Voorbeelden daarvan zijn setbackground (om de achtergrondkleur te veranderen) en getbounds (om de afmetingen op te vragen). Methoden die alleen van toepassing zijn op componenten waarin de gebruiker iets kan intikken, zoals gettext, zijn gedefinieerd in de klasse TextComponent. Methoden die te maken hebben met het onderverdelen van de ingetikte tekst in meerdere regels, zoals getrows, zijn geplaatst in TextArea, omdat ze niet van toepassing zijn op TextFields. Event-listeners Om te reageren op de acties van de gebruiker, kun je aan interactie-componenten een eventlistener koppelen. Verschillende soorten componenten hebben verschillende soorten event-listeners: een Button en een TextField kunnen een ActionListener hebben, maar een Scrollbar heeft een AdjustmentListener. Er zijn verschillen tussen een ActionListener en een AdjustmentListener, maar ze hebben ook

117 10.3 Klasse-hiërarchieën in de Java-libraries 117 dingen gemeenschappelijk. Dat maakt dat het een goed idee is om ze in een hiërarchie onder te brengen. Informeel hebben we dat al gedaan, door ze samenvattend event-listener te noemen. Dat is inderdaad de naam van de superklasse. Strikt genomen zijn EventListeners geen klassen, maar interfaces: er zitten geen methoden in, maar slechts methode-headers, dat wil zeggen wensenlijstjes die door andere klassen geïmplementeerd moeten worden. Maar ook interfaces zijn in een hiërarchie geordend. (Om het onderscheid toch te honoreren hebben we ze in de schets van de hiërarchie een andere vorm en kleur gegeven). Events Een event-listener zet andere objecten aan het werk door het aanroepen van methoden als actionperformed of adjustmentvaluechanged. Als parameter wordt daarbij een object meegegeven waarin nadere details over de gebeurtenis die is opgetreden worden meegedeeld. Het type van dat object verschilt bij de diverse methoden, maar al die types hebben iets gemeenschappelijks: ze beschrijven een event. De klassen die de types beschrijven zijn dan ook ondergebracht in een klasse-hiërarchie: Alles in één hiërarchie Hoe hoger je in de hiërarchie kijkt, des te algemener is een beschrijving van de klasse. Het is vaak ook lastig om het onder woorden te brengen zonder al te vaag te worden. Probeer maar eens uit te leggen wat een interactie-component is, zonder voorbeelden te geven ( dingen zoals buttons en scrollbars enzo ). Het is de sport om toch zoveel mogelijk overeenkomsten te zoeken tussen klassen die op het eerste gezicht weinig met elkaar te maken hebben. Wat is het gemeenschappelijke van een String en een Component? En wat van een Image en een ActionEvent? Niet zo heel erg veel, maar wel: het zijn allemaal beschrijvingen van een object. En dat is al reden genoeg om ze een gemeenschappelijke superklasse te geven, die dan ook, heel toepasselijk, Object heet. De methoden van de klasse Object zijn noodzakelijkerwijs erg algemeen. We zullen er hier twee noemen: clone: maakt een exacte kopie van het object tostring: converteert het object naar een leesbare String De methode tostring wordt automatisch aangeroepen als je een object met de +-operator aan een string probeert te plakken. In veel klassen wordt de methode tostring daarom hergedefinieerd,

118 118 Overerving zodat objecten van die klasse wanneer dat nodig is, gemakkelijk in tekst-vorm op het scherm kunnen worden getoond. Opgaven 10.1 Type van add Iemand schrijft: b = new Button("druk hier"); t = new TextField(10); this.add(b); this.add(t); Hoe kan het correct zijn dat add zowel een Button als een TextField als parameter accepteert? Wat is het type van de parameter van add? 10.2 Veel objecten met actionlisteners In de methode actionperformed kun je te weten komen welke button of textfield de actie heeft veroorzaakt. (Hoe ging dat ook alweer?) In een programma zijn er tien tekstvelden en een button. De button is gedeclareerd als objectvariabele met de declaratie Button b;. Als de gebruiker op de button drukt, moet het programma stoppen door aanroep van de statische methode exit uit de klasse System. Als de gebruiker op Enter drukt in één van de tien tekstvelden, moet de inhoud van dat tekstveld worden veranderd: de tekst moet een hoofdletter-versie worden van de tekst die is ingetikt. Schrijf de methode actionperformed die dit doet Klasse-hiërarchieën In sectie 10.2 staat een klassehiërarchie van vervoermiddelen. Maak een dergelijke hiërarchie voor één (of meer) van onderstaande toepassingsgebieden naar keuze. Neem daarbij zo nodig ontwerpbeslissingen: welke eigenschap vind je het belangrijkste, om als eerste naar onder te verdelen? Scheikunde: chemische elementen Biologie: levende organismen Taalkunde: natuurlijke talen Informatica: computertalen Zijn er klassen die zich niet goed in de hiërarchie laten onderbrengen? Waarom niet? 10.4 Type-controle Worden de types van expressies over het algemeen gecontroleerd door de Java-compiler of door de bytecode-interpreter? Er is een uitzondering op deze regel. In welk geval is dat? 10.5 Repaint en update Tot nu toe hebben we altijd gezegd dat repaint de methode paint aanroept met een ergens vandaan gehaald Graphics-object als parameter. Dat is niet helemaal waar: eigenlijk roept repaint de methode update aan, met het Graphics-object als parameter. Die methode tekent eerst een grote witte rechthoek (zodat een eventueel nog aanwezige achtergrond uitgewist wordt), en roept vervolgens pas paint aan, waarbij het Graphics-object wordt doorgegeven. Als je snel achter elkaar repaint aanroept (bijvoorbeeld in een animatie) gaat het beeld hinderlijk knipperen. Hoe kun je dat verhelpen? 10.6 TextField als source van een ActionEvent Bekijk de volgende implementatie van actionperformed: public void actionperformed(actionevent e) { TextField tf; tf = (TextField) e.getsource(); tf.settext("hallo"); Waarom staat er (TextField) in de toekenningsopdracht? Wat gaat er fout als je dit weglaat? Wat kan er toch nog fout gaan nu het er wel staat?

119 119 Hoofdstuk 11 Strings en Arrays 11.1 Strings en characters De klasse TextArea In eerdere programma s gebruikten we TextField-objecten om de gebruiker waarden te laten invoeren. In zo n TextField kan echter maar één regel worden ingetikt. Zijn er meerdere regels nodig, dan kun je in plaats daarvan TextArea-objecten gebruiken. Je kunt deze objecten gebruiken voor invoer van de gebruiker, maar ook om resultaten aan de gebruiker te laten zien. In de klasse TextArea zitten onder andere de volgende methoden: TextArea(int r, int k): maakt een TextArea met het gespecificeerde aantal rijen en kolommen void settext(string s): zet een string in de TextArea String gettext(): zet de tekst die door de gebruiker is ingetikt in een string void append(string s): voeg een tekst toe aan de tekst die al in de TextArea staat void seteditable(boolean b): specificeer of de gebruiker de tekst wel of niet mag veranderen. In tegenstelling tot een TextField kan een TextArea geen ActionListener hebben. Bij een TextField treedt de ActionListener in werking als de gebruiker op de Enter-toets drukt, maar in een TextArea is de Enter-toets nodig om naar de volgende regel te kunnen gaan. Om de gebruiker aan te laten geven dat de tekst volledig is ingetikt en verwerkt mag worden, is het daarom handig om naast een TextArea voor dit doel een Button te plaatsen. Toepassing: hoeveel letters ingetikt? Een eenvoudige toepassing biedt de gebruiker de gelegenheid om een tekst in te tikken in een TextArea. Zodra de gebruiker op een button drukt, wordt de lengte van de tekst getoond in een tweede TextArea. De klasse-header van een extensie Applet veronderstellen we bekend, dus we gaan direct kijken naar de methode init, waar de interactiecomponenten worden gemaakt: public void init() { invoer = new TextArea(5, 40); uitvoer = new TextArea(2,40); knop = new Button("Tel"); this.add(invoer); this.add(uitvoer); this.add(knop); knop.addactionlistener(this); In de methode die wordt aangeroepen als reactie op het indrukken van de Button pakken we de tekst uit de textarea, tellen hoeveel symbolen daar in zitten, en zetten het resultaat op in het tweede tekstveld: public void actionperformed(actionevent e) { String s; int n; s = invoer.gettext(); n = s.length(); uitvoer.settext("je hebt " + n + " tekens getikt");

120 120 Strings en Arrays Interessanter is het echter om behalve het totale aantal letters ook iets te zeggen over het aantal woorden dat is ingetikt. Om dat te kunnen doen, moeten we niet alleen de lengte van de string bekijken, maar ook de individuele symbolen daarvan, op zoek naar spaties. Daarvoor hebben we behalve length nog wat meer methoden uit de klasse String nodig. De klasse String In de klasse String zitten onder andere de volgende methoden: int length(): bepaalt de lengte van de string String substring(int x,int y): selecteert een deel van de string, aangegeven door twee posities, en levert die op als resultaat String concat(string s): plakt een tweede string erachter, en levert dat op als resultaat boolean equals(string s): vergelijkt de string letter-voor-letter met een andere string char charat(int n): bepaalt welk symbool er op een bepaalde posititie staat Met de methode substring kun je een deel van de string selecteren, bijvoorbeeld de eerste vijf letters: kop = s.substring(0,5); uitvoer.settext(kop + " waren de eerste vijf letters"); De telling van de posities in de string is een beetje merkwaardig: de eerste letter staat op positie 0, de tweede letter op positie 1, enzovoorts. Als parameters van de methode substring geef je de positie van de eerste letter die je wilt hebben, en de positie van de eerste letter die je net niet meer wilt hebben. Dus de aanroep s.substring(0,5) geeft de letters op positie 0, 1, 2, 3 en 4; met andere woorden de eerste 5 letters. Je kunt de eerste letter van een string te pakken krijgen met een aanroep als: String voorletter; voorletter = s.substring(0,1); Het resultaat is dan een string met lengte 1. Er is echter nog een andere manier om losse letters uit een string te pakken: de methode charat. Het resultaat daarvan is niet een nieuw string-object, maar een losse waarde van het primitieve type char, die je dus direct in een variabele kunt opslaan: char eerste; eerste = s.charat(0); Een van de voordelen van char boven string-objecten met lengte 1, is dat je char-waarden direct op gelijkheid kunt testen met de operator ==, terwijl dat bij strings enigzins onnatuurlijk met een aanroep van de methode equals moet gebeuren. Het primitieve type char Net als alle andere primitieve types kun je char-waarden opslaan in variabelen, meegeven als parameter aan een methode, opleveren als resultaatwaarde van een methode, onderdeel laten uitmaken van een object, enzovoorts. Er is een speciale notatie om constante char-waarden in het programma aan te duiden: je tikt gewoon het gewenste letterteken, en zet daar enkele aanhalingstekens omheen. Dit ter onderscheiding van String-constanten, waar dubbele aanhalingstekens omheen staan: char sterretje; String hekjes; sterretje = * ; hekjes = "####"; Tussen enkele aanhalingstekens mag maar één symbool staan; tussen dubbele aanhalingstekens mogen meerdere symbolen staan, maar ook één symbool, of zelfs helemaal geen symbolen. Geschiedenis van char Het aantal verschillende symbolen dat in een char-variabele kan worden opgeslagen is in de geschiedenis (en in verschillende programmeertalen) steeds groter geworden: In de jaren 70 van de vorige eeuwe dacht men aan 2 6 = 64 verschillende symbolen wel genoeg te hebben: 26 letters, 10 cijfers en 28 leestekens. Dat er op die manier geen ruimte was om hoofd- en kleine letters te onderscheiden nam men voor lief.

121 11.1 Strings en characters 121 In de jaren 80 werden meestal 2 7 = 128 verschillende symbolen gebruikt: 26 hoofdletters, 26 kleine letters, 10 cijfers, 33 leestekens en 33 speciale tekens (einde-regel, tabulatie, piep, enzovoorts). De volgorde van deze tekens stond bekend als ascii: de American Standard Code for Information Interchange. Dat was leuk voor Amerikanen, maar Fran caises, Deutsche Mitbürger, en inwoners van España en de Fær-Œr denken daar anders over. In de jaren 90 kwam dan ook een codering met 2 8 = 256 symbolen in zwang, waarin ook de meest voorkomende land-specifieke letters een plaats vonden. dos had een eigen codering, later kwam er een andere codering van ansi (American National Standards Institute), die werd overgenomen door de internationale standaarden-organisatie iso. Maar hoe moet dat met de Griekse en Cyrillische letters? En het Indiase Devangari-alfabet? En de Japanse Kanji-tekens? In de jaren 00 werd daarom de codering opnieuw uitgebreid tot een tabel met 2 16 = verschillende symbolen. Dat is voorlopig wel genoeg. Deze codering heet Unicode. De eerste 256 tekens van Unicode komen overeen met de iso-codering, dus die blijft ook gelden. In Java worden char-waarden opgeslagen via de Unicode-codering. Niet dat er veel computers zijn waarop je al deze tekens daadwerkelijk kunt weergeven, maar op deze manier hoeft de taal, en onze programma s tenminste niet te worden aangepast zodra dat wel het geval wordt. Aanhalingstekens Bij het gebruik van Strings en char-waarden is het belangrijk om de aanhalingstekens pijnlijk precies op te schrijven. Als je ze vergeet, is wat er tussen staat namelijk geen letterlijke tekst meer, maar een stukje Java-programma. En er is een groot verschil tussen de letterlijke string "hallo" en de variabele-naam hallo de letterlijke string "boolean" en de type-naam boolean de letterlijke string "123" en de int-waarde 123 de letterlijke char-waarde + en de optel-operator + de letterlijke char-waarde x en de variabele-naam x de letterlijke char-waarde 7 en de int-waarde 7 Informatici hebben iets met aanhalingstekens. Grapjes en woordspelingen die gebaseerd zijn op de verwarring die door het weglaten van aanhalingstekens ontstaat, zijn dan ook erg populair in kringen van informatici (en worden daarbuiten vaak helemaal niet begrepen). In de casus met de geheime sleutel in sectie 8.2 stond zo n woordspeling. Had je die gezien? Speciale char-waarden Speciale lettertekens zijn, juist omdat ze speciaal zijn, niet op deze manier aan te duiden. Voor een aantal speciale tekens zijn daarom aparte notaties in gebruik, gebruik makend van het omgekeerdeschuine-streep-teken (backslash): \n voor het einde-regel-teken, \t voor het tabulatie-teken. Dat introduceert een nieuw probleem: hoe die schuine streep dan zelf weer aan te duiden. Dat gebeurt door twee schuine strepen achter elkaar te zetten (de eerste betekent: er volgt iets speciaals, de tweede betekent: het speciale symbool is nu eens niet speciaal ). Ook het probleem hoe de aanhalingstekens zelf aan te duiden is op deze manier opgelost: \\ voor het backslash-teken, \ voor het enkele aanhalingsteken, \" voor het dubbele aanhalingsteken. Er staan in deze gevallen dus weliswaar twee symbolen tussen de aanhalingstekens, maar samen duiden die toch één char-waarde aan. Rekenen met char De symbolen in de Unicode-tabel zijn geordend; elk symbool heeft zijn eigen rangnummer. Het volgnummer van de letter A is bijvoorbeeld 65, dat van de letter a is 97. Let op dat de code van het symbool 0 niet 0 is, maar 48. Ook de spatie heeft niet code 0, maar code 32. Het symbool dat wel code 0 heeft, is een speciaal teken dat geen zichtbare representatie heeft. Je kunt het code-nummer van een char te weten komen door de char-waarde toe te kennen aan een int-variabele:

122 122 Strings en Arrays char c; int i; c = * ; i = c; of zelfs direct i = * ; Dit kan altijd; er zijn tenslotte maar verschillende symbolen, terwijl de grootse int meer dan 2 miljard is. Andersom kan de toekenning ook, maar dan moet je voor de int-waarde nog eens extra garanderen dat je accoord gaat met onverwachte conversies, mocht de int-waarde te groot zijn. Die garantie geef je door voor de int-waarde tussen haakjes te schrijven dat je er een char van wilt maken: c = (char) i; Je kunt op deze manier rekenen met symbolen: het symbool na de z is (char)( z +1), en de hoodletter c is de c- A +1-de letter van het alfabet. Deze garantie -notatie heet een cast. We hebben hem ook gebruikt om bij conversie van doublenaar int-waarde te garanderen dat we accoord gaan met afronding van het gedeelte achter de komma: double d; int i; d = ; i = (int) d; Toepassing: tellen van woordaantal We kunnen nu het voorbeeldprogramma aanpassen, zodat niet alleen het totaal aantal lettertekens wordt getoond, maar ook het aantal woorden. Om te beginnen plukken we daartoe weer de ingetikte tekst van het scherm: public void actionperformed(actionevent e) { String s; s = invoer.gettext(); Het tellen is nu wat lastiger, omdat we alle indiviuele lettertekens van de string moeten inspecteren. Daarbij gaan we het totaal aantal spaties en regelovergangen tellen; daarmee worden woorden immers gescheiden. We declareren dus twee variabelen waarin we de telling gaan bijhouden, en een derde variabele waarmee we de positie in de string kunnen aangeven: int spaties, regels, positie; Met een for-opdracht kunnen we de variabele positie achtereenvolgens alle mogelijke posities in de string laten langslopen. Let op dat de telling begint bij 0, en doorloopt tot (en dus niet tot en met) de lengte van de string spaties = 0; regels = 0; for (positie=0; positie<s.length(); positie++) In de body van de for-opdracht pakken we door aanroep van charat het symbool dat op de positie staat zoals aangeduid door positie, zodat we kunnen controleren of het een spatie of een regelovergang is: { if (s.charat(positie)== ) spaties++; if (s.charat(positie)== \n ) regels++; Na afloop van de telling kunnen we de resultaten presenteren aan de gebruiker: uitvoer.settext( spaties+regels + " woorden\n" + "op " + regels + " regels" ); Merk op dat we in de string het einde-regel-teken \n gebruiken om de tekst in het uitvoerveld over twee regels te spreiden.

123 11.2 Arrays Arrays Array: rij variabelen van hetzelfde type In de volgende sectie gaan we een programma schrijven dat niet alleen het aantal spaties telt, maar dat de frequentie van èlke letter bepaalt. De uitvoer wordt dus zoiets als 23 A s, 7 B s, 3 C s, 8 D s.... Zoals we in het vorige voorbeeld twee int-waarden gebruikten als teller voor het aantal spaties en het aantal regelovergangen, zo zouden we nu zesentwintig tellers kunnen maken: voor elke letter één. Dat wordt een omvangrijke declaratie: int as, bs, cs, ds, es, fs, gs, hs, is, js, ks, ls, enzovoort en wat nog erger is: in de body van de for-opdracht zijn zesentwintig if-opdrachten nodig om al die letters te testen: if (s.charat(positie)== a ) as++; if (s.charat(positie)== b ) bs++; if (s.charat(positie)== c ) cs++; if (s.charat(positie)== d ) ds++; // enzovoort Gelukkig kan dat handiger. Er is een manier om in één klap een heleboel genummerde variabelen te declareren. Als je zo n variabele wilt gebruiken kun je die aanduiden met zijn volgnummer. Zo n rij genummerde variabelen heet een array. Letterlijk dus: een rij. Creatie van arrays Een array heeft veel gemeenschappelijk met een object. Als je een array wilt gebruiken moet je, net als bij een object, een variabele declareren die een verwijzing naar de array kan bevatten. Voor een array met int-waarden kan de declaratie er zo uitzien: int [] tabel; De vierkante haken geven aan dat we niet een losse int-variabele declareren, maar een array van int-variabelen. De variabele tabel zelf echter is niet de array: het is een verwijzing die ooit nog eens naar de array zal gaan wijzen, maar dat op dit moment nog niet doet: Om de verwijzing daadwerkelijk naar een array te laten wijzen, hebben we een toekenningsopdracht nodig. Net als bij een object gebruiken we een new-expressie, maar die ziet er ditmaal iets anders uit: achter new staat het type van de elementen van de array, en tussen vierkante haken het gewenste aantal: tabel = new int[5]; De situatie die hiermee in het geheugen ontstaat is: Het array-object dat is gecreëerd bestaat uit een int-variabele waarin de lengte van de array is opgeslagen, en uit een aantal genummerde variabelen van het gevraagde type (in dit voorbeeld ook int). De nummering begint bij 0, en daarom is het laatste nummer één minder dan de lengte. Gebruik van array-waarden Je kunt de genummerde variabelen aanspreken door de naam van de verwijzing te noemen, met tussen vierkante haken het nummer van de gewenste variabele. Je kunt die de genummerde variabelen op deze manier een waarde geven: tabel[2] = 37; en je kunt de variabelen gebruiken in een expressie:

124 124 Strings en Arrays x = tabel[2] + 5; Het zijn, kortom, echte variabelen. De variabele length, die ook deel uitmaakt van de array, kun je wel gebruiken in een expressie, bijvoorbeeld if (tabel.length < 10) iets Je kunt deze variabele echter niet veranderen. De lengte van een eenmaal gecreëerde array ligt dus vast; je kunt de array niet meer langer of korter maken. De echte kracht van arrays ligt in het feit dat je het nummer van de gewenste variabele met een expressie kunt aanduiden. Neem bijvoorbeeld het geval waarin alle array-elementen dezelfde waarde moeten krijgen. Dat kan met een hele serie toekenningsopdrachten: tabel[0] = 0; tabel[1] = 0; tabel[2] = 0; tabel[3] = 0; tabel[4] = 0; maar dat is, zeker bij lange arrays, natuurlijk erg omslachtig. Je kunt de regelmaat in deze serie opdrachten echter uitbuiten, door op de plaats waar het volgnummer (0, 1, 2, 3 en 4) staat, een variabele te schrijven. In een for-opdracht kun je deze variabele dan alle gewenste volgnummers laten langslopen. De length-variabele kan mooi dienen als bovengrens in deze for-opdracht: int nummer; for (nummer=0; nummer<tabel.length; nummer++) tabel[nummer] = 0; Arrays als parameter Je kunt een array ook als parameter meegeven aan een methode. Het is eigenlijk niet zozeer de array als geheel die wordt meegegeven, als wel de verwijzing ernaartoe. De array zelf blijft in het geheugen staan waar hij stond toen hij werd gecreëerd. We mogen hopen dat de persoon die de methode aanroept inderdaad een verwijzing naar een bestaande array meegeeft (en niet de waarde null), en dat bovendien de elementen van de array al een waarde hebben. De situatie zou bijvoorbeeld als volgt kunnen zijn: We kunnen een methode schrijven die zo n array als parameter krijgt, en die bijvoorbeeld uitzoekt wat de kleinste waarde is die in de array voorkomt: static int kleinste(int[] tabel) Zolang we de waarde nog niet geïnspecteerd hebben, gaan we er voorlopig van uit dat de waarde met volgnummer 0 de kleinste is. In het voorbeeld is dat 12; dat is niet echt de kleinste waarde, want dat is 11, maar dat kunnen we op dit moment nog niet weten. { int resultaat; resultaat = tabel[0]; Met een for-opdracht lopen we nu alle elementen langs. Bij elk element controleren we of die misschien kleiner is dan wat we tot nu toe dachten dat de kleinste was. Als dat zo is, passen we de waarde van de resultaatwaarde aan. int nummer; for (nummer=0; nummer<tabel.length; nummer++) if (tabel[nummer] < resultaat) resultaat = tabel[nummer];

125 11.2 Arrays 125 Na afloop van de for-opdracht kunnen we de resultaatwaarde veilig opleveren, want die is getoetst aan alle waarden in de array. return resultaat; Arrays van objecten De elementen van de array kunnen van elk gewenst type zijn. Dat kan een primitief type zijn zoals int, double, boolean of char, maar ook een object-type. Zo kun je bijvoorbeeld een array maken van Strings, van Buttons, van TextFields, of van objecten van een zelfgemaakte klasse, zoals Deeltje. Hier is een array met Deeltje-objecten, die hoofdstuk 9 gebruikt had kunnen worden in plaats van losse Deeltje-variabelen d1, d2 en d3: De verwijzings-variabele kan worden gedeclareerd met: Deeltje[] deeltjes; De eigenlijke array kan worden gecreëerd met deeltjes = new Deeltje[3]; Maar pas op: hiermee is weliswaar het array-object gecreëerd, maar nog niet de individuele deeltjes! Dat moet apart gebeuren in een for-opdracht, die elk deeltje apart creëert: int nummer; for (nummer=0; nummer<deeltjes.length; nummer++) deeltjes[nummer] = new Deeltje(); Arrays versus strings Als de elementen van een array van het type char zijn, begint zo n array verdacht veel op een Stringobject te lijken: immers, zowel in een array-van-char als in een String kan een tekst, bestaande uit meerdere symbolen, worden opgeslagen. Toch zijn er verschillen tussen een array-van-char en een String. De belangrijkste verschillen zijn: In een array kun je de individuele elementen nog veranderen met a[x]=... ; in een String kan dat niet. Van een String kun je daarentegen allerlei methoden aanroepen, zoals equals en substring. Bovendien kun je strings optellen met de +-operator. Met arrays kan dat allemaal niet. Er zijn ook een aantal overeenkomsten, hoewel de notatie voor sommige zaken voor een array en een string verschillend is: Zowel van een array als van een String kun je het zoveelste element opvragen, met respectievelijk de expressie a[x] en s.charat(x) Zowel van een array als van een String kun je de lengte bepalen. Bij een array door de object-variabele length te bekijken: a.length, bij een String door de methode length aan te roepen: s.length(). Let op: bij een array zijn dus geen haakjes nodig, bij een String wel!

126 126 Strings en Arrays import java.applet.applet; import java.awt.*; import java.awt.event.*; 5 public class Tekst extends Applet implements ActionListener { TextArea invoer, uitvoer; Button knop; 10 public void init() { invoer = new TextArea(5,30); uitvoer = new TextArea(28,20); knop = new Button("Tel letters"); uitvoer.seteditable(false); 15 this.add(invoer); this.add(knop ); this.add(uitvoer); knop.addactionlistener(this); 20 public void actionperformed(actionevent e) { TurfTab tabel; tabel = new TurfTab(); 25 tabel.turf( invoer.gettext() ); uitvoer.settext( tabel.tostring() ); Listing 19: Tekst/Tekst.java 11.3 Toepassing: Tekst-analyse met letterfrequentie Tellen van aparte letter-frequenties Nu kunnen we het programma schrijven dat van elke letter apart telt hoe vaak hij in een tekst voorkomt. We gaan nog eens de methode actionperformed herzien, voortbordurend op het voorbeeld eerder in dit hoofdstuk. public void actionperformed(actionevent e) { String s; int n; char c; s = invoer.gettext(); Voor de zestentwintig afzonderlijke tellertjes kunnen we nu een array gebruiken. Die moet worden gedeclareerd, gecreëerd, en alle waarden moeten om te beginnen op nul worden gezet: tabel = new int[26]; for (n=0; n<26; n++) tabel[n] = 0; Nu kunnen we de string langslopen. Voor elk symbool dat we tegenkomen, verhogen we de bijbehorende teller in de array. Het volgnummer van de gewenste teller bepalen we door van de Unicode-code van het aangetroffen symbool c, de Unicode-code van de constante A af te trekken. Daardoor wordt het aantal letters A bijgehouden in teller nummer 0, het aantal letters B in teller nummer 1, en het aantal letters Z in teller nummer 25. Voor hoofdletter en kleine letters treffen we aparte maatregelen, andere symbolen worden genegeerd. for (n=0; n<s.length(); n++) { c = s.charat(n); if (c>= A && c<= Z ) tabel[c- A ]++;

127 11.3 Toepassing: Tekst-analyse met letterfrequentie class TurfTab { int [] tellers; int totaal; public TurfTab() { tellers = new int[26]; private void turf(char c) { if (c>= A && c<= Z ) { 15 tellers[c- A ]++; totaal++; else if (c>= a && c<= z ) { 20 tellers[c- a ]++; totaal++; 25 public void turf(string s) { for (int i=0; i<s.length(); i++) turf( s.charat(i) ); 30 public String tostring() { String s = ""; for (int i=0; i<26; i++) 35 s += (char)(i+ A ) + ": " + tellers[i] + " keer\n"; s += "totaal: " + totaal; return s; Listing 20: Tekst/TurfTab.java

128 128 Strings en Arrays Figuur 18: De applet Tekst in werking else if (c>= a && c<= z ) tabel[c- a ]++; Nadat de hele telling is uitgevoerd, kunnen we de resultaten op het scherm weergeven. Daartoe lopen we de hele array langs, ditmaal op volgorde met behulp van een for-opdracht. Voor elke teller voegen we een toepasselijke tekst op het scherm toe. uitvoer.settext(""); for (n=0; n<26; n++) { c = (char)(n+ A ); uitvoer.append (c + ": " + tabel[n] + " keer\n" ); Scheiden van inhoud en userinterface Het is al met al een behoorlijk ingewikkelde methode geworden. Wat het vooral zo onoverzichtelijk maakt, is dat er een aantal zaken door elkaar lopen: het van het scherm plukken van de string, creatie van de array, het eigenlijke telwerk, en de lay-out van het resultaat. Veel overzichtelijker zou het worden als we een deel van deze taken konden uitbesteden aan de methoden van het object. Het zou bijvoorbeeld erg handig zijn als er een klasse TurfTabel bestond, die we met de aanroep van een methode turf konden vragen om alle letters in een bepaalde string te turven. Als we daarna ook zouden kunnen vragen om het resultaat als string weer te geven, dan hadden we de hele methode in vier regels kunnen schrijven: public void actionperformed(actionevent e) { TurfTab telling; telling = new TurfTab(); telling.turf( invoer.gettext() ); uitvoer.settext( telling.tostring() ); Zo n klasse TurfTabel bestaat natuurlijk niet. We kunnen hem voor de gelegenheid echter wel zelf maken. De verschillende deel-taken kunnen worden ondergebracht in aparte methoden: in de constructormethode: creatie en op-nul-zetten van de array

129 11.3 Toepassing: Tekst-analyse met letterfrequentie 129 in de methode turf: het eigenlijke telwerk in de methode tostring: de layout van het resultaat We kunnen zelfs nog een vierde methode maken, die het turven van één symbool voor zijn rekening neemt, rekening houdend met hoofdletters en dergelijk. In de methode turf kunnen we dan die methode aanroepen voor elk symbool dat in de string wordt aangetroffen. In de andere klasse blijft, naast bovenstaande korte versie van actionperformed, dan alleen nog het opbouwen van de userinterface over. Op deze manier is de communicatie met de gebruiker in één klasse ondergebracht, en alle inhoudelijke zaken in de andere klasse. Zo n scheiding van aandachtspunten is altijd een goede gewoonte. In listing 19 en listing 20 is de complete uitwerking blz. 126 van het programma weergegeven. blz. 127 Opgaven 11.1 Character escapes Hoeveel en welke tekens worden in het tekstveld uitvoer zichtbaar bij het uitvoeren van de opdracht: uitvoer.settext( "\"/\\//\"/" ); Schrijf bovendien een opdracht die de broncode van de hierboven geciteerde opdracht toont op het tekstveld bron Strings vergelijken Twee strings s en t kunnen vergeleken worden met s==t en met s.equals(t). Wat is het verschil? Vul bovendien het juiste woord in in de volgende uitspraken, en licht het antwoord toe: als s==t dan geldt (altijd / soms / nooit) s.equals(t) als s.equals(t) dan geldt (altijd / soms / nooit) s==t 11.3 Uppercase In de klasse String zit de methode touppercase, die een hoofdletterversie van de string oplevert. Die kun je bijvoorbeeld aanroepen met t = s.touppercase( ); Als je zelf de klasse String zou moeten schrijven, hoe zou je deze methode dan kunnen definiëren (gebruikmakend van andere methoden in de klasse String)? 11.4 Zoek de grootste a. Schrijf een statische methode grootste met als parameter een array van doubles, met als resultaat de hoogste waarde die in de array voorkomt. b. Schrijf een andere statische methode plaatsgrootste, met als resultaat het volgnummer (de index in de array) van de grootste waarde Frequentie van de kleinste Schrijf een methode met als parameter een array van doubles. De methode moet opleveren hoe vaak de kleinste waarde van de array voorkomt. Bijvoorbeeld: als de array de waarden 9,12,9,7,12,7,8,25,7 bevat, dan is het resultaat 3, omdat de kleinste waarde (7) drie maal voorkomt Sorteren Maak een variant van de methode plaatsgrootste, waarbij niet de hele array doorzocht wordt, maar alleen de eerste n elementen, waarbij n een aparte parameter is. Gebruik deze methode in de volgende methode: schrijf een statische methode sorteer, met als parameter een array van doubles. Het resultaattype is void. Na afloop van het uitvoeren van de methode moeten de elementen in de array in opklimmende volgorde van grootte staan. Hint hierbij: zoek eerst de plaats van de grootste waarde in de hele array. Verwissel deze waarde met de waarde op de laatste plaats. Dan staat de grootste waarde alvast achteraan, waar hij hoort. Zoek nu de grootste waarde van het resterende deel van de array. Die waarde wordt verwisseld met de op één na laatste waarde in de array. Dit gaat zo verder: zoek weer het grootste element van de array minus de laatste twee elementen, verwissel die met op het twee na laatste element, enzovoorts Zoek positie van een waarde

130 130 Strings en Arrays Schrijf een statische methode met als parameter een array van integers en een losse integer, die oplevert op welke plaats de losse integer als eerste in de array voorkomt. Als het getal nergens voorkomt, moet de methode 1 opleveren Slim zoeken (moeilijker) Als je weet dat de array op volgorde gesorteerd is, kun je op een slimmere manier zoeken: kijk in het midden van de array of je daar de gezochte waarde aantreft. Zo ja, mooi. Zo nee, dan weet je of je in de helft links ervan of in de helft rechts ervan moet verder zoeken. Op deze manier wordt het te doorzoeken stuk van de array bij elke stap twee keer zo klein. Gebruik twee integers die de grenzen van het te doorzoeken stuk array aanduiden Histogram Schrijf een statische methode met een array van int s als parameter. De methode levert een lange string op, met daarin de regels van een staafdiagram. De array-elementen bevatten getallen van 1 tot en met 10. Deze string moet een staafdiagram bevatten van de waarden in de array. Bijvoorbeeld, als de array de waarden 6, 10, 8, 6, 6, 4, 8, 5, 5, 7, 6, 2, 4, 10, 9, 5, 6, 8, 7, 6 bevat moet het staafdiagram 10 regels bevatten: 1: 2: * 3: 4: ** 5: *** 6: ****** 7: ** 8: *** 9: * 10: ***

131 131 Hoofdstuk 12 Ontwerp van programma s 12.1 Layout van de userinterface Layout managers De interactie-componenten die je door aanroep van add aan een applet toevoegt, komen allemaal naast elkaar op het scherm te staan. Past het er niet meer naast, dan gaat het verder op de rij daaronder. De ruimte die er voor het totale applet beschikbaar is, kan variëren. Als je het programma uitvoert met appletviewer, kun je dat zelfs doen terwijl het programma loopt; als je het programma uitvoert met een web-browser, kun je de maten die in de html-file staan gespecificeerd veranderen. Verandert de afmeting, dan worden de interactie-componenten opnieuw gerangschikt: het zou kunnen zijn dat er nu meer of juist minder naast elkaar passen. Door de afmetingen slim te kiezen, kun je een nette layout afdwingen (zie bijvoorbeeld het programma Simulatie, waar de drie ruimtes met deeltjes net op de eerste rij passen, zodat de twee buttons op de rij daaronder komen te staan). De indeling gebeurt geheel automatisch, en wordt verzorgd door een zogenaamde layout manager, die aan de applet is gekoppeld. Ben je niet tevreden met de verzorging van de layout, dan kun je een andere layout manager uitkiezen. Dat gebeurt door aanroep van de methode setlayout van de klasse Container. Dat is een superklasse van Applet, dus deze methode is ook in applets beschikbaar. Als parameter geef je de gewenste layout manager mee, wat hoe kan het ook anders een object is. Bijvoorbeeld: this.setlayout( new BorderLayout() ); Aangezien de layout manager verder in het programma niet expliciet nodig is, kan het gecreëerde object direct worden meegegeven aan setlayout, zonder het eerst in een variabele op te slaan. Beschikbare Layout managers Er zijn een viertal verschillende layout managers beschikbaar in de Java-bibliotheek. Ze zijn alle vier een implementatie van de interface LayoutManager, en dat is mooi, want dat verwacht de methode setlayout van zijn parameter. Het implementeren van een interface met implements is iets anders dan het maken van een subklasse met extends. In de klasse-diagrammen zullen we voor implements een stippellijn gebruiken, om het te onderscheiden van de doorgetrokken lijn die extends symboliseert: De standaard layout manager is FlowLayout. Met een FlowLayout object als layout managers worden alle componenten van links naar rechts en van boven naar beneden naast/onder elkaar gezet; zie figuur 19a. Met een GridLayout kun je componenten in nette rijen en kolommen weergeven. Als parameter van de constructor moet je opgeven hoeveel rijen en kolommen er zijn, en hoeveel pixels tussenruimte je tussen de rijen en kolommen wilt openhouden: this.setlayout( new GridLayout(2,3,5,5) );

132 132 Ontwerp van programma s Figuur 19: Een panel met respectievelijk FlowLayout, GridLayout, BorderLayout en null layout. Om de componenten in rijen en kolommen te kunnen indelen, verandert de layout manager de vorm van de componenten. Alle beschikbare ruimte wordt eerlijk verdeeld over de rijen en kolommen. Als er niet genoeg ruimte is voor een object, dan heb je pech (zie de knop met het opschrift kortschildkever in figuur 19b). Een BorderLayout ordent de componenten langs de vier randen van het scherm, met een vijfde component in het midden. Er kunnen in dit geval maximaal vijf componenten worden gerangschikt. Bij elke aanroep van add moet worden gespecificeerd waar de component terecht moet komen. Dat gebeurt met een extra parameter bij add, die één van de vijf daarvoor bestemde constanten uit de klasse BorderLayout moet zijn: this.setlayout( new BorderLayout() ); this.add(new Button("koe"), BorderLayout.NORTH ); this.add(new Button("varken"), BorderLayout.WEST ); this.add(new Button("kortschildkever"), BorderLayout.CENTER ); this.add(new Button("kip"), BorderLayout.EAST ); this.add(new Button("olifant"), BorderLayout.SOUTH ); Bij deze layoutmanager krijgen Noord en Zuid zoveel hoogte als ze nodig hebben, maar worden ze in de breedte opgerekt. West en Oost krijgen zoveel breedte als ze nodig hebben, en worden in de lengte opgerekt. Alle overblijvende ruimte gaat naar het centrum; zie figuur 19c. Een laatste alternatief is om helemaal geen layout manager te gebruiken. Om de default FlowLayout kwijt te raken, moet je dan null opgeven als layout manager. De consequentie is dat je van elke component door middel van een aanroep van setbounds moet aangeven waar hij op het scherm geplaatst moet worden. Dat geeft je complete vrijheid in de layout, zoals in figuur 19d. Maar pas op: als de gebruiker nu de afmetingen van het window verandert, wordt de layout niet aangepast!

133 12.2 Toepassing: Rekenmachine 133 De init-methode bevat opdrachten als this.setlayout(null); b =new Button("koe"); b.setbounds(10,10,70,20); this.add(b); b =new Button("varken"); b.setbounds(25,50,50,30); this.add(b); b =new Button("kortschildkever"); b.setbounds(40,30,100,15); this.add(b); De klasse Panel Als je wel de layout managers wilt gebruiken, maar je wilt toch een complexere layout bereiken, dan kun je gebruik maken van de klasse Panel. Dit is een component zoals alle andere, maar met een bijzondere eigenschap: je kunt er met add deel-componenten aan toevoegen. Bovendien kan een Panel-object een eigen layout-manager krijgen. Je kunt dus bijvoorbeeld het applet een BorderLayout geven, en in het centrum een Panel-object plaatsen die voor zijn inhoud een GridLayout gebruikt. In de casus in de volgende sectie gebruiken we dit mechanisme. Je kunt zelfs aan het Panel weer deel-panels toevoegen, die er weer een andere layout op nahouden. Op deze manier kun je ingewikkelde dashboards opbouwen Toepassing: Rekenmachine Beschrijving van de casus We gaan een eenvoudige 4-functie calculator maken. Voorlopig werkt de calculator alleen met integer getallen (maar dat kan eenvoudig aangepast worden). De gebruiker kan de calculator bedienen met buttons op het scherm, zoals in figuur 20. Net als in het vorige hoofdstuk maken we weer twee klassen: een klasse Calc waar de userinterface wordt opgebouwd, en het indrukken van de knoppen wordt afgehandeld (zie listing 21); blz. 134 een klasse Proc waar al het inhoudelijke werk gebeurt, maar die juist niets met de userinterface te maken wil hebben (zie listing 22). blz. 135 De processor van de rekenmachine Voor het schrijven van de klasse Proc moeten we ons eerst afvragen welke variabele er in deze klasse nodig zijn. Met andere woorden: in welke toestand de processor zich kan bevinden. Om daar achter te komen, bekijken we in detail wat er gebeurt als de gebruiker de rekenmachine bedient. De gebruiker begint met een getal in te toetsen. Alle ingedrukte cijfertoetsen worden geaccumuleerd in een steeds groter wordend getal. Is het getal 12 al ingevoerd, en drukt de gebruiker op de 3-toets, dan wordt de nieuwe waarde 123; dat is 10 maal de oude waarde plus het ingedrukte cijfer. Daarna drukt de gebruiker een operator-toets in. Op het scherm verandert ogenschijnlijk niet (er blijft bijvoorbeeld 123 staan), maar de huidige waarde is blijkbaar anders geworden. Na het indrukken van de 4-toets verschijnt namelijk niet 1234, maar 4 op het scherm. De gebruiker gaat door met het intoetsen van het tweede getal, bijvoorbeeld 456, en drukt tenslotte op de =-toets. Op dat moment is de oude waarde (123) ineens weer van belang, want die moet worden opgeteld/vermenigvuldigd/enz. bij de nieuwe waarde. Bovendien is de eerder gekozen operator van belang, want na het indrukken van de =-toets moet de machine weten of er opgeteld of vermenigvuldigd moet worden. Voor de toestand van de rekenmachine zijn dus van belang: de huidige waarde de vorige waarde de waarde op het scherm (niet altijd gelijk aan de huidige waarde!) de laatst gekozen operator De klasse Proc krijgt dus vier object-variabelen: drie getallen en een char. Voor de getallen gebruiken we long-waarden in plaats van int-waarden, zodat de calculator getallen tot 18 cijfers aankan. Verder maken we methoden voor de diverse acties die op de calculator kunnen worden toegepast: De methode cijfer accumuleert een cijfer bij het huidige getal. De methode reken voert de berekening die de variabele operator aangeeft uit op het vorige getal en de huidige waarde. Het resultaat komt in de variabele scherm, en wordt ook de nieuwe vorige waarde, zodat het resultaat meteen als linker operand van de volgende operatie gebruikt kan worden.

134 134 Ontwerp van programma s import java.applet.applet; import java.awt.*; import java.awt.event.*; 5 public class Calc extends Applet implements ActionListener { Label result; Panel knoppen; Proc proc; 10 public void init() { Button knop; String opschrift; 15 proc = new Proc(); result = new Label( "0", Label.RIGHT ); knoppen = new Panel(); result.setfont( new Font("Arial", Font.BOLD, 20) ); 20 this. setlayout( new BorderLayout() ); knoppen.setlayout( new GridLayout(4,4,6,6) ); for (int n=0; n<16; n++) { opschrift = "789/456*123+0C=-".substring(n,n+1); knop = new Button(opschrift); 25 knop.addactionlistener(this); knoppen.add( knop ); this.add(result, BorderLayout.NORTH ); this.add(knoppen, BorderLayout.CENTER); 30 public void actionperformed(actionevent e) { Button b; char c; 35 b = (Button) (e.getsource()); c = b.getlabel().charat(0); if (c== C ) proc.schoon(); else if (c== = ) proc.reken(); 40 else if (c>= 0 &&c<= 9 ) proc.cijfer( c- 0 ); else proc.operatie(c); result.settext( ""+proc.scherm ); Listing 21: Calc/Calc.java

135 12.2 Toepassing: Rekenmachine class Proc { long waarde, vorige, scherm; char operator; Proc() { schoon(); void schoon() { waarde = 0; vorige = 0; 15 operator = + ; scherm = 0; void reken() 20 { switch(operator) { case + : vorige += waarde; break; case - : vorige -= waarde; break; 25 case * : vorige *= waarde; break; case / : vorige /= waarde; break; scherm = vorige; waarde = 0; 30 void cijfer(int n) { waarde = 10*waarde+n; 35 scherm = waarde; void operatie(char c) { 40 reken(); operator = c; Listing 22: Calc/Proc.java

136 136 Ontwerp van programma s Figuur 20: De applet Calc in werking. De methode operatie correspondeert met het indrukken van een operator-toets. Eerst wordt de eventueel nog hangende berekening uitgevoerd; vervolgens wordt de nieuwe operator opgeslagen. De methode schoon initialiseert de toestand. Door het toekennen van + aan de variabele operator, kan de =-toets ook veilig ingedrukt worden als er alleen maar cijfers zijn ingevoerd. De schoon-methode correspondeert met de C-toets, maar wordt ook in de constructormethode aangeroepen. De userinterface van de rekenmachine In de methode init bouwen we de userinterface op. In een for-opdracht worden 16 buttons gecreëerd. Met een aanroep van substring kiezen we voor elke button het juiste opschrift uit een string met alle opschriften. De buttons worden met add toegevoegd aan een voor dat doel aangemaakt Panel-object, die een GridLayout heeft. Daardoor komen de knoppen netjes in een 4 4 raster te staan. Het Panel op zijn beurt wordt, samen met een Label voor het resultaat, aan de totale applet toegevoegd, die een BorderLayout heeft gekregen. In de methode actionperformed reageren we op een actie van de gebruiker. Omdat in dit programma alleen maar Buttons een action-event kunnen genereren, weten we zeker dat getsource een Button-object zal opleveren. Met een cast kunnen we die dus veilig naar Button converteren, waarna we het opschrift van de button kunnen achterhalen. Afhankelijk van dit opschrift wordt één van de methodes van Proc aangeroepen. Het resultaat wordt vervolgens op de display van de calculator getoond. De switch-opdracht In de klasse Calc wordt een nieuw soort opdracht gebruikt: de switch-opdracht. De switch-opdracht kan gebruikt worden als vereenvoudiging van een veel voorkomend soort ifopdracht. Vaak wordt een if-opdracht gebruikt om een variabele met een aantal alternatieven te vergelijken: if (x==1) een(); else if (x==2) twee(); else if (x==3) drie(); else meer(); Met een switch-opdracht kan dit worden geschreven als: switch(x) { case 1: een(); break; case 2: twee(); break; case 3: drie(); break; default: meer();

137 12.3 Applications 137 Bij uitvoering van een switch-opdracht wordt de waarde tussen de haakjes uitgerekend. Vervolgens wordt de verwerking van opdrachten voortgezet achter het woord case met de betreffende waarde. Is dit er niet, dan worden de opdracht(en) achter default uitgevoerd. De waarden achter de diverse cases moeten constanten zijn. De break-opdracht Als we niet uitkijken, worden in een switch-opdracht niet alleen de opdrachten achter betreffende case uitgevoerd, maar ook de opdrachten achter de volgende cases. Alleen de plaatsing van de speciale break-opdracht verhindert dit. De betekenis van de break-opdracht is: stop de verwerking van de body van de switch-, while- of for-opdracht waarmee je bezig bent. Zonder de plaatsing van deze opdrachten zou in bovenstaand voorbeeld in het geval dat x de waarde 2 heeft niet alleen methode twee worden aangeroepen, maar ook drie en meer Applications Opbouw van een application Tot nu toe hadden alle voorbeeldprogramma s de vorm van een applet, bedoeld om hun resultaat te tonen in het window van een web-browser. Je maakt een applet door een subklasse te maken van de klasse Applet, en daarin init en/of paint te herdefiniëren. Als reactie op een <APPLET>-tag in de html-file maakt de browser dan een object met die klasse als type, en bewerkt dat object vervolgens met de methoden init en paint. In plaats van met een browser kun je het applet ook runnen met het programma appletviewer. Die toont het applet in een apart window, maar dat window is eigendom van appletviewer: de titel van het window luidt altijd Applet Viewer, er is één menu genaamd Applet waarmee je het programma kunt starten en stoppen, en onderin beeld verschijnen meldingen als Applet started. Je kunt echter ook Java-programma s maken die hun resultaat tonen in een zelfstandig window, compleet met een eigen titel en eigen menu-keuzes. Zo n programma heet een application. Applications kunnen, net als applets, gebruik maken van de Java-bibliotheken, inclusief de AWTlibrary om buttons, scrollbars en dergelijke te maken. Alleen de klasse Applet mag niet gebruikt worden in een application. In feite mag er in applications zelfs meer dan in applets: zo kan een application bijvoorbeeld files op de disk lezen en schrijven. Ook kan een application extra windows aanmaken en aparte dialoog-windows opstarten om bijvoorbeeld om een filenaam te vragen. Applets mogen dat soort faciliteiten niet gebruiken, omdat dat een veiligheids-risico zou betekenen: applets worden immers gestart als je een web-pagina bezoekt, en het surft niet lekker als je ieder moment een applet zou kunnen tegenkomen dat ongevraagd allerlei files op je harddisk zou weggooien. Applications worden door de gebruiker expliciet opgestart, en hebben daarmee dezelfde rechten die andere programma s hebben; de gebruiker dient een application dus te vertrouwen. De methode main Voor het uitvoeren van een application heb je geen web-browser nodig, en ook niet het programma appletviewer. Je hebt echter wel een Java-bytecode-interpreter nodig. Die is aanwezig in de vorm van het programma java. Je kunt deze interpreter starten vanaf een commandoregel met bijvoorbeeld het commando java Hallo of vanuit de JCreator-omgeving met menukeuze Build Execute Project (of simpelweg F5). In beide gevallen reageert de interpreter met het aanroepen van een methode. Ditmaal is dat niet (zoals bij applets) de methode init, maar in plaats daarvan de methode main. Er is nog een verschil met applets: bij applets maakt de browser eerst een object aan, om dat onder handen te geven aan de methode init; bij applications maakt de interpreter geen object aan. De methode main is dan ook een statische methode, dat wil zeggen een methode zonder object onder handen. Deze methode mag dan ook niet eventuele object-variabelen, die boven in de klasse zijn gedeclareerd, gebruiken. De methode main moet exact de volgende header hebben: public static void main(string[] p)

138 138 Ontwerp van programma s De methode levert dus void op, is public omdat hij van buitenaf gebruikt moet kunnen worden, is static omdat hij geen object onder handen krijgt, en heeft een array met strings als parameter. Alleen de naam van de parameter mag je nog variëren. Hier is de naam p gekozen. Creatie van een window Omdat er in applications nog geen window aanwezig is om de resultaten in te kunnen tonen, zul je die zelf moeten maken (tenzij je een niet-window-programma schrijft, dat zijn uitvoer toont in een dos-sessie of iets vergelijkbaars; zie sectie 13.6). Je kunt zo n window maken door een object van het type Frame te creëren. Dan krijg je echter een leeg window; in de praktijk zul je daarom een subklasse van Frame definiëren, en van die klasse een object aanmaken. In de constructor-methode van deze klasse gebeurt wat in een applet in de methode init zou gebeuren. De opbouw van een application is daarom vaak als volgt: class Hallo extends Frame { Hallo() { // de noodzakelijke initialisaties public static void main(string[] p) { // aanmaak van een Hallo-object De methode main hoeft strikt genomen niet in diezelfde klasse te staan. Het is misschien zelfs wel een beetje raar dat de methode main een object van zijn eigen klasse aanmaakt. Maar kwaad kan dit ook niet, en zeker in kleine programma s zou het wat overdreven zijn om speciaal voor de methode main een andere klasse aan te maken. Een initialisatie die in ieder geval nodig is, is het vaststellen van de afmetingen van het window. Er is immers geen html-file meer, zoals die bij applets werd gebruikt om de afmetingen op te geven. Om het window ook zichtbaar te maken, kun je in main van het kersverse object vervolgens de methode setvisible aanroepen. Resultaten kun je tonen in het window door, net als in het geval van applets, de methode paint te herdefiniëren. Een complete Hallo-application luidt bijvoorbeeld: import java.awt.*; class Hallo extends Frame { Hallo() { this.setsize(200,100); public void paint(graphics gr) { gr.drawstring("hallo", 50, 50); public static void main(string[] p) { Hallo h; h = new Hallo(); h.setvisible(true); Let op dat de methode paint niet static is: deze methode heeft immers het Frame-object, of liever gezegd het object van de subklasse daarvan, onder handen. Een alternatief voor het directe tekenen door herdefinitie van paint, is het gebruikmaken van interactie-componenten zoals TextField en Button. Die kunnen zoals gebruikelijk van ActionListeners worden voorzien, zodat het programma kan reageren op het gebruik ervan. Interactie-componenten die nodig zijn in de methode actionperformed, moeten als objectvariabele worden gedeclareerd. Een application heeft standaard een BorderLayout als LayoutManager. Dat betekent dat je bij aanroep van add een windrichting moet opgeven. Bijvoorbeeld: import java.awt.*; import java.awt.event.*; class Hallo extends Frame implements ActionListener

139 12.4 Menu s en WindowEvents 139 { TextField t; Button b; Hallo() { t = new TextField(20); b = new Button("druk hier"); this.setsize(200,100); this.add(t, BorderLayout.CENTER); this.add(b, BorderLayout.NORTH ); b.addactionlistener(this); public void actionperformed(actionevent e) { t.settext("hallo"); public static void main(string[] p) { Hallo h; h = new Hallo(); h.setvisible(true); 12.4 Menu s en WindowEvents Creatie van een menu-balk Om een menu-balk te creëren zijn er een drietal standaard-klassen beschikbaar: MenuBar, Menu en MenuItem. Door aanroep van de methode setmenubar kan de menubar boven in het Frame-object worden neergezet. De MenuItems kunnen een ActionListener krijgen, die een seintje krijgt als het item wordt gekozen. Zoals we ook steeds bij applets hebben gedaan, kunnen we het totale (Frame-subklasse-)object gebruiken als listener. Hoe de creatie van menu s precies verloopt kun je eigenlijk raden. De objecten worden geconstrueerd, en met add aan elkaar toegevoegd. Als parameter van de constructor dient de String die als menu-titel moet worden getoond. Een typisch menu kun je opbouwen met: MenuBar bar; Menu menu; MenuItem item; bar = new MenuBar(); menu = new Menu("File"); item = new MenuItem("Open"); menu.add(item); item.addactionlistener(this); item = new MenuItem("Save"); menu.add(item); item.addactionlistener(this); menu.addseparator(); item = new MenuItem("Quit"); menu.add(item); item.addactionlistener(this); bar.add(menu); Aanroep van de methode addseparator heeft als effect dat er een horizontale scheidingslijn verschijnt tussen de menuitems Save en Quit. De variabele item wordt steeds opnieuw gebruikt. Dit natuurlijk pas nadat de nieuwe objecten zijn toegevoegd aan het File-menu. Nadat het menu in zijn geheel aan de menubar is toegevoegd, kan ook de variabele menu opnieuw worden gebruikt voor het volgende menu: menu = new Menu("Edit"); item = new MenuItem("Find"); menu.add(item); item.addactionlistener(this); item = new MenuItem("Copy"); menu.add(item); item.addactionlistener(this); item = new MenuItem("Paste"); menu.add(item); item.addactionlistener(this); bar.add(menu); Tot slot wordt de aldus opgebouwde menubar aan het frame gehangen: this.setmenubar(bar); Het window-sluitmenu In de rand van het window zijn de gebruikelijke knopjes aanwezig om het window te sluiten, te maximaliseren, en te iconificeren. Het sluit-knopje werkt echter nog niet. Het indrukken ervan genereert wel een Event, maar we moeten zelf in het programma schrijven dat hierop gereageerd moet worden door het programma te beëindigen.

140 140 Ontwerp van programma s Het event is van een nieuw type: WindowEvent. Daarnaar kan worden geluisterd door implementaties van WindowListener. We schrijven in de klasse daarom class Programma extends Frame implements ActionListener, WindowListener In de constructor komt te staan dat het window luistert naar zijn eigen window-events: private Programma() { this.addwindowlistener(this); De belofte om WindowListener te zijn wordt ingelost door het schrijven van de methode windowclosing. In de body roepen we de methode exit aan (een statische methode uit de klasse System), die onmiddelijke beëindiging van het programma tot gevolg heeft. public void windowclosing (WindowEvent e) { System.exit(0); Dat is echter nog niet voldoende. Door de interface WindowListener worden namelijk niet één, maar zeven methodes gevraagd. Hoewel die in dit programma allemaal niet nodig zijn, moeten ze wel gedefinieerd worden. De body kan daarbij dan leeg blijven. Aldus definiëren we ook: public void windowclosed (WindowEvent e) { public void windowopened (WindowEvent e) { public void windowiconified (WindowEvent e) { public void windowdeiconified(windowevent e) { public void windowactivated (WindowEvent e) { public void windowdeactivated(windowevent e) { 12.5 Toepassing: een bitmap-editor Bitmaps Een bitmap is een tekening die uit kleine puntjes is opgebouwd. Bitmaps worden veel gebruikt, omdat beeldschermen (bijna) altijd uit losse beeldpunten bestaan, waarop een bitmap goed kan worden weergegeven. De naam bitmap geeft aan dat elk punt door een bit (0 of 1), of boolean waarde (false of true) kan worden afgebeeld. Een 0 of false duidt een wit punt aan, een 1 of true een zwart punt. Eigenlijk is het een woord uit het zwartwit-tijdperk, want plaatjes zijn tegenwoordig natuurlijk over het algemeen in kleur. De correcte term voor een kleurenplaatje dat uit losse punten is opgebouwd is eigenlijk pixmap : een plaatje dat uit pixels oftewel beeldpunten is opgebouwd. Functies van de bitmap-editor We gaan in deze sectie een bitmap-editor ontwikkelen. Het is geen pixmap-editor: met dit programma kun je alleen zwartwit-plaatjes maken. Het programma toont een vergrote weergave van een bitmap, die de gebruiker met de muis kan veranderen: met de linker muisknop worden punten ingekleurd, met de rechter muisknop weer blanco. We maken twee versies van het programma: een applet en een application. De gebruiker kan een aantal operaties op het plaatje uitvoeren. In de application gaat dat met behulp van menukeuzes, in de applet met buttons. Deze operaties zijn: schoonmaken van het hele plaatje inverteren van het plaatje (wit wordt zwart en zwart wordt wit) het plaatje één beeldpunt opschuiven naar links, rechts, boven of beneden het plaatje massiever maken, of juist hol het zogenaamde game of life spelen, waarbij een nieuwe generatie wordt berekend, of een continue animatie wordt getoond Als het programma gewoon start, dan wordt als afmetingen van de bitmap beeldpunten gebruikt. Wil de gebruiker een bitmap met andere afmetingen editten, dan moet die het programma parametriseren: in de application via de commandoregel, in de applet met <PARAM>-tags in de html-file. blz. 148 Opdeling in klassen We verdelen het programma over een aantal verschillende klassen: De hoofdklasse van de application wordt zoals altijd een subklasse van Frame, die we BitEdit zullen noemen (zie listing 29 en verder).

141 12.5 Toepassing: een bitmap-editor 141 De hoofdklasse van de applet wordt zoals altijd een subklasse van Applet, die we BitApplet zullen noemen. De applet-versie van het programma heeft vrijwel dezelfde opbouw als de applicatie: de methode main is niet nodig, en in plaats van de constructormethode hebben we een methode init, maar de rest is hetzelfde. Alleen enkele details bij het maken van de menu s verschillen nog, omdat de menu s in de applet de vorm van buttons hebben in plaats van drop-down menu s. De listing is daarom niet apart afgedrukt. De belangrijkste interface-component die in het programma gebruikt worden is de weergave van de tekening. De tekening is een subklasse van Canvas, waarin de paint-methode opnieuw is gedefinieerd. Onze subklasse van Canvas zullen we BitCanv noemen (zie listing 23 en blz. 142 verder). Voor de eigenlijke bitmap gaan we een klasse maken waarin de inhoud van een bitmap wordt gemodelleerd, zonder daarbij de precieze visuele representatie vast te leggen. Een bitmap, compleet met alle benodigde operaties die erop uitgevoerd kunnen worden, zullen we modelleren in de klasse BitMap (zie listing 25 en verder). Dit is eenzelfde aanpak als in blz. 144 het rekenmachine-programma in hoofdstuk 12, die een aparte klasse Proc had waarin het blz. 131 model ligt opgeslagen. Methoden van BitEdit In de klasse BitEdit wordt de grafische userinterface (GUI) gemodelleerd. Zoals in eerdere programma s ook het geval was, wordt de statische methode main in deze klasse ondergebracht. Het belangrijkste wat daar gebeurt is het creëren en visible maken van één BitEdit-object. In de constructormethode van BitEdit worden de voor een application gebruikelijke administratieve details afgehandeld. Verder wordt de opbouw van de interface en het menu gedaan, al wordt dat werk voor de overzichtelijkheid uitbesteed aan twee daarvoor te schrijven methoden initinterface en initmenu. Om te kunnen reageren op commando s die de gebruiker via de drop-down menu s geeft, wordt de methode actionperformed gedefinieerd, en krijgen alle menu-items this als actionlistener. Verder is er nog een methode run om de in de Game of Life benodigde animatie te laten lopen. Muiskliks worden niet afgehandeld: dat doet de BitCanv, die deeluitmaakt van de GUI, zelf. Methoden van BitCanv In de klasse BitCanv wordt de grafische afbeelding van de tekening gemodelleerd. De tekening zelf wordt in in deze klasse niet gemodelleerd, uitsluitend het weergeven ervan op het scherm. Daartoe wordt de methode paint hergedefinieerd. Hierin wordt een lijnenstelsel getekend, waarmee de individuele beeldpunten afgebakend worden. Verder worden de gekleurde beeldpunten als vierkantje ingetekend. Hiertoe wordt aan een BitMap-object gevraagd welke punten gekleurd zijn. Het indrukken van de muis wordt afgevangen door het implementeren van de methoden uit de interface MouseListener en MouseMotionListener. Als reactie op het indrukken van de muis, of het draggen (bewegen met ingedrukte muisknop) wordt het betreffende punt in het BitMapobject gekleurd (of juist blanco) gemaakt, en het hele plaatje opnieuw getekend. Bij het tekenen wordt zoveel mogelijk het hele scherm gevuld. Om te bepalen hoe groot de beeldpunten kunnen worden afgebeeld, wordt de beschikbare ruimte wordt gedeeld door het aantal beeldpunten. Om de beeldpunten vierkant te houden, wordt het minimum van de uitkomsten van deze delingen voor de breedte en de hoogte gebruikt. Methoden van BitMap In een object van de klasse BitMap worden de bits van het plaatje opgeslagen. Er komen methoden iszwart en maakzwart om de status van een beeldpunt op te vragen, respectievelijk te veranderen. (Die kunnen vanuit BitCanv worden aangeroepen als de bitmap getekend moet worden, of met de muis veranderd wordt). Daarnaast komen er de volgende methoden in de klasse BitMap: Twee constructor-methoden: de ene maakt een blanco bitmap met gegeven breedte en hoogte, een andere maakt een kopie van een bestaande bitmap. Zes methoden die het hele plaatje tegelijk veranderen: clear om te wissen, invert om het te inverteren, en left, right, up en down om het beeld te verschuiven. Deze operaties werken op de bitmap die onderhanden is; na aanroep van deze methoden is de bitmap dus veranderd. Drie methoden die het plaatje veranderen vogens bepaalde regels: bold maakt het plaatje massiever, outline bepaalt de omtrek van het plaatje, en life bepaalt een nieuwe generatie

142 142 Ontwerp van programma s import java.awt.*; import java.awt.event.*; class BitCanv extends Canvas implements MouseListener, MouseMotionListener 5 { BitMap model; BitCanv(int w, int h) { model = new BitMap(w,h); 10 this.addmouselistener(this); this.addmousemotionlistener(this); public void setmodel(bitmap bm) 15 { model = bm; this.repaint(); private int diameter() 20 { Dimension dim = getsize(); return Math.min( dim.width/model.getwidth(), dim.height/model.getheight() ); public void update(graphics g) 25 { paint(g); private static boolean islinkerknop(mouseevent e) { return (e.getmodifiers()&mouseevent.button1_mask)!=0; 30 public void mousepressed (MouseEvent e) { int x, y, w, h, d; 35 d = diameter(); x = e.getx()/d; y = e.gety()/d; w = model.getwidth(); h = model.getheight(); if (x>=0 && x<w && y>=0 && y<h) model.maakzwart(x, y, islinkerknop(e) ); this.repaint(); Listing 23: BitEdit/BitCanv.java, deel 1 van 2

143 12.5 Toepassing: een bitmap-editor 143 public void paint(graphics g) { int x, y, w, h, d; w = model.getwidth(); h = model.getheight(); 50 d = this.diameter(); g.setcolor(color.blue); for (y=0; y<=h; y++) g.drawline(0, y*d, w*d, y*d ); 55 for (x=0; x<=w; x++) g.drawline(x*d, 0, x*d, h*d ); for (y=0; y<h; y++) { for (x=0; x<w; x++) 60 { if (model.iszwart(x,y)) g.setcolor(color.red); else g.setcolor(color.white); g.fillrect( x*d+1, y*d+1, d-1, d-1 ); 65 public void doeoperatie(string aktie) 70 { if (aktie.equals("step")) model.life(); else if (aktie.equals("left")) model.left(); else if (aktie.equals("right")) model.right(); else if (aktie.equals("up")) model.up(); else if (aktie.equals("down")) model.down(); 75 else if (aktie.equals("clear")) model.clear(); else if (aktie.equals("invert")) model.invert(); else if (aktie.equals("bold")) model.bold(); else if (aktie.equals("outline")) model.outline(); else if (aktie.equals("rotate")) this.setmodel(model.rotate()); 80 else return; this.repaint(); public void mouseentered (MouseEvent e) { 85 public void mouseexited (MouseEvent e) { public void mousereleased(mouseevent e) { public void mouseclicked (MouseEvent e) { public void mousemoved (MouseEvent e) { public void mousedragged (MouseEvent e) { this.mousepressed(e); 90 Listing 24: BitEdit/BitCanv.java, deel 2 van 2

144 144 Ontwerp van programma s import java.awt.*; import java.awt.image.*; 5 { class BitMap extends BufferedImage static final int WIT = 0xFFFFFFFF; static final int ZWART = 0xFF000000; BitMap(int w, int h) 10 { super(w, h, BufferedImage.TYPE_INT_RGB ); this.clear(); BitMap(BufferedImage ander) 15 { this(ander.getwidth(), ander.getheight()); int w = ander.getwidth(); int h = ander.getheight(); this.setrgb(0,0,w,h, ander.getrgb(0,0,w,h,null,0,w), 0, w); void maakzwart(int x, int y, boolean b) { if (b) this.setrgb(x, y, ZWART); else this.setrgb(x, y, WIT); boolean iszwart(int x, int y) { return this.getrgb(x, y)==zwart; void clear() { int x, y, w, h; w = this.getwidth(); h = this.getheight(); 35 for (y=0; y<h; y++) for (x=0; x<w; x++) this.setrgb(x, y, WIT); 40 void invert() { int x, y, w, h; w = this.getwidth(); h = this.getheight(); for (y=0; y<h; y++) 45 for (x=0; x<w; x++) this.maakzwart(x, y,! this.iszwart(x,y) ); Listing 25: BitEdit/BitMap.java, deel 1 van 4

145 12.5 Toepassing: een bitmap-editor 145 void left() 50 { int x, y, w, h; w = this.getwidth(); h = this.getheight(); for (y=0; y<h; y++) { for (x=1; x<w; x++) 55 this.setrgb(x-1, y, this.getrgb(x,y) ); this.setrgb(w-1, y, WIT); 60 void right() { int x, y, w, h; w = this.getwidth(); h = this.getheight(); for (y=0; y<h; y++) 65 { for (x=w-1; x>0; x--) this.setrgb(x,y, this.getrgb(x-1,y)); this.setrgb(0, y, WIT); 70 void up() { int x, y, w, h; w = this.getwidth(); h = this.getheight(); 75 for (x=0; x<w; x++) { for (y=1; y<h; y++) this.setrgb(x,y-1, this.getrgb(x,y)); this.setrgb(x, h-1, WIT); 80 void down() { int x, y, w, h; w = this.getwidth(); 85 h = this.getheight(); for (x=0; x<w; x++) { for (y=h-1; y>0; y--) this.setrgb(x, y, this.getrgb(x,y-1)); this.setrgb(x, 0, WIT); 90 Listing 26: BitEdit/BitMap.java, deel 2 van 4

146 146 Ontwerp van programma s void or (BitMap ander) { int x, y, w, h; 95 w = this.getwidth(); h = this.getheight(); for (y=0; y<h; y++) for (x=0; x<w; x++) this.maakzwart(x, y, this.iszwart(x,y) ander.iszwart(x,y) ); 100 void xor (BitMap ander) { int x, y, w, h; w = this.getwidth(); 105 h = this.getheight(); for (y=0; y<h; y++) for (x=0; x<w; x++) this.maakzwart(x, y, this.iszwart(x,y) ^ ander.iszwart(x,y) ); 110 void and (BitMap ander) { int x, y, w, h; w = this.getwidth(); h = this.getheight(); 115 for (y=0; y<h; y++) for (x=0; x<w; x++) this.maakzwart(x, y, this.iszwart(x,y) && ander.iszwart(x,y) ); 120 void bold() { BitMap ander; ander = new BitMap(this); ander.left(); this.or(ander); 125 ander = new BitMap(this); ander.down(); this.or(ander); 130 void outline() { BitMap ander; ander = new BitMap(this); ander.left(); ander.down(); 135 this.xor(ander); Listing 27: BitEdit/BitMap.java, deel 3 van 4

147 12.5 Toepassing: een bitmap-editor 147 void life() { int x, y, n; 140 BitMap oud; oud = new BitMap(this); int w, h; w = this.getwidth(); h = this.getheight(); 145 for (y=0; y<h; y++) for (x=0; x<w; x++) { n = oud.buren(x,y); this.maakzwart(x,y, n==3 (oud.iszwart(x,y)&&n==2) ); 150 int buren(int x, int y) { 155 int w, h; w = this.getwidth(); h = this.getheight(); int xl = x-1; if (xl<0) xl+=w; 160 int yl = y-1; if (yl<0) yl+=h; int xr = x+1; if (xr>=w) xr-=w; int yr = y+1; if (yr>=h) yr-=h; int n=0; if (this.iszwart(xl,yl)) n++; 165 if (this.iszwart(x,yl)) n++; if (this.iszwart(xr,yl)) n++; if (this.iszwart(xl,y )) n++; if (this.iszwart(xr,y )) n++; if (this.iszwart(xl,yr)) n++; 170 if (this.iszwart(x,yr)) n++; if (this.iszwart(xr,yr)) n++; return n; 175 BitMap rotate() { int x, y, w, h; w = this.getwidth(); h = this.getheight(); 180 BitMap result = new BitMap( h, w ); for (y=0; y<h; y++) for (x=0; x<w; x++) result.setrgb(h-1-y, x, this.getrgb(x,y) ); return result; 185 Listing 28: BitEdit/BitMap.java, deel 4 van 4

148 148 Ontwerp van programma s 5 10 import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.io.*; class BitEdit extends Frame implements ActionListener, WindowListener, Runnable { BitCanv uitvoer; Thread animatie; final int animatievertraging = 100; final String items [][] = { {"File", "Open", "Save", "Quit", {"Move", "Left", "Right", "Up", "Down", {"Edit", "Clear","Invert","Bold", "Outline" 15, {"Transform", "Rotate","Scale", {"Life", "Step", "Start", "Stop" ; private BitEdit (int w, int h) 20 { this.setsize(800,600); this.settitle("bitmap editor"); this.addwindowlistener(this); initinterface(w, h); initmenu(); 25 private void initmenu() { MenuBar bar; bar = new MenuBar(); 30 for (int i=0; i<items.length; i++) bar.add( maakmenu(items[i], this) ); this.setmenubar(bar); 35 private Menu maakmenu(string [] keuzes, ActionListener listener) { Menu menu; MenuItem item; menu = new Menu(keuzes[0]); for (int i=1; i<keuzes.length; i++) 40 { item = new MenuItem(keuzes[i]); menu.add(item); item.addactionlistener(listener); return menu; 45 private void initinterface(int w, int h) { uitvoer = new BitCanv(w, h); this.setlayout(new BorderLayout() ); 50 this.add(uitvoer, BorderLayout.CENTER); public void actionperformed(actionevent ev) { String aktie; 55 aktie = ((MenuItem) ev.getsource()).getlabel(); this.doeoperatie(aktie); Listing 29: BitEdit/BitEdit.java, deel 1 van 2

149 12.5 Toepassing: een bitmap-editor 149 public void doeoperatie(string aktie) 60 { if (aktie.equals("quit")) { System.exit(0); else if (aktie.equals("start") && animatie==null) { animatie = new Thread(this); 65 animatie.start(); else if (aktie.equals("stop")) { animatie=null; 70 else uitvoer.doeoperatie(aktie); public void run() { while (animatie!=null) 75 { uitvoer.doeoperatie("step"); try {Thread.sleep(animatieVertraging); catch (InterruptedException e) { 80 public static void main(string [] ps) { int w=30, h=20; if (ps.length>0) w = h = Integer.parseInt(ps[0]); 85 if (ps.length>1) h = Integer.parseInt(ps[1]); BitEdit b; b = new BitEdit(w, h); 90 b.setvisible(true); public void windowclosing (WindowEvent e) {System.exit(0); public void windowclosed (WindowEvent e) { 95 public void windowopened (WindowEvent e) { public void windowiconified (WindowEvent e) { public void windowdeiconified(windowevent e) { public void windowactivated (WindowEvent e) { public void windowdeactivated(windowevent e) { 100 Listing 30: BitEdit/BitEdit.java, deel 2 van 2

150 150 Ontwerp van programma s Figuur 21: Vier stappen in het bold maken van een plaatje volgens de regels van het game of life. Drie hulpmethoden die van pas komen om bold en outline te implementeren: een methode and die alleen die punten gekleurd laat die in beide plaatjes gekleurd zijn; een methode or die de punten gekleurd maakt die in een van beide plaatjes (of in allebei) zwart zijn; en een methode xor die de punten kleurt die in een van beide plaatjes, maar niet in allebei gekleurd zijn. Twee methoden die het plaatje van vorm veranderen: rotate draait het plaatje een kwartslag, en scale maakt het dubbel zo groot. Anders dan de overige methoden kunnen deze methoden niet de bitmap die onder handen is aanpassen: daarin is immers niet genoeg ruimte om alle nieuwe beeldpunten op te slaan. In plaats daarvan zullen deze methode dus een nieuwe bitmap maken, en die als resultaat opleveren. De bitmap die onder handen is wordt niet veranderd. Beeldbewerkingsoperaties In de klasse BitMap komt een methode bold die een plaatje massiever maakt. Een makkelijke manier om dat te doen is de volgende. We maken eerste een kopie van het plaatje. De kopie schuiven we één beeldpunt naar links. Daarna kleuren we de beeldpunten die in het oorspronkelijke plaatje of in het verschoven plaatje zwart zijn. Het plaatje krijgt er daardoor een extra rand bij. Vervolgens herhalen we het procedé in verticale richting. Om de outline van een plaatje te bepalen gaan we als volgt te werk. We maken weer een kopie, en verschuiven die één beeldpunt naar links en naar beneden. Vervolgens laten we alleen die

151 12.6 Details van de bitmap-editor 151 Figuur 22: Tien stappen tijdens het Game of Life beeldpunten zwart, die in het oorspronkelijke plaatje zwart zijn, maar in het verschoven plaatje wit; die liggen immers aan de rand. Ook de beeldpunten die wit zijn, maar in het verschoven plaatje zwart liggen aan de rand, en moeten dus zwart gemaakt worden. Het Game of Life Nee, dit is geen actiespelletje met high score en verborgen extra levels. Het Game of Life is een simulatieproces, in de jaren zestig van de vorige eeuw bedacht door John Conway. Het bijzondere ervan is dat door hele simpele regels toch hele complexe patronen kunnen ontstaan. Het wordt daarom soms gebruikt als metafoor voor biologische processen: op grond van eenvoudige biochemische processen kunnen ingewikkelde organismen, en zelfs intelligentie en bewustzijn ontstaan. De wereld in het Game of Life bestaat uit een twee-dimensionaal raster, waarin elke cel bewoond kan worden door een beestje. Je kunt deze wereld dus weergeven met een bitmap: een zwart vakje duidt op de aanwezigheid van een beestje, een wit vakje is onbewoond. Het bewoningspatroon in de volgende generatie wordt bepaald door de acht buur-vakjes van elk vakje: Een vakje blijft bewoond als twee of drie buurvakjes bewoond zijn (bij minder buren sterft het beestje van eenzaamheid, en bij meer buren door verstikking). In een leeg vakje wordt een nieuw beestje geboren als er precies drie buurvakjes bewoond zijn. De vakjes aan de randen hebben minder buurvakjes dan de rest. Om deze vakjes ook eerlijk te behandelen, doen we alsof de linker-en de rechterrand aan elkaar grenzen, en de onder- en bovenrand ook. In figuur 22 is een aantal opeenvolgende generaties uit het Game of Life getekend. Opvallend is dat bepaalde clusters van beestjes stabiel zijn, of zich zelfs over de wereld kunnen verplaatsen! De individuele beestjes sterven en worden geboren, maar een soort hogere organismen blijven leven en kunnen bewegen Details van de bitmap-editor Implementatie van BitMap met een array Alle genoemde beeldbewerkingsmethoden kunnen de individuele beeldpunten van het plaatje aanspreken door de methoden iszwart en maakzwart aan te roepen. Maar om die twee methoden te schrijven, zullen we een beslissing moeten nemen over de variabelen waarmee een bitmap-object wordt gemodelleerd. De meest voor de hand liggende implementatie is een array van boolean waarden: voor elk beeldpunt eentje. Twee extra integers bewaren de breedte en hoogte van het plaatje. De array wordt geïnitialiseerd in de constructor, en methoden iszwart en maakzwart kiezen het juiste element er uit:

152 152 Ontwerp van programma s class BitMap { int breed, hoog; boolean [] punten; BitMap(Int b, int h) { breed=b; hoog=h; punten = new boolean[breed*hoog]; void maakzwart(int x, int y, boolean b) { punten[y*breed+x] = b; boolean iszwart(int x, int y) { return punten[y*breed+x]; Als een bitmap bijvoorbeeld 10 beeldpunten breed is, worden array-elementen 0 t/m 9 gebruikt voor de eerste rij, 10 t/m 19 voor de tweede rij, enzovoort. Daarom wordt in de methoden iszwart en maakzwart het nummer van de rij, y, vermenigvuldigd met de breedte van de bitmap. Implementatie van BitMap met een tweedimensionale array Bij het modelleren van tweedimensionale structuren, zoals plaatjes, is het eigenlijk gemakkelijker om een tweedimensionale array te gebruiken. Berekeningen zoals y*breed+x zijn dan niet meer nodig: je kunt de twee coordinaten in een tweedimensionale array apart specificeren. Tweedimensionale arrays worden op dezelfde manier gebruikt als eendimensionale arrays, alleen staan er nu (bij declaratie, initialisatie, en gebruik) twee paar vierkante haken achter de naam in plaats van één. De implementatie van Bitmap komt er dan als volgt uit te zien: class BitMap { int breed, hoog; boolean [][] punten; BitMap(int b, int h) { breed=b; hoog=h; punten = new boolean[breed][hoog]; void maakzwart(int x, int y, boolean b) { punten[x][y] = b; boolean iszwart(int x, int y) { return punten[x][y]; Implementatie van BitMap als subklasse van BufferedImage Maar waarom zouden we al die moeite doen? Er zijn bij Java zo veel standaardklassen meegeleverd, dat er voor een heleboel situaties al een kant-en-klare klasse beschikbaar is, compleet met meer methoden dan je ooit zou willen gebruiken. De creativiteit van het programmeren gaat daarmee wel een beetje verloren, want in plaats van zelf zo n klasse in elkaar te knutselen zit je alleen maar in dikke manuals te bladeren om op te zoeken welke methoden er allemaal beschikbaar zijn. Maar het is natuurlijk wel gemakkelijk... Ook in dit geval is er een klasse die vrijwel dat modelleert wat we nodig hebben: de klasse BufferedImage modelleert een complete bitmap, zij het eentje met kleuren-pixels: eigenlijk dus een pixmap. Methoden getrgb en setrgb zijn aanwezig om de kleur van een beplaad beeldpunt op te vragen, respectievelijk te verandern. Gebruikmakend daarvan kunnen we onze eigen iszwart en maakzwart schrijven, die dan alleen nog maar de vertaling van kleur naar zwartwit hoeven te doen. Er zijn geen eigen variabelen meer nodig om de bitmap op te slaan: die worden geërfd van BufferedImage. Wel maken we twee constanten om gemakkelijk de kleuren zwart en wit te kunnen aanduiden. class BitMap extends BufferedImage { static final int WIT = 0xFFFFFFFF; static final int ZWART = 0xFF000000; BitMap(int b, int h) { super(b,h, BufferedImage.TYPE_INT_ARGB );

153 12.6 Details van de bitmap-editor 153 this.clear(); void maakzwart(int x, int y, boolean b) { if (b) this.setrgb(x, y, ZWART); else this.setrgb(x, y, WIT); boolean iszwart(int x, int y) { return this.getrgb(x,y)==zwart; Deze implementatie van BitMap wordt in het uiteindelijke programma gebruikt. Codering van kleuren in integers Het gebruik van de klasse BufferedImage roept wel de vraag op hoe de kleur van een beeldpunt eigenlijk met één int gecodeerd kan worden (het resultaat van getrgb en de parameter van setrgb). Voor de beschrijving van een kleur zijn immers drie getallen nodig: de hoeveelheid rood, groen en blauw. De mogelijke waarden voor rood, groen en blauw zijn echter beperkt tot het bereik van 0 tot en met 255 dat is voor elke kleurcomponent precies één byte. In een int zijn vier bytes beschikbaar, dus dat moet passen; er is zelfs nog een byte over die gebruikt kan worden om de ondoorschijnendheid (die meestal alpha wordt genoemd) van de kleur te representeren. Wil je de losse kleurcomponenten gebruiken, dan zou je met de operatoren / en % met enige moeite de int is losse bytes splitsen. kleur = plaatje.getrgb(); blauw = kleur % 256; groen = (kleur / 256 ) % 256; rood = (kleur /(256*256) ) % 256; alpha = (kleur /(256*256*256)) % 256; Omgekeerd kun je met de operator * de losse componenten inpakken in een int: kleur = alpha*256*256*256 + rood*256*256 + groen*256 + blauw; In plaats van de delingen kunnen we ook gebruik maken van de operator >>. Om de uitkomst van a>>b te berekenen worden de bits die het getal a voorstellen b posities naar rechts geschoven. De uitkomst van 53>>2 bijvoorbeeld is 13. De binaire codering van 53 is namelijk Als je dat twee posities naar rechts schuift, krijg je 1101 (de 0 en de 1 aan de rechterkant vallen buitenboord), en dat is de binaire codering van het getal 13. Om een groepje bits te isoleren kunnen we vervolgens de operator & gebruiken. Deze werkt op twee getallen. De bits van die twee getallen worden stuk voor stuk bewerkt met de logische en-operator: een bit van het resultaat is alleen 1, als op de overeenkomstige plaats in beide getallen een 1 staat. De uitkomst van 172&15 bijvoorbeeld is 12. De binaire codering van 172 is namelijk , die van 15 is Bitsgewijs gecombineerd geeft dat , en dat is 12. Let er op dat de operator & slechts uit een enkel &-teken bestaat, niet te verwarren met het dubbele &&-teken, dat gebruikt wordt om de en-operatie op twee boolean waarden uit te voeren. Gebruikmakend van deze nieuwe operatoren >> en & kan het opsplitsen van de samengestelde kleur nu als volgt gebeuren: kleur = plaatje.getrgb(); blauw = kleur & 0xFF; groen = (kleur >> 8) & 0xFF; rood = (kleur >> 16) & 0xFF; alpha = (kleur >> 24) & 0xFF; In plaats van het hexadecimale getal 0xFF hadden we ook het decimale getal 255 kunnen schrijven. De hexadecimale notatie benadrukt echter onze bedoeling, namelijk dat we de laatste acht bits van het getal willen pakken. Omgekeerd kunnen we de kleurcomponenten inpakken met behulp van de operatoren << (schuif naar links) en (combineer de bits van twee getallen met de logische or-operatie): kleur = (alpha<<24) (rood<<16) (groen<<8) blauw; plaatje.setrgb(kleur); Het uit- en inpakken van kleurcomponenten met behulp van de schuif-operatoren is te prefereren boven de versie met de delings-operatoren, om twee redenen:

154 154 Ontwerp van programma s het is voor de lezer van het programma duidelijker wat de bedoeling van de programmeur is het werkt ook als alpha groter is dan 127. De delings-versie gaat dan de mist in omdat er overflow optreedt. Geparametriseerde opbouw van de menu s In sectie 12.4 is besproken hoe je een menu kunt maken: Menu menu; MenuItem item; menu = new Menu("File"); item = new MenuItem("Open"); menu.add(item); item.addactionlistener(this); item = new MenuItem("Save"); menu.add(item); item.addactionlistener(this); item = new MenuItem("Quit"); menu.add(item); item.addactionlistener(this); Dat wordt wel erg langdradig als er, zoals in de bitmap-editor, erg veel menu-keuzes zijn. Weliswaar kun je tijdens het programmeren de copy-paste functie van de editor gebruiken om het tikwerk te beperken, maar toch is dat niet zo n goed idee: het maakt het programma lastiger te overzien en te onderhouden. Beter is het om de regelmaat te proberen te vangen in een while-opdracht. We krijgen dan: Menu menu; MenuItem item; menu = new Menu("File"); for (int i=iets ; i<iets ; i++) { item = new MenuItem(iets ); menu.add(item); item.addactionlistener(this); Als parameter van de MenuItem-constructor moet elke keer een andere string worden meegegeven. Om het nog even flexibel te houden welke strings dat zijn, plaatsen we het hele stuk code in een methode, die als parameter een array met de te gebruiken strings meekrijgt. Het eerste element daarvan geven we mee aan de constructor van Menu, de overige aan de respectievelijke MenuItems. Het opgebouwde Menu wordt als resultaat van de methode opgelevert: private Menu maakmenu(string [] keuzes) { Menu menu; MenuItem item; menu = new Menu(keuzes[0]); for (int i=1; i<keuzes.length; i++) { item = new MenuItem(keuzes[i]); menu.add(item); item.addactionlistener(this); return menu; Deze methode kunnen we nu meerdere keren aanroepen, een keer voor elk menu, waarbij we steeds een andere array met strings meegeven: final String [] items1 = {"File", "Open", "Save", "Quit" ; final String [] items2 = {"Move", "Left", "Down", "Up" ; MenuBar bar; bar = new MenuBar(); bar.add( maakmenu(items1) ); bar.add( maakmenu(items2) ); Maar nog mooier is het om ook deze herhaling weer in een for-opdracht te vangen. We maken daarom de twee arrays van strings op hun beurt onderdeel van een array: een array van arrays van strings, met andere woorden een tweedimensionale array van strings. Van die tweedimensionale array wordt er in de body van de for-opdracht steeds één rij uitgekozen. Door de tweedimensionale array één index te geven, blijft er een eendimensionale array over, en dat is precies wat de methode maakmenu verwacht. final String [][] items = { {"File", "Open", "Save", "Quit", {"Move", "Left", "Down", "Up" ; MenuBar bar; bar = new MenuBar(); for (int i=0; i<items.length; i++) bar.add( maakmenu(items[i]) );

155 12.6 Details van de bitmap-editor 155 Opgaven 12.1 Grafisch histogram Een groothandel in eieren kan per maand eieren verkopen. In sommige maanden is de vraag naar eieren echter groter dan in andere maanden. Om de productie te plannen maakt men gebruik van het volgende applet. Op het scherm zijn twee dingen zichtbaar: Op de linkerhelft staat een tekening van 300 bij 300 pixels. De tekening is een staaf-diagram: bij elke maand geeft een rechthoek aan hoeveel eieren er die maand verkocht zijn: een staaf van 0 pixels hoog betekent geen eieren verkocht, de totale hoogte van de tekening betekent de maximale omzet van eieren, een staaf van de onderrand tot de halve hoogte is eieren, enzovoorts. Op de rechterhelft staan onder elkaar 12 tekstvelden, waarin de gebruiker aantallen eieren kan intikken. Het programma moet op twee manieren reageren op de gebruiker: De gebruiker tikt een getal in in een van de tekstvelden, afgesloten met de Enter-toets. Het staafdiagram wordt dan opnieuw getekend, gebruikmakend van de nieuwe aantallen. De gebruiker klikt met de muis in de tekening op één van de 12 maanden in het diagram. De staaf moet dan zo hoog worden als er geklikt is; het bijbehorende getal in een van de tekstvelden moet automatisch worden aangepast. Aan het begin zijn alle getallen nul, en is de tekening dus nog blanco. Hints: Maak een array-variabele waarin de toestand (dat is, de aantallen eieren in elke maand) wordt bijgehouden. Gebruik ook een array om de 12 tekstvelden in te maken. De 12 tekstvelden kun je stapelen in een Panel met een Gridlayout. Maak methodes die de toestand in het staafdiagram, respectievelijk de tekstvelden zichtbaar maken. Maak methodes die de toestand veranderen als gevolg van handelingen van de gebruiker. Je kunt reageren op muiskliks op de achtergrond van de applet (dat is: de tekening van het staafdiagram) met gebruikmaking van een MouseListener. Je kunt de Applet zijn eigen MouseListener laten zijn. Zoek op welke methoden er nodig zijn in een MouseListener, en hoe je aan de parameter van die methoden kunt zien op welke positie er geklikt is Cirkelklikker Schrijf een applet met de volgende specificaties. Boven in beeld is een knop met opschrift clear aanwezig, daarnaast staat een schuifregelaar, en daarnaast nog twee knoppen met opschrift black en green. De hele rest van het window kan door de gebruiker overal worden aangeklikt.

156 156 Ontwerp van programma s Iedere keer als de gebruiker een punt van het window aanklikt, verschijnt gecentreerd op die plaats een cirkel. Maximaal mogen 100 punten worden aangeklikt. Als de gebruiker toch meer punten probeert aan te klikken, gebeurt er niets. De gebruiker kan de straal van de cirkels bepalen met de schuifregelaar: als die helemaal naar links wordt geschoven wordt de straal van alle cirkels 2, helemaal naar rechts 20. Aan het begin is de straal (dus niet de diameter!) van de cirkels gelijk aan 8. Als de gebruiker op de knop clear drukt, verdwijnen alle cirkels, en kan de gebruiker weer beginnen met het aanklikken van maximaal 100 nieuwe punten. Als de gebruiker op de knop green drukt, worden alle cirkels groen; met een druk op de knop black worden ze weer zwart, zoals in het begin. Om het schrijfwerk te verminderen, mag je de import-regels bovenaan het programma weglaten, en ook de muismethoden die een lege body zouden hebben Halters Schrijf een applet met de volgende specificaties. Boven in beeld (200 bij 200 beeldpunten) is een drietal knoppen zichtbaar, met als opschrift respectievelijk clear, grid en move. De hele rest van het window kan door de gebruiker overal worden aangeklikt. Iedere keer als de gebruiker een punt van het window aanklikt, verschijnt gecentreerd op die plaats een stip met een doorsnede van 10 beeldpunten. Maximaal mogen honderd punten worden aangeklikt. Als de gebruiker toch meer punten probeert aan te klikken, gebeurt er niets. Elk tweede punt wordt met het vorige punt verbonden door een lijn (dus de tweede met de eerste, de vierde met de derde, de zesde met de vijfde enz.). Als de gebruiker op de knop grid drukt, wordt een raster van lijnen met tussenafstand van 20 beeldpunten zichtbaar (zodat de gebruiker de punten beter kan mikken). Als de gebruiker nogmaals op de knop grid drukt, verdwijnt het raster weer, bij een derde druk op de knop verschijnt het weer, enzovoorts. Als de gebruiker op de knop clear drukt, verdwijnen alle punten en lijnen, en kan de gebruiker weer beginnen met het aanklikken van nieuwe punten. Als de gebruiker op de knop move drukt, begint elk tweetal door een lijn verbonden punten naar elkaar toe te kruipen: elke seconde wordt de afstand ertussen 10% kleiner (theoretisch gesproken zullen ze elkaar dus nooit raken, maar in de praktijk zullen ze op een gegeven moment samenvallen). Als de gebruiker nogmaals op move drukt, stopt de beweging; bij een derde druk op de knop gaan de punten weer bewegen, enzovoorts.

157 12.6 Details van de bitmap-editor 157

158 158 Hoofdstuk 13 Objectgeoriënteerd ontwerp 13.1 Abstracte klassen en interfaces Het Once and only once principe Een goede programmeur is altijd op zoek naar een mogelijkheid om het programma compact te houden. In plaats van een stuk code met de tekstverwerker te knippen, elders nog eens in het programma toe te voegen, en er dan kleine wijzigingen aan te brengen, zal deze liever proberen om er een toepasselijk geparametriseerde methode van te maken. Zo wordt het once and only once principe hoog gehouden: programmeer iets één keer, en niet meer dan dat. Objectgeoriënteerde talen zijn bij hier uitstek geschikt voor. Niet alleen kun je groepjes opdrachten elders nog eens gebruiken door ze in een methode te zetten, die je daarna vele malen kunt aanroepen; je kunt ook groepjes variabele-declaraties opnieuw gebruiken door ze in een klasse te zetten, waarvan je daarna vele objecten kunt maken. Dankzij het mechanisme van subklassen kun je ook nog uitbreidingen en/of wijzigingen plegen aan eerder gemaakte klassen. Toch ligt zelfs dan het gevaar van copy/paste programmeren nog op de loer. Waarom dat zo erg is, en welke desastreuse gevolgen het heeft voor de onderhoudbaarheid van het programma zouden we hier uitgebreid kunnen bespreken. Maar dat doen we niet, want dat is elders al gedaan. Geheel in stijl van once and only once zullen we er hier naar verwijzen. Kijk op voor een overtuigende beschrijving, en klik ook eens door naar aanverwante programmeerprincipes. Gezien? Dan kunnen we nu op zoek naar nog subtielere manieren om once and only once ideeën in een programma op te schrijven. Klassen en interfaces We hebben het begrip klasse op twee manieren ingevoerd: een klasse is een groepje methoden dat bij elkaar hoort een klasse is het type van een object Later bleek dat het in feite om hetzelfde idee gaat. Methoden hebben immers een object onder handen; dat moet echter wel een object zijn van het juiste type: je kan wel de length uitrekenen van een String-object, maar niet van bijvoorbeeld een Color-object. Anderszijds kun je een Color-object onder handen nemen met de methode darken, en dat kan weer niet met een Stringobject. De methoden die je bij een bepaald object kunt gebruiken zijn precies de methoden die in de klasse zitten die het type is van dat object. Bij het schrijven van een klasse kunnen drie dingen van belang zijn: wat is een object van dit type? (dit leg je vast met de declaraties van variabelen in de klasse: elk object met deze klasse als type heeft beschikking over die variabelen); wat kun je doen met een object van dit type? (dit leg je vast met de methode-headers in de klasse: daarin staat hoe je het object onder handen neemt, en welke parameters daarbij nodig zijn); hoe doen de methoden hun werk? (dit leg je vast in de body van de methoden). Niet altijd zijn ze alle drie even interessant. Van een Color-object is het nog wel handig om te weten dat het is opgebouwd uit drie getallen (de hoeveelheid rood, groen en blauw); wat er allemaal nodig is voor de interne administratie van een Graphics-object hoef je eigenlijk niet te weten. Wel is het van belang om te weten dat er in de klasse Graphics een methode drawline is, maar hoe dat precies gebeurt is weer monder van belang. Tenzij je toevallig de programmeur van zo n klasse bent, dan is dat juist weer wel belangrijk.

159 13.1 Abstracte klassen en interfaces 159 Vaak hebben methoden een object als parameter. Zij zullen in hun body over het algemeen gebruik maken van dat object (anders was de parameter helemaal niet nodig... ). Dat gebruik kan zijn dat het object weer als parameter wordt meegegeven aan een andere methode, of dat het object onder handen wordt genomen door een van de toepasbare methoden. Het is bij dat laatste alleen maar van belang wat je met het parameter-object kunt doen, niet hoe dat precies gebeurt, of hoe het object intern is opgebouwd. Dit is precies de reden dat er in Java naast het begip klasse ook het begrip interface gehanteerd wordt. Een klasse is een groepje declaraties en methoden, en interface is alleen maar een groepje methode-headers. Een interface is daarmee een verlanglijstje voor wat er met een object moet kunnen. Voorbeeld: interface ActionListener Een voorbeeld van een interface in de Java-bibliotheek is ActionListener. Die is (ongeveer) als volgt gedefinieerd: interface ActionListener { public void actionperformed(actionevent e); Omdat een interface alleen maar uit methode-headers bestaat is zo n definitie meestal erg kort (tenzij er heel veel methodes op het verlanglijstje staan). Er zijn methodes die als parameter een object met het type ActionListener verwachten. Een voorbeeld daarvan is de methode addactionlistener in de klasse Button, die in zo n beetje elk interactief programma wordt gebruikt. De bedoeling is dat de button, als hij wordt ingedrukt, een seintje kan geven aan wie het maar horen wil. Dat gebeurt door het aanroepen van actionperformed, en elk object dat die methode kent is welkom. Het aardige van dit mechanisme is dat de methode addactionlistener in feite parameters kan accepteren met allerlei verschillende klassen als type; het enige wat van belang is, is dat die klasse de methode actionperformed bevat, zodat die te zijner tijd kan worden aangeroepen. Op deze manier is er maar één definitie van addactionlistener nodig, die simpelweg een ActionListener parameter verlangt, in plaats van een heleboel definities voor elke denkbare klasse die zo n actionperformed methode kent. Bovendien kan de programmeur van Button (waarin de methode addactionlistener staat) nog helemaal niet weten welke klassen er in de toekomst nog geschreven zullen gaan worden met een methode actionperformed. Dankzij het interface-mechanisme blijft het once and only once principe dus overeind: er is maar één methode addactionlistener, en niet meer dan dat. Implementatie van interfaces In de header van een klasse kun je aankondigen dat zo n klasse aan het verlanglijstje van een bepaalde interface tegemoet komt. We hebben dat in programma s al vaak gedaan voor de interface ActionListener, met een constructie als: class Luisteraar implements ActionListener { public void actionperformed(actionevent e) { doe iets nuttigs in reactie op een actie Een interactief programma kan dan een Luisteraar-object laten reageren op het indrukken van een button: class ProgrammaMetKnop extends Applet { public void init() { Luisteraar oor = new Luisteraar(); Button b = new Button("klik hier!"); this.add(b); b.addactionlistener(oor); In de voorbeeldprogramma s in eerdere hoofdstukken zijn we nogal zuinig geweest met het aantal klassen, en hebben we deze twee elementen gecombineerd in een enkele klasse. Daarmee waren het

160 160 Objectgeoriënteerd ontwerp programma s die naar hun eigen buttons luisterden, met de volgende opbouw: class ProgrammaMetKnopDatZelfLuistert extends Applet implements ActionListener { public void init() { Button b = new Button("klik hier!"); this.add(b); b.addactionlistener(this); public void actionperformed(actionevent e) { doe iets nuttigs in reactie op een actie Omdat het programma, door de aanwezigheid van een methode actionperformed, zelf naar z n buttons kan luisteren (en dat in de klasse-header ook belooft met implements ActionListener), is er nu ook geen apart object oormeer nodig: deze rol kan nu immers worden vervuld door this. Zo zuinig met klassen zijn programmeurs van wat grotere programma s meestal niet. Het is dan ook heel gebruikelijk dat er in een programma meerdere klassen zijn die allemaal op hun eigen manier ActionListener implementeren, en die gebruikt worden als luisteraar voor verschillende buttons. In het extreme geval heeft elke button zijn eigen luisteraar-object, al dan niet met een verschillend type. In het voorbeeldprogramma dat we in sectie 13.4 zullen bespreken zijn er zeven buttons, ieder met een eigen luisteraar-object, die bovendien twee verschillende implementaties van de interface ActionListener als type hebben. Once and only once implementatie van interfaces Het gebruik van interfaces maakt het mogelijk om een methode te schrijven met een parameter die van een heleboel verschillende typen kan zijn (mits die typen allemaal de interface implementeren). Dit stimuleert once and only once programmeren: de methode hoeft maar één keer te worden geschreven, in plaats van voor elk type parameter apart. Toch is er ook een aspect van interfaces dat uitnodigt tot copy/paste programmeren, het tegenovergestelde van once and only once programmeren. Je merkt dat vooral aan interfaces die niet één maar meerdere methode-headers bevatten, zoals MouseListener en MouseMotionListener. Als je een programma wilt schrijven dat reageert op het indrukken van de muis dan maak je een klasse die MouseListener implementeert. Je kunt dan een invulling geven aan de methode mousepressed, maar je bent tevens verplicht om de andere vier methoden van de interface te implementeren: mousereleased, mouseclicked, mouseentered en mouseexited, ook als je in die gebeurtenissen helemaal niet geïnteresseerd bent. In programma s waar alleen het indrukken van de muis van belang is, kom je dan ook vaak iets tegen als: class MuisReactie implements MouseListener { public void mousepressed (MouseEvent e) { doe iets nuttigs in reactie het indrukken van de muis public void mousereleased(mouseevent e) { public void mouseclicked (MouseEvent e) { public void mouseentered (MouseEvent e) { public void mouseexited (MouseEvent e) { Schrijf je later nog eens een programma dat alleen mousepressed nodig heeft, dan is het verleidelijk om die andere vier methodes met hun lege body te kopiëren en in het nieuwe programma in te plakken. Maar dat is eigenlijk copy/paste programmeren, en dat is niet netjes! Een ander voorbeeld is de interface MouseMotionListener. Deze eist implementatie van twee methodes: mousemoved en mousedragged. De eerste reageert op beweging van de muis zonder ingedrukte muisknop, de tweede op beweging van de muis met ingedrukte muisknop. Is een programma geïnteresseerd in het bewegen van de muis ongeacht het indrukken van de knop, dan kun je iets schrijven als: class BewegingReactie implements MouseMotionListener { public void mousemoved(mouseevent e)

161 13.1 Abstracte klassen en interfaces 161 { doe iets nuttigs in reactie het bewegen van de muis public void mousedragged(mouseevent e) { mousemoved(e); De methode mousedragged roept de methode mousemoved aan, zodat in reactie op het bewegenmet-ingedrukte-knop hetzelfde gebeurt als in reactie op het bewegen-zonder-ingedrukte-knop. Op deze manier wordt duplicatie van het stuk programma doe iets nuttigs voorkomen. Toch ligt copy/paste programmeren op de loer: schrijf je een ander programma dat moet reageren op jet bewegen van de muis ongeacht het indrukken van de muisknop, dan is het verleidelijk om een kopie van de methode mousedragged in te plakken. Abstracte klassen Om aan de verleiding van copy/paste weerstand te bieden, en aldus toch het once and only once principe te kunnen volhouden, is het in Java mogelijk om een interface gedeeltelijk te implementeren. Je kunt bijvoorbeeld een klasse maken die wel de methode mousedragged implementeert, maar nog niet de methode mousemoved. Zo n klasse is een gedeeltelijke implementatie van de interface mousemotionlistener. Om geen ruzie te krijgen met de compiler, moet je in de header van zo n klasse aangeven dat het een zogeheten abstracte klasse betreft: abstract class MuisBewegingOngeachtKnop implements MouseMotionListener { public void mousedragged(mouseevent e) { mousemoved(e); Bij het programmeren heb je misschien onbedoeld al kennisgemaakt met abstracte klassen. Als je bij de implementatie van een interface namelijk een van de methoden vergeet geeft de compiler niet de foutmelding je bent een methode vergeten!, maar als je een methode wilt weglaten, moet je de klasse abstract maken. Het heeft wel zijn prijs om een klasse abstract te maken: van een abstracte klasse kun je niet met new een object maken. Ook dit ben je misschien al onbedoeld tegengekomen: als je een van de benodigde methoden van een interface vergeten bent te implementeren, en je volgt de suggestie van de compiler om de klasse dan maar abstract te maken klakkeloos op, dan krijg je bij de volgende compilatie meteen weer een nieuwe foutmelding: cannot instantiate abstract classes, oftewel: van een abstracte klasse kun je geen nieuwe objecten maken. Wat heeft zo n abstracte klasse dan voor zin, als je er niet eens objecten van kunt maken? Als losstaande klasse is een abstracte klasse inderdaad zinloos. Het is dan ook de bedoeling dat een abstracte klasse verder wordt uitgewerkt in subklassen, die de ontbrekende methoden alsnog aanvullen. Die subklassen zijn niet abstract (je zou kunnen zeggen: ze zijn concreet, maar dat hoeft je in de header niet op te schrijven), en daar kun je dus wel objecten van maken. In een programma kun je bijvoorbeeld twee subklassen maken, die op verschillende manieren kunnen reageren op muisbeweging: class MuisBeweging1 extends MuisBewegingOngeachtKnop { public void mousemoved(mouseevent e) { doe iets in reactie het bewegen van de muis class MuisBeweging2 extends MuisBewegingOngeachtKnop { public void mousemoved(mouseevent e) { doe iets anders in reactie het bewegen van de muis Beide subklassen profiteren van het feit dat in de abstracte superklasse wordt vastgelegd dat mousedragged hetzelfde doet als mousemoved. En dat zonder copy/paste van de methode mousedragged!

162 162 Objectgeoriënteerd ontwerp Adapters Je kunt abstracte klassen gebruiken, zoals in het voorbeeld hierboven, om een soort default-gedrag vast te leggen, die dan in subklassen nader wordt verfijnd. Een wel heel simpele vorm van defaultgedrag is doe niets. Veel listener-interfaces, zoals MouseListener en WindowListener specificeren hele rijen methodes, waarvan er in de praktijk veel niets hoeven te doen. Je zou dus voor elke interface een bijbehorende abstracte klasse kunnen maken die elke methode een lege body geeft. Omdat dit een handig idee is, dat door elke Java-programmeur kan worden gebruikt, zijn die abstracte klassen in de Java-bibliotheek alvast neergezet. Zo is er bijvoorbeeld voor de interface WindowListener interface WindowListener { void windowclosing (WindowEvent e); void windowclosed (WindowEvent e); void windowopened (WindowEvent e); void windowiconified (WindowEvent e); void windowdeiconified(windowevent e); void windowactivated (WindowEvent e); void windowdeactivated(windowevent e); een bijbehorende abstracte klasse, die alle gespecificeerde methoden van een lege body voorziet: abstract class WindowAdapter implements WindowListener { public void windowclosing (WindowEvent e) { public void windowclosed (WindowEvent e) { public void windowopened (WindowEvent e) { public void windowiconified (WindowEvent e) { public void windowdeiconified(windowevent e) { public void windowactivated (WindowEvent e) { public void windowdeactivated(windowevent e) { Hoewel alle methoden zijn geïmplementeerd, is de klasse toch abstract gemaakt, omdat het niet de bedoeling is om nieuwe objecten van het type WindowAdapter te maken. Maar wel kun je in je eigen programma een subklasse maken van WindowAdapter, waarin je een of meer methoden van een zinvolle inhoud voorziet. Bijvoorbeeld: class WindowSluiter extends WindowAdapter { public void windowclosing(windowevent e) { System.exit(); Van deze klasse kun je wel nieuwe objecten maken, die je kunt gebruiken op de plaats waar je een WindowListener nodig hebt: hoofdwindow.addwindowlistener( new WindowSluiter() ); Naast WindowAdapter als triviale implementatie van WindowListener voorziet de Java-biliotheek ook in MouseAdapter als triviale implementatie van MouseListener, en MouseMotionAdapter als triviale implementatie van MouseMotionListener. Hoe de triviale implementatie van KeyListener heet is dan niet moeilijk meer te raden Collections De beperkingen van arrays Als je grote hoeveelheden gegevens van hetzelfde type in een programma wilt verwerken, kun je die opslaan in een array. Met een tellertje in een for-opdracht kun je de elementen van een array langslopen, maar je kunt ook naar believen de elementen van een array kris-kras door elkaar benaderen, om hun waarde te bekijken of eventueel te veranderen. Dit is een zeer krachtig hulpmiddel, en de array is dan ook al zo oud als de geschiedenis van programmeertalen. Toch is vanuit modern object-georiënteerd perspectief een array eigenlijk een onding. Dit vanwege het feit dat de mogelijkheden van een array precies vastliggen, en ingebouwd zijn in de taal: je kunt m creëren: int [] a = new int[100];

163 13.2 Collections 163 je kunt een waarde op een bepaalde plaats veranderen: a[n] = x; je kunt de waarde op een bepaalde plaats bekijken: x = a[n]; je kunt de bij creatie vastgelegde lengte later nog eens opvragen: x = a.length; Soms is dit precies wat je nodig hebt, en dan is er geen probleem. Maar soms zou je nog andere dingen met een array willen kunnen doen, bijvoorbeeld een extra element tussenvoegen, of de array langer maken dan bij z n oorspronkelijke creatie is vastgelegd. Je zou voor dat soort dingen eigenlijk methoden willen kunnen toevoegen in een subklasse. Maar hoewel een array een object is, geschiedt de toegang niet via methodes, maar via de speciale vierkante-haakjesnotaties. Ook heeft een array-object geen bijbehorende klasse, waarvan je een subklasse zou kunnen maken. In andere gevallen biedt een array juist meer dan je nodig hebt. Soms wil je bijvoorbeeld een gegevensverzameling opbouwen (strings die een gebruiker intikt ofzo), en die later nog eens in dezelfde volgorde langlopen (om ze op het scherm te tekenen ofzo). Je kunt daarvoor natuurlijk een array gebruiken, maar van de faciliteit dat je zo n array kriskras kunt benaderen maak je helemaal geen gebruik: je wilt hem immers alleen maar op volgorde langslopen. Voor de faciliteit die je niet eens nodig hebt betaal je wel een dure prijs, namelijk dat je de lengte van de array vooraf moet vastleggen. Sommige beperkingen van een array zijn wel te omzeilen. De vaste lengte bijvoorbeeld: als je die dreigt te overschreiden, kun je snel een nieuwe array creëren met de dubbele lengte van de vorige, en de oude elementen daarnaartoe kopiëren. Of de mogelijkheid om elementen tussen te voegen: als je eerste alle overige elementen een plaatsje opschuift, kun je dat in een array wel voor elkaar krijgen. Het wordt al snel een heel gedoe met indexen en hulptellertjes. Maar als je dat netjes opbergt in methoden, kun je een klasse maken waar achter de schermen weliswaar een array wordt gebruikt, maar waar je bij het gebruik van die klasse geen last van hebt, omdat dat door de diverse methoden wordt afgeschermd. Of misschien hoeft er achter de schermen niet een array te worden gebruikt, omdat de gewenste functionaliteit ook op een andere manier bereikt kan worden. Interfaces voor Collection, List en Set Natuurlijk hoef je dit soort klassen niet allemaal zelf te schrijven. Elke programmeur heeft op zijn tijd wel eens behoefte aan een array waarvan je de lengte niet vooraf hoeft op te geven, of waar je elementen op een bepaalde plaats kunt tussenvoegen. In de Java-bibliotheek zijn er daarom een aantal klassen aanwezig met dit soort mogelijkheden. Om het helemaal mooi te maken staan in Java de gewenste mogelijkheden van een gegevensverzameling centraal, zonder dat je daarbij al aan een bepaalde implementatie hoeft te denken. Er zijn dus een aantal interfaces gedefinieerd: specificaties van groepjes methoden die je nodig zou kunnen hebben. Natuurlijk zijn er in de bibliotheek ook al een aantal implementaties voorhanden, maar daarover later meer: eerst bekijken we de functionaliteiten die door de diverse interfaces worden gespecificeerd. In package java.util zijn in totaal negen interfaces gedefinieerd. Sommige interfaces zijn een uitbreiding van andere: ze voegen nog een aantal methoden toe aan de specificatie. In figuur 23 zijn de verschillende interfaces afgebeeld. Hieronder bespreken we de belangrijkste methoden die in deze interfaces worden gespecificeerd. (Dit zijn ze niet allemaal, want dat zou een beetje onoverzichtelijk worden; raadpleeg de online documentatie voor een volledige opsomming). De interface Collection beschrijft de meest basale eigenschappen van een gegevensverzameling: interface Collection { boolean add (Object o); boolean remove (Object o); boolean contains (Object o); int size (); boolean isempty (); void clear (); Iterator iterator (); Met de methode add kun je een object aan de collectie toevoegen, met remove kun je een bepaald element weghalen, en met contains kun je kijken of een bepaald element er al in zit. Het resultaat van add en remove geeft aan of de collectie is veranderd als gevolg van de aanroep. Als je dus een element probeert te verwijderen dat er niet in zit, zal remove de waarde false opleveren.

164 164 Objectgeoriënteerd ontwerp Figuur 23: De klassen en interfaces voor collections Het vergelijken van objecten door contains en remove gebeurt door de methode equals van de betreffende objecten aan te roepen (tenzij het te vergelijken object null is: dan geldt: null is gelijk aan null, en aan niets anders). Methode size geeft het aantal elementen dat in de collectie zit, isempty geeft aan of dat aantal groter is dan nul. Met clear kun je de collectie leeg maken. De methode iterator tenslotte levert een speciaal object als resultaat op, waarmee je de collectie helemaal langs kunt lopen. Wat voor soort object dat is kan per collectie verschillen, maar het implementeert in ieder geval aan de interface Iterator. Die is als volgt: interface Iterator { boolean hasnext (); Object next (); Met methode hasnext kun je vragen of er nog meer elementen te bekijken zijn. Zo ja, dan kun je veilig next aanroepen om het volgende element te verkrijgen. Zo n iterator is bedoeld om in een while-opdracht te gebruiken. Je kunt de elementen van een collectie coll gemakkelijk als volgt langslopen: Iterator iter; iter = coll.iterator(); while (iter.hasnext()) doeietsmet( iter.next() ); of nog compacter met een for-opdracht: for (Iterator iter=coll.iterator(); iter.hasnext(); ) doeietsmet( iter.next() ); In een Collection staan de gegevens niet in een bepaalde volgorde. Het is dus niet zeker dat de Iterator die door iterator wordt teruggegeven de elementen in dezelfde volgorde oplepelt als waarin ze zijn toegevoegd. Is voor een bepaalde toepassing de volgorde wel van belang, dan is de interface List wat je nodig hebt. In een List zijn de elementen genummerd: van 0 tot en met size()-1, of anders gezegd: van 0 tot maar zonder de size() van de lijst; precies zoals dat ook bij arrays het geval is. De

165 13.2 Collections 165 interface List is een uitbreiding van Collection, dus de eerder genoemde methoden add, remove, contains, size, isempty en iterator zijn ook hier aanwezig. De methode add van een Listzal een element nu aan het eind toevoegen, en de iterator somt de elementen in volgorde op. Daarnaast zijn er nog een aantal nieuwe methoden: interface List extends Collection { Object get (int index); Object set (int index, Object o); boolean add (int index, Object o); Object remove (int index); De methode get levert het element met een bepaald nummer; methode set vervangt het element met een bepaald nummer door het meegegeven object. Deze twee operaties zijn bekend van de array. De methode add zet een nieuw element op een bepaalde plaats, maar anders dan bij set worden de elementen die op die plaats en hoger al stonden eerst opgeschoven. Methode remove verwijdert het element op een bepaalde plaats, en plempt daarna de rest netjes aan. Een andere sub-interface van Collection is Set. Net als bij List gedraagt methode add zich hierin bijzonder. In een Set garandeert add dat elk element hoogstens één keer in de collectie wordt opgenomen. Gelijkheid wordt hierbij getest met behulp van equals. Zit het element er al in, dan wordt het niet nogmaals toegevoegd, en levert add het resultaat false op. Behalve dit veranderde gedrag van add zitten er in Set geen nieuwe methoden ten opzichte van Collection. Wel nieuwe methodes zijn er te vinden in de sub-sub-interface SortedSet. Bij implementaties van deze interface zorgt methode add er voor dat de toegevoegde elementen op opklimmende volgorde komen te staan. De iterator zal ze ook in die volgorde opsommen. Nieuw zijn twee methoden die de kleinste en de grootste waarde opleveren: interface SortedSet extends Set { Object first (); Object last (); Om de volgorde te kunnen bepalen moet de elementen die in een SortedSet worden opgeslagen de interface Comparable implementeren. Voor een aantal standaardklassen, zoals Integer en Date, is dat al het geval, en voor je eigen klassen kun je daar ook voor zorgen. Als alternatief kun je aan de constructormethode van de SortedSet een Comparator-object meegeven, die de vergelijkingen uitvoert. Raadpleeg de online documentatie voor de details hiervan. Interfaces voor Map Stel je eens voor dat in een array de elementen niet aangeduid zouden worden met een nummer, maar met een String. Dat zou handig zijn: je zou dan bijvoorbeeld een vertaal-tabel kunnen maken tussen het Nederlands en het Engels, door als index de Nederlandse woorden te gebruiken, en als bijbehorende waarde de Engelse vertaling. Om een woord te vertalen hoef je alleen nog maar op de goede plaats de tabel te raadplegen. Zo n vertaaltabel is precies wat er wordt gespecificeerd door de interface Map. Het lijkt op een List, maar waar een List de elementen nummert met int-waarden, mag als plaatsbepaling in een Map een willekeurig Object gebruikt worden. In de praktijk zijn dat vaak String-waarden. De interface Map bevat (onder andere) de volgende methoden: interface Map { int size (); boolean isempty (); void clear (); Object put (Object key, Object value); Object remove (Object key); Object get (Object key); Set keyset (); Collection values (); Met put kun je nieuwe vertalingen aan de tabel toevoegen, met remove kun je ze weer verwijderen, en met get kun je de vertaling opzoeken. Van een Map kun je geen iterator krijgen, maar wel kun

166 166 Objectgeoriënteerd ontwerp je alle keys of alle waarden opvragen. Je krijgt dan respectievelijk een Set of een Collection, en daarvan kun je wel een iterator krijgen. Zo kun je, om bij het voorbeeld van de Nederlands-Engelse vertaaltabel te blijven, naar keuze alle Nederlandse of alle Engelse woorden langslopen. De sub-interface SortedMap zorgt er voor dat de vertaaltabel geordend wordt opvolgorde van de keys, zodat er twee nieuwe methodes beschikbaar zijn: interface SortedMap extends Map { Object firstkey (); Object lastkey (); Abstracte klassen voor collections Het is niet zo heel eenvoudig om een nieuwe implementatie van een interface zoals Collection oflist te maken, omdat deze interfaces zo veel methoden bevatten (het zijn er zelfs nog iets meer dan in de vorige paragraaf werden besproken). Om het voor toekomstige programmeurs van implementaties wat gemakkelijker te maken, is er voor elke interface een bijbehorende abstracte klasse waar alvast sommige (maar niet alle) methoden zijn geïmplementeerd. De gedeeltelijke implementatie van interface Collection is de abstracte klasse AbstractCollection. Daarin staat onder andere een invulling van de methode isempty: abstract class AbstractCollection implements Collection { boolean isempty() { return this.size()==0; De programmeur die een echte, concrete implementatie van Collection wil maken kan een subklasse van AbstractCollection definiëren. Het blijft nodig om de methode size te maken, maar de methode isempty erft-ie van de superklasse. Overigens is het niet verboden om de geërfde methode alsnog van een nieuwe body te voorzien. Dat kan bijvoorbeeld handig zijn als in een bepaalde implementatie de methode size veel tijd zou kosten, omdat de elementen daadwerkelijk geteld moeten worden. Het is in dat geval zonde van de tijd om alle elementen daadwerkelijk te tellen als je ten behoeve van isempty alleen maar wilt weten of dat aantal ongelijk aan nul is. Naast AbstractCollection zijn er de abstracte klassen AbstractList, AbstractSet en AbstractMap. In de online documentatie kun je opzoeken welke methoden er nog te implementeren zijn als je een concrete implementatie zou willen maken. Concrete implementaties van collections Voorlopig is het echter niet nodig om zelf implementaties van Collection, List of Map te maken. In de Java-bibliotheek zijn er namelijk al een aantal beschikbaar. Van elke hierboven genoemde abstracte klasse zijn er zelfs meerdere concrete subklassen beschikbaar. Deze verschillen in welke methoden ze snel kunnen uitvoeren, en welke meer tijd kosten. Afhankelijk van welke methode je voor een bepaald doel vaak nodig hebt kun je dus de keuze maken. De volgende concrete implementaties zijn beschikbaar: Als subklasse van AbstractList, en dus implementatie van List: de klasse ArrayList. Deze gebruikt achter de schermen een array, en kan dus de operaties get en set razendsnel uitvoeren. Het tussenvoegen van elementen, het opzoeken met contains en het weghalen met remove kan veel tijd kosten. de klasse LinkedList. Deze gebruikt achter de schermen een keten van verwijzingen. Het vinden van een element met een bepaald nummer is hier lastig, maar het tussenvoegen kan relatief makkelijk gedaan worden. Als subklasse van AbstractSet, en dus implementatie van Set: de klasse HashSet. De operaties gaan in het gemiddelde geval tamelijk snel, maar in ongunstige situaties kan het incidenteel lang duren. de klasse TreeSet. De operaties duren iets langer dan bij HashSet, maar de benodigde tijd kent gegarandeerd geen uitschieters. Deze klasse implementeert bovendien de interface SortedSet, en is dus de juiste keuze als de volgorde van elementen van belang is. Als subklasse van AbstractMap, en dus implementatie van Map: de klasse HashMap.

167 13.2 Collections import java.awt.*; import java.awt.event.*; import java.applet.*; import java.util.*; public class CollDemo extends Applet implements ActionListener { TextField invoer; Collection coll; 10 public void init() { invoer = new TextField(20); this.add(invoer); invoer.addactionlistener(this); coll = new TreeSet(); 15 public void actionperformed(actionevent e) { coll.add( invoer.gettext() ); invoer.settext(""); 20 this.repaint(); public void paint(graphics g) { Iterator iter; 25 String s; int y = 50; for (iter = coll.iterator(); iter.hasnext(); ) { s = (String) iter.next(); g.drawstring(s, 10, y); 30 y += 20; Listing 31: CollDemo/CollDemo.java de klasse TreeMap. Hierbij gelden dezelfde efficiëntie-afwegingen als bij de implementaties van Set. Voorbeeld van gebruik van collections In listing 31 staat een zeer kort voorbeeld van het gebruik in de praktijk van collections in een blz. 167 compleet programma. Het programma toont een tekstveld. Alle teksten die daar door de gebruiker worden ingetikt worden opgespaard in een collectie, en door paint op het scherm getekend. Het programma kent twee objectvariabelen: een tekstveld voor de interactie, en een Collection als model voor de opgespaarde strings. Die laatste staat echt als Collection gedeclareerd; we leggen ons hier dus nog niet vast op een concrete implementatie. Die keuze wordt pas in de constructor gemaakt, waar de Collection-variabele coll een waarde krijgt: coll = new TreeSet(); Deze toekenning is toegestaan, want inderdaad is TreeSet een implementatie van Collection, wat het type van de variabele coll is. In de methode paint wordt van de collectie (eigenlijk dus een treeset, maar dat hoeft paint niet eens te weten) de iterator opgevraagd. Van die iterator wordt herhaaldelijk next aangeroepen. De methode next geeft als resultaat een Object terug. Maar in dit bijzondere geval weten we als programmeur wel beter: we hebben aan de collectie uitsluitend String-waarden toegevoegd, dus het resultaat van next moet ook wel een String zijn. Daarom gebruiken we een cast om

168 168 Objectgeoriënteerd ontwerp te garanderen dat het door next teruggegeven Object inderdaad een String is. Anders zou de compiler weigeren om de expressie iter.next() aan een String-variabele toe te kennen. Die String kan vervolgens probleemloos op het scherm getekend worden. Bij het gebruik van iteratoren is vrijwel altijd zo n cast nodig, dus dit is een motief dat je in veel programma s tegenkomt. Bij het runnen van dit programma merkt de gebruiker al snel het gedrag van de TreeSet: de teksten worden permanent in op alfabetische volgorde weergegeven, en dubbele elementen worden niet getoond. Zou je bij de initialisatie van coll hebben geschreven coll = new ArrayList(); dan worden de teksten niet gesorteerd, en zijn dubbelen wel mogelijk. Uit oogpunt van het once and only once principe is het natuurlijk erg fraai dat je met één enkele ingreep het gedrag van het programma kunt aanpassen: er zijn geen wijzigingen in de methoden actionperformed en/of paint nodig. Vector: een historische vergissing De uitgebreide en goed doordachte bibliotheek voor collecties die in deze sectie werd beschreven is een verworvenheid van Java 1.2. Voor die tijd waren er een paar simpelere klassen in gebruik. In oudere Java-programma s kom je ze nog wel eens tegen, daarom worden ze hier kort vermeld. Het heeft echter weinig zin om deze verouderde klassen in je eigen programma s te gaan gebruiken. De klasse Vector werd gebruikt waar we tegenwoordig een ArrayList zouden gebruiken. De methoden volgden echter niet de naamgeving zoals benodigd voor implementatie van List: in plaats van set schreef men setelementat, in plaats van get schreef men elementat. Tegenwoordig is Vector alsnog tot implementatie geworden van List, door get en set alsnog hun voorganger te laten aanroepen. Maar het gebruik van Vector biedt geen voordelen boven ArrayList, en dat is bovendien een duidelijkere naam. De abstracte klasse Dictionary was de voorganger van wat tegenwoordig de interface Map is. De klasse Hashtable was een concrete invulling van Dictionary. Gebruik in nieuwe programma s liever HashMap. Om te itereren over een Vector was er een interface Enumeration. Tegenwoordig gebruiken we liever Iterator Uitbreidingen van AWT De beperkingen van AWT In vrijwel alle programma s tot nu toe hebben we het package java.awt gebruikt om een grafische gebruikersinterface te maken. Het opzetten van een interactief programma met AWT is relatief eenvoudig althans als je de werking van ActionListeners eenmaal doorhebt... Bijkomend voordeel is dat je profiteert van het uitgangspunt van Java: het programma is niet specifiek voor een bepaald operating systeem; je kunt een applet bekijken met (vrijwel) elke browser onder (vrijwel) elk operating systeem. Dat verklaart ook de A in de naam AWT : het is een abstracte window toolkit, die je kunt aanspreken zonder te weten welk concreet windowsysteem er aan ten grondslag ligt. Toch, zodra je probeert wat serieuzere programma s te schrijven die aan professionele eisen moeten voldoen, loop je aan tegen de beperkingen van AWT. De twee belangrijkste beperkingen zijn: met de klasse Graphics kun je wel lijntjes, cirkels, letters en zelfs bitmaps tekenen, maar sommige dingen zijn gewoonweg niet mogelijk. De beperking waar de meeste programmeurs het eerst tegenaan lopen is dat de dikte van de door drawline getekende lijnen niet kan worden ingesteld. in het package java.awt zitten weliswaar klassen voor buttons, tekstvelden, keuzelijsten en checkboxen, maar daarmee heb je het wel zo n beetje gehad. Een mooie combobox, zoals je in MS-Windowsprogramma s vaak ziet is niet mogelijk, laat staan zo n hippe treeview waarmee je hiërarchische structuren kunt visualiseren. De beperkingen van Graphics zijn het gevolg van ondoordacht ontwerp. De beperkingen zijn inmiddels verholpen in een nieuwe klasse Graphics2D, waarover zo dadelijk meer. De beperkingen van AWT zijn het gevolg van de princiële keuze die destijds is gemaakt voor een zogeheten zwaargewicht GUI-bibliotheek. Die keuze is op de snelle computers van deze eeuw minder noodzakelijk,

169 13.3 Uitbreidingen van AWT 169 en daarmee ligt de weg open naar een alternatieve GUI-bibliotheek. Zwaargewicht en lichtgewicht GUI s Elk modern operating system biedt ondersteuning voor het programmeren van grafische userinterfaces (GUI s). Apple was de eerste commerciële aanbieder van GUI s met de Macintosh look&feel, Microsoft volgde met Microsoft Windows, voor Unix heb je Motif, en Linux heeft KDE. Het mooie is dat als je eenmaal gewend bent aan zo n operating systeem, je precies weet wat je van de verschillende controls kun verwachten: wanneer menu s openklappen, hoe je eventueel met het toetsenbord een button kunt aanklikken, enzovoort. Er zijn verschillen tussen de GUI s van verschillende operating systemen: dat verklaart de aanpassingsproblemen als een Macintosh-gebruiker op een Windows-systeem moet werken of andersom. Zo lang iedereen merktrouw blijft en zoch niet met het andere kamp bemoeit gaat dat goed. Bij het ontwerp van Java, dat immers beoogt platform-onafhankelijk te zijn, gaf dat wel een probleem. De verschillende platforms bieden verschillende primitieven aan voor het samenstellen van GUI s. Daarom hebben de Java-ontwerpers hun window-toolkit abstract gemaakt, dat wil zeggen dat je het onderliggende window-systeem er niet in herkend. De toolkit vertaalt de abstracte ideeën uit het Java-programma naar concrete aanroepen in het onderliggende window-systeem. Dat heeft twee voordelen: de werking van de GUI is snel, omdat gebruik wordt gemaakt van de zwaar geoptimaliseerde primitieven van het operating system. het Java-programma ziet er voor de gebruikers van de diverse operating systemen altijd vertrouwd uit: de buttons en dergelijke hebben precies de vorm die ze gewend zijn. Maar er zijn ook nadelen: Java kan slechts de algemeen gangbare controls aanbieden: buttons zijn er altijd wel, maar de Macintosh kent geen combobox à la Microsoft, en Microsoft kent weer niet de schuifregelaar van Motif. De in AWT beschikbare controls zijn dus beperkt tot de grootste gemene deler van alle systemen. als Java-programmeur weet je nooit zeker hoe de GUI er uit komt te zien: die is immers op elk systeem anders. Dat kan nog voor lelijke verrassingen zorgen. Een GUI-toolkit die zo veel mogelijk beroep doet op het onderliggende operating system heet een zwaargewicht GUI-toolkit. AWT is een zwaargewicht GUI-toolkit. Wat is dan een lichtgewicht GUI? Hierbij wordt zo min mogelijk gebruik gemaakt van het onderliggende operating system. Die moet zijn toetsenbord- en muisakties doorgeven en graphics op het scherm kunnen tekenen, maar zich verder nergens mee bemoeien. De complete vormgeving van de controls, inclusief de driedimensionale suggestie dat een button wordt ingedrukt wordt helemaal door de Java-bibliotheek verzorgd. Tja, is dat nou handig? Een mogelijk bezwaar is de traagheid. Zo n simulatie van een button kan nooit zo snel zijn als de in het operating system ingebouwde functie. Dat was ten tijde van het begin van Java een belangrijke reden om AWT zwaargewicht te maken. Maar met de snelle computers van tegenwoordig is een lichtgewicht GUI-toolkit goed haalbaar. Een groot voordeel is dat zo n lichtgewicht GUI-toolkit allerlei zeer aantrekkelijke nieuwe controls kan aanbieden. Als het een nieuw ontworpen toolkit is, kan die ook profiteren van allerlei moderne object-georiënteerde inzichten. Een dilemma is hoe de lichtgewicht GUI er uit moet komen te zien: à la Microsoft? à la Motif? of geen van beide, maar juist iets heel nieuws? Java is sinds versie 1.2 voorzien van een lichtgewicht GUI-toolkit, in de vorm van het package javax.swing, kortweg bekend als als Swing. Die is snel genoeg, en inderaad voorzien van allerlei aantrekkelijke controls. Uit het laatste dilemma hebben de ontwerpers zich gered door door Swing te voorzien van een configureerbare look&feel : verwisselbare skins, zogezegd. Standaard zijn er drie look&feels: Motif, Windows, en een nog niet elders bestaande look&feel genaamd Metal. In principe is elke look&feel te gebruiken op elke systeem het is immers een lichtgewicht GUItoolkit, die de look&feel zelf simuleert zonder gebruik te maken van de faciliteiten het operating system. Da s leuk: nou kan je Metal-look maken op een Windows-computer, Motif-look op een Macintosh, of Windows-look op een Unix-systeem, of andere combinaties. Helaas, Microsoft was minder blij met een Windows-look op vreemde systemen. Het gevolg is dat de Microsoft-look nu alleen maar ingesteld kan worden op een Microsoft-computer. De default look&feel is Metal. De gebruiker kan iets anders uitkiezen met een ingewikkelde optie

170 170 Objectgeoriënteerd ontwerp voor de Java-interpreter. Dat kan door de programmeur weer worden overruled door in de methode main de look&feel van de doel-computer te kiezen met: UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() ); of juist de Metal look&feel met: UIManager.setLookAndFeel( UIManager.getCrossPlatformLookAndFeelClassName() ); In de klasse UIManager zijn nog veel meer methoden beschikbaar, onder andere om te onderzoeken of er naast de drie standaard-vormen wellicht nog extra look&feels beschikbaar zijn. Afijn, daar kun je heel knap in worden door veel in de online documentatie te lezen maar je kunt hem natuurlijk ook gewoon op de default laten staan... Klassen in Swing Net als in AWT zit er in Swing voor elk soort interactiecomponent een klasse. Om ze te onderscheiden van de klassen in AWT begint de naam steeds met een J. Dus waar Button een AWT-button beschrijft, is JButton en Swing-button. De klassen voor een toplevel window, zoals JApplet en JFrame zijn de enige zwaargewicht componenten van Swing. Ze zijn een directe uitbreiding van de overeenkomstige klassen in AWT. Alle andere componenten zijn lichtgewicht; ze zijn een uitbreiding van de abstract klasse JComponent, die op zijn beurt een subklasse is van de AWT-klasse Container. Dat laatste is een beetje raar, want niet elke JComponent is bedoeld om iets in te stoppen. Maar JPanel en Box zijn dat wel, en bij de andere controls, zoals JButton en JSlider heb je er geen last van. Indirect zijn in ieder geval alle componenten ook subklasse van Component. Anders dan bij AWT zijn ook de klassen die met menu s te maken hebben (zoals JMenu en JMenuBar) in de hiërarchie opgenomen. Dat heeft als voordeel dat je het menu gewoon in de layout kunt opnemen. Nu kunnen dus ook applets een menu krijgen! Geef je applet een Border- Layout en voeg een JMenuBar-component toe in het noorden. Een verschil tussen JApplet en JFrame enerzijds, en hun AWT-tegenhanger Applet en Frame anderszijds, is dat je aan de Swing-versies niet direct componenten kunt toevoegen met add. In plaats daarvan moet je de methode getcontentpane aanroepen. Die levert een Container op, en daar kun je de componenten aan toevoegen. Die container heeft altijd een BorderLayout, ook in applets. Als dat je niet bevalt, kun je hem natuurlijk alsnog een FlowLayout geven. Dit is een voorbeeld van een AWT-applet dat een enkele button maakt: class Hallo extends Applet { public void init() { Button b = new Button("druk hier"); this.add(b); Dit is de overeenkomstige Swing-versie: class Hallo extends JApplet { public void init() { JButton b = new JButton("druk hier"); Container c = this.getcontentpane(); c.setlayout( new FlowLayout() ); c.add(b); Een laatste verschil tussen AWT en Swing is het gedrag van paint. Door het lichtgewicht karakter van Swing is paint nu ook verantwoordelijk voor het tekenen van de onderdelen van een container. Als je dus een panel hebt met daarin bijvoorbeeld een aantal buttons, maar ook een tekening op de achtergrond, dan heb je een probleem: als je paint zoals gewoonlijk herdefiniëert, dan raak je de buttons kwijt! In plaats daarvan moet je in deze situatie de methode paintcomponent herdefiniëren, die verantwoordelijk is voor het tekenen van de component zonder de onderdelen. Ongelukkigerwijze werkt dit weer niet in het toplevel JApplet, omdat dat geen lichtgewicht component is. Je moet het dus vermijden om in het toplevel applet componenten toe te voegen én een tekening te gebruiken. In plaats daarvan kun je beter een canvas toevoegen, en de tekening daarop maken. In dit verband is het ook belangrijk om op te merken dat er geen klasse JCanvas is. Maar als je een canvas nodig hebt in een Swing-programma kun je voor dit doel een JPanel gebruiken.

171 13.3 Uitbreidingen van AWT 171 Figuur 24: Componenten in Swing (en in AWT)

172 172 Objectgeoriënteerd ontwerp In sectie 13.4 staat een uitgebreid voorbeeldprogramma, dat gebruik maakt van de Swing-toolkit. Tevens wordt daarin gedemonstreerd hoe je omgaat met een Action. Dat is een interface die in Swing wordt gedefinieerd: een geavanceerde versie van ActionListener. Het voert te ver om hier de circa 50 verschillende Swing-componenttypen te bespreken. In figuur 24 staan er een aantal opgesomd, sommige worden gebruikt in het voorbeeldprogramma in sectie Voor de overige klassen kun je de online documentatie raadplegen op het moment dat je ze nodig hebt. Graphics2D Zodra je in Java een vrije tekening wil maken, krijg je te maken met de klasse Graphics. Objecten van deze klasse vervult verschillende rollen. Het is de abstractie van het medium waarop getekend wordt. Dat kan een component zijn, maar ook een bitmap in het geheugen, of een printer. Het heeft geheugen voor een aantal eigenschappen de huidige tekenkleur, te veranderen met setcolor het huidige lettertype, te veranderen met setfont een rechthoek waarbuiten niets getekend mag worden, te veranderen met setclip de oorsprong van het coördinatenstelsel, te veranderen met translate de huidige teken mode: XOR of gewoon de achtergrondkleur: niet veranderbaar, maar wordt gebruikt bij clear Het biedt methoden om iets te tekenen lijnen met drawline open figuren met drawrect, drawoval, en nog vier andere opgevulde figuren met fillrect, filloval, en nog vier andere tekst met drawstring tekeningen met drawimage Dat is tamelijk beperkt. Er is bijvoorbeeld geen eigenschap huidige lijndikte, je kunt geen curves (anders dan cirkelbogen) tekenen, en je kunt figuren weliswaar helemaal opvullen, maar niet met een stippelpatroon. Deze tekortkomingen worden verholpen in een subklasse van Graphics genaamd Graphics2D. (In de toekomst komt er hopelijk ook nog eens een Graphics3D). Maar hoe krijg je zo n Graphics2Dobject te pakken met die extra mogelijkheden? Het antwoord is dat (vanaf versie Java 1.2) zo n Graphics2D-object gewoon aan paint wordt meegegeven. Men heeft in Java 1.2 de header van paint echter niet meer willen wijzigen, omdat oude programma s dan niet meer bruikbaar zouden zijn. Maar als je zeker weet dat je nieuwe programma ook met een Java-interpreter verise 1.2 of nieuwer wordt bekeken, kun je de parameter van paint gewoon casten naar Graphics2D: public void paint(graphics g) { Graphics2D g2 = (Graphics2D) g; // cast g2.setstroke( new BasicStroke(10) ); // zet lijndikte g2.drawline(0,0,100,100); // teken een dikke lijn Wat kan je met zo n Graphics2D? In ieder geval alles wat met Graphics ook kan, want het is een subklasse. Maar daarnaast heeft een Graphics2D-object: extra geheugen voor een aantal nieuwe eigenschappen: de nu wel wijzigbare achtergrondkleur de huidige lijn-stijl, bestaande uit lijndikte, stippeligheid, en vorm van de eindpunten de huidige opvul-stijl: massief (solid), met een patroon (texture), of een overvloeiende kleur (gradient) de huidige manier waarop nieuwe kleuren worden gecombineerd met oude de huidige draaiing en/of vergroting enkele opties (hints) betreffende de tekenkwaliteit nieuwe methoden om iets te tekenen een generieke methode draw en fill die een willekeurig Shape-object kan tekenen. In het apart te construeren Shape-object kun je een simpele rechthoek, maar ook een ingewikkelde curve specificeren. Het voorbeeld hierboven demonstreert hoe je de lijndikte kunt instellen: maak een BasicStrokeobject, en geef dat mee aan setstroke. Behalve de hier gebruikte constructormethode van

173 13.4 Toepassing: een schets-programma 173 BasicStroke die alleen de lijndikte meekrijgt, zijn er ook varianten waarmee je andere eigenschappen van de getekende lijnen kunt instellen. Zie de online help voor de details. De opvul-stijl specificeer je door aan setpaint hetzij een TexturePaint-object of een GradientPaint-object mee te geven, of een simpele Color voor massieve invulling. Met een aanroep van setrenderinghint kun je nog wat extra aanwijzingen geven over de kwaliteit van de tekening. Hoe hoger de kwaliteit, hoe langer het meestal gaat duren. Niet elk Graphicsobject kan elke hint opvolgen, maar ze doen hun best. Dit is een voorbeeld van gebruik van deze methode: g2.setrenderinghint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); Met deze hint zal het Graphics-object proberen om ronde en schuine lijnen een zachte rand te geven, zodat er niet zo n karakteristiek zigzagpatroon ontstaat. Het kan wat langer duren, maar het wordt wel mooier. En als het je niet bevalt, zet je het weer... OFF Toepassing: een schets-programma Beschrijving van het programma In deze sectie schrijven we een compleet tekenprogramma. In figuur 26 is het programma in werking te zien. Anders dan de bitmap-editor in het vorige hoofdstuk kent dit programma tools waarmee je verschillende figuren kunt tekenen: pennetje, lijntje, open en gesloten rechthoekje, en een gummetje om weer te kunnen wissen. Ook is er een tool om tekst toe te voegen aan de tekening. Behalve de tools zijn er onderin het window nog wat bedieningselementen, die we ter onderscheid maar controls zullen noemen: een knop om het plaatje leeg te maken, eentje om hem te draaien (die doet het trouwens nog niet: dat is een van de opgaven), en een combobox om de penkleur uit te kiezen. Als de gebruiker een rechthoek-tool uitkiest kan hij/zij een blok trekken op het canvas. Het gekozen gebied wordt met een grijze contour aangegeven, en pas bij het loslaten van de muis wordt de rechthoek definitief getekend. In plaats van met de knoppen langs de linkerrand kunnen de tools ook worden uitgekozen via het uitklapmenu Tool ; de functie van de controls is ook beschikbaar via het menu Edit. Zo n dubbele bediening komt in professionelere programma s vaak voor, en het is een uitdaging aan de programmeur om dit met inachtneming van het once and only once principe voor elkaar te krijgen. Dat is hier met name van belang, want er komt in een latere versie van het programma vast nog wel eens een tool bij, en het zou mooi zijn als die met één wijziging aan het programma zowel in de toolbox als in het menu terecht komt. Het programma bouwt de gebruikersinterface met behulp van Swing. Er is zowel een applicatieals een applet-versie, maar dankzij Swing kan ook de applet-versie een menu krijgen. Opzet van het programma In dit programma zijn we nu eens niet zuinig met klassen. Het programma bestaat uit maar liefst 22 verschillende klassen. In figuur 25 is de subklasse-hiërarchie van deze klassen getekend, en ook hoe ze samenhangen met de bestaande klassen in Swing en AWT. Het is belangrijk om deze figuur goed te bekijken alvorens het programma te bestuderen, anders ga je al die klassen door elkaar halen. Zo n groot aantal klassen is typerend voor een object-georiënteerd programma. Als je de listings van de aparte klassen leest, zul je zien dat er nergens echt opzienbarende dingen gebeuren. Veel van de listings zijn ook erg kort, en bevatten maar een paar methoden met een handjevol opdrachten. De magie zit hem in de samenwerking van de klassen. Globale opzet van de klassen Voordat we de klassen in detail gaan bekijken, nemen we globaal hun rol in het programma door. Het begint met een idee dat we overnemen van de bitmap-editor: er zijn aparte klasse voor de applicatie, de applet, en het eigenlijke canvas, en er is een klasse die modelleert wat er op dat canvas te zien is. SchetsAppic is het startpunt van de applicatie. Hierin zit ook de methode main die een window aanmaakt. Op dit window is precies één ding te zien: de complete applet-versie van het programma. De overeenkomstige klasse uit de bitmap-editor moest nog een menu aanmaken, maar dat hoeft hier niet: dankzij Swing gebeurt dit hier in de applet.

174 174 Objectgeoriënteerd ontwerp Figuur 25: De klassen in het schets-programma

175 13.4 Toepassing: een schets-programma 175 SchetsApplet is verantwoordelijk voor de opbouw van de interface: het canvas, de toolbox, het menu, en het control panel. Dit is de langste listing, voornamelijk omdat het opbouwen van ene interface nu eenmaal veel gedoe geeft. Er is een objectvariabele om het canvas in te bewaren (want daar moet nog wel eens wat mee gebeuren), en een om de momenteel uitgekozen tool in te onthouden. En tenslotte is er een boolean die vastlegt of het applet is opgestart als los applet, of als deel van een applicatie; voor beide doeleinden is er een eigen constructormethode. Het object luistert naar muisbewegingen en toetsenbordakties op het canvas; als daar wat interessants gebeurt, waarschuwt hij de momenteel uitgekozen tool. Verder luistert het object naar keuze-veranderingen in de kleurkeuze-combobox; als daar wat gebeurt, waarschuwt hij het canvas. Het object luistert echter niet naar ingedrukte buttons of gekozen menu s: die doen dat zelf (daarover zodadelijk meer). SchetsCanv is het canvas met de eigenlijke tekening. Er is een objectvariabele waarin de tekening wordt bewaard, en eentje voor de huidige penkleur. De tekening kan zichzelf tekenen en van afmeting veranderen, dus de methoden van de klasse SchetsCanv doen weinig meer dan verzoekjes over en weer doorspelen. Schets is het model van de tekening. Het bewaren van de huidige tekening besteedt-ie uit aan een BitMap-object. Die klasse hadden we in het vorige hoofdstuk al geschreven, dus die komt hier goed van pas. Als de schets groter moet worden wordt de bitmap vervangen door een groter exemplaar, waar de oude alvast in wordt gekopiëerd. Zoals bekend is een ActionListener een object dat kan reageren op een aktie. Swing definieert een uitbreiding van deze interface: Action. Die beschrijft een luister-actie, maar daarnaast nog een aantal eigenschappen, zoals een naam, desgewenst een toelichting, een afkorting, en een bijbehorend icoon. Om het helemaal makkelijk te maken definieert Swing ook een abstracte klasse AbstractAction die Action implementeert, met alvast wat standaardfuncties voor de meestgebruikte eigenschappen. Een Action-object bundelt alle informatie die van belang is voor een aktie: zowel voor het weergeven in een menu o.i.d. (gebruikmakend van naam en/of icoon), maar ook voor het afhandelen van de aktie. Van dit mechanisme maken we in het programma dankbaar gebruik: we maken drie gespecialiseerde klassen AboutAktie, ToolAktie, en ControlAktie, die de informatie voor de drie soorten akties die in de schets-editor voorkomen vastleggen. Ook voor het gedrag van een icoon biedt Swing een interface: Icon. Een icoon moet zichzelf kunnen tekenen, en moet kunnen zeggen hoe groot-ie is. Swing biedt alvast een implementatie: ImageIcon. Die gebruiken we in het programma voor het icoon van het pennetje en het gummetje. Maar de andere iconen maken we op een andere manier. Die tonen immers een kleine versie van een figuur die het programma toch al moet kunnen tekenen op het canvas (lijn, open of gesloten rechthoek, tekst). We werken netjes once and only once door dat te gebruiken bij twee eigen implementaties van Icon: TekstIcon voor de tekst, en TweepuntIcon voor de figuren waar een start- en eindpunt een rol speelt (lijn, rechthoek). Om het helemaal mooi te maken zetten we het gemeenschappelijke van deze twee klassen, namelijk de afmeting van het icoon, in een zelfgemaakte abstracte superklasse ToolIcon. Het definiëren van interfaces hoeft niet beperkt te blijven tot de standaard-bibliotheken: je kunt ook zelf in een programma een nieuwe interface definiëren, en vervolgens klassen schrijven die die interface implementeren. Hier komt dat goed van pas voor het begrip Tool. Een tool is iets dat muis- (en toets-)bewegingen op een schets-canvas kan omzetten in iets blijvends. In de interface Tool specificeren we daarom een viertal methoden die dit idee in methode-met-parameter-vorm vastleggen. De abstracte klasse StartpuntTool werkt het idee alvast wat nader uit voor het soort van tools waarbij je eerst een startpunt aanklikt, dat bewaard moet worden om het figuur later definitief te kunnen tekenen. Deze klasse is abstract, want wat er dan precies op dat startpunt getekend wordt is in deze klasse nog niet uitgewerkt. In het programma hebben we twee soorten tools die voortborduren op het idee van de StartpuntTool: TekstTool, die een tekst toont op het startpunt, en TweepuntTool voor de tools die behalve een startpunt ook nog een tweede punt nodig hebben. Die laatste is abstract, omdat we nog niet hebben uitgewerkt wat er dan met die twee punten gebeurt. LijnTool en RectTool zijn twee concrete invullingen van het abstracte TweepuntTool.

176 176 Objectgeoriënteerd ontwerp FillRectTool werkt voor de plaatsbepaling hetzelfde als RectTool, maar voor het definitief tekenen een beetje anders. Door het een subklasse van RectTool te maken wordt once and only once de plaatsbepaling uitgeprogrammeerd. Zelfs PenTool blijkt nog weer wat gemeenschappelijk te hebben met LijnTool: een pentekening bestaat immers uit een serie (korte) lijnen. Een gum-spoor is in feite niets anders dan een dikke, witte lijn, dus wordt GumTool een subklasse van PenTool. We zullen nu de details van een aantal van deze klasse wat uitgebreider bespreken. Neem steeds de betreffende listing erbij! blz. 179 blz. 179 blz. 180 blz. 186 blz. 181 blz. 184 blz. 182 blz. 188 De klasse SchetsApplet De tekst van de klasse SchetsApplet is verpreid over vier listings, vanaf listing 32. In listing 32 staat de methode init. Hier wordt de GUI opgebouwd: het canvas, de toolbar, het controlpanel, en het menu. Eerst worden de akties verzameld die in respectievelijk de toolbar en het controlpanel nodig zijn. Voor de overzichtelijkheid gebeurt dat in twee aparte methoden maaktoolakties en maakcontrolakties. Collections komen hierbij goed van pas om de lijst akties op te slaan (al had het eventueel ook wel met een array gekund). De verkregen collections worden doorgespeeld aan drie methoden, die de eigenlijke toolbox, controlpanel en menu opbouwen. Ook dit gebeurt voor de duidelijkheid in aparte methoden. Die methoden leveren een Component op, die aan de verschillende randen van het applet worden neergezet. Of liever gezegd: de randen van de met getcontentpane verkregen container, want zo moet dat in Swing-programma s. De andere niet-triviale methode in deze listing is getimageicon. Deze roept de juiste constructor van de Swing-klasse ImageIcon aan, afhankelijk van het feit of we een los applet zijn of deel uitmaken van een application. In listing 33 (nog wel deel van de klasse SchetsApplet) staan de methoden die de aktie-lijsten samenstellen. Het enige wat de buitenwereld (d.w.z. de methode init) van ze vraagt is dat ze een Collection opleveren. Intern in de methode moeten we de keuze maken welk van de mogelijke collections (zie de hiërarchie in figuur 23) we gebruiken. Omdat het wel handig is als de elementen in dezelfde volgorde in het menu verschijnen als we ze hier opsommen, moeten we voor een list kiezen en niet voor een set. Welk van de twee concrete lists we kiezen maakt niet zoveel uit: in plaats van de hier gekozen LinkedList had dat ook ArrayList kunnen zijn. De lijsten zijn opgebouwd uit allemaal losse Action-objecten, of meer specifiek: ToolAkties in maaktoolaktie en ControlAkties in maakcontrolakties. Als je nauwkeurig de parameters telt, zie je dat er twee verschillende contructoren zijn van ToolAktie: eentje voor akties met een grafisch icoon, en eentje voor akties met een zelfgetekend icoon. Die constructoren worden gedefinieerd in listing 43. De details van het opbouwen van de GUI staan in listing 34. Het controlpanel is inderdaad een JPanel, die uit twee onderdelen bestaat: de rij buttons, die worden ondergebracht in een JToolBar, en de JComboBox voor de penkleurkeuze. De akties die worden met behulp van een Iterator (zie sectie 13.2) één voor één met add aan de toolbar toegevoegd. Het handige van een JToolBar is dat je daar Actions direct aan kunt adden; het maken van de button en het koppelen van de ActionListener gebeurt dan automatisch. Alle benodigde informatie kan-ie immers in de Action vinden. Ook JMenu lust Actions rauw, dus het opbouwen van de drie menu s is eenvoudig. Voor het maken van de toolbox hadden we ook weer een JToolBar kunnen gebruiken. Maar die maakt buttons zonder plaatje, en in zo n toolbox zijn die icoontjes nou juist zo leuk. Voor de toolbox kiezen hier dus juist niet voor een JToolBar, maar voor een eenvoudige Box (dit is de enige Swing-component die niet met een J begint). Dat heeft als prijs dat we nu zelf de buttons moeten maken en van actionlistener voorzien. Dat geeft meteen een idee van het soort werk dat JToolBar achter de schermen doet. Het vullen van de combobox gebeurt door het aanroepen van additem. Die methode lust elk object dat de methode tostring kent. Zo n beetje alles dus, en in ieder geval objecten van de speciaal voor dit doel gemaakte klasse Kleur (zie listing 39). Het vierde en laatste deel van SchetsApplet staat in listing 35. Hier worden de diverse event-listen methodes gedefinieerd. De relevante muis-methoden (maar niet de irrelevante) en keytyped worden aan de momenteel geselecteerde Tool doorgespeeld, die daarbij ook het canvas krijgt doorgespeeld: daar moeten de tools immers hun effect op uitoefenen. De interface Tool (zie listing 47) is precies gemaakt om dit soort boodschappen in ontvangst te nemen.

177 13.4 Toepassing: een schets-programma 177 De methode itemstatechanged reageert op het veranderen van de keuze in de kleurkeuzecombobox. Hier vissen we de gekozen kleur uit het Kleur-object, en maken dat tot de huidige penkleur, die door het canvas wordt beheerd. De klasse SchetsCanv en diens model Schets De klasse SchetsCanv (zie listing 37) beheert dus een penkleur, en heeft daartoe een private Color- blz. 183 variabele. Door aanroep van setpenkleur kan die door anderen worden veranderd. Een tweede variabele bevat een Schets-object, die op zijn beurt (zie listing 40) een BitMap-object bevat. Die blz. 185 hadden we al in het vorige hoofdstuk geschreven (zie listing 25); het was een uitbreiding van blz. 144 bibliotheekklasse BufferedImage. In de klasse SchetsCanv is er daarom de beschikking over twee verschillende Graphics-objecten: de Graphics die elke Component tot zijn beschikking heeft, en die je kunt opvragen met de uit Component geërfde methode getgraphics. Deze gaan we gebruiken om tijdelijk op het canvas te kunnen knoeien terwijl de gebruiker bezig is om een blok te trekken. de Graphics die bij de bitmap hoort, die staat opgeslagen in het Schets-object. Deze gaan we gebruiken om de permanente tekening in te bewaren. Om ook de buitenwereld hier toegang toe te geven, definiëren we een extra methode getgraphicspermanent. Merk op dat de buitenwereld niet om de huidige penkleur kan vragen. De variabele is private, en er is geen methode getpenkleur. In plaats daarvan wordt de penkleur alvast geselecteerd als de buitenwereld om de permanente Graphics vraagt. Een Schets moet zichzelf kunnen tekenen. Hij doet dat (zie listing 40) simpelweg door zijn bitmap blz. 185 te tekenen. Het enige wat Schets verder nog doet is de bitmap vervangen door een grotere, wanneer dat nodig is. De Icon-hiërarchie Volgens de specificatie van de interface Icon in Swing is een icon een object dat zijn breedte en hoogte kan meedelen, en dat zichzelf op een bepaalde plaats op een Graphics kan tekenen. In Swing is er al een implementatie ImageIcon, die we gebruiken voor het icon van het pennetje en het gummetje. Voor onze zelfgemaakte icons hebben we om te beginnen de abstracte klasse ToolIcon in listing 44. blz. 187 Deze implementeert twee van de drie gevraagde methoden: het opvragen van de breedte en de hoogte. Subklasse TekstIcon in listing 45 voegt de derde methode toe, die een symbolische tekst blz. 187 van het juiste formaat tekent. Een andere invulling van het tekenen wordt gegeven in subklasse TweepuntIcon in listing 46. Voor het eigenlijke tekenen doet deze een beroep op het TweepuntTool- blz. 187 object dat bij constructie wordt meegegeven. De Aktie-hiërarchie Een Action is, volgens de specificatie van deze interface in Swing, een uitgebreid ActionListener. Er moet dus in ieder geval een methode actoinperformed zijn. De uitbreiding bestaat daaruit, dat een Action ook een aantal eigenschappen heeft, zoals naam, toelichting en icoon. Al onze drie implementaties van Action definiëren dus in ieder geval een methode actionperformed. De klasse AboutAktie (zie listing 41) vult die in met het tonen van een blz. 185 informatie-dialoog. Zie je meteen hoe je dat doet. De naam "About" (die in het menu wordt getoond) wordt doorgespeeld aan super, de constructormethode van de superklasse AbstractAction. De klasse ControlAktie (zie listing 42) vult actionperformed in met het leegmaken van het blz. 186 canvas, of met andere in de toekomst nog toe te voegen operaties op het canvas. De identiteit van het canvas moet aan de constructormethode worden meegegeven, en wordt daar bewaard voor later gebruik. De overige parameters van de constructor woren doorgespeeld aan de superklasse. De klasse ToolAktie (zie listing 43) geeft de invulling voor actionperformed voor de tools (pen- blz. 186 netje, lijntje, enz). We geven simpelweg aan de applet door welke tool vanaf dit moment moet worden gebruikt. De voor deze action relevante tool moet bij constructie worden meegegeven, en wordt voor gebruik in actionperformed bewaard in een private variabele. Ook de identiteit van de applet wordt op die manier bewaard. Verder heeft de constructormethode een naam en een toelichting als parameter, die aan de superklasse worden doorgespeeld. De vijfde parameter van de constructor tenslotte is een Icon. Die hebben we in listing 33 ingevuld met een ImageIcon van blz. 180 het pennetje of het gummetje. Maar er zijn ook vier-parameterversies van de constructormethode: in dat geval wordt, afhankelijk van het type van de vierde parameter, een TekstIcon of een TweepuntIcon gemaakt, waarmee alsnog de vijf-parameterversie wordt aangeroepen.

178 178 Objectgeoriënteerd ontwerp blz. 188 blz. 188 blz. 188 blz. 189 blz. 189 blz. 190 blz. 190 blz. 190 blz. 190 De Tool-hiërarchie De interface en de klassen in deze hiërarchie zijn specifiek voor dit programma ontworpen. Ze borduren dus niet voort op een interface in Swing. In de interface Tool (zie listing 47) leggen we vast wat we voor dit programma eigenlijk onder een tool verstaan: een object dat als reaktie op het indrukken, verslepen, of laslaten van de muis iets kan doen met een punt en een SchetsCanvas. Het feit dat er een SchetsCanvas bij betrokken is maakt het natuurlijk specifiek voor dit programma. Herinner je dat we het applet zo hebben ingericht dat bij er bij mouse-events altijd een van deze methoden wordt aangeroepen van de op dat moment actieve tool. Van deze interface kunnen we vervolgens diverse implementaties gaan maken. Het begint met een abstracte klasse StartpuntTool (zie listing 48), die alvast één methode invult. Op het indrukken van de muis reageert zo n startpunt-tool door dat punt voor later gebruik te bewaren in een speciaal daarvoor gedeclareerde variabele. De klasse TekstTool in listing 49 is een concrete uitbreiding daarvan. Bij het slepen en loslaten van de muis hoeft er niets te gebeuren, maar bij het intikken van een letter moet deze letter worden afgebeeld op de positie van het eerder bewaarde startpunt. Daarna moet de x-coordinaat van het startpunt worden verhoogd. Met hoeveel precies hangt af van de eigenschappen van het font. Het is een beetje gepeuter om dat te berekenen, maar gelukkig kun je het zo gek niet bedenken of Java heeft er een klasse voor. In dit geval is FontRenderContext wat we nodig hebben. De klasse TweepuntTool in listing 50 is een andere uitbreiding van StartpuntTool. Deze reageert op het slepen van de muis met het tekenen van een grijze contour. Bij het loslaten van de muis wordt de figuur definitief getekend. Toetsindrukken worden genegeerd. Daarmee zijn alle vier de methoden van de interface Tool ingevuld, maar er zijn twee nieuwe bijgekomen: tekencontour en tekenfiguur. We kunnen tekenfiguur alvast een default-invulling geven, namelijk dat hij ook alleen de contour tekent. Maar hoe je een contour tekent (lijn? rechthoek? cirkel?) kunnen we nog niet vastleggen. Deze methode wordt daarom abstract gedeclareerd, en hoeft daarom geen body te hebben. De prijs is dat ook de klasse daarmee nog abstract is. Wel concreet zijn de subklassen LijnTool in listing 51 en RectTool in listing 52. Deze geven ieder een eigen invulling aan de ontbrekende methode tekencontour. De default-invulling dat het definitieve tekenen net zo gebeurt als het voorlopige, is voor deze twee tools in orde. De klasse FillRectTool in listing 53 erft het tekenen van de contour van RectTool, maar geeft het definitieve tekenen van de figuur een nieuwe invulling, die het default-gedrag vervangt. De figuur wordt nu ingevuld getekend met behulp van fillrect. De implementatie van PenTool in listing 54 is subtiel. Deze herdefinieert de methode muisversleept: bij elk verslepen van de muis wordt nu zogenaamd de muis even losgelaten enb weer ingedrukt. Die methoden waren al ingevuld met het tekenen van een lijn tot hier, en het beginnen van een nieuw lijnsegment. Het gevolg is dat de pen-tool bij elke muisbeweging (met inngedrukte muisknop) een klein lijnstukje tekent en aan een nieuw lijntje gaat beginnen. Dat is precies wat we nodig hebben voor een pen. De klasse GumTool in listing 55 tenslotte is weer een verfijning van PenTool. Bij het tekenen van de contour (en daarmee ook van de definitieve figuur, want voor lijnen, en dus ook voor pennen, en dus ook voor gummen, is die hetzelfde als de contour) wordt eerst de pendikte 7 uitgekozen, en de tekenkleur wit. Daarna wordt alsnog de oorspronkelijke methode in de superklasse aangeroepen (met behulp van super). Op deze manier doet een gum hetzelfde als een pen, maar dan met een dikke witte lijn in plaats van een dunne gekleurde lijn.

179 13.4 Toepassing: een schets-programma 179 import java.awt.*; import java.awt.event.*; import java.util.*; import java.net.url; 5 import javax.swing.*; public class SchetsApplet extends JApplet implements MouseListener, MouseMotionListener, ItemListener, { 10 SchetsCanv canvas; Tool currenttool; boolean isdeelvanapplicatie; KeyListener public SchetsApplet() 15 { this(false); SchetsApplet(boolean app) { this.isdeelvanapplicatie = app; 20 if (app) this.init(); public void init() { canvas = new SchetsCanv(); 25 Collection tools = maaktoolakties(); Collection controls = maakcontrolakties(); Container c = this.getcontentpane(); c.setlayout(new BorderLayout()); 30 c.add(canvas, BorderLayout.CENTER); c.add(maaktoolbox(tools), BorderLayout.WEST ); c.add(maakmenubar(tools, controls), BorderLayout.NORTH ); c.add(maakcontrolpanel( controls), BorderLayout.SOUTH ); 35 canvas.addmouselistener(this); canvas.addmousemotionlistener(this); canvas.addkeylistener(this); 40 public void setcurrenttool(tool tool) { currenttool = tool; private ImageIcon getimageicon(string filename) 45 { try { if (isdeelvanapplicatie) return new ImageIcon(filename); else return new ImageIcon(new URL(this.getCodeBase(), filename)); 50 catch (Exception e) { return null; Listing 32: Schets/SchetsApplet.java, deel 1 van 4

180 180 Objectgeoriënteerd ontwerp Figuur 26: Het schets-programma in werking 55 private Collection maaktoolakties() { currenttool = new PenTool(); LinkedList result; result = new LinkedList(); 60 result.add( new ToolAktie(this, "Pen", "Vrije pentekening", currenttool, getimageicon("pen.gif"))); result.add( new ToolAktie(this, "Lijn", "Lijntekening", new LijnTool())); result.add( new ToolAktie(this, "Open rect", "Open rechthoek", new RectTool())); result.add( new ToolAktie(this, "Fill rect", "Gevulde rechthoek", new FillRectTool())); 65 result.add( new ToolAktie(this, "Tekst", "Tekst", new TekstTool())); result.add( new ToolAktie(this, "Gum", "Uitgummen van de tekening", new GumTool(), getimageicon("gum.gif"))); return result; 70 private Collection maakcontrolakties() { LinkedList result; result = new LinkedList(); result.add( new ControlAktie(canvas, "Clear", "Tekening wissen" )); 75 result.add( new ControlAktie(canvas, "Rotate", "Tekening draaien")); return result; Listing 33: Schets/SchetsApplet.java, deel 2 van 4

181 13.4 Toepassing: een schets-programma 181 private Component maakcontrolpanel(collection controls) 80 { JPanel controlpanel = new JPanel(); JToolBar toolbar = new JToolBar(JToolBar.HORIZONTAL); toolbar.setfloatable(false); for (Iterator iter=controls.iterator(); iter.hasnext(); ) 85 toolbar.add((action) iter.next()); controlpanel.add(toolbar); controlpanel.add(box.createhorizontalstrut(20)); controlpanel.add(new JLabel("Penkleur")); 90 JComboBox combo = new JComboBox(); combo.additem( new Kleur("zwart", Color.black) ); combo.additem( new Kleur("rood", Color.red ) ); combo.additem( new Kleur("groen", Color.green) ); combo.additem( new Kleur("blauw", Color.blue ) ); 95 combo.additemlistener(this); controlpanel.add(combo); return controlpanel; private Component maakmenubar(collection tools, Collection controls) { JMenuBar menubar = new JMenuBar(); JMenu menu; Iterator iter; menu = new JMenu("Tool"); for (iter=tools.iterator(); iter.hasnext(); ) menu.add((action) iter.next()); menubar.add(menu); menu = new JMenu("Edit"); for (iter=controls.iterator(); iter.hasnext(); ) menu.add((action) iter.next()); menubar.add(menu); menu = new JMenu("Help"); menu.add(new AboutAktie()); menubar.add(menu); 120 return menubar; private Component maaktoolbox(collection tools) { Box toolbox = new Box(BoxLayout.Y_AXIS); 125 for (Iterator iter=tools.iterator(); iter.hasnext(); ) { Action a = (Action) iter.next(); JButton button = new JButton((Icon) a.getvalue(action.default)); button.settooltiptext((string) a.getvalue(action.short_description)); button.addactionlistener(a); 130 toolbox.add(button); toolbox.add(box.createverticalglue()); return toolbox; Listing 34: Schets/SchetsApplet.java, deel 3 van 4

182 182 Objectgeoriënteerd ontwerp public void mousepressed(mouseevent e) { this.currenttool.muisingedrukt (e.getpoint(), canvas); 140 public void mousereleased(mouseevent e) { this.currenttool.muislosgelaten (e.getpoint(), canvas); public void mousedragged(mouseevent e) 145 { this.currenttool.muisversleept (e.getpoint(), canvas); public void mouseclicked(mouseevent e) { canvas.requestfocus(); 150 public void itemstatechanged(itemevent event) { Object item = event.getitem(); if (item instanceof Kleur) 155 canvas.setpenkleur( ((Kleur)item).getKleur() ); public void keytyped(keyevent e) { this.currenttool.letteringetikt((char) e.getkeychar(), canvas); 160 public void keyreleased(keyevent e) { public void keypressed(keyevent e) { public void mouseentered(mouseevent e) { 165 public void mouseexited(mouseevent e) { public void mousemoved(mouseevent e) { Listing 35: Schets/SchetsApplet.java, deel 4 van 4 import java.awt.event.*; public class WindowSluiter extends WindowAdapter { public void windowclosing(windowevent e) 5 { System.exit(0); Listing 36: Schets/WindowSluiter.java

183 13.4 Toepassing: een schets-programma 183 import java.awt.*; import javax.swing.*; class SchetsCanv extends JPanel 5 { private Schets schets; private Color penkleur = Color.black; public SchetsCanv() 10 { schets = new Schets(); public void update(graphics g) { paint(g); public void paint(graphics g) { schets.teken(g); public void setbounds(int x, int y, int w, int h) { schets.resize(w, h); super.setbounds(x, y, w, h); repaint(); public void repaintnu() { this.paint(this.getgraphics()); public Graphics getgraphicspermanent() { Graphics g = schets.getgraphics(); g.setcolor(penkleur); return g; 35 public void setpenkleur(color c) { penkleur = c; public void clear() { schets.clear(); this.repaint(); Listing 37: Schets/SchetsCanv.java

184 184 Objectgeoriënteerd ontwerp import java.awt.*; import javax.swing.jframe; public class SchetsApplic extends JFrame 5 { SchetsApplic() { this.setsize(800,600); this.settitle("schets editor"); this.setbackground(color.lightgray); 10 this.addwindowlistener( new WindowSluiter() ); this.getcontentpane().add( new SchetsApplet(true), BorderLayout.CENTER); public static void main(string[] args) 15 { new SchetsApplic().show(); Listing 38: Schets/SchetsApplic.java import java.awt.color; class Kleur { String naam; 5 Color kleur; public Kleur(String naam, Color kleur) { this.naam = naam; this.kleur = kleur; 10 public Color getkleur() { return kleur; public String tostring() 15 { return naam; Listing 39: Schets/Kleur.java

185 13.4 Toepassing: een schets-programma 185 import java.awt.*; class Schets { 5 private BitMap bitmap; Schets() { bitmap = new BitMap(1,1); 10 public void teken(graphics g) { g.drawimage(bitmap, 0, 0, null); 15 public void resize(int w, int h) { if (w!=bitmap.getwidth() h!=bitmap.getheight()) { BitMap nieuw = new BitMap(w, h); nieuw.getgraphics().drawimage(bitmap, 0, 0, null); 20 bitmap = nieuw; public Graphics getgraphics() 25 { return bitmap.getgraphics(); public void clear() { bitmap.clear(); 30 Listing 40: Schets/Schets.java import java.awt.event.*; import javax.swing.*; public class AboutAktie extends AbstractAction 5 { public AboutAktie() { super("about"); 10 public void actionperformed(actionevent event) { JOptionPane.showMessageDialog( null, "SchetsEditor versie 1.0", "About", JOptionPane.INFORMATION_MESSAGE 15 ); Listing 41: Schets/AboutAktie.java

186 186 Objectgeoriënteerd ontwerp import java.awt.event.*; import javax.swing.*; public class ControlAktie extends AbstractAction 5 { private SchetsCanv canvas; public ControlAktie(SchetsCanv canvas, String naam, String tip) { super(naam); this.canvas = canvas; 10 putvalue(action.short_description, tip); public void actionperformed(actionevent event) { String naam = (String) this.getvalue(action.name); 15 if (naam.equals("clear")) canvas.clear(); Listing 42: Schets/ControlAktie.java import java.awt.event.*; import javax.swing.*; public class ToolAktie extends AbstractAction 5 { protected Tool tool; protected SchetsApplet applet; public ToolAktie( SchetsApplet applet, String naam 10, String tip, Tool tool, Icon icon ) { super(naam /*,icon */ ); // twee parameters geeft icons in de menu s this.applet = applet; this.tool = tool; 15 putvalue(action.default, icon); putvalue(action.short_description, tip); public ToolAktie(SchetsApplet applet, String naam, String tip, TweepuntTool tool) 20 { this(applet, naam, tip, tool, new TweepuntIcon(tool) ); public ToolAktie(SchetsApplet applet, String naam, String tip, TekstTool tool) { this(applet, naam, tip, tool, new TekstIcon() ); public void actionperformed(actionevent event) { applet.setcurrenttool(tool); Listing 43: Schets/ToolAktie.java

187 13.4 Toepassing: een schets-programma 187 import java.awt.dimension; import javax.swing.icon; public abstract class ToolIcon implements Icon 5 { private Dimension dim = new Dimension(36, 36); public int geticonwidth() { return dim.width; public int geticonheight() { return dim.height; Listing 44: Schets/ToolIcon.java import java.awt.*; public class TekstIcon extends ToolIcon { 5 public void painticon(component c, Graphics g, int x, int y) { int w = this.geticonwidth(), h = this.geticonheight(); g.setcolor(c.getbackground()); g.fillrect(x, y, w, h); g.setcolor(color.black); 10 g.setfont(new Font("Helvetica", Font.BOLD, h-5 )); g.drawstring("tx", x+2, y+h-2 ); Listing 45: Schets/TekstIcon.java import java.awt.*; public class TweepuntIcon extends ToolIcon { private TweepuntTool tool; 5 public TweepuntIcon(TweepuntTool t) { this.tool = t; 10 public void painticon(component c, Graphics g, int x, int y) { int w = this.geticonwidth(), h = this.geticonheight(); g.setcolor(c.getbackground()); g.fillrect(x, y, w, h); 15 g.setcolor(color.black); tool.setstartpunt( new Point(x+2, y+2 ) ); tool.tekenfiguur (g, new Point(x+w-3, y+h-3) ); Listing 46: Schets/TweepuntIcon.java

188 188 Objectgeoriënteerd ontwerp import java.awt.point; public interface Tool { 5 void muisingedrukt (Point p, SchetsCanv canvas); void muislosgelaten (Point p, SchetsCanv canvas); void muisversleept (Point p, SchetsCanv canvas); void letteringetikt (char c, SchetsCanv canvas); Listing 47: Schets/Tool.java import java.awt.*; public abstract class StartpuntTool implements Tool { protected Point startpunt; 5 public void setstartpunt(point s) { startpunt = s; 10 public void muisingedrukt(point p, SchetsCanv canvas) { this.setstartpunt(p); Listing 48: Schets/StartpuntTool.java import java.awt.*; import java.awt.font.*; class TekstTool extends StartpuntTool 5 { private Font font = new Font("Helvetica", Font.BOLD, 24); public void letteringetikt(char c, SchetsCanv canvas) { String tekst = ""+c; 10 Graphics g = canvas.getgraphicspermanent(); g.setfont(font); g.drawstring(tekst, startpunt.x, startpunt.y ); FontRenderContext frc = ((Graphics2D)g).getFontRenderContext(); startpunt.x += font.getstringbounds(tekst, frc).getbounds().width; 15 canvas.repaint(); 20 public void muisversleept (Point p, SchetsCanv canvas) { public void muislosgelaten (Point p, SchetsCanv canvas) { Listing 49: Schets/TekstTool.java

189 13.4 Toepassing: een schets-programma 189 import java.awt.*; public abstract class TweepuntTool extends StartpuntTool { 5 protected static Point minimumpunt(point p1, Point p2) { return new Point( Math.min(p1.x, p2.x), Math.min(p1.y, p2.y) ); protected static Dimension puntafstand(point p1, Point p2) 10 { return new Dimension( 1+Math.abs(p1.x - p2.x), 1+Math.abs(p1.y - p2.y) ); public void muisversleept (Point p, SchetsCanv canvas) { canvas.repaintnu(); 15 Graphics g = canvas.getgraphics(); g.setcolor(color.gray); this.tekencontour(g, p); 20 public void muislosgelaten (Point p, SchetsCanv canvas) { Graphics g = canvas.getgraphicspermanent(); this.tekenfiguur(g, p); canvas.repaint(); 25 public void letteringetikt (char c, SchetsCanv canvas) { public void tekenfiguur(graphics g, Point p) 30 { this.tekencontour(g, p); public abstract void tekencontour(graphics g, Point p); Listing 50: Schets/TweepuntTool.java import java.awt.*; class LijnTool extends TweepuntTool { public void tekencontour(graphics g, Point p) 5 { g.drawline(startpunt.x, startpunt.y, p.x, p.y); Listing 51: Schets/LijnTool.java

190 190 Objectgeoriënteerd ontwerp import java.awt.*; class RectTool extends TweepuntTool { public void tekencontour(graphics g, Point p) 5 { Point xy = minimumpunt(startpunt, p); Dimension wh = puntafstand(startpunt, p); g.drawrect(xy.x, xy.y, wh.width, wh.height); Listing 52: Schets/RectTool.java import java.awt.*; class FillRectTool extends RectTool { public void tekenfiguur(graphics g, Point p) 5 { Point xy = minimumpunt(startpunt, p); Dimension wh = puntafstand(startpunt, p); g.fillrect(xy.x, xy.y, wh.width, wh.height); Listing 53: Schets/FillRectTool.java import java.awt.point; public class PenTool extends LijnTool { public void muisversleept(point p, SchetsCanv canvas) 5 { muislosgelaten(p, canvas); muisingedrukt (p, canvas); Listing 54: Schets/PenTool.java import java.awt.*; public class GumTool extends PenTool { public void tekencontour(graphics g, Point p) 5 { ((Graphics2D) g).setstroke( new BasicStroke(7) ); g.setcolor(color.white); super.tekencontour(g, p); Listing 55: Schets/GumTool.java

191 13.5 File input/output File input/output Klassen voor file input/output Er is een package met ruim 40 verschillende klassen beschikbaar die iets te maken hebben met input en output (afgekort: I/O) van en naar files, en daarbij zijn de bijbehorende exceptions nog niet eens meegerekend. Deze package heet java.io. De klassen vormen een subklassenhiërarchie, compleet met tussenliggende abstracte klassen. De hiërarchie maakt dat het allemaal nog wel te begrijpen valt: zonder het hiërarchische beeld zou je het overzicht over die 40 klassen snel verliezen. Dat is trouwens in het algemeen de kracht van object-georiënteerde talen. Jammer genoeg hebben de ontwerpers van de package java.io niet alle mogelijkheden voor hiërarchische ordening gebruikt. In de nu volgende bespreking zijn daarom een paar fantasie-klassen toegevoegd, die dus niet ècht in de package java.io zitten, maar het begrip wel vergemakkelijken. De hele hiërarchie staat afgebeeld in figuur 27. Klassen zijn, net als in de eerdere voorbeelden, aangegeven met een gele/lichtgrijze rechthoek, abstracte klassen met een groen/donkerder trapezium. De fantasie-klassen zijn getekend als omgekeerd trapezium en met een cursieve naam. De pijlen aan de rechterkant van het schema behoren niet tot de hiërarchie, maar geven aan welk type parameter nodig is voor de constructor-methode van de klasse ernaast. Op het eerste gezicht komt dit overzicht natuurlijk nogal overweldigend over. Dat geeft niet; we gaan de hiërarchie gewoon stap voor stap bespreken, te beginnen bij de klasse bovenin. Uiteraard is dat de klasse Object, want alle klassen in Java zijn een subklasse van Object. Files: vorm of inhoud? Het zal geen verbazing wekken dat er in de package java.io als directe subklasse van Object een klasse File is, waarmee een file wordt gemodelleerd. Je zou je zelfs kunnen afvragen waarom er eigenlijk nog meer klassen nodig zijn... Bij de constructie van een File object geef je als parameter de naam van de file mee. Daarmee is het nog niet zeker dat de file ook daadwerkelijk op de disk aanwezig is; een File-object beschrijft dus een mogelijk op de computer aanwezige file. Door het aanroepen van de methode exists kun je nagaan of de file inderdaad aanwezig is: File f; f = new File("hallo.txt"); if (f.exists()) uitvoer.settext("file bestaat"); Met andere methodes (isfile en isdirectory, die net als exists een boolean resultaat hebben) kun je nagaan of het een file dan wel een directory betreft. Is het een directory, dan kun je een methode listfiles aanroepen, die een array van File-objecten oplevert met alle files die in de directory zitten. Als je een programma schrijft dat onder verschillende operating systems moet kunnen werken, en dat is in Java over het algemeen wel het streven, dan moet je oppassen met de tekens waarmee je files en directories in de filenaam scheidt. In Windows wordt daar het teken \ voor gebruikt, maar in Unix juist /. Als je het universeel wilt houden gebruik je geen van beiden, maar de constante pathseparator, die in de klasse beschikbaar is. Of je gebruikt de constructor van File met twee parameters: een File-object die de directory aanduidt, en een String met de directe naam van de file. Er zijn nog wel wat meer methodes beschikbaar (zie de online help), maar geen van deze methodes zijn in staat om de inhoud van de file te lezen; het gaat altijd om de file als geheel. Om de inhoud van een file te kunnen lezen, of nieuwe inhoud in een file te kunnen schrijven, zijn er andere klassen nodig. Die klassen zijn, net als File, directe subklassen van Object. Stream-I/O versus Random-access-I/O Er zijn twee manieren om de inhoud van een file te benaderen: als continue stream gegevens, dat wil zeggen dat de gegevens in de file alleen op volgorde worden gelezen of geschreven met random access, waarbij op willekeurige posities stukjes van de file kunnen worden gelezen of veranderd. Voor de laatste is er de klasse RandomAccessFile. Die zullen we hier verder niet gebruiken, maar wie hierin geïnteresseerd is kan in de online help de beschikbare methodes opzoeken. Voor streams van gegevens is er een grote hoeveelheid klassen. Het zou wel logisch geweest zijn als die subklasse

192 192 Objectgeoriënteerd ontwerp Figuur 27: De klassen voor file-input en -output

193 13.5 File input/output 193 waren van een abstracte klasse Stream. Dat is niet het geval; er zijn een viertal klassen die direct subklasse zijn van Object. Om toch een beetje structuur aan het overzicht te geven, is in figuur 27 toch een abstracte klasse Stream ingetekend. Om aan te geven dat dit eigenlijk een fantasieklasse is, is het trapezium andersom getekend en de naam cursief gemaakt. Byte-streams versus Character-streams Er zijn twee soorten streams: byte-streams, waarbij iedere byte in de file ook als één byte in het programma beschikbaar komt character-streams, waarbij de inhoud van een file als characters wordt geïnterpreteerd, en waarbij mogelijk meerdere bytes tot één character worden gecombineerd. Voor deze twee categorieën zijn er geen abstracte klassen, maar we doen toch maar even alsof: Beide soorten streams kun je lezen (waarbij je de inhoud van een al bestaande file kunt bekijken) of schrijven (waarbij een nieuwe file ontstaat). In het geval van byte-streams gebruik je respectievelijk een InputStream of een OutputStream, in het geval van character-streams heten de klassen respectievelijk Reader en Writer. Afwisselend lezen en schrijven van dezelfde file kan niet met streams; daarvoor zou je random-access nodig hebben. Tot voor kort (in talen als C en C++) werden bytes en characters zo ongeveer als synoniem beschouwd. Omdat Java de Unicode-characterset met veel meer dan 256 mogelijke symbolen gebruikt, is dat echter niet meer het geval. Bij het gebruik van character-streams worden de benodigde conversies automatisch uitgevoerd. De te gebruiken conversie kan daarbij zonodig worden opgegeven. Zo kun je bijvoorbeeld tekstfiles waarin de oude ibm/dos-codering wordt gebruikt toch gemakkelijk inlezen. Een andere conversie die automatisch wordt uitgevoerd, is de codering van het einde-regel-teken. Die is bij verschillende operating systems verschillend: Windows gebruikt twee bytes (13 en 10) aan het eind van elke regel; Apple gebruikt alleen de 13, en Unix gebruikt alleen de 10. Door character-stream-gebaseerde klassen te gebruiken voor het lezen van tekstfiles, kun je tekstbestanden toch moeiteloos uitwisselen. Byte-streams zijn daarentegen geschikt voor binair gecodeerde bestanden, waarbij je echt elke byte wilt kunnen gebruiken in het programma. Lezen en schrijven Heb je een InputStream beschikbaar, dan kun je de methode read aanroepen om één byte te lezen. Je zou verwachten dat die methode als resultaat een byte oplevert. Dat is echter niet het geval; de methode levert een int waarde op. De reden daarvoor is, dat het nu mogelijk is om te signaleren of het einde van de stream bereikt is: dan levert read namelijk de speciale waarde -1 op. Je kunt nu dus dingen schrijven als int n; n = inpstr.read(); if (n==-1) uitvoer.settext("er valt niets te lezen"); else uitvoer.settext("gelezen byte is " + n ); Hierin is inpstr de InputStream waaruit je wilt lezen. Hoe je die variabele kunt initialiseren bespreken we zodadelijk. Een tweede manier om te lezen is het aanroepen van read, waarbij je een array van bytes meegeeft waarin je het resultaat wilt hebben. Je kunt zo in één klap een heleboel bytes lezen. Het resultaat van de methode is het aantal gelezen bytes (de lengte van de array, of minder als er niet zoveel bytes meer beschikbaar waren). Schrijven naar een OutputStream gaat door aanroep van methode write, waarbij je naar keuze één byte of een hele array van bytes meegeeft.

194 194 Objectgeoriënteerd ontwerp Ook de klassen Reader en Writer kennen respectievelijk een methode read en write, maar hier zijn de parameters niet (arrays van) byte maar van char. Bij een Writer is er ook een versie van write beschikbaar met een String parameter; die string wordt dan netjes in characters opgedeeld en naar de Writer gestuurd. Constructie van streams: subklassen van InputStream Heb je eenmaal een stream gemaakt (hetzij een byte-stream in de vorm van een InputStream of een OutputStream, of een character-stream in de vorm van een Reader of Writer), dan kun je dus dingen lezen of schrijven. Maar hoe krijg je zo n stream in handen? Dat gebeurt niet door met new de constructor van een van deze vier klassen aan te roepen. In plaats daarvan moeten we kiezen voor één van de (vele) subklassen die er beschikbaar zijn. We bekijken daarom eerst de subklassen van InputStream wat nader (voor de andere drie is de onderverdeling net zoiets). Er zijn twee mogelijkheden om een InputStream te maken: een directe, en een indirecte, zogeheten gefilterde inputstream, een FilterInputStream. Om met de directe constructie te beginnen: ook hier zijn weer twee mogelijkheden: een FileInputStream of een ByteArrayInputStream. Een FileInputStream kun je gebruiken om uit een file te lezen. De gewenste File moet je meegeven als parameter van de constructor. Daarna kun je het object gebruiken om methode read aan te roepen; het is tenslotte een subklasse van InputStream. Bijvoorbeeld: int n; File f; FileInputStream fis; f = new File("hallo.txt"); fis = new FileInputStream(f); n = fis.read(); Voor het gemak is er ook een alternatieve constructor van FileInputStream beschikbaar, waarbij je niet een file-object maar een string met de naam van de file meegeeft. Daarmee spaar je uit dat je eerst een file-object moet creëren. Een hele andere manier om een InputStream te maken is het gebruik van de subklasse ByteArrayInputStream. Van zo n object kun je (natuurlijk!) ook de methode read aanroepen. De gegevens komen dan echter niet uit een echte file, maar worden in plaats daarvan geput uit de array van bytes die bij de constructie werd meegegeven. Je kunt hiermee als het ware een file in het geheugen simuleren, zonder dat de rest van het programma daar iets van hoeft te merken (daar roep je immers gewoon read aan, zonder je druk te maken waar de gegevens precies vandaan komen). Dan zijn er nog de indirecte manieren om een InputStream te maken. Ze zijn indirect omdat je eerst een andere InputStream moet maken (bijvoorbeeld een FileInputStream of een ByteArrayInputStream). Die geef je dan mee aan de constructor van de indirecte stream, bijvoorbeeld DataInputStream. Dat is dus een heel gedoe: File f; FileInputStream fis; DataInputStream dis; f = new File("hallo.dat"); fis = new FileInputStream(f); dis = new DataInputStream(fis); maar dan heb je ook wat: zo n DataInputStream kent namelijk een aantal extra methodes, waarmee je bijvoorbeeld double waarden (binair gecodeerd!) uit een file kunt lezen:

195 13.5 File input/output 195 double d; d = dis.readdouble(); Een BufferedInputStream is een andere indirecte InputStream. Ook hier moet je eerst een andere InputStream maken, en die meegeven bij de constructie. Heb je eenmaal zo n BufferedInputStream, dan zul je merken dat die efficiënter werkt dan bijvoorbeeld een gewone FileInputStream, zeker bij langzame netwerkconnecties. Een gebufferde stream pakt namelijk een grote hoeveelheid data tegelijk van het netwerk, bewaart die in een interne buffer, en verstrekt die vervolgens mondjesmaat bij elke aanroep van read. In de rest van het programma heb je er geen omkijken meer naar: hij werkt net zoals een ongebufferde stream in de zin dat je de methode read kunt aanroepen. Constructie van streams: subklassen van OutputStream De subklasse-hiërarchie onder OutputStream heeft dezelfde vorm als die onder InputStream, en bevat dus weinig verrassingen meer; zie het grote schema in figuur 27. Ook hier zijn er weer twee directe manieren om een stream te maken, en twee indirecte manieren. De subklasse PrintStream van FilterOutputStream heeft geen constructor-methode, en daar heb je dus weinig aan. Het is een historische vergissing, die we in sectie 13.6 zullen bespreken. Opvallend is dat je bij constructie van een ByteArrayOutputStream geen byte[] hoeft mee te geven bij constructie. De benodigde array wordt automatisch gemaakt, en groeit wanneer dat nodig is. Als je de array op een gegeven moment wilt bekijken, dan kun je hem opvragen door aanroep van de methode tobytearray. Constructie van streams: subklassen van Reader Character-streams zijn onderverdeeld in Reader-klassen (voor input) en Writer-klassen (voor output). Net als bij bytestreams kun je objecten van deze klassen niet direct creëren, maar moet je een keuze maken uit één van de subklassen. De hiërarchische ordening daarvan doet sterk denken aan die van byte-streams. Er zijn twee directe manieren om een character-stream te maken en twee indirect (waarbij je eerst een andere Reader moet maken). Ook nu kun je weer kiezen om te lezen uit een echte file, of uit een string in het geheugen. Om uit een file te kunnen lezen, moet je eerste een InputStream object maken (dat is dus een bytestream!). Helemaal direct gaat de constructie dus ook weer niet: File f; FileInputStream fis; InputStreamReader isr; f = new File("hallo.txt"); fis = new FileInputStream(f); isr = new InputStreamReader(fis); Uit de aldus geconstrueerde InputStreamReader kun je characters lezen, maar in de praktijk is het handiger om hele strings tegelijk te lezen. Om dat te kunnen doen, wordt het nog een stapje indirecter: BufferedReader br; br = new BufferedReader(isr); Zo n BufferedReader kent namelijk een methode om een hele regel tekst regelijk te lezen: String s; s = br.readline(); Je krijgt dan precies één tekstregel uit de file, waar de einde-regel-markering al van afgehaald is. Als er geen regels meer beschikbaar zijn, levert readline de speciale object-verwijzing null als resultaat. Constructie van streams: subklassen van Writer De subklasse-hiërarchie onder Writer is weer een variatie op hetzelfde thema. Een handige klasse is vooral PrintWriter. Objecten van dat type kennen een methode println, waarmee je strings kunt schrijven, maar ook waarden van alle standaardtypen, die daarbij automatisch naar String worden geconverteerd. Het is een beetje omslachtig om zo n PrintWriter-object te maken, maar de stappen zijn direct uit het schema af te lezen: bij de constructie heb je een andere Writer nodig, wat bijvoorbeeld een OutputStreamWriter kan zijn. Bij de constructie daarvan het je een OutputStream nodig, wat

196 196 Objectgeoriënteerd ontwerp bijvoorbeeld een FileOutputStream kan zijn. Bij de constructie daarvan heb je een File nodig, en bij de constructie daarvan een String. Je kunt dat overigens in één grote expressie opschrijven: PrintWriter pw; pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(new File("hallo.txt")))); pw.println("deze tekst komt in de file"); Convenience klassen Een veelvoorkomende klus als het lezen of schrijven van een tekstfile is al met al toch wel omslachtig. Daarom zijn er in package java.io een paar extra s toegevoegd die de logica van het bouwwerk enigszins ondermijnen, maar in de praktijk wel handig zijn. Sommige klassen hebben een alternatieve constructormethode, en er zijn zelfs bovendien een paar extra subklassen (zogenaamde convenience klassen ). In het grote schema in figuur 27 vind je deze terug: De constructor van een FileInputStream heeft een File-parameter, die op zijn beurt bij constructie een String-parameter (de filenaam) heeft. Je kunt echter ook de String direct meegeven aan de constructor van FileInputStream. Hetzelfde geldt voor FileOutputStream. De constructor van InputStreamReader heeft een InputStream als parameter. Vaak wordt daar een FileInputStream voor gebruikt, die een File-parameter heeft, die een Stringparameter heeft. Er is echter een subklasse FileReader van InputStreamReader die direct de File, of zelfs een String (de filenaam) als parameter heeft. Hetzelfde geldt voor de subklasse FileWriter van OutputStreamWriter. De constructor van PrintWriter heeft een Writer als parameter. Vaak wordt daar een OutputStreamWriter voor gebruikt, die op zijn beurt een OutputStream nodig heeft. Je kunt die OutputStream echter ook direct aan PrintWriter meegeven. Lezen uit een tekstfile kan daarom als volgt gedaan worden: String s; BufferedReader br; br = new BufferedReader(new FileReader("in.txt")); s = br.readline(); Schrijven naar een tekstfile gaat als volgt: PrintWriter pw; pw = new PrintWriter(new FileOutputStream("uit.txt")); pw.println("tekst"); blz. 197 Voorbeeld: tekst-editor In listing 56 staat het volledige programma voor de tekst-editor. De meeste ideeën zijn hierboven al besproken. Nog een paar details: er zijn twee interactie-componenten: een TextArea voor de eigenlijke tekst, en een Label waarop eventuele foutmeldingen kunnen worden getoond. de individuele menu-items worden niet in object-variabelen bewaard. Daardoor kunnen we in actionperformed ook niet de identiteit van e.getsource() testen tegen de verschillende items. In plaats daarvan achterhalen we met de methode getlabel het opschrift van het item dat de actie veroorzaakte; dat kunnen we vervolgens als string vergelijken met de mogelijkheden. als reactie op menukeuze Open wordt de methode open aangeroepen. Daar wordt de gebruiker gevraagd om een filenaam. Het eigenlijke leeswerk van de file gebeurt in een aparte methode lees. Zo is de communicatie met de gebruiker en die met het filesysteem mooi gescheiden. deze scheiding komt meteen goed van pas. We kunnen lees nu namelijk ook aanroepen vanuit main. In de methode main is er als parameter een array van strings beschikbaar, die de teksten bevat die de gebruiker op de commandoregel heeft ingetikt. Zo kunnen we meteen aan het begin al een file openen Non-window-programma s Console input/output Niet elk programma hoeft een window te maken om zijn resultaten in te tonen. Hoewel op PC s het gebruik van dos-programma s nauwelijks meer gangbaar is, is het vooral op Unix-computers

197 13.6 Non-window-programma s 197 import java.awt.*; import java.awt.event.*; import java.io.*; 5 class Editor extends Frame implements ActionListener, WindowListener { TextArea tekst; Label status; FileDialog opendial, savedial; 10 private Editor() { tekst = new TextArea(20,60); status = new Label(); opendial = new FileDialog(this, "Open File...", FileDialog.LOAD); 15 savedial = new FileDialog(this, "Save File...", FileDialog.SAVE); this.setsize(400,350); this.settitle("simpele teksteditor"); this.addwindowlistener(this); 20 this.add(borderlayout.center, tekst); this.add(borderlayout.south, status); this.maakmenu(); 25 private void maakmenu() { MenuBar bar; Menu menu; MenuItem item; 30 bar = new MenuBar(); menu = new Menu("File"); item = new MenuItem("Open"); menu.add(item); item.addactionlistener(this); item = new MenuItem("Save"); 35 menu.add(item); item.addactionlistener(this); menu.addseparator(); item = new MenuItem("Quit"); menu.add(item); item.addactionlistener(this); bar.add(menu); 40 menu = new Menu("Edit"); item = new MenuItem("Upper"); menu.add(item); item.addactionlistener(this); item = new MenuItem("Lower"); 45 menu.add(item); item.addactionlistener(this); bar.add(menu); this.setmenubar(bar); Listing 56: Editor/Editor.java, deel 1 van 3

198 198 Objectgeoriënteerd ontwerp 55 public void actionperformed(actionevent e) { MenuItem item; String keuze; item = (MenuItem) (e.getsource() ); keuze = item.getlabel(); if (keuze.equals("open")) 60 this.open(); else if (keuze.equals("save")) this.save(); else if (keuze.equals("quit")) System.exit(0); 65 else if (keuze.equals("upper")) this.vertaal(true); else if (keuze.equals("lower")) this.vertaal(false); 70 private void open() { String naam; opendial.show(); 75 naam = opendial.getfile(); if (naam!=null) this.lees(naam); 80 private void save() { String naam; savedial.show(); naam = savedial.getfile(); 85 if (naam!=null) this.schrijf(naam); private void vertaal(boolean upper) 90 { String inhoud; inhoud = tekst.gettext(); if (upper) inhoud = inhoud.touppercase(); else inhoud = inhoud.tolowercase(); tekst.settext(inhoud); 95 Listing 57: Editor/Editor.java, deel 2 van 3

199 13.6 Non-window-programma s 199 private void lees(string naam) { String regel; BufferedReader invoer; 100 try { invoer = new BufferedReader( new FileReader(naam) ); tekst.settext(""); regel = invoer.readline(); while (regel!= null) 105 { tekst.append(regel + "\n" ); regel = invoer.readline(); invoer.close(); 110 catch (FileNotFoundException fnf) { status.settext("file " + naam + " bestaat niet"); catch (IOException io) { status.settext("leesfout in " + naam ); 115 status.settext(naam + " ingelezen."); private void schrijf(string naam) 120 { PrintWriter uitvoer; try { uitvoer = new PrintWriter( new FileWriter(naam), true); uitvoer.print( tekst.gettext() ); uitvoer.close(); 125 catch (IOException io) { status.settext("schrijffout in " + naam ); 130 public void windowclosing (WindowEvent e) {System.exit(0); public void windowclosed (WindowEvent e) { public void windowopened (WindowEvent e) { public void windowiconified (WindowEvent e) { 135 public void windowdeiconified(windowevent e) { public void windowactivated (WindowEvent e) { public void windowdeactivated(windowevent e) { public static void main(string [] params) 140 { Editor e; e = new Editor(); e.setvisible(true); 145 if (params.length>0) e.lees(params[0]); Listing 58: Editor/Editor.java, deel 3 van 3

200 200 Objectgeoriënteerd ontwerp blz. 201 nog vrij gebruikelijk dat programma s tekstuitvoer genereren in hetzelfde window waarin ze (door het intikken van een commando) werden gestart. Dit soort programma s kunnen de muis niet gebruiken, en krijgen al hun input van de gebruiker dus vanaf het toetsenbord. De klasse System De klasse System mag in elk Java-programma gebruikt worden, zelfs zonder hem te importeren. In deze klasse zitten twee interessante variabelen, die nodig zijn bij console-programma s: System.in en System.out. Dit zijn variabelen met als type respectievelijk InputStream en PrintStream. De variabele System.in stelt de stroom van bytes voor die afkomstig zijn van het toetsenbord. Alle strings die je naar de stream System.out stuurt verschijnt als tekst op de console. De console-versie van een Hallo-programma is wel heel gemakkelijk te schrijven. Er zijn geen windows nodig, je hoeft dus geen objecten te creëren, en het is dus ook niet nodig om een subklasse van Frame te maken met een hergedefinieerde paint. In plaats daarvan kun je direct vanuit de methode main de tekst sturen naar het standaard aanwezige object System.out: import java.io.*; class Hallo { public static void main(string [] ps) { System.out.println("Hallo!"); Het object System.out mag in elk Java-programma gebruikt worden, ook in applets. In windowprogramma s kan het zijn diensten bewijzen bij het debuggen van het programma. In het geval van applets kun je de tekst zichtbaar maken door in de browser voor het menuitem Java console te kiezen. Voorbeeld: grep-programma Een voorbeeld van een typisch console-programma is het programma grep. Onder Unix is dit een standaard aanwezig programma, waarvan de naam betekent: get regular expression pattern. We gaan dit programma, althans een eenvoudige versie ervan, zelf even maken. Bij het starten van het programma moet op de commandoregel een tekst worden gespecificeerd (het zogenaamde patroon), en een of meer filenamen. Het programma zal nu alle regels uit de genoemde files tonen waarin het tekstpatroon voorkomt. Het patroon en de filenamen zijn in de methode main beschikbaar in de vorm van de string-arrayparameter: het patroon staat op de nulde positie in deze array, en de filenamen op de posities vanaf 1. Omdat eenzelfde soort actie op alle files moet worden uitgevoerd, is het het gemakkelijkst om een aparte methode te maken die het werk voor één file uitvoert. Die methode kan dan vanuit main voor alle files worden aangeroepen. In listing 59 heet deze methode bewerk. De methode kan (en moet zelfs) statisch zijn, omdat er geen window-object beschikbaar is. Het werk dat resteert in de methode main is controleren of de gebruiker wel voldoende strings heeft ingetikt. Zo niet, dan krijgt de gebruiker een korte uitleg van wat hij geacht wordt in te tikken. Toekenningen hebben een resultaat Toekenningen aan variabelen hebben we tot nu toe altijd als opdracht gebruikt. Maar toekenningen zijn ook bruikbaar als expressie. Terwijl deze expressie wordt uitgerekend krijgt de variabele zijn nieuwe waarde. Deze waarde is bovendien de resultaatwaarde van de expressie, en kan dus in een grotere expressie worden gebruikt. Een simpel voorbeeld van zo n toekenning-expressie is: int x, y; x = (y=0); De toekenning y=0 is hier als expressie gebruikt, die zelf aan de rechterkant van de toekenningsopdracht aan x staat. Met de toekenning y=0 krijgt y de waarde 0. Dit is bovendien de waarde van het geheel (y=0), en dus ook x krijgt de waarde 0. Voor dit geval is het niet zo n nuttige notatie, want we hadden net zo goed twee aparte toekenningen kunnen doen: x=0; y=0; Toekennings-expressies worden dan ook zelden gebruikt. Er is echter één situatie waarin ze goed

201 13.6 Non-window-programma s 201 import java.io.*; class Grep { 5 private static void bewerk(string patroon, String filenaam ) { BufferedReader br; InputStreamReader reader; String regel; try { if (filenaam.equals("")) reader = new InputStreamReader(System.in); else reader = new FileReader(filenaam); br = new BufferedReader(reader); for (int nr=1; (regel=br.readline())!= null; nr++) if (regel.indexof(patroon)!=-1) System.out.println( filenaam + " regel " + nr + ": " + regel ); 20 catch (Exception e) { System.out.println( filenaam + ": " + e ); 25 public static void main(string [] parameters) { switch (parameters.length) { 30 case 0: System.out.println("Usage: java Grep pattern [files]"); break; case 1: Grep.bewerk( parameters[0], "" ); break; default: for (int i=1; i<parameters.length; i++) 35 Grep.bewerk( parameters[0], parameters[i] ); Listing 59: Grep/Grep.java

202 202 Objectgeoriënteerd ontwerp van pas komen, en dat is bij het lezen van files. In het editor-programma schreven we de volgende code om alle regels van een file te lezen: String regel; regel = invoer.readline(); while (regel!=null) { tekst.append(regel); regel = invoer.readline(); De aanroep van de methode readline staat dus op twee plaatsen in het programma: eenmaal om de eerste regel te lezen; dat moet immers gebeuren alvorens de test regel!=null kan worden uitgevoerd. En eenmaal aan het eind van de body van de while-opdracht, om de volgende regel te lezen. Met gebruikmaking van een toekennings-expressie kan dit handiger: we kunnen in dezelfde expressie die test of de regel ongelijk null is, in het voorbijgaan de regel inlezen. De opdracht wordt dan: while ( (regel=invoer.readline())!= null) tekst.append(regel); Omdat er nu nog maar één opdracht in de body staat, kunnen zelfs de accolades vervallen. In het voorbeeldprogramma wordt bovendien met een teller bijgehouden wat het regelnummer is. In plaats van een while-opdracht kunnen we dus beter een for-opdracht gebruiken, in de header waarvan alles gecombineerd wordt: for (nr=1; (regel=invoer.readline())!=null; nr++) tekst.append(nr + ":" + regel); Lezen van System.in Het gebruik van System.in is iets gecompliceerder dan dat van System.out. Je kunt in System.in weliswaar gebruiken om losse bytes van het toetsenbord te lezen, maar in de praktijk komt het vaker voor dat je complete strings wilt lezen. Omdat strings uit characters bestaan, en niet uit bytes, moeten we de InputStream die System.in is transformeren tot een Reader. Dat gebeurt door System.in als parameter mee te geven bij constructie van een InputStreamReader object. Dat object gebruiken we vervolgens bij de constructie van een BufferedReader, zodat we niet alleen losse characters, maar ook hele strings kunnen lezen. Samengevat: InputStreamReader isr; BufferedReader br; String s; isr = new InputStreamReader(System.in); br = new BufferedReader(isr); s = br.readline(); Het is in console-programma s zoals grep gebruikelijk om, als er geen filenamen worden gespecificeerd, in plaats daarvan vanaf System.in te lezen. Daarom staat er in de methode bewerk een if-opdracht die beslist hoe de BufferedReader gemaakt wordt: met de InputStreamReader zoals hierboven beschreven, of met de FileReader die met een file is geassocieerd. Een historische vergissing: PrintStream Het object System.out is een byte-stream, of preciezer een OutputStream, of nog preciezer een PrintStream. Die klasse kent een methode println, waarmee een string kan worden vertoond. Dat is eigenlijk raar, want een string bestaat uit characters, en het converteren daarvan naar bytes is een taak van een character-stream, of preciezer een Writer, of nog preciezer een PrintWriter. In oudere versies van Java (versie 1.0) werd het onderscheid tussen byte- en character-streams echter nog niet gemaakt. Vanaf Java versie 1.2 is de constructor van PrintStream deprecated, dat wil zeggen dat wordt aangeraden hem niet meer te gebruiken omdat er betere alternatieven voorhanden zijn (namelijk PrintWriter). Inmiddels waren er echter al te veel programma s die veel gebruik maken van System.out, zodat het veranderen van het type daarvan in PrintWriter teveel problemen zou geven. We zitten dus nu al opgescheept met een erfenis van het (recente) verleden, die tot in lengte van dagen in de Java-library zichtbaar zal zijn. Als je System.out gebruikt om tijdens het debuggen van je programma even wat tekst op het

203 13.6 Non-window-programma s 203 scherm te schrijven, is het niet-ondersteunen van de Unicode-codering niet zo n probleem. Maar als je programma s voor internationaal gebruik wil schrijven, dan zou je de PrintStream eigenlijk moeten verpakken in een PrintWriter, als volgt: OutputStreamWriter osr; PrintWriter pw; osr = new OutputStreamWriter(System.out); pw = new PrintWriter(osr); pw.println(s); Dat is mooi symmetrisch als je het vergelijkt met het verpakken van System.in in een BufferedReader, zoals in de vorige paragraaf. Opgaven 13.1 Stream-hiërarchie De standaard packages zijn duidelijk door meerdere personen geschreven. Vergelijk: a. de onderlinge positie van gefilterde en gebufferde streams in het geval van byte- en characterstreams; b. de namen van de methodes waarmee een string kan worden gelezen en geschreven, en de klassen waarin deze methoden zich bevinden Implementatie stream-klassen We verplaatsen ons even in de rol van de schrijvers van het package java.io. Schrijf de volgende methoden en/of klassen, zonder gebruik te maken van de al bestaande exemplaren: a. de methode read in de klasse InputStream die een array van bytes inleest, daarbij gebruikmakend van de al bestaande methode read die één byte inleest. b. de klasse FileReader, met daarin een constructormethode en een herdefinitie van de methode read die één byte inleest c. de klasse BufferedInputStream, met daarin een constructormethode en een herdefinitie van de methode read die één byte inleest 13.3 Database application Maak een database-programma (application). Het programma heeft twee tekstvelden, die een naam en een telefoonnummer kunnen bevatten. De gegevens worden opgeslagen in een array (je mag aannemen dat er nooit meer dan 1000 ingangen zijn). Als de gebruiker tekst wijzigt en op Enter drukt, moeten de gegevens in de array veranderd worden. Verder is er een knop Nieuw ; als de gebruiker die indrukt, krijgt zhij een nieuwe, blanco, ingang te zien, die vervolgens veranderd kan worden. Tenslotte is er een scrollbar waarmee de gebruiker door de ingangen kan bladeren. Die moet zo zijn gemaakt, dat er precies mee door de aanwezige ingangen kan worden gebladerd. Dat betekent dat het bereik van de scrollbar moet worden aangepast als er een nieuwe ingang bij komt! (dat kan met een aanroep van setmaximum(n) ); Bij het starten van het programma moet het een file inlezen, waarvan de naam data.txt luidt, of nog mooier: waarvan de naam op de commandoregel wordt gespecificeerd. Die file bevat op elke regel een naam en een telefoonnummer, gescheiden door een # -teken. Die gegevens komen aan het begin in de array te staan. Op het moment dat de gebruiker de applicatie afsluit, moet op de valreep nog snel de (mogelijk gewijzigde) array worden weggeschreven naar de file Maak Index Schrijf een statische methode maakindex, met de volgende specificaties. De methode heeft als parameter een array met namen van files. De methode moet deze files lezen, en een nieuwe file schrijven met een samenvatting van die files. Deze file krijgt de naam index.txt. De samenvatting geeft van elke file: een vermelding van de naam van de file, zoals in: filenaam: aap.txt vermelding van het aantal regels, karakters en woorden, zoals in: 12 regels

204 204 Objectgeoriënteerd ontwerp 417 karakters 74 woorden Bij de karakters worden regel-overgangen niet meegerekend. Woorden zijn gescheiden door één of meer van de symbolen spatie, punt, komma of puntkomma. Aan het eind van de regel is een woord ook altijd afgelopen. vermelding van het langste woord in de file, zoals in: langste woord: primatendagverblijf Aan het eind van de index wordt tenslotte de naam van de moeilijkste tekst vermeld, zoals in: moeilijkste tekst: noot.txt Een tekst is moeilijker dan een andere tekst, als de woorden gemiddeld langer zijn. Als er problemen zijn bij het lezen van een file, dan moet op de console-output worden gemeld dat er problemen zijn met die file, zoals in: probleem bij lezen van file mies.txt Die file mag dan verder worden genegeerd, en hoeft niet in de index te komen.

205 205 Hoofdstuk 14 Algoritmen In het voorbeeldprogramma Bitmap-editor in sectie 12.5 lag de nadruk sterk op de opbouw van de gebruikersinterface en de opzet van een programma met een frame, een canvas en een model. Het eigenlijke werk waar het allemaal om begonnen is (het opschuiven, bold maken enz. van een bitmap) is uiteindelijk erg eenvoudig. In het Schets-programma in sectie 13.4 gebeurt inhoudelijk zo mogelijk nog minder: uiteindelijk tekent het programma alleen maar lijntjes en rechthoeken... Wat het programma ingewikkeld maakt is de zeer strikte opzet volgens het once and only once principe. Het komt natuurlijk ook wel voor dat het werk dat het programma moet doen minder triviaal is. In dit hoofdstuk geven we daarvan twee voorbeelden: een programma dat een route zoekt door een netwerk, en een programma dat de taal waarin een tekst is geschreven probeert te herkennen aan de hand van een aantal voorbeeld-teksten. Het gaat in deze programma s dus vooral om de algoritmen of berekeningsmethoden; we laten de GUI opzettelijk eenvoudig. Wel maken we natuurlijk dankbaar gebruik van alle in eerdere hoofdstukken besproken Java-klassen, met name voor collections en files Toepassing: een zoekend programma Beschrijving van de casus Het programma in deze sectie heet Net. Het maakt het mogelijk dat de gebruiker op een landkaart met wegen (of spoorlijnen, of telecommunicatieverbindingen) de snelste (of goedkoopste of anderszins beste) route tussen twee plaatsen kan vinden. De plaatsen van het netwerk en de verbindingen daartussen leest het programma uit een file. Daarna wordt aan de gebruiker een grafische presentatie hiervan aangeboden.de gebruiker mag vervolgens twee plaatsen aanklikken, waarop de beste route tussen die plaatsen in een andere kleur wordt weergegeven. Daarna mag de gebruiker opnieuw twee plaatsen aanklikken. In figuur 28 is het programma in werking te zien. Als netwerk zijn de belangrijkste spoorwegverbindingen in Nederland ingelezen. Zojuist is de beste route van Den Haag naar Almelo gevonden, en heeft de gebruiker Nijmegen alweer aangeklikt voor een nieuwe zoekopdracht. De modellering van het probleem is tamelijk ingewikkeld. Zowel het opdelen van het programma in klassen, als het ontwerp van sommige methoden vergen zorgvuldige planning. Opdeling in klassen Omdat dit een interactief programma is, hebben we in ieder geval een subklasse van Frame nodig, die bovendien als MouseListener fungeert. Hiervoor zullen we een klasse Net maken, waarin ook de methode main een plaats kan vinden. In deze klasse zullen we alleen de interactie met de gebruiker te beschrijven. Voor het eigenlijke model van het netwerk maken we een aparte klasse Netwerk. Op dezelfde manier als de klasse Calc een object-variabele van het type Proc had, waarin de toestand van de rekenmachine was vastgelegd, zal Net een object-variabele van het type Netwerk krijgen. Alle dingen die te maken hebben met frames, muis-activiteit en dergelijke horen thuis in Net; de opbouw van het netwerk uit steden en wegen, alsmede het zoekproces worden gemodelleerd in de klasse Netwerk. Belangrijke onderdelen van een netwerk zijn de steden daarin en de verbindingswegen tussen de steden. Daarom maken we aparte klassen Stad en Weg die objecten van dat type modelleren. Bij het zoeken is verder sprake van een route door het netwerk. Om objecten die zo n route door het netwerk voorstellen te modelleren, maken we een aparte klasse Pad.

206 206 Algoritmen Figuur 28: Het programma Net in werking

207 14.1 Toepassing: een zoekend programma 207 Klasse Net: De grafische interface De muiskliks van de gebruiker kunnen worden opgevangen door implementatie van de methode mousepressed, en de grafische output kan getekend worden in de hergedefinieerde methode paint. Maar welke object-variabelen zijn er nodig? Zoals hierboven al werd opgemerkt is er een Netwerk-object nodig, waarin het netwerk ligt opgeslagen. Verder zijn er objecten nodig waarmee de inhoud van het window op verzoek getekend kan worden. Daarvoor is nodig: Een tekening van het netwerk. Met deze klus kunnen we de Netwerk object-variabele opzadelen, door daarin een methode te maken die zichzelf tekent op een als parameter meegegeven Graphics-object. Een tekening van de route in een andere kleur. Er is dus een Pad-object nodig, die ook in staat moet zijn om zichzelf te tekenen. Opmerkelijk aan de methode mousepressed is dat de eerste en de tweede klik verschillend behandeld moeten worden. De eerste klik moet alleen maar bewaard worden: oh, dit is blijkbaar de stad waar de route moet beginnen. Pas na de tweede klik kan het zoekproces beginnen. Er is dus een object-variabele nodig waarin de begin-stad bewaard kan worden. Aan het wel of niet geïnitialiseerd zijn van deze variabele kan mousepressed ook zien of het de tweede of de eerste klik betreft. Een eerste opzet van de klasse Net is al met al als volgt: class Net extends Frame { Netwerk netwerk; // modellering van het netwerk Pad pad; // de gevonden route door het netwerk Stad stad1; // eerste aangeklikte stad Net() {... // initialisatie paint(g) {... // laat netwerk en pad zichzelf tekenen mousepressed() {... // eerste keer: bewaar stad1; tweede keer: zoek pad main() {... // creeer een Net en setvisible Klasse Netwerk: de modellering van een netwerk Een netwerk bestaat uit een aantal steden en wegen daartussen. Een voor de hand liggende keuze voor de objectvariabelen van een netwerk is een collectie van Stad-objecten en een collectie van Weg-objecten. Als we er echter voor zorgen dat elke stad zijn eigen uitgaande wegen bewaart, hoeven de wegen niet eens apart in het netwerk bewaard te worden, en bestaat een netwerk dus alleen maar uit een collectie van steden. Een van de methoden van Netwerk hebben we al beloofd: een netwerk-object moet zichzelf kunnen tekenen op een mee te geven Graphics-object. Ook moet een netwerk zichzelf kunnen opbouwen met de informatie uit een file. Verder komt er natuurlijk een methode voor de centrale vraag: het vinden van een pad tussen twee steden. Tijdens het opbouwen van het netwerk is het handig om aparte methoden te maken die een enkele stad, respectievelijk een enkele weg aan het netwerk toevoegen. De gebruiker klikt punten aan, geen steden. Er zal dus een vertalingsmethode moeten komen die aangeeft of er een stad ligt op de aangegeven plaats, en zo ja, welke. Dit is ook een mooie taak voor het netwerk. Het ontwerp voor de klasse Netwerk is hiermee als volgt: class Netwerk { Collection steden; // object-variabele Netwerk() {... // initialisatie void bouwstad(...) {... void bouwweg(...) {... Stad vindstad(int,int) {... // ligt er een stad op dit punt? Pad zoekpad(stad,stad) {... // zoek route tussen twee steden void lees(string) {... // bouw netwerk volgens data in file void teken(graphics) {... // teken jezelf Klassen Stad en Weg: de onderdelen van het netwerk Een stad heeft een naam en een positie op de kaart. Verder hadden we beloofd bij een stad de uitgaande wegen op te slaan. Het is handig als een stad zichzelf op een Graphics-object kan

208 208 Algoritmen Figuur 29: Twee mogelijke representaties van een Pad tekenen, zodat Netwerk dat werk kan delegeren. Omdat een stad zijn uitgaande wegen beheert, moet er ook een methode zijn om een nieuwe weg te bouwen. Het ontwerp van de klasse Stad is dus op z n minst: class Stad { String naam; int x,y; Collection wegen; Stad(...) {... void bouwweg(...) {... void teken(graphics) {... Als we straks naar de goedkoopste route tussen twee steden willen gaan zoeken, dan moet het wel bekend zijn wat het gebruik van een weg tussen twee steden kost. Verder is het van een weg van belang waar hij naartoe loopt. Een weg wordt dus gemodelleerd door: class Weg { Stad doel; int kosten; Weg(...) {... void teken(graphics) {... Klasse Pad: een route door het netwerk Een pad lijkt op het eerste gezicht te bestaan uit een aantal aan elkaar gekoppelde wegen, bijvoorbeeld opgeslagen in een collection. Het zal echter handiger blijken om een pad te representeren door de steden waar het pad langs loopt. Een pad zou dus een collection van steden kunnen zijn. Maar een andere leuke representatie is (zie ook figuur 29): class Pad { // Collection steden // vervangen door een eigen implementatie: Stad hier; Pad int rest; kosten; Pad(...) {... boolean bevat(stad) {... void teken(graphics) {... Een pad wordt dus voorgesteld door de stad waar hij begint, en daarbij weer een pad-object waarin

209 14.2 Het zoekalgoritme 209 de rest van het pad is opgeslagen. Daarin bevindt zich dan de volgende stad, en opnieuw een padobject waarin het dan nog resterende deel van het pad ligt. In de laatste schakel van het pad laten we eenvoudigweg de object-variabele rest ongeïnitialiseerd. Behalve een constructor- en een teken-methode is er een methode waarmee we kunnen kijken of een bepaalde stad op het pad ligt Het zoekalgoritme De zoekmethode Het zoeken van een pad tussen twee steden is een lastige aangelegenheid. De essentie van deze methode is dat we een verzameling van veelbelovende, maar nog niet complete paden gaan aanleggen. Die paden gaan we vervolgens proberen uit te breiden tot paden die het doel nog beter benaderen. Dat alles net zo lang totdat we een pad tegenkomen dat op de gewenste bestemming eindigt. De verzameling paden kan worden geïnitialiseerd met een zeer kort pad: het pad met lengte 0 dat in de begin-stad begint én eindigt. Dat is weliswaar nog geen geweldig goed pad, maar door het maar gestaag uit te breiden komen we vanzelf dichter bij het doel. Een eerste opzet van de methode zoekpad is: Pad zoekpad(stad van, Stad naar) { paden.push(beginpad); // voeg een pad van van naar van toe aan de paden; while (...) // zolang er nog veelbelovende paden zijn { pad =...; // pak een pad uit de verzameling if (pad.hier==naar) // eindigt het pad in de bestemming? return pad; // gevonden! for (...) // voor alle hier vertrekkende wegen paden.push(...); // voeg een pad toe aan de verzameling return...; // helaas, niets gevonden Zoeken: niet in een kringetje lopen Door de paden aldoor maar uit te breiden lopen we het gevaar dat een pad weer uitkomt bij het vertrekpunt, zonder dat de bestemming is bereikt. Op die manier blijf je steeds in kringetjes lopen, zonder ooit op de bestemming te geraken. We moeten daarom in de methode een nieuw pad alleen aan de verzameling toevoegen als er geen lus in zit, dat wil zeggen als de nieuwe stad nog niet op het pad voorkomt. De methode bevat in de klasse Pad komt daarbij goed van pas. Zoeken: niet de eerste, maar de beste Bovenstaande opzet van de methode stopt direct met zoeken zodra er een pad gevonden is. Dat wil nog niet zeggen dat dit ook het beste pad is dat er bestaat. Daarvoor moeten we doorzoeken, in de hoop nog een beter pad te vinden. We kunnen hiertoe het algoritme combineren met het patroon zoek de grootste. We krijgen dan: Pad zoekpad(stad van, Stad naar) { paden.push(beginpad); beste = niets; // voorlopig nog niets gevonden while (...) { pad =...; if (pad.hier==naar) { if (...) // nieuwe oplossing beter dan de beste? beste = pad; // dan is dit de nieuwe beste for (...) if (...) // geen kringetje? paden.push(...); return beste; // retourneer de beste, of niets Sneller zoeken: geen hopeloze gevallen Als we eenmaal een oplossing hebben gevonden, blijven we nu dus verder zoeken in de hoop een betere oplossing te vinden. Maar het heeft natuurlijk geen zin om nog niet-complete oplossingen

210 210 Algoritmen blz. 214 blz. 215 blz. 215 blz. 211 blz. 212 blz. 213 blz. 213 te blijven exploreren die nu al duurder zijn dan een eerder gevonden oplossingen. Die kunnen dus, net als de circulaire paden, van het zoekproces worden uitgesloten. Sneller zoeken: veelbelovendste eerst Door de truc hierboven gaat het zoeken dus aanzienlijk sneller zodra er een eerste oplossing gevonden is. Het is dus zaak zo snel mogelijk alvast een oplossing te vinden. Een manier om dit te bespoedigen is om als eerste te zoeken in een richting die veelbelovend lijkt. We kennen tenslotte de coördinaten van de steden, en als de road prizing een beetje logisch is opgebouwd dan zijn wegen die de verkeerde kant op gaan een goede manier om de reis duur te maken... Wil je van Utrecht naar Groningen reizen, dan kun je het beste eerst Amersfoort of desnoods Hilversum proberen, en niet Geldermalsen! Dit gaat helaas niet altijd goed. Van Hoorn naar Leeuwarden reis je niet via Enkhuizen, hoewel dat op het eerste gezicht de goede kant op lijkt te gaan. Maar geen nood: kwaad kan het ook niet om de wegen in een bepaalde volgorde te proberen. Tot nu toe hadden we ze tenslotte in een willekeurige volgorde geprobeerd. (Een poging tot versnelling van een algoritme die in veel gevallen, maar niet altijd, goed werkt, maar verder ook geen kwaad kan, heet een heuristiek ). Klasse Stack: een stapel objecten In de package java.util staat nog een bijzondere implementatie van List: de klasse Stack. Deze klasse modelleert een stapel objecten, waarbovenop nieuwe elementen kunnen worden toegevoegd, en waar elementen alleen maar van bovenop de stapel kunnen worden teruggepakt (slecht idee om je post op deze manier te behandelen). Deze klasse kent een paar extramethoden, waaronder: boolean empty() kijkt of de stapel leeg is void push(object x) voegt een object toe bovenop de stapel Object pop() verwijdert het object bovenop de stapel en levert dat op Uitwerking van het programma Het routezoek-programma staat in de hiernavolgende klassen. Netwerk, met drie taken: het opbouwen van het netwerk (zie listing 64); het zoekalgoritme (zie listing 65); het inlezen van een netwerk vanuit een file, en het tekenen van een netwerk op een Graphics-object (zie listing 65). NetApplet, voor de grafische userinterface (zie listing 60); Stad, voor de modellering van een stad (zie listing 61); Weg, voor de modellering van een weg tussen twee aangrenzende steden (zie listing 62); Pad, voor de modellering van een route tussen twee steden (zie listing 63). Net, een applicatie die een applet opstart Toepassing: automatische taalherkenning Beschrijving van de casus Het programma in deze toepassing heet taal, omdat het van een ingetikte tekst kan herkennen in welke taal het geschreven is. Om zo n programma te kunnen schrijven moeten we ons eerst afvragen: hoe doe je dat eigenlijk, talen herkennen? Bekijk eens het onderstaande couplet uit het gedicht Jabberwocky van Lewis Carroll, en drie vertalingen daarvan: Twas brillig, and the slithy toves Did gyre and gimble in the wabe: All mimsy were the borogoves, And the mome raths outgrabe. Es brillig war. Die schlichten Toven Wirrten und wimmelten in Waben; Und aller-mümsigen Burggroven Die mohmen Räth ausgraben. Il brigue: les tôves libricilleux Se gyrent en vrillant dans la guave. Enmîmés sont les goubebosqueux Et le mômerade horsgrave. t Wier bradig en de slijp le torfs driltolden op de wijde weep. Misbrozig stonden borogorfs t verdwoolde grasvark schreep. De woorden zijn in alle vertalingen net zo onzinnig als in het origineel. Toch zie je direct om welke taal het gaat. Dat kan blijkbaar zonder de inhoud te begrijpen, dus puur op grond van de vorm van de tekst.

211 14.3 Toepassing: automatische taalherkenning 211 import java.awt.*; import java.awt.event.*; import java.applet.applet; 5 public class NetApplet extends Applet implements MouseListener { Netwerk netwerk; Pad pad; Stad stad1; 10 public void init() { this.addmouselistener(this); netwerk = new Netwerk(); if (isdeelvanapplicatie) 15 netwerk.lees("spoor.txt"); else netwerk.leesurl( this.getdocumentbase(), this.getparameter("netwerk") ); public void paint(graphics g) 20 { netwerk.teken(g); g.setcolor( Color.red ); if (pad!=null) pad.teken(g); g.setcolor( Color.blue ); if (stad1!=null) stad1.teken(g); 25 public void mousepressed (MouseEvent e) { Stad s = netwerk.vindstad(e.getx(), e.gety()); if (s!=null) 30 { if (stad1==null) stad1 = s; else { pad = netwerk.zoekpad(stad1, s, true); stad1 = null; 35 repaint(); 40 public void mouseentered (MouseEvent e) { public void mouseexited (MouseEvent e) { public void mousereleased(mouseevent e) { public void mouseclicked (MouseEvent e) { 45 boolean isdeelvanapplicatie; public NetApplet() { this(false); NetApplet(boolean app) { this.isdeelvanapplicatie = app; if (app) this.init(); Listing 60: Net/NetApplet.java

212 212 Algoritmen import java.util.*; import java.awt.graphics; 5 { class Stad implements Comparator String naam; Collection wegen; int x, y; 10 public Stad(String s, int ax, int ay) { naam = new String(s); wegen = new LinkedList(); x = ax; y = ay; public void bouwweg(stad doel, int kosten) { wegen.add( new Weg(doel, kosten) ); public void teken(graphics g) { g.fillrect( x-5, y-5, 10, 10 ); g.drawstring(naam, x+6, y ); // orden twee wegen in *dalende* volgorde // wat betreft de afstand van hun doel tot deze Stad public int compare(object a, Object b) { return afstand(((weg)b).doel) - afstand(((weg)a).doel); 30 int afstand(stad a) { return afstand(a, this); 35 static int afstand(stad a, Stad b) { return kwadraat(a.x-b.x) + kwadraat(a.y-b.y); 40 static int kwadraat(int x) { return x*x; Listing 61: Net/Stad.java

213 14.3 Toepassing: automatische taalherkenning 213 import java.awt.graphics; class Weg { 5 Stad doel; int kosten; public Weg(Stad s, int k) { doel = s; 10 kosten = k; public void teken(graphics g, Stad s) { g.drawline( s.x, s.y, doel.x, doel.y ); 15 Listing 62: Net/Weg.java import java.awt.graphics; class Pad { 5 Stad hier; Pad rest; int kosten; public Pad( Stad s, Pad r, int k) 10 { hier = s; rest = r; kosten = k; if (rest!=null) kosten += rest.kosten; 15 public boolean bevat(stad s) { if (hier==s) return true; if (rest==null) return false; return rest.bevat(s); 20 public void teken(graphics g) { hier.teken(g); if (rest!=null) 25 { g.drawline( hier.x, hier.y, rest.hier.x, rest.hier.y ); rest.teken(g); Listing 63: Net/Pad.java

214 214 Algoritmen 5 import java.util.*; import java.io.*; import java.net.*; import java.awt.graphics; class Netwerk { Collection steden; 10 public Netwerk() { steden = new LinkedList(); public void bouwstad(string naam, int x, int y) 15 { steden.add( new Stad(naam, x, y) ); public void bouwweg(string naam1, String naam2, int prijs) { Stad st1 = vindstad(naam1); 20 Stad st2 = vindstad(naam2); st1.bouwweg(st2, prijs); st2.bouwweg(st1, prijs); 25 public Stad vindstad(string naam) { for (Iterator iter=steden.iterator(); iter.hasnext(); ) { Stad st = (Stad)iter.next(); if (st.naam.equals(naam)) return st; 30 return null; public Stad vindstad(int x, int y) 35 { for (Iterator iter=steden.iterator(); iter.hasnext(); ) { Stad st = (Stad)iter.next(); if ( Math.abs(x-st.x)<5 && Math.abs(y-st.y)<5 ) return st; 40 return null; Listing 64: Net/Netwerk.java, deel 1 van 3

215 14.3 Toepassing: automatische taalherkenning 215 public Pad zoekpad(stad van, Stad naar, boolean zoekslim) { Stack paden = new Stack(); 45 Pad beste = null; paden.push( new Pad(van,null,0) ); while(!paden.empty() ) 50 { Pad pad = (Pad) paden.pop(); if (pad.hier==naar) { if (beste==null pad.kosten<beste.kosten) beste = pad; 55 Collection wegen; if (zoekslim) { // sorteer de wegen op grond van afstand tot "naar" 60 wegen = new TreeSet( naar ); wegen.addall(pad.hier.wegen); else wegen = pad.hier.wegen; 65 for (Iterator iter=wegen.iterator(); iter.hasnext(); ) { Weg weg = (Weg) iter.next(); if (!pad.bevat(weg.doel) && (beste==null pad.kosten+weg.kosten<=beste.kosten) ) paden.push( new Pad(weg.doel, pad, weg.kosten) ); 70 return beste; Listing 65: Net/Netwerk.java, deel 2 van 3

216 216 Algoritmen 75 public void lees(string filenaam) { try { this.lees(new BufferedReader(new FileReader(filenaam))); catch (Exception e) { 80 public void leesurl(url base, String naam) { try { BufferedReader br = new BufferedReader(new InputStreamReader(new URL(base, naam) 85. openconnection(). getinputstream() )); this.lees(br); catch (Exception e) {System.out.println(""+e); 90 private void lees(bufferedreader regels) throws Exception { String regel; while ( (regel=regels.readline())!= null) 95 { StringTokenizer st = new StringTokenizer(regel, " "); if (st.hasmoretokens()) { String command = st.nexttoken(); if (command.equals("stad")) { String naam = st.nexttoken(); 100 int x = Integer.valueOf(st.nextToken()).intValue(); int y = Integer.valueOf(st.nextToken()).intValue(); bouwstad( naam, x, y ); else if (command.equals("weg")) 105 { String van = st.nexttoken(); String tot = st.nexttoken(); int kosten = Integer.valueOf(st.nextToken()).intValue(); bouwweg( van, tot, kosten ); 110 public void teken(graphics g) 115 { for (Iterator iter=steden.iterator(); iter.hasnext(); ) { Stad stad = (Stad)iter.next(); stad.teken(g); for (Iterator iter2=stad.wegen.iterator(); iter2.hasnext(); ) { Weg w = (Weg) iter2.next(); 120 w.teken(g, stad); Listing 66: Net/Netwerk.java, deel 3 van 3

217 14.3 Toepassing: automatische taalherkenning 217 Een aspect van de vorm is de frequentie van de verschillende letters. Ook als je geen Albanees en Fins kent kun je een Albanese tekst herkennen aan de vele q s en ë s, en een Finse tekst aan de vele u s en andere klinkers. Op deze manier moet het programma ook werken: op grond van de letterfrequentie wordt de beslissing genomen in welke taal de aangeboden tekst waarschijnlijk geschreven is. Natuurlijk kent het programma de talen niet echt, zoals wij Nederlands of Engels kennen. Maar wat doet dat er toe, als hij ze op deze manier wel feilloos kan onderscheiden? Of zou je toch kunnen zeggen dat een computer zo n taal dan kent? Modellering We kunnen natuurlijk de kennis over diverse talen in het programma inbouwen. Het programma wordt daar behoorlijk lang van; met veel if-opdrachten moeten de kenmerken van de verschillende talen worden onderscheiden. Bovendien moeten we van tevoren weten welke talen de gebruiker zoal wil onderscheiden, en voor al deze talen een letterfrequentie-analyse plegen. Omdat het programma toch al de letterfrequentie voor de aangeboden tekst moet kunnen bepalen, kunnen we het programma met gebruikmaking van dezelfde methoden ook de letterfrequenties in de diverse talen laten uitzoeken. Daarvoor is het nodig dat we van de te herkennen talen een representatieve voorbeeldtekst aanbieden. Op grond daarvan kan het programma een beeld vormen van de letterfrequenties in de verschillende talen. In het programma zelf zit dus geen kennis over talen ingebouwd; het programma leert door het bekijken van voorbeelden. Een voordeel hiervan is dat de gebruiker zelf kan beslissen welke talen er herkend moeten kunnen worden (op voorwaarde dat er een voorbeeldtekst beschikbaar is). In het programma zijn tabellen nodig die voor elke letter uit het alfabet aangeven hoe vaak hij voorkomt. We beperken ons tot letters; leestekens en andere symbolen worden niet in de analyse meegenomen. Hoofdletters en kleine letters worden niet onderscheiden; dat zou maar afleiden van de hoofdzaak: de letterfrequentie. Voor het gemak beperken we ons tot het westerse 26-letter alfabet (het programma kan desgewenst aangepast worden om ook andere letters mee te tellen). Verschil van frequentietabellen We moeten een manier bedenken waarmee we kunnen beslissen wanneer een frequentietabel meer op een bepaalde tabel lijkt dan op een andere. Daarvoor moeten we een maat hebben voor het verschil van twee tabellen: hoe groter de waarde daarvan, hoe minder de tabellen op elkaar lijken. Een tabel lijkt dan het meeste op de tabel waarmee het verschil het kleinste is. Er zijn verschillende mogelijkheden voor de definitie van zo n maat. We moeten een aantal arbitraire beslissingen nemen. Misschien kan het programma later nog verbeterd worden door deze keuzes aan te passen of te verfijnen. De frequentie van alle letters draagt bij in het totaal. We bepalen daarom het verschil van alle 26 individuele tellers, en totaliseren dat. Eventueel zouden sommige letters zwaarder meegewogen kunnen worden dan andere. Het is echter niet meteen duidelijk welke letters dat zouden moeten zijn, dus daarom wegen we alle letters voorlopig maar even zwaar. In een langere tekst komen letters natuurlijk vaker voor dan in kortere teksten. Omdat de lengte van de tekst niet van invloed mag zijn op de keuze van de taal, vergelijken we de relatieve letterfrequenties ten opzichte van het totaal. Zowel een te kleine als een te grote letterfrequentie moet bijdragen aan een groter verschil. Het mag niet zo zijn dat een tekort aan a s een teveel aan b s opheft. Daarom gebruiken we de absolute waarde van het (relatieve) verschil van de tellers. Het kwadraat van het verschil had ook gekund. Hier is nog ruimte om te experimenteren. Als maat voor het verschil van twee tabellen nemen we dus de som van de absolute waardes van de verschillen tussen de relatieve letterfrequenties. Om op een mooi getal tussen 0 en 1 uit te komen delen we het geheel nog door 26. Gebruikersinterface Op de gebruikersinterface is ruimte om de te determineren tekst in te tikken. Verder moet de gebruiker een aantal voorbeeldtalen kunnen aangeven. Daarvan wordt steeds de naam opgegeven, en een representatieve voorbeeldtekst. Die voorbeeldtekst wordt gegeven in de vorm van een url

218 218 Algoritmen Figuur 30: Het programma Taalherkenning in werking op het www. Er zijn genoeg buitenlandse sites waarvandaan we gemakkelijk voorbeeldteksten kunnen ophalen. Verder is er een knop aanwezig, waarmee de gebruiker het herken-proces kan starten. Tenslotte is er een Label waarop het resultaat getoond wordt. De vijf windrichtingen van de BorderLayout komen precies van pas om deze componenten onder te brengen. De taal-namen en -urls worden ondergebracht in een aparte Panels, die ieder een GridLayout hebben. De interface is afgebeeld in figuur 30. Opbouw van de klassen Een frequentietabel is in dit programma duidelijk een belangrijk object. Nu hadden we in sectie 11.3 al een klasse TurfTab gemaakt, waarmee de frequentie van afzonderlijke letters geturfd kan worden. Die klasse komt hier ook weer goed van pas. Er zat in de klasse echter nog geen methode waarmee de relatieve frequentie van de letters bepaald kon worden, en er was ook nog geen methode om de invoer van het WWW te lezen. We gaan daarom een subklasse RelTurfTab maken van TurfTab, waarin de benodigde extra methoden worden toegevoegd. In een andere klasse, genaamd Taal, bouwen we de userinterface op. In de methode actionperformed daarvan wordt het vergelijken van de frequentietabellen gestuurd. Algoritme zoek de kleinste Hart van het programma is de methode actionperformed. Daarin maken we om te beginnen een frequentietabel van de onbekende tekst, die uit het tekstveld wordt geplukt: onbekend = new RelTurfTab(); onbekend.turf( tekst.gettext() ); Die moet vergeleken worden met alle voorbeeldteksten. In een for-opdracht pakken we daarom één voor één de URL van zo n voorbeeldtekst, en construeren daarmee een nieuwe frequentietabel: for (int i=0; i<aantal; i++) { naam = url[i].gettext(); voorbeeld = new RelTurfTab(); voorbeeld.leesurl(naam); De frequentietabel van de onbekende tekst wordt nu vergeleken met die van de onderhavige voorbeeldtekst. Het resultaat is een verschilwaarde:

219 14.3 Toepassing: automatische taalherkenning 219 verschil = onbekend.verschil(voorbeeld); Is die verschilwaarde kleiner dan wat we tot nu toe dachten dat de kleinste was, dan hebben we een nieuwe waarde voor kleinste gevonden. Bovendien onthouden we in dat geval de naam van de bijbehorende taal: if (verschil<kleinste) { kleinste = verschil; antwoord = taal[i].gettext(); Na afloop van de for-opdracht kunnen we het antwoord aan de gebruiker tonen: uitvoer.settext("best passende taal is " + antwoord ); Natuurlijk moeten de variabelen kleinste en antwoord worden geïnitialiseerd. Dat gebeurt helemaal aan het begin met: kleinste = 1.0; antwoord = "niet te bepalen"; De waarde 1.0 is de groter dan het grootst mogelijke verschil tussen frequentietabellen. Dat kan dus alleen maar kleiner worden. De beginwaarde van antwoord is een relevante tekst voor het geval alle teksten onverhoopt onleesbaar blijken (bijvoorbeeld als er geen internet-verbinding is). Vergelijk relatieve frequenties De methode verschil in de klasse RelTurfTab moet het verschil van twee frequentietabellen berekenen, op de in de vorige sectie besproken manier. De methode heeft één van deze twee tabellen onder handen (aanspreekbaar met de naam this). De tweede tabel geven we als parameter van de methode mee (met de naam andere). De methode verloopt precies zoals besproken: public double verschil(relturftab andere) { double totaal = 0.0; for (int i=0; i<26; i++) totaal += Math.abs( this.relatief(i) - andere.relatief(i) ); return totaal/26; De hierin benodigde methode relatief maakt gebruik van de van TurfTab geërfde variabelen tellers en totaal: double relatief(int i) { return (double)tellers[i] / totaal; Initialisaties Voor het aantal talen dat de gebruiker als voorbeeld kan invoeren is in het programma gekozen voor 10. Het had echter net zo goed iets meer of minder kunnen zijn. Toch is die waarde 10 steeds nodig: bij het creëren van de array, als bovengrens van de for-teller, enzovoort. Om het programma later eventueel te veranderen met een groter of kleiner aantal, is consequent de variabele aantal gebruikt, in plaats van de constante 10. Om er zeker van te zijn dat deze variabele niet (per ongeluk) veranderd wordt, is bij de declaratie aangegeven dat de waarde final is, dat wil zeggen: niet veranderd mag worden. Daarom ook moet de waarde direct bij declaratie al toegekend worden. Om de gebruiker niet op te zadelen met de vervelende taak elke keer weer de voorbeeldtalen in te tikken, zet het programma alvast een aantal voorbeelden neer. In het programma zijn deze talen met bijbehorende urls in een array neergezet, zodat ze in de for-opdracht waar de tekstvelden worden gecreëerd, meteen van een opschrift kunnen worden voorzien. Deze array wordt bij declaratie meteen van een waarde voorzien. String [] defaultwaarden = { "Nederlands", " "Engels", " "Duits", "

220 220 Algoritmen, "Frans", " "Spaans", " ; blz. 221 Exceptions niet afhandelen In de methode leesurl worden allerlei handelingen met files verricht, zonder dat deze opdrachten in een try-catch-opdracht staan. Normaal gesproken is dat niet toegestaan, maar hier wordt in de methode-header aangekondigd dat de methode niet z n eigen exceptions afhandelt: public void leesurl(string naam) throws Exception Met de mededeling throws Exception wordt de verantwoordelijkheid voor het afvangen van de exception overgedragen aan degene die de methode aanroept. Inderdaad is er bij aanroep van leesurl zorg gedragen voor een try-catch opdracht. Invoer van WWW Lezen van hypertekst op het WWW gebeurt op vrijwel dezelfde manier als lezen van lokale files. De kunst is om aan de gewenste InputStreamReader te komen. Dat gaat als volgt: uitgaande van een string die de locatie van de gewenste pagina bevat, wordt een object van type URL gemaakt: u = new URL(s); naar die URL wordt connectie gezocht (waarvoor dus een internet-verbinding noodzakelijk is): c = u.openconnection(); van zo n connection kun je een byte-stream te pakken krijgen, door middel van de aanroep: is = c.getinputstream(); die kan geconverteerd worden naar een character-stream door hem als parameter mee te geven: isr = new InputStreamReader(is); daarmee kan tenslotte, net als bij files, een BufferedReader worden gemaakt: br = new BufferedReader(isr); de BufferedReader kan gebruikt worden om strings te lezen door aanroep van: regel = br.readline(); Als deze opdrachten (behalve de laatste) zijn in het programma in listing 67 gecombineerd tot één opdracht; het is tenslotte niet nodig om de tussenresultaten steeds in variabelen op te slaan. Herdefinitie van geërfde methoden In WWW-pagina s komt vaak veel Engelse tekst voor in de vorm van tags en filenamen. Die kan de letterfrequentie van de voorbeeldtalen lelijk verstoren. Daarom bouwen we een vooziening in dat alles tussen < en > niet wordt meegeteld, zodat tags en hun parameters buiten beschouwing blijven. Hiertoe herdefiniëren we de methode turf uit TurfTab, die één character turft. In de herdefinitie wordt gecontroleerd of dit zo n punt-haakje is. Zo ja, dan wordt een boolean variabele veranderd die aangeeft of letters vanaf nu wel of niet meegeteld moeten worden. Als het character geen punthaakje is, wordt de oorspronkelijke methode turf (die bereikbaar is via de pseudo-variabele super) alleen maar aangeroepen als de boolean variabele aangeeft dat we niet met een tag bezig zijn. De boolean variabele teltmee is, doordat hij als klasse-variabele is gedeclareerd, een onderdeel geworden van de toestand van de turftabel. In de constructormethode krijgt deze zijn initiële waarde. De hergedefinieerde methode turf wordt aangeroepen vanuit de klasse TurfTab. Zoals altijd bij herdefinities, hebben we daarmee die oorspronkelijke klasse mooi te pakken: in plaats van zijn eigen turf-een-character methode, roept die nu de hergedefinieerde versie aan! Opgaven 14.1 Preprocessing Bekijk het taalherkennings-programma. Elke keer als de gebruiker op de knop drukt, wordt de taal in de TextArea vergeleken met de voorbeeld-talen. De voorbeeld-talen worden op dat moment ingelezen. Als de gebruiker in de TextArea een andere tekst intikt en weer op de knop drukt, worden de voorbeeld-talen nogmaals ingelezen, en wordt er weer een frequentietabel van gemaakt. Dat is eigenlijk zonde van het werk. Hoe kan het programma aangepast worden, zo dat de voorbeeld-talen alleen worden ingelezen als dat echt nodig is?

221 14.3 Toepassing: automatische taalherkenning 221 import java.awt.*; import java.awt.event.*; import java.applet.applet; 5 public class Taal extends Frame implements ActionListener { TextArea tekst; Label uitvoer; TextField [] taal; TextField [] url; 10 final int aantal = 10; public Taal(String [] startwaarde) { Panel talen, urls; Button knop; int i; 15 // Maken van de interfacecomponenten talen = new Panel(); urls = new Panel(); tekst = new TextArea(10,50); uitvoer = new Label(); 20 knop = new Button("Herken"); taal = new TextField[10]; // de array als geheel url = new TextField[10]; for (i=0; i<aantal; i++) { taal[i] = new TextField(10); // de aparte elementen 25 url [i] = new TextField(30); // Configureren van de interfacecomponenten this.setsize(600,500); 30 this.settitle("taalherkenning"); talen.setlayout( new GridLayout(aantal+1,1) ); urls.setlayout( new GridLayout(aantal+1,1) ); // Opbouw van de interface 35 talen.add( new Label("Taal")); urls.add( new Label("Voorbeeldtekst")); for (i=0; i<aantal; i++) { talen.add(taal[i]); urls.add(url [i]); 40 if (i<startwaarde.length/2) { taal[i].settext(startwaarde[2*i] ); url [i].settext(startwaarde[2*i+1]); 45 this.add(tekst, BorderLayout.NORTH ); this.add(talen, BorderLayout.WEST ); this.add(urls, BorderLayout.CENTER); this.add(knop, BorderLayout.EAST ); this.add(uitvoer, BorderLayout.SOUTH ); 50 // Toevoegen van interactie this.addwindowlistener(new WindowSluiter()); knop.addactionlistener(this); Listing 67: Taal/Taal.java, deel 1 van 2

222 222 Algoritmen public void actionperformed(actionevent e) { RelTurfTab onbekend, voorbeeld; double kleinste, verschil; String s, naam, antwoord; 60 onbekend = new RelTurfTab(); onbekend.turf( tekst.gettext() ); kleinste = 1.0; 65 antwoord = "niet te bepalen"; uitvoer.settext("bezig met vergelijken..."); for (int i=0; i<aantal; i++) { 70 naam = url[i].gettext(); if (!naam.equals("")) try { voorbeeld = new RelTurfTab(); voorbeeld.leesurl(naam); 75 verschil = onbekend.verschil(voorbeeld); uitvoer.settext(naam + ": " + verschil + "..." ); if (verschil<kleinste) { kleinste = verschil; 80 antwoord = taal[i].gettext(); catch (Exception ex) { uitvoer.settext( naam + ": " + ex ); 85 uitvoer.settext("best passende taal is " + antwoord + " (" + kleinste + ")" ); 90 public static void main(string [] parameters) { String [] defaultwaarden = { "Nederlands", " "Engels", " "Duits", " 95, "Frans", " "Spaans", " ; if (parameters.length==0) 100 parameters = defaultwaarden; 105 Taal t; t = new Taal(parameters); t.setvisible(true); Listing 68: Taal/Taal.java, deel 2 van 2

223 14.3 Toepassing: automatische taalherkenning 223 class TurfTab { int [] tellers; int totaal; 5 public TurfTab() { tellers = new int[26]; 10 public void turf(char c) { if (c>= A && c<= Z ) { tellers[c- A ]++; totaal++; 15 else if (c>= a && c<= z ) { tellers[c- a ]++; totaal++; 20 public void turf(string s) { for (int i=0; i<s.length(); i++) 25 turf( s.charat(i) ); public String tostring() { String s = ""; 30 for (int i=0; i<26; i++) s += (char)(i+ A ) + ": " + tellers[i] + " keer\n"; s += "totaal: " + totaal; return s; 35 Listing 69: Taal/TurfTab.java

224 224 Algoritmen import java.io.*; import java.net.*; class RelTurfTab extends TurfTab 5 { boolean teltmee; public RelTurfTab() { teltmee = true; public double relatief(int i) { return (double)tellers[i] / totaal; public double verschil(relturftab andere) { double totaal = 0.0; for (int i=0; i<26; i++) totaal += Math.abs( this.relatief(i) - andere.relatief(i) ); 20 return totaal/26; public void leesurl(string naam) throws Exception { BufferedReader reader; 25 String s; reader = new BufferedReader(new InputStreamReader (new URL(naam). openconnection(). getinputstream())); while ( (s = reader.readline())!= null) 30 this.turf(s); public void turf(char c) { if (c== < ) teltmee = false; 35 else if (c== > ) teltmee = true; else if (teltmee) super.turf(c); Listing 70: Taal/RelTurfTab.java

225 14.3 Toepassing: automatische taalherkenning Klasse Stack In deze opgave verplaatsen we ons in de schrijver van het package java.util: je moet zelf (een deel van) de klasse Stack schrijven. De bestaande collection-klassen mag je daarbij niet gebruiken. Je mag wel arrays gebruiken. De klasse Stack kent vier methodes: Stack, push, pop en empty (er zijn nog wel meer methodes, maar die laten we hier buiten beschouwing). In een stack kunnen objecten worden opgeslagen op een stapel : met de methode push leg je iets bovenop de stapel, en met de methode pop kun je het object van de stapel pakken dat het laatst bovenop de stapel is gelegd (en daarbij tevens van de stapel verwijderen). De methode Stack creëert een lege stapel, en de methode empty kijkt of een stapel op dit moment leeg is. Schrijf de klasse Stack en de methoden daarvan, waarbij je de objecten achter de schermen in een array opslaat. In eerste instantie kun je in deze array ruimte maken voor 10 elementen, maar als de gebruiker push aanroept op een moment dat er al 10 objecten op de stapel liggen, moet je de beschikbare ruimte verdubbelen, door een nieuwe array te creëren en de bestaande gegevens daarheen te kopiëren. Als later ook die ruimte op is, moet de ruimte weer worden verdubbeld, zodat er ruimte komt voor 40 objecten, enzovoort. Schrijf een statische methode test waarin de klasse Stack als volgt uitgeprobeerd wordt: er wordt een nieuwe stack gemaakt, daarop worden de strings aap, noot, en mies neergezet, daarna wordt het bovenste element van de stack gepakt, en er wordt getest of dat een string is die de letters mies bevat. De uitkomst van de vergelijking wordt als waarheidwaarde opgeleverd als resultaat van de methode test. (Als de test goed verloopt, zal deze methode dus de waarde true opleveren). Hint: zorg ervoor dat in elk stack-object een array beschikbaar is waarin de strings worden opgeslagen, plus een teller die aangeeft tot hoever de array op dit moment gevuld is Klasse hashtabel Nog een kijkje achter de schermen van de collection-klassen. Een hashtabel is een datastructuur waarin een verzameling strings bewaard kan worden. Er zijn twee operaties op gedefinieerd: voegtoe, waarmee een string aan de verzameling kan worden toegevoegd; zoek, waarmee bepaald kan worden of een bepaalde string deel uitmaakt van de verzameling Natuurlijk kan zo n datastructuur gemaakt worden door de strings domweg in een array te zetten. Toevoegen is dan gemakkelijk, maar voor het zoeken zul je de hele array moeten doorlopen. Een hash-tabel is daarom slimmer opgebouwd: zowel toevoegen als zoeken kunnen doorgaans in één of enkele stappen worden gedaan. In deze opgave gebruiken we hash-tabellen met ruimte voor 100 strings. We gaan aan elke string een getalswaarde koppelen: de zogenaamde hashwaarde. De hashwaarde wordt als volgt berekend: de ascii-waarde van alle symbolen in de string worden opgeteld, en van het resultaat wordt het gedeelte kleiner dan 100 gebruikt. Voorbeeld: de hash-waarde van de string AAP is 10, want de opgetelde ascii-waarden geven =210. De hash-waarde van NOOT is 20, want =320. Opgave: schrijf een statische methode hash, die de hash-waarde van een string berekent. Bij het toevoegen van een string aan de verzameling wordt deze in principe op de plaats aangeduid door de hash-waarde gezet. Alleen als deze plaats al bezet is, wordt de volgende plaats gebruikt. Als die ook bezet is de daaropvolgende, enzovoorts. Als je zo het eind van de tabel bereikt, kun je weer van voren af doortellen: na plaats 99 komt plaats 0. Opgave: schrijf een methode voegtoe, die een string toevoegt aan een hash-tabel. Schrijf nu de methode zoek. Deze methode begint te zoeken op de plaats aangeduid door de hash-waarde van de string. Als deze bezet is door een andere string, moeten we nog even verder kijken. Kom je echter een lege plaats tegen, dan is verder zoeken onnodig. Schrijf een klasse Hashtabel, met declaraties en constructormethode, waarin bovenstaande methodes ondergebracht kunnen worden.

226 226 Bijlage A Gebruik van de compiler Deze appendix beschrijft het gebruik van de Java-copiler Java2 SDK standard edition version 1.4 van Sun, met gebruikmaking van de IDE JCreator van Xinox software. A.1 Installatie van de software Als je de software op een eigen computer wilt gebruiken, moet je deze eerst installeren. Op de practicumcomputers is dit niet nodig; daar is de software al geïnstalleerd. Het installeren gaat als volgt: Stap 1: download compiler (alleen als je de CD niet hebt): Download de compiler (36 Megabyte) en de bijbehorende documentatie (30 Megabyte) van de website java.sun.com/j2se/1.4/download.html. Stap 2: download IDE (alleen als je de CD niet hebt): Download de geïntegreerde ontwikkelomgeving (IDE) Jcreator LE van de website Kies voor de light edition (LE) en niet voor de Pro-editie. Stap 3: installeer compiler Start het installatieprogramma van de compiler: j2sdk140.exe. Volg de aanwijzingen van de wizard. De wizard vraagt in welke directory de software moet worden neergezet, en doet als suggestie C:\jdk1.4.0_01. Als je je harddisk een beetje opgeruimd wilt houden, wil je dit misschien veranderen in C:\Program Files\jdk140. Stap 4: installeer help Unzip de documentatie j2sdk140-doc.zip. Laat het bestand uitpakken in de directory die je in stap 3 hebt gekozen. Door het uitpakken van van de zip-file onstaat daar een nieuwe subdirectory docs. Stap 5: installeer IDE Start het installatieprogramma van de geïntegreerde ontwikkelomgeving (IDE) JCreator: jcreator250.exe. Volg de aanwijzingen van de wizard. Deze wizard doet wel een zinvolle suggestie voor de installatiedirectory: C:\Program Files\Xinox Software\Jcreator LE.

227 A.2 Configuratie van de IDE 227 A.2 Configuratie van de IDE De eerste keer dat je de ontwikkelomgeving start, moet je een aantal extra handelingen verrichten om de boel goed te configureren. Dat gaat zo: Stap 6: start IDE Run Jcreator. Het icoon staat waarschijnlijk op de desktop, en anders kun je het vinden onder menukeuze Start Programs Programming Jcreator (op de practicumcomputers) of onder menukeuze Start Programs Jcreator (op je eigen computer). Het hoofdwindow van JCreator verschijnt. Links in dit window staan twee lege panelen, en bovenin staat de gebruikelijke overvloed aan menu s en knoppen. Stap 7: kies IDE profile JCreator kan worden gebruikt voor verschillende versies van de compiler, bijvoorbeeld voor versie en de beta-versie van Welke versie je gebruikt staat vermeld in een profile. Ook als je maar één versie van de compiler gebruikt, moet je zo n profile kiezen: Kies uit het menu menukeuze Configure Options In de boomstructuur aan de linkerkant van de dialoog kies je JDK Profiles De lijst in het midden is waarschijnlijk nog leeg. Druk op de knop New... en blader naar de directory C:\apps\jdk1.4 (op de practicumcomputers), of naar de directory die je in stap 3 hebt gekozen In de lijst in het midden verschijnt nu JDK version 140. Druk op OK. Stap 8: kies directories Je moet nu aan JCreator vertellen waar je files bewaard moeten worden. Standaard is dat op de C-schijf, en dat is geen goed idee in een netwerkomgeving. Je kunt dat als volgt veranderen: Kies menukeuze Configure Options In de boomstructuur aan de linkerkant van de dialoog kies je Directories Druk op de... -knop naast Default project directory, en kies een plek uit waar je files kunt schrijven. Het is handig om een nieuwe directory aan te maken, want zeer binnenkort zal deze directory overspoeld worden met files. Goede keuzes zijn H:\Java (op de practicumcomputers) of My Documents\Java (op je eigen computer). De andere twee directories ( Syntax en Project Templates ) kun je onveranderd laten.

228 228 Gebruik van de compiler Stap 9: maak workspace Files die bij elkaar horen (zoals de java-file en de html-file van één programma) worden geadministreerd in een project. Al gauw zul je aan meerdere projecten tegelijk werken. De verschillende projecten worden op hun beurt geordend in een workspace. We gaan zo n workspace maken: Kies menukeuze File New Selecteer het tabblad Workspaces Tik een naam voor de workspace achter Workspace name:, bijvoorbeeld Imp of Practicum. Onder Location wordt automatisch een subdirectory met diezelfde naam aangemaakt onder de directory die je in stap 8 hebt aangegeven. Dat is een goed idee; druk op OK. In een tweede workspace zou je de voorbeeldprogramma s kunnen opnemen, die op de CD in de directory Demo beschikbaar zijn. Die directory zou je in zijn geheel in je Java-directory kunnen kopiëren. Een JCreator-workspace is bijgeleverd.

229 A.3 Een programma schrijven en uitvoeren 229 A.3 Een programma schrijven en uitvoeren Alle voorbereidende werkzaamheden zijn nu gedaan. Op dit punt begin je elke keer als je een nieuw programma wilt gaan beginnen. Stap 10: maak project We gaan een project maken, waarin alle files die bij een programma horen zijn gebundeld. De makkelijkste manier is om een wizard te gebruiken, die een kant-en-klaar project neerzet: Kies menukeuze File New Selecteer het tabblad Projects Kies het icoon Basic Java Applet (en dus niet Application ) Tik een naam voor het project achter Project name:, bijvoorbeeld Hallo. Deze naam moet met een hoofdletter beginnen. Onder Location wordt automatisch een subdirectory met diezelfde naam aangemaakt onder de workspace-directory die je in stap 9 hebt aangegeven. Dat is een goed idee; druk op OK. Stap 11: edit programma In de boomstructuur det de opbouw van de workspace weergeeft, heeft de wizard twee files neergezet: Hallo.java en Hallo.html. Dubbelklik op zo n filenaam, en de file verschijnt in een edit-window. Het programma lijkt sterk op het in hoofdstuk 2 besproken programma.

230 230 Gebruik van de compiler Stap 12: compileer programma Nu moet het programma gecompileerd worden. Kies daartoe menukeuze Build Compile Project. Als je het programma zelf hebt ingetikt, is het goed mogelijk dat het programma fouten bevat. Je kunt bijvoorbeeld een tikfout hebben gemaakt. In de afbeelding is bijvoorbeeld Grahpics getikt, in plaats van het correcte Graphics. Daarom verschijnt er in het onderste paneel een foutmelding van de compiler, die cannot resolve the symbol. Andere fouten die veel worden gemaakt is het verwarren van hoofdletters en kleine letters: in Java (anders dan in html), is er een verschil tussen paint en Paint. Als je dubbelklikt op een foutmelding, springt de cursor naar de regel die de fout bevat. Vooral in lange programma s is dat erg handig. Aldaar kun je de fout herstellen, en het programma opnieuw compileren met menukeuze Build Compile Project. Ga hiermee door totdat er geen fouten meer over zijn, en in het Build-paneel de melding Process completed verschijnt. Stap 13: run programma met appletviewer Nu het programma succesvol is gecompileerd, kun je het programma runnen, om te zien wat het resultaat is. Kies daartoe menukeuze Build Execute Project. Er verschijnt een zwarte DOS-box, die je kunt negeren. Maar er verschijnt ook een Appletviewer window appears, waarin de uitvoer van het applet verschijnt. Eindelijk staat er Hallo! op het scherm... Je kunt de appletviewer sluiten door op de X in de rechterbovenhoek te klikken. De DOS-box

231 A.4 Documentatie raadplegen 231 verdwijnt dan ook. Stap 14: run programma met browser In plaats van met Appletviewer, kun je het programma ook interpreteren met een webbrowser. Zoek de html-file met Windows Explorer en dubbelklik op het icoon. De uitvoer verschijnt ditmaal in een Internet Explorer window. Merk op dat je ditmaal de hele inhoud van de html file te zien krijgt, dus ook de tekst Here is a simple applet. Het eigenlijke applet verschijnt in een aparte (grijze) rechthoek. Dit in tegenstelling tot de Appletviewer-aanpak uit stap 8, die alleen de uitvoer van het eigenlijke applet laat zien (op een witte achtergrond), met daarbij alleen de tekst Applet started. Er zijn natuurlijk veel makkelijkere manieren om de tekst Hallo in een Internet Explorer window te krijgen (bijvoorbeeld door het gewoon in de html-file te tikken). Dit eenvoudige voorbeeld was natuurlijk alleen maar bedoeld om alle stappen van het ontwikkelen van een programma te demonstreren - voor interessantere programma s gaat dit op dezelfde manier. A.4 Documentatie raadplegen Meer informatie over een bepaalde klasse of methode kun je krijgen door de naam in de editor te selecteren (bijvoorbeeld Graphics), en dan menukeuze Help JDK Help te kiezen. Of klik het woord met de rechter muisknop en kies Show JDK Help uit het context menu, of druk op Ctrl-F1.

232 232 Bijlage B Programmeerprojecten B.1 Kalender Maak een applet dat de gebruiker in de gelegenheid stelt om in twee tekstvelden een maandnummer (1 tot 12) en een jaartal (1600 tot 3000) in te tikken. (Zorg dat het voor de gebruiker duidelijk is wat hij waar moet invullen!) Bij aanvang moet in deze velden automatisch de huidige maand en het huidige jaar zijn ingevuld. Onder de tekstvelden moet een kalender worden getoond van de in de tekstvelden aangegeven maand. De kalender bevat de volgende informatie: op de eerste regel de maandnaam (voluit) en het jaartal, op de tweede regel de weeknummers, en op de zeven regels daaronder de dagnummers. In de linkerkolom staan de namen van de dagen. Dus bijvoorbeeld: december ma di wo do vr za zo De weken beginnen op maandag en eindigen -het woord zegt het al- met het weekend. Weeknummer 1 is de eerste week van het jaar waar een donderdag in valt. Uiteraard moet de kalender rekening houden met schrikkeljaren: februari heeft normaal gesproken 28 dagen, maar... in jaartallen die deelbaar zijn door 4 heeft februari 29 dagen, maar... in jaartallen die deelbaar zijn door 100 heeft februari toch 28 dagen, maar... in jaartallen die deelbaar zijn door 400 heeft februari toch weer 29 dagen. In de kalender moeten de zondagen en enkele feestdagen met gebruik van kleuren of anderszins extra benadrukt worden. Feestdagen zijn: nieuwjaarsdag (1 jan), eerste en tweede kerstdag (25 en 26 dec), bevrijdingsdag (5 mei vanaf 1945), koninginnedag (31 aug van en 30 april van ). Als de gebruiker een leeg veld of een ongeldig nummer intikt, moeten de tekstvelden automatisch terugspringen op de huidige datum. Je hoeft er echter geen rekening mee te houden dat de gebruiker iets anders dan cijfers intikt. Optionele extra s: Vermeld ook eerste en tweede paasdag (zoek het algoritme daarvoor op in encyclopedie of internet), eerste en tweede pinksterdag (49 dagen na pasen), hemelvaartsdag (39 dagen na pasen). Houd rekening met schrikkeljaren, en met het feit dat de Gregoriaanse kalender hier (althans in de provincie Utrecht) pas in december 1700 werd ingevoerd (en niet in 1582 zoals de Javalibrary zonder tegenbericht aanneemt). Dat betekent dat 1700 toch nog een schrikkeljaar was, en dat de data 1 t/m 11 december 1700 niet bestaan hebben. Hint: lees in de helpfiles wat de klasse Calendar te bieden heeft. Zonder dat is het veel moeilijker!

233 B.2 Mandelbrot 233 B.2 Mandelbrot Mandelgetallen Voor elk punt (x, y) van het platte vlak, waarbij x en y rele getallen zijn, kan een bijbehorend getal worden bepaald laten we dit het mandelgetal noemen. Om het mandelgetal te kunnen uitrekenen, bekijken we eerst de volgende functie, die punten (a, b) van het vlak transformeert naar andere punten: f (a, b) = (a a b b + x, 2 a b + y) Let op: deze functie transformeert het punt (a, b), maar in de berekening speelt ook de waarde van x en y, dat is het punt waarvan we het mandelgetal willen bepalen, een rol. Deze functie f nu, passen we toe op het punt (a, b) = (0, 0). Op het punt dat daar uitkomt, passen we nog eens de functie f toe. Op het punt dat daar weer het resultaat van is, passen we opnieuw f toe, enzovoorts enzovoorts. We stoppen pas met toepassen van f als het resultaat-punt een afstand van meer dan 4 tot het punt (0, 0 heeft. Het mandelgetal is nu gelijk aan het aantal keren dat f is toegepast. Voor sommige punten (x, y) is dat meteen al zo, en is het mandelgetal dus gelijk aan 1. Voor andere punten duurt het langer: die hebben een groter mandelgetal. Er zijn ook punten waarbij je f kan blijven toepassen, zonder dat de afstand tot de oorsprong ooit meer dan 4 wordt: die punten hebben mandelgetal oneindig. In de praktijk kunnen we niet oneindig vaak een functie toepassen; daarom stoppen we met toepassen van f bij een bepaald maximum aantal keren, zeg 100 keer; is dan de afstand nog niet groter dan 4, dan nemen we maar aan dat het mandelgetal oneindig is. De Mandelbrot-figuur Aan de hand van mandelgetallen kunnen we gemakkelijk een kleur van elk punt bepalen. Een mogelijkheid is deze: punten met een even mandelgetal worden wit, punten met een oneven mandelgetal worden zwart. Ook punten met mandelgetal oneiding worden zwart. Teken je van elk punt de aldus bepaalde kleur, dan zie je een afbeelding van de Mandelbrot-figuur. Voor x en y tussen 2 en 2 ziet die er als volgt uit: De figuur is opmerkelijk grillig, zeker als je bedenkt hoe relatief eenvoudig het algoritme eigenlijk is. Het wordt nog mooier als je ïnzoomtöp onderdelen van de figuur, zoals in het tweede plaatje. Het programma We gaan een programma maken waarmee de gebruiker kan inzoomen op de Mandelbrotfiguur. In het window staan behalve de figuur vier tekstvelden. De gebruiker kan daarin intikken: De x-coordinaat van het middelpunt van het scherm De y-coordinaat van het middelpunt van het scherm De schaalfactor. Bij een schaalfactor van bijvoorbeeld 0.01 kan in een window van 400 bij 400 beeldpunten een gebied voor x en y tussen bijvoorbeeld -2 en 2 worden weergegeven.

234 234 Programmeerprojecten Het maximum aantal herhalingen van f Druk de gebruiker in een van de tekstvelden daarna op de Enter-toets, dan moet het daarin aangegeven deel van de Mandelbrotfiguur worden getoond. Ook kan de gebruiker ook met de muis op de figuur klikken. Het aangeklikte punt wordt het nieuwe middelpunt, en de schaal wordt twee keer zo klein; de gebruiker zoomt daarmee in op het aangeklikte punt. De nieuwe waarden van midden en schaal worden ook in de tekstvelden weergegeven. De HTML-file Aan het begin van het programma moet het midden (0,0) zijn, de schaal 0.01, en het maximum aantal herhalingen 100. Als echter in de html-file <PARAM>-tags worden gespecificeerd met de naam x, y, schaal en max, dan moet die waarde als beginwaarde worden gebruikt. De auteur van de html-file kan daarmee alvast een bepaald detail van het plaatje tonen. Maak bij je programma een html-file, waarin je vier exemplaren van het applet opstart: eentje in de beginsituatie, en drie andere op een interessant detail van de figuur. De kleuren Hierboven is een zeer eenvoudige kleuring van de figuur beschreven: punten met even mandelgetal worden wit, andere punten zwart. Daar kun je het programma in eerste instantie mee testen. Maar in het definitieve programma moeten de punten gekleurd worden, waarbij de rood-, groenen blauw-component alledrie van het mandelgetal afhangen. De kleur mag echter niet afhankelijk zijn van x en y. Hier zijn twee voorbeelden, maar er zijn nog vele andere mogelijkheden. Wees eens creatief! Optionele extra (voor als je nog tijd over hebt): je kunt de kleurbepaling wellicht nog laten afhangen van extra, door de gebruiker te bepalen parameter(s). Daarvoor kun je eventueel nog extra interactiecomponenten toevoegen.

235 B.3 Reversi-spel 235 B.3 Reversi-spel Het spel Het spel wordt gespeeld op een bord met vierkante velden. De twee spelers kunnen de beurt een blauwe, respectievelijk een rode steen neerzetten op een vrij veld. Aan het begin van het spel liggen er al twee blauwe en twee rode stenen op het bord, volgens het volgende patroon (hier getoond op een bord met 6x6 velden, maar het spel kan ook gespeeld worden op een bord met andere afmetingen). Spelers zijn niet helemaal vrij in hun keuze waar ze een steen kunnen zetten. Blauw mag een steen alleen maar neerzetten, als daarmee een of meer rode stenen worden ingesloten tussen de nieuwe steen en een van de al op het bord liggende blauwe stenen. Het insluiten kan horizontaal, vertikaal of diagonaal gebeuren. Bijvoorbeeld, in de beginsituatie heeft blauw (donkergrijs), die als eerste aan zet is, de keus uit de velden zoals hiernaast met een rondje aangegeven. Door het doen van de zet veranderen alle stenen van de tegenspeler die met die zet worden ingesloten in de eigen kleur. Bijvoorbeeld, als blauw in de beginsituatie voor de bovenste van de vier mogelijkheden kiest, dan wordt de ingesloten, direct daaronder liggende rode steen blauw. Daarna is rood aan zet, die drie mogelijkheden heeft om een blauwe steen in te sluiten. Na nog een tijdje spelen kan de situatie onstaan zoals hier links onder getekend is. Rood is aan zet, en kiest bijvoorbeeld voor het veld aangegeven met de letter A. De twee blauwe stenen er diagonaal rechtsboven (aangegeven met de letters B en C) worden ingesloten, maar ook de blauwe steen er recht boven (aangegeven met de letter D). Die worden dus allemaal rood, en de eindsituatie is zoals hier rechts onder getekend is. Merk op dat als gevolg van het veranderen van kleur weer andere blauwe stenen ingesloten raken (zoals die aangegeven met de letter E), maar die veranderen niet van kleur: bij het insluiten moet de nieuw neergezette steen betrokken zijn. In zeldzame gevallen kan het gebeuren dat een speler geen zet kan doen, omdat er geen enkele mogelijkheid is om stenen van de andere partij in te sluiten. De speler moet dan passen, en de andere speler is weer aan de beurt. Kan ook de andere speler geen zet doen, dan is het spel afgelopen. Winnaar is dan degene met de meeste stenen op het bord (bij gelijk aantal is de uitslag remise ).

236 236 Programmeerprojecten Het programma Het programma moet een applet zijn dat de volgende dingen toont: Het bord met de huidige situatie De huidige stand: een indicatie van het aantal stenen van rood en blauw De status: blauw aan zet, rood aan zet, blauw is winnaar, rood is winnaar, of remise Twee knoppen: nieuw spel en help Het aantal rijen en kolommen van het bord moet via een <PARAM>-tag hoogte en breedte in de html-file kunnen worden gespecificeerd. Hoogte en breedte hoeven niet hetzelfde te zijn. Als de <PARAM>-tags ontbreken of kleiner zijn dan 3 moet het bord 6x6 velden bevatten. De beginstand staat altijd in het midden van een bord, of (bij een oneven aantal rijen of kolommen) vlakbij het midden. Met de knop nieuw spel kan de gebruiker op elk moment een nieuw spel starten. Door het indrukken van de help -knop geeft het programma aan op welke velden een zet gedaan mag worden. Nogmaals indrukken van de help -knop schakelt deze functie weer uit. Door met de muis op een veld van het bord te klikken kan de gebruiker een zet doen voor de partij die aan de beurt is. Het moet natuurlijk wel een toegestane zet zijn, anders gebeurt er niets. (De twee spelers zullen dus om de beurt de muis moeten bedienen. Je hoeft niet tegen de computer te kunnen spelen). De afbeeldingen hieronder tonen het scherm aan het begin, en na een tijdje spelen en het aanschakelen van de help-functie. De layout van het scherm hoeft niet hetzelfde te zijn als in deze afbeeldingen, als de genoemde elementen maar aanwezig zijn. Hints Geef namen aan de constanten die je arbitrair kiest, zoals de diameter van de stenen. Beperk het aantal object-variabelen tot een minimum: naast de constanten en de buttons zijn alleen variabelen nodig die de momentele toestand van het spel beschrijven. Maak methoden (met parameters!) voor spel-specifieke vragen en handelingen, zoals is een zet op een bepaald veld voor een bepaalde kleur legaal? en verander de door een bepaalde zet in een bepaalde richting ingesloten stenen van kleur. Bedenk dat er in acht richtingen ingesloten kan worden: alle combinaties van 1, 0 en +1 in de x- en y-richting behalve (0,0). Vermijd het om de acht richtingen allemaal apart te behandelen: als er dan een foutje in zit, moet je die acht keer herstellen, en bovendien maakt het het programma onoverzichtelijk. Prober de regelmaat liever in een methode met handig gekozen parameters te vangen. Als je niet weet hoe te beginnen: teken eerst een (flexibel ingevuld) bord, voeg dan de interactie toe maar nog zonder controle van legaliteit en veranderen van kleur van ingesloten stenen, maak dan de help-functie (daarvoor heb je een methode voor controle op legaliteit nodig, die je daarna ook mooi kunt gebruiken voor het controleren van de zetten), enz.

Mobiel programmeren. Hoofdstuk 1. 1.1 Computers en programma s

Mobiel programmeren. Hoofdstuk 1. 1.1 Computers en programma s 1 Hoofdstuk 1 Mobiel programmeren 1.1 Computers en programma s Computer: processor plus geheugen Een computer bestaat uit tientallen verschillende componenten, en het is een vak apart om dat allemaal te

Nadere informatie

Korte inhoudsopgave. 1 Programmeren 5. 2 Java 14. 3 Tekenen en rekenen 25. 4 Nieuwe methoden 36. 5 Objecten en methoden 49. 6 Invloed van buiten 65

Korte inhoudsopgave. 1 Programmeren 5. 2 Java 14. 3 Tekenen en rekenen 25. 4 Nieuwe methoden 36. 5 Objecten en methoden 49. 6 Invloed van buiten 65 1 Korte inhoudsopgave 1 Programmeren 5 2 Java 14 3 Tekenen en rekenen 25 4 Nieuwe methoden 36 5 Objecten en methoden 49 6 Invloed van buiten 65 7 Herhaling 77 8 Keuze 87 9 Objecten en klassen 101 10 Overerving

Nadere informatie

Mobiel programmeren. Jeroen Fokker Departement Informatica Universiteit Utrecht. Korte inhoudsopgave. 12 november 2015. 1 Mobiel programmeren 1

Mobiel programmeren. Jeroen Fokker Departement Informatica Universiteit Utrecht. Korte inhoudsopgave. 12 november 2015. 1 Mobiel programmeren 1 i Mobiel programmeren Jeroen Fokker Departement Informatica Universiteit Utrecht 12 november 2015 Korte inhoudsopgave 1 Mobiel programmeren 1 2 Hallo, App! 13 3 En... aktie! 27 4 Methoden om te tekenen

Nadere informatie

Korte inhoudsopgave. 1 Programmeren 5. 2 Hallo, C#! 15. 3 Methoden om te tekenen 33. 4 Objecten en methoden 49. 5 Interactie 63.

Korte inhoudsopgave. 1 Programmeren 5. 2 Hallo, C#! 15. 3 Methoden om te tekenen 33. 4 Objecten en methoden 49. 5 Interactie 63. 1 Korte inhoudsopgave 1 Programmeren 5 2 Hallo, C#! 15 3 Methoden om te tekenen 33 4 Objecten en methoden 49 5 Interactie 63 6 Herhaling 78 7 Keuze 87 8 Objecten en klassen 99 9 Arrays en strings 119 10

Nadere informatie

Modelleren & Programmeren. Jeroen Fokker

Modelleren & Programmeren. Jeroen Fokker Modelleren & Programmeren Jeroen Fokker Wat heb je nodig? Collegediktaat kopen bij A-Eskwadraat (BBG-238) of zelf downloaden en uitprinten www.cs.uu.nl / docs / vakken / KI1V13009 Solis-id met password

Nadere informatie

eerste voorbeelden in Java

eerste voorbeelden in Java Beginselen van programmeren 2 eerste voorbeelden in Java vereisten: een editor: om programma in te tikken en te bewaren een Java compiler: zet ingetikte (bron-) programma om naar byte-code een Java Virtuele

Nadere informatie

http://www.liacs.nl/home/kosters/java/

http://www.liacs.nl/home/kosters/java/ sheets Programmeren 1 Java college 2, Walter Kosters De sheets zijn gebaseerd op de hoofdstukken 2 tot en met 6 van: D. Bell en M. Parr, Java voor studenten, Prentice Hall, 2002 http://www.liacs.nl/home/kosters/java/

Nadere informatie

1.Noem de vijf categorieën waarin programmeertalen kunnen worden ingedeeld en geef van elke categorie één voorbeeld.

1.Noem de vijf categorieën waarin programmeertalen kunnen worden ingedeeld en geef van elke categorie één voorbeeld. Module 4 programmeren 1.Noem de vijf categorieën waarin programmeertalen kunnen worden ingedeeld en geef van elke categorie één voorbeeld. Machinecode Assembleertalen: assembly Hogere programmeertalen:

Nadere informatie

Vakgroep CW KAHO Sint-Lieven

Vakgroep CW KAHO Sint-Lieven Vakgroep CW KAHO Sint-Lieven Objecten Programmeren voor de Sport: Een inleiding tot JAVA objecten Wetenschapsweek 20 November 2012 Tony Wauters en Tim Vermeulen tony.wauters@kahosl.be en tim.vermeulen@kahosl.be

Nadere informatie

BEGINNER JAVA Inhoudsopgave

BEGINNER JAVA Inhoudsopgave Inhoudsopgave 6 Configuratie Hallo wereld! Praten met de gebruiker Munt opgooien Voorwaarden Lussen......6 Configuratie Met deze Sushi kaarten ga je een simpel spel maken met één van de meest populaire

Nadere informatie

Lab Webdesign: Javascript 11 februari 2008 JAVASCRIPT

Lab Webdesign: Javascript 11 februari 2008 JAVASCRIPT H1: INLEIDING JAVASCRIPT Met HTML kun je fraaie webpagina's bouwen, alleen: ze zijn erg statisch. HTML is dan ook een pagina-beschrijvingstaal en geen echte programmeertaal. Om je homepagina interactief

Nadere informatie

Algoritme noteren? Algoritmen voor de computer worden vastgelegd met behulp van een programmeertaal.

Algoritme noteren? Algoritmen voor de computer worden vastgelegd met behulp van een programmeertaal. Programmeertalen Algoritme noteren? Algoritmen voor de computer worden vastgelegd met behulp van een programmeertaal. Taal // machine De geschiedenis van de programmeertalen loopt parallel met de geschiedenis

Nadere informatie

Algoritme noteren? Algoritmen voor de computer worden vastgelegd met behulp van een programmeertaal.

Algoritme noteren? Algoritmen voor de computer worden vastgelegd met behulp van een programmeertaal. Programmeertalen Algoritme noteren? Algoritmen voor de computer worden vastgelegd met behulp van een programmeertaal. Taal // machine De geschiedenis van de programmeertalen loopt parallel met de geschiedenis

Nadere informatie

Kleine cursus PHP5. Auteur: Raymond Moesker

Kleine cursus PHP5. Auteur: Raymond Moesker Kleine cursus PHP5 Auteur: Raymond Moesker Kleine cursus PHP PHP is platform en CPU onafhankelijk, open source, snel, heeft een grote userbase, het is object georiënteerd, het wordt omarmd door grote bedrijven

Nadere informatie

Tentamen Imperatief en Object-georiënteerd programmeren in Java voor CKI

Tentamen Imperatief en Object-georiënteerd programmeren in Java voor CKI Tentamen Imperatief en Object-georiënteerd programmeren in Java voor CKI Vrijdag 22 januari 2010 Toelichting Dit is een open boek tentamen. Communicatie en het gebruik van hulpmiddelen zijn niet toegestaan.

Nadere informatie

Een spoedcursus python

Een spoedcursus python Een spoedcursus python Zoals je in de titel misschien al gezien hebt, geven wij een spoedcursus Python. Door deze cursus leer je alle basics, zoals het rekenen met Python en het gebruik van strings. Het

Nadere informatie

Handleiding JCreator. Inhoud. Een Workspace en een eerste project maken

Handleiding JCreator. Inhoud. Een Workspace en een eerste project maken Handleiding JCreator Inhoud Een Workspace en een eerste project maken Een tweede project maken De editor van JCreator Aanpassen van de basis-directory Documentatie over klassen en methoden van de JDK Bestand

Nadere informatie

1e college Introductie Applicatiebouw. Applicatiebouw{ } Onderdeel van SmartProducts

1e college Introductie Applicatiebouw. Applicatiebouw{ } Onderdeel van SmartProducts 1e college Introductie Applicatiebouw Applicatiebouw{ } Onderdeel van SmartProducts EVEN VOORSTELLEN DOCENT Fjodor van Slooten N208 (Horstring Noord) F.vanSlooten@utwente.nl Assistentie door: Nadia Klomp,

Nadere informatie

JavaScript. 0 - Wat is JavaScript? JavaScript toevoegen

JavaScript. 0 - Wat is JavaScript? JavaScript toevoegen 0 - Wat is JavaScript? JavaScript is hele andere koek dan Scratch. Het wordt ook door professionele programmeurs gebruikt. Doordat er veel mensen gebruik maken van JavaScript is er veel informatie over

Nadere informatie

Bij dit hoofdstukken horen geen opgaven.

Bij dit hoofdstukken horen geen opgaven. 6. Programmeertalen Een computer begrijpt eigenlijk alleen maar binaire code (bestaande uit 1 en 0). Om hem/haar makkelijk opdrachten te geven zijn programmeertalen ontwikkeld. Deze moeten een goed gedefinieerde

Nadere informatie

Module 4 Hoofdstuk 1. Programmeertalen

Module 4 Hoofdstuk 1. Programmeertalen Module 4 Hoofdstuk 1 Programmeertalen Programmeertalen Een programmeertaal is een taal waarin de opdrachten worden geschreven die een computer moet uitvoeren Reeksen van die opdrachten of instructies vormen

Nadere informatie

Programmeren in Java

Programmeren in Java 4 september 2015 Even voorstellen Naam: Wessel Oele(39) Email: W.Oele@hr.nl Website: http://med.hro.nl/oelew Kop van Zuid Rotterdam, 3 juni 2007 Overzicht van modules programmeren in Java In totaal 4 modules

Nadere informatie

Wat is JAVA? Peter van Rijn

Wat is JAVA? Peter van Rijn Wat is JAVA? Peter van Rijn Wat is JAVA? een concept een platform een ontwikkelomgeving een mentaliteit 2 Het ontstaan van JAVA Patrick Naughton ergernis over de verschillende programma bibliotheken schrijft

Nadere informatie

Programmeren in Java 3

Programmeren in Java 3 2 september 2007 voor deeltijdstudenten Kop van Zuid Rotterdam, 3 juni 2007 Even voorstellen Naam: Wessel Oele(31) Docent bij opleiding technische informatica Kamer: I210 (tweede verdieping, links de gang

Nadere informatie

APPLICATIEBOUW 1E COLLEGE: INTRODUCTIE. Onderdeel van SmartProducts

APPLICATIEBOUW 1E COLLEGE: INTRODUCTIE. Onderdeel van SmartProducts APPLICATIEBOUW 1E COLLEGE: INTRODUCTIE Onderdeel van SmartProducts EVEN VOORSTELLEN DOCENT Fjodor van Slooten N208 (Horstring Noord) F.vanSlooten@utwente.nl Assistentie door: Hans Tragter, Marc Schreiber,

Nadere informatie

start -> id (k (f c s) (g s c)) -> k (f c s) (g s c) -> f c s -> s c

start -> id (k (f c s) (g s c)) -> k (f c s) (g s c) -> f c s -> s c Een Minimaal Formalisme om te Programmeren We hebben gezien dat Turing machines beschouwd kunnen worden als universele computers. D.w.z. dat iedere berekening met natuurlijke getallen die met een computer

Nadere informatie

1 Inleiding in Functioneel Programmeren

1 Inleiding in Functioneel Programmeren 1 Inleiding in Functioneel Programmeren door Elroy Jumpertz 1.1 Inleiding Aangezien Informatica een populaire minor is voor wiskundestudenten, leek het mij nuttig om een stukje te schrijven over een onderwerp

Nadere informatie

Mobiel programmeren. Jeroen Fokker

Mobiel programmeren. Jeroen Fokker Mobiel programmeren Jeroen Fokker Wat heb je nodig? Website Met collegediktaat en software www.cs.uu.nl / docs / vakken / b1mop Laptop bring your own device Microsoft Visual Studio Community 2015 of Xamarin

Nadere informatie

Kennismaken Greenfoot

Kennismaken Greenfoot HOOFDSTUK 1 Kennismaken met Greenfoot onderwerpen: de interface van Greenfoot, omgaan met objecten, methodes aanroepen, een scenario uitvoeren concepten: object, klasse, methode-aanroep, parameter, retourwaarde

Nadere informatie

Vereiste kennis. 1 Java-editor. 2 Het compileren van een programma

Vereiste kennis. 1 Java-editor. 2 Het compileren van een programma 3 Vereiste kennis Dit boek richt zich op het leren programmeren door het oefenen met programmeercodes. Veel theorie komt in het begin niet aan de orde. Dat is een grote uitdaging want het is niet makkelijk

Nadere informatie

Gebruik van de compiler

Gebruik van de compiler 1 Bijlage A Gebruik van de compiler Deze appendix beschrijft het gebruik van de Java-compiler Java2 SDK standard edition version 1.4 van Sun, met gebruikmaking van de IDE Eclipse. A.1 Installatie van de

Nadere informatie

MDL-lib maakt Pascal eenvoudiger BIBLIOTHEEK VOOR PASCAL PROGRAMMEURS

MDL-lib maakt Pascal eenvoudiger BIBLIOTHEEK VOOR PASCAL PROGRAMMEURS MDL-lib maakt Pascal eenvoudiger BIBLIOTHEEK VOOR PASCAL PROGRAMMEURS MSX Computer Magazine nummer 45 - maart 1991 Scanned, ocr ed and converted to PDF by HansO, 2001 Het programmeren in Turbo Pascal is

Nadere informatie

Veel succes! 1. Deze opgave bestaat uit een aantal vragen. Houd het antwoord kort: één of twee zinnen per onderdeel kan al genoeg zijn.

Veel succes! 1. Deze opgave bestaat uit een aantal vragen. Houd het antwoord kort: één of twee zinnen per onderdeel kan al genoeg zijn. Eerste deeltentamen Gameprogrammeren Vrijdag 27 september 2013, 8.30-10.30 uur Naam: Studentnummer: Het tentamen bestaat uit 4 opgaven. Elke opgave levert 10 punten op. Je cijfer is het totaal aantal punten

Nadere informatie

PSD Turtle. Om op een goede manier een programma te schrijven wordt er ook een algoritme gevolgd. Dit algoritme bestaat uit de volgende stappen/fasen:

PSD Turtle. Om op een goede manier een programma te schrijven wordt er ook een algoritme gevolgd. Dit algoritme bestaat uit de volgende stappen/fasen: Inleiding Small Basic is een gratis versie van de programmeertaal BASIC wat staat voor Beginner All-purpose Symbolic Instruction Code. Een computer taal die vooral in de beginjaren zeer populair was onder

Nadere informatie

DE ASTRO PI PROGRAMMEREN VOOR MISSION ZERO

DE ASTRO PI PROGRAMMEREN VOOR MISSION ZERO DE ASTRO PI PROGRAMMEREN DOCENTENHANDLEIDING 1 Deze handleiding is bedoeld om leerlingen te ondersteunen bij de Astro Pi Challenge, waarbij leerlingen een programma voor de Astro Pi-computer in het ISS

Nadere informatie

icafe Project Joeri Verdeyen Stefaan De Spiegeleer Ben Naim Tanfous

icafe Project Joeri Verdeyen Stefaan De Spiegeleer Ben Naim Tanfous icafe Project Joeri Verdeyen Stefaan De Spiegeleer Ben Naim Tanfous 2006-2007 Inhoudsopgave 1 2 1.1 Programmeertaal PHP5..................... 2 1.2 MySQL database......................... 3 1.3 Adobe Flash...........................

Nadere informatie

HET BESTURINGSSYSTEEM

HET BESTURINGSSYSTEEM HET BESTURINGSSYSTEEM Een besturingssysteem (ook wel: bedrijfssysteem, in het Engels operating system of afgekort OS) is een programma (meestal een geheel van samenwerkende programma's) dat na het opstarten

Nadere informatie

Tentamen Object Georiënteerd Programmeren TI1200 30 januari 2013, 9.00-12.00 Afdeling SCT, Faculteit EWI, TU Delft

Tentamen Object Georiënteerd Programmeren TI1200 30 januari 2013, 9.00-12.00 Afdeling SCT, Faculteit EWI, TU Delft Tentamen Object Georiënteerd Programmeren TI1200 30 januari 2013, 9.00-12.00 Afdeling SCT, Faculteit EWI, TU Delft Bij dit tentamen mag je geen gebruik maken van hulpmiddelen zoals boek of slides. Dit

Nadere informatie

Het voert eenvoudige instructies uit die achter elkaar in het geheugen van de machine zijn opgeslagen.

Het voert eenvoudige instructies uit die achter elkaar in het geheugen van de machine zijn opgeslagen. Antwoorden door een scholier 1809 woorden 28 september 2006 3,6 14 keer beoordeeld Vak Informatica Samenvatting Informatica 6.1) Van kleine instructies naar grote processen Noem 2 termen voor het centrale

Nadere informatie

van PSD naar JavaScript

van PSD naar JavaScript 2015 van PSD naar JavaScript F. Vonk versie 2 19-9-2015 inhoudsopgave 1. inleiding... - 2-2. ontwikkelomgeving... - 3-3. programmeerconcepten... - 4 - statement... - 4 - sequentie... - 4 - variabele en

Nadere informatie

Opdrachten herhalen. public void tekenscherm (object o, PEA pea) { int x; x = 1; zolang de voorwaarde geldig is

Opdrachten herhalen. public void tekenscherm (object o, PEA pea) { int x; x = 1; zolang de voorwaarde geldig is Opdrachten herhalen public void tekenscherm (object o, PEA pea) { int x; x = 1; while ( x

Nadere informatie

De Arduino-microcontroller in de motorvoertuigentechniek (2)

De Arduino-microcontroller in de motorvoertuigentechniek (2) De Arduino-microcontroller in de motorvoertuigentechniek (2) E. Gernaat (ISBN 978-90-79302-11-6) 1 Procescomputer 1.1 Microprocessoren algemeen De informatie-verwerking zoals is behandeld, is vrijwel geheel

Nadere informatie

10 Meer over functies

10 Meer over functies 10 Meer over functies In hoofdstuk 5 hebben we functies uitgebreid bestudeerd. In dit hoofdstuk bekijken we drie andere aspecten van functies: recursieve functies dat wil zeggen, functies die zichzelf

Nadere informatie

Excel reader. Beginner Gemiddeld. bas@excel-programmeur.nl

Excel reader. Beginner Gemiddeld. bas@excel-programmeur.nl Excel reader Beginner Gemiddeld Auteur Bas Meijerink E-mail bas@excel-programmeur.nl Versie 01D00 Datum 01-03-2014 Inhoudsopgave Introductie... - 3 - Hoofdstuk 1 - Databewerking - 4-1. Inleiding... - 5-2.

Nadere informatie

2. Syntaxis en semantiek

2. Syntaxis en semantiek 2. Syntaxis en semantiek In dit hoofdstuk worden de begrippen syntaxis en semantiek behandeld. Verder gaan we in op de fouten die hierin gemaakt kunnen worden en waarom dit in de algoritmiek zo desastreus

Nadere informatie

Modulewijzer tirprog02/infprg01, programmeren in Java 2

Modulewijzer tirprog02/infprg01, programmeren in Java 2 Modulewijzer tirprog02/infprg01, programmeren in Java 2 W. Oele 17 november 2009 1 Inhoudsopgave 1 Inleiding 3 2 Studiehouding 3 3 Voorkennis 4 4 Inhoud van deze module 5 5 Leermiddelen 5 6 Theorie en

Nadere informatie

APPLICATIEBOUW 1E COLLEGE: INTRODUCTIE. Onderdeel van SmartProducts

APPLICATIEBOUW 1E COLLEGE: INTRODUCTIE. Onderdeel van SmartProducts APPLICATIEBOUW 1E COLLEGE: INTRODUCTIE Onderdeel van SmartProducts EVEN VOORSTELLEN DOCENT Fjodor van Slooten N208 (Horstring Noord) F.vanSlooten@utwente.nl Assistentie door: Hans Tragter, Nadia Klomp,

Nadere informatie

Vakinhoudelijke uitwerking Keuzevak Applicatieontwikkeling van het profiel MVI vmbo beroepsgericht

Vakinhoudelijke uitwerking Keuzevak Applicatieontwikkeling van het profiel MVI vmbo beroepsgericht Vakinhoudelijke uitwerking Keuzevak Applicatieontwikkeling van het profiel MVI vmbo beroepsgericht Deze vakinhoudelijke uitwerking is ontwikkeld door het Redactieteam van de Schooleamenbank vmbo voor dit

Nadere informatie

Uitleg: In de bovenstaande oefening zie je in het eerste blokje een LEES en een SCHRIJF opdracht. Dit is nog lesstof uit het tweede trimester.

Uitleg: In de bovenstaande oefening zie je in het eerste blokje een LEES en een SCHRIJF opdracht. Dit is nog lesstof uit het tweede trimester. In onderstaande oefeningen zijn kleuren gebruikt. Deze dienen aleen om de structuren makkelijker terug te kunnen herkennen. Ze worden niet standaard zo gebruikt. De dunne rood/roze balken zijn ook geen

Nadere informatie

Zelftest Java concepten

Zelftest Java concepten Zelftest Java concepten Document: n0838test.fm 22/03/2012 ABIS Training & Consulting P.O. Box 220 B-3000 Leuven Belgium TRAINING & CONSULTING INLEIDING BIJ DE ZELFTEST JAVA CONCEPTEN Om de voorkennis nodig

Nadere informatie

Tentamen Objectgeorienteerd Programmeren IN1205 Voorbeeld

Tentamen Objectgeorienteerd Programmeren IN1205 Voorbeeld Tentamen Objectgeorienteerd Programmeren IN1205 Voorbeeld Afdeling ST Faculteit EWI TU Delft Bij dit tentamen mag u gebruik maken van: Barnes, Object-Oriented Programming with Java en de Notitie Algoritmiek

Nadere informatie

Inhoud leereenheid 8. Programmeren in JavaLogo (1) Introductie 73. Leerkern 75. Samenvatting 94. Zelftoets 95. Terugkoppeling 97

Inhoud leereenheid 8. Programmeren in JavaLogo (1) Introductie 73. Leerkern 75. Samenvatting 94. Zelftoets 95. Terugkoppeling 97 Inhoud leereenheid 8 Programmeren in JavaLogo (1) Introductie 73 Leerkern 75 1 Inleiding 75 1.1 Wat is programmeren? 75 1.2 Logo, Java en JavaLogo 76 2 Eerste programma s 77 2.1 Pen en Tekenblad 77 2.2

Nadere informatie

Je gaat leren programmeren in Ruby. En daarna in Ruby een spelletje maken. Websites zoals Twitch en Twitter gemaakt zijn met behulp van Ruby?

Je gaat leren programmeren in Ruby. En daarna in Ruby een spelletje maken. Websites zoals Twitch en Twitter gemaakt zijn met behulp van Ruby? 1 Je gaat leren programmeren in Ruby. En daarna in Ruby een spelletje maken. Websites zoals Twitch en Twitter gemaakt zijn met behulp van Ruby? Voordat je begint met programmeren, moet je Ruby installeren.

Nadere informatie

HOOFDSTUK 3. Imperatief programmeren. 3.1 Stapsgewijs programmeren. 3.2 If Then Else. Module 4 Programmeren

HOOFDSTUK 3. Imperatief programmeren. 3.1 Stapsgewijs programmeren. 3.2 If Then Else. Module 4 Programmeren HOOFDSTUK 3 3.1 Stapsgewijs programmeren De programmeertalen die tot nu toe genoemd zijn, zijn imperatieve of procedurele programmeertalen. is het stapsgewijs in code omschrijven wat een programma moet

Nadere informatie

Afhankelijk van wanneer je het programma uitvoert, zie je een van de volgende resultaten:

Afhankelijk van wanneer je het programma uitvoert, zie je een van de volgende resultaten: Hoofdstuk 4 Voorwaarden en vertakkingen Laten we eens teruggaan naar ons eerste programma. Zou het niet leuk zijn als we in plaats van het algemene Hallo wereld, afhankelijk van de tijd van de dag, Goedemorgen

Nadere informatie

Programmeren: Visual Basic

Programmeren: Visual Basic PETERSTUYVESANT COLLEGE INFORMATICA 2009-2010 Programmeren: Visual Basic Algemene Kennis: 01. Programmeren Programmeren is het schrijven van een computerprogramma, een concrete verzameling instructies

Nadere informatie

Programmeren. Inleiding

Programmeren. Inleiding Programmeren Inleiding STAPPEN IN DE ONTWIKKELING VAN EEN PROGRAMMA 1. Probleem 1. Probleem Ideaal gewicht berekenen Wortel van een vierkantsvergelijking berekenen Schaakspel spelen Boekhouding doen 2.

Nadere informatie

Inhoud Inhoud. Over dit boek 7. 1 Eclipse IDE (Integrated Development Environment) 9. 2 Functionele specificatie 13

Inhoud Inhoud. Over dit boek 7. 1 Eclipse IDE (Integrated Development Environment) 9. 2 Functionele specificatie 13 5 Inhoud Inhoud Over dit boek 7 1 Eclipse IDE (Integrated Development Environment) 9 2 Functionele specificatie 13 3 Implementatie grafische gebruikersinterface 31 4 De klassen en methoden 57 5 Technische

Nadere informatie

Verder zijn er de nodige websites waarbij voorbeelden van objectgeoriënteerd PHP (of Objec Oriented PHP, OO PHP) te vinden zijn.

Verder zijn er de nodige websites waarbij voorbeelden van objectgeoriënteerd PHP (of Objec Oriented PHP, OO PHP) te vinden zijn. Objectgeoriënteerd PHP (versie 5) Kennisvereisten: Ervaring met programmeren in PHP met MySQL Je weet wat een class of klasse is Je weet wat een instantie van een klasse (een object) is Je weet wat een

Nadere informatie

Je gaat leren programmeren en een spel bouwen met de programmeertaal Python. Websites zoals YouTube en Instagram zijn gebouwd met Python.

Je gaat leren programmeren en een spel bouwen met de programmeertaal Python. Websites zoals YouTube en Instagram zijn gebouwd met Python. 1 Je gaat leren programmeren en een spel bouwen met de programmeertaal Python. Websites zoals YouTube en Instagram zijn gebouwd met Python. Voordat je leert programmeren, moet je jouw pc zo instellen dat

Nadere informatie

Een stoomcursus door Edgar de Graaf, november 2006

Een stoomcursus door Edgar de Graaf, november 2006 Programmeren in Java Een stoomcursus door Edgar de Graaf, november 2006 Deze tekst geeft een zeer korte inleiding in de programmeertaal Java, uitgaande van kennis van de taal C++. Daarnaast bestudere men

Nadere informatie

HANDLEIDING PROGRAMMEREN IN PASCAL (LAZARUS)

HANDLEIDING PROGRAMMEREN IN PASCAL (LAZARUS) HANDLEIDING PROGRAMMEREN IN PASCAL (LAZARUS) Vereiste voorkennis Voldoende kennis van het besturingssysteem (in deze handleiding wordt uitgegaan van Windows) De basisprincipes van programmeren Vereiste

Nadere informatie

Module 3: Scratch programmeren: is het logisch of is het niet logisch?

Module 3: Scratch programmeren: is het logisch of is het niet logisch? Module 3: Scratch programmeren: is het logisch of is het niet logisch? Inhoudsopgave Module 3: Scratch programmeren: is het logisch of is het niet logisch?...1 Wat is een computerprogramma eigenlijk?...2

Nadere informatie

Les F-02 UML. 2013, David Lans

Les F-02 UML. 2013, David Lans Les F-02 UML In deze lesbrief wordt globaal beschreven wat Unified Modeling Language (UML) inhoudt. UML is een modelleertaal. Dat wil zeggen dat je daarmee de objecten binnen een (informatie)systeem modelmatig

Nadere informatie

Als een PSD selecties bevat, deelt de lijn van het programma zich op met de verschillende antwoorden op het vraagstuk.

Als een PSD selecties bevat, deelt de lijn van het programma zich op met de verschillende antwoorden op het vraagstuk. HOOFDSTUK 3 3.1 Stapsgewijs programmeren In de vorige hoofdstukken zijn programmeertalen beschreven die imperatief zijn. is het stapsgewijs in code omschrijven wat een programma moet doen, net als een

Nadere informatie

Samenvatting Hoofdstuk 1

Samenvatting Hoofdstuk 1 1.1 Software Categorieën 1. Je kunt software in twee manieren indelen: 1. Systeemsoftware 2. Applicatiesoftware Systeemsoftware Systeemsoftware regelt en ondersteunt de werking van de computer. Dus het

Nadere informatie

Opdracht 7a. Applicatiebouw 2014/2015

Opdracht 7a. Applicatiebouw 2014/2015 Applicatiebouw 2014/2015 Opdracht 7a Inhoud Applicatiebouw op dag 7 College In het college wordt oa. overerving behandeld, waarmee je uit een bestaande klasse een nieuwe andere klasse kan maken. Ook zijn

Nadere informatie

Inhoud. Introductie tot de cursus

Inhoud. Introductie tot de cursus Inhoud Introductie tot de cursus 1 De functie van de cursus 7 2 De inhoud van de cursus 7 2.1 Voorkennis 7 2.2 Leerdoelen van de cursus 8 2.3 Opbouw van de cursus 8 3 Leermiddelen en wijze van studeren

Nadere informatie

Ga naar en remix dit project.

Ga naar   en remix dit project. Quiz In deze handleiding leer je hoe je een quiz kunt maken waarmee je kunt testen hoeveel jouw vriendjes en vriendinnetjes over jouw favoriete onderwerp weten. Ga naar https://scratch.mit.edu/projects/112774047/

Nadere informatie

IMP Uitwerking week 13

IMP Uitwerking week 13 IMP Uitwerking week 13 Opgave 1 Nee. Anders moet bijvoorbeeld een venster applicatie een subklasse zijn van zowel Frame en WindowListener. Als de applicatie ook een button of een menu heeft, dan moet het

Nadere informatie

Inleiding C++ Coding Conventions

Inleiding C++ Coding Conventions Inleiding C++ Coding Conventions Opleiding Bachelor of Science in Informatica, van de Faculteit Wetenschappen, Universiteit Antwerpen. Nota s bij de cursus voor academiejaar 2012-2013. Ruben Van den Bossche,

Nadere informatie

HvA Instituut voor Interactieve Media ActionScript 3.0

HvA Instituut voor Interactieve Media ActionScript 3.0 PPRO 1: OEFENINGEN LES 1 Hierbij de werkgroepoefeningen behorend bij het practicum week 1. Lees de stukken uitleg aandachtig door, zonder deze informatie zullen de principes in de oefeningen moeilijk te

Nadere informatie

De AT90CAN microprocessor van ATMEL in de motorvoertuigentechniek (2)

De AT90CAN microprocessor van ATMEL in de motorvoertuigentechniek (2) De AT90CAN microprocessor van ATMEL in de motorvoertuigentechniek (2) Timloto o.s. / E. Gernaat / ISBN 978-90-79302-06-2 Op dit werk is de Creative Commens Licentie van toepassing. Uitgave: september 2012

Nadere informatie

Uitwerking Aanvullend tentamen Imperatief programmeren Woensdag 24 december 2014, 13.30 15.30 uur

Uitwerking Aanvullend tentamen Imperatief programmeren Woensdag 24 december 2014, 13.30 15.30 uur Uitwerking Aanvullend tentamen Imperatief programmeren Woensdag 24 december 2014, 13.30 15.30 uur 1. deze opgave telt voor 30% van het totaal. Schrijf een compleet programma, dat door de gebruiker vanaf

Nadere informatie

Lab Webdesign: Javascript 3 maart 2008

Lab Webdesign: Javascript 3 maart 2008 H5: OPERATORS In dit hoofdstuk zullen we het hebben over de operators (of ook wel: operatoren) in JavaScript waarmee allerlei rekenkundige en logische bewerkingen kunnen worden uitgevoerd. Daarbij zullen

Nadere informatie

Veel succes! 1. Deze opgave bestaat uit een aantal vragen. Houd het antwoord kort: één of twee zinnen per onderdeel kan al genoeg zijn.

Veel succes! 1. Deze opgave bestaat uit een aantal vragen. Houd het antwoord kort: één of twee zinnen per onderdeel kan al genoeg zijn. Eerste deeltentamen Gameprogrammeren Vrijdag 26 september 2014, 8.30-10.30 uur Naam: Studentnummer: Het tentamen bestaat uit 4 opgaven. Elke opgave levert 10 punten op. Je cijfer is het totaal aantal punten

Nadere informatie

Een topprogrammeur in het OO programmeren is Graig Larman. Hij bedacht de volgende zin:

Een topprogrammeur in het OO programmeren is Graig Larman. Hij bedacht de volgende zin: Java Les 2 Theorie Beslissingen Algemeen Net als in het dagelijks leven worden in software programma s beslissingen genomen, naast het toekennen van waarden aan variabelen zijn beslissingen één van de

Nadere informatie

PRAKTICUMOPGAVE 1. De eerste prakticumopgave is het maken van een applet om een "Mandelbrotfiguur" te tekenen, zoals hieronder omschreven.

PRAKTICUMOPGAVE 1. De eerste prakticumopgave is het maken van een applet om een Mandelbrotfiguur te tekenen, zoals hieronder omschreven. 1 of 5 3-5-2006 14:58 PRAKTICUMOPGAVE 1 De eerste prakticumopgave is het maken van een applet om een "Mandelbrotfiguur" te tekenen, zoals hieronder omschreven. Het practicum moet individueel worden gemaakt

Nadere informatie

Les A-03 Binaire en hexadecimale getallen

Les A-03 Binaire en hexadecimale getallen Les A-03 Binaire en hexadecimale getallen In deze les wordt behandeld hoe getallen kunnen worden voorgesteld door informatie die bestaat uit reeksen 0-en en 1-en. We noemen deze informatie digitale informatie.

Nadere informatie

Rekenen aan wortels Werkblad =

Rekenen aan wortels Werkblad = Rekenen aan wortels Werkblad 546121 = Vooraf De vragen en opdrachten in dit werkblad die vooraf gegaan worden door, moeten schriftelijk worden beantwoord. Daarbij moet altijd duidelijk zijn hoe de antwoorden

Nadere informatie

Uitwerking Tweede deeltentamen Imperatief programmeren - versie 1 Vrijdag 21 oktober 2016, uur

Uitwerking Tweede deeltentamen Imperatief programmeren - versie 1 Vrijdag 21 oktober 2016, uur Uitwerking Tweede deeltentamen Imperatief programmeren - versie 1 Vrijdag 21 oktober 2016, 13.00-15.00 uur 1. De situatie die ontstaat door class A : B C D; kan beschreven worden door (a) B is een A (b)

Nadere informatie

Zo gaat jouw kunstwerk er straks uitzien. Of misschien wel heel anders.

Zo gaat jouw kunstwerk er straks uitzien. Of misschien wel heel anders. Spirograaf in Python Een kunstwerk maken Met programmeren kun je alles maken! Ook een kunstwerk! In deze les maken we zelf een kunstwerk met Python. Hiervoor zal je werken met herhalingen en variabelen.

Nadere informatie

Scratch les 3: Quiz! Je eigen spelshow

Scratch les 3: Quiz! Je eigen spelshow Scratch les 3: Quiz! Je eigen spelshow Hoeveel weten jouw vriendjes en vriendinnetjes over jouw favoriete onderwerp? Test het met je zelfgemaakte quiz! Ga naar https://scratch.mit.edu/projects/112774047/.

Nadere informatie

Inleiding tot computers en programmeertalen

Inleiding tot computers en programmeertalen wat is informatica (eng. computer science)? Beginselen van programmeren 1 Inleiding tot computers en programmeertalen informatica is een wetenschap van abstractie: creëren van het juiste model voor een

Nadere informatie

Imperatief Programmeren, derde deeltentamen (INFOIMP) 4 november 2005

Imperatief Programmeren, derde deeltentamen (INFOIMP) 4 november 2005 Departement Informatica en Informatiekunde, Faculteit Bètawetenschappen, UU. In elektronische vorm beschikbaar gemaakt door de TBC van A Eskwadraat. Het college INFOIMP werd in 2005/2006 gegeven door Jeroen

Nadere informatie

Visual Basic.NET. Visual Basic.NET. M. den Besten 0.3 VB. NET

Visual Basic.NET. Visual Basic.NET. M. den Besten 0.3 VB. NET Visual Basic.NET M. den Besten 0.3 VB. NET Inhoud Voorwoord Deel 1 Visual Basic.NET 1.1 Inleiding...13 1.2 De programmeertaal Visual Basic.NET...14 1.3 Microsoft Visual Basic 2010 Express Edition...15

Nadere informatie

PROGRAMMEERTALEN EN -PARADIGMA S. Universiteit Utrecht

PROGRAMMEERTALEN EN -PARADIGMA S. Universiteit Utrecht PROGRAMMEERTALEN EN -PARADIGMA S Jeroen Fokker Informatica-instituut Universiteit Utrecht Willem van der Vegt Chr. Hogeschool Windesheim Zwolle 1 2 INHOUDSOPGAVEInleiding Geschiedenis van programmeertalen

Nadere informatie

Tentamen Objectgeorienteerd Programmeren TI februari Afdeling ST Faculteit EWI TU Delft

Tentamen Objectgeorienteerd Programmeren TI februari Afdeling ST Faculteit EWI TU Delft I ' Tentamen Objectgeorienteerd Programmeren TI 1200 1 februari 2012 9.00-12.00 Afdeling ST Faculteit EWI TU Delft Bij dit tentamen mag je geen gebruik maken van hulpmiddelen zoals boek of slides. Dit

Nadere informatie

slides6.pdf 16 nov

slides6.pdf 16 nov Inhoud Inleiding Algemeen 5 Waarom programmeertalen? Geschiedenis Abstractiemechanismen Programmeertalen Piet van Oostrum 16 november 2001 INL/Alg-5 1 X INL/Alg-5 1 X Machinecode Voor- en nadelen assemblercode

Nadere informatie

Functioneel programmeren

Functioneel programmeren Functioneel programmeren Practicumopgave 2: Mastermind Het doel van deze opgave is het implementeren van het spel Mastermind; zie http://nl.wikipedia.org/wiki/mastermind voor een uitleg. Het spel is klein

Nadere informatie

Datatypes Een datatype is de sort van van een waarde van een variabele, veel gebruikte datatypes zijn: String, int, Bool, char en double.

Datatypes Een datatype is de sort van van een waarde van een variabele, veel gebruikte datatypes zijn: String, int, Bool, char en double. Algemeen C# Variabele Een variabele is een willekeurige waarde die word opgeslagen. Een variabele heeft altijd een datetype ( De soort waarde die een variabele bevat). Datatypes Een datatype is de sort

Nadere informatie

[7] Variabelen en constanten

[7] Variabelen en constanten [7] Variabelen en constanten We gaan een eenvoudig programma schrijven waarbij we reclame maken voor CoderDojo Dendermonde. Volg mee via 07_VariabelenConstanten.py Dit is wat er moet verschijnen op het

Nadere informatie

APPLICATIEBOUW 3E COLLEGE: OBJECT GEORIËNTEERD PROGRAMMEREN, METHODEN, PARAMETERS, SCOPE VAN VARIABELEN. Onderdeel van SmartProducts

APPLICATIEBOUW 3E COLLEGE: OBJECT GEORIËNTEERD PROGRAMMEREN, METHODEN, PARAMETERS, SCOPE VAN VARIABELEN. Onderdeel van SmartProducts APPLICATIEBOUW 3E COLLEGE: OBJECT GEORIËNTEERD PROGRAMMEREN, METHODEN, PARAMETERS, SCOPE VAN VARIABELEN Onderdeel van SmartProducts INHOUD COLLEGE 3 Scope van variabelen {3.9} Class ontwerpen en maken,

Nadere informatie

Toets Programmeren, 2YP05 op donderdag 13 november 2008, 09:00-12:00

Toets Programmeren, 2YP05 op donderdag 13 november 2008, 09:00-12:00 Toets Programmeren, 2YP05 op donderdag 13 november 2008, 09:00-12:00 TU/e Technische Universiteit Eindhoven Faculteit Wiskunde en Informatica (Na de toets gecorrigeerde versie) PROBLEEM: Sleutels Lees

Nadere informatie

II. ZELFGEDEFINIEERDE FUNCTIES

II. ZELFGEDEFINIEERDE FUNCTIES II. ZELFGEDEFINIEERDE FUNCTIES In Excel bestaat reeds een uitgebreide reeks van functies zoals SOM, GEMIDDELDE, AFRONDEN, NU enz. Het is de bedoeling om functies aan deze lijst toe te voegen door in Visual

Nadere informatie

Hoofdstuk 26: Modelleren in Excel

Hoofdstuk 26: Modelleren in Excel Hoofdstuk 26: Modelleren in Excel 26.0 Inleiding In dit hoofdstuk leer je een aantal technieken die je kunnen helpen bij het voorbereiden van bedrijfsmodellen in Excel (zie hoofdstuk 25 voor wat bedoeld

Nadere informatie

Turbo Pascal (deel 1)

Turbo Pascal (deel 1) Turbo Pascal (deel 1) MSX CLUB MAGAZINE 34 Erik van Bilsen Scanned, ocr ed and converted to PDF by HansO, 2001 Erik van Bilsen leert u het klappen van de Turbo Pascal zweep. Turbo Pascal toepassen Deze

Nadere informatie

Programmeermethoden NA. Week 5: Functies (vervolg)

Programmeermethoden NA. Week 5: Functies (vervolg) Programmeermethoden NA Week 5: Functies (vervolg) Kristian Rietveld http://liacs.leidenuniv.nl/~rietveldkfd/courses/prna2016/ Functies Vorige week bekeken we functies: def bereken(a, x): return a * (x

Nadere informatie

Grafisch ontwerp. Referenties. https://developers.google.com/webmasters/mobile-sites/ http://www.bluetrainmobile.com/mobile-showcase

Grafisch ontwerp. Referenties. https://developers.google.com/webmasters/mobile-sites/ http://www.bluetrainmobile.com/mobile-showcase Mobiel Datanose Op dit moment is mobiel datanose niet goed gedaan; je krijgt gewoon de site te zien zoals je het te zien krijgt op pc's of laptops. Maar vaak heb je het probleem dat je op je mobiel moet

Nadere informatie

Informatica. Objectgeörienteerd leren programmeren. Van de theorie met BlueJ tot een spelletje met Greenfoot... Bert Van den Abbeele

Informatica. Objectgeörienteerd leren programmeren. Van de theorie met BlueJ tot een spelletje met Greenfoot... Bert Van den Abbeele Informatica Objectgeörienteerd leren programmeren Van de theorie met BlueJ tot een spelletje met Greenfoot... Bert Van den Abbeele http://creativecommons.org/licenses/by-nc-nd/3.0/legalcode Objectgeörienteerd

Nadere informatie