Datateknik A, Syfte: Att få en djupare förståelse hur metoder i Java konstrueras och används. Veta vad en konstruktor är och hur vi använder dem för att skapa objekt. Kunna generera dokumentation med hjälp av javadoc. Att läsa: Kursboken, Kapitel 2 (sida 82 88) Kursboken, Kapitel 3 (sida 95 102) Kursboken, Kapitel 4 (sida 121 137) http://java.sun.com/docs/books/tutorial/java/javaoo/classes.html http://java.sun.com/j2se/javadoc/writingdoccomments/index.html
I denna lektion gör vi en utförlig genomgång av metoder i Java. Vi tittar närmare på hur vi skickar och tar emot data från en metod. Vi förklarar vad överlagring av metoder och statiska metoder innebär. Vi tar även en titt på en lite speciell metod nämligen konstruktorn. Avslutningsvis går vi igenom verktyget javadoc för att automatgenerera dokumentation för våra Javaprogram. Robert Jonsson Sida 1
Redan i Lektion 2 började vi titta på vad metoder i Java är och hur vi kan anropa metoder på ett objekt. Vi börjar denna lektion med en kort repetition. En metod definieras alltid med en eller flera modifierare. Vi börjar med en åtkomstmodifierare som regler åtkomsten till metoden (vilka andra objekt som kan anropa metoden). Normalt vill vi att andra objekt ska kunna anropa metoderna och därför används nästan alltid public som åtkomstmodifierare. En metod kan även sättas till private om vi vill att enbart metoder i samma klass ska kunna nå aktuell metod. Efter åtkomstmodifieraren anger vi metodens returtyp. Returtypen berättar om metoden returnerar ett värde tillbaka till den som anropade metoden och i så fall vilken datatyp det returnerade värdet har (kan även vara objektreferenser som returneras). Alla metoder har ett namn med vilken vi kan anropa metoden med. Vi kan själva välja vilket namn metoderna ska ha och vi låter första ordet i metodnamnet ha en liten bokstav. För resterande ord i metodnamnet låter vi första bokstaven i varje ord ha en STOR bokstav (t.ex. ändranamn()). Det finns två undantag vad gäller det jag nyss skrev. main()-metoden måste alltid heta main och även konstruktorn för en klass måste alltid ha ett bestämt namn (kommer vi till lite senare). När vi anropar en metod kan vi skicka med data som metoden behöver eller ska bearbeta. Detta är metodens parametrar. För varje parameter anger vi dess datatyp och ett namn. En metod behöver inte ha några parametrar. Metodens kropp (innehåll) omsluts av matchande klamrar { }. I klassen Person från lektion 2 hade vi en metod med namnet setnamn() och som tog en parameter med namnet n och som var av typen sträng (String). Robert Jonsson Sida 2
Om vi behöver anropa en metod på ett objekt gör vi det genom punktnotation där vi anger namnet på det objekt, vars metod vi vill anropa, följt av namnet på metoden. Om metoden vi anropar finns i samma klass behöver vi inte använda punktnotation utan det räcker då att enbart skriva metodens namn (samt eventuella parametrar metoden behöver). Robert Jonsson Sida 3
Det som händer när en metod anropas är att exekveringen av programmet överförs till den anropade metodens första kodsats. Som nämnts behöver metoderna som anropas inte återfinnas i samma objekt (klass). Metodanrop kan göras mellan olika objekt men fungerar på samma sätt som exemplet i bilden. För att ge en kort förklaring om vad som händer vid metodanrop har jag utgått från exemplet med klasserna Person och PersonTest ovan. Observera att detta är en förenklad beskrivning men som ger en förståelse över hur det fungerar. 1. Den virtuella Javamaskinen anropar vår main()-metoden i klassen PersonTest varpå applikationen startar. Där börjar vi med att skapa ett nytt Person-objekt (ett objekt av typen Person). Här sker egentligen ett metodanrop som överför exekveringen från PersonTest till Person, men detta visar jag inte (förklaras lite senare i lektionen). 2. När vi nu har vårt Person-objekt p1 kan vi anropa dess publika metoder. När vi anropar setnamn() överförs exekveringen till första reden i denna metod (först kopieras eventuella parametrar metoden behöver. I vårat fall namnet på personen Kalle Karlsson). 3. När metoden är klar (eller när vi stöter på nyckelordet return) överförs exekveringen igen till samma ställe i objektet där metodanropet skedde. En metod returnerar alltid när alla programsatser i metoden har exekverats (d.v.s. när vi kommer till den avslutande högerklammern } för metoden). Vill vi av någon anledning returnera från metoden innan alla kod har exekverats görs detta genom att skriva return. 4. Direkt anropar vi nu metoden print() och exekveringen överförs återigen till klassen Person och första raden i print() metoden. 5. När alla programsatser körts i print() överförs exekveringen till raden som följer anropet i PersonTest. 6. När main()-metoden är slut är även applikationen slut (gäller ej vid applikationer som använder grafiska gränssnitt). Exekveringen överförs till den virtuella maskinen (som ju anropade main()-metoden). Där avslutas applikationen. Robert Jonsson Sida 4
I och med att en instansvariabel i en klass deklareras som private förhindras andra objekt att få en direkt tillgång till variabeln och dess värde. Detta är det normala för instansvariabler vilket håller de gömda från andra. För att komma åt de gömda instansvariablerna brukar man implementera publika metoder med vilka man kan sätta nya värden och hämta aktuellt värde, s.k. set()- och get()-metoder. Set()-metoden används för att tilldela den privata instansvariabeln sitt värde, medan get()-metoden används för att lämna ut (returnera) variabelns värde. För att sätta värden i dessa publika set()-metoder (och i andra metoder) används parametrar för att skicka informationen till metoderna när dessa anropas. Robert Jonsson Sida 5
Tittar vi på klassen Person och dess metod setnamn() är tanken med denna metod att sätta namnet på personen. För att göra detta måste metoden ges (eller få skickat) en sträng som representerar personens namn (en sträng eftersom vi i klassens metoddeklaration angett att typen på parametern ska vara en String). Metoden setnamn() kommer att ta emot värdet i denna sträng och lagra den temporärt i variabeln n. Därefter tilldelas vår instansvariabel namn (som är av samma typ) värdet som lagrats i variabeln n. På så sätt kommer objektet att få det namn som skickades till metoden. Variabeln n kommer att existera så länge som metoden exekverar. När metoden returnerar tappar variabeln n sitt värde och n städas bort från minnet. Instansvariabeln namn kommer däremot att behålla sitt värde. Inom parenteserna (som följer metodens namn i metoddeklarationen) anger man vilka parametrar metoden ska ta. Först anges vilken typ det är frågan om, följt av en identifierare (namn på variabeln). Om metoden behöver mer än en parameter används kommatecknet för att skilja parametrarna åt (exempel på detta kommer senare). Robert Jonsson Sida 6
När vi anropar en metod är det viktigt att vi matchar antalet och typ av parametrar. I vårt exempel PersonTest vore det felaktigt att skicka ett meddelande till setnamn()-metoden som inte var av typen String. I bilden ovan är det endast det första anropet till setnamn som är korrekt och tillåtet. De tre följande anropen är inte tillåtna eftersom antingen parameterns typ eller antal är felaktigt. För att visa på ett exempel på metod som har fler än en parameter har jag skrivit klassen Adderare. Denna innehåller en metod som tar två heltal som parametrar. Dessa heltal adderas och resultatet skrivs ut på skärmen. Observera att en metod med fler än en parameter kan ha olika datatyper på parametrarna. Ta en titt på klassen Adderare.java, kompilera och kör den. Klassen finner du bland exemplen som tillhör lektionen. Robert Jonsson Sida 7
Instansvariabler och metoder har alltid en räckvidd som sträcker sig över hela klassen. D.v.s. att de kan användas (kommas åt) från andra metoder i klassen. Parametrar till en metod har däremot en räckvidd som är begränsad till den metod i vilken den deklarerats. Anledningen till parametrar endast lever i metoden är att parametrar har en relativt kort livslängd. Parametern behöver vi enbart för att t.ex. sätta värdet på en instansvariabel eller att utföra någon bearbetning med. Så snart metoden exekverat klart har vi inget behov av den och parametern slutar att existera. Detta betyder att vi i bilden ovan enbart kan referera till variabeln (parametern) n i setnamn()-metodens inuti själva metoden. Vi kan inte komma åt variabeln n och dess värde från t.ex. metoden getnamn(). Robert Jonsson Sida 8
Jag nämnde tidigare att de värden som skickas som parameter kopieras över till metoden i samband med metodanropet. I Java överförs alla parametrar till metoder via s.k. värdeanrop. Detta innebär att när en variabel skickas som parameter till en metod så överförs endast en kopia av parameterns värde till metoden, och metoden kan inte ändra den ursprungliga variabelns värde. Detta låter säkert invecklat och till en början kan det vara svårt att förstå. För de primitiva datatyperna (int, char, double etc) innebär detta att om vi gör en förändring på kopian (t.ex. ett heltal som skickas till en metod) så sker inte denna förändring på originalvariabeln. Eftersom alla variabler i Java som är deklarerade att innehålla ett objekt är s.k. objektreferenser så är det en kopia på referensen som skickas till en metod. Via denna referens kan det refererade objektet påverkas (det är alltså referensen som överförs via värdeanrop och inte själva objektet). Exempel på detta kommer strax. Robert Jonsson Sida 9
I exemplet ovan skickas en heltalsvariabel med värdet 10 som ett argument till metoden nollstall(). Här är det en kopia på variabeln tal och dess värde som skickas till metoden (via värdeanrop). Parametervariabeln tal initieras till att innehålla värdet som skickats (d.v.s. 10). När sedan variabeln tal i metoden nollstall sätts till noll påverkar detta endast den lokala parametervariabeln, och inte variabeln tal i den anropade metoden (metoden main). Prova att öppna exemplet VardeAnrop.java i mina exempel och testkör. Robert Jonsson Sida 10
Exemplet ovan fungerar på liknande sätt som VardeAnrop, men här har vi en metod vars ena parameter är ett objekt av typen Person. Tanken är att vi har en metod där vi som parameter skickar ett Person-objekt samt en sträng med ett namn vi vill sätta på personen. När det gäller objekt som skickas som parameter till en metod är kopian och originalet en referens till samma objekt i minnet. Så det som överförs vid värdeanrop för ett objekt är alltså en kopia på referensen. I metoden bytnamn() lagras denna objektreferens i p. När vi sedan använder p för att byta namn så byter vi namn på objektet som p refererar till. I och med att originalet p1 refererar till samma objekt som kopian p är namnet bytt även på p1. Öppna exemplet ReferensAnrop.java för att prova detta. Robert Jonsson Sida 11
Det är möjligt att i metoddeklarationen ange att en parameter inte får modifieras i en metod (ges ett annat värde). Om ett argument skickas till en metod och man dels vill tydliggöra för anroparen att metoden inte modifierar parametern och dels få en kontroll av kompilatorn att så inte sker, så kan parametern deklareras som final. En sådan deklaration talar om att parametern när den väl har initieras är slutgiltig och att den således inte får eller kan modifieras. I exemplet ovan kommer vi att få ett kompileringsfel när vi försöker sätta nytt värde på parametervariablen x. Observera att när det är frågan om objektreferenser så kan vi fortfarande anropa metoder på objektet, t.ex. setnamn("stina"), och förändra värdet på eventuella instansvariabler objektet har. Däremot kan vi inte tilldela objektreferensen en referens till ett annat objekt (vilket är möjligt om inte final används). final kan också användas på instansvariabler och har då samma betydelse. D.v.s. efter att variabeln tilldelats ett värde kan detta värde inte förändras. Robert Jonsson Sida 12
Vi har nu tittat på hur vi kan skicka in värden till metoder,. Det är ibland även nödvändigt för metoder att sända tillbaka ett resultat. T.ex. kan en metod ta ett tal som parameter och utföra beräkningar på detta tal. När beräkningarna är gjorda vill vi returnera resultatet så att vi kan använda det beräknade värdet i resten av programmet. Som du kommer ihåg från genomgången av metoddeklarationen har alla metoder en returtyp. För metoder måste vi alltid ange en returtyp oavsett om metoden returnerar ett värde eller inte. För metoder som inte returnerar ett värde anger vi void som returtyp. I de exempel vi hittills har stött på så har metoderna inte returnerat något värde. Returtypen kan generellt sett delas upp i två olika typer. Antingen är returtypen en primitiv datatyp (dvs en int, double, char, boolean etc.) eller så är returtypen ett objekt av någon klass. Detta kan vara egendefinierade klasser (t.ex Person, Punkt etc.) eller någon av de fördefinierade klasser som följer med Java (t.ex. String, Button, Graphics etc.). Robert Jonsson Sida 13
När vi anropar en metod som returnerar ett värde så kommer själva metodanropet att få (innehålla, ersätta, tilldelas) det värde som returneras. Om vi t.ex. har en klass där det finns en metod med namnet settal() och en metod gettal() för att sätta/hämta värdet på en instansvariabel och exekverar följande programsatser obj.settal(10); obj.gettal(); kommer uttrycket obj.gettal() att tilldelas värdet 10 efter det att metoden gettal()har exekverats klart och returnerat sitt värde. Det returnerade värdet kan vi sen manipulera på samma sätt som andra variabler, vi kan skriva ut det i kommandofönstret med System.out.println(). Ett annat sätt vi kan använda värden som returneras är att tilldela den till en annan variabel. I exemplet i bilden med metoden gettal() kan vi tilldela det returnerade värdet till en variabel av typen int (heltal). // tilldelar variabeln i värdet som returneras av // metodanropet obj.gettal(),(vilket blir 10) int i = obj.gettal(); OBS!! Jämt när vi returnerar ett värde från en metod måste vi använda oss av nyckelordet return följt av det värde som ska returneras. Det värde som returneras måste även vara av samma typ som metodens returtyp. Robert Jonsson Sida 14
Låt oss nu utöka klassen Person från tidigare lektioner med följande: I stället för att en person har ett namn låter vi en person ha både ett för- och ett efternamn. Det kan även vara lämpligt att veta en persons ålder (vilket vi i och för sig kunnat räkna fram utifrån personnumret och aktuellt datum). Vidare utökar vi metoderna med set() och get()-metoder för att ändra dessa instansvariabler. Vi utökar även med metod för att sätta och hämta hela namnet på en gång. Set()-metoden kommer att ta två parametrar varav det första är förnamnet och den andra parametern är efternamnet. I exemplet Person2.java har jag gjort utökningen enligt ovan. Ta en titt på källkoden och titta framför allt på get()-metodernas uppbyggnad samt metoden setnamn() som nu tar två parametrar. Öppna även PersonTest2.java för att provköra exemplet. Robert Jonsson Sida 15
Det är möjligt att i en klass ha flera metoder med samma namn. Detta kallas att överlagra en metod, och leder då till att det finns flera implementationer (varianter) av samma metod (det är egentligen fråga om olika metoder men med samma namn). Kravet vid överlagring är att varje implementation antigen har olika antal parametrar eller olika typ på parametrarna. Returtypen måste vara samma för alla varianter. När ett anrop görs på metodens namn anropas den implementation som passar bäst för de argument som angivits i anropet. Det är viktigt att samtliga implementationer utför i princip samma sak. D.v.s. betydelsen av vad metoden gör ska bibehållas hos samtliga implementationer av de överlagrade metoderna. Det finns ingen begränsning av hur många överlagrade metoder vi kan ha. Huvudsaken är att signaturen är olika mellan de olika varianterna, annars säger kompilatorn ifrån. Robert Jonsson Sida 16
Här är ett exempel på en metod som överlagrats. Det finns tre varianter av samma metod, tre metoder med samma namn, men där signaturen är olika. Alla metoder har samma betydelse nämligen att skriva ut en text. Första metoden har en parameter och skriver enbart ut texten som anges i variabeln s, andra metoden har två parametrar och skriver ut texten i en viss färg och den sista metoden har tre parametrar och skriver ut texten i en viss färg och med en viss font (teckensnitt/stil). Här skiljer sig signaturen åt genom att antalet parametrar är olika. Vi skulle kunna ha ytterligare en metod skrivut() med bara en parameter, men där vi istället för String sätter int som typ (för att skriva ut ett heltal på skärmen). I exemplet Adderare2.java har jag utökat den ursprungliga Addera.java med överlagrade metoder så att vi kan addera två heltal med varandra, två decimaltal med varandra och ett decimaltal med ett heltal. I vår utökade version av Person.java med set()- och get()-metoder, samt uppdelningen av namnet till både för- och efternamn, kan det vara smidigt för användare av klassen att kunna sätta namnet på olika sätt. I Person2.java hade vi metoden setnamn(string f, String e) för att sätta både för- och efternamn samtidigt. Denna metod överlagras nu i Person3.java så att vi har en variant som inte tar några parametrar. Istället används i den metoden nu dialogrutor som frågar användaren efter det namn som personen ska ha. Notera vad utskriften för objektet okänd blir. För detta objekt sätts endast värden för vissa av instansvariablerna. Övriga instansvariabler tilldelas default-värden, vilka då? Robert Jonsson Sida 17
Vi har hittills skapat ett objekt och sen använt olika set()-metoder för att ge objektet lämpliga värden. Detta sätt att tilldela ett objekt sina värden är inte speciellt smidigt då det är lätt att glömma att sätta ett värde för en instansvariabel (och som då tilldelas ett default-värde). Det är även ganska jobbigt att använda set()-metoder om objektet har många instansvariabler vars värden ska sättas. I stället för att manuellt anropa varje set()- metod vore det smidigt om fanns ett sätt där vi kunde ge objektet dess värden i samband med skapandet av objektet. Och det finns det (vilken tur!). Konstruktorn är en speciell typ av metod som används just för tilldela objektet sina värden. Med konstruktorn initierar vi objektet med lämpliga startvärden direkt när objektet skapas. Som åtkomstmodifierare för en konstruktor sätter vi antingen public eller private (väldigt sällan private). En konstruktor saknar alltid returtyp, vilket innebär att vi inte kan anropa en konstruktor som en vanlig metod eftersom en sådan metod alltid har ett returvärde. En konstruktor måste även ha samma namn som klassen för annars är det ingen konstruktor. Tack vare konstruktorn kan vi garantera att ett objekt alltid initieras till lämpliga initialvärden. Om vi återgår till vår första klass (Punkt) så ser en konstruktor som inte har några parametrar ut så som bilden ovan visar, d.v.s. public punkt(). Robert Jonsson Sida 18
En konstruktor används för att skapa instanser (förekomster, objekt) av en klass. En konstruktor-deklaration ser precis ut som en metod-deklaration fast med skillnaden att konstruktorn måste ha samma namn som klassen och att den inte kan returnera ett värde. Därför anger vi ingen returtyp i konstruktor-deklarationen (inte ens void). Tillskillnad mot klassens instansvariabler och metoder så anses inte konstruktorn vara en medlem av klassen. Därför ärvs inte konstruktorn av klassens subklasser (mer om arv i kommande lektioner). Konstruktorn bör användas till att utföra nödvändiga initieringar av objektets variabler i samband med att objektet skapas. I vårt exempel med klassen Punkt vore det lämpligt att kunna tilldela instansvariablerna xkord och ykord lämpliga värden i samband med att ett objekt av klassen skapas. Istället för att använda set()-metoder för att sätta ett objekts initialvärden kan konstruktorn användas istället. Konstruktorn för vår klass Punkt skulle då ta två parametrar, en för x-koordinaten och en för y-koordinaten. Det vi sen gör i själva konstruktorn är att tilldela våra instansvariabler xkord och ykord värdet av de argument som skickades till konstruktorn, med andra ord samma funktion som set()- metoderna har. Fast nu sker det direkt när objektet skapas. På det här sättet har vi garanterat att ett objekt alltid har initierats med lämpliga värden, vilket förhindrar att vi jobbar med objekt som inte har initierats. Robert Jonsson Sida 19
I våra klasser har vi hittills inte skrivit någon egen konstruktor, men faktum är att alla klasser alltid har en konstruktor. Om vi för en klass inte skriver någon konstruktordeklaration kommer Java att automatiskt att tillhandahålla en default-konstruktor. Denna konstruktor tar inga parametrar och innehåller dessutom ingen kod. Om klassen är deklarerad som publik kommer också default-konstruktorn att vara publik och således vara åtkomlig för andra objekt att använda. Default-konstruktorns roll är helt enkelt att skapa en förekomst (ett objekt) av aktuell klass. Default-konstruktorn för vår Punkt-klass är en publik konstruktor utan paramentrar och utan kod. Precis som bilden ovan visar. Det förklarar varför den programsats vi använt hittills använt för att skapa Punkt-objekt (eller Person-objekt för den delen) är giltig. Det som skrivs efter operatorn new, d.v.s. Punkt(),är klassens default-konstruktor. Robert Jonsson Sida 20
Till skillnad mot metoder som kan anropas när som helst av vem som helst (i stort sett) kan konstruktorn endast anropas en gång och det är när objektet i fråga skapas. D.v.s. i samband med att nyckelordet new används. Precis som för metoder måste argumenten som skickas till konstruktorn exakt "matcha" parametrarna i konstruktordeklarationen. D.v.s. vi måste skicka med rätt antal och rätt typ i anropet när vi skapar ett objekt. Tidigare skapades ett Punkt-objekt (p1 i exemplen) med hjälp av defaultkonstruktorn och därefter användes metoder för att sätta punktens koordinater. I exemplet Punkt.java har vi nu en konstruktor som kan användas för att direkt initiera objektet. Försöker man nu skapa ett objekt som inte "matchar konstruktorns parametrar ges ett kompileringsfel. Observera att har vi skrivit en egen konstruktor i klassen så tillhandahålls inte längre någon default-konstruktor (vi kan dock själva skriva en konstruktor som inte tar några parametrar och som inte innehåller någon kod, men det sker inte automatiskt längre). Robert Jonsson Sida 21
I samtliga klasser (och dess metoder) finns det en speciell instansvariabel som heter this (finns alltid och är ett nyckelord). this är en objektreferens till det objekt som metoden anropats på (en s.k. självreferens). Denna självreferens kan användas på tre olika sätt: Oftast används den för att skicka en referens till det egna objektet som argument till ett annat objekt. Detta för att gör det möjligt med tvåvägs kommunikation mellan objekt. Hittills har vi enbart använt envägskommunikation där vi från ett objekt anropar ett annat objekt. Skickar vi med en självreferens kan den anropade metoden i sin tur göra anrop tillbaka. Vi kan också använda this för att från en konstruktor anropa en annan (överlagrad) konstruktor i samma klass. Ett sista sätt att använda this är för att skilja instansvariabler från lokala variabler med samma namn. this-referensen är inte tillgänglig i statiska metoder (förklaras snart) i en klass men kan användas i alla andra metoder. Robert Jonsson Sida 22
Precis som med metoder är det möjligt att överlagra konstruktorer. Här gäller samma krav för konstruktorerna som för överlagring av metoder. D.v.s. att varje implementation har olika antal parametrar eller olika typ på parametrarna (att signaturen skiljer sig åt). I exemplet i bilden har vi två stycken konstruktorer som tar antingen två eller inga parametrar. Beroende på om två eller inga argument anges när ett objekt skapas av klassen anropas alltså någon av dessa konstruktorer (den konstruktor som matchar bäst anropas). Notera att implementationen för ingen parametrar innehåller ett anrop av typen this(). Ett sådant anrop anropar en annan konstruktor i samma klass, i detta fall kommer alltså konstruktorn för inga parametrar att anropa den som tar två parametrar (vi skickar med två heltal i this-anropet). I den överlagrade konstruktorn (som har två parametrar) ges även exempel på en annan användning av självreferensen this. Här har parametrarna samma namn som klassens instansvariabler. För att i konstruktorn kunna skilja dessa variabler åt använder vi this.namn_på_instansvariabel för att referera till instansvariabeln. Detta gäller även i vanliga metoder när parametrarna har samma namn som instansvariablerna. Det är inget krav att alla överlagrade konstruktorer leder fram till samma slutgiltiga konstruktor, men det är en ganska vanligt förekommande konstruktion att några konstruktorer innehåller default-värden för argumenten i en annan konstruktor. this är som sagt ett nyckelord i Java och används för att inom ett objekt referera till sig själv. this representerar alltså en referens till det egna objektet och genom att anropa this med en serie argument anropas en konstruktor i objektets egen klass. Det finns ingen regel för hur många konstruktorer vi ska skriva för en klass utan vi får helt enkelt sätta oss ner och tänka igenom vilka olika sätt som kan vara bra att kunna skapa ett objekt på. I exemplet Person4.java har jag utökat klassen med tre överlagrade konstruktorer som på ett eller annat sätt sätter värden på objekten i samband med skapandet. Notera användandet av this. Robert Jonsson Sida 23
En instansvariabel som deklarerats med modifieraren final kan, efter att det har tilldelats ett värde, inte förändra värdet senare i programmet. D.v.s. det är en konstant instansvariabel som alltid kommer att ha det värde det initialt tilldelats. Värdet för en instansvariabel satt till final sätts antingen direkt vid deklarationen (vanligast) eller så tilldelas instansvariabeln ett värde i konstruktorn (som sedan inte kan förändras i några andra metoder). Om klassen har flera konstruktorer måste instansvariabeln ges ett värde i samtliga konstruktorer. Värdet som tilldelas en variabel deklarerad som final behöver inte vara en konstant utan kan beräknas eller passas in till konstruktorn som en parameter. Robert Jonsson Sida 24
Låt säga att vi utökar vår klass Punkt med konstanter för en punkts storlek (diameter som sen inte kan ändras) och de maximala värden en punkt kan ha på sin x- och y- koordinat. En konstant måste som nämndes tidigare antingen tilldelas ett värde direkt vid deklarationen, vilket är fallet för MAX_X och MAX_Y. Görs ingen initiering av konstanten vid deklarationen så måste vi göra det i alla konstruktorer (eller skicka med ett värde för konstanten i ett anrop av this()). Notera att namnet på konstanter normalt skrivs med enbart STORA BOKSTÄVER. Inför de ändringar som visas i bilden ovan i Punkt.java. Prova därefter att i någon metod försöka sätta ett nytt värde på någon av konstanterna. Robert Jonsson Sida 25
En medlem i en klass (en instansvariabel eller en metod) kan definieras med modifieraren static. Den blir då en statisk medlem som endast finns i ett exemplar för klassen, oavsett hur många objekt som skapas av klassen. Detta till skillnad mot icke-statiska instansvariabler då ju ett nytt exemplar av varje instansvariabel skapas för varje objekt som skapas. En statisk medlem implementerar något som lagras och används på klassnivå istället för objektnivå. Man kan säga att statiska medlemmar är gemensamma för (delas av) alla objekt som skapats av klassen. Ges en statisk instansvariabel ett nytt värde för något av de skapade objekten får alla andra objekt detta värde. Om vi i Punkt-klassen vill räkna hur många Punkt-objekt som skapats i en applikation kan detta göras med en statiskt instansvariabel. I exemplet i bilden ovan så initieras den statiska klassvariabeln antalpunkter till 0. Denna initiering görs endast en gång i en applikation och görs redan vid uppstarten av applikationen (innan några objekt av klassen skapats). Konstruktorn för klassen räknar sedan upp variabeln varje gång den anropas, d.v.s. varje gång ett objekt av klassen skapas. Eftersom klassvariabler delas mellan samtliga objekt av klassen så är den också åtkomlig från samtliga objekt (av aktuell klass). Hade antalpunkter inte varit deklarerade som statisk hade varje objekt haft ett eget exemplar av denna variabel och i samband med att objektet skapas hade det initierats till 0. Efter att konstruktorn exekverat klart hade alla objekt haft ett eget exemplar av antalpunkter satt till värdet 1. Genom att den nu i stället är statisk så blir den gemensam för samtliga objekt och lagras i stället på klassnivå. Robert Jonsson Sida 26
En statisk metod, en s.k. klassmetod, har den begränsningen att den endast kan referera klassvariabler, d.v.s. instansvariabler som också är deklarerade som statiska (static). Det går alltså inte i en statisk metod att referera till vanliga instansvariabler i klassen. En klassmetod saknar också this-referensen för att referera till ett aktuellt objekt (eftersom en statisk metod deklarerats på klassnivå i stället för på objektnivå och tillhör alla objekt och inte ett speciellt enskilt objekt). En statisk metod (klassmetod) kan endast anropa andra statiska metoder. Det innebär t.ex. att om vi från main()-metoden (som är statisk) vill anropa en annan metod i samma klass som main(), måste även den andra metoden deklareras som static. Fördelen med en statisk metod är att den kan anropas genom att endast ange klassens namn vid anropet, något objekt av klassen behöver inte finnas tillgänglig. I exemplet Person5.java har jag utökat klassen med en statisk instansvariabel för att räkna hur många objekt som skapas av klassen. Det finns även en metod som returnerar värde på den statiska instansvariabeln. Robert Jonsson Sida 27
I den genomgång av Javaspråkets som gjordes nämnde jag kort vad arv är. Med arvs-mekanismen kan vi från en befintlig klass skapa nya klasser som ärver sina egenskaper från den andra klassen. I Java härstammar alla klasser från en och samma grundklass, d.v.s. alla klasser ärver sina egenskaper från en annan klass. Denna klass heter Object och är vad som kallas en superklass till alla övriga klasser i Java. Klassen Object innehåller ett par metoder som alla klasser måste ha och en av dessa är metoden tostring(). Tips! Ta en titt i Javas API för klassen Object och se vilka fler metoder som finns. I och med att alla klasser ärver sina egenskaper från Object innebär det att alla klasser har en tostring()-metod trots att vi inte skrivit någon sådan metod själv i våra klasser. Denna metod används för att representera information om objektet som en sträng, d.v.s. metoden returnerar en sträng innehållandes en s.k. strängrepresentation av objektet. Som default returneras något som kan liknas vid en adress när vi anropar denna metod och därför bör vi alltid omdefiniera tostring()-metoden i våra egna klasser. Du kan prova att ändra i PersonTest5.java så att du på något av objekten anropar metoden tostring() och skriva ut det returnerade värdet. T.ex: String s = p1.tostring(); System.out.println(s); System.out.println(p2.toString()); System.out.println(p3); I vår klass Punkt är en lämplig representation av ett punkt-objekt dess x- och y-koordinater. Så lämpligen returnerar vi objektets koordinater i ett snyggt format (se bilden). För ett Person-objekt kan en lämplig representation vara förnamnet och efternamnet. Det är helt och hållet upp till utvecklaren att tänka igenom på vilket sätt ett objekt av klassen kan representeras som en sträng. I exemplet Person6.java och PersonTest6.java har jag överlagrat, som det heter, metoden tostring() för att returnera en strängrepresentation av ett Person-objekt. Robert Jonsson Sida 28
Bortsett från String (som är en klass) så har vi uteslutande använd de primitiva typerna som instansvariabler i våra klasser. Det är dock ytterst sällan vi enbart använder primitiva typer utan ofta används även andra klasser som typ på våra instansvariabler. Dessa klasser kan vara både egendefinierade klasser och färdiga klasser från det standardbibliotek som medföljer. Principen är dock den samma. Vår tidigare Person-klass innehåller information om en persons för- och efternamn. Det kan vara praktiskt att låta namnet representeras av en egen klass i stället. Klassen döper vi till Namn och den kommer att innehålla information om för- och efternamn (se UML i bilden). Vår nya klass Namn kommer att få två konstruktorer. En där vi anger för- och efternamn (två parametrar)och en där default-värden kommer att sättas (inga parametrar). Utöver konstruktorerna skriver vi diverse set() och get() metoder för att manipulera instansvariablerna fornamn och efternamn. I Person-klassen kan vi nu ersätta de båda gamla instansvariablerna fornamn och efternamn med en ny instansvariabel som är av typen Namn (som ju innehåller både ett förnamn och efternamn). För att enkelt kunna skapa objekt av Person lägger vi till konstruktorer som tar ett Namn-objekt som parameter, vilket innebär att för att kunna skapa ett Person-objekt måste vi först skapa ett Namnobjekt som vi skickar med som argument när vi skapar ett Person-objekt. Precis som vanligt har vi set() och get()metoder för alla instansvariabler i klassen. Metoden getnamn() kommer vi att skriva om så att den returnera ett objekt av typen Namn i stället för strängar med för- och efternamn som tidigare. Vill vi ta reda på personens för- och efternamn måste vi först anropa getnamn() för att få personens Namn-objektet. På detta objekt måste vi sen anropa metoderna getfornamn() och getefternamn(). Detta är inte speciellt smidigt så därför lägger vi till metoden getnamnasstring() som returnerar förnamnet och efternamnet direkt som en sträng. Lägg märke till att den statiska metoden visas i UML genom ett streck under sig. Robert Jonsson Sida 29
Dokumentationskommentaren används för automatgenererad dokumentation. I JDK finns ett verktyg som heter javadoc, som utifrån kod skriven i Java kan producera dokumentationen i HTML-format. På så sätt kan man hålla ihop dokumentationen av en klass med koden för en klass. En dokumentationskommentar måste finnas omedelbart före den klassdeklaration eller klassmedlem som ska dokumenteras. Det genererade resultatet blir i form av en HTML-fil där metodnamn blir hyperlänkar till mer information i form av de kommentarer som knutits till metoden. Hela den API som finns för Java är gjord med javadoc och dokumentationskommentarer. Robert Jonsson Sida 30
För alla dokumentationskommentarer gäller det att första meningen ska vara en summering av det som dokumenteras. Därefter kan man skriva en mer utförlig beskrivning. För klasser ska man ange vilken författaren av klassen är samt vilken version det är. Detta gör vi genom att använda taggarna @author och @version. Det normala är att vi endast dokumenterar publika medlemmar. Så privata instansvariabler är något vi vanligtvis inte dokumenterar. Jag har enbart gjort det i bilden ovan för demonstrativt syfte. Robert Jonsson Sida 31
För konstruktorer och metoder gäller även här att första meningen ska vara en sammanfattning. För metoder ska vi dokumentera eventuella parametrar och returvärden. Parametrar dokumenterar vi med taggen @param enligt följande modell: @param namn_på_parameter en_kort_beskrivning_av_parametern Har metoden flera parametrar skrivs en @param för varje parameter Returvärden dokumeterar vi med taggen @return enligt följande modell: @return en_beskrivning_av vad_som_returneras Saknas returvärde för en metod (d.v.s. void anges) utesluter vi helt användandet av taggen @return. Robert Jonsson Sida 32
Utifrån de dokumentationskommentarer vi skrivit i källkoden kan sen verktyget javadoc, som följer med installationen av JDK, generera HTML-filer. För att på smidigaste sättet gör detta samlas alla källkodsfiler (som vi vill ska ingå i dokumentationen) i en och samma mapp. Därefter öppnar vi ett kommandofönster och byter katalog till den katalog där våra källkodsfiler ligger. Därefter skriver vi javadoc följt av de filer vi vill ska ingå i dokumentationen. För att generera dokumentation för alla filer i katalogen anger vi *.java. javadoc *.java Efter en stund har ett antal olika html-filer skapats och vi kan titta igenom vår dokumentation genom att öppna filen index.html i en webbläsare. Ta nu en titt på hur jag byggt upp klasserna Namn.java, Person.java och PersonTest.java. Det är viktigt att du får bra grepp på hur vi använder andra klasser i en klass så var extra observant på hur klassen Namn används i Person och hur vi på olika sätt sätter namn och returnerar namn i Person. Jag har även dokumenterat alla publika medlemmar i klasserna Namn och Person. Efter att du har provat klasserna kan du kopiera Person.java och Namn.java till en tom katalog och generera dokumentationen. Robert Jonsson Sida 33