Informaticasite van het Lauwers College te Buitenpost                 © R.J. van der Beek
 

Hoofdstuk 18 Java

Hoofdstuk 18.8. Zelf een klasse maken, messagebox

 18.8.1 Een methode Gok(int kleinste,int grootste).

In paragraaf 18.4 hebben we een applet gemaakt waarin optelsommen verschenen.
We hebben daar op een bepaalde manier er voor gezorgd dat de computer willekeurige getallen tussen -50 en 50 bedacht.
De opdrachten daarvoor zagen er zo uit:
int g1=(int)Math.ceil(100.0*Math.random( ) );
g1=g1-50;


Dat is best ingewikkeld om te onthouden.
En wat zou je er van moeten maken als je getallen tussen 40 en 80 zou willen hebben ?
Of tussen 1 en 6, zoals bij een dobbelsteen gebeurt ?
Het zou best handig zijn als je als opdracht zou kunnen geven:
int a = gok(40,80);
waarbij die a dan een waarde tussen 40 en 80 zou krijgen.
Dat is best mogelijk maar dan moet je zelf een methode daarvoor schrijven in je programma.
Die zou er als volgt uit kunnen zien:

    int Gok(int kleinste,int grootste)
    {
        int t=kleinste-grootste;
        double d=(double)t*Math.random( ) + (double)kleinste;
        int g=(int)Math.ceil(d);
        return g;
    }

Als je nu de opdracht a=Gok(40,80); geeft dan wordt de methode die hier boven staat uitgevoerd. Dan krijgt de parameter kleinste de waarde 40, en de parameter grootste krijgt de waarde 80 (de variabelen tussen de haakjes bij een methode worden parameters genoemd)
Er komen in de methode ook nog drie lokale variabelen voor: t, d en g
De laatste opdracht is return g, en dat heeft tot gevolg dat de waarde die a krijgt bij de opdracht a = gok(40,80); gelijk aan de waarde van g wordt. Dat wordt wel de terugkeerwaarde genoemd.

Elke methode eindigt dan ook met een return-opdracht waarin staat welke waarde er mee gegeven wordt, behalve als het een void-methode is. Want void betekent leeg, en dan is er geen terugkeerwaarde.
En elke methode begint met het type van de terugkeerwaarde, dat is hier int
Daarom staat er int Gok(int kleinste,int grootste)

 18.8.2 De klasse Gok.

Je kunt er dus voor zorgen dat je de opdracht a=Gok(40,80); kunt gebruiken door er een methode voor te schrijven.
Als je zo'n opdracht in een heleboel verschillende programma's gebruikt dan moet je in elk programma die methode invoeren.
Het kan ook anders, namelijk als je er een aparte klasse voor maakt.
Die klasse kun je dan in elk programma gebruiken.

Als je zelf een klasse wilt maken waarmee je een willekeurig getal kunt genereren, dan kun je dat op de volgende manier doen:
In JCreator klik je op File, en dan op New.
In het volgende venster klik je op het tabblad Files, en je klikt op Java Files. Als naam van de file voer je in Gok.
En dan voer je het volgende in:

public class Gok
{
    // attributen
    private int min;
    private int max;
    private int gokgetal;
    // constructor
    public Gok(int kleinste,int grootste)
    {
        min=kleinste;
        max=grootste;
        gokgetal=kleinste;
    }
    // methoden

    public int getGokgetal()
    {
        int t=max-min;
        double d=(double)t*Math.random( ) + (double)min;
        gokgetal=(int)Math.ceil(d);
        return gokgetal;
    }

    public int getMin()
    {
        return min;
    }

    public int getMax()
    {
        return max;
    }
}

Als je dit hebt ingetypt dan klik je op File, en dan op Save as.
Dan voer je als naam in: Gok (het moet beslist de zelfde naam zijn als de naam die achter class staat op de eerste regel.
De extensie wordt automatisch java, als er onder Opslaan als Java Files (*.java) staat. De naam wordt dan dus Gok.java

Als je deze klasse wilt gebruiken in een programma, dan moet je klikken op File en dan Open.
Dan krijg je een venster waarin je kunt aangeven om welke klasse het gaat.
Selecteer de klasse en klik dan op Add. Klik dan op OK.
Je moet er wel voor zorgen dat het klasse-bestand (dus Gok.class) in de zelfde map staat als het project waar je mee bezig bent, ( dus in de submap classes van de projectmap) anders krijg je een foutmelding als je het programma wilt uitvoeren !

 18.8.3 Private en public, attributen en constructor

Attributen
In de klasse Gok komen drie attributen voor. Dat zijn gewone variabelen, hier zijn dat allemaal integer-variabelen: min, max en gokgetal
Die zijn alledrie private. Wat daarmee bedoeld wordt merk je straks wel.

Constructor
Dan komt de constructor. Die heeft dezelfde naam als de klasse, dat is verplicht.
De constructor heeft nog twee parameters, want er staat: public Gok(int kleinste,int grootste)

Methoden
Daarna komen de methoden.. Elke methode eindigt met een return-opdracht waarin staat welke waarde er mee gegeven wordt, behalve als het een void-methode is.
En elke methode begint met het type van de terugkeerwaarde, bijvoorbeeld int

In een ander programma kun je nu de opdracht Gok a; gebruiken. Dan weet de computer dat er een instantie van de klasse Gok gebruikt zal worden, maar er wordt nog geen ruimte voor gereserveerd.
Dan kun je daarna de opdracht a=new Gok(40,80); gebruiken.
Dan wordt er echt ruimte gereserveerd voor de instantie a van de klasse Gok: er worden geheugenplaatsen voor de variabelen min, max en gokgetal aangewezen, en min krijgt de waarde 40 en max krijgt de waarde 80 en gokgetal krijgt de waarde 30. Want dat staat in de procedure die hoort bij de constructor.

Als er geen constructor is, of de constructor heeft geen parameters dan krijgen de getal-variabelen de waarde 0.

Je mag de constructor in je eigen programma gebruiken, want er staat public voor, en dat betekent dat die opdracht in elk programma gebruikt mag worden.

De attributen mag je niet in je eigen programma gebruiken, want die zijn private.
Dus als je de opdracht g=a.gokgetal geeft dan krijg je een foutmelding.
De variabele gokgetal mag alleen in de klasse Gok zelf gebruikt worden, verder niet.

De methoden mag je wel gebruiken, want die zijn weer public.
Dus als je aan de variabele g een willekeurig getal tussen 40 en 80 wilt toekennen, dan kun je daar de opdracht g=a.getGokgetal(); voor gebruiken.

In de klasse Gok hier boven staan de termen public en private er steeds voor.
Die mag je ook weglaten, maar dan worden de attributen automatisch private, en de constructor en de methoden public.

Bijna alle attributen uit klassen zijn private, dus die mag je niet gebruiken.
Er zijn een paar uitzonderingen.
In de klasse Color hebben je de kleur-attributen, en die zijn public. Bijv. Color.yellow mag je wel gebruiken.
En in de klasse Math heb je de attributen E en PI, die public zijn.
Dus Math.PI mag je gebruiken, en de waarde daarvan is 3.14159.....

Je kunt de klasse Gok bijvoorbeeld op de volgende manier in een applet gebruiken:

import java.awt.*;
import java.applet.*;

public class TestGok extends Applet 
{
	public void paint(Graphics g) 
	{
		Gok a = new Gok(40,80);
		int b = a.getGokgetal();
		String bs=String.valueOf(b);
		g.drawString(bs, 50, 60 );
	}
}

Een klasse hoeft niet beslist een constructor te hebben (dan krijgen de attributen automatisch de waarde 0 bij de initialisatie), en er hoeven ook niet beslist attributen te zijn. We hadden dus ook wel een klasse Gok met alleen de methode getGokgetal kunnen maken.

Behalve private en public zijn er nog meer termen in gebruik.
Protected, default, package, static, final, extends, super. Wat betekenen die ?
  • een static variabele heb je als niet elke instantie van een klasse een nieuwe variabele van dit type levert, maar als alle instanties samen één zo'n variabele hebben; dus het kan maar één waarde hebben.
  • een final variabele is een variabele die een constante waarde heeft, bijv. PI
    een final variabele is ook static.

  • Je hebt ook static methoden: die horen niet bij een bepaalde instantie van de klasse maar bij de klasse zelf.
  • extends wordt gebruikt als je een subklasse hebt, bijv. public class Button extends Object
    Dat betekent dat de klasse Button alle variabelen en alle methoden van de klasse Object bezit, met nog een aantal extra variabelen en/of methoden. Er wordt wel gezegd dat de klasse Button de methoden en variabelen van de klasse Object erft.
    Je hebt dus overerving of inheritance
    In dit geval wordt de klasse Button wel de subklasse genoemd en Object de superklasse.
  • Als de term final voor het woord class staat dan betekent het dat die klasse geen subklasse heeft.
  • Als de opdracht super() in een constructor wordt gebruikt dan verwijst dat naar de constructor van de directe superklasse.
  • Tot protected methoden of variabelen hebben alleen de methoden toegang die in de zelfde klasse-hierarchie zitten.
  • En je hebt alleen toegang tot package-methoden (of default-methoden) vanuit methoden in dezelfde package.

  • Een applet is zelf ook een klasse.
    Daarom begint de code (na de import-regels) altijd met public class naamapplet extends Applet
    De methoden en attributen van de klasse Applet worden dus overgeërfd.

    Een aantal van die methoden zijn:
    getParameter(String) Daarmee kun je via de html-code iets aan de applet doorgeven

    init() Deze methode komt in ieder geval in de applet voor. Zorgt er o.a. voor dat de browser weet dat de applet getoond moet worden.

    resize(int, int) geeft aan wat de breedte en de hoogte van het applet is.

    showStatus(String) zorgt er voor dat de string op de statusbalk verschijnt.

 18.8.4 Een nieuwe klasse Breuk

We maken een nieuwe klasse voor de opslag van een breuk. De naam van de klasse wordt ook Breuk.
We beginnen heel eenvoudig, en gaan de klasse in de volgende alinea's uitbreiden.
Het programma voor de klasse (beter gezegd: de implementatie van de klasse) is als volgt:

public class Breuk
{
      // de attributen
      private int teller;
      private int noemer;

      // constructor
      public Breuk()
      {
            teller = 0;
            noemer = 1;
      }

      // er zijn geen methoden
}

Voor de opslag van de gegevens van een breuk heb je een teller en een noemer nodig. Daarom worden twee attributen teller en noemer gedeclareerd. Teller en noemer kunnen alleen maar gehele getallen zijn, vandaar het type int.
Beide attributen zijn voor exclusief gebruik binnen de klasse, daarom staat er private bij de declaratie.

De klasse Breuk heeft ook een constructor, dat is de methode om objecten te kunnen aanmaken.
De constructor heeft dezelfde naam als de klasse en de attributen teller en noemer krijgen resp. 0 en 1 als startwaarde.

Een methode om het quotiënt uit te rekenen
Als je wilt weten wat de waarde van de breuk is, dan moet je een methode maken die de waarde uitrekent door de teller en de noemer op elkaar te delen.
De methode ziet er als volgt uit:

public double geefWaarde()
{
      return teller / noemer;
}

Deze methode krijgt de naam geefWaarde en is public. Dat betekent dat iedereen van buiten de klasse deze methode kan gebruiken.
Deze methode levert trouwens vaak een verkeerde waarde op, daarom gaan we hem verderop verbeteren.

Een extra constructor
We hebben een constructor voor een breuk, waarbij de teller 0 wordt en de noemer 1.
Die constructor heeft geen parameters.
We kunnen nog een constructor maken, als je er maar voor zorgt dat de verschillende constructoren verschillende types parameters hebben. Je kunt dus meerdere constructoren hebben bij een klasse, daar maken we hier gebruik van.
We maken een nieuwe constructor, zodat we gemakkelijk andere waarden voor teller en noemer dan 0 en 1 kunnen geven.
Deze constructor ziet er als volgt uit:

public Breuk(int te, int no)
{
      teller = te;
      noemer = no;
}

Deze constructor kun je dus gebruiken om objecten aan te maken die een andere waarde voor teller en noemer krijgen dan 0 en 1.

Set-methoden
We gaan methoden maken waarmee het mogelijk is een nieuwe waarde voor de teller of de noemer in een breuk in te stellen. Dit heeft als voordeel dat je geen nieuw object hoeft aan te maken als alleen maar de waarden van de teller en de noemer gewijzigd moeten worden. We maken twee nieuwe methoden: setTeller() en setNoemer().

public void setTeller(int te)
{
      teller = te;
}

public void setNoemer(int no)
{
      noemer = no;
}

Elk van deze twee methoden ontvangt een parameter; het is telkens de nieuwe waarde voor respectievelijk de teller of de noemer. Parameters zijn speciale variabelen die fungeren als doorgeefluik van gegevens buiten de klasse naar attributen.

Get-methoden
Je kunt ook methoden bijvoegen in de klasse waarmee het mogelijk is een waarde op te vragen. We maken twee nieuwe methoden: getTeller() en getNoemer().

public int getTeller()
{
      return teller;
}

public int getNoemer()
{
      return noemer;
}

Merk op dat een methode altijd met haakjes eindigt: ofwel staat er tussen die haakjes één of meer parameters, ofwel niks.
Verder zie je dat élke methode een return-opdracht heeft waarmee de return-waarde wordt gegeven: het resultaat van de methode (behalve als het return-type void is).

Een verbeterde methode geefWaarde()
Als je een aantal verschillende waarden voor teller en noemer probeert dan zul je zien dat het resultaat van geefWaarde() meestal niet klopt. Dit komt omdat de uitdrukking teller/noemer een twee gehele getallen op elkaar deelt, en dan gaat java er vanuit dat de uitkomst ook een geheel getal moet zijn.
Zo geeft de deling 7/2 het quotiënt 3 en niet 3.5.
Je kunt dit probleem oplossen door de teller om te zetten in een kommagetal. Dit doe je met geforceerde omzetting (casting): (double) teller

Door een type tussen haken te schrijven wordt de waarde die erop volgt omgezet naar dit type. We passen dit toe in de methode.
Verder is het zo dat je met de methode setNoemer() een 0 in de noemer van een Breuk-object kunt plaatsen, en dus bestaat de mogelijkheid om op een deling door 0 te botsen. Daarom moet je een beveiliging inbouwen die er voor zorgt dat er geen deling uitgevoerd wordt wanneer de noemer 0 is.

public double geefWaarde()
{
      if (noemer != 0)
      {
            return ((double) teller) / noemer;
      }
      else
      {
            return 0.0;
      }
}

Wanneer de methode geefWaarde() gestart wordt, kom je eerst bij de if. Er horen twee blokken bij deze if. In het eerste blok wordt de deling uitgerekend en teruggegeven met return; in het tweede blok wordt enkel een 0 teruggegeven. Het programma zal dan in ieder geval niet worden afgebroken door een deling door 0.

Breuken optellen
We gaan een methode maken die een andere breuk bij de huidige breuk optelt. Dan zal die methode een parameter moeten hebben die ook een breuk is, die bij de huidige breuk opgeteld moet worden. De parameter moet dus een ander object van dezelfde klasse zijn.
public Breuk telop(Breuk b)
{
      // p/q + r/s = (ps + qr) / qs
      int n = this.getNoemer() * b.getNoemer();
      int t = this.getTeller() * b.getNoemer() + this.getNoemer() * b.getTeller();
      return new Breuk(t,n);
}

Bij het gebruik van deze methode moet je er voor zorgen dat je het resultaat van de methode in een variabele van de klasse Breuk steekt, bv. breuk3 = breuk1.telop(breuk2);

Breuken vereenvoudigen
We gaan een methode maken die breuk probeert te vereenvoudigen.
Dan moet er gekeken worden of de teller en de noemer door hetzelfde getal gedeeld kunnen worden.
Als bijvoorbeeld teller % 7 gelijk aan 0 is, dan kan de letter worden gedeeld door 7. Want teller % 7 (spreek uit als teller modulo 7) is de rest bij delen van teller door 7, en als de rest 0 is dan komt de deling dus precies uit.
Als je bijvoorbeeld de breuk 253/398 hebt, dan moet eerst gekeken worden of de teller en de noemer allebei door 2 gedeeld kunnen worden. Daarna of ze allebei door 3 gedeeld kunnen worden. En zo verder, tot 253, dus tot het minimum van de teller en de noemer.
De code wordt dan als volgt:

public Breuk vereenvoudig()
{
      int n = this.getNoemer();
      int t = this.getTeller();
      int min = t;
      if (n<min) {min = n;}
      int d = 1;
      for (int i=2; i<min; i++)
      {
            if ((n % i == 0) && (t % i == 0))
            {d=i;}
      }
      return new Breuk(t/d,n/d);
}

De volledige klasse Breuk
Tenslotte volgt de volledige code van de klasse Breuk.

public class Breuk
{
      // De teller van de breuk
      private int teller;

      // De noemer van de breuk
      private int noemer;

      // Constructor voor Breuk met waarde 0/1
      public Breuk()
      {
            // initialiseer instantie variabelen
            teller = 0;
            noemer = 1;
      }

      // Constructor voor Breuk met externe startwaarden
      public Breuk(int te, int no)
      {
            // initialiseer instantie variabelen
            teller = te;
            noemer = no;
      }

      // plaats een nieuwe waarde voor de teller
      public void setTeller(int te)
      {
            teller = te;
      }

      // plaats een nieuwe waarde voor de noemer
      public void setNoemer(int no)
      {
            noemer = no;
      }

      public int getTeller()
      {
            return teller;
      }

      public int getNoemer()
      {
            return noemer;
      }

      // Bereken de waarde van de breuk
      public double geefWaarde()
      {
            if (noemer != 0)
            {
                  return ((double) teller) / noemer;
            }
            else
            {
                  return 0.0;
            }
      }

      public Breuk telop(Breuk b)
      {
            // p/q + r/s = (ps + qr) / qs
            int n = this.getNoemer() * b.getNoemer();
            int t = this.getTeller() * b.getNoemer() + this.getNoemer() * b.getTeller();
            return new Breuk(t,n);
      }

      public Breuk vereenvoudig()
      {
            int n = this.getNoemer();
            int t = this.getTeller();
            int min = t;
            if (n<min) {min = n;}
            int d = 1;
            for (int i=2; i<min; i++)
            {
                  if ((n % i == 0) && (t % i == 0))
                  {d=i;}
            }
            return new Breuk(t/d,n/d);
      }
}

Opgaven.
Maak nu opgave 7 van hoofdstuk 18 (Java)