Java Programmering - 10. del

Tags:    java
Skrevet af Bruger #4487 @ 24.03.2011

Indledning


I sidste artikel lærte vi om brugen af interfaces, og hvordan man laver et interface selv. Vi lærte om at en klasse, hvor alt kun var abstrakt, fungerede ligesom et interface, men med den lille hage at underklasser kun kan nedarve fra en klasse ad gangen, hvorimod at de kan implementere så mange interfaces som de har lyst til. Vi lærte også om at ting angivet med ordet static betød at man ikke behøvede at 'instantiere' klassen, altså man behøvede ikke at lave et objekt af klassen, før man kunne bennytte dens metoder osv, netop fordi at metoderne var angivet som statiske. Dette betød dog også at man ikke kunne 'personliggøre' sin klasse, da man jo ikke behøvede at lave objekter af den. Vi lærte også om ordet final, som betød at noget var konstant. Når noget er konstant, kan indholdet ikke ændres, og vi vil ofte kun gøre variabler konstante. Når vi gør variabler konstante skriver vi, pga god programmeringspraksis, alle bogstaverne i variablens navn med store bogstaver. Dette sikrer at man kan huske at denne variabel var en konstant variabel, og indholdet kunne derfor ikke ændres. Vi lærte om hvad et enum var, og hvordan man lavede sit eget enum. Vi lærte at man kan ikke lave objekter af et enum uden for dens krop, men kun inde i dens krop. Derfor var en enums konstruktør også altid private, og aldrig public. På trods af denne lille ændring i hvordan man lavede objekterne kunne man ellers benytte et enum, ligesom en almindelig klasse, med almindelige metoder, variabler osv.

Efter at have forstået alt dette, kan vi nu kalde os selv for forholdsvis øvede Java programmører, i al fald på Teorien. Tro nu ikke at der ikke er mere at lære, fordi det er der. Vi er nemlig langt fra færdige, da der stadig er mange spændende emner at tage op til gennemgang, så vi kan blive endnu bedre. I denne artikel skal vi lære lidt om hvordan man håndterer små fejl/undtagelser, som desværre altid kan ske når man arbejder med 'variable' og uforudsigelige forhold, nemlig en anden bruger. På engelsk kalder man dette emne for Errors & Exceptions, og det er noget som faktisk er meget vigtig at forstå når du som programmør skal sidde og programmere et program, som andre skal bruge, men lad os komme i gang.

Fejl (Errors) & Undtagelser (Exceptions)


Når du programmerer i Java er det vigtigt at fejlsikre dit program, mod logiske fejl. Lad os sige at brugeren skulle hente noget fra et array, og han skal derfor angive hvilket indeks nummer som han gerne vil hente. Brugeren kommer så til at skrive et for højt indeks nummer, og programmet kan ikke finde det indtastede indeks, og afslutter måske programmet. Brugeren tænker nu, 'Hvad pokker, programmet lukkede bare', men det var i virkeligheden en indtastningsfejl fra brugerens side. Måske ville programmet ikke lukke, men bare ikke finde det ønskede objekt fra vores array, fordi den ikke kan finde vores indeks. Brugeren vil derfor måske føle at der ikke rigtig sker noget, fordi programmet ikke henter det som han har bedt om, og han får derfor også en dårlig brugeroplevelse. Problemet med at man forsøger at hente et objekt fra et array ved et indeks som er for højt, kaldes også for IndexOutOfBoundsException, og som ordet antyder, så er det en undtagelse som der kører ved sådan en bruger fejl. Man kan sige at alle nærmere logiske og brugerbestemte indtastningsfejl håndteres ved undtagelser, hvorimod at hvis brugeren har en fejl i sin Java installation, eller der på anden måde er en fejl i hans styresystem som gør at programmet ikke kan køre, så kaldes det ikke for en undtagelse, men en fejl (Error). Du har som udgangspunkt ikke kontrol over sådanne fejl som programmør, da det ikke er fejl som dit program er ansvarlig for, men brugerens computer. Når du nu ved dette kan du måske regne ud at vi kun skal håndtere logiske fejl, som man kan håndtere med undtagelser (Exceptions).

Der findes andre fejl, som man som bruger kan lave, men som vi som programmører kan fange og gøre brugeren opmærksom på. I vores eksempel her vil vi gerne have at brugeren skal indtaste først hans/hendes navn, derefter hans/hendes alder og til sidst hans/hendes køn. Dette lyder som værende meget simpelt og hvordan kan der ske fejl i dette? Det burde der heller ikke kunne ske, men da mennesket er meget uberegneligt kan der altid opstå fejl, og da dette er meget simple indtastningsfejl, er vi nødt til at håndtere dem. Brugeren er måske alt for hurtig og læste så ikke at der stod indtast alder. Brugeren indtaster måske så sit efternavn eller noget helt tredje, og det er jo ikke så smart, når det vi ville var at sikre at brugeren indtastede sin alder. Vores eksempel indeholder ikke et grafisk UI, men et tekst baseret UI, som køres via kommandoprompten/terminalen. Derfor skal vi også bruge scanner klassen til at læse brugerens input. Vi kan så bruge en while løkke, som skal køre indtil at vi har fået alle oplysningerne fra brugeren, og han har indtastet en afslutningskommando. Det hele laver vi i vores Main metode, så vi nemt kan køre den. Lad os se hvordan skelettet ser ud til vores simple program:
Fold kodeboks ind/udJava kode 

Vi har et Scanner objekt, som skal læse de input, som brugeren skriver i terminalen, og så har vi en boolsk variabel, som bruges til at køre løkken indtil den sættes til false. Nu skal vi så benytte et nyt nøgleord i Java, som hedder try. Man kalder også dette for en try blok, fordi efter ordet try skal vi nemlig have to krøllede parenteser { og }, som angiver den kode, som køres inde i vores try blok. Som ordet antyder, vil vores try blok 'prøve' (Eng.: try), og kører vores kode indenfor blokkens rammer, og hvis der skulle ske en fejl, vil koden stoppe, og vi har nu mulighed for en meget vigtig ting. Vi har nemlig nu mulighed for at fange den fejl, som der skete, fordi vi kørte koden i en try blok, og vi kan så behandle den som vi har lyst til. Dette gøres med noget som man kalder en catch blok, som skal angives lige efter ens try blok. Vores catch blok gør det som ordet også antyder, nemlig at den fanger (Eng.: catch) den fejl, som der skete, og behandler den så på den måde, som vi har lyst til (Ofte ved at udskrive en fejl besked). Men fordi at du kan lave så mange catch blokke efter din try blok, som du ønsker, hvordan finder man så ud af at udskrive den rigtige fejlmeddelse? Problemet er nemt at løse, fordi alle catch blokke indeholder nemlig et parameter felt, hvor man angiver den Exception klasse, som der er tale om. Ved fejlen at brugeren valgte et indeks i vores array, som var for højt, for man f.eks. en IndexOutOfBoundsException, og det er derfor denne klasse man skriver i parameteren. Vores catch blok vil derfor se således ud - catch (IndexOutOfBoundsException ex) { Kode her!! } - Fordi man altid skal have en catch blok efter try blokken, kalder man også det hele for en try, catch blok. Hvis du ikke vil definere hvilken Exception som skal fanges, kan du også benytte Superklassen for alle exceptions, og den hedder selvfølgelig bare 'Exception'. I vores eksempel skal vi selvfølgelig indføre en try, catch blok i vores while løkke. Indtil videre skal vores catch bok bare have parameteren (Exception ex), da vi ikke helt er sikker på hvilke fejl der kan ske. Vores kode ser nu således ud:
Fold kodeboks ind/udJava kode 

Vi kan nu indenfor vore try blok begynde at lave den kode som skal køres normalt i vores while løkke. Altså den kode som skal 'prøve' og køre, indtil der sker en fejl, som vi så er nødt til at fange i vores catch blok. Vi skal her bare benytte simple system.out.println() statements, til at udskrive de 'informations' beskeder der er til brugeren (såsom 'Enter name:'). Herefter skal vi benytte vores scanner objekt til at læse inputtet fra brugeren, som vi så gemmer i en lokal variabel. Metoden til at læse en linje med scanner objektet hedder nextLine(). Der er dog en lille twist, da vi gerne vil gemme vores alder input, i en lokal variabel med datatypen 'int', skal vi gøre følgende - int age = Integer.parseInt(input.nextLine()); - Metoden parseInt fra klassen Integer, vil så omskrive vores String som indeholder vores alder, til en integer værdi. Husk også at gemme inputtet omkring brugerens køn, og vores kode ser så således ud:
Fold kodeboks ind/udJava kode 

Nu har vi sådan set et fungerende program, hvor brugeren kan indtaste input, men der er også det eneste. Lad os nu vise det input som brugeren lavede, ved at udskrive de lokale variabler som vi lagde inputtet ind i. Men denne gang skal vi ikke gøre det med en almindelig system.out.println() statement, men med noget man kalder et formateret udskrivningsstatement. Det smarte med sådan et statement, er at vi nemmere kan lave en opsætning på hvordan vores udskrift skal vises. Vores statement ser således ud - System.out.printf(format, args... ) - Parameteren format er en String, hvor vi skriver nogle specielle formaterings tegn, og angiver hvordan det skal formateres, og parameteren args... er vores argumenter, eller vores variabler, som vi gerne vil have vist i vores String. Lad os antage at jeg har to variabler (besked og navn) med henholdsvis strengende "Mit navn er" og "Martin". Nu kan jeg så i min første parameter skrive dette - "%s = %s" - procent tegnet betyder at her kommer et af det første argumenter, som jeg har angivet efterfølgende i min formated udskrivningsstatement. Lige efter procent tegnet har jeg så et 's', som er tegnet for en String. %s betyder altså at dette argument der skal stå på denne plads i vores String, skal være en String værdi. Vores lig med tegn betyder ikke noget, men bliver en del af vores samlede String, hvorefter der kommer et nyt procenttegn med et tilhørende s, og dette er så selvfølgelig det andet argument i vores parameter, skal også være en String. Lad os se på hvordan vores fulde statement ville se ud - System.out.printf("%s = %s", besked, navn); - Når vi kører dette statement, vil den så udskrive 'Mit navn er = Martin', og vi har nu formateret vores udskrift. Efter al denne smøre af Teori, kan du så spørge, om hvad fordelen er ved at formatere, og svaret er at det er nemmere at få ens udskrift til at være som man ønsker det. F.eks. kan man også sætte afstand imellem ens argumenter, så man f.eks. kan opstille ting ovenover hinanden. Det er bl.a. det som vi skal benytte vores formaterede udskrivningsstatement til. Der er dog en lille detalje med vores eksempel, fordi vi jo også har en int variabel som indeholder vores alder, og %s tegnet udskrev jo kun strenge. For at udskrive tal, er vi så nødt til at bruge %d (d står for digit). Desuden skal vi for at lave afstand fra et argument til et andet, skrive vores afstand, og dette gør vi ved at skrive således %-12s, Som så betyder at det næste argument efter dette, skal stå 12 pladser længere til højre. Vi kan nu skrive vores kode således:
Fold kodeboks ind/udJava kode 

Du kan nu prøve at køre din kode, og du skulle gerne få et output som ligner lidt det nedenstående:



Nu kan vi rigtig se hvad vores formaterede udskrivningsstatement har gjort, nemlig opstillet vores tekst, så vi har fået en noget pænere udskrift af vores bruger input. Det var som sagt sådan her vores formaterings String så ud - "%-12s %s \n" - og -12 betød at det næste argument (altså næste gang den skulle udskrive vores %s), skulle være 12 pladser længere henne på linjen. Vores \n (husk at \ er omvendt vej fra en almindelig skråstreg), betød at vi gerne ville lave et linjeskift. Nu da vi har brugt så meget tid på at lære om vores formaterede udskrivningsstatement, kan vi vende tilbage til det som er hovedemner her, nemlig Exceptions.

Vi har nu et program, som fungere upåklageligt, eller gør det? Hvad nu hvis jeg kom til at skrive mit efternavn i stedet for alderen? Dette kan jeg jo ikke, da vi ikke kan konvertere Strengen "Rohwedder" om til en Integer. Så er det at der sker en fejl, men den fejl der sker kaldes en NumberFormatException, så det første vi kan gøre er at udskrifte parameteren i catch blokken fra 'Exception ex' til at indeholder 'NumberFormatException ex'. Nu kan vi så i vores Catch blok lave et almindeligt udskrivningsstatement, som udskriver en besked som f.eks. 'You have to type in your age!'. Vores kode ser nu således ud:
Fold kodeboks ind/udJava kode 

Hvis vi så nu kører programmet og med vilje laver en indtastningsfejl ved vores alder input, så vil Java sige hov, der skete da lige en fejl her, fordi jeg kan ikke konverterer denne String om til en integer værdi, så jeg smider en 'NumberFormatException' som vi så skal fange i en catch blok. Vores output kunne så se således ud:



Sådan nu har vi fanget vores første Exception, og kan derfor se resultatet blomstre ved at der bliver udskrevet en fejlmeddelse til brugeren. Man kan også udskrive noget som man kalder et 'Stack Trace', som er en beskede med hvor fejlen er i koden og hvad fejlen sådan set var. Det kan du gøre ved at skrive vores Exception parameter variabels navn (Som i vores eksempel er 'ex'), og herefter kalde metoden 'printStackTrace()', som så udskriver en besked om hvor fejlen opstod (hvilken linje i koden osv.) Så vi kan altså yderligere i vores catch blok skrive - ex.printStackTrace(); - og den vil så udskrive således:



Normalt er sådan en Stack Trace ikke læseligt for almindelige brugere, og da vi har med et logisk problem at gøre (altså en brugerindtastningsfejl), så er det alligevel ikke noget du som programmør kan stille op, andet en at fortælle brugeren hvad han har gjort galt.

Lad os nu gøre vores eksempel færdig, ved at tilføre noget til vores try catch blok, nemlig en valgfri blok, som hedder finally. Finally blokken er altid den sidste del i vores try catch blok, og den skal derfor skrives under alle de forskellige catch blokke, som vi har tilført. Det som finally gør, er nemlig at når den har kørt koden via try og catch blokken, så springer den direkte over til finally blokken. Det betyder at finally blokken altid bliver udført som det sidste, uanset om der sker en brugerindtastningsfejl, eller ej. Derfor benyttes en finally blok også altid til at afslutte ting med (eller hvis vi har med filer at gøre - lukke dem). i vores eksempel skal vi benytte finally blokken, til at spørge om brugeren ønsker at køre programmet en gang til, eller afslutte programmet. Dette gør vi ved at benytte et if statement, som tjekker om brugeren indtastede y for 'yes', eller n for 'no', og herefter sætte vores bolske variabel 'running' til en passende værdi. Vores kode ser nu således ud:
Fold kodeboks ind/udJava kode 

Vi har nu et program, som vi nu også kan afslutte med en simpel indtastning, nu må programmet da kunne være brugerfejlfrit, eller hvad? Der er stadig en ting som brugeren kan lave forkert, nemlig at indtaste et forkert køn. Vi er kun interesserede i indtastningerne male og female, og alt andet skal give en fejl meddelse. Så hvilken exception kunne vi monstro benytte til denne opgave? umiddelbart er der ikke nogen exception, som passer meget godt lige på denne her opgave, men frygt ej. Vi laver jo bare vores helt egen exception klasse, og benytter den.

Lav dine egne Exceptions


Når man skal lave sine egne exceptions, er det godt at vide noget om nedarvning. Men dette har vi jo allerede beskæftiget os med i nogle tidligere artikler, så dette er selvfølgelig ikke noget problem. Vi skal nemlig, når vi skal lave vores egne exceptions, nedarve fra superklassen som hedder Exception. Vores nye exception klasse, kunne vi f.eks. kalde 'SexIncorrectException', og det eneste denne klasse skal indeholde er en konstruktør, som indeholder et kald til vores superklasses konstruktør, hvor vi angiver en String parameter til denne superklasses konstruktør. Vores String parameter er den besked, som vores Exception skal bruge. Vores nye Exception klasse ser nu således ud:
Fold kodeboks ind/udJava kode 

Vi kan nu i vores main metode, efter at brugeren har indtastet hans/hendes køn, lave et if statement, som tjekker om indtastningen ikke var lig med enten 'Male' eller 'Female', fordi hvis den ikke var et af de to, så er der jo sket en fejl, og vi skal være parat til at smide en exception. Vores kode ser nu således ud:
Fold kodeboks ind/udJava kode 

Sådan nu er vi klar til at smide vores 'SexIncorrectException', hvis brugeren har indtastet noget andet end female/male. Måden vi smider vores egne exceptions på er ved at benytte et nøgleord i Java, som er ordet throw (Bemærk at nøgleordet er uden s til sidst, da der også er noget der hedder 'throws'). throw betyder som bekendt 'kast/smid', og det er jo det vi gerne vil, nemlig at kaste/smide vores nye Exception hvis vores køn ikke er female/male. Vi skal herefter lave et nyt objekt af klassen 'SexIncorrectException', som jo er det objekt som skal smides. Vi skal derfor skrive - throw new SexIncorrectException(); - inde i vores if statement. Nu har vi kastet vores nye exception, og vi skal derfor også fange den igen. Dette gør vi som sagt med en catch blok, som selvfølgelig skal have parameteren 'SexIncorrectException ex', hvor ex er navnet på parameteren. Husk at catch blokken skal være efter vores try blok, men før vores finally blok, da finally jo altid skal stå som det sidste. Vi kan nu i vores catch blok, udskrive vores fejl besked ved at lave et almindeligt udskrivningsstatement, og som parameter til denne skrive - ex.getMessage(); - Og den vil nu udskrive den besked, som vi lavede i konstruktøren til vores egen Exception. Vores kode ser nu således ud:
Fold kodeboks ind/udJava kode 

Hvis vi nu prøver at lave en fejl ved køn indtastningen, vil vi se at vi får udskrevet fejl beskeden fra vores nye Exception klasse. Det ser f.eks. således ud:



Så blev vores program faktisk færdig, og vi kan nu bruge det, som det er tænkt. Vi fanger de nødvendige Exceptions som der kunne ske i forbindelse, med vores bruger input, men hvordan vidste vi egentlig at det var 'NumberFormatException', som vi skulle fange i forbindelse med metoden 'parseInt()' fra Integer klassen? Det meste kan man huske fra erfaring, men du kan altid se hvilke exceptions, som de enkelte metoder kaster, ved at gå ind i dit Java bibliotek og finde metoden. Hvis du f.eks. går ind i Java biblioteket nu, og finder metoden 'parseInt()' i klassen Integer, kan du læse metodens signatur. Her står følgende - public static int parseInt(String s) throws NumberFormatException - specielt ordet 'throws' efter metodens navn er vigtigt, fordi det er her der bliver angivet, hvilke exceptions, som vores metode kaster hvis der skulle ske en fejl. Bemærk at nogle metoder sagtens kan smide flere Exceptions, og nogle Exceptions er underklasser til andre Exceptions, så hvis du fanger superklassen, kan du undgå at skulle fange de underliggende exceptions. Som sagt tidligere er alle Exception klassernes over superklasse, klassen Exception. Den har f.eks. nogle underklasser, som f.eks. IOException, som igen har nogle underklasser indefor IO kategorien (IO står for Input-Output).

Throws


Vi har allerede lært lidt om nøgleordet throw, men ikke om ordet throws. Forskellen grammatisk er s'et til sidst i ordet, men hvad er forskellen teoretisk? Det er meget simpelt, hvor ordet throw bruges inde i en metode, til direkte at kunne specificerer hvor en metode skal kaste en speciel exception (ligesom vi gjorde i forrige eksempel da vi kastede vores exception SexIncorrectException). Ordet throws derimod kan ikke bruges inde i en metode, men skal altid defineres i metodens signatur efter parameter feltet. Du har måske undret dig over hvordan jeg vidste at metoden parseInt(String s) smed en 'NumberFormatException', som vi skulle fange i forrige eksempel. Dette er faktisk let at finde ud af, fordi i vores Java bibliotek kan man slå den enkelte metode op og se hvilke exceptions metoden kan risikerer at smide i tilfælde af fejl. Prøv f.eks. at slå metoden 'parseInt(String s)' fra Integer klassen op, og kig nøje på dens metodesignatur. Hvis du har gjort dette, vil du se at metode signaturen ser således ud - public static int parseInt(String s) throws NumberFormatException - Vi kan altså her læse at metoden 'parseInt(String s)' kan smide en 'NumberFormatException'. Prøv nu at slå metoden 'readObject()' fra klassen 'ObjectInputStream' op i Java biblioteket, og find ud af hvilke metoder den kaster. Hvis du har gjort dette, vil du se at metodesignaturen ser således ud - public final Object readObject() throws IOException, ClassNotFoundException - Vi kan altså se at denne metode kaster flere en end exception, og det eneste man gør er at adskille dem med et lille komma.

Vi skal nu prøve at lave en klasse med 4 metoder, som også kaster nogle exceptions med ordet throws. Lad os kalde klassen for 'Regnemaskine', og den skal så indeholde 4 accessor metoder, som alle returnerer en integer værdi. Metoderne skal bruges til at lave nogle beregninger (Addition, Subtraktion, Multiplikation og Division), og derfor skal den bruge to variable integer værdier, så alle metoderne skal have 2 parametre felter, som begge skal kunne håndterer en Integer værdi. de enkelte metoder skal så lave beregninger med de to tal fra parameterfelterne, og returnerer resultatet. Vores klasse 'Regnemaskine' ser nu således ud:
Fold kodeboks ind/udJava kode 

Nu kan vi begynde at finde ud af hvilke Exceptions der skal kastes, og en god måde at finde ud af dette på, er ved at lave nogle små test's, men da dette er en artikel, har jeg allerede testet det for dig, og fundet en fejl, som der kan ske. Man kan nemlig ikke dividerer med nul. Dette kan vi bruge til at lade metoden kaste en 'ArithmeticException'. Vi skal altså ved metoden 'lavDivision(int a, int b)' bruge ordet throws efterfulgt af navnet 'ArithmeticException'. Vores klasse ser nu således ud:
Fold kodeboks ind/udJava kode 

Nu kan vi så i vores main klasse med skelettet fra før med en while løkke der indeholdt en try, catch blok, fange vores exception, hvis brugeren skulle vælge at dividere med nul. Vores Main klasse kunne nu se således ud:
Fold kodeboks ind/udJava kode 

Nu har vi gjort klar, og endda gjort klar til at fange vores aritmetiske exception, men brugeren kunne jo også komme til at indtaste en String i stedet for tal, eller han kunne skrive hele regnestykket uden mellemrum, og dette ville give et anden form for Exception, nemlig en 'InputMismatchException', som vi også er nødt til at fange. Denne exception er vi også nødt til at importerer fra pakken, Java.util. Så vores klasse ser nu således ud:
Fold kodeboks ind/udJava kode 

Nu skal vi så bare tjekke variablen tegn med nogle if statements, også lave den rigtige beregning. Vores Main klasse ser nu således ud:
Fold kodeboks ind/udJava kode 

Sådan nu kan vi prøve at køre programmet, og lave en division, og vi vil så herefter få en fejl besked fordi vores metode lavDivision smed en Aritmetisk Undtagelse. Det kunne f.eks. se således ud:



Dette var alt for denne gang, men hvad har vi egentlig lært her? Jo vi har lært hvad en Exception og sådan set også en fejl er, og hvordan sådan nogle håndteres i Java. Vi har lært at fange dem med en try, catch blok, og vi har lært at en finally blok er valgfri til vore try, catch, men den skal altid bruges som det sidste hvis man bruger den. Vi har lært at lave vores helt egne Exceptions, og bruge dem i vores program. Vi har derfor også lært at kaste Exceptions inde fra en metode med nøgleordet throw, og at definerer hvad en metode kaster med ordet throws. Husk på at Exceptions ikke altid er nødvendigt da kompileren, også nogle gange opfanger disse ting, men ofte så lukker programmet bare uventet ned, og det stiller brugeren af programmet i en dårlig position. Det er vigtigt når man programmerer altid at tænke på sin bruger, og derfor gøre brugervenligheden for brugeren så god som mulig. Det betyder at fejl skal håndteres på en brugervenlig måde, ofte sådan så brugeren får af vide hvad han har gjort forkert, og hvad han skal lave om.

Husk at læse med i den næste artikel i denne Java kavalkade for dig der gerne vil lære at 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 (0)

Du skal være logget ind for at skrive en kommentar.
t