TDA550 Objektorienterad programvaruutveckling IT, forts. kurs Övning vecka 3 Pelle Evensen, Daniel Wetterbro 12 november 2009 Sammanfattning Denna vecka ska vi titta på gränssnitten Comparable och Comparator samt exceptions. Vi gör också ett återbesök i programming by contract. Övningarna är graderade (något subjektivt) från lätt ( ) till svår ( ). Svårighetsgraden hos en övning har inte nödvändigtvis med lösningens storlek att göra. 1 Comparator & Comparable Vi kan för en viss klass göra så att den får en total ordning genom att låta den implementera gränssnittet Comparable. I de fall då vi vill tillhandahålla mer än en ordning för en klass använder vi oss av gränssnittet Comparator. Med lån från övningar av Bror Bjerner 1
1.1 Implementation av Comparable I fig. 1 ges klassen Car. Vi vill göra så att klassen vid sortering sorteras i stigande ordning jämförandes följande variabler; 1. modelyear 2. manufacturer 3. modelname Om två bilar är av samma årsmodell så jämför man också tillverkare, om tillverkare också är lika, jämför också modellnamn. Skriv om compareto i Car så att den får denna totala ordning. public final class Car implements Comparable<Car> { private final String manufacturer; private final String modelname; private final int modelyear; public Car(String manufacturer, String modelname, int modelyear) { this.manufacturer = manufacturer; this.modelname = modelname; this.modelyear = modelyear; // Accessors go here... public int compareto(car c) { // Provide code to compare a car in ascending order // according to the fields // * modelyear // * manufacturer // * modelname // Every car compares as equal to any other car. Fix by // implementing what should be here. return 0; public boolean equals(object o) { if (o instanceof Car) { Car c = (Car) o; return c.manufacturer.equals(manufacturer) && c.modelname.equals(modelname) && c.modelyear == modelyear; else { return false; Figur 1: Klassen Car. 2
1.2 Implementation av en flexibel(?) Comparator Det kan tänkas att man inte kan förutspå alla totala ordningar som kan vara av intresse vid en senare tidpunkt. Ett vanligt fall är då vi i en applikation visar en tabell och man genom att klicka på kolumnernas rubriker bestämmer om posterna ska sorteras i stigande eller fallande ordning med avseende på värdet i kolumnen. Givet att vi har n kolumner så kan dessa ordnas på n! olika sätt. Om vi dessutom lägger till egenskapen att vi ska kunna ha både stigande och fallande ordning för varje fält så får vi 2 n n! möjliga ordningar. Även för små n blir detta snabbt ohanterligt. Vi skulle t.ex. behöva 384 olika komparatorer för att få alla totala ordningar av 4 fält (2 4 4! = 16 24 = 384). /** * A comparator that can provide all total orderings for a Car-object. public class CarComparator implements Comparator<Car> { public enum Order {ASCENDING, DESCENDING, DONT CARE; public enum Field {MANUFACTURER, MODELNAME, MODELYEAR; // Instansvariabler här... /** * @pre What preconditions should hold? public CarComparator(Field f1, Order o1, Field f2, Order o2, Field f3, Order o3) { // Fixa med instansvariabler? public int compare(car c1, Car c2) { throw new UnsupportedOperationException("Compare is missing! Help!"); Figur 2: Klassen CarComparator. 1.2.1 Implementera Klassen CarComparator (fig. 2) är ett skelett för att kunna tillhandahålla alla möjliga komparatorer för att ordna objekt av typ Car. Slutför implementationen av konstruktorn och metoden compare. 1.2.2 Reflektera Skälet att vi har en ordning för DONT CARE är att det kan vara bra att slippa jämföra vissa fält. Detta val gör man alltså av prestandaskäl och inget annat. Om vi inte bryr oss om ordningen så hade den ju lika gärna kunnat vara fallande eller stigande. 3
Vilka förvillkor (preconditions) för konstruktorn är rimliga? Har alla par f, o en vettig tolkning? Har alla tripplar av par med fält och ordning en vettig tolkning? Ger alla unika val av parametrar unika totala ordningar? Tips: Jämför storleken på definitionsmängden för konstruktorn med antalet totala ordningar för 3 fält. Definitionsmängdens storlek är antalet element i den cartesiska produkten, alltså antalet möjliga distinkta indata för parameter 1 gånger antalet möjliga indata för parameter 2, etc. Om vi t.ex. har en metod minmetod(int a, int b) så är definitionsmängdens storlek 2 32 2 32 = 2 64 en int i Java har ju 32 bitar. Två int efter varandra blir då 64 bitar som tillsammans kan anta 2 64 olika värden. 2 Exceptions 2.1 Propagering av fel uppför anropsstacken En fördel med exceptions är möjligheten att propagera felrapportering uppåt i anropsstacken. Betrakta PseudoKod1 (sid. 5). Antag att metoden readfile() är den fjärde metoden som anropas i en serie av nästlade anrop från vår mainmetod: metod1() anropar metod2(), som anropar metod3(), vilken slutligen anropar readfile(). Antag vidare att metod1() i PseudoKod1 är den enda metod som är intressant för hantering av något fel som uppstått i readfile(). 2.1.1 Om vi inte haft exceptions? Gör en implementation i pseudo-kod av dessa metoder utan att använda exceptions, d.v.s. komplettera PseudoKod2 (sid. 5) med metoderna metod2 och metod3. Antag att readfile() rapporterar ett fel på samma sätt som metod2() gör i PseudoKod1. 2.1.2 Exceptions räddar dagen? Hur kan nu koden i PseudoKod2 skrivas om med användning av exceptions? Antag att vi kan använda oss av en exception-typ ReadFileFailedException. 2.2 Arv av exception Antag att du i Java har definierat en egen typ NotANumberException som en subklass till Exception. Vidare har du definierat ytterligare en typ NotAPositiveNumberException som en subklass till NotANumberException. 4
2.2.1 Vad fångas I? Kommer koden i Catcher1 att fånga ett NotAPositiveNumberException? Varför/Varför inte? 2.2.2 Vad fångas II? Kommer koden i Catcher2 att fungera? Varför/Varför inte? void metod1() { do something A; metod2(); do something B; void metod2() { do something C; metod3(); do something D; void metod3() { do something E; readfile(); do something F; do something A; error = metod2(); if (error) doerrorprocessing; else do something B;...... Figur 4: Klassen PseudoKod2. Figur 3: Klassen PseudoKod1. catch(notanumberexception nane) { System.out.println("Fångade ett " + "NotANumberException"); Figur 5: Klassen Catcher1. try {... catch(notanumberexception nane) { System.out.println("Fångade " + "ett NotANumberException"); catch(notapositivenumberexception napne) { System.out.println("Fångade ett " + "NotAPositiveNumberException"); Figur 6: Klassen Catcher2. 5
2.3 Gissningslek Spelet UglyGuessGame utgör en mer generell variant av det klassiska gissa-tal-spelet där användaren efter varje gissning får besked om gissningen är lägre eller högre än det rätta svaret. Generaliseringen bygger på att ett gränssnitt (GuessGameComponents) definierats genom vilket en implementerande klass kan låta gissningsleken bygga på valfri typ av element; men bara så länge elementen har en inbördes ordning (total ordning genom arv av gränssnittet Comparator). I exemplet finns två varianter: Det klassiska gissa-tal-spelet genom klassen IntegerGuessGameComponents och ett gissa-land-spel (världens tio till ytan största länder) genom klassen CountriesGuessGameComponents. Det finns (faktiskt) en anledning till att klassen namngetts Ugly... ; det har nämligen smugit sig in ett antal dåliga designval 1 och/eller fel som alla knyter an till undantag (exceptions). Försök hitta dem! Koden återfinns i Appendix A fig. 7 fig. 11. 1 Vi har försökt bryta mot så många sunda principer som möjligt från [Blo08]. 6
A Källkod till gissningsspelet /** * Ett gränssnitt för komponenter i gissningsspelet. En implementerande klass * tillhandahåller ett intervall med element som har en total ordning och bildar * på detta sätt en s.k. strategi för gissningsspelet. * * @author Daniel Wetterbro * @param <T> Elementtyp för gissningar interface GuessGameComponents<T> extends Comparator<T> { /** @return Användarens angivna gissning. public T getguess() throws IOException; /** @return Ett slumpmässigt element av gissningslekens typ public T generaterandomelement(); /** @inheritdoc public int compare(t t1, T t2); /** @return Första elementet i intervallet av element public String firstelement(); /** @return Sista elementet i intervallet av element public String lastelement(); Figur 7: Klassen GuessGameComponents. /** * Världens tio till ytan största länder, ordnade i * storleksordning där den med störst yta har störst * ordinal-tal. enum TenLargestCountries { Sudan, Kazakhstan, Argentina, India, Australia, Brazil, China, USA, Canada, Russia Figur 8: Klassen TenLargestCountries. 7
/** * Gissningsspel Ett element slumpas fram. Användaren får sedan gissa sig fram till detta * genom att få information om det gissade elementet är större eller mindre än målet. public class UglyGuessGame { public <E> void rungame(guessgamecomponents<e> ge) throws IOException { E goal = ge.generaterandomelement(); // Slumpa fram målelementet try { do { E guess = null; boolean successfulread = false; int guessagain = 3; System.out.print("Ange ett element mellan " + ge.firstelement() + " och " + ge.lastelement() + "\n: "); // Hämta in en gissning från användaren, maximalt 3 försök till input do { try { guess = ge.getguess(); successfulread = true; catch (IOException e) { System.out.println("Fel vid inläsning, försök igen!"); guessagain ; while (!successfulread && guessagain > 0); // Avgör om gissningen var rätt, för stor respektive för liten if (ge.compare(guess, goal) == 0) { throw new GameOverException("Grattis! Rätt svar!"); else if (ge.compare(guess, goal) > 0) { System.out.println("Gissningen var för stor, försök igen!"); else { System.out.println("Gissningen var för liten, försök igen!"); while (true); catch (GameOverException e) { System.out.println(e.getMessage()); /** Undantag som används för att meddela att spelet nått sitt slut. private class GameOverException extends Exception { private static final long serialversionuid = 1L; public GameOverException(String message) { super(message); public static void main(string[ ] arg) throws IOException { UglyGuessGame game = new UglyGuessGame(); // Spela gissa tal! game.rungame(new IntegerGuessGameComponents()); // Spela gissa land! game.rungame(new CountriesGuessGameComponents()); 8 Figur 9: Klassen UglyGuessGame.
/** * Komponenter för gissningslek med världens 10 till ytan största länder. class CountriesGuessGameComponents implements GuessGameComponents<TenLargestCountries> { public TenLargestCountries getguess() throws IOException { BufferedReader myin = new BufferedReader(new InputStreamReader(System.in)); String strinput = null; strinput = myin.readline(); // Läs in en rad från standard input // Försök att översätta den inlästa strängen till ett land TenLargestCountries enuminput = TenLargestCountries.Russia; try { enuminput = Enum.valueOf(TenLargestCountries.class, strinput); catch (IllegalArgumentException e) { return enuminput; public TenLargestCountries generaterandomelement() { int numcountries = TenLargestCountries.values().length; return TenLargestCountries.values()[(int) (Math.random() * numcountries)]; public int compare(tenlargestcountries t1, TenLargestCountries t2) { return t1.compareto(t2); public String firstelement() { return TenLargestCountries.values()[0].toString(); public String lastelement() { int numcountries = TenLargestCountries.values().length; return TenLargestCountries.values()[numCountries 1].toString(); Figur 10: Klassen CountriesGuessGameComponents. 9
/** * Komponenter för gissningslek med heltal. class IntegerGuessGameComponents implements GuessGameComponents<Integer> { public static final int RANGE = 100; // 0-100 public Integer getguess() throws IOException { BufferedReader myin = new BufferedReader(new InputStreamReader(System.in)); String strinput = null; strinput = myin.readline(); // Läs in en rad från standard input Integer numinput = 0; // Försök att översätta den inlästa strängen till ett heltal try { numinput = Integer.parseInt(strInput); catch (NumberFormatException e) { //... return numinput; public Integer generaterandomelement() { return new Integer((int) (Math.random() * (RANGE + 1))); public int compare(integer i1, Integer i2) { return i1 i2; public String firstelement() { return "0"; public String lastelement() { return "" + RANGE; Figur 11: Klassen IntegerGuessGameComponents. Referenser [Blo08] Joshua Bloch. Effective Java. Addison-Wesley, 2nd edition, 2008. 10