INF1010 Arv Marit Nybakken marnybak@ifi.uio.no 2. februar 2004 Motivasjon Arv bruker vi så vi skal slippe å skrive oss i hjel. Når vi programmerer, prøver vi gjerne å modellere en del av verden ved hjelp av objekter. I verden er det mange ting som ligner på hverandre, men som ikke er helt like allikevel. De har noen felles egenskaper, og noen egenskaper som er unike. Dette skyldes ofte evolusjon, som ikke dekkes i dette kurset. Vi kan reflektere dette i programmene våre, ved å lage superklasser som samler de felles egenskapene, og subklasser som spesifiserer de spesielle egenskapene. Når vi snakker om egenskaper her, så mener vi både objektvariable og metoder. Hvis tingene også gjør noe på den samme måten, så kan metoden som representerer dette flyttes opp i superklassen. Vi lager oss en superklasse Kattedyr og samler de felles egenskapene vi kan komme på for alle kattedyr i denne klassen. Vi bestemmer også at alle katter kan male og fange dyr. class Kattedyr { protected double pelslengde; protected String levested; protected String [ ] diett; Kattedyr(double pelslengde, String levested, String [ ] diett) { this.pelslengde = pelslengde; this.levested = levested; 1
this.diett = diett; 10 void mal() { // mjau :D void fangetdyr(int nr) { fang og spis(diett[nr]); 20 Så spesialiserer vi litt og lager en Løve-klasse som arver fra Kattedyr-klassen. Vi kan få klasser til å arve fra andre klasser ved å skrive: class Subklassenavn extends Superklassenavn class Løve extends Kattedyr { private double brølevolum; Løve(String pelslengde, String levested, String [ ] diett, double brølevolum) { super(pelslengde, levested, diett); this.brølevolum = brølevolum; void brøl() { 10 Brøl b = new Brøl(brølevolum); b.start(); Nå får alle Løve-objekter både de variable og metoder som er definert i subklassen og de som er definert i superklassen. Løvene får alle de felles kattedyregenskapene i tillegg til at de kan brøle på et bestemt volum. Dette betyr at dette er mulig: Løve l = new Løve(13.2, "Namibia", {"gaselle", "spretthare"); // Lage løve l.brøl(); // Gjøre løveting l.fangetdyr(1); // Gjøre kattedyrting 2
super I konstruktøren i Løve står det super(pelslengde, levested, diett); super er et kall på konstruktøren i superklassen. Dette må gjøres helt først i konstruktøren i subklassen. Legger vi ikke inn noe kall på super, setter java inn en usynlig super(); øverst i subklasse-konstruktøren. Har vi ingen konstruktør som ikke tar parametre i superklassen, får vi da problemer. Konstruktøren i superklassen passer fint til å sette objektvariablene som hører til superklassen. Vi har også en super-peker. Denne peker på superklassen (altså superklassedelen av objektet), akkurat som this refererer til objektet vi er i akkurat nå. Vi finner på litt flere katter. class Kattunge extends Kattedyr { 3
private int kosefaktor; Kattunge(String pelslengde, String levested, String[ ]diett, int kosefaktor) { super(pelslengde, levested, diett); this.kosefaktor = kosefaktor; void kos() { 10 kosefaktor++; mal(); class SerbokroatiskStuekatt extends Kattedyr { SerbokroatiskStuekatt(String pelslengde, String levested, String[ ]diett) { super(pelslengde, levested, diett); void tapetser() { //... 10 Den serbokroatiske stuekatten har faktisk ingen egne objektvariable. Men det er fremdeles et poeng i å lage en egen klasse for denne, da den kan gjøre noe de andre kattene ikke kan. På tide å lage katter nå // Lager ett objekt av hver klasse Kattedyr k = new Kattedyr(1.2, "Sverige", {"fisk", "mus", "leverpostei"); Løve l = new Løve(6, "Namibia", {"pelikan", "gaselle", "vårhare", 100.2); Kattunge ku = new Kattunge(2, "Senga mi", {"whiskas", "melk", 10); SerbokroatiskStuekatt ss = new SerbokroatiskStuekatt(0.5, "Ikea", {"snadderflesk", "tannkrem"); Så kaller vi metodene som finnes i subklassene, og ber dermed kattene gjøre det bare de kan gjøre. 4
l.brøl(); ku.kos(); ss.tapetser(); Så kaller vi superklassemetoder og ber kattene gjøre det de alle får til: l.mal(); ku.fangetdyr(0); ss.mal(); Overlasting Subklassene kan, om de vil, definere sine egne versjoner av metodene i superklassen. class Løve extends Kattedyr { //... void brøl() { Brøl b = new Brøl(brølevolum); b.start(); void mal() { brøl(); super.mal(); // male littegranne også 10 Hvis vi nå kaller mal i et løveobjekt, vil versjonen i subklassen kalles i stedet, og løven vil brøle før den maler. Løver er uansett ikke veldig flinke til å male, de kan kun male på utpust. Merk bruken av super-pekeren, den eneste måten å få kalt malemetoden i superklassen på nå er å kalle den via superpekeren. Sier vi bare mal(), kaller vi på malemetoden i Løve-klassen. 5
Pekere Til nå har vi alltid skrevet MinKlasse mk = new MinKlasse(); når vi har laget et objekt. Står det MinKlasse på høyre side skal det stå det på venstre også, fordi pekeren skal være av samme type som objektet. Men vi kan faktisk også ha pekere av typen superklasse til subklasseobjekter. Kattedyr k1 = l; // løven Kattedyr k2 = ku; // kattungen Kattedyr k3 = ss; // stuekatten Hva betyr dette? Når vi har en superklassepeker til et subklasseobjekt, så får vi bare tak i metoder og variable i superklassen via denne pekeren. Vi ser altså på kattungen, løven og stuekatten som kattedyr, og glemmer at de kan tapetsere, kose og brøle. Vi får litt tunnelsyn når vi ser på objekter via superklassepekere. k3.fangetdyr(0); // ok k2.mal(); // ok k1.mal(); // ok, men hva skjer? k1.brøl(); // går ikke an lenger k2.kos(); // går ikke an lenger Løveklassen hadde overlastet metoden mal, og det har faktisk den effekten at når vi kaller på malemetoden, selv via en superklassepeker, så er det løvens egen malemetode som kalles. Hvis vi nå skal se på dem som spesielle kattedyr igjen, så må vi konvertere pekerne ned til subklassenivå. Når vi gjør det, må vi være sikre på at vi konverterer til riktig subklasse, det går ikke an å kalle en løve for en kattunge, da ber vi om bråk. Vi konverterer slik: Subklasse nypeker = (Subklasse) superklassepekeren; Løve l2 = (Løve) k1; // Nå ser vi på løven som en løve igjen, via l2-pekeren Kattunge ku2 = (Kattunge) k2; // Og kattungen er helt og fullt kattunge via ku2-pekeren SerbokroatiskStuekatt ss2 = (SerbokroatiskStuekatt) k3; 6
Kattunge ku2 = (Kattunge) k1; // Dette går veldig dårlig. En løve er ingen kattunge. //(Vi får ClassCastException) Nå får vi tak i subklassemetodene via de nye pekerne (i og for seg så fikk vi tak i dem via de opprinnelige pekerne også da). l2.brøl(); ku2.kos(); ss2.tapetser(); Abstrakte klasser Egentlig gir det kanskje ikke mening at det skal være lov til å lage objekter av superklassen Kattedyr, å lage kattedyr uten å si hva slags type kattedyr det er. Hvem vet hva slags uhyggelige beist som kan oppstå hvis vi ikke tar kontrollen? Vi kan faktisk spesifisere at en klasse ikke kan lages objekt av ved å gjøre den abstrakt. abstract class Kattedyr {... Et kattedyr blir nå et abstrakt konsept, og vi kan ikke si new Kattedyr() lenger. Vi må lage subklasser av Kattedyr for å kunne lage katter. Ellers fungerer alt som normalt. Abstrakte klasser kan ha abstrakte metoder. En abstrakt metode har ingen metodekropp, bare metodedeklarasjon: abstract returtype metodenavn(parametre); Når vi deklarerer en abstrakt metode i en superklasse, må alle subklasser implementere metoden. abstract class Kattedyr { public abstract void klorpådøra(); 7
class Løve extends Kattedyr { public void klorpådøra() { 10 rivdøraifiller(); Det er en måte å sikre at subklassene oppfører seg riktig på. Samtidig kan man nå ha Kattedyrpekere til Løveobjektene og fremdeles kunne kalle på klorpådøra(). Kattedyr pusekatt = new Løve(13, "Sverige", {"Kjøttboller", 29); pusekatt.klorpådøra(); instanceof Siden man kan ha så mange forskjellige pekere til objektene, trenger man en måte å sjekke hva slags objekt en peker faktisk peker på. Dette bruker man instanceof til: if(peker instanceof EnEllerAnnenKlasse) Dette er spesielt praktisk hvis man har puttet objekter av forskjellige klasser inn i en hashmap. Når man tar objekter ut av en hashmap, får man ut en peker av type Object, som er superklassen for alle klasser. Det er så vanlig å konvertere disse pekerne ned til riktig klasse: Elefant e = (Elefant) dyrene.get( Pellefant ); Har vi både elefanter og giraffer i hashmapen, må vi først ta ut pekeren og så teste den før vi konverterer ned: Object o = dyrene.get("pellefant"); if(o instanceof Elefant) { // Konverter pekeren ned til Elefant Elefant e = (Elefant) o; else if (o instanceof Giraff) { Giraff g = (Giraff) o; 8
else if(... 9