Java Programmering - 9. del

Tags:    java
Skrevet af Bruger #4487 @ 07.03.2011

Indledning


Vi har nu haft en helt masse omkring GUI's og den bedste måde at blive god til at programmerer GUI's på, er at starte i det helt små, og derefter arbejde sig opad. I denne artikel skal vi ikke arbejde med GUIs. Vi skal nemlig lære at lave vores eget interface, og høre om fordelene ved sådan et interface. Vi skal også høre om abstrakte klasser og metoder, og herefter opveje det imod et interface. Vi skal ydermere høre om statiske og konstante felter og metoder, og hvorfor sådanne er smarte. Til allersidst, skal vi høre om noget som hedder Enumeration. Men lad os komme i gang.

Interfaces


Du kan måske huske at vi brugte et interface i vores forrige artikel. Interfacet hed 'ActionListener', og blev brugt, når vi skulle få et komponent til at lytte efter en handling. Vi var nødt til at lave metoden 'actionPerformed(ActionEvent evt)' hver eneste gang vi havde implementeret interfacet 'ActionListener' til vores klasse. Interfaces virker nemlig sådan at de giver en slags regler for hvilke ting, som vores klasse skal indeholde for at være en del af det interface. Det betyder også at interfacet kun indeholder metode signaturer og ikke nogen metode krop. Der er nogle gyldne regler man skal følge når man vil lave sit eget interface, og jeg har listet dem her nedenunder.

  1. Når du skal lave et interface, laver du ikke direkte en klasse. Derfor skal du heller ikke benytte ordet 'class' i definitionen, men ordet interface istedet. En definition på et interface kunne se således ud - public interface MitInterface

  2. Alle metoderne som er defineret i et interface, må aldrig have en metodekrop. Man skriver kun signaturen på metoden og afslutter med et semikolon. Alle metoder defineret i et interface er også altid public. Man behøver derfor heller ikke at angive en access modifier til metoden, da de kun kan være public alligevel. En definition på en metode i et interface kunne se ud som følger - void tegn(Graphics g);

  3. Interfaces kan godt indeholde variabler/felter, men de bliver alle sammen static og final (kosntante), mere om disse senere.

  4. Interfaces kan ikke indeholder konstruktører, men dette er også ligemeget, da man aldrig har brug for at lave et objekt af et interface.

  5. Alle metoderne behøver egentlig heller ingen access modifier, da alle metoder kun kan være med access modifieren public.


Ud fra de regler vi kender her, kan vi prøve at lave vores eget interface. Husk at interfaces er noget som giver en slags regler, som vores klasser, der implementerer vores interface så er nødt til at følge og indeholde. Lad os prøve at lave et interface, som vi kunne kalde Rovdyr, som så kan give nogle generelle regler (metoder) for alle rovdyr. Det første vi skulle gøre, var at deklarerer at vi ville lave et interface med navnet Rovdyr, så vores kode ser derfor først således ud (Husk at et interface stadig skal gemmes ligesom en klasse, med navnet.java - I vores tilfælde Rovdyr.java):
Fold kodeboks ind/udJava kode 

Herefter kan vi så begynde at implementerer vores regler, i dette tilfælde metoderne, som alle Rovdyr skal have. Vi kunne f.eks. sige at alle Rovdyr jager deres bytte, og derefter spiser dem. Vi kan derfor lave 2 metoder signaturer til dette, nemlig en der hedder jagBytte() og spisBytte(String bytte). Den første kan vi så give returneringstypen boolean, mens den anden skal have String. Vores interface kan så se således ud.
Fold kodeboks ind/udJava kode 

Nu er vores interface faktisk færdig og klar til at blive brugt. Vi kan nu lave en klasse, som skal bruge dette interface. Da vi gerne vil blive i tankegangen, skal klassen, selvfølgelig være at rovdyr, så vi kan f.eks. kalde vores klasse for Puma. For at bruge et interface i en almindelig klasse, skal vi efter klassens navn skrive nøgleordet implements efterfulgt af navnet på det interface vi gerne vil implementerer. Vores kode ser nu således ud:
Fold kodeboks ind/udJava kode 

Da vores klasse implementerer interfacet Rovdyr, er klassen tvunget til også at implementerer de to metoder som vi specificerede i vores interface. Til at starte med skal vi bare give dem tomme metode kroppe, da vi vil fylde indhold i senere. Vores kode ser nu således ud:
Fold kodeboks ind/udJava kode 

Lad os først Specifere et array, hvor vi lister nogle af de dyrearter, som en puma godt kan lide at spise. Værdierne i vores array skal være - 'hest', 'hjort', 'ged' og 'ko'. Vores kode ser nu således ud:
Fold kodeboks ind/udJava kode 

Nu da vi ved hvilke 'livretter' vores puma har, kan vi begynde at lave koden til vores metoder. Vores første metode er 'jagBytte()', som returnerer en boolean værdi, med enten true hvis dyret blev fanget, eller false, hvis ikke den fangede noget. Til at udføre denne opgave, skal vi benytte klassen Random, som findes i 'java.util' pakken. Vi skal herefter returnere enten true, eller false, ved hjælp af et if statement, der tjekker det tal som blev genereret randomly af vores Random klasse. Vores metode ser nu således ud:
Fold kodeboks ind/udJava kode 

Herefter kan vi lav spisBytte(String bytte) metoden, hvor vi først tjekker om vores Puma fangede dyret via metoden jagBytte(). Hvis den fangede dyret, så skal Pumaen spise byttet, og herefter skal vi udskrive en besked, som viser om Pumaen kunne lide byttet eller ej. Dette kan vi opnå ved at tjekke vores 'livretter' array igennem og så om vores parameter matcher en af livretterne. Vores metode ser nu således ud:
Fold kodeboks ind/udJava kode 

Vores fulde kode ser nu således ud
Fold kodeboks ind/udJava kode 

Nu har vi så implementeret og brugt vores interface Rovdyr, fordi en puma er et rovdyr, men vi kunne f.eks. også lave endnu et interface kaldet Kat, fordi en Puma er jo foruden et Rovdyr også en del af kattefamilien. Vores Kat interface skal kun have en metode, som returnerer en String og har navnet sigLyd(). Vores Kat interface ser nu således ud.
Fold kodeboks ind/udJava kode 

Vi kan nu implementerer dette interface i vores klasse Puma, da den jo også er en Kat. Det smarte med interfaces er nemlig at du kan implementerer så mange du har lyst til, bare du husker at implementerer interfacets tvungne metoder også. Så implementer nu også interfacet Kat, og metoden sigLyd() (i metodens krop skal du bare lave en returnering af en String, med en besked der siger en lyd som en Puma, f.eks. "Roooarrrr"). Vores kode ser nu således ud:
Fold kodeboks ind/udJava kode 

Nu har vi altså en klasse med navnet Puma, som implementerer både interfacet Rovdyr, som indeholder nogle retningslinjer for alle Rovdyr i vores system, men vi har også implementeret interfacet Kat, som jo indeholder nogle retningslinjer vi kan give til alle Katte i vores system. Indtil nu har vi kun en Puma at lege med, men det kunne være at du også havde en Løve osv. Nu kan du jo sige er dette ikke bare ekstra arbejde, fordi vi jo alligevel skal implementerer alle metoderne fra interfacet, også kan det jo være lige meget om vi først specificerer det i et interface? Nej, desværre er det ikke lige meget, fordi at et interface for det første tvinger dig til at implementerer dens metoder, og du vil således få en kompileringsfejl hvis ikke dette gøres, og på den måde kan du sikre dig at du har husket at implementerer alle metoderne. Desuden er ens kode design langt bedre, fordi at koden nu giver mere mening, nemlig at en Puma er både et Rovdyr, men også en del af kattefamilien. Alle katte har en speciel lyd som de kan sige, mens alle Rovdyr først jager deres bytte, for derefter at spise det. Så linjen - public class Puma implements Rovdyr, Kat - giver jo super meget logisk mening, baseret på et designmæssigt synspunkt. En anden smart ting ved at implementerer interfaces i sin klasse, er at man kan benytte interfacet som datatype, når man gerne vil lave et objekt af klassen Puma. fordi at vi med interfacet har gjort dette muligt - Puma 'er et' Rovdyr og Puma 'er en' Kat - altså akkurat ligesom hvis vu benyttede et almindeligt nedarvningshieraki.

Abstrakte Klasser


Vi har allerede arbejdet med et almindeligt nedarvningshierarki i en af de andre artikler, og når man snakker om abstrakte klasser, snakker man også om nedarvning, fordi en abstrakt klasse ikke kan fungere alene. En abstrakt klasse fungere egentlig lidt ligesom et interface, fordi du kan i din abstrakte klasse definerer en hel masse 'abstrakte metoder', som brugeren der nedarver fra den abstrakte klasse, så er nødt til at implementerer, akkurat ligesom med et interface. Dog er der en lille hage ved dette, som vi kommer ind på senere, men noget andet er at abstrakte klasser faktisk ikke behøver at have abstrakte metoder. Lad os f.eks. sige at vi har en abstrakt klasse kaldet GeometriskFigur, og i denne abstrakte klasse har vi så nogle metoder, som ikke er abstrakte, hvad er så formålet med at have denne klasse? Jo svaret er at vi sikrer at brugeren ikke kan lave et objekt af den abstrakte klasse, men stadig kan benytte den som datatype til at lave objekter af dens underklasser (polymorfi). Lad os f.eks. sige at vi har en Klasse kaldet cirkel, som arver fra den abstrakte klasse GeometriskFigur. Klassen Cirkel arver altså alle metoderne, fordi den er underklasse til denne superklasse, men er ikke tvunget til at implementerer dem, hvis dette ikke er nødvendigt. Man er altså heller ikke tvunget til at 'Override', eller overskrive metodernes kroppe, med sin egen kode. Når en sådan klasse er 'halvt' abstrakt kalder man det ofte også en hjælpe klasse, fordi den har til henblik at hjælpe underklasserne med allerede foruddefinerede metoder osv. Hvis metoderne dermed også var abstrakte, ville klassen pludselig være en fuldstændig abstrakt klasse, og ville i princippet fungere ligesom et interface. Lad os nu prøve at lave en abstrakt klasse til vores 'dyre' eksempel fra før. Vi kunne kalde klassen for 'Pattedyr', da en Puma faktisk også er et Pattedyr. Måden man starter en abstrakt klasse på er ved i klassedefinitionen ikke kun at skrive 'class', men faktisk 'abstract class', og man laver abstrakte metoder også ved at skrive ordet 'abstract' i metodens signatur. Vores abstrakte klasse Pattedyr skal have to abstrakte metoder, en som returnerer en boolean, og hedder harFodt(), og en som returnerer en String og hedder plejUnge(). Husk at du ikke kan skrive metodens krop i den abstrakte metode, da de jo fungere ligesom ved et interface. Vores nye klasse ser nu således ud:
Fold kodeboks ind/udJava kode 

Nu kan vi så 'udvide' vores Puma klasse med denne abstrakte klasse, og dette gøres ved at skrive ordet 'extends' efterfulgt af navnet på vores abstrakte klasse, som så bliver vores Superklasse. Da klassen er fuldt ud abstrakt, skal du altså implementerer alle dens metoder, og give den en krop, indtil videre kan du bare lade kroppen være tom i de to metoder. Vores Puma klasse ser nu således ud:
Fold kodeboks ind/udJava kode 

Nu kan vi så lave en med et random tal finde ud af om Pumaen har født en unge, og herefter returnere den passende boolean (true hvis den har født, ellers false). Herefter kan vi så i metoden plejUnge først tjekke om den overhovedet har født, for derefter at returnerer en passende String. Vores kode ser nu således ud:
Fold kodeboks ind/udJava kode 

Nu har vi gjort brug af vores abstrakte klasse, som egentlig fungerer akkurat ligesom et interface. Man kan sige, hvad er forskellen så på en fuldstændig abstrakt klasse og et interface? Forskellen er at du kan implementerer så mange interfaces, som du har lyst til i en enkelt klasse, men du kan kun, og jeg gentager, du kan kun udvide fra en abstrakt klasse for hver enkelt klasse. Derfor hedder tommelfingerreglen at du kun skal udvide med abstrakte klasser, hvis du kun skal bruge nedarvningsfordelene med denne ene klasse. Så snart du har mere end en klasse, som du gerne vil benytte i et nedarvningshieraki, skal du benytte interfaces. Desuden så gem det med abstrakte klasser, mere til hjælpe klasser, og lav derfor fuldstændigt abstrakte klasser til interfaces i stedet, hvis dette er muligt. Nå men nok om det, nu er det vist på tide at så vores klasser i aktion. Lad os lave en Main klasse, som her lavet et objekt af vores Puma, og kører nogle af dens forskellige metoder. Vores Main klasse ser nu således ud:
Fold kodeboks ind/udJava kode 

Et output kunne så være som følgende:

Roooarrr
Vores Puma spiste en af sine livretter, nemlig ko.
Vores Puma plejede sine dejlige unger, indtil de blev voksne.
Vores Puma spiste en/et Hund som den ikke så godt kunne lide.
Vores Puma plejede sine dejlige unger, indtil de blev voksne.
Roooarrr


Statisk (Static)


Du har sikkert lagt mærke til nøgleordet static hver eneste gang vi brugte vores Main metode. Det er der en helt speciel grund til, fordi ordet static betyder nemlig at metoden i dette tilfælde er statisk og derfor ikke behøver et objekt for at kunne fungere. Det betyder altså at vi kan bruge metoden uden først at skulle lave et objekt af den klasse, som metoden er lavet i, for at kunne bruge metoden. Det betyder dog også at ting som er deklareret statiske, ikke kan 'personliggøres' ligesom objekterne kan. Vi kan altså kun arbejde med 'et objekt' om man vil af klassen, når den er statisk. Det betyder altså at hver gang du ændrer noget via en metode, vil det altså blive ændret for hele klassen som bruges. Lad os prøve at lave en ny klasse, som vi kan kalde Hane. Giv den en mutator metode (void), som hedder sigLyd(), og gør metoden statisk ved at skrive nøgleordet static i metode signaturen. Lav også en variabel med datatypen String og navnet lyd, hvor du med det samme initialiserer den med Stringen - "Kykke-Li-Kyy" - Husk også at gøre variablen statisk. Vores kode ser nu således ud:
Fold kodeboks ind/udJava kode 

Vi kan nu i vores Main klasse benytte vores metode sigLyd() fra klassen Hane, uden først at lave et objekt af denne klasse. Vi skriver sådan set bare klassens navn og benytter herefter dot notation til at kalde klassen metoder. Vi skal altså for at kalde metoden sigLyd() som er statisk fra klassen hane simpelthen bare skrive - Hane.sigLyd();, og vi har nu kaldt metoden sigLyd(). Lad os se hvordan det ser ud i vores Main klasse.
Fold kodeboks ind/udJava kode 

Vi har nu kaldt vores statiske metode, uden at lave et objekt af klassen Hane først. Vi kan altså når ting er statiske kalde dem uden at bruge et objekt af den pågældende klasse først. Derfor kaldes statiske metoder og for klasse metoder og statiske variabler/felter kaldes sjovt nok for klasse variabler.

Konstant (Final)


Vi har lige lært om static nøgleordet, og nu er det tid til at lære om nøgleordet Final, som i Java programmering betyder at noget er konstant. Når noget er deklareret Final (konstant), så kan man altså ikke ændre på indholdet. Oftest deklarerer man variablerne for Final, men du kan også deklarerer metoder som Final. I vores eksempel skal vi kun se på hvordan man laver en konstant variabel. Lad os f.eks. lave en ny klasse, og kalde den for Person. Lav så to variabler med henholdsvis datatyperne String og int, og kald dem så henholdsvis navn og alder. Lav variablen med navnet 'navn' som en konstant variabel (med nøgleordet final), og vi har nu en Person klasse der ser således ud:
Fold kodeboks ind/udJava kode 

Læg mærke til at den konstante variabel's navn er udelukkende med store bogstaver. Dette er god programmeringspraksis at huske, når man arbejder med konstante variabler, fordi det er en anden måde for dig at kunne huske på når ja, variablen er konstant, og kan derfor ikke ændre sit indhold. Men syntaks mæssigt har det ikke nogen betydning, det er sådan set bare en god skik og huske. Hvis du laver et objekt af vores klasse Person og kører metoden 'udskrivDetaljer()', vil den udskrive navnet og alderen. Hvis du så prøver at ændre navnet via en metode eller lignende vil du finde ud af at du ikke kan ændre indholdet i variablen NAVN, da den er konstant. I vores næste eksempel med enum's skal vi se et yderligere eksempel på nogle konstanter.

Enumeration (Enum)


En enum er en slags klasse, hvor vi inde i klassen/enumerationen definerer objekterne. Du kan altså ikke uden for vores enum definerer objekter af den. Det betyder at alle objekterne af en enum er konstanter, og vi kan derfor kun benytte dem, men aldrig ændre dem direkte. Når man skal lave en enum, skal man i klasse definitionen skrive nøgleordet enum i stedet for order class. herefter skal men i kroppen til vores enum, definerer navnene på vores forskellige konstante objekter. Lad os f.eks. lave en enum klasse, som symboliserer vores solsystem, og objekterne i vores solsystem enum kan så være navnene på solsystemets planeter. Vores Enum ser nu således ud:
Fold kodeboks ind/udJava kode 

Læg mærke til at Alle vores objekter er med store bogstaver, fordi de som sagt altid er konstante. Læg også mærke til at hver gang vi har deklareret et objekt af vores enum, har vi ikke lavet et semikolon, men et almindeligt komma. Dette er fordi vi specificerer mere end et objekt af vores enum klasse. først ved det sidste objekt (planet), afslutter vi med et semikolon. Lad os nu giv alle vores planeter 2 parametre, nemlig planetens masse (kg), og planetens radius (km) - du kan her finde de forskellige oplysninger om hver af de enkelte planeters masse og radius. - Når du har indført alle planeternes masse og radius som parametre, ser vores enum således ud (bemærk at jeg har ved massen benyttet den statiske metode pow(double a, double b), fra klassen 'Math', for at benytte eksponenter, hvor a er det grundlæggende tal, og b er det antal gange tallet skal opløfte sig selv i.):
Fold kodeboks ind/udJava kode 

Nu skal vi bare lave konstruktør, til vores enum objekter, men da alle objekterne i en enum er lavet inde i selve enum klasse, vil en enum konstruktør altid være private, og ikke som vi er vant til, nemlig public. Vores konstruktør skal kunne tage to parametre, som vi lige har angivet, nemlig den første for planetens masse, og den næste for planetens radius (begge med datatypen double.), Vi skal også lave 2 almindelige objekt variabler, så vi kan gemme vores objekters parametre i nogle variabler, så vi altid kan finde ud af hvad massen eller radiusen er for en enkelt planet. Vores enum ser nu således ud:
Fold kodeboks ind/udJava kode 

Nu har vi faktisk en fungerende enum klasse, men vi kan ikke rigtig gøre noget med den endnu, så lad os lave en lille metode som kan beregne din vægt på de enkelte planeter, i vores solsystem. Kald metoden for beregnVaegt(double vaegt), og lad den returnere en double. metodens krop skal indtil nu bare være tom, da vi skal have lidt teoretisk ved før vi går videre. Vores enum ser nu således ud:
Fold kodeboks ind/udJava kode 

Nu kan vi så lave beregningen, som beregner vægten på de enkelte planeter, men først skal vi deklarerer 2 konstante variabler, nemlig en med Jordens masse, og en med Jordens radius. Dette gjorde vi ved at bruge ordet final i deklarationen af variablerne, og du kan finde massen og radiusen på Jorden ved at kigge på planeten Jorden i vores enum. Vores kode ser nu således ud:
Fold kodeboks ind/udJava kode 

Vi kan nu benytte følgende matematisk udtryk til at finde vores vægt på de enkelte planeter.

vægtenPåPleneten = dinVægt * JordensRadius^2 * planetensMasse / planetensRadius^2 * JordensMasse

Vi kan nu lave beregningen i vores metode, og herefter returnerer resultatet. Vores enum ser nu således ud:
Fold kodeboks ind/udJava kode 

Vi har nu en enum klasse, med objekter der repræsenterer planeterne i solsystemet, og hver objekt har en speciel masse og radius. Vi kan så ved at kalde metoden for hvert enkelt objekt, finde den specifikke vægt for os på den enkelte planet. Vi kan nu i vores Main klasse lave for each løkke, som går igennem alle objekterne i vores enum kaldet solsystem, og altså kalde metoden beregnVaegt(double vaegt) for hver planet. Bemærk også at vi ikke behøver/kan lave objekter af vores enum klasse, da disse objekter jo allerede er lavet for os, vi skal sådan set bare kalde de enkelte objekter, som havde vi allerede lavet dem. Alle enum klasser har en metode som hedder 'values()', som giver os alle objekterne i enum klassen, som var de i en kollektion. Vores Main klasse ser nu således ud:
Fold kodeboks ind/udJava kode 

Vi vil så få følgende output, hvis vi kører vores kode.


Vægten er 26.634211458083662 kg, på MERKUR
Vægten er 63.820715265441656 kg, på VENUS
Vægten er 70.5 kg, på JORDEN
Vægten er 26.70410818008885 kg, på MARS
Vægten er 178.3422370906399 kg, på JUPITER
Vægten er 75.13840613928849 kg, på SATURN
Vægten er 63.815726120617036 kg, på URANUS
Vægten er 80.1901838050505 kg, på NEPTUN


Dette var så alt for denne artikel, og glæd dig til næste artikel, hvor vi skal lære endnu mere om hvordan man programmerer i Java.

Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.

Del også gerne artiklen med dine Facebook venner:  

Kommentarer (2)

User
Bruger #5620 @ 12.03.11 18:15
Var da en fornuftig artikel :).

Kan man ikke sætte en variable til en enum?

noget i retning af:
Solsystem e_planet = MERKUR;


Nu kender jeg intet til java random, men en int generator der returnerer hel tal mellem 0 og 2, hvor 2 er exclusive vil forhåbentlig altid enten give 0 eller 1. Så hvis du ikke har fået 0 må du have fået 1 og behøver ikke teste på det.

Alternativt kan man i de fleste sprog skrive:

return fanget == 1;


Jeg synes det er fornuftigt at du i interface sektion kun viser en metode af gangen når du taler om den, synes du bør gøre det ved alle sektionerne.
User
Bruger #4487 @ 13.03.11 23:21
Du kan godt sætte dit Enum objekt ind i en variabel.

Dette kan f.eks. gøres således:

Fold kodeboks ind/udJava kode 


Men husk at du ikke kan lave et objekt af en enum, da objekterne kun kan laves inde i vores Enum alene.

Og med hensyn til test af om det vilkårlige tal var 0 eller 1, har du ret, jeg behøvede måske ikke at teste for den sidste. Men ellers tak for de fine ord :)
Du skal være logget ind for at skrive en kommentar.
t