Presentasjon lastes. Vennligst vent

Presentasjon lastes. Vennligst vent

Objekt-orientering og Java

Liknende presentasjoner


Presentasjon om: "Objekt-orientering og Java"— Utskrift av presentasjonen:

1 Objekt-orientering og Java
Oppfriskning og videreføring av Java-kunnskaper

2 Forelesningen Gjennomgang av grunnleggende begreper
Klasser og objekter Kjøretidsmodell Spesielle Java-temaer Informasjonsskjuling Teknikker for strukturering av Java-kode Konvensjoner Refactoring-teknikker

3 Objekter og klasser Objekter Klasser
identitet: hvert objekt er unikt har en tilstand i form av felt innkapsling: tilgangen til tilstanden kan begrenses Klasser beskriver generelle trekk ved objekter mal for å lage nye objekter metoder for å se på og endre objektene klasser er også objekter: har egen tilstand og egne metoder (og klasser!) Objekter har én klassetilhørighet livet gjennom Klassen definerer hva et objekt ”kan” #f34d:C1 a = 1 o = #f350 C1:Class b = 2 o = #f34d

4 Objekter og identitet hvert objekt er unikt
tradisjonelt angitt med objektets adresse identiteten kan kun sammenlignes med andre testes med ==-operator, e.g. o1 == o2 støtter ikke adresse-aritmetikk <=, >= og +, - likt innhold betyr ikke lik identitet equals-metode, e.g. ”!”.equals(s) ”!”.equals(”!”) er alltid sant ”!” == ”!” kan være sant! vær varsom med egen equals-implementasjon a.equals(b) => b.equals(a) a.equals(b) => a.hashCode() == b.hashCode()

5 Identitet og referanser
Object o2 #f34d:C a = 1 o = #f350 tilordning og parameteroverføring skjer ALLTID vha. referanser (ulikt C og C++) static boolean foo(Object o1, Object o2) { return o1 == o2;} String a = ”test”, b = a; både foo(a, a) og foo(a, b) er da sanne referanser er begrenset til visse typer null referanse til ingenting! e.g. (a != null ? a.foo() : false) alle objekt-variabler kan tilordnes/referer null-verdien Object o2

6 Søppel og søppeltømming
Objekter tar plass hver type data tar en spesifikk mengde plass objektets plass er omtrent summen av feltene objekter er ”i live” så lenge referanser finnes Søppeltømming referanser holder objekter ”i live”: Object o1 = new Object(), o2 = o1; hva skjer når siste referanse forsvinner: o1 = null; /* her? */ o2 = null; /* her! */ søppeltømmeren (GC) kommer og tar den! Objekter tar plass og tømming tar tid

7 Ikke-objekt-verdier Såkalte ”immediate/direkte values” direkte verdier
1 3.14 true ’c’ Såkalte ”immediate/direkte values” tall (byte, int, float, double), brukes til aritmetikk tegn (char, e.g. ’c’), brukes i tekst og filbehandling sannhetsverdier (true, false), brukes i tester direkte verdier tar liten plass, ikke pekere, overføres direkte syntax for konstanter, e.g. 1, , ’c’, true maskinnære representasjoner og raske operasjoner

8 Ikke-objekt-verdier... Finnes også i objekt-drakt
1 3.14 true ’c’ Finnes også i objekt-drakt Integer: a = new Integer(1) => a.intValue() == 1 ditto for Double/doubleValue(), Character/charValue(), Boolean: true == new Boolean(true).booleanValue() bruk Boolean.TRUE/FALSE når Boolean-objekter trengs Kan ikke endres, e.g. ikke støtte for new Integer(1).setIntValue(2) Objekt-varianten er nyttig når: trenger referanse til ingen verdi: null referanse til flere typer verdier: Object eller Number arrays (int[] vs. Integer[] og liste (List))

9 Arrays Deklarasjon Initialisering Tilordning Referanse Casting
<type>[] <variabel>; <type> <variabel>[]; Initialisering <variabel> = {<elt1>, <elt2>, <elt3>, ... ,}; Tilordning <variabel> = new <type>[<n>]; <variabel> = new <type>[] { <elt1>, <elt2>, <elt3>, ... , }; Referanse <variabel>[<n>] Casting (<subtype>[])(<variabel av type>) Integer i = ((Integer[ ])(objectArray))[0]; vs. Integer i = (Integer)((objectArray)[0]);

10 java.lang.String Strenger er objekter Men: strenger kan ikke endres
”Hello world” eller ”Hello world” Strenger er objekter har identitet, lokal tilstand og metoder e.g. s1 == s2 => s1.equals(s2), ikke omvendt triks: ”hei”.equals(s), ikke omvendt pga. null Men: strenger kan ikke endres s.charAt(0), men ikke s.setCharAt(0, ’c’) betyr at like strenger kan slås sammen: ”t” == ”t” Implisitt kreasjon av nye strenger uten new ”a” + ”b” gir ”ab” ”1” + 2 gir ”12” ”objekt: ” + s gir enten ”objekt: null” eller ”objekt: ” + s.toString()

11 String gir lett mye søppel
java.lang.String er bygget på char[] Strenger lages automatisk av +-operator String result = ””; for (int i = 0; i < 10; i++) result = result + Integer.toString(i); result.equals(” ”) er sant hver iterasjon lager nytt String-objekt og char[]! StringBuffer er et godt alternativ: StringBuffer buf = new StringBuffer(10); for (int i = 0; i < 10; i++) buf.append(i); String result = buf.toString(); bruker mindre hukommelse og er raskere

12 Klasser og objekter Klasser beskriver objekter
felt / attributter funksjoner / operasjoner / metoder Hvert objekt er av én klasse skapes med new <klasse>(<parametre>) klasse-objektet retureres av o.getClass() o = new <klasse>() => o instanceof <klasse> && o.getClass() == <klasse>.class nyttig ved debugging: System.out.println(o.getClass() + ”:” + o); objekter kan ALDRI skifte klasse o må være != null

13 Klasse og objekt... public class C1 { public int f1; public String f2;
} Refererer til felt med dott-notasjon: c1.f1 og c1.f2; Objekt Klasse Identitet og attributter #1234 : C1 f1 = 10 f2 = ”ti” Klassedefinisjon

14 Arving en klasse kan bygge på en annen
nye felt og metoder legges til metoder kan omdefineres objektene inneholder alle feltene C2 arver fra C1: o = new C2() => o instanceof C2 && o instanceof C1 Men: o.getClass() == C2.class variabler og referanser C1 o1 = new C1(); C2 o2 = new C2(); o1 = o2 er lov, men ikke o2 = o1 casting (ikke fiske) o1 = o2; o2 = (C2)o1; // lovlig sekvens!?! typisk: if (o instanceof C2) ((C2)o).foo(); #f34d:C2 f1 = 1 f2 = ”hei” f3 = true C1 o1 C2 o2 #f34d:C1 f1 = 1 f2 = ”hei”

15 } Arving... } public class C2 extends C1 { public boolean f3; } Objekt
superklassen public class C2 extends C1 { public boolean f3; } klasse Objekt #2468 : C2 f1 = 10 f2 = ”ti” f3 = true } Klassedefinisjon C1-del } C2-del

16 } Arving... C1 o1 = new C1(); C1 o2 = new C2(); C2 o3 = (C2)o2;
f1 = 10 f2 = ”ti” #2468 : C2 f1 = 10 f2 = ”ti” f3 = true C1 o2 } kun C1-del er synlig gjennom o2 C2 o3 lov, siden o2 faktisk er C2 C2 o3 = (C2)o1; // ulovlig, gir ClassCastException

17 Konstruktor-metoder og -kall
public class C1 { public int f1; public String f2; } public C1(int i, String s) { f1 = i; f2 = s; new C1(10, ”ti”); Konstruktor-kall og objekt Klassedefinisjon og konstruktor Identitet og attributter #1234 : C1 f1 = 10 f2 = ”ti” Først lages boksen av systemet, så kalles konstruktoren, som initialiserer feltene

18 ... konstruktor-metoder public class C1 { public int f1;
public String f2; } public C1(int i, String s) { f1 = i; f2 = s; public C1(int i) { this(i, ”???”); public C1(String s) { this(s.length()); new C1(10, ”ti”); #1234 : C1 f1 = 10 f2 = ”ti” new C1(10); #1234 : C1 f1 = 10 f2 = ”???” new C1(”10”); #1234 : C1 f1 = 2 f2 = ”???” En konstruktor kan kalle en annen, vha. this(<parametre>)-konstruksjonen

19 Initialiseringskode Alternativ til konstruktorer
class C1 { List l = new ArrayList(); String lString = null; { l.add(”en”); lString = l.toString(); } } Brukes typisk for å initialisere mer komplekse datastrukturer Kjøres automatisk før konstruktor-koden aktiveres initialisering av verdi, kun uttrykk/expressions generell kjørbar kode, kan referere til felt

20 Konstruktorer og arving
public class C2 extends C1 { public boolean f3; } public C2(int i, String s, boolean b) { super(i, s); f3 = b; new C2(10, ”ti”, true); Konstruktor-kall og objekt Klassedefinisjon og konstruktor #2468 : C2 f1 = 10 f2 = ”ti” f3 = true } C1-del kall til superklassens konstruktor } C2-del super-konstruktoren må kalles først, for å garantere gyldig tilstand

21 Metoder Funksjoner / operasjoner / metoder
kode som utføre ”inni” objektet <referanse>.<metodenavn>(<parametre>), e.g. o.toString() tilsynelatende som referanse til felt (funksjon som feltverdi!) kan referere direkte til objektets tilstand public String toString() { return ”f1= ” + f1 + ”, f2: ” + f2; } new C1(”10”).toString() => ”f1=2, f2: ???” public String toString(String prefix) { return prefix + toString(); }

22 Metoder forts. void foo(int delta) { #2468 : C2 f1 += delta; f1 = 10
f3 = ! f3; } foo(1); void foo(int f1) { this.f1 = f1; foo(11); #2468 : C2 f1 = 10 f2 = ”ti” f3 = true #2468 : C2 f1 = 11 f2 = ”ti” f3 = false

23 Metoder... void foo(int f1) { this.f1 = f1; f3 = ! f3; } foo(11);
#2468 : C2 f1 = 10 f2 = ”ti” f3 = true #2468 : C2 f1 = 10 f2 = ”ti” f3 = true void foo(int f1) { this.f1 = f1; f3 = ! f3; } foo(11); this foo : C2 this = #2468 f1 = 11 foo f1 = 11

24 Metoder... Klasse B foo(A a) { a.foo(1); }
Klasse A boolean b; bar(int i) { b = (i == 2); } foo(int f) { bar(f+1)} Klasse B foo(A a) { a.foo(1); } : B : A b = : A b = true : B foo a = : A foo f = 1 bar i = 2 new B().foo(new A());

25 Metoder og arving class C1 { int f1 = 0; void foo(int delta) {
f1+=delta; } class C2 extends C1 { void foo(int delta) { super.foo(delta); new C2().foo(2); #2468 : C2 f1 = 0 foo = (int) super this foo delta f1 = ?

26 Metoder og arving... class C1 { void foo(int i) { bar(i); } #2468 : C2
void bar(int i) { ... } } class C2 extends C1 { void foo(int i) { super.foo(i); super.bar(i); new C2().foo(2); #2468 : C2 foo = (int) bar = (int) foo : C2 i foo : C1 i vs. Sekvens: C2.foo, C1.foo, C2.bar, C1.bar

27 Polymorfi Metodesignatur Metodekall Automatiske konvertering av typer
typene til parametrene retur-verdien(?) eks. public void drawString(String, int, int) Metodekall typene til parametrene må stemme med metodesignaturen f.eks. drawString(”Hello world”, 10, 10+10) Automatiske konvertering av typer int til double og float s + o => s + o.toString(), s + i => s + Integer.toString(i)

28 Polymorfi... Samme metodenavn, ulik signatur Bruk når en vil
teknisk sett ulike metoder bør ha ”samme” bruk public int parseInt(String s, int base) { ... } og public int parseInt(String s) { parseInt(s, 10);} tilsvarende for konstruktorer (<init>) Bruk når en vil unngå for mange ulike navnevariasjoner gjøre API’er hendigere å bruke

29 Grensesnitt Metoder = garanti om evner Algoritmer
formuleres vha. sett av metoder: ”dersom du kan dette, kan jeg gjøre ... med deg” et objekt har en ”rolle” ift. algoritmen den egentlige klassen spiller ingen rolle, bare disse metodene er implementert Klassisk eksempel: sortering krever mulighet for skanning og sammenligning to typiske grensesnitt: List og Comparator List: int size, Object get, void add, void remove, ... Comparator: int compare, boolean equals sorteringsalgoritmen trenger kun disse som støtte

30 Grensesnitt... Metoder krever: Objekter tilbyr: Interface-definisjoner
objekter som kan visse ting Objekter tilbyr: sett med metoder Interface-definisjoner samler relevante sett med metoder/evner klasser signaliserer at de implementerer dem objekter av klasser som implementerer List, kan brukes overalt hvor en List kreves en klasse kan implementere flere grensesnitt, dvs. inneha flere roller

31 Grensesnitt i Java public interface List { public int size(); public boolean contains(Object o); public Object get(int i); public void set(int i, Object o); } public interface Comparator { // o1 < o2 gir -1, o1 = o2 gir 0, og o1 > o2 gir 1 public int compare(Object o1, Object o2); } Collection.sort(List, Comparator);

32 Grensesnitt i Java... public class IntegerList implements List, Comparator { public int size() { ... } public boolean contains(Object o) { ... } public Object get(int i) { ... } // egentlig Integer public void set(int i, Object o) { ... } // egentlig Integer public int compare(Object o1, Object o2) { int i1 = ((Integer)o).intValue(), i2 = < (Integer)o).intValue(); if (i1 < i2) return -1; else if (i1 > i2) return 1; else return 0; } } IntegerList il = new IntegerList(); add(il, ...); add (il, ...), ... Collections.sort(il, il); // sorter liste, med spesifikk sammenligning

33 Abstrakte klasser Deklarerer visse evner
Implementerer dem ikke alle selv Kan ikke lage objekter fra klasse ”Delvis implementert klasse”, subklasser må implementere resten Sier eksplisitt hvilke som mangler Kan arve metoder (forplikte seg), uten å implementere disse

34 Abstrakte klasser... public abstract class AbstractList implements List { public boolean contains(Object o) { for (int i =0; i < size(); i++) if (get(i).equals(o)) return true; return false; } // resten av metodene er implisitt abstrakte f.eks. public abstract int size(); // kun deklarasjon, ingen implementasjon ... } Grensesnitt tilsvarer abstrakt klasse med kun abstrakte metoder (Merk: implements vs. extends)

35 Grensesnitt-konstanter
Arve av grensesnitt arv innebærer forpliktelse, ikke gjenbruk av kode sub-klasse i praksis uavhengig av grensesnitt Arv av konstanter grensesnitt kan deklarere konstanter av innebygde type (integer, char, etc.) konstantene kan nås gjennom sub-klasse Eks. SwingConstants LEFT/CENTER/RIGHT, TOP/MIDDLE/BOTTOM

36 Synlighet av navn Grensesnitt Innkapsling Mekanisme
gir frihet til reimplementasjon uten endring av ytre forpliktelser Innkapsling skjul alt som utsiden ikke trenger å vite Mekanisme redusere synlighet av navn Kunst å la akkurat passe mye være synlig

37 Synlighet av navn... ”public” ”private” navn er synlig for alle
felt og metoder ut av klasse klasse ut av pakke(/klasse) implisitt i interface-definisjoner mål å redusere mengden public-navn reduserer frihet til å endre ting siden ”private” navn er ikke synlig felt og metoder synes ikke utenfor klasse klasse synes ikke utenfor pakke(/klasse)

38 Synlighet av navn... ”protected” ”” = pakke-privat
navn er synlig for sub-klasser, via arv delvis eksponering av detaljer brukes ofte til hjelpemetoder, f.eks. vanlig i abstrakte klasser gir mulighet til mer effektive sub-klasser kan gi utilsiktede bindinger ”” = pakke-privat navn inni klasse synlig for alle klasser i samme pakke brukes blant tett koblede klasser vanligvis ikke nødvendig

39 Pakker i Java Samler sammenhørende klasser
Gjør det lettere å holde oversikt over store API’er Hierarkisk pakke-struktur java.lang. - grunnleggende klasser f.eks. Integer, String java.util. – nyttige hjelpeklasser, f.eks. List java.awt. – ”gamle” GUI-klasser (inkl. java.awt.event.) javax.swing. – ”moderne” GUI-klasser (javax angir java-utvidelse) javax.swing.tree – klasser knyttet til JTree (men ikke JTree selv) ... Tilsvarer mappestruktur  Meget viktig filnavn = klassenavn mappe = pakke mappehierarki = pakkehierarki

40 Pakker i Java... En klasses egentlige navn inneholder pakkenavnet
java.lang.String java.util.List javax.swing.JTree; To måter å referere til en klasse fullt navn, f.eks. java.lang.List kombinasjon av import og kortnavn import java.lang.List; List l = new java.util.ArrayList(); import samles i toppen av fil (før klassedeklarasjonen) liste med import-setninger gir nyttig oversikt over avhengigheter importere hele pakker vha. import <pakkenavn>.*;

41 Jar-filer Java ARchive Kjøring av Java-applikasjon krever
enkeltfil med mappestruktur inni innpakning av applikasjons pakkestruktur Kjøring av Java-applikasjon krever tilgang til mappestruktur med .class-filer (kompilerte java-filer) jar-fil med tilsvarende katalogstruktur CLASSPATH (eller –classpath/cp direktiv) refererer til nødvendige mapper og jar-filer refererer til mappen som inneholder toppnivå-pakken må være komplett, slik at alle klasser kan lokaliseres java –cp vips/classes;jar1.jar;jar2.jar vanlig at prosjektmappe inneholde src-mappe kompiler til classes-mappe

42 Static-modifikator Klasse som mal for objekter
identitet, feltverdier og initialiseringskode konstruktorer og metoder håndterer tilstanden Klassen er også selv et objekt har navn, ikke identitet har felt og initialiseringskode har metoder, men ikke konstruktorer ”Static” angir felt, kode og metoder av type 2 Kode kjører i kontekst av klasse, ikke objekt ”vanlige” klasser er implisitt static

43 Static-modifikator... class C { private static List allCs = new ArrayList(); public C() { allCs.add(this); } public static List getAll() { return allCs;} } new C(); C.getAll().size() == 1; #:ArrayList C:Class allCs = #:C class

44 Nøstede klasser Klasser inni klasser Static indre-klasse
klasse C1 er konteksten til klasse C2 to tilfeller, med eller uten static Static indre-klasse som vanlig klasse, men spesiell type synlighet dott-notasjon brukes for benevning C1.C2 objekt = new C1.C2(); greit å bruke for å samle kode i en fil

45 Indre-klasser Klasse (C2) definert inni annen klasse (C1) C2-objekter
kode i C2 ser navn definert i C1 C2 arver ikke fra C1, men kan arve fra andre C2-objekter inneholder som vanlig egne og arvede egenskaper eksisterer alltid i konteksten av C1-objekt kan referere direkte eller indirekt til C1 sine egenskaper Bruksområder et objekt uløselig knyttet til ett annet objekt behov for å skjule klassen

46 Indre-klasser... public class C1 { String n = ””; public C1(String s) { n = s;} public class C2 { public C2() { ... } public getName() { return n;} } public C2 createC2() { return new C2(); } } new C1(”hal”).createC2().getName() => ”hal” implisitt referanse til kontekst-objekt #:C1 n = ”hal” C1 #:C2 createC2 getName

47 Komplisert tilfelle ”ser” C1 fra to kanter public class C1 { String n = ””; public C1(String s) { n = s;} public class C2 extends C1 { public C2(String s) { super(s); } public getName() { return this.C1.n + n;} } public C2 createC2(String n) { return new C2(n);} } new C1(”hal”).createC2(”9000”).getName() => ”hal9000” #:C2 n = ”hal” #:C1 n = ”hal” C1 this this createC2 getName kontekstuelt felt vs. arvet

48 Anonyme indre-klasser
har annen klasse som kontekst arver (extends) eller implementerer (implements) annen klasse Anonym variant navnløs klasse defineres i konstruktor-kall direkte (re)implementasjon av metoder To typer bruk direkte implementasjon av grensesnitt variant av eksisterende (evt. abstrakt) klasse Svært hendig, men kan virke forkludrende

49 Anonyme indre-klasser...
List l = new ArrayList(); ... Collection.sort(l, new Comparator() { // implementasjon av metode public int compare(Object o1, Object o2) { return o1.toString().compareTo(o2); } }); // vanlig å glemme

50 Anonyme indre-klasser...
public class MyFrame extends JFrame { public MyFrame() { add(new JPanel() { public void paint(Graphics g) { System.out.println(”paint!”); }}) } }

51 Anonyme indre-klasser...
public class MouseAdapter implements MouseListener { // dummy-metoder, gjør ingenting public void mousePressed(MouseEvent event) { } public void mouseReleased(MouseEvent event) { } public void mouseClicked(MouseEvent event) { } } public class MyPanel extends JPanel { public MyPanel() { addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { System.out.println(”Click!”); }}); } }

52 Anonyme indre-klasser...
add(new JButton(new AbstractAction(”OK”) { public void actionPerformed(ActionEvent ae) { // alt som må gjøre // når OK-knappen trykkes }} )); Anonym klasse er indre-klasse inni annen klasse Kode-kroppen vil typisk kalle metoder i kontekst-objektet

53 Anonyme indre-klasser...
public class MyPanel extends JPanel { public MyPanel() { add(new Button(new AbstractAction() { ... })); } public doOK(ActionEvent ae) { ... } } #:MyPanel doOK = (ActionEvent) #: AbstractAction actionPerformed = (ActionEvent) MyPanel this actionPerformed

54 Anonyme indre-klasser...
Anonyme indre-klasser kan også referere til kontekst-objektets felt variable definert i metoder (!) Disse må være deklarert som final, som garanterer at de ikke endres public void addButton(final String s) { add(new Button(new AbstractAction(s) { public void actionPerformed(ActionEvent ae) { doButton(ae, s); } })); } public doButton(ActionEvent ae, String s) { System.out.println(s + ”-knappen ble trykket ned”); } }

55 Anonyme indre-klasser...
Det er som om konteksten er metoden hvor den anonyme klassen er definert #:MyPanel doOK = (ActionEvent) this addButton s = ”OK” MyPanel #: extends AbstractAction actionPerformed = (ActionEvent) this actionPerformed ae = ActionEvent

56 Anonyme indre-klasser...
final-deklarasjonen sikrer at variabel-verdien kan kopieres og dermed overleve addButton-kallet MyPanel #: extends AbstractAction actionPerformed = (ActionEvent) <kopi av> s = ”OK” #:MyPanel doOK = (ActionEvent) this this addButton s = ”OK” actionPerformed ae = ActionEvent

57 Strukturering av Java-kode
Konvensjoner navngiving sammenhørede metoder ”Sunne” strukturer lettere å forstå og jobbe med fleksible i bruk ”patterns”

58 Navnekonvensjoner Feltnavn Accessorer
enkelt-objekter angis med entall arrays og lister angis med flertall Accessorer getter: lese felt x med getX() setter: sette felt x med setX(<type>) adder: legge til felt xs med addX(<type>) remover: fjerne fra xs med removeX(<type>) Ekkel blanding av norsk og engelsk?

59 Navnekonvensjoner... Hendelser Lyttere XEvent fireXEvent
XListener-klasse xChanged(XEvent)-metode addXListener-metode removeXListener-metode

60 Delegering Viktigste OO-prinsipp etter arv Grunnidé: Fordel
Moderne rammeverk bruker grensesnitt og delegering, i mindre grad arv Grunnidé: en logisk funksjon legges til ny klasse metoder kopieres over felt innføres, som refererer til ny klasse metoder ”delegerer” til objekt Fordel logisk avgrensing av funksjon reduserer kompleksitet i klasse oppførsel kan endres ved kjøretid, ved å bytte instans

61 Delegering... Komponent har mange funksjoner Ny Border-klasse
hierarki hendelseshåndtering tegning av ramme og innhold Ny Border-klasse paintBorder og getBorderSize-metoder border-felt og get/setBorder-metoder internt kalles border.paintBorder Eller enda bedre Border-grensesnitt (og evt. AbstractBorder-klasse) Mange ulike Border-implementasjoner

62 Delegering... Grunnlag for MVC-arkitektur
metoder for datahåndtering delegeres til Model-klasse metoder for ”rendering” delegeres til View-klasse metoder for input delegeres til Controller-klasse Samles i åpent tilgjengelig klasse Tre grensesnitt, mange implementasjoner Grunnlag for Swing-komponenter, ikke rendyrket


Laste ned ppt "Objekt-orientering og Java"

Liknende presentasjoner


Annonser fra Google