28
Typer og variable (2.2.1-2.2.4, 2.2.7) En variabel er en ”kasse”, som kan rumme netop én værdi af en given type . Skal erklæres , før den kan bruges. int antalBiler; // en variabel af typen int, ved navn antalBiler Afsætter en mængde hukommelse i computeren til at gemme denne værdi. Navnet på en variabel bør Starte med et lille bogstav Skrives med camelCase Beskrive, hvad variablen skal bruges til Simpel brug: int antalBiler; // erklæring (værdi udefineret) antalBiler = 22; // tildeling (værdi 22) antalBiler = 42; // ændring (værdi 42) Variablen antalBiler er af typen int, hvilket betyder heltal (alle hele tal). Hvis vi ønsker at kunne gemme et kommatal, skal vi bruge typen double: double pi; pi = 3.1415926; // cirka... En tildeling kan også bestå af et regneudtryk på højre side: int antalDyr, antalKatte, antalHunde; antalKatte = 9; antalHunde = 6; antalDyr = antalKatte + antalHunde; // tildeling (værdi 15) En tekst-streng – hvilket bare er en sekvens af tegn – har typen String (med stort S) String hilsen = "Hej alle sammen"; // erklæring OG tildeling!

laerer.rhs.dklaerer.rhs.dk/psl/rhs/HHX-materiale/Programmering... · Web viewTyper og variable (2.2.1-2.2.4, 2.2.7) En variabel er en ”kasse”, som kan rumme netop én værdi af

Embed Size (px)

Citation preview

Typer og variable (2.2.1-2.2.4, 2.2.7)

En variabel er en ”kasse”, som kan rumme netop én værdi af en given type.Skal erklæres, før den kan bruges.

int antalBiler; // en variabel af typen int, ved navn antalBiler

Afsætter en mængde hukommelse i computeren til at gemme denne værdi.

Navnet på en variabel bør Starte med et lille bogstav Skrives med camelCase Beskrive, hvad variablen skal bruges til

Simpel brug:

int antalBiler; // erklæring (værdi udefineret)antalBiler = 22; // tildeling (værdi 22)antalBiler = 42; // ændring (værdi 42)

Variablen antalBiler er af typen int, hvilket betyder heltal (alle hele tal). Hvis vi ønsker at kunne gemme et kommatal, skal vi bruge typen double:

double pi;pi = 3.1415926; // cirka...

En tildeling kan også bestå af et regneudtryk på højre side:

int antalDyr, antalKatte, antalHunde;antalKatte = 9;antalHunde = 6;antalDyr = antalKatte + antalHunde; // tildeling (værdi 15)

En tekst-streng – hvilket bare er en sekvens af tegn – har typen String (med stort S)

String hilsen = "Hej alle sammen"; // erklæring OG tildeling!

Bemærk, at vi foretager erklæring og tildeling på samme linie. Det er helt legalt, og kan gøres for variable af alle typer.

Tekst-strenge er nyttige, når vi skal have skrevet noget ud på skærmen, og det er ret let at sætte en tekst-streng sammen af variable af forskellige typer:

int antalDyr, antalKatte, antalHunde;antalKatte = 9;antalHunde = 6;antalDyr = antalKatte + antalHunde;String besked = "Vi har i alt " + antalDyr + " dyr";

Hvis vi skriver besked ud, vil vi få følgende skrevet på skærmen:Vi har i alt 15 dyr

En lidt speciel type variabel er en logisk variabel (også kaldet en boolsk variabel), som kun kan have værdierne true eller false. Disse er nyttige til at repræsentere noget, som kun kan have to tilstande (viser mønten plat eller krone, er en person gift eller ej, og så videre…). Typen for denne slags variabel hedder boolean:

boolean levende = true;

Ligesom for andre typer variable kan vi have et regneudtryk på højre side af tildelingen, men i dette tilfælde skal det være et logisk udtryk:

int puls = 60;boolean levende = (puls > 0); // Dette er sandt, dvs. levende sættes til true

Input og output (2.1, 2.3.1)

Det er lidt besværligt, hvis vi skal ind og rette direkte i programmet, hver gang vi skal prøve programmet for forskellige værdier. Det er bedre, hvis vi kan give programmet noget input via skærmen og tastaturet, og få output på skærmen.

Output er ret nemt; vi har allerede set et eksempel:

System.out.println("Hello RHS"); // Skriver “Hello RHS” i output-vinduet

Dette er faktisk alt vi skal bruge i første omgang. Hvis vi har brug for noget output, sætter vi blot en passende tekst-streng sammen, og skriver den ud på samme vis:

int antalDyr, antalKatte, antalHunde;antalKatte = 9;antalHunde = 6;antalDyr = antalKatte + antalHunde;String besked = "Vi har i alt " + antalDyr + " dyr";System.out.println(besked);

Skulle vi have brug for ikke at skifte til en ny linie, når vi har skrevet en tekst-streng ud, bruger vi blot metoden print i stedet:

System.out.print("Vi har i alt ");System.out.println(antalDyr + " dyr");

Med passende kombinationer af print og println kan vi strikke nogle passende udskrifter af vores data sammen.

Input er en lille smule mere kompliceret. Vi har brug for at lave en objekt af typen Scanner (vi kommer tilbage til Klasser og Objekter senere), på denne vis:

java.util.Scanner tastatur = new java.util.Scanner(System.in);

Variablen tastatur kan nu bruges til at få input til programmet via tastaturet:

System.out.println("Skriv din alder herunder og tryk retur:"); int alder; alder = tastatur.nextInt(); // læs et helt tal fra tastaturet

Den første linie er blot noget ledetekst til brugeren, så han ikke undrer sig over, hvorfor programmet pludselig stopper op og venter. I den sidste linie vil programmet standse op og vente på, at brugeren taster et tal ind, efterfulgt af et tryk på Retur-tasten. Det tal brugeren taster ind vil blive gemt i variablen alder. Bemærk, at programmet vil give en fejl, hvis man taster noget andet end et helt tal ind.

Ønsker man at få en anden slags input end et helt tal, kan dette også lade sig gøre. Ønsker man et kommatal, skal nextInt erstattes med nextDouble:

System.out.println("Skriv din temperatur herunder og tryk retur:"); double temperatur; temperatur = tastatur.nextDouble(); // læs et kommatal fra tastaturet

Har man brug for at få en tekst-streng som input, kan dette gøres ved brug af nextLine:

System.out.println("Skriv dit navn herunder og tryk retur:"); String navn; navn = tastatur.nextLine(); // læs en tekst-streng fra tastaturet

Med disse muligheder kan man få langt de fleste typer relevant input fra en bruger

Betinget udførelse – if/else sætningen (2.3)

I langt de fleste interessante programmer vil variationer af input medføre, at programmet opfører sig forskelligt. For at opnå dette i et program må man have sætninger, som kan styre udførslen af programmet, alt efter om visse betingelser er opfyldt eller ej.

I Java hedder sådan en sætning en if-sætning. Den har følgende struktur:

if (betingelse) kode

Med almindelige ord; hvis betingelsen er sand, udføres den angivne kode, ellers ikke.Et eksempel på brug af en if-sætning kunne være:

if (alder < 18) System.out.println("Du er ikke voksen endnu! ");

Altså vil teksten kun blive udskrevet, hvis værdien af variablen alder er mindre end 18.

Grafisk ser logikken for en if-sætning således ud:

Det er afgørende at forstå, at det altså er værdien af alder – på det tidspunkt hvor if-sætningen bliver udført – der afgør, om teksten bliver skrevet ud.

Betingelsen i parentesen er et logisk udtryk, som vil have værdien false eller true. Betingelsen kan være meget simpel eller meget kompliceret, men det er altid et logisk udtryk. Til at sammenligne værdier kan man bruge forskellige logiske operatorer:

Operator Brug Forklaring> a > b a større end b>= a >= b a større end eller lig med b< a < b a mindre end b<= a <= b a mindre end eller lig med b== a == b a er lig med (identisk med) b!= a != b a forskellig fra b

Herudover kan man bruge parenteser i sit logiske udtryk. God brug af parenteser gør det ofte nemmere at læse og forstå udtrykket.

Ofte vil man gerne få programmet at udføre forskellige stykker kode, alt efter om en betingelse er opfyldt eller ej. Dette kan opnås med en if/else-sætning. Den har følgende struktur:

if (betingelse) kodeAelse kodeB

Med andre ord; hvis betingelsen er opfyldt, vil programkoden i kodeA blive udført. Hvis betingelsen ikke er opfyldt, vil programkoden i kodeB blive udført i stedet for. Vi kan altså aldrig komme i en situation, hvor både kodeA og kodeB bliver udført! Grafisk ser logikken i en if/else-sætning således ud:

Et eksempel på brug af en if/else-sætning kunne være:

if (alder < 18) System.out.println("Du er ikke voksen endnu!");else System.out.println("Du er voksen.");

Endelig kan man også kombinere flere if/else-sætninger til en slags kæde af betingelser, hvis man gerne vil håndtere mere end to alternativer, f.eks. således:

if (alder >= 18) System.out.println("Du er myndig."); else if (alder >= 13) System.out.println("Du er teenager og ikke myndig."); else if (alder >= 2) System.out.println("Du er et barn og ikke myndig."); else System.out.println("Du er et spædbarn!");

Bemærk, at det ikke er vigtigt om koden står på samme linie som betingelsen, eller en linie længere nede. Hvilket layout man vælger, er mest et spørgsmål om smag og behag.

Kode-blokke (2.4)

Som regel vil man gerne have flere liniers kode til at blive udført sammen, når en betingelse er opfyldt. Dette gøres ved at samle kode-linierne i en kode-blok. En kode-blok ser således ud:

{ // Start på kode-blokken kodelinieA; kodelinieB; kodelinieC; …} // Slut på kode-blokken

Man kan have så mange liniers kode i en blok, som man har lyst til. Ved hjælp af en kode-blok kan vi f.eks. skrive denne if-sætning:

if (alder < 18){ System.out.println("Du er ” + alder + ” år"); System.out.println("Du er ikke voksen endnu!");}

Den præcise placering af de ”krøllede” parenteser, som markerer start og slut på kode-blokken, er ikke afgørende. Den ovenstående if-sætning kan også skrives som:

if (alder < 18) { System.out.println("Du er ” + alder + ” år"); System.out.println("Du er ikke voksen endnu!");}

Igen er det udelukkende et spørgsmål om smag og behag i forhold til layoutet af koden. Det samme gælder for indrykningen af linierne. Det er kun gjort for at gøre koden mere læselig. Man kunne godt skrive ovenstående kode som:

if (alder < 18) {System.out.println("Du er " + alder + " år"); System.out.println("Du er ikke voksen endnu!"); }

men hvorfor skulle man dog det…?

Det vigtigste m.h.t. layout af kode er at holde en konsekvent stil, så man forvirrer sig selv og eventuelle andre læsere af ens kode mindst muligt.

Løkker – for- og while-sætninger (2.5)

Ofte er vi interesserede i at gentage et stykke kode et vist antal gange. Vi vil have en eller anden form for betingelse, som afgør hvorvidt koden skal gentages endnu en gang, eller der skal fortsættes til den efterfølgende kode.

I Java – og i programmering i det hele taget – kaldes sådan en konstruktion for en løkke. I Java kan en løkke laves på flere måder. Den første måde kaldes for en while-løkke. Den har følgende struktur:

while (betingelse) kode

Dette ligner jo en if-sætning meget, men der er en fundamental forskel. I dette tilfælde bliver kodelinierne i kode gentaget igen og igen, så længe betingelsen er opfyldt. Bemærk, at hvis betingelsen ikke er opfyldt første gang den undersøges, bliver koden slet ikke udført.

Ligesom for en if-sætning kan vi benytte en kode-blok, hvis vi ønsker at gentage flere kodelinier. Som i dette eksempel:

int tal = 1;while (tal < 5){ System.out.println("Kvadratet af " + tal + " er " + tal*tal); tal = tal + 1;}

Dette er en meget typisk while-løkke; betingelsen afhænger af en variabel (ofte kaldet en tælle-variabel), og denne variabel ændres i den til while-løkken hørende kodeblok. Hvad ville der ske, hvis betingelsen var opfyldt første gang, og koden i kode-blokken ikke ændrede på variablen?

Logikken i en while-løkke ser således ud i et diagram:

Et alternativ til while-løkken er for-løkken. Konstruktionen med en tælle-variabel, der skal forøges med 1 i hvert gennemløb af løkken, er så almindelig, at for-løkken så at sige er designet til netop denne situation. for-løkken har følgende struktur:

for (initialisering; betingelse; opdatering) kode

initialisering er en (evt. erklæring og) tildeling af en tællevariabel, f.eks. alder = 15 betingelse er et logisk udtryk, der angiver betingelsen for, at løkken skal fortsætte med at

blive udført, f.eks. alder < 18 opdatering er ændringen i tællevariablen, f.eks. alder = alder + 1

Det kan umiddelbart se mere kompliceret ud end en while-løkke, men faktisk er de to typer løkke helt ligeværdige; alt man kan lave med en while-løkke, kan man også lave med en for-løkke, og omvendt. I en for-løkke har man samlet de nødvendige operationer i den første linie, mens det i en while-løkke kun er betingelsen, der skal være i den første linie.

Det er ligetil at omskrive eksemplet fra før til en for-løkke:

for (int tal = 1; tal < 5; tal++){ System.out.println("Kvadratet af " + tal + " er " + tal*tal);}

Bemærk at tal++ er en kort skrivemåde for tal = tal+1.

Ikke overraskende minder logikken i en for-løkke meget om logikken i en while-løkke:

Der er ingen grænser for, hvilken type kode man kan have i kode-blokken for en løkke. Følgelig kan man sagtens have andre løkker i kode-blokken. Løkker inde i andre løkker kaldes ofte for indlejrede løkker. Visse opgaver kan løses meget kompakt med en indlejret løkke, f.eks. at udskrive den lille multiplikationstabel:

for (int x = 1; x <= 10; x++){ for (int y = 1; y <= 10; y++) { System.out.println(x + " gange " + y + " er " + x*y); }}

Endlig skal man som programmør være meget opmærksom på at undgå uendelige løkker, som typisk opstår hvis man laver en fejl i forhold til de variable, der indgår i løkkens betingelse.

Brug af klasser (3.1 – 3.3)

De såkaldt simple typer i Java, kan bruges ”direkte”, f.eks. på denne måde:

int tal = 4;

Ofte vil vi gerne gøre brug af mere komplicerede typer, i form af objekter. Et objekt vil altid have en klasse som sin type. F.eks. kan vi have en klasse BankAccount, som kan rumme funktionalitet til at modellere en bankkonto. En BankAccount klasse kunne se således ud:

Hvis man vil bruge funktionaliteten i BankAccount-klassen, skal man lave sig et objekt af typen BankAccount:

BankAccount myAccount = new BankAccount;myAccount.deposit(1000);

Bemærk, hvordan vi benytter en metode på objektet. Vi skriver objektets navn, og derefter kalder vi en metode på objektet, ved at skrive en punktum ”.”, og derefter metodens navn.

myAccount.deposit(1000);

Derved kalder vi metoden på netop dette objekt, og ikke andre objekter. Hvis vi havde et andet objekt hisAccount, ville beløbet på hisAccount ikke være ændret.

Det er vigtigt at lægge mærke til, at vi skal bruge kommandoen new for at lave et nyt objekt. Ellers får vi ikke reserveret plads i computerens hukommelse til objektet. Modsat en variabel af en simpel type, ved vi ikke på forhånd hvor meget plads et objekt vil bruge, derfor bliver hukommelsen reserveret på en lidt anden måde. Vi behøver ikke vide så meget om detaljerne omkring hvordan dette gøres, blot skal vi huske at bruge new, når vi laver et nyt objekt.

Det er også vigtigt at bemærke, hvilke muligheder vi har, når vi skal konstruere et objekt af en given klasse. Mange klasser har en specialiseret konstruktør-metode, hvor vi kan angive visse parametre til objektet, som vil definere den tilstand, objektet bliver skabt i.

BankAccount myAccount = new BankAccount(2000);

Denne konstruktør kan skabe objektet i en tilstand, hvor der fra start er 2000 kr. på kontoen.

Tekst-strenge, klassen String (3.4)

Vi benytter tekst-strenge, så snart vi har brug for en form for tekst i vores program, f.eks. en ledetekst når brugenren skal indtaste noget data:

System.out.println("Indtast venligst dit navn: ");

Her er "Indtast venligst dit navn: " en tekst-streng. En specifik tekst-streng er altid omgivet af et sæt af "". En variabel til at gemme en tekst-streng har typen String.

String s; // Bemærk, at new ikke benyttes!

Som man måske kan gætte af skrivemåden (stort bogstav først) er String en klasse. Men man behøver ikke benytte new, når man laver sig et objekt af typen String!!

Det er ganske nemt at konvetere andre typer variable til en tekst-streng. Vi har allerede gjort det før, uden at tænke så meget over det, f.eks. således:

int tal = 17;String s = "Din alder er " + tal;

Umidelbart giver det jo ikke mening at prøve at læge 17 til en tekst-streng… Men Java opfatter dette som at vi gerne vil konvertere tallel til en tekst-streng, og derfor bliver resultatet:

"Din alder er 17"

Der findes en lang række metoder som kan kaldes på String-objekter:

String replace(String søgetekst, String erstatning)String substring(int startindeks)String substring(int startindeks, int slutindeks)String toUpperCase()boolean equals(String str)... // Og så videre

Man skal dog være opmærksom på, at ingen af disse metoder i sig selv ændrer på den tekst-streng, som objektet rummer! Selv om man kalder replace på et objekt s, er det således ikke tekst-strengen i s, der ændres. Metoden returnerer derimod en ny tekst-streng (i et String-objekt), hvori ændringen er foretaget.

Endelig skal man også være varsom, når man vil undersøge om to tekst-strenge er ens. Hvis vi feks. har to String objekter s1 og s2, er dette ikke måden at sammenligne dem på:

if (s1 == s2) // Forkert!

Dette sammenligner ikke om selve tekst-strengen i hhv. s1 og s2 er den samme, men derimod om s1 og s2 refererer til samme objekt. Vil man teste om de to objekter rummer samme tekst-streng, skal man benytte metoden equals:

if (s1.equals(s2)) // Rigtigt

ArrayList klassen (3.5)

Vi kan ofte have brug for at arbejde på en hel samling af variable af en given type, i stedet for blot en enkelt variabel. Det kunne f.eks. være en samling tal, som angiver resultatet af et antal målinger i et eksperiment.

Til at rumme sådan en samling af variable kan vi benytte en såkaldt ArrayList. En ArrayList er en klasse i Java, som vi kan bruge til at indsætte, finde og slette variable af en given type. Når vi skal erklære en variabel af typen ArrayList, skal vi også angive typen af de variable, listen skal rumme:

ArrayList<String> navne; // En ArrayList, som rummer tekst-strengeNavne = new ArrayList<String>();

Formelt set skal typen af de variable altid være en klasse, derfor kan vi ikke umiddelbart lave en liste af tal:

ArrayList<int> talListe; // Forkert!!

Dette vil dog være en helt urimelig begrænsning, derfor har man i Java lavet klasser svarende til de primitive typer, f.eks. klassen Integer for int:

ArrayList<Integer> talListe; // Rigtigt!!

Det er dog kun i selve erklæringen af listen, at vi skal huske dette. Når vi arbejder med listen, behøver vi ikke tænke over denne specielle type.

Det smarte ved en liste er som nævnt, at vi kan indsætte, slette og finde elementer i listen. Hvis vi indsætter nogle navne i listen fra før, vil det se således ud:

navne.add("Lis");navne.add("Bo");navne.add("John");

Man skal tænke på listen som en fortløbende sekvens af elementer, hvor det første element i listen har positionen – eller indexet – nummer 0, det næste element nummer 1, og så videre.

0 1 2 3 4 …Lis Bo John … … …

Metoden add sætter således et element ind bagerst i listen. Man skal ikke bekymre sig om at reservere plads til nye elementer; det finder listen selv ud af.

Når man vil se hvilke element der befinder sig på en bestemt plads i listen, benytter man metoden get(int index), hvor man som parameter angiver en position i listen. Således vil kaldet

navne.get(1);returnere tekst-strengen "Bo" til os. I den forbindelse kan det være meget praktisk at vide, hvor mange elementer der er i listen lige nu. Dette gøres ved hjælp af metoden size, hvor kaldet

navne.size();

således vil returnere værdien 3. En lille ”fælde” i den forbindelse er, at det sidste element i en liste af længde 3 vil være på position nummer 2, idet vi jo starter listen fra position 0. Endelig kan vi også fjerne elementer fra listen. Hvis vi ønsker at fjerne elementet på position 1 fra listen, kalder vi:

navne.remove(1);

Her skal man også bemærke, at når et element fjernes, vil de efterfølgende elementer i listen blive rykket sammen, således at der ikke er ”huller” i listen. Efter kaldet vil vores derfor se således ud:

0 1 2 3 4 …Lis John … … … …

Løkker og lister hænger tæt sammen. Vi vil næsten altid have behov for at løbe en liste igennem, og gøre et eller andet med hvert element. Dette kan bekvemt gøres med en for-løkke:

for (int index = 0; index < navne.size(); index++){ System.out.println(navne.get(index)); // Skriv navn på position index ud}

Denne syntaks er meget standard, men der findes faktisk en variant af for-løkken, specielt til situationen hvor man vil gennemløbe alle elementer i en liste:

for (String navn : navne){ System.out.println(navn);}

Med denne syntaks skal man ikke angive grænser for start og slut af gennemløbet; i stedet erklærer man en variabel af samme type som elementerne i listen (i dette tilfælde String), og angiver hvilken liste der skal løbes igennem. Hvilken type løkke man vil bruge er smag og behag, blot kan sidste variant kun bruges, hvis hele listen skal løbes igennem.

Endelig skal man bemærke, at hvis man ønsker at benytte ArrayList-klassen i sit program, skal man skrive linien:

import java.util.*;

i toppen af sin fil. De klasser som er standard i Java ligger i forskellige biblioteker, og hvis man vil bruge en standard-klasse, skal man ”importere” dette bibliotek i sin kode.

Definition af klasser (4.1-4.4)

Når vores kode når en vis størrelse, bliver det uoverskueligt at proppe al koden ind i main-metoden. Vi bør derfor fordele koden ud i diverse klasser, som vi selv definerer.

Når vi skal lave en ny klasse i NetBeans, kommer koden for klassen til at ligge i sin egen fil, der hedder det samme som klassen, efterfulgt af ”.java” (se note).

En klasse-definition ser altid således ud

public clas MinKlasse // Erstat MinKlasse med klassens navn{ // Kroppen af klasse-definitionen}

I selve kroppen af klasse-definitionen har vi:

Instans-variable Konstruktører Metode-definitoner

En instans-variabel er en variabel, som hører tæt sammen med et specifikt objekt. Hvert objekt vil rumme en ”instans” af variablen, og variablens værdi er specifik fra objekt til objekt. Med andre ord; vi vi ændrer på værdien for en instans-variabel for et specifikt objekt, vil den ikke blive ændret for de andre objekter.

Formålet med instans-variable er at repræsentere objektets tilstand. Et objekt har jo altid tilstand og opførsel, og tilstanden udgøres netop til enhver tid af instans-variablenes værider.

En konstruktør tjener til at oprette et objekt i en fornuftig tilstand, således at objektet ikke får en tilfældig tilstand fra starten. En konstruktør kan være uden parametre; i så fald vil objekter af klassens type altid få den samme start-tilstand. Hvis konstruktøren har parametre, kan disse bruges til at definere et specifikt objekts start-tilstand. Bemærk, at man godt kan definere flere konstruk-tører for en klasse, blot skal de have forskellige parametre. Navnet på konstruktøren er altid det samme som klassens eget navn.

En klasses metoder definerer klassens opførsel. Hver metode består af et hoved og en krop. Hovedet definerer metodens navn, returtype samt eventuelle parametre. I kroppen er selve den kode, som skal udføres når nogen kalder denne metode på et objekt. Hvis metoden har en anden reurtype end void, skal sidste linie i kroppen altid være en return-sætning.

Lad os se på en BankKonto-klasse som eksempel. På en bankkonto skal man kunne indsætte og hæve penge, samt kunne få oplyst den nuværende saldo. Nedenstående klasse er en mulig måde at lave en BankKonto-klasse på:

public class BankKonto{ double saldo; // Instans-variabel

BankKonto() // Konstruktør { saldo = 0.0; }

void indsæt(double beløb) // Metode uden returværdi { saldo = saldo + beløb; }

void hæv(double beløb) // Metode uden returværdi { saldo = saldo – beløb; }

double hentSaldo() // Metode med returværdi { return saldo; }}

Denne klasse-definition er i sig selv i orden, idet den tilbyder den ønskede funktionalitet. Dog overtræder den et vigtigt princip i Objekt-Orienteret programmering – princippet om indkapsling.

Med indkapsling menes mere specifikt, at detaljer om hvordan en klasse repræsenterer tilstand internt ikke er noget brugeren af klassen skal vide noget om. Der er flere grunde til dette. For det første skal udvikleren af klasse have frihed til at ændre repræsentationen af tilstand, uden at det har effekt på en bruger af klassen. Hvis en bruger af klassen har direkte adgang til repræsenta-tionen af tilstande, f.eks. således:

minKonto.saldo = 1000.0;

ville det give problemer, hvis vi f.eks. besluttede at benytte en int til repræsentation. En anden vigtig grund er sikre kontrol over tilstanden. Som BankKonto-klassen er nu, kan saldoen ændres dels ved at kalde indsæt og hæv, men også ved at ændre på saldo direkte! Det kan lede til farlige situationer, hvor tilstanden af et objekt så at sige ændres bag om ryggen på os.

Vi kan styre en brugers adgang til metoder og tilstand ved brug af ordene public og private. Disse nøgleord har denne betydning:

public: adgang for alle private: kun adgang internt i klassen

Med andre ord skal de metoder og instans-variable, som en bruger ikke skal have adgang til, mærkes med private, resten med public. Hvis vi med dette in mente omskriver BankKonto-klassen, kommer den til at se således ud:

public class BankKonto{ private double saldo; // Instans-variabel, ikke adgang udefra!

public BankKonto() // Konstruktør { saldo = 0.0; }

public void indsæt(double beløb) // Metode uden returværdi { saldo = saldo + beløb; }

public void hæv(double beløb) // Metode uden returværdi { saldo = saldo – beløb; }

public double hentSaldo() // Metode med returværdi { return saldo; }}

Man siger, at en klasses interface udgøres af de metoder, som er mærket med public, d.v.s. alle de metoder, en bruger af klassen kan benytte.

Samspil mellem klasser (4.5)

Så snart vores programmer når en vis størrelse, vil vi definere flere forskellige klasser, og nogle af disse klasse vil endda skulle arbejde sammen. En meget typisk konstruktion er det tilfælde, hvor en klasse skal holde styr på mange objekter af en anden klasse; den første klasse er en”beholder”for objekterne af en anden klasse. Typisk vil man også føje nogle funktioner til beholder-klassen, som kan give noget information om de indeholdte objekter, f.eks. hvor mange der er. Klassen ArrayList er faktisk et eksempel på sådan en beholder.

Et andet eksempel kunne være de to klasser Terning og Raflebæger. I den virkelige verden har terning og raflebæger dette forhold:

Et raflebæger kan rumme fra 0 terninger og opefter En terning befinder sig i 0 eller 1 raflebæger

Vi vil gerne have, at det samme forhold afspejler sig mellem klasserne Terning og Raflebæger. Vores klasse-model for de to klasser vil derfor se således ud i UML:

Dette siger ikke i sig selv alverden, men det indikerer for den som skal lave klasserne, at

Klassen Raflebæger skal kunne rumme en samling af Terning-objekter Klassen Raflebæger skal rumme metoder til at tilføje et Terning-objekt til et Raflebæger-

objekt

Hvad der derudover er behov for af metoder, afhænger helt af specifikationen af den opgave, vi er ved at løse. Til en start kan vi lave denne skitse af klassen Raflebæger

public class Raflebæger{ private ArrayList<Terning> terninger;

public Raflebæger() // Konstruktør { Terninger = new ArrayList<Terning>(); }

public void tilføjTerning(Terning t) { terninger.add(t); }

...}

Man kunne måske forestille sig, at klassen Terning har en metode kast, som kaster terningen (d.v.s. laver et tilfældigt tal mellem 1 og 6). Hvis vi gerne vil lave et slag med alle terningerne i et raflebæger, kan vi lave en metode ryst i klassen Raflebæger til dette formål

public void ryst(){ for (Terning t : terninger) { t.kast(); }}

Nedarvning (5.1)

Når vi arbejder med et program, kan vi undertiden være i den situation, at en eksisterende klasse næsten opfylder de krav vi har til en klasse vi selv skal definere. Vi kan forestille os, at vi – måske fra et tidligere program – allerede har en klasse Person, der i sig selv passer fint til vores nuværende model, bortset fra at vi gerne vil lave en klasse StraffetPerson, som kan rumme en liste over straffe, en person har modtaget. Det ville i så fald være lidt omstændeligt at skulle lave denne klasse helt fra starten; det ville være langt smartere, hvis vi kunne genbruge funktionaliteten fra Person, og så lige føje det sidste til. Det kan vi gøre ved at lade StraffetPerson nedarve fra Person.

public class StraffetPerson extends Person{ ... // Bemærk nøgleordet ”extends”}

På denne måde kan klassen StraffetPerson alt som Person kan, og vi skal blot definere den ekstra bid, som omhandler den nye funktionalitet

public class StraffetPerson extends Person{ private ArrayList<Straf> straffeListe;

public tilføjStraf(Straf s) {...} public udskrivStraffe() {...} ... // Og så videre}

Lidt terminologi: En klasse kan arve variabler og metoder fra en anden klasse Klassen, der nedarves fra, kaldes superklassen Klassen, der arver fra superklassen, kaldes underklassen Underklassen kan tilsidesætte (omdefinere) metoder arvet fra superklassen ved at definere

dem igen

Andre steder i litteraturen er der brugt talrige betegnelser for superklasse, underklasse og tilsidesættelse. Her er et udpluk:

Superklasse kaldes også: Basisklasse, forældreklasse, stamklasse. Underklasse kaldes også: Afledt klasse, nedarvet klasse, subklasse. Tilsidesætte (eng.: override) kaldes også: omdefinere, overskrive

Som vist i eksemplet, kan vi frit definere nye instans-variable og metoder i underklassen, ganske som i en almindelig klasse. Herudover kan man også omdefinere metoder fra superklassen. Vi kan forestille os, at klassen Person har en metode udskrivAlleOplysninger, defineret således:

// Metoden som defineret i Personpublic void udskrivAlleOplysninger(){ udskrivNavn(); udskrivAdresse(); udskrivNationalitet(); ... // Og så videre}

For underklassen StraffetPerson vil det være naturligt også at udskrive straffe-oplysningerne. Derfor kan vi omdefinere metoden udskrivAlleOplysninger i StraffetPerson til

// Metoden som defineret i StraffetPersonpublic void udskrivAlleOplysninger(){ udskrivNavn(); udskrivAdresse(); udskrivNationalitet(); ... // Og så videre udskrivStraffe();}

I den forbindelse skal man kende nøgleordet super. Ved hjælp af super kan man referere til superklassens metoder, hvilket giver mulighed for f.eks. at skrive en smartere version af ovenstående metode.

// Metoden som defineret i StraffetPersonpublic void udskrivAlleOplysninger(){ super.udskrivAlleOplysninger();}

Dette er et meget brugt princip for at omdefinere en metode på: gør det som superklassens metode gør – ved at kalde super.metodensNavn – og føj derefter det til, som er specifikt for underklassen. Dette princip kan også bruges for konstruktøren, med en lidt speciel syntaks:

public StraffetPerson(){ super(); straffeListe = new ArrayList<Straf>();}

I UML illustreres nedarvning således:

Polymorfi (5.2)

Hvis vi har defineret klassen StraffetPerson som en underklasse til Person, er følgende kode lovlig:

Person p1 = new Person(); // SelvfølgeligStraffetPerson sp1 = new StraffetPerson(); // SelvfølgeligPerson p2 = new StraffetPerson(); // Hov!

Den sidste linie er lovlig, fordi ethvert objekt af klassen StraffetPerson også opfattes som et objekt af klassen Person, netop fordi StraffetPerson er defineret som en underklasse til Person. Derfor kan et StraffetPerson objekt spille begge ”roller”, så at sige. I Objekt-Orienteret terminologi kaldes dette for polymorfi (ploy-morf: mange former).

Polyformi er meget nyttigt, hvis man gerne vil kunne behandle en samling af objekter af forskellig type – men samme superklasse, dette er meget vigtigt – på samme måde. Hvis vi f.eks. har en superklasse Ansat med en metode beregnLøn, og vi ydermere definerer underklasser Arbejder og Funktionær, som hver især omdefinerer beregnLøn, vil følgende kode være lovlig.

Arbejder a1 = new Arbejder("Lis");Funktionær f1 = new Funktionær("Anne");Arbejder a2 = new Arbejder("Bo");Funktionær f2 = new Funktionær("Jesper");Arbejder a3 = new Arbejder("John");Funktionær f3 = new Funktionær("Ralf");

ArrayList<Ansat> ansatte = new ArrayList<Ansat>(); // Bemærk typen!

ansatte.add(a1);ansatte.add(f1);ansatte.add(a2);ansatte.add(f2);ansatte.add(a3);ansatte.add(f3);

for (Ansat a : ansatte){ a.beregnLøn();}

Det store spørgsmål er; hvilken version af metoden beregnLøn bliver kaldt i løkken? Når typen af variablen a er Ansat, ville det måske være naturligt at gætte på, at det må være versionen defineret i superklassen Ansat, der bliver kaldt. Det er det imidlertid ikke! Alt efter hvilken type a reelt har – altså Arbejder eller Funktionær – bliver versionen i den tilsvarende underklasse kaldt. Med andre ord:

Variablens type bestemmer, hvilke metoder der kan kaldes Objektets type bestemmer, hvilken version af metoden der bliver udført

Hvis vi har lavet en metode beregnArbejderTillæg på klassen Arbejder, kan vi altså ikke kalde den metode i løkken, da a jo netop har typen Ansat.