inhoudstafel en auteursrecht
* STER *
Java-opdrachten worden uitgevoerd in de volgorde waarin ze in het opdrachtenblok voorkomen, van boven naar onder en van links naar rechts. Het einde van een regel heeft geen bijzondere betekenis (nauwkeuriger: het wordt als een spatie beschouwd). Of we dus verscheiden opdrachten op één regel plaatsen, of één opdracht uitsmeren over verschillende regels, maakt voor de compiler niets uit.
Goede programmeurs gebruiken de regelindeling om het
programma leesbaar te maken en te houden. Die kwaliteitszorg
kan nog worden verbeterd door het gebruik van commentaar.
Elke tekst tussen /* en */ of tussen
// (dubbele schuine streep) en het einde van een
regel wordt door de compiler genegeerd.
De volgende opdrachten hebben tot gevolg dat de inhoud van de
veranderlijken x en y van plaats
verwisseld wordt.
temp = x; x = y; y = temp;
Waarom kan bovenstaand voorbeeld niet vereenvoudigd worden tot de volgende twee regels ?
y = x; x = y;
Breng uitgebreide commentaar aan bij de code van bovenstaand voorbeeld.
In een voorwaardelijke opdracht wordt de uitvoering van een opdracht of een blok afhankelijk gemaakt van het al dan niet waar zijn van een of andere voorwaarde.
if (<voorwaarde>)
<opdracht>
if (<voorwaarde>) {
<reeks opdrachten>
}
De <voorwaarde> bestaat in het
eenvoudigste geval uit een vergelijking van
twee uitdrukkingen met behulp van een vergelijkingsoperator,
dat is een van de volgende bewerkingen:
| bewerking | betekenis |
|---|---|
== |
is gelijk aan |
!= |
is verschillend van |
< |
is strikt kleiner dan |
> |
is strikt groter dan |
<= |
is kleiner dan of gelijk aan |
>= |
is groter dan of gelijk aan |
De volgende opdracht verhoogt de waarde van getal met
één eenheid als de oorspronkelijke waarde even is
(als de rest bij deling door 2 nul bedraagt).
if (getal % 2 == 0) getal = getal + 1;
Optioneel kan de voorwaardelijke opdracht nog woorden uitgebreid
met een else-deel voor het geval dat de voorwaarde
onwaar is.
if (<voorwaarde>)
<opdracht 1>
else
<opdracht 2>
if (<voorwaarde>) {
<reeks 1>
}
else {
<reeks 2>
}
Eenvoudige voorwaarden kunnen worden samengesteld tot complexe voorwaarden met behulp van logische bewerkingen. Java kent ondermeer de volgende logische bewerkingen.
| bewerking | betekenis |
|---|---|
&& |
en (is waar wanneer beide leden waar zijn) |
|| |
of (is waar wanneer minstens een van beide leden waar is) |
! |
niet (heeft slechts een rechterlid, waarvan de betekenis omgekeerd wordt - dus waar wordt onwaar en onwaar wordt waar) |
De volgende opdracht gaat na of het getal a binnen
het gesloten interval [1.5, 3.5] ligt. Zo ja, dan wordt a
met 1 verhoogd. Zo neen, dan wordt a met 2 verminderd,
en dan wordt een boodschap op het scherm gedrukt.
if ((a >= 1.5) && (a <= 3.5))
a = a + 1;
else {
a = a - 2;
System.out.println("a = " + a);
}
Formuleer een Java-uitdrukking die de volgende voorwaarde expliciteert: "a is verschillend van 3 en 4, of b is ten hoogste a."
Wat is het effect van de volgende opdracht ?
if (a < 0) a = 0 - a;
Java kent drie verschillende structuren voor het herhalen
van een opdracht of opdrachtenblok. De eenvoudigste is de
while-lusopdracht met algemene vorm
while (<voorwaarde>) {
<opdrachten>
}
(Zoals bij de voorwaardelijke opdracht, mogen ook hier de accoladen weggelaten worden indien het slechts om één te herhalen opdracht gaat.)
Telkens opnieuw wordt de <voorwaarde>
getest, en als het resultaat waar is, voert Java
de <opdrachten> nog eens uit.
Zodra de voorwaarde ook maar één keer
op onwaar uitkomt, is de lusopdracht
afgelopen.
Het volgende programma berekent alle delers van het getal 1440. Het gaat daarbij als volgt tewerk: één voor één worden alle delers van 1 tot 1440 uitgeprobeerd, en telkens als de deling opgaat (als de rest nul is) wordt de deler afgedrukt.
class Delers {
public static void main(String[] args) {
int deeltal, i;
deeltal = 1440;
i = 1;
while (i <= deeltal) {
if (deeltal % i == 0)
System.out.println(i);
i = i + 1;
}
}
}
Belangrijk is dat bij een while-opdracht
de test voorafgaat aan de opdrachten: het zou dus kunnen
dat de opdrachten nooit worden uitgevoerd.
Een ander extreem geval doet
zich voor wanneer de test altijd waar is. We spreken
dan om begrijpelijke redenen van een
oneindige lus.
Veel lusopdrachten hebben met bovenstaand voorbeeld het volgende gemeen:
Dit soort lussen kan eigenlijk handiger worden uitgedrukt
met een for-opdracht. De algemene vorm van
de for-lus in Java luidt
for (<initialisatie> ; <voorwaarde> ; <increment>) {
<opdrachtenblok>
}
waarbij <initialisatie> en
<increment> gewone Java-opdrachten zijn.
De interpretatie van de for kan worden uitgedrukt
met behulp van een while:
<initialisatie> ;
while (<voorwaarde>) {
<opdrachtenblok>
<increment> ;
}
Het vorige voorbeeld kan korter worden herschreven als
class Delers {
public static void main(String[] args) {
int deeltal, i;
deeltal = 1440;
for (i = 1; i <= deeltal; i = i + 1)
if (deeltal % i == 0)
System.out.println(i);
}
}
De derde en laatste soort lusopdracht in Java is de do-opdracht.
Ze onderscheidt zich van de vorige twee doordat de test op het einde van de
lus plaatsvindt. Het opdrachtenblok wordt dus altijd minstens één
keer uitgevoerd.
De algemene vorm luidt
do {
<opdrachtenblok>
}
while (<voorwaarde>) ;
(Merk op dat dit de enige van de drie lusopdrachten is waar de lusopdracht zelf op een kommapunt moet eindigen - in de andere twee gevallen maakt de sluitende accolade van het opdrachtenblok zulks overbodig.)
We geven nog eens een derde versie van het programma om delers
van 1440 te zoeken. Dit keer testen we op het einde van de lus,
weliswaar vlak nadat we de waarde van de teller i
verhoogd hebben.
class Delers {
public static void main(String[] args) {
int deeltal, i;
deeltal = 1440;
i = 1;
do {
if (deeltal % i == 0)
System.out.println(i);
i = i + 1;
}
while (i <= deeltal);
}
}
Schrijf een Java-programma dat twee veranderlijken a
en b van het type int
initialiseert met twee positieve getallen, en dat vervolgens hun
grootste gemene deler zoekt volgens het algoritme van Euclides:
a het grootste is.a door b.a door b, en b door de zojuist
berekende rest.b nu nul is, dan is a de grootste gemene deler.
Anders beginnen we opnieuw vanaf stap 2.Gestructureerd programmeren vereist dat we een complex probleem kunnen splitsen in deelproblemen. Het is dan ook normaal dat onze programmeertaal toelaat, deze opsplitsing expliciet aan te geven in de vorm van het programma.
Een deelprogramma in Java wordt een methode genoemd. Methoden maken steeds deel uit van een klasse. Een methode wordt gekenmerkt door:
Methoden die geen betrekking hebben op een welbepaald object
(zie volgend hoofdstuk) heten statisch. Zolang we nog niet
met object-georiënteerd programmeren bezig zijn, dragen al
onze methoden het sleutelwoord static.
De algemene vorm van een methode luidt voorlopig
public static <terugkeertype> <naam> (
<parametertype> <parameternaam> ,
<parametertype 2> <parameternaam 2> , ... ) {
<opdrachtenblok>
}
Hierin is
<terugkeertype> de naam van een geldig Java-gegevenstype
(bijvoorbeeld int, double of String). Dit
is het type van de uitdrukking die na afloop door het deelprogramma aan het
hoofdprogramma wordt doorgegeven. Indien er zo geen dergelijke uitdrukking is,
geven we dit aan met het sleutelwoord void.<naam> een zelfgekozen naam voor de methode. Dezelfde naam
mag gebruikt worden voor methoden in verschillende klassen. Zelfs binnen dezelfde
klasse kan een naam worden hergebruikt, op voorwaarde dat het aantal of het type
van de parameters verschillende is. Het hergebruik van een methodenaam binnen dezelfde
klasse heet overladen.<parametertype>, <parametertype 2>,...
de naam van een geldig Java-gegevenstype.
Dit is het type van een uitdrukking die het hoofdprogramma aan het deelprogramma
meegeeft bij de start van dit laatste.<parameternaam>, <parameternaam 2>,...
een zelfgekozen naam waaronder de
door het hoofdprogramma meegegeven uitdrukking binnen het deelprogramma
bekend staat. Een parameter gedraagt zich zoals een veranderlijke. Als
een parameter voorkomt als het linkerlid van een toekenningsopdracht,
dan is het effect van die toekenningsopdracht slechts geldig tot bij het
einde van het deelprogramma. Methoden zonder paramters zijn ook mogelijk:
de haakjes zijn evenwel steeds verplicht.<opdrachtenblok> de reeks opdrachten (declaraties,
toekenningen en andere) die het gedrag van de methode definiëren. De bijzondere
opdracht return; of return <uitdrukking> beëindigt
de methode en definieert desgevallend de waarde van de uitdrukking die aan het
hoofdprogramma wordt teruggegeven.De volgende code declareert een methode die twee gehele getallen als parameter neemt, en die een derde geheel getal teruggeeft.
public static int machtsverheffing(int grondtal, int exponent) {
int teller;
int resultaat;
resultaat = 1;
for (teller = 0; teller < exponent; teller = teller + 1)
resultaat = resultaat * grondtal;
return resultaat;
}
In al het bovenstaande zijn de begrippen "hoofdprogramma" en "deelprogramma"
natuurlijk relatief: een methode kan op haar beurt andere methoden aanroepen.
En zelfs het ultieme "hoofdprogramma" is zelf een methode, namelijk
main.
Het aanroepen van een methode is gewoon een opdracht die deel uitmaakt van het hoofdprogramma. De vorm van een methode-aanroep binnen dezelfde klasse is
<naam> ( <uitdrukking> , <uitrukking 2 2> , ... );
Als een methode een terugkeertype heeft (dus niet void),
dan is een methode-aanroep ook een uitdrukking. Het type van die uitdrukking
is het terugkeertype van de methode zelf. Deze uitdrukking kan dan bijvoorbeeld
optreden in het rechterlid van een toekenningsopdracht, of zelf een parameter
vormen van een andere methode-aanroep, enzovoort.
Als een methode wordt aangeroepen vanuit een andere klasse dan die waarin ze gedefinieerd is, moet haar naam worden voorafgegaan door de naam van haar klasse en een punt.
De methode machtsverheffing uit het vorige voorbeeld kan als
volgt worden gebruikt om het getal 25 te berekenen en het
resultaat op te slaan in de veranderlijke aantal.
aantal = machtsverheffing(2, 5);
Als de methode machtsverheffing gedefinieerd is in een klasse
met de naam Rekenwerk, en we willen haar oproepen vanuit een
methode van de klasse Boekhouding, dan zie dat er als
volgt uit.
aantal = Rekenwerk.machtsverheffing(2, 5);
Je kan ook complexere uitdrukkingen evalueren, zoals hier bijvoorbeeld (52 + 54)3:
aantal = machtsverheffing(machtsverheffing(5, 2) + machtsverheffing(5, 4), 3);
We herschrijven het voorbeeldprogramma BTW.java van paragraaf 2.6
zodat de drie grote groepen opdrachten in drie afzonderlijke methoden
worden gegroepeerd. We maken daarbij gebruik van
terugkeerwaarden om de twee numerieke
gegevens aan het hoofdprogramma door te spelen. Elke methode die gebruik
maakt van het toetsenbord, zelfs onrechtstreeks (via een methode-aanroep,
zoals main dat doet) dient throws IOException te vermelden.
Opgelet: een methode heeft geen toegang tot de veranderlijken die in andere methoden (dus in een ander blok) gedeclareerd worden. Parameters en terugkeerwaarden zijn voorlopig de enige mechanismen die ons ter beschikking staan voor communicatie tussen hoofd- en deelprogramma's.
import java.io.*;
class BTW {
/** Stel een vraag aan de gebruiker en lees het getal
* met vlottende komma dat hij/zij vervolgens invoert.
* De enige parameter is:
* vraag: de tekst van de vraag aan de gebruiker
* De terugkeerwaarde is:
* het gelezen getal
*/
public static double leesGetal(String vraag) throws IOException {
BufferedReader toetsenbord;
toetsenbord = new BufferedReader(new InputStreamReader(System.in));
System.out.println(vraag);
String invoertekst;
invoertekst = toetsenbord.readLine();
return Double.parseDouble(invoertekst);
}
/** Bereken het BTW-bedrag en de verkoopprijs inclusief BTW
* en druk deze resultaten af op het scherm.
* De parameters zijn:
* netto: de verkoopprijs exclusief BTW
* btw: de BTW-aanslagvoet, in percent
*/
public static void berekenResultaten(double netto, double btw) {
double btwBedrag;
btwBedrag = netto * btw / 100;
double brutoBedrag;
brutoBedrag = netto + btwBedrag;
System.out.println("bedrag BTW: " + btwBedrag);
System.out.println("bruto verkoopprijs: " + brutoBedrag);
}
/** Vraag de gebruiker een verkoopprijs exclusief BTW en
* een BTW-voet, en geef hem/haar het BTW-bedrag en
* de verkoopprijs inclusief BTW.
*/
public static void main(String[] args) throws IOException {
double nettoBedrag;
nettoBedrag = leesGetal("netto verkoopprijs ?");
double btwPercent;
btwPercent = leesGetal("BTW-voet (in percent) ?");
berekenResultaten(nettoBedrag, btwPercent);
}
}
Schrijf een methode faculteit met één parameter
van het type int en met terugkeertype int die de
faculteitsfunctie uit de combinatieleer berekent. Het getal "n
faculteit", meestal genoteerd met een uitroepteken (n!), is het
product van alle positieve gehele getallen tot en met n:
| 0! | = | 1 | ||
| 1! | = | 1 | = | 1 |
| 2! | = | 1 . 2 | = | 2 |
| 3! | = | 1 . 2 . 3 | = | 6 |
| ... | ||||
Vul het werk aan met een methode main die een getal n van het
toetsenbord leest, en het resultaat n! afdrukt.
Een interessant aspect van Java is dat methoden ook zichzelf kunnen aanroepen. Dit fenomeen noemen we recursie. In het volgende voorbeeld wordt de eigenschap van machtsverheffing gebruikt dat
Als de exponent groter is dan 0, gebruiken we recursie om de machtsverheffing te reduceren tot een andere machtsverheffing met een exponent die één eenheid lager is.
import java.io.*;
class MachtsRecursie {
public static int machtsverheffing(int grondtal, int exponent) {
if (exponent == 0)
return 1;
else
return grondtal * machtsverheffing(grondtal, exponent - 1);
}
public static void main(String[] args) throws IOException {
BufferedReader toetsenbord;
toetsenbord = new BufferedReader(new InputStreamReader(System.in));
System.out.println("grondtal: ");
int grondtal;
grondtal = Integer.parseInt(toetsenbord.readLine());
System.out.println("exponent: ");
int exponent;
exponent = Integer.parseInt(toetsenbord.readLine());
int macht;
macht = machtsverheffing(grondtal, exponent);
System.out.println(grondtal + " tot de macht " + exponent
+ " is gelijk aan " + macht);
}
}
Herschrijf je antwoord op oefening 3.6 door gebruik te maken van de volgende recursieformule:
| n! | = | n . (n - 1) als n > 0; |
| 0! | = | 1. |
Schrijf een Java-programma dat de (oneindige) rij der Fibonaccigetallen afdrukt:
1 1 2 3 5 8 13 21 ...
De rij begint met tweemaal 1, en elk volgend getal is de som van de twee voorgaande.
Natuurlijk kan geen enkele computer dit programma oneindig lang laten lopen. Op een bepaald moment worden de getallen zo groot dat het geheugen van alle bestaande computers tesamen niet volstaat om er één van voor te stellen ! Neem passende maatregelen om je programma elegant te laten stoppen wanneer de getallen te groot worden.
Een korte geschiedenis van de Fibonaccigetallen, en vele van hun merkwaardige eigenschappen, staan vermeld in paragraaf 1.2.8 van [Knu].