TDA550 Objektorienterad programmering, fortsättningskurs Föreläsning 9 Generiska enheter Inre klasser Anonyma klasser Kloning Objektorienterad programmering fk 1 Föreläsning 9
Generiska programenheter Från och med version 5.0 är det möjligt att skriva generiska programenheter i Java. generiska klasser generiska interface generiska metoder Generiska programenheter innebär återanvändning av kod. Generiska programenheter parametriseras med en eller flera typer, så kallade typparametrar. Man kan se den generiska programenheten som en mall från vilken kompilatorn kan generera programenheter för specifika typer. Objektorienterad programmering fk 2 Föreläsning 9
Generiska klasser Nedan visas hur en klass Pair, som används för att lagra ett namn och ett konto, har skrivits om till en generisk klass. Den generiska klassen kan användas för att lagra godtyckliga par av objekt. public class Pair { private String name; private BankAccount account; public Pair(String name, BankAccount account) { this.name = name; this.account = account; public String getfirst() { return name; public BankAccount getsecond() { return account; public class Pair <T, S>{ private T first; private S second; public Pair(T first, S second) { this.first = first; this.second = second; public T getfirst() { return first; public S getsecond() { return second; Pair <String, Integer> p = new Pair<String, Integer>("Hello", 12); Pair <Integer, Integer> p = new Pair<Integer, Integer>(34, 56); Objektorienterad programmering fk 3 Föreläsning 9
Generiska klasser Typerna som handhas i en generisk klass måste namnges och anges i klassens parameterlista. public class pair<s, T> Det är praxis att ge korta namn enligt: E K V T elementtyp i en samling nyckel i en map värde i en map generell typinformationen S, U fler generella typer Typparametrar i klassdeklaration är endast information till kompilatorn. typparametrarna används när kompilatorn kontrollerar programmet och sätter in vissa konverteringar (autobox/unboxing) när kompilatorn översätter klassdeklarationen till bytekod tas denna information bort. Inuti den generiska klassdefinitionen kan de formella typparametrar användas som vanliga referenstyper utom att: de får inte användas i klassens statiska metoder man får inte skapa arrayer av dem (men man kan deklarera arrayreferenser). Objektorienterad programmering fk 4 Föreläsning 9
Generiska metoder I en generisk klass får en statisk metod inte använda klassens typparametrar. Men det går dock att deklarera generiska metoder, genom att parametrisera metoden med en eller flera typparametrar. public class ArrayUtil { public static void print(string[] a) { for (String e : a) System.out.print(e + " "); System.out.println(); public class ArrayUtil { public static <E> void print(e[] a) { for (E e : a) System.out.print(e + " "); System.out.println(); Retangle[] retangles = ; String[] strings = ; ArrayUtils.print(retangles); ArrayUtils.print(strings); Objektorienterad programmering fk 5 Föreläsning 9
Begränsade typparametrar Det är ibland nödvändigt att definiera vilka typer som man kan använda i en generisk klass eller i en generisk metod. Betrakta en metod min som bestämmer det minsta värdet i ett generiskt fält. Hur kan man finna det minsta värdet när man inte vet vilken typ elementen i fältet har? Ett sätt är att kräva att elementen tillhör en typ som implementerar interfacet Comparable (som är generiskt). I detta fall behöver vi således specificera typparametern ytterligare eftersom inte alla typer kan accepteras. public static <E extends Comparable<E>> E min(e[] a) { E smallest = a[0]; for (int i = 1; i < a.length; i++) { if (a[i].compareto(smallest) < 0) smallest = a[i]; return smallest; Metoden min ovan kan anropas med fält vars element är av en typ som implementerar Comparable<E>. Ibland behövs det sätta flera begränsningar på en typparameter: <E extends Comparable<E> & Cloneable> Observera att här betyder det reserverade ordet extends både extends eller implements. Objektorienterad programmering fk 6 Föreläsning 9
Wildcards Konceptet wildcards har införts för att beteckna vilken typ som helst. Det finns tre olika slag av wildcards: wildcard med undre gräns? extends B godtycklig subtyp till B wildcard med övre gräns? super B godtycklig supertyp till B wildcard utan gräns? godtycklig typ Objektorienterad programmering fk 7 Föreläsning 9
Wildcards godtycklig subtyp Antag vi har följande klasshierarki: public abstract class Shape { public abstract void draw(); public class Circle extends Shape { public void draw() {... public class Rectangle extends Shape { public void draw() {... Metoden public static void drawall(linkedlist<shape> shapes) { for (Shape s: shapes) s.draw(); kan dock inte anropas med en parameter av typ LinkedList<Circle> mylist = new LinkedList<Circle>(); ty LinkedList<Circle> är inte subklass till LinkedList<Shape>. Däremot fungerar följande metod: public static void drawall(linkedlist<? extends Shape> shapes) { for (Shape s: shapes) s.draw(); Objektorienterad programmering fk 8 Föreläsning 9
Wildcards godtycklig supertyp Antag vi har följande klasshierarki: public class BankAccount implements Comparable<BankAccount> { public class SavingAccount extends BankAccount { Eftersom SavingAccount ärver från BankAccount så implementerar SavingAccount gränssnittet Comparable<BankingAccount> och inte Comparable<SavingAccount>. Om vi återvänder till metoden public static <E extends Comparable<E>> E min(e[] a) { Kan denna metod anropas med ett fält av typen SavingAccount enligt: Nej! SavingAccount[] sa = ; min(sa); SavingAccount implementerar inte Comparable<SavingAccount> utan Comparable<BankingAccount>! Metodhuvudet måste ha följande utseende: public static <E extends Comparable<? super E>> E min(e[] a) Objektorienterad programmering fk 9 Föreläsning 9
Länkade listor En länkad lista består av ett antal noder. Varje nod innehåller dels en referens till nästa nod och en referens till ett element i listan. first Vi skall göra en implementation av en enkel generisk länkad lista. Specifikationen är enligt följande: /*skapar en tom lista public SimpleList() //returnerar första elementet i listan //kastar NoSuchElementException om inget sådant element finns public E getfirst() //tar bort första elementet i listan //kastar NoSuchElementException om inget sådant element finns E removefirst() //lägger in ett element först i listan public void addfirst(e element) //returnerar en iterator för listan public Iterator<E> Iterator() Objektorienterad programmering fk 10 Föreläsning 9
Länkad lista implementation Vi använder en privat inre klass för att handha noderna, och en inre klass för att implementera iteratorn för vår lista. public class SimpleList<E> implements Iterable<E> { private Node first; private class Node { public E data; private Node next; private class OurIterator implements Iterator <E>{ public OurIterator() { public boolean hasnext() { public E next() { public void remove () { En inre klass är en klass som deklareras inuti en annan klass. En inre klass kan ses som en hjälpklass som endast är av intresse för den yttre klassen. En inre klass kan komma åt den ytter klassens instansvariabler även om dessa är deklarerad private. Objektorienterad programmering fk 11 Föreläsning 9
Implementation klassen SimpleList import java.util.*; public class SimpleList<E> implements Iterable<E> { private Node first; public SimpleList() { first = null; //konstruktor public E getfirst() { if (first == null) throw new NoSuchElementException(); return first.data; //getfirst public E removefirst() { if (first == null) throw new NoSuchElementException(); E element = first.data; first = first.next; return element; //removefirst public void addfirst(e element) { Node newnode = new Node(); newnode.data = element; newnode.next = first; first = newnode; //addfirst public Iterator<E> iterator() { return new OurIterator(); //iterator private class Node { public E data; public Node next; //Node Objektorienterad programmering fk 12 Föreläsning 9
Implementation OurIterator private class OurIterator implements Iterator <E>{ private Node position; private Node previous; public OurIterator() { position = null; previous = null; //konstruktor public boolean hasnext() { if (position == null) return first!= null; else return position.next!= null; //hasnext public E next() { if (!hasnext()) throw new NoSuchElementException(); previous = position; if (position == null) position = first; else position = position.next; return position.data; //next public void remove () { throw new UnsupportedOperationException(); //remove //OurIterator //SimpleList Objektorienterad programmering fk 13 Föreläsning 9
Anonyma klasser En entitet är anonym om den inte har något namn. Anonyma entiteter används ofta i ett program add.data(new Integer(123)); anonymt objekt I Java är det är möjligt att definiera anonyma klasser, vilket kan vara användbart när man bara behöver ett objekt av en klass. Comparator<Country> comp = new Comparator<Country>() { //annonym klass public int compare(country1 c1, Country c2) { return c1.getname().compareto(c2.getname()); ; Ovanstående är likvärdigt med att skriva: public class MyComparator implements Comparator<Country> { public int compare(country1 c1, Country c2) { return c1.getname().compareto(c2.getname()); Comparator<Country> comp = new MyComparator(); Objektorienterad programmering fk 14 Föreläsning 9
Mer om anonyma klasser Anonyma klasser används ofta som throw away -klasser när man endast behöver ett objekt av en klass. Men genom att lägga den anonyma klassen i en metod kan man enkelt skapa multipla objekt av en anonym klass. Antag att vi i klassen Country vill ha en klassmetod som returnerar ett Comparator-objekt för att jämföra namnen på länder. Detta kan åstadkommas enligt följande: public class Country { public static Comparator<Country> comparatorbyname() { return new Comparator<Country>() { public int compare(country1 c1, Country c2) { return c1.getname().compareto(c2.getname()); ; Objektorienterad programmering fk 15 Föreläsning 9
Metoden Clone Metoden clone() används för att skapa en kopia av ett existerande objekt. Klassen Object definierar en default implementering av clone(): protected Object clone() throws CloneNotSupportedException { if (this instanceof Cloneable) { //Copy the instance fields else throw new CloneNotSupportedException(); Vi ser, i implementationen av clone() ovan, att en exception av typen CloneNotSupportedException kastas om metoden anropas för ett objekt av en klass som inte implementerar gränsnittet Cloneable. Detta betyder att en klass vars objekt skall gå att klona måste implementera gränssnittet Cloneable och överskygga metoden clone(). Det rekommenderas att överskuggade metoder av clone skall uppfylla följande villkor: x.clone()!= x (x.clone()).equals(x) (x.clone()).getclass() = x.getclass() Objektorienterad programmering fk 16 Föreläsning 9
Grund kopiering (shallow copy) Metoden clone i klassen Object, skapar och returnerar en kopia av det aktuella objektet. Varje instansvariabel i kopian har exakt samma värde som motsvarande instansvariabeln i originalet. Antag att vi vill kunna skapa kopior av objekt av klassen SomeClass nedan: public class SomeClass { private int value; private boolean status; private String str; public SomeClass(int value, boolean str, String str) { this.value = value; this.status = status; this.str = str; Vi måste således låta klassen implementera interfacet Cloneable och överskugga metoden clone(). Vill man att clone() skall vara tillgänglig utanför paketet som klassen SomeClass tillhör, skall clone() göras public. Objektorienterad programmering fk 17 Föreläsning 9
Grund kopiering (shallow copy) I klassen SomeClass är instansvariablerna value och status primitiva variabler, och instansvariabeln str är ett icke-muterbart objekt. Därför gör clone() i klassen Object allt som är nödvändigt för att skapa en kopia. public class SomeClass implements Cloneable { private int value; private boolean status; private String str; public SomeClass(int value, boolean str, String str) { this.value = value; this.status = status; this.str = str; public SomeClass clone() { try { return (SomeClass) super.clone(); catch (CloneNotSupportedException e) { return null; //never invoked SomeClass org = new SomeClass(5, true, "Hello "); SomeClass copy = org.clone(); Eftersom clone() i klassen Object kastar ett undantag av typen CloneNotSupportedException måste detta hanteras. Objektorienterad programmering fk 18 Föreläsning 9
Grund kopiering (shallow copy) Vad händer som vi glömmer att implementera Cloneable? public class SomeClass { private int value; private boolean status; private String str; public SomeClass(int value, boolean str, String str) { this.value = value; this.status = status; this.str = str; public SomeClass clone() { try { return (SomeClass) super.clone(); catch (CloneNotSupportedException) { return null; //never invoked SomeClass org = new SomeClass(5, true, "Hello "); SomeClass copy = org.clone(); Object.clone() kastar CloneNotSupportedException! Objektorienterad programmering fk 19 Föreläsning 9
Djup kopiering (deep copy) Grund kopiering är trivial, eftersom det bara är att nyttja metoden clone() i klassen Object. Grund kopiering fungerar då attributen för objektet som klonas utgörs av primitiva typer och icke-muterbara typer. För primitiva typer skapas kopior och för icke-muterbara typer spelar det ingen roll att de delas, eftersom de inte kan förändras. Om ett objekt har muterbara referenser måste man använda djup kopiering (deep copy) för att klona objektet. Djup kopiering innebär att man skapar kopior av de refererade objekten. Vilket kan vara mycket komplicerat. Principiellt gör man enligt följande: public class SomeClass implements Cloneable { public SomeClass clone() { try { //Fix all primitive and immutables SomeClass result = (SomeClass) super.clone(); //Handle mutables // code here (deep copy) return result; catch (CloneNotSupportedException) { return null; //never invoked Objektorienterad programmering fk 20 Föreläsning 9
Djup kopiering enkelt exempel Klassen Person har en muterbar referensvariabel birthday, av typen java.util.date. Person -name: String -birthday: Date import java.util.date; public class Person implements Cloneable { private String name; private Date birthday; public SomeClass clone() { try { Person result = (Person) super.clone(); result.birthday = birthday.clone(); return result; catch (CloneNotSupportedException) { return null; //never invoked Objektorienterad programmering fk 21 Föreläsning 9
Kloning och arv Vid arv anropas clone() metoden i superklassen, varefter de muterbara referensvariablerna som deklareras i subklassen måste djup kopieras. public class Member extends Person { private Date dayofmembership; public Member clone() { try { Member result = (Member) super.clone(); //add copies of sub class specified fields to result result.dayofmembership = dayofmembership.clone(); return result; catch (CloneNotSupportedException) { return null; //never invoked Varför behöver inte Member implementera Cloneable? Objektorienterad programmering fk 22 Föreläsning 9
Djup kopiering krångligare exempel I nedanstående scenario gör metoden clone() i klassen C1 ett anrop till super.clone(), d.v.s vi har endast en grund kopiering. v2 = v1.clone(); :C1 v1 :C1 :C2 :C2 :C2 v2 :C3 :C3 :C3 Objektorienterad programmering fk 23 Föreläsning 9
Djup kopiering krångligare exempel I nedanstående scenario skapar metoden clone() i klassen C1 en kopia av sin instansvariabel, vilken är en lista. Men clone() skapar inte kopior av elementen i listan. v2 = v1.clone(); :C1 v1 :C1 v2 :C2 :C2 :C2 :C3 :C3 :C3 Objektorienterad programmering fk 24 Föreläsning 9
Djup kopiering krångligare exempel I nedanstående scenario skapar metoden clone() i klassen C1 en djup kopia av sin instansvariabel (listan och elementen i listan) på ett korrekt sätt men clone() i klassen C2 gör en grund kopia. v2 = v1.clone(); v1 :C1 :C2 :C2 :C2 :C1 :C3 :C3 :C3 v2 :C2 :C2 :C2 Objektorienterad programmering fk 25 Föreläsning 9
Djup kopiering krångligare exempel I nedanstående scenario har vi en korrekt djup kopiering. Metoden clone() i klassen C1 skapar en kopia av listan och elementen i listan och clone() i klassen C2 gör en djup kopiering. v2 = v1.clone(); v1 :C1 :C2 :C2 :C2 :C3 :C3 :C3 :C1 :C3 :C3 :C3 v2 :C2 :C2 :C2 Objektorienterad programmering fk 26 Föreläsning 9
Djup kopiering samlingar Metoden clone() i samlingar och arrayer använder grund kloning. Detta betyder att alla instansvariabler som är samlingar eller arrayer måste klonas element för element om elementen är muterbara. public class SomeClass implements Cloneable { private List<Dog> list = new ArrayList<Dog>(); public SomeClass clone() { try { SomeClass result = (SomeClass) super.clone(); List<Dog> copy = new ArrayList<Dog>(list.size()); for(dog item: list) copy.add(item.clone()); result.list = copy; return result; catch (CloneNotSupportedException e) { return null; //never invoked Objektorienterad programmering fk 27 Föreläsning 9
Kopieringskonstruktor och kloning En kopieringskonstruktor tar som parameter ett objekt av samma klass och gör en djupkopiering av parameterobjektet. public class Leg { private GeoPosition startpos; private int course; private float length; public Leg(Leg other) { this.startpos = other.startpos.clone(); this.course = other.course; this.length = other.length; @Override public Leg clone() { return new Leg(this); Objektorienterad programmering fk 28 Föreläsning 9
Kopieringskonstruktor och arv Om en basklass har en kopieringskonstruktor kan denna anropas av en kopieringskonstruktor i en subklass: public class Sub extends Base { public Sub(Sub other) { // the copy constructor in Base copies // the base class field from other super(other); // then copy sub class specific fields... @Override public Sub clone() { return new Sub(this); Objektorienterad programmering fk 29 Föreläsning 9
Förhindra kloning Det finns situationer då man, av olika andledningar, vill förhindra kloning. Nedanstående scheman är då användbara: public class NoCopy { private int value; private boolean status; private String str; public NoCopy clone() throw new CloneNotSupportedException(); public class NoCopy extends SomeCopyClass { private int value; private boolean status; private String str; public NoCopy clone() throw new CloneNotSupportedException(); Objektorienterad programmering fk 30 Föreläsning 9
Problem vid kloning Det finns många problem med att implementera clone(): superklassen måste implementerar clone(). superklassens implementation måste vara korrekt. många klasser saknar implementation av clone() många klasser har felaktig implementation av clone() alla instansvariabler som är samlingar eller arrayer måste klonas element för element i cykliska strukturer och strukturer där objekt är delade, måste också motsvarande objekt vara delade i kopian Copy Original Wrong Objektorienterad programmering fk 31 Föreläsning 9