270 likes | 343 Views
Grundlæggende programmering Forår 2002. Forelæsning 8 onsdag 3/4 2002 kl. 9:15 – 12:00. Dagens program. Det praktiske Arv (inheritance). Det praktiske.
E N D
Grundlæggende programmeringForår 2002 Forelæsning 8 onsdag 3/4 2002 kl. 9:15 – 12:00
Dagens program • Det praktiske • Arv (inheritance)
Det praktiske • Obligatoriske opgaver: I skal tjekke at Jeres opgaveafleveringerne er registreret korrekt i course grader. Find linket til course grader på GP-hjemmesiden eller tast ind direkte: http://hug.it.edu:8002/vu/index.tcl. I skal have mindst 72,7% korrekte afleveringer (8 ud af 11 obligatoriske opgavesæt) for at gå op til eksamen.
Scenarie I MiniBanken er der 3 forskellige slags Konti. • Check konti har et tilknyttet check hæfte. Der udbetales ikke renter, og det koster 50 kr. i kvartals gebyr, som trækkes når andre konti får skrevet renter til. • Opsparingskonti giver 4% i rente hvis saldo er over 75.000 kr, ellers kun 2 0/00. • Gevinst konti giver 2% i rente til en fælles pulje. Med 1 i 10000 vinder man hele puljen. • Mini banken har planer om at udvide fremover med nye konto typer. F.eks. Politimestrenes gevinst konti, hvor politimestrene har egen pulje, og lidt højere rentesats hvis de er over 5000 kunder med gevinst konti. Den problemstilling der skal belyses er hvordan vi kan skal skrive den metode i klassen Minibank der tilskriver rente på alle konti.
Hvorfor duer den simple løsning ikke Simpel løsning, lav 3 klasser: CheckKonto, OpsparingsKonto og GevinstKonto. class MiniBank { CheckKonto [] cKonti; OpsparingsKonto[] oKonti; GevinstKonto[] gKonti; ...// Osv for hver ny konto type public void tilskrivAlle(){ for (int index=0;...) cKonti[index].tilskrivRente(); for (int index=0;...) oKonti[index].tilskrivRente(); ... // og flere for hver ny konto type } Problemet er at når der tilføjes en ny konto type, så skal vi oprette en ny tabel, og vi skal rette i tilskrivAlle metoden.
Specialisering Situationen med at der er en række klasser der har en del fællestræk (her at det er konti der kan hæves og indsættes på) og som har nogle punkter hvor de er forskellige (her rentetilskrivning) er almindeligt forekommende i programmering. Man taler om at check konto er en speciel type konto, på samme måde som en hest er en speciel type pattedyr. Objektorienterede programmeringssprog har en særlig konstruktion til at beskrive dette specialiseringsforhold. I programmeringssprog kaldes specialisering for nedarvning eller udvidelse.
public class CheckKonto extends Konto{ private int checkNo; public CheckKonto(){ super(); checkNo = 0; } public void skrivCheck(double beløb){ hæv(beløb); checkNo++; } public void tilskrivRente(){ saldo = saldo - 50; } } CheckKonto Nøgleordet ”extends” angiver at CheckKonto er en specialisering af Konto. Ud over saldo, hæv og indsæt, så udvider CheckKonto klassen med checkNo attributten og skrivCheck metoden. super bruges til at sige at man ønsker at kalde en metode der er lavet i den klasse man udvider. Dette er kun nødvendigt hvis metoden har samme navn som en i den specialiserede klasse. Som det ses på næste slide, så er der i alle Konto klasser en metode tilskriveRente(), de gør bare noget forskelligt alt efter konto type.
public abstract class Konto { protected double saldo; public Konto() { saldo = 0.0; } public void indsæt(double beløb) { saldo = saldo + beløb; } public void hæv(double beløb) { saldo = saldo - beløb; } abstract void tilskrivRente(); } Konto Konto er en abstrakt klasse, som beskriver hvad der er fælles for alle konkrete kontoklasser. Dette svarer til at der ikke er væsner i den virkelige verden der kun er et Pattedyr. Der er altid tale om et konkret dyr, f.eks. Hest eller Peruviansk dværglama. Hvis en klasse er erklæret abstract kan der derfor ikke instantieres vha. new. En abstrakt metode er en metode som man ikke siger hvordan virker. Dens virkemåde skal så beskrives i subklasserne (andet ord for specialiserede klasser som udvider den givne abstrakte klasse).
Konto - protected public abstract class Konto { protected double saldo; public Konto() { saldo = 0.0; } public void indsæt(double beløb) { saldo = saldo + beløb; } public void hæv(double beløb) { saldo = saldo - beløb; } abstract void tilskrivRente(); } attributter (og metoder) kan erklæres protected. Det betyder at den ikke kan ses fra klienter (andre klasser som bruger Konto), men godt fra specialiseringer (klasser som udvider Konto). Hvis saldo var erklæret private, ville man ikke kunne ændre den i CheckKonto.
class MiniBank { Konto [] konti; ... public void tilskrivAlle(){ for (int index=0;...) konti[index].tilskrivRente(); } } En Minibank der virker Hvis en objekt variabel har typen Konto, så kan den indeholde et vilkårligt konto objekt, herunder også CheckKonto objekter. Når vi kalder en metode (her tilskrivRente() ), så bruger vi altid den mest specifikke definition. Hvis konti tabellen på et sted indeholder en CheckKonto, så kalder man tilskrivRente i CheckKonto, hvis der er tale om et Gevinst konto objekt bliver tilskrivRente i Gevinst Konto kaldt. Man siger at konti variablen er polymorf, hvilket betyder ”flerformet”. Det dækker over at den kan referere til objekter af forskellige klasser – som dog alle skal være instanser af en eller anden specialisering af Konto.
public class OpsparingsKonto extends Konto{ public void tilskrivRente(){ if (saldo>75000) saldo = saldo + 0.04*saldo; else saldo = saldo + 0.002*saldo; } public String toString(){ return "OpsparingsKonto: " + saldo; } } Opsparingskonto
Gevinst konto import java.util.Random; public class GevinstKonto extends Konto { private static double pulje = 0.0; private static Random rnd = new Random(); public GevinstKonto() { super(); } public void tilskrivRente(){ double rente = 0.02 * saldo; pulje = pulje + rente; if (rnd.nextFloat() > 0.9999 ) { saldo = saldo + pulje; pulje = 0.0; } } public String toString(){ return "GevistKonto: " + saldo + ”Puljen er: " + pulje; } }
MiniBank klassen 1) public class MiniBank { private Konto[] konti; public MiniBank(){ konti = new Konto[] {new CheckKonto(), new GevinstKonto(), new OpsparingsKonto()}; } public void udskrivAlle(){ for (int index=0; index<konti.length; index++) System.out.println( konti[index].toString() ); } }
MiniBank klassen 2) public void alleIndsæt(double beløb){ for (int index=0; index<konti.length; index++) konti[index].indsæt(beløb); } public void alleTilskriv(){ for (int index=0; index<konti.length; index++) konti[index].tilskrivRente(); } public void udskivCheck(){ ( (CheckKonto)konti[0]).skrivCheck(300); }
Afprøvning i BlueJ BlueJ angiver nogle sammen-hænge i klasserne. Fuldt optrukne linjer med store pile angiver specialisering, med den generelle klasse for enden af pilen Stiplede linjer angiver ”bruger” hvilket ofte er klient server relationen. Pilen udpeger server.
Umiddelbart kan det lyde mærkeligt at en specialisering og udvidelse er samme sag. Et begreb er karakteriseret ved to aspekter ekstension: mængden af de fænomener der der dækket ind under begrebet intension: de egenskaber som er fælles for fænomenerne i ekstensionen. Specialisering fra Konto til CheckKonto har følgende sammenhæng i ekstension og intension ekstensionen indskrænkes ved specialisering. Det er ikke alle konti der er check konti. intensionen udvides ved specialisering. CheckKonti har flere fælles egenskaber en blot Konti. Man kan faktisk sige at ekstension må indskrænkes når intensionen udvides, der kommer jo flere egenskaber, og det bliver dermed vanskeligere at opfylde intensionen, og derfor er der færre fænomener der kan gøre det. Specialisering og udvidelse
Betragt følgende to erklæringer Konto k; CheckKonto ck; Man kan så spørge hvilke af følgende kald der er OK. k.hæv(100); k.skrivCheck(200); k.tilskrivRente(); ck.hæv(100); ck.tilskrivRente() ck.skrivCheck(); Betydnings eksercits Generel regel om metodekald: Givet en variabel v som er erklæret af typen T. Så er kaldet v.metode() lovligt hvis metode er erklæret i T, eller en af T’s superklasser (T arver metoden) Dette er ikke lovligt. Når man skal afgøre hvilke metoder man kan kalde, kikker man på typen af variablen k, ikke på objektet som variablen indeholder. Dette er lovligt. Idet k er af typen Konto, må man kalde tilskrivRente. Den tilskriv rente der kaldes afhænger af objektet som variablen indeholder.
Betragt igen følgende to erklæringer Konto k; CheckKonto ck; Man kan så spørge hvilke af følgende assignments der er OK. k = new Konto(); k = new CheckKonto(); k = ck; ck = new Konto(); ck = new CheckKonto(); ck = k; Betydnings eksercits 2 Generel regel om assignment: en objekt variabel må indeholde objekter af dens erklærede type, eller den erklærede types subklasser. Dette er ikke lovligt idet Konto er er abstract. Hvis Konto ikke var abstract ville det være OK. OK Konto er ikke en specialisering af CheckKonto. Ulovligt. k kan indeholde noget der ikke er en CheckKonto, derfor ulovligt.
Overlæsning og arv • En klasse kan definere flere metoder med samme navn, bare deres metodesignatur er forskellig. • Det gælder også for subklasser. En subklasse kan definere en metode med samme navn som en metode i superklassen. Begge metoder er derefter tilgængelig i subklassen (vel at mærke hvis de har forskellig metodesignatur). • F.eks. class A { void m(){ System.out.println(“Metode m i klasse A”); }class B extends A { void m(String s){ System.out.println(”Metode m i klasse B” + s); }class Test { void test() { B x = new B(); x.m(); x.m(” med argument”); }
Overskrivning og dynamisk metodevalg • Subklasser kan overskrive (omdefinere, override) metoder i deres superklasse. • Det gøres ved at definere en metode med samme metodesignatur som i superklassen. • F.eks.class A { void m(){ System.out.println(“Metode m i klasse A”); }class B extends A { void m(){ System.out.println(”Metode m i klasse B”); }class Test { void test() { A x = new B(); x.m(); } • Der udføres den metode, der tilhører objektets dynamiske type (actual type), dvs. den metode er tilhører klassen fra hvilken objektet blev instantieret. Dette hedder dynamisk metodevalg (dynamic dispatch). • NB: Man kan ikke bare kigge på en variabels erklærede type (static type), for at finde ud af, hvilken metode vil blive invokeret.
Object som rodklasse • Alle klasser er subklasser af klasse Object. • F.eks.class A { void m() { System.out.println(“Blablabla”); }}er en forkortelse forclass A extends Object { void m(() { System.out.println(“Blablabla”); }} • Object har nogle metoder, f.eks. clone, hashCode, toString. Disse metoder kan derfor anvendes på alle objekter. F.eks., med klasse A ovenfor: class Test { void test() { new A().toString(); }}
Typekonvertering for referencetyper • Objekter kan typekonverteres fra en referencetype til en anden referencetype ved hjælp af et cast-udtryk. F.eks.class A { void m(){ System.out.println(“Metode m i klasse A”); }class B extends A { void m(String s){ System.out.println(”Metode m i klasse B” + s);} class Test { void test() { A x = (A) new B(); B y = (B) x;} • Downcasts (narrowing conversions) kan rejse en undtagelse. • Upcasts (widening conversions) går altdig godt. • Kun downcasts og upcasts er tilladt. • Cast-delen kan udelades i tilfælde af upcasts (subtype polymorfi). Upcast (fra subklasse til superklasse) Downcast (fra superklasse til subklasse)
Brug af super • Man kan bruge super for at få fat i felter, metoder og konstruktorer for superklassen. • Superklassens konstruktor kan kaldes med super(...), men kun som allerførste ordre i en konstruktor. • Superklassens felt f kan kaldes som super.f. • Superklassens metode m kan kaldes som super.m(...). Det er nyttigt hvis metoden m er blevet overskrevet i subklassen.
Subtiliteter • Private metoder og felter: • Hvis et felt eller en metode er privat i en superklasse A, så kan det ikke benyttes (”ses”) i subklasser af A. • Feltet arves dog alligevel. Det findes i subklassens objekter. Ellers ville det jo ikke kunne benyttes i metoder der arves fra superklassen. • Konstruktorer og arv: • Konstruktorer arves ikke. • Men det første en subklasses konstruktor gør, er at kalde en konstruktor for superklassen. Dette kan gøres eksplicit med et kald af formen super(...). • Hvis ikke det gøres eksplicit, så sker implicit et kald super() til en argumentløs konstruktor. I det tilfælde er superklassen altså nødt til at have en argumentløs konstruktor. Sådan findes automatisk hvis man ikke definerer andre konstruktorer.
Subtiliteter... • Felter og overskrivning: • Felter overskrives ikke. • Hvis subklassen erklærer et felt f som også findes i superklassen, så får man to felter med samme navn. • Feltet f arvet fra superklassen kan tilgås som super.f, men subklassens hedder f. • Det er næsten altid forkert og resulterer i forvirring og hovedpine!
Klassegrænseflader (interfaces) • En klassegrænseflade (interface) kan opfattes som en ultra-abstrakt klasse: • En klassegrænseflade kan kun erklære felter der er konstante. (Modifiers final og static er underforstået, hvis de er udeladt.) • En klassegrænseflade kan kun erklære metoder der er abstrakte og synlige overalt. (Modifiers abstract og public er underforstået, hvis de er udeladt.) • En klassegrænseflade kan ikke erklære konstruktorer. • En klasse kan implementere en klassegrænseflade. • Det er angivet med nøgleordet implements i klassedefinitionen. • Klassen skal indeholde en public metode med samme metodesignatur for hver metode i klassegrænsefladen. • En klasse kan implementere flere en kun en klassegrænseflade. • En klassegrænseflade kan udvide en eller flere klassegrænseflade. Dette angives ved nøgleordet extends.
Klasser kan specialiseres. En specialiseret klasse kan udvide med flere metoder og attributter. En specialiseret klasse kan overskrive metoder. En generel klasse kan erklæres abstrakt, det betyder at man ikke kan instansiere den. En abstrakt klasse er beregnet på at blive specialiseret. En polymorf objekt variabel kan indeholde objekter af dens erklærede type eller types subklasser Alle objektvariable i Java er polymorfe. Et metodekald aktiverer altid den specialiserede metode hvis en sådan findes, ellers den arvede. I forbindelse med nedarvning introduceres protected, som siger at klienter ikke kan se metoden, men at den kan ses fra subklasser. Et interface er en abstrakt klasse hvor alle metoder er abstrakte. Opsummering