Klasser
Vad är en klass?
En klass kan sägas vara en ritning eller beskrivning hur en viss typ objekt ser ut (innehåller). Beskrivningen omfattar dels egenskaper i form av data och dels operationer.
De "tekniska" komponenterna i en klass är instansvariabler och metoder. Vanligen använder man instansvariabler för att representera egenskaper och metoder för operationer (även om inte gränsdragningen är kristallklar)
Exempel: I klassen Turtle
finns egenskaper som storlek, färg, position
och färdriktning. Alla dessa är representerade med instansvariabler.
Med metoder kan man fråga en specifik padda om aktuella värden på egenskaperna (ex getXPos
),
ändra på (en del av) egenskaperna (ex setWidth
)
samt be paddan röra sig på olika sätt (ex forward
).
En speciell typ av metoder är de så kallade konstruktorerna.
Hur skriver man en klass?
I en klass finns alltså instansvariabler, konstruktorer och metoder. Dessa kan skriva i vilken ordning som helst (man kan inte ha metoder inuti metoder) men för att underlätta läsbarheten skall man hålla sig till följande ordning: läsbarheten av klassen.
-
Instansvariabler som så gott som alltid skall deklareras som
private
(ellerprotected
). - Konstruktorer
- Övriga metoder - gärna i bokstavsordning om klassen har många metoder.
-
Om klassen har en
main
-metod så placeras den lämpligen sist.
Vi kommer använda nedanstående, antagligen bekanta, klass som exempel. Den kan, trots sin enkelhet, illustrera många saker. Observera att klassen har ett "skönhetsfel" som vi rättar till under avsnittet om konstruktorer.
Instansvariabler
Dessa deklareras vanligen (dvs alltid) först i klassen.
De är åtkomliga från alla metoder men kan döljas genom att samma namn används i en lokal deklaration
(formell parameter eller lokal variabel). I så fall man får sätta this.
före namnet för att
ange att det är instansvariabeln man avser. Se den första konstruktorn i Dice
!
Eftersom instansvariablerna existerar i hela klassen är det viktigt att de har tydliga, beskrivande namn.
Som instansvariabler skall man bara ha saker som verkligen är egenskaper hos objekten. Att ha loopvariabler och andra arbetsvariabler i metoder som instansvariabler är helt förkastligt!
Instansvariablerna skall normalt deklareras som private
(eller möjligen protected
).
Alla instansvariabler får ett initialvärde som är typiskt 0
, 0.
, '\0'
, false
eller null
beroende på typ. Observera att motsvarande inte gäller för variabler som deklareras lokalt i metoder!
Det går också att tilldela instansvariablerna initialvärden i deklarationen men detta görs oftast bäst i konstruktorerna.
Konstruktorer
En konstruktor ser ut som en metod men den saknar typ och heter samma sak som klassen (alternativt kan man säga att den har en typ som är klassen den finns i men saknar namn).
Konstruktorerna definierar vad som skall hända när ett objekt skapas.
En uppenbar uppgift är att ge instansvariablerna startvärden men de bör också kontrollera att värdena
är orimliga. Se den den första konstruktorn i Dice
.
(Satsen throw new RuntimeException("
message")
medför att programmet avbryts med felutskrift)
Man kan ha flera konstruktorer som då måste ha olika antal eller typer på parametrarna. I exempelklassen finns en konstruktor där man anger önskat antal sidor som parameter och en annan utan parameter. I det senare fallet får man en 6-sidig tärning.
Man kan låta en konstruktor utnyttja en annan genom anropet this(
parametrar)
.
I klassen Dice
kan alltså den andra konstruktorn skrivas
Även om det i denna lilla kod inte spelar så stor roll så är det mycket bättre att göra så. Det följer principen att man inte ska upprepa identisk eller nästan identisk kod utan finna ett sätt att skriva koden på ett ställe och sedan anropa det "stället". Om man t ex upptäcker fel eller av annat skäl vill ändra i koden så behöver man bara göra det på ett ställe. Om koden finns på många platser utspridd i programmet är risken stor att man glömmer något.
Av samma skäl ordnar konstruktorn ett slumpmässigt startvärde genom att anropa metoden roll
i stället för att kopiera in koden från roll
.
Om någon eller några av parametrarna till en konstruktor är en objektreferens så
skall man tänka på att den som anropade konstruktorn troligen också har en referens till
objektet i fråga.
Det gör det möjligt att manipulera objektet utifrån trots att den egna
instansvariabeln är deklarerad som private
.
Om detta skall förhindras måste konstruktorn göra en egen kopia av objektet.
Instansmetoder
Instansmetoder (oftast säger man bara metoder) anropas via en objektreferens
(t ex t.move(100)
där t
refererar ett Turtle
-objekt).
Inom klassen kan man anropa metoderna med this.
metodnamn(parametrar) men det
går också bra att utelämna this.
.
Metoderna är ofta deklarerade som public
men det ibland finns det skäl att göra dem som
private
eller protected
.
Olika metoder kan ha samma namn under förutsättning att de har olika antal och/eller typer på
parametrarna. (Ex: int distance(Turtle t)
och int distance(int x, int y)
).
Metoden roll
i tärningsklassen sparar tärningens värde i instansvariabeln value
men returnerar också värdet. Det gör att den kan anropas på två olika sätt.
Exempel:
I första fallet slår man tärningen men tar inte emot värdet (men kan naturligtvis fråga efter det i efterhand med
getValue()
). I det andra fallet slår man den och tar emot värdet direkt.
Särskilda metoder
Metoden toString
Alla klasser har en toString
-metod som ärvs från den generella basklassen Object
.
Den som ärvs är dock sällan särskilt upplysande.
Om man enkelt vill kunna får reda på tillståndet hos ett objekt (och det vill man oftast åtminstone medan man utvecklar programmet) bör man alltså skriva sin egen toString
.
Denna metod anropas automatiskt i många situationer (t ex när man lägger ihop en sträng och ett objekt med +
).
För att man skall ta över den ärvda metoden så måste den exakt ha metodhuvudet:
Vad som skall returneras av metoden bestämmer man själv.
Kom ihåg att toString
-metoder INTE skriver ut något - de returnerar bara en sträng.
Anroparen avgör vad som skall göras med resultatet!
Metoden equals
Operatorn ==
på referensvariabler returnerar true
om det är samma fysiska objekt, annars false
.
Ofta vill man ha något annat kriterium på att två objekt är lika.
Två tärningsobjekt vill man kanske
betrakta som lika om de visar samma värde och har samma antal sidor medan
två personobjekt antagligen är lika om de har samma personnummer.
För att definiera vad man menar med lika brukar metoden equals
användas.
Se exemplet för hur den kan se ut för tärningsobjekt.
I en Person
-klass där personnumret är lagrat som en sträng skulle metoden kunna ha utseendet
(Strängar har alltså en fungerande equals
-metod)
Många av Javas inbyggda klasser använder metoden equals
och har man inte skrivit
en egen används en som ärvs från basklassen Object
.
Den ärvda metoden gör sällan vad man vill så man måste skriva en egen om man vill att objekt skall kunna
jämföras.
Generellt:
- Metoden skall heta
equals
- Ha en parameter (vanligen samma typ som klassen som metoden ligger i)
- Returnera ett värde av typen
boolean
Hur den skall definieras beror naturligtvis på tillämpningen.
Metoden compareTo
Denna metod används för att avgöra vilken ordning objekten ha t ex i en sortering.
Metoden ärvs inte från basklassen Object
men den finns i många
standardklasser (t ex i klassen String
).
Metoden tar emot ett objekt (t ex i parametern p
, på samma sätt som equals
) och returnerar
ett negativt tal om det egna objektet anses mindre än p
, ett positivt tal om det egna objektet anses
större än p
och 0 om objekten anses lika (bör vara liktydigt med att equals
skulle
returnera true
) .
Exempel: Antag att klassen Person
bl a har ett attribut String lastname
och ett
int age
Om vi vill att efternamnet skall styra ordningen kan vi utnyttja String
-klassens
compareTo
:
Om vi i stället vill att åldern skall styra ordningen kan metoden definieras
Se också compareTo
-metoden i klassen Dice
som baserar sitt svar
på tärningsvärdet oavsett antalet sidor.
Så kallade get-metoder
Metoder som returnerar information om ett objekt brukar kallas getmetoder.
Det kan röra sig om värdet på någon instansvariabel (t ex geValue
i
tärningsklassen, int getAge()
) i Person
-exemplet ovan eller getXPos()
i Turtle
-klassen men den kan också vara resultatet av någon beräkning som görs
(t ex int getDistanceToOrigo())
.
Metodnamnet behöver inte heller inledas med get
även om det är en bra upplysning av vad metoden gör.
(Metoden som berättar hur stor en ArrayList
heter t ex size()
, inte getSize()
)
Om man har en get-metod som returnerar en objektreferens måste man vara medveten att man då utelämnar det
private
-deklarerade attributet till omvärlden.
Om detta skall förhindras måste man skapa en kopia av objektet och returnera en referens till denna.
String
-objekt kan alltid riskfritt lämnas ut eftersom dessa är "opåverkbara".
Så kallade set-metoder
Metoder som ändrar ett objekts status (position, storlek, ...) kallas set-metoder.
Man skall givetvis överväga om det skall vara tillåtet att ändra på statusen på detta sätt
(man vill antagligen inte tillåta metoden setValue(int v)
som ändrar en tärnings värde).
Samma övervägande man gör för get-metoder bör göras även för set-metoder när det gäller objektreferenser. Lagrar man en referens till ett objekt som man fått som parameter så har man ingen kontroll på vad omvärlden gör med objektet. Om man behöver garantera attributets integritet måste skaffa en egen kopia av det.
Klassmetoder och klassvariabler
Om variabler eller metoder deklareras som static
hör de inte till enskilda objekt utan är gemensamma för klassen som helhet och kallas därför för klass-variabler respektive klass-metoder.
Eftersom de inte hör till något objekt kommer de inte åt instansvariablerna eller instansmetoderna direkt utan måste
gå via en objektreferens.
main
-metoden måste alltid vara static
men den kan naturligtvis skapa objekt och via
objektreferenser använda instansmetoder och instansvariabler.
I static
-metoder finns naturligtvis inget this
.