|
|
|
Avdeling for informatikk og e-læring, Høgskolen i Sør-Trøndelag |
Else Lervik
Lærestoffet er utviklet for faget LO191D Videregående programmering
Opphavsrett: Else Lervik og Stiftelsen TISIP
Resymé: Det er kanskje en stund siden du har programmert. Denne leksjonen skal hjelpe deg i gang igjen. Egentlig inneholder den ikke noe nytt i forhold til grunnkurset, men jeg tror en repetisjon er nødvendig for de fleste. Forsøk deg på oppgavene (med løsning) som du finner i leksjonen. Også de med gode karakterer fra grunnkurset vil nok finne noe nytt her - eller rettere sagt nye anvendelser av kjent stoff. Det er svært viktig at grunnlaget er på plass før vi fortsetter med nye ting. Hovedtemaet for denne leksjonen er en del grunnleggende begreper, samt programmering av klasser og tabeller. Den første øvingen er obligatorisk og handler om tabeller av strenger.
Studenter med bakgrunn i C++ må begynne her
Innledning:
Objektorientert programmering
Innkapsling:
Hvorfor private data med get-metoder?
To repetisjonsoppgaver
Litt om static -
klassemetoder og klassevariabler
Tabeller av objekter (strenger)
Hvordan lage konstruktør når vi har en tabell som objektvariabel
I moderne systemutvikling snakker vi om objektorientert analyse, objektorientert design og objektorientert programmering.
Hva ligger i begrepet objektorientering? Vi konsentrerer utviklingsprosessen om objekter.
Hva er et objekt?
Det problemet vi skal løse er alltid knyttet til virkeligheten. Vi begynner alltid med en forestilling, mer eller mindre presist formulert, i vanlig norsk språkdrakt. Ettersom vi arbeider med problemet får vi stadig mer innsikt i hva det dreier seg om. Problembeskrivelsen inneholder, som all prosatekst, substantiver og verb. Den beskriver objekter (substantivene) og hva som skjer med disse (verbene).
Et objekt er altså de "tingene" problemet vårt handler om. Det kan være konkret (person, by, vare, butikk) eller abstrakt (organisasjon, idrettskonkurranse, møte, biltur). Vi modellerer alle disse objektene som om de var levende. Ikke bare person-objekter, men også f.eks. møte-objekter har masse kunnskap om seg selv og er i stand til å utføre kompliserte oppgaver.
Det vi ønsker at objektene skal kunne gjøre, kaller vi operasjoner. Eksempler: En butikk selger en vare, en tur kjøres, et møte starter. Her er "selge" en operasjon som et butikk-objekt utfører, mens "kjøre" er en operasjon som objektet tur har ansvaret for.
Et objekt har også en tilstand. Et møte kan være planlagt, under avvikling, eller avsluttet. Et objekt kan endre tilstand når en operasjon utføres, f.eks. møte starter. Tilstanden til et objekt er gitt ved verdiene til attributtene. En statusvariabel kan holde orden på hvor langt en er kommet i møtet. Attributter (og tilstanden) omfatter også opplysninger av mer statisk natur som f.eks. møtested og -tid.
Mengden av attributter og operasjoner som er felles for en gruppe objekter, kaller vi en klasse. Eksempelvis vil en klasse Møte ha attributtene sted, tid, deltakere, status, og operasjonene lag møteplan, send ut innkalling, start møtet, registrer referat, etc.
Møteobjektet har kunnskapen om tid, sted, deltakere, osv. samt utførelse av operasjonene inni seg. Omverden kjenner objektet gjennom de operasjonene det utfører, f.eks. utsendelse av møteinnkalling. Vi sier at kunnskapen er kapslet inn ("encapsulated") i objektet. Mer om det nedenfor.
Tanken bak objektorientert systemutvikling er at det er en stor fordel om vi under systemutviklingen i størst mulig grad kan fortsette å forholde oss til virkeligheten i form av begreper hentet fra den tidlige problemformuleringen vi har.
Dette betyr at vi lar operasjoner og objekter følges ad hele veien, fra analyse til ferdig produkt. Dette er selvfølgelig det mest naturlig av verden. Hvorfor sier jeg dette i det hele tatt? (Dersom du aldri har programmert uten å bruke klasser, kan du hoppe over de følgende linjene.) Tenk et øyeblikk på et program uten klasser. Det består av et hovedprogram og en mengde funksjoner (prosedyrer, subrutiner). I utviklingsfasen er det fokusert på algoritmer og trinnvis forfining av disse. Algoritmene er det sentrale, dataene sendes hit og dit avhengig av algoritmen. I strukturert programmering har vi ingen fast binding mellom data (objektene) og algoritmer (operasjoner). Derfor kan parameterlistene bli veldig lange ... I objektorientert programmering har vi dataene i objektet, og parameterlistene er ofte svært korte.
Objektorientert analyse, design og programmering viser seg å være velegnet i de fleste systemutviklingsprosjekt fordi det er mulig å lage en bedre modell av virkeligheten enn ved å bruke en strukturert angrepsmåte.
I dette kurset er objektorientert programmering det sentrale, men vi vil nødvendigvis måtte angripe problemene vi skal løse ved hjelp av objektorientert analyse og design. I årenes løp er det utviklet mange metoder for objektorientert analyse og design. De mest kjente metodene er utviklet av Ivar Jacobson (OOSE, use-cases), James Rumbaugh (OMT) og Grady Booch. De har samkjørt sine metoder i et felles modelleringsspråk - Unified Modeling Language (UML). Språket beskriver bestemte måter (f.eks. en bestemt måte å tegne bokser og piler på) å lage modeller på. I et utviklingsprosjekt arbeider man med mange modeller underveis, fra kravmodell via analyse- og designmodell til implementasjonsmodell, som er det ferdige programmet. Når man anvender metodene i en bestemt rekkefølge får man en prosess. The Objectory Software Development Process er navnet på prosessen som anvender UML til å beskrive modellene underveis. Jacobson, Rumbaugh og Booch har gitt ut tre store bøker som beskriver UML og Objectory. En mindre bok som kan anbefales dersom du har noe kjennskap til systemutvikling fra før er: Martin Fowler, Kendal Scott: UML Distilled. Addison-Wesley.
Et objektorientert programmeringsspråk har språkelementer som gjør det enkelt å programmere objekter og operasjoner, samt sammenhengen mellom disse. Det er mulig å programmere objektorientert også i språk som ikke har denne støtten, men det krever selvfølgelig mer av programmereren.
(Dette stoffet er behandlet i boka, kapittel 6.7.)
Et objektorientert programmeringsspråk må ha støtte for innkapsling. Det betyr at datastrukturer og måten metoder gjennomføres på skal være skjult for omverden (klientene). Klientenes eneste måte å kommunisere med objektet på, er via meldinger som sendes til objektet (metodekall). At implementasjonen av metodene er skjult for klientene er greit å forstå, men enkelte studenter undres på hvorfor vi må gjøre dataene private. Klientene må da aksessere dataene via get-metoder.
Altså: Hvorfor kan vi ikke bare sette dataene public? Da slipper vi å lage get-metodene. Dataene blir jo likevel knyttet til objektet.
Jeg skal forsøke å svare på hvorfor dette ikke er en god idé. La oss lage en klasse Student med blant annet attributtene navn og alder. Vi lager klassen slik vi har lært med private variabler og get-metoder:
class Student {
private final String navn;
private final int alder;
public Student(String navn, int alder) {
this.navn = navn;
this.alder = alder;
}
public String getNavn() {
return navn;
}
public int getAlder() {
return alder;
}
}
Men etter at programmet vårt har vært i drift et år oppdager vi at alderen blir feil - studenten har jo blitt et år eldre, men getAlder() returnerer fortsatt den alderen som gjaldt i fjor. Vi finner ut at vi i stedet bør legge inn fødselsåret, og så la programmet beregne alderen ut fra det året som gjelder når programmet kjører. Det er greit å gjøre denne endringen så lenge alder er en privat variabel. For grensesnittet mot omverden er det samme, nemlig metoden getAlder().
Hvorfor kunne vi fått problemer med offentlige variabler? Da hadde klassen sett slik ut (eventuelt med get-metoder i tillegg):
class Student {
public final String navn; // skummelt!
public final int alder; // skummelt!
public Student(String navn, int alder) {
this.navn = navn;
this.alder = alder;
}
... eventuelle get-metoder ...
}
Jo, med denne løsningen kunne vi hatt klienter som hadde benyttet variabelen alder direkte (uten å gå via metoden getAlder()), og når variabelen alder forsvinner til fordel for fødselsår, så vil jo disse klientprogrammene ikke lenger virke. Og om klassen Student hadde blitt skikkelig populær, kunne klientprogrammene vært spredt over hele verden ... du ser problemet?
Tilsvarende problemer kunne vi hatt med variabelen navn. Etter en stund hadde vi kanskje funnet ut at vi ønsket å skille mellom fornavn og etternavn ...
Skjønner du nå hvorfor variabler bør være private?

Vi viderefører eksemplet foran - med fødselsår som objektvariabel og getAlder() som metode. I tillegg vil vi i oppgave 2 håndtere karakterene til studenten.
Ta utgangspunkt i eksemplet foran.
Forandre konstruktøren slik at den tar fødselsdatoen som argument i stedet for alderen.
Endre getAlder() slik at alderen regnes ut fra fødselsdatoen. For enkelhets skyld regner vi bare i hele år. Prøv ut koden med dette enkle testprogrammet:
class TestStudent {
public static void main(String[] args) {
Student studenten = new Student("Ole Andreas Thomassen", "10101980");
System.out.println("Studenten heter " +
studenten.getNavn() + " og er "
+ studenten.getAlder() + " år gammel.");
}
}
Du trenger kanskje litt hjelp til metoden getAlder(). Du skal hente ut årstallet fra fødselsdatoen og trekke dette fra inneværende år. Vi tar ikke hensyn til når på året vedkommende student er født. Gjør følgende:
int fÅr = Integer.parseInt(fÅrString);
GregorianCalendar dagensDato = new GregorianCalendar();
int detteÅr = dagensDato.get(Calendar.YEAR);
Klassene GregorianCalendar og Calendar ligger i pakken java.util.
Løsningen finner du her.
Vi skal jobbe videre med løsningen fra oppgave 1 og programmere behandlingen av karakterer. Karakterene skal lagres i en tabell av char. Variabelen antall holder orden på hvor mange karakterer som er registrert. Det vil si at i begynnelsen er tabellen tom, men antall øker etter hvert som nye karakterer registreres.
Begynnelsen av klassen Student ser nå slik ut:
class Student {
public static final String GYLDIG_KAR = "ABCDEF";
private final String navn;
private final String fdato;
// *** Oppgave 2 starter her
private char[] karakterer = new char[3];
private int antall = 0;
Programmer operasjonene:
• Registrer ny karakter
– skal sjekke om karakteren er gyldig (A-F)
– (hva gjør du dersom tabellen er full?)
• Finn gjennomsnittskarakteren
– omform til heltall, regn ut snitt, omform tilbake til bokstav
• toString()-metode som lager en streng med navn, fødselsdato og alle registrerte karakterer med mellomrom mellom
Lag et klientprogram som (se brukerdialoger nedenfor)
• oppretter et studentobjekt
• går i løkke og registrerer nye karakterer. Karakterene leses inn fra brukeren. Anta at brukeren alltid skriver inn ett tegn. Avslutt innlesingen når brukeren trykker Esc-tasten.
• navn, fødselsdato og hittil registrerte karakterer samt gjennomsnitt skal skrives ut i hvert løkkegjennomløp
Løsningen på hele oppgaven finner du her.

Referanser til læreboka: Klassemetoder er gjennomgått i boka i forbindelse med sortering, kapittel 9.1, side 287. Klassevariabler gjennomgås ikke før i kapittel 15.5. Der gjennomgås også static-blokk, som ikke er pensum i dette kurset.
Erfaringsmessig er static et noe ullent begrep for studenter på dette stadiet. Static er en såkalt modifikator som settes foran en deklarasjon. Vi benytter den for variabler (og konstanter, som i prinsippet er det samme som en variabel, bare at den ikke kan endre verdi - vi setter modifikatoren final foran) som er felles for alle objekter i en klasse, og for metoder som kan benyttes uten å knyttes til et objekt. Det vil si at variablene og metodene kan benyttes uten at objekter av klassen eksisterer. Vi kaller gjerne slike variabler og metoder for henholdsvis klassevariabler og klassemetoder. Dette i motsetning til objektvariabler og objektmetoder som krever at vi har et objekt av klassen.
Vi benytter koden foran som eksempel:
int fÅr = Integer.parseInt(fÅrString);
GregorianCalendar dagensDato = new GregorianCalendar();
int detteÅr = dagensDato.get(Calendar.YEAR);
Her er parseInt() en klassemetode i klassen Integer. Vi refererer til metoden utenfor klassen ved å oppgi klassenavnet foran, slik: Integer.parseInt(). Vi sier at vi kvalifiserer parseInt() med Integer.
YEAR er en klassevariabel (konstant) i klassen Calendar.
Vi har også en objektmetode i koden over: Metoden get() kvalifiseres med navnet dagensDato.
La oss utvide løsningen fra repetisjonsoppgave 1 med to klassevariabler der den ene er deklarert final:
class Student {
private final String navn;
private final String fdato;
private static int antStud = 0; // variabel, antall studentobjekter
laget
public static final String SKOLE = "HiST"; // konstant, kan være
offentlig
public Student(String navn, String fdato) {
this.navn = navn;
this.fdato = fdato;
antStud++;
}
public static int finnAntStud() { // merk static, kan kalles
uten at objekt er laget
return antStud;
}
....
}
class TestStudent {
public static void main(String[] args) {
Student studenten = new Student("Ole Andreas Thomassen",
"10101980");
System.out.println("Studenten heter " + studenten.getNavn()
+ " og er "
+
studenten.getAlder() + " år gammel. Skole: " + Student.SKOLE);
System.out.println("Antall studentobjekter laget " +
Student.finnAntStud() + "\n");
}
}
En klassevariabel eksisterer uavhengig av om det er laget objekter av klassen. Slike variabler kan brukes både i klasse- og i objektmetoder.
En objektvariabel forutsetter at det eksisterer et objekt av klassen, den kan derfor ikke brukes i klassemetoder, kun i objektmetoder.
Jo, her sier vi at main() kan kjøres uten at vi har et objekt av klassen. Ved oppstart av et java-program ser Java-tolkeren etter metoden main() i klassen vi starter. Java-tolkeren lager ikke objekt av klassen, derfor må metoden være static.
Vi ser på argumentlisten med det samme: Den er en tabell av strenger. Dersom vi kjører programmet fra kommandolinjen, vil ordene vi skriver etter programnavnet havne i denne tabellen.
Eksempel:
class etProgram {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
}
Vi kompilerer og kjører programmet fra kommandolinjen, slik:
>
java EtProgram Trondheim Oslo Bergen StavangerDa blir utskriften som følger:
Trondheim
Oslo
Bergen
Stavanger
Også dette bør være kjent stoff. Øving 1 er sterkt knyttet til eksemplet som gjennomgås her.
Vi forutsetter nå at du er fortrolig med tabeller der datatypen er en primitiv datatype (repetisjonsoppgave 2 foran). Du ser fordelen med å legge en slik tabell inn i en klasse. En klient kan sende meldinger til objekter av denne klassen uten å måtte forholde seg til detaljene i tabellen.
Hva betyr det at datatypen er en primitiv datatype? Vi har følgende primitive datatyper: byte, short, int, long, char, float, double og boolean. Typenavnet er et reservert ord, og ikke en klasse. I praksis setter dette store begrensninger på hva vi kan lagre i tabellen. Ikke en gang en tabell av tekster kan legges i en slik tabell.
Behovet for å lagre objekter i tabeller er ganske åpenbart. Vi velger å begynne med tabeller av streng-objekter. I neste leksjon ser vi på tabeller av andre typer objekter.
Altså:
1. Først lager vi en tabell av referanser:
String[] navneliste = new String[4];
Vi har her laget en tabell med 4 referanser. De har automatisk fått verdien null (med bokstaver). null er ikke et objekt, det er dermed heller ikke en streng, men en verdi som betyr at denne referansen ikke inneholder en adresse til et objekt.
2. Deretter må vi opprette objektene som disse referansene skal peke til:
navneliste[0] = new String("Hanne");
navneliste[1] = new String("Berit");
.. osv. ..Nå er null byttet ut med adressene til de aktuelle objektene.
Vi skal se at vi sjelden oppretter alle objektene i trinn 2 på en gang.
Dersom vi senere vil fjerne et objekt setter vi referansen lik null:
navneliste[1] = null;
Dersom det ikke fins andre referanser til objektet, blir det fysisk fjernet neste gang Java-tolkeren foretar en opprydding i minnet.
Vi skal i resten av denne leksjonen jobbe med et eksempel der vi har en tabell av strenger gjemt inne i en klasse, på tilsvarende måte som vi i repetisjonsoppgave 2 hadde en tabell av primitiv datatype inne i klassen.
Det er mye snakk om hva man skal spise og ikke spise for tiden. Et objekt av klassen Diett skal inneholde et maksimalt antall "tillatte" matslag. Det skal tilby følgende tjenester:
Vi har ikke lov å registrere det samme matslaget to ganger i tabellen. Dette må alle operasjonene som legger inn nytt matslag passe på at ikke skjer.
Vi begynner med å se på datastrukturen. Vi har altså en tabell av strenger som objektvariabel i klassen Diett. Etter at flere matslag er lagt inn og tatt ut kan vi for eksempel ha følgende:

Her gjelder:
Maksimalt antall matslag er 4, mens antall registrerte er 3. Det betyr at det her er plass til å registrere ett matslag til.
Ang. navnene på matslagene:
(Prinsippene som gjennomgås her gjelder alle typer tabeller, ikke bare tabeller av strenger.)
Vi skal lage konstruktør. Litt repetisjon: I en klasse kan vi lage ingen, en eller flere konstruktører. Dersom vi lager mer enn en konstruktør, må de ha forskjellig signatur (type og antall av parametere). En konstruktør skal gi startverdier til objektvariabler. Den behøver ikke initiere alle variablene. Dersom en variabel ikke får verdi i den konstruktøren vi bruker, er det den verdien som variabelen har fått i deklarasjonen som gjelder. Dersom det ikke er satt opp noen bestemt verdi der, er det 0, false eller null avhengig av datatypen, som gjelder. Dersom vi ikke lager noen konstruktør selv, blir det laget en konstruktør uten parameterliste og med tom kropp.
I dette eksemplet har vi kun én objektvariabel. Vi har likevel flere muligheter siden dette er en tabell, og ikke en enkel variabel.
Da må tabellen opprettes i listen over objektvariabler. For vårt eksempel gjelder da følgende:
private String[] matslag = new String[10];
Her har vi satt av plass til 10 matslag. Dette blir en lite fleksibel klasse. Klienten har ingen kontroll med størrelsen på tabellen. Denne metoden egner seg dersom tabellen alltid har fast størrelse. (Metoden brukes også dersom klassen har ansvaret for å tilpasse tabellen til riktig størrelse, jamfør char-tabellen i repetisjonsoppgave 2 foran.)
Klassen tilbyr gjerne metoder som lar klienten legge inn data i tabellen.
Merk at vi må opprette tabellen. Dersom vi bare skriver:
private String[] matslag;
vil matslag ha verdien null, og alle referanser til matslag vil kaste unntaksobjekt av typen NullPointerException under kjøring.
En tabell er et mutabelt objekt (det vil si at det er mulig å endre på innholdet i objektet), det er derfor nødvendig å la konstruktøren lage en kopi av tabellobjektet. Les om igjen kapittel 7.4 fra side 223 og utover der temaet behandles for tabeller av primitive datatyper. Merk deg forskjellen mellom grunn og dyp kopiering.
I vårt eksempel har vi ikke en tabell av en primitiv datatype. Objektvariabelen ser slik ut:
private String[] matslag;
Og konstruktøren kan kanskje se slik ut:
public Diett(String[] matslag) {
this.matslag = new String[matslag.length]; // OBS! Husk å
opprette tabellen!
for (int i = 0; i < matslag.length; i++) {
if (matslag[i] != null) {
this.matslag[i] = matslag[i].trim(); //
fjerne ev. blanke foran og bak
}
}
}
Vi prøver med følgende kode i klientprogrammet:
String[] tekst = {"Ost", "Pølse", "Egg", "Syltetøy"};
Diett minDiett = new Diett(tekst);
Konstruktøren kopierer referansene, slik at matslag-referansene peker til de samme strengobjektene som hos klienten (jamfør figur 12.5 side 402.)
Kanskje må vi også kopiere objektene som tabell-elementene peker til. Denne problemstillingen er drøftet i kapittel 12.3, fra side 402 og utover. I diett-eksemplet er disse objektene immutable (String-objekter), så i dette tilfellet er det unødvendig å lage kopier.
Programliste 9. 5 (side 299) viser et eksempel på dette. Nå lar vi klienten bestemme tabellstørrelsen, dataene legges ikke inn med en gang, men etter hvert.
I vårt tilfelle vil dette se slik ut:
public Diett(int maksMatslag) {
this.matslag = new String[maksAntMatslag];
}
Og hos klienten:
Diett minDiett = new Diett(4); // kan eventuelt lese inn maksimalt antall matslag
I eksemplet har vi altså laget konstruktører etter alternativ 2 og 3.
Algoritmene for å jobbe med tabeller av referanser er de samme som for å jobbe med tabeller av primitive datatyper. Du må bare huske at det er referanser du jobber med og ikke primitive datatyper. La oss lage operasjoner mot tabellen matslag i klassen Diett.
Vi skal kode tre forskjellige operasjoner her:
Her har vi det felles kravet at vi ikke kan legge inn et matslag dersom det fins der fra før. Vi begynner med å lage en privat metode som sjekker dette. Vi lager den slik at den, dersom matslaget eksisterer, returnerer indeksen. Det gjør det enkelt å programmere den tredje av de tre metodene. Dersom matslaget ikke eksisterer, returnerer vi -1. Hjelpemetoden ser slik ut:
private int finnIndeks(String mat) {
if (mat != null) {
mat = mat.trim();
for (int i = 0; i < matslag.length; i++) {
if (matslag[i] != null && matslag[i].equalsIgnoreCase(mat)) {
return i;
}
}
}
return -1;
}
I if-setningen skal vi sammenligne verdien til et tabell-element med parameteren mat. Vi bruker metoden equalsIgnoreCase() på grunn av at vi skal sammenligne strengene, og ikke referansene. (Dersom vi bruker == sammenligner vi referansene, og de er like bare dersom de peker til det samme objektet). Nå vet vi at enkelte av tabell-elementene kan ha verdien null. For å unngå NullPointerException må vi derfor kontrollere at elementet ikke er null, før vi sender meldingen equalsIgnoreCase() til det:
if (matslag[i] != null &&
matslag[i].equalsIgnoreCase(mat)) {
return i;
}
Når dette er sagt, aksepterer metoden equalsIgnoreCase() null som argument. Ettersom vi allerde har forsikret oss om at parameteren mat ikke har verdien null, oppnår vi det samme dersom vi skriver:
if (mat.equalsIgnoreCase(matslag[i])) {
return i;
}
Metoden som registrerer et nytt matslag ser slik ut:
/**
* Metoden registrerer nytt matslag.
* Det går bra dersom det er plass, og dette matslaget ikke er
* registrert fra før.
*/
public int registrerNyttMatslag(String nyMat) {
if (nyMat == null) {
return NULL;
}
nyMat = nyMat.trim();
if (finnIndeks(nyMat) >= 0) {
return FINNS_FRA_FØR; // RETUR
}
for (int i = 0; i < matslag.length; i++) {
if (matslag[i] == null) {
matslag[i] = nyMat;
return REGISTRERT; // RETUR, ok.
}
}
return FULLT ; // RETUR
}
Her har vi vært litt avanserte når det gjelder returverdier. I stedet for bare å returnere false når ting ikke går bra, har vi brukt en statusverdi som sier litt mer. Statusverdiene er klassekonstanter som er deklarert i begynnelsen av klassen:
public static final int REGISTRERT = 1;
public static final int FINNS_FRA_FØR = -1;
public static final int NULL = -2; // nullverdi er sendt inn som argument
public static final int FULLT = 0;
Klientprogrammet kan bruke dette på følgende måte:
int status = minDiett.registrerNyttMatslag("test");
if (status == Diett.FINNS_FRA_FØR) {
...gjør ditt....
} else if (status == Diett.FULLT) {
...gjør datt....
} else ...
Vi henter verdien til offentlige klassekonstanter ved å kvalifisere med klassenavnet, f.eks. Diett.FULLT. Vi sier at Diett her fungerer som en kvalifikator.
La oss nå se på metoden som bytter ut et bestemt matslag med et annet. Metodehodet skal se slik ut:
public boolean byttUt(String gmlMat, String nyMat)
gmlMat er et matslag som skal eksistere i tabellen fra før. Det skal byttes ut med matslaget gitt ved nyMat, som da altså ikke skal eksistere i tabellen fra før. Dersom disse forutsetningene er oppfylt, kan utskifting skje. Metoden returnerer true dersom utskrifting skjer, ellers false.
public boolean byttUt(String gmlMat, String nyMat) {
if (gmlMat == null || nyMat == null) {
return false;
}
gmlMat = gmlMat.trim();
nyMat = nyMat.trim();
int indeksGml = finnIndeks(gmlMat);
boolean gmlMatFins = (indeksGml >= 0); // gmlMat skal finnes fra før
boolean nyMatFinsIkke = (finnIndeks(nyMat) < 0); // nyMat skal ikke
finnes
if (gmlMatFins && nyMatFinsIkke) { // nå skjer utskifting
matslag[indeksGml] = nyMat;
return true;
}
return false;
}
Litt repetisjon: Merk måten vi gir verdi til de logiske variablene på. I stedet for
boolean gmlMatFins = (indeksGml >= 0); // gmlMat skal finnes fra før
kan vi skrive
boolean gmlMatFins;
if (indeksGml >= 0) {
gmlMatFins = true;
} else {
gmlMatFins = false;
}
Her finner du klassen så langt som vi er kommet, sammen med et meget enkelt klientprogram. Kompiler og kjør dette. Kontroller at du skjønner klientprogrammet.
Resten av eksemplet skal du programmere steg for steg, slik at du får litt praktisk erfaring med streng-tabeller før du begynner på denne ukes innleveringsoppgave.
Metoden som finner maksimalt antall matslag ser slik ut:
public int finnMaksAntMatslag() {
return matslag.length;
}
Men hvordan ser metoden som finner virkelig antall matslag ut? Husk at det kan godt hende ikke hele tabellen er fylt opp, og det kan også hende at noen matslag er fjernet. Du skal lage en metode som løper gjennom tabellen og teller antall elementer som er forskjellig fra null.
Vi har igjen en metode for å registrere data. Det er den som bytter ut et tilfeldig matslag. Vi skal altså velge et tilfeldig matslag fra tabellen. Det enkleste er å trekke et heltall (en indeks) i intervallet [0, 4] og så sjekke at det virkelig ligger et matslag på denne indeksen. Dersom det ikke er tilfelle, trekker vi på nytt inntil vi treffer på en gyldig indeks. Vi trenger en tilfeldigtallgenerator. Det er nok med én slik for alle objektene av klassen, vi lager den derfor som en klassekonstant:
private static final java.util.Random RANDOM_GEN = new java.util.Random(17); // klassen Random, se side 238.
En tilfeldigtallgenerator må ha en startverdi, gjerne et primtall. Samme startverdi gir den samme sekvensen med "tilfeldige" tall. Her har vi brukt 17.
Nå skal du prøve å lage metoden.
Du skal lage en metode som leter fram et bestemt matslag. Dersom det finnes, skal det fjernes, og metoden skal returnere true. Hvis ikke skal metoden returnere false. Du fjerner matslaget ved å sette det aktuelle tabell-elementet lik null. Metodehodet skal se slik ut:
public boolean fjern(String mat)
Til slutt skal du lage en metode som finner ut om tabellen er full eller ikke. Den skal ha følgende metodehode:
public boolean fullt()
Her finner du fullstendig kildekode.