1.1-Introductie-Programmeren

Theorie Methods 3

Competentie: Ik kan (statische) methoden met argumenten en return types maken.

Specifieke leerdoelen:

De opdrachten kunnen gevonden worden in de losse modulen.

Samenvatting

Een methode is een verzameling van instructies (coderegels) die samen een bepaalde functie vervullen. Tot nu toe heb je methoden alleen gebruikt om bepaalde taken uit te laten voeren, zoals printLine of pause.

In het algemeen worden methoden gebruikt om drie dingen te doen:

De eerste twee van deze voordelen (codehergebruik / voorkomen van codeduplicatie en verbetering van de leesbaarheid), zullen onder deze competentie vallen. De laatste, het verbergen van implementatiedetails, zal in een latere module aan bod komen.

Alle methoden hebben een vergelijkbare structuur:

public <returnType> methodName(<parameters>) {
      // One or multiple lines of code, including a "return" statement.
}

(Merk op dat we in deze cursus alleen public methoden zullen gebruiken.)

Een voorbeeldmethode zou er als volgt uit kunnen zien:

/**
 * Method that determines whether a number is even or odd.
 */
public boolean isEven(int number) {
    if(number % 2 == 0) { // The number is even.
        return true;
    } else {
        return false;
    }   
}

Deze methode bepaalt of een geheel getal (bekend als nummer binnen deze methode), even of oneven is. In het geval van een even getal zal de methode “true” opleveren, anders (bij oneven) “false”. Een methode heeft altijd een eerste regel die wat informatie over de methode zelf bevat, ook wel bekend als de method signature. (de regel voor de {... }).

Delen die moeten worden ingevuld uit de method signature zijn het returntype (in dit geval boolean) en de naam van de methode (bijv. isEven). Methodennamen worden geschreven in camelCase (wat betekent dat de eerste letter van het eerste woord begint met een kleine letter, gevolgd door hoofdletters voor alle overige woorden) en moeten uniek zijn voor een bepaald toepassingsgebied (bijv. binnen SaxionApp). Wat een methode oplevert kan van elk type zijn dat bekend is in de applicatie, dus booleans, int’s, doubles en Strings kunnen ook worden geretourneerd. Alternatief kan een methode ook void als returntype hebben, wat betekent dat de methode niets oplevert. (Void betekent ‘leegte’ in het Engels.)

Eventuele parameters die de methode nodig heeft staan tussen de haakjes ((...) ). Van elke parameter moet het type worden vermeld, gevolgd door een variabele naam die helpt bij het identificeren van de variabele binnen de code van de methode. Elke methode kan 0 of meer parameter(s) hebben. De gegevens die u aan de parameter doorgeeft, worden argumenten genoemd.

Het is goed om te onthouden dat alle methoden, die geen returntype void hebben, een return-statement moeten hebben in alle mogelijke uitkomsten. Aangezien isEven een if-else verklaring heeft, worden er twee returnstatements gebruikt om beide situaties te behandelen. Er is geen maximum aan het aantal returnstatements dat je in een methode kunt hebben, zolang er tenminste één is (tenzij het returntype void is, dan is het retouroverzicht optioneel).

Merk op dat de bovengenoemde methode ook kan worden afgekort tot slechts één regel (maar was niet voor onderwijsdoeleinden):

public boolean isEven(int number) {
    return number % 2 == 0;
}

Een methode kan vervolgens worden gebruikt zoals je ook al eerder hebt gezien:

// From within the run() method or other methods:

int someValue = 3;

boolean someValueIsEven = isEven(someValue);

SaxionApp.printLine("Is " + someValue + " even? " + someValueIsEven);

Merk op dat er geen verband is tussen de namen van de variabelen someValue (in de run() methode) en nummer (binnen de methode isEven). Alleen de werkelijke waarde (3) wordt doorgegeven aan de methode, zonder rekening te houden met de naam van de variabele waarin deze kan worden opgeslagen vanuit elke aanroepende methode.

Introductie

In deze wereld is al veel code geschreven en beschikbaar gesteld door andere programmeurs. Het grootste deel van deze code komt in de vorm van classes (die je later in deze cursus leert kennen) en methods. En aangezien jij nu ook een programmeur bent, wordt het tijd dat u ook een bijdrage levert aan de enorme wereld van de code. En we gaan beginnen om dit te doen door te leren hoe je zelf een eigen methode kan schrijven.

Zoals je geleerd hebt, zijn methoden blokken van code en instructies die samen een specifieke functie uitvoeren. Je hebt methoden gebruikt om tekst af te drukken naar het scherm, een lijn te trekken over het scherm, rechthoeken te creëren, etc. etc. Je hebt ook methoden gebruikt om wat berekeningen voor je te doen zoals bijv. een willekeurige waarde te bepalen of een kleur te maken.

In deze sectie leer je meer over hoe je methoden kunt maken en wat goede praktijken zijn in het maken van methoden. Dus laten we aan de slag gaan en kijken hoe je dit doet. Vreemd genoeg is het eerste wat we moeten bespreken is hoe je een methode bedenkt en welke naam je deze geeft.

Over het doel van een methode en de naam

Als je programmeurs vraagt hoe ze zich alle methodes herinneren die ze zouden gebruiken, dan krijg je het antwoord dat ze over het algemeen dit niet doen. Met meer ervaring ga je namelijk patronen zien in de namen van methoden en op een gegeven moment kun je bijna raden wat een methode zal doen op basis van zijn naam. Dit betekent echter wel dat je je moet houden aan een aantal goede praktijken qua naamgeving, zodat je andere programmeurs niet op het verkeerde been zet.

Voor nu, onthoud dit: 1) Een methode naam wordt altijd geschreven in camelCase (of eigenlijk dromedaris-case). Dit betekent dat de eerste letter van een methode begint met een kleine letter, gevolgd door elk volgende woord met een hoofdletter. Enkele voorbeelden printLine, drawLine, saveImage, generateRandomNumber, enz. 2) Probeer een werkwoord in de naam van uw methode te krijgen. printLine bevat het werkwoord ‘print’ dat u een idee moet geven van wat de methode doet. 3) Een naam van de methode moet zo klein mogelijk worden gehouden en toch gemakkelijk te begrijpen zijn, dus houd de naam van de methode kort (maar maak het niet te gek): printLine is goed genoeg, in vergelijking met printLineOnSaxionApp(...) of method1(...). 4) Een methode moet altijd maar een ding te doen en dit goed te doen. printLine is duidelijk: je verwacht maar één ding… Een methode als promptUserForInputCalculateAverageWriteResultToFile zou wel eens te groot kunnen zijn.

Nog meer voorbeelden over hoe je je methoden niet wil noemen:

public void awesome() {...}       // While obvioulsy an awesome method.. what does it do? 
public void makeXAwesome() {...}  // Slightly better, but what does "making something awesome" mean? 
public void exerciseOne() { ... } // Yeah.. what exercise? 
public void partOne() { .. }      // I guess it comes before partTwo()?

Het kiezen van een juiste methode naam is in eerste instantie best moeilijk, maar zal in de loop van de tijd gemakkelijker worden. Wees voorlopig niet bang om de naam van een methode te veranderen om het echte doel weer te geven nadat je een aantal veranderingen hebt aangebracht. Het veranderen van een methode naam in IntelliJ is heel eenvoudig… dus wees niet bang om het te doen!

Oké! Nu we een idee hebben hoe we onze creaties een naam kunnen geven, laten we een methode bouwen! Maar niet voor waar we ze moeten maken…

Waar maak je een methode?

Methodes behoren altijd tot een aantal “class” (we zullen het in de toekomst meer over klassen hebben), zoals de SaxionApp of Application. Voorlopig richten we ons alleen op het maken van methodes in onze eigen Applicaties, dus laten we eens kijken naar de algemene lay-out van ons Applicatiebestand en waar onze methodes aan toegevoegd moeten worden:

import ... // Import statements come ALWAYS first.

public class Application implements Runnable { // This is always the same for this course.. Note the "open" curly bracket. All code between these brackets belong to "our Application".

    public static void main(String[] args) { ...  } // This method is always in your Application file.

    public void run() { ... } // this method is also always in your Application file.

    public void yourMethodGoesHere() { ... } // Your code goes in here !!
   
    public int yourOtherMethodGoesHere() { ... }  // And here.. etc!

} // <-- Note this curly bracket. You need this one to close your program!

De accolades ({...}) worden in Java gebruikt om “blokken” te vormen (ook wel “scope” genoemd), vergelijkbaar met wat je hebt gezien met de if-statements en while-statements. Hetzelfde geldt voor methodes… je kunt ze gewoon “onder” (of boven, volgorde maakt niet uit) je bestaande run en main methoden plaatsen. (Je moet die twee methoden wel behouden!).

Merk op dat variabelen binnen afzonderlijke methoden van elkaar geïsoleerd zijn, wat betekent dat je geen toegang kan krijgen tot variabelen die gedeclareerd zijn in een andere methode. Dus wanneer je een methode maakt, worden alle variabelen die binnen die methode worden gebruikt automatisch “verborgen” voor elke andere methode.

Heb je een geschikte plek gevonden voor je methode en weet je zeker dat je niet tegen scopingproblemen aanloopt? Dan is het tijd om er een te maken! Laten we eens kijken naar de lay-out van alle methoden.

Algemene layout van een methode

Elke methode ziet er ongeveer hetzelfde uit, namelijk:

public <someReturnType> methodName(<listOfParameters>) {
  // Lines of code..
}

De eerste regel van een methodedefinitie voor de gekrulde haakjes ({...} ) staat bekend als de method signature (of te wel: handtekening van de methode). Deze signature wordt gebruikt om wat meer informatie te geven over uw nieuw aangemaakte methode. Het bevat op zijn minst de naam van de methode (die je kunt gebruiken om de methode aan te roepen), een lijst van parameters die de methode nodig heeft en het eventuele datatype dat de methode zal teruggeven (ook wel return-type genoemd). We hebben de naamgevingsconventies al eerder besproken, dus dat laten we nu even rusten.

Het woord public voor deze cursus moet als verplicht worden beschouwd (dus altijd gebruiken). Er zijn andere woorden die je hier zou kunnen zetten (zoals static, private, abstract, etc.) maar al deze onderwerpen vallen buiten de scope van deze module.

Voordat we een methode kunnen opstellen moeten we eerst iets meer weten over return-typen en de parameters.

Return-typen

Methoden worden met name gebruikt omdat ze herbruikbaar zijn en om de structuur en leesbaarheid van applicaties te verbeteren. In het algemeen heb je twee soorten methoden, methoden die een specifieke functie uitvoeren en direct effect hebben op je applicatie (zoals een lijn tekenen, tekst printen, etc.) of methoden die wat berekeningen kunnen doen of wat gegevens voorbewerken voor je om later te gebruiken. In de eerste situatie kan een methode gewoon doen wat het moet doen en zijn er verder weinig verwachtingen, zoals bijv. bij printLine. Je ziet iets op het scherm verschijnen en meer gebeurt er ook niet.

Het andere soort methoden moet worden gezien als methoden die gedeeltelijke resultaten kunnen opleveren, zoals bijv. het lezen van een geheel getal readInt van de gebruiker en het gebruik van die waarde om iets anders mee te doen (readInt is dus een beginstap). In dat geval kan je de methode als een “robot” (of “werker”) zien die een taak voor je uitvoert en met het resultaat van die taak mee terugkomt. Een methode levert dus ook daadwerkelijk een resultaat op wat je vervolgens kan gebruiken.

Methoden die verondersteld worden iets terug te geven, moeten in hun method signature aangeven wat voor soort waarde ze gaan teruggeven. Dit kunnen alle types zijn die je al eerder hebt gezien (en nog veel meer). De plek waar je dit moet aangeven is het 2e woord in de method signature (direct na public).

Een voorbeeld van een zeer eenvoudige methode:

public int plus(int valueOne, int valueTwo) {...}

In dit voorbeeld verklaart de plus methode dat het een integer zal opleveren. Voor het gemak laten we de implementatie van de methode even weg, maar je kan je vast voorstellen wat deze methode precies moet doen.

Laten we deze methode eens gebruiken! Dit kan je op twee manieren doen, los-staand (als statement) of als onderdeel van een grotere instructie (als expressie). Dit ziet er uit als:

SaxionApp.printLine("The result of 3 + 4 is " + plus(3, 4)); 

of

int result = plus(3, 5) + plus (10, 15);

Als je een methode schrijft die in principe niets hoeft op te leveren (zoals bijvoorbeeld printLine of drawHouse) moet je alsnog een return type opgeven. Het veld kan namelijk niet zomaar open gelaten worden. Om echter aan te geven dat je niets wil opleveren is er daarom een speciaal return-type bedacht genaamd void (wat ook wel “leegte” betekent in het Engels). Methoden die een void opleveren zijn dus methoden die geen waarde opleveren (en kan je dus ook niet opvangen). Ook is het niet mogelijk om een “void” op te slaan in een variabele.

public void drawHouse(int xCoordinate, int yCoordinate) {...} 

Methoden die void opleveren kunnen niet worden gebruikt in variabelen of andere instructies. Ze kunnen alleen worden gebruikt als afzonderlijke, op zichzelf staande instructies, zoals:

SaxionApp.printLine("Hi!");

drawHouse(10, 20);

Nu we weten wat return-typen zijn is het tijd om het laatste onderdeel te bekijken: parameters.

Parameters

Het laatste deel van de method signature is de lijst met parameters. Voor elke parameter moet je het type specificeren en deze een naam geven, vergelijkbaar met andere variabelen die je hebt gemaakt. Dit zou eruit kunnen zien:

public int plus(int valueOne, int valueTwo) {...}

In dit geval moeten twee parameters (waarde One en waardeTwo) van het type int worden meegegeven tijdens de aanroeping van de methode, zoals:

int result = plus(3, 5);

In alle andere situaties (met meer of minder parameters, parameters van het verkeerde type, etc.) zal Java een fout produceren.

Methoden hoeven geen parameters te hebben, zoals de pauze of clear methoden van SaxionApp of de run methode waarin je vaak je code zet. In dat geval hoef je geen type of naam op te geven, maar hoef je alleen de verplichte haakjes neer te zetten.

public void run() { ... } 

Technisch gezien is er geen limiet aan het aantal parameters dat een methode kan hebben, maar het is vrij gebruikelijk om “maximaal” 4 parameters te hebben (dit raden we je ook aan om als limiet te gebruiken). Het hebben van meer dan 4 parameters betekent meestal dat je methode “te veel” wil laten doen, welk punt 4 (een methode moet een ding en een ding goed doen) eigenlijk teniet doet. Nogmaals, maak je hier in dit stadium niet te veel zorgen over, maar probeer je methoden gaandeweg te verbeteren.

Subtiele verschillen: Parameters of Argumenten

Als je over het maken en gebruiken van methoden leest, zie je waarschijnlijk twee woorden opduiken die bijna hetzelfde betekenen (maar ook weer net niet): parameters en argumenten. En hoewel ze zeer nauw verwant zijn, is er een (formeel) verschil. Een parameter is een variabele in een methodedefinitie (zoals we al eerder hebben laten zien), een argument is iets wat je invult tijdens het aanroepen van een methode.

Dus, in onderstaande voorbeeld zijn de variabelen valueOne en valueTwo parameters van de methode plus.

public int plus(int valueOne, int valueTwo) {...}

en zijn “3” en “5” de argumenten van de aanroep in het voorbeeld hieronder.

int result = plus(3, 5);

Tijd om alles samen te voegen

Laten we een methode maken die van 3 meegegeven waarden kan bepalen wat de hoogste waarde is. We noemen de methode findMax en gaan deze methode 3 integer waarden meegeven waarna vervolgens de hoogste waarde zal worden teruggegeven. Het return-type van deze methode is dus een integer, gelijk aan het type van de parameters.

Stap één is het creëren van het skelet en het invullen van de methods signature zoals we die hebben ontworpen:

public int findMax(int valueOne, int valueTwo, int valueThree)  {
    // Nothing here yet!
}

Als je deze code nu aan IntelliJ zou toevoegen, zal dat een fout opleveren. De methode belooft namelijk een integer terug te geven, maar geeft nog niets terug. We kunnen dit veranderen door gebruik te maken van een return-statement in onze code.

public int findMax(int valueOne, int valueTwo, int valueThree)  {
    return 0; // Fixed the compilation issue - The answer isn't correct, but now your program runs!    
}

Het woordje “return” geeft de computer de opdracht om uw methode daar te beëindigen en het geleverde resultaat terug te sturen (naar de plek van de aanroep van de methode).

Laten we het voorbeeld afronden en een voorbeeldinvulling geven. Er zijn natuurlijk meerdere implementaties mogelijk… maar laten we aannemen dat je zoiets hebt gemaakt:

public int findMax(int valueOne, int valueTwo, int valueThree)  {
    int maximum = valueOne; // Assume valueOne is the maximum value.
    
    // Check whether or not valueTwo is larger than max.. if so, it becomes the new maximum.
    if(valueTwo > maximum) {
        maximum = valueTwo;
    }

    // Repeat for valueThree
    if(valueThree > maximum) {
        maximum = valueThree;
    }
   
    // Return the found maximum.
    return maximum;
}

Zoals je kunt zien, hebben we een aantal (inefficiënte) codes toegepast om de maximale waarde te bepalen en het maximum aan het einde van de code te “returnen”. Stel nu dat je nu met een andere programmeur in discussie bent geraakt die beweert dat hij/zij het beter kan en de volgende code produceert:

public int findMax(int valueOne, int valueTwo, int valueThree)  {
    if(valueOne >=  valueTwo && valueOne >= valueThree) {
        return valueOne; // If valueOne is larger (or equal) to both valueTwo and valueThree, it must be the maximum.
    } else if(valueTwo >= valueOne && valueTwo >= valueThree) {
        return valueTwo; // Similar conclusion, now for valueTwo.
    } else {
        return valueThree;
    }
}

Deze methode vereist wel wat meer “return” statements (wat ok is - je mag net zoveel “returns” gebruiken als je wilt!), maar over het algemeen is het aan jou om te beslissen welke versie van de findMax methode je wilt gebruiken in jouw programma. Je kan ze echter niet allebei tegelijk opnemen (een method signature moet uniek zijn)!

Merk op dat de handtekening van de methode verder niet is veranderd, het heeft alleen een andere (en mogelijk efficiëntere) implementatie gekregen, wat betekent dat alle andere onderdelen van je applicatie die deze methode gebruiken ook niet hoeven te worden aangepast. Dit is ook wel wat bekend staat als verbergen van de implementatie. Alle gebruikers van jouw methode hoeven op zich niet te weten hoe jouw methode werkt.. maar kunnen wel gebruik maken van jouw findMax methode. De exacte werking wordt dus verscholen voor andere programmeurs (net zoals wij met de SaxionApp gedaan hebben!).