17. Containerklasser l.setopaque(true); // ogenomskinlig bakgrund String fname = p.getproperty("fontname", "Serif"); String fstyle = p.getproperty("fontstyle", "PLAIN"); // översätt stilen till en heltalskonstant int ifstyle; if (fstyle.equals("bold") ) ifstyle = Font.BOLD; else if (fstyle.equals("italic")) ifstyle = Font.ITALIC; else ifstyle = Font.PLAIN; int fsize = Integer.parseInt (p.getproperty("fontsize", "24")); l.setfont(new Font(fName, ifstyle, fsize)); String fcolor = p.getproperty("foreground", "black"); l.setforeground(colors.get(fcolor)); String bcolor = p.getproperty("background", "white"); l.setbackground(colors.get(bcolor)); setsize(400,150); // fönstrets storlek setvisible(true); // gör fönstret synligt setdefaultcloseoperation(exit_on_close); När man kör programmet och egenskapsfilen har det innehåll som visades tidigare kommer det att se ut som i figur 17.6. Figur 17.6 Användning av egenskapsfil 17.9 Funktioner för avbildningstabeller I faktarutan på sidan 664 visades metoder som kan utföras på strömmar. En av metoderna var collect. Denna skall som parameter ha ett objekt av typen Collector. I 684 Författaren och Studentlitteratur kap17.fm 20 februari 2014 10.58:31 sida 684 av 690
17.9 Funktioner för avbildningstabeller klassen Collectors finns ett antal färdiga kollektorer (se faktarutan.) Flera av dessa, t.ex. tomap, kan generera avbildningstabeller utgående från strömmar. java.util.stream.collectors Följande kollektorer är klassmetoder som kan användas som argument till metoden collect för att ange hur data i en ström skall samlas upp. tocollection(u) lägger data i en ny samling som skapas av u (en Supplier) tolist() lägger data i en ny lista som skapas automatiskt toset() lägger data i en ny mängd som skapas automatiskt tomap(fk,fv) lägger data i en ny avbildningstabell, fk och fv är funktioner som anger hur data skall omvandlas till nycklar resp. värden tomap(fk,fv,bf) som ovan, men funktionen bf slår ihop ev. dubletter av nycklar tomap(fk,fv,bf,u) som ovan, men avbildningstabellen skapas av u (en Supplier) toconcurrentmap finns i samma tre versioner som tomap groupingby(fk) skapar en avbildningstabell, nycklarna beräknas med fk och värdena är listor med de data som får samma nyckelvärde groupingby(fk,co) som ovan, men samlar ihop data i listorna med kollektorn co groupingby(fk,u,co) som ovan, men tabellen skapas av u (en Supplier) groupingbyconcurrent finns i samma tre versioner som groupingby partitioningby(p) skapar en avbildningstabell, nycklarna har typen boolean, delar in data i två listor med hjälp av predikatet p partitioningby(p,co) som ovan, samlar ihop data i listorna med kollektorn co counting() beräknar antalet element i strömmen mapping(f,co) avbildar data med f, samlar ihop resultaten med kollektorn co collectingandthen(co,f) samlar ihop data med co, avbildar resultatet med f maxby(c) beräknar det största elementet, c är en Comparator minby(c) beräknar det minsta elementet, c är en Comparator reducing(bop) ger ett värde där varje nytt värde är resultatet av operationen bop mellan det förra värdet och det aktuella elementet reducing(v0,bop) som ovan men v0 är startvärdet reducing(v0,f,bop) som ovan men applicerar först funktionen f på alla element joining() slår ihop elementen till en String joining(del) slår ihop elementen till en String, avgränsade med del joining(del,pre,pos) som ovan men lägger pre först och pos sist I nedanstående kollektorer står T för Int, Long eller Double och f är en funktion som avbildar elementens typ till en Int, Long resp. Double averagingt(f) ger medelvärdet av resultaten av funktionen f på elementen summingt(f) ger summan av resultaten av funktionen f på elementen Författaren och Studentlitteratur 685 kap17.fm 20 februari 2014 10.58:31 sida 685 av 690
17. Containerklasser Antag t.ex. att vi byggt upp en lista med namnet namn: List<String> namn = new ArrayList<>(); namn.add("alice"); namn.add("maja"); namn.add("oscar"); Antag att vi vill skapa en avbildningstabell med namnen och namnens längder: Alice 5 Maja 4 Oscar 5 Detta kan vi åstadkomma enkelt med hjälp av kollektorn tomap: Map<String, Integer> namntab = namn.stream().collect(tomap(n -> n, n -> n.length())); De två parametrarna till tomap är funktioner som anger hur elementen i strömmen skall omvandlas till nycklar resp. värden. tomap är en klassmetod i klassen Collectors. För att vi enkelt skall kunna komma åt tomap och andra metoder i denna klass antar vi i fortsättningen av detta avsnitt att vi i början av programmet har skrivit import static java.util.stream.collectors.*; En annan kollektor i klassen Collectors som genererar avbildningstabeller är groupingby. Vi kan t.ex. skriva Map<Integer, List<String>> namntab2 = namn.stream().collect(groupingby(n -> n.length())); Resultatet blir en tabell med innehållet 4 [Maja] 5 [Alice, Oscar] Nycklarna i tabellen utgörs av de värden som beräknas i lambda-uttrycket och värdena är listor med alla element som avbildades på ett visst värde. Här hamnar alltså alla ord med samma längd i samma lista. I dessa exempel har vi skapat avbildningstabeller utgående från strömmar, men kan man göra tvärtom, dvs. generera strömmar från avbildningstabeller? I gränssnittet Map har man lagt till några default-metoder som har funktionsgränssnitt som parametrar (se faktarutan) men det finns ingen metod stream, som för listor och mängder. Det finns en metod foreach som är speciellt användbar. Med hjälp av denna kan man löpa igenom alla avbildningar på ett något enklare sätt än som demonstrerades på sidan 674. Följande sats ger t.ex. en utskrift av nycklarna och värdena i tabellen namntab. namntab.foreach((k, v) -> System.out.println(k + " " + v)); Metoden foreach ger ingen ström som resultat, men vi kan generera strömmar från avbildningstabeller genom att först anropa någon av metoderna entryset, keyset eller 686 Författaren och Studentlitteratur kap17.fm 20 februari 2014 10.58:31 sida 686 av 690
17.9 Funktioner för avbildningstabeller Tillägg till java.util.map foreach(c) utför operationen c (en BiConsumer) på avbildningarna replaceall(bf) beräknar med bf (en BiFunction) och tidigare värden nya värden för alla avbildningar. compute(k,bf) beräknar med bf (en BiFunction) och tidigare värde ett nytt värde för k. Ev. tidigare värde för k ersätts. computeifpresent(k,bf) som ovan, men bara om det finns ett tidigare värde computeifabsent(k,f) beräknar med f (en Function) ett nytt värde för k men bara om det inte finns ett tidigare värde merge(k,v,bf) om inget värde finns för k sätts det till v, annars beräknas ett nytt med bf (en BiFunction ). Det tidigare värdet och v är då argument values. Dessa ger som resultat mängder eller listor för vilka vi kan anropa metoden stream. Antag t.ex. att vi definierat följande avbildningstabell Map<String,Integer> m = new TreeMap<>(); och (med metoden put) lagt in information i tabellen. (Värdena är temperaturer.) Malmö 16 Lund 16 Göteborg 15 Stockholm 12 Gävle 12 Vi kan då skapa en mängd av alla avbildningar med metoden entryset: Set<Map.Entry<String,Integer>> eset = m.entryset(); Följande rader skriver ut alla avbildningar vilkas nycklar innehåller bokstaven ö : eset.stream().filter(e -> e.getkey().indexof('ö') >= 0).forEach(e -> System.out.println(e.getKey()+" "+e.getvalue())); Utskriften blir: Göteborg 15 Malmö 16 Nästa exempel skapar en ny tabell med alla avbildningar vilkas värde är minde än 15: Map<String,Integer> m2 = eset.stream().filter(e -> e.getvalue()<15).collect(tomap(e -> e.getkey(), e -> e.getvalue())); Tabellen m2 får innehållet Gävle 12 Stockholm 12 Författaren och Studentlitteratur 687 kap17.fm 20 februari 2014 10.58:31 sida 687 av 690
17. Containerklasser Kollektorn partitioningby skapar en avbildningstabell med nycklarna false och true och delar upp elementen i strömmen i två grupper, en för de element som inte uppfylller ett viss villkor och en för dem som gör det. Vi kan t.ex. dela upp data från tabellen m: Map<Boolean,List<Map.Entry<String,Integer>>> m3 = eset.stream().collect(partitioningby(e -> e.getvalue() < 15)); Den nya tabellen m3 kommer att få innehållet: false [Göteborg=15, Lund=16, Malmö=16] true [Gävle=12, Stockholm=12] Vi såg tidigare hur man kunde använda kollektorn groupingby för att skapa en avbilningstabell där alla element som avbildades på ett visst värde hamnade i en lista i samma avbildning. Kollektorn groupingby finns i en mer avancerad version där man förutom att ange villkoret för nycklarna också anger hur den nya avbildningstabellen skall se ut. Antag t.ex. att vi vill bilda en ny tabell i vilken de uppmätta temperaturerna är nycklar och värdena är listor med alla städer som har samma temperatur 16 [Lund, Malmö] 12 [Gävle, Stockholm] 15 [Göteborg] Vi kan då skriva följande Map<Integer,List<String>> m3 = eset.stream().collect(groupingby(e -> e.getvalue(), mapping(e -> e.getkey(), tolist()))); Här anger den första parametern till groupingby vad som skall vara nycklar i den nya avbildningstabellen. I detta exempel är det värdena i den ursprungliga tabellen. Den andra parametern anger hur dessa nycklar skall avbildas. Här skall värdena utgöras av listor som innehåller nycklarna från den ursprungliga tabellen. Vi ger nu en helt ny version av programmet TextAnalys2 på sidan 673. Programmet skulle läsa en fil och för vart och ett av orden i filen skriva ut hur många gånger detta ord förekom. I den nya versionen använder vi, liksom i tidigare visade exempel, först metoden lines för att skapa en ström med rader och sedan delar vi upp raderna i ett antal ord med hjälp av flatmap och splitasstream som finns i klassen Pattern. Orden samlas sedan ihop i en avbildningstabell med hjälp av kollektorn groupingby. import java.util.*; import java.util.regex.*; import java.text.*; import java.io.*; import static java.util.stream.collectors.*; 688 Författaren och Studentlitteratur kap17.fm 20 februari 2014 10.58:31 sida 688 av 690
17.10 Övningsuppgifter public class TextAnalys2 { // Alternativ version public static void main(string[] arg) throws IOException { Pattern p = Pattern.compile("\\s+"); Collator co = Collator.getInstance(); co.setstrength(collator.primary); new BufferedReader(new FileReader(arg[0])).lines().flatMap(s -> p.splitasstream(s)).collect(groupingby(s -> s, () -> new TreeMap<>(co), mapping(s -> s, tolist()))).foreach((k,v)-> System.out.println(k+" "+v.size())); Vi använder här den version av groupingby som har tre parametrar och där vi kan ange hur avbildningstabellen skall skapas. (Se faktarutan.) Vi anger att det skall vara en TreeMap så att nycklarna blir sorterade. Vad som händer i anropet av groupingby är att det skapas key-value-par där nyckeln är ett ord och värdet en lista bestående av alla likadana ord. I det efterföljande anropet av foreach beräknas listornas längder. Man får då reda på hur många ord det fanns av varje slag. 17.10 Övningsuppgifter 1. Ändra programmet TextAnalys på sidan 659 så att det bara skriver ut sådana ord som inte är reserverade ord i Java. (Se appendix A.) Använd en mängd som innehåller de reserverade orden och undersök om de inlästa orden finns i denna mängd. 2. Gör en ny lösning av övningsuppgift nr 5 på sidan 348. Använd en avbildningstabell för att avbilda artikelbeteckningar på objekt av klassen Artikel. Använd klasserna ObjectInputStream och ObjectOutputStream för att läsa in och skriva ut avbildningstabellen. 3. Istället för att ha flera egenskapstabeller i ett program kan det vara lämpligt att kombinera de avbildningar man läser in från en viss egenskapsfil med systemegenskaperna så att man bara har en egenskapstabell att leta i. Skriv satser som bildar en ny sådan gemensam egenskapstabell och ersätter systemets egenskapstabell med den nya tabellen. 4. Uppgiften är att skriva ett program som utför dragningen i ett lotteri. Antag att man i en textfil har lagt in information om vinsterna. Det finns en rad för varje typ av vinst. De finaste vinsterna kommer först. Början av filen kan t.ex. ha utseendet: 1 Resa för två till Azorerna 5 Weekend för två i Köpenhamn Siffran som står först anger hur många vinster av detta slag som finns. Programmet skall börja med att fråga efter filens namn samt minsta och högsta lottnummer Författaren och Studentlitteratur 689 kap17.fm 20 februari 2014 10.58:31 sida 689 av 690