Kokoelmat
- Kurssi: Ohjelmointitekniikka:Java, kevät 2005
- Opintopiiri: Ryhmä rämä
- Opintopiirin jäsenten nimet:
- Thompson Coon, Jon
- Hela, Ilkka
- Palviainen, Markus
- Forsgren, Jukka
- Piuva, Tero
- Päiväys 4.3.2005
Kokoelma
Kokoelma(tai säiliö) on olio, joka ryhmittää useampia alkioita yhteen pakettiin. Kokoelmia käytetään tiedon tallennukseen, saantiin ja muuttamiseen. Kokoelmat muodostavat luonnollisen kaltaisia ryhmiä, kuten pokerikäsi (korttien kokoelma), kirjekansio (kirjeiden kokoelma) tai puhelinluettelo (nimi-puhelinnumero kuvausten kokoelma). Mm. Seuraavia etuja saadaan kokoelmien käytöstä (Sunin sivut):
- Ohjelmointityön vähentyminen
- Ohjelman laadun ja nopeuden parantuminen
- Vähentää tarvetta oppia uusia rajapintoja
- Suosii tarvetta suunnitella uusia rajapintoja datan tallennukseen
- Edistää ohjelmiston kierrätystä
- Mahdollistaa toisiinsa liittymättömien luokkien keskinäisen yhteistyön
Kokoelmakehys
Kokoelmakehys on kokoelmien esittämiseen ja muuttamiseen tarkoitettu yhtenäinen arkkitehtuuri joka mahdollistaa muokkaamisen riippumatta joukon olioiden toteutuksesta. Kokoelmakehys on määritelty joukkona rajapintoja ja siihen liitettyjä sopimuksia. Kehys tarjoaa myös abstrakteja toteutuksia, joista voi luoda omia tietorakennerajapintoja sekä konkreettisia toteutuksia rajapinnoista.Kaikki kokoelmakehykset sisältävät seuraavat kolme asiaa:
Rajapinnat: Abstraktit tietotyypit kokoelmien esittämiseen. Rajapinnat luovat edellytyksen muokata kokoelmia riippumatta niiden toteutuksesta. OOP-kielissä, kuten Java, nämä rajapinnat yleisesti muodostavat hierarkian.
Toteutukset: Konkreettiset kokoelmarajapintojen toteutukset. Nämä ovat uudelleenkäytettävät tietorakenteet.
Algoritmit: Metodit jotka suorittavat hyödylliset laskennat, kuten etsinnän tai lajittelun olioille jotka toteuttavat kokoelmarajapinnan. Näitä algoritmeja sanotaan polymorphisiksi, koska samaa metodia voidaan käyttää tarkoituksenmukaisen kokoelmarajapinnan useassa eri toteutuksessa. Algoritmit ovat uudelleenkäytettävää toiminnallisuutta.
Kokoelmakehyksen sisältö (Sunin sivut):
- Rajapinnat
-Esittävät eri kokoelmatyyppejä, kuten listoja tai joukkoja. Muodostavat kehyksen rungon. - Yleiskäyttöiset toteutukset
-Rajapintojen (primary) päätoteutukset. - Legagy Implementations
-Aikaisemmissa julkaisuissa mukana olleet kokoelmaluokat Vector ja Hashtable, on sovitettu uudelleen toteuttamaan kokoelmien rajapinnat. - Erikoiskäyttöön tarkoitetut toteutukset
-Toteutuksia erikoistilanteita varten. - Concurrent Implementations
- Kääretoteutukset (Wrapper Implementations)
- Abstraktit toteutukset
- Algoritmit
- Infrastruktuuri
- Taulukon käsittely (Array Utilities)
- Kielen lisäysten tuomat muutokset
- Kolme uutta kokoelmarajapintaa
- Kaksi uutta Queue-luokan konkreettista toteutusta
- Viisi uutta BlockingQueue toteutusta.
- Uusi ConcurrentMap toteutus.
- List ja Set -luokista erikoistoteutukset. Tilanteisiin, jossa lukuoperaatiot ovat huomattavasti isommat mitä kirjoitus ja iteraatiota ei voida/saa synkronoida.
- Set ja Map erikoistoteutukset enum:in käyttöä varten.
- Uusi kääre-toteutusten perhe geneeristen kokoelmien käyttöä varten.
- Kolme uutta geneeristä algoritmia.
Rajapinnat
Rajapintojen perustarkoitus kokoelmakehyksessä on on mahdollistaa kokoelmien muuttaminen riippumatta siitä, mikä on kokoelman alkioiden toteutus. Perusrajapintana toimii Collection. Collection-rajapinnasta periytyvät viisi muuta rajapintaa: Set, List, SortedSet, Queue ja BlockingQueue. Lisäksi on olemassa kolme rajapintaa, jotka eivät periydy Collection rajapinnasta: Map, SortedMap ja ConcurrentMap. Nämä rajapinnat esittävät enemmänkin kuvauksia, kuin todellisia kokoelmia. Niistä löytyy kuitenkin samankaltaisia operaatioita kuin kokoelmien rajapinnoista, joten niitä voidaan muuntaa samalla tavoin.
Rajapintoja hyödyntämällä on erotettu tietorakenteiden toteutus omaksi kokonaisuudekseen ja toisaalta niiden avulla voidaan tarjota ohjelmoijalle yksi rajapinta useisiin eri tietorakenteiden toteutuksiin. Esimerkiksi List-rajapinnan toteuttavat luokat LinkedList ja ArrayList; Map-rajapinnan HashMap, IdentityHashMap ja LinkedHashMap-luokat. Set-rajapinnan taas toteuttavat HashSet ja LinkedHashSet sekä SortedSet-rajapinnan TreeSet.
Kaikki kokoelmien rajapintojen muokkausmetodit on merkitty valinnaisiksi. Jotkut toteutukset eivät voi suorittaa yhtä
tai useampaa näistä operaatiosta, heittäen UnsupportedOperationException -poikkeuksen, jos näitä
näitä operaatioita yritetään suorittaa. Toteutusten dokumentonnissa onkin mainittava niistä operaatioista
joita ne tukevat. Muutamia termejä on esitetty helpottamaan tässä määrittelyssä:
unmodifiable: Kokoelmat jotka eivät tue muuttamisoperaatioita.
modifiable: Kokoelmat joita voi muuttaa.
immutable: Collection-olion muutos ei ole näkyvä.
mutable: Collection-olion muutos on näkyvä.
fixed-size: Listat joiden koko pysyy vakiona huolimatta listan sisällöstä.
variable-size: Listat joiden koko ei pysy vakiona.
random access (lists): Listat joiden alkioiden viittaaminen onnistuu indeksillä.
sequential access (lists): Listat joiden alkioihin ei ole indeksiviittausta.
Jotkut toteutukset voivat rajata sitä millaisia alkioita voidaan kokoelmaan tallettaa. Esim. ne voivat olla
vain tiettyä tyyppiä tai eivät saa olla tyhjiä (null) viittauksia.
Set
Joukko on kokoelma joka ei voi sisältää duplikaatteja alkiosta. Rajapinta mallintaakin matemaattista joukkoa. Sitä käytetään esittämään mm. pokerikättä tai koneen suorittamia prosesseja.
List
Lista on järjestetty kokoelma (joskus kutsutaan myös jonoksi). Toisin kuin joukot, listat voivat sisältää duplikaatteja alkioista. Listan käyttäjällä on yleensä tarkka hallinta siitä, minne kukin alkio on sijoitettu. Käyttäjä voi hakea alkiota indeksin avulla.
SortedSet
Joukko joka ylläpitää alkioitaan nousevassa järjestyksessä. Joitain lisäoperaatioita on lisätty, että saataisiin hyöty järjestyksestä. Rajapintaa käytetään mm. sanalistoihin.
Queue
Jono on erikoistapaus listasta, jossa lisäykset voidaan tehdä vain jonon perään ja poistot (ja haut) jonon päästä.
BlockingQueue
Jono joka tukee operaatioita, jotka alkiota haettaessa odottavat että jono ei ole tyhjä ja alkiota lisättäessä odottavat että lisätilaa vapautuu jonoon.
Map
Olio jossa jokaista arvoa vastaa jokin avain. Jokainen avain voi kuvautua vain täsmälleen yhdelle arvolle.
SortedMap
Kuvaus joka ylläpitää kuvauksia nousevassa järjestyksessä (vrt. Set - SortedSet). Käytetään mm. sanakirjoissa tai puhelinluetteloissa.
Kokoelmien toteutukset
Luokat jotka toteuttavat jonkin kokoelmarajapinnan ovat yleensä muotoa <Toteutuksen tyyli><rajapinta>.
Yleiskäyttöiset toteutukset tukevat kaikkia valinnaisia kokoelmarajapinnan operaatioita ja niiden alkioissa
ei ole mitään rajoituksia. Nämä luokat ovat synkronoimattomia, mutta Collections-luokka sisältää
synchronization wrappers-tehtaita, joita voidaan käyttää lisäämään kokoelmaan synkronointi. Kaikista
uusista toteutuksista löytyy fail-fast iteraattorit, jotka havaitsevat luvattoman rinnakkaisen muuntamisen.
AbstractCollection, AbstractSet, AbstractList, AbstractSequentialLista ja AbstractMap -luokat muodostavat
perustoteutukset tärkeimmistä kokoelmarajapinnoista.
Rajapinnoista Set, Map, List esitellään seuraavat yleiskäyttöiset toteutukset:
HashSet
Toteuttaa Set rajapinnan, lisänä siinä on hajautustaulu (HashMap instanssi). Ei takaa joukon järjestystä iteroidessa tai yleisesti. Perusoperaatiot (add, remove, contains ja size) ovat vakioaikaisia sillä oletuksella, että hajautusfunktio toimii oikein. Iterointi yli joukon vaatii ajan joka on suoraan verrannollinen HashSetin alkioiden määrään + hajautuksen sankkojen määrä.
HashMap
Hajautustaulun toteutus joka perustuu Map-rajapintaan. Toteuttaa kaikki valinnaiset map operaatiot ja sallii null-arvot ja null-avaimet. Ei anna takuita järjestyksestä. Perusoperaatiot (put ja get) toimivat vakioaikaisesti jos hajautusfunktio toimii oikein. Iterointi yli hajautustaulun vie ajan joka on verrannollinen alkioiden + sankkojen määrään. HashMap instanssilla on kaksi parametria, jotka vaikuttavat sen tehokkuuteen: kapasiteetti (initial capasity) ja kuormituskerroin (load factor). Kapasiteetti kertoo sankkojen määrän alussa ja kuormituskerroin sen miten täyteen taulu saa täyttyä, ennenkuin sen kokoa aletaan kasvattamaan. Taulun saavuttaessa kuormituskertoimen ja alkioiden tulon, taulun koko kasvaa suunnilleen kaksinkertaiseksi.
ArrayList
Toteutus List-rajapinnasta, jossa koko taulukon kokoa voidaan vaihtaa. Toteuttaa kaikki valinnaiset listaoperaatiot ja sallii kaikki alkiot, mukaanlukien null. Vastaa periaatteessa Vector-luokkaa, mutta on synkronoimaton. size, isEmpty, get, set, iterator ja listIterator -operaatiot ovat vakioaikaisia. add toimii ajassa O(n). Jokaisella ArrayList-instanssilla on kapasiteetti, joka määräytyy listan koon mukaan.
TreeSet
Toteuttaa Set rajapinnan ja sisältää TreeMap-instanssin. Takaa, että järjestetyn joukon alkiot ovat nousevassa järjestyksessä. Perusoperaatiot (add, remove ja contains) ovat aikavaativuusluokkaa log(n).
TreeMap
Punamusta-puihin perustuva toteutus SortedMap-rajapinnasta. Takaa, että kuvaus on nousevassa järjestyksessä. Perusoperaatiot (containsKey, get, put ja remove) ovat aikavaativuusluokkaa log(n).
LinkedList
Linkitetyn listan toteutus List-rajapinnasta. Toteuttaa kaikki valinnaiset listaoperaatiot ja sallii kaikki alkiot, mukaanlukien null. Luokka sisältää get, remove ja insert operaatiot alkioille listan alkuun ja loppuun, minkä avulla voidaan linkitetystä listasta toteuttaa jono ja pino. LinkedList toteuttaakin Queue-rajapinnan. Listan operaatiot toteuttavat aikavaativuuden, jota voidaan odottaa kahteen suuntaan linkitetyltä listalta.
LinkedHashSet
Hajautustaulun ja linkitetyn listan toteutus Set-rajapinnasta, odotuksenmukaisella iterointijärjestyksellä. Eroaa HashSet-luokan toteutuksesta siinä, että linkitetty lista määrää iterointijärjestyksen - järjestyksessä joka on sen mukaan kun alkioita on lisätty joukkoon. Sisältää valinnaiset operaatiot ja hyväksyy myäs null-alkiot. Linkitetyllä hajautusjoukolla on kaksi sen suoritustehoon vaikuttavaa parametria: kapasiteetti ja kuormituskerroin, jotka on määritelty kuten HashSet-luokassa.
LinkedHashMap
Hajautustaulun ja linkitetyn listan toteutus Map-rajapinnasta. Iterointi samalla tavoin kuin LinkedHashSet-luokassa.
Esimerkkejä kokoelmien käytöstä
Esimerkki TreeSet- ja HashSet-luokkien käytöstä. Luodaan kaksi TreeSet-tyyppistä joukkoa nimistä ja muodostetaan niiden yhdiste, leikkaus ja erotus HashSet-luokan avulla.
import java.util.*; public class Union { private static String[] nimet1 = {"Tero", "Miina", "Erkki", "Pentti", "Anni", "Sanna", "Laura", "Eero", "Sami", "Kalle"}; private static String[] nimet2 = {"Tero", "Miina", "Ville", "Vesa", "Anni", "Risto", "Laura", "Eero", "Sami", "Jari"}; public static void main(String[] args) { Set s1 = new TreeSet(); Set s2 = new TreeSet(); for (int i = 0; i < 10; i++) { s1.add(nimet1[i]); s2.add(nimet2[i]); } System.out.println(); System.out.println("Molempien Set:ien sisällöt:"); System.out.println(s1); System.out.println(s2); Set union = new HashSet(s1); union.addAll(s2); System.out.println(); System.out.println("s1 ja s2 union:"); System.out.println(union); Set intersection = new HashSet(s1); intersection.retainAll(s2); System.out.println(); System.out.println("s1 ja s2 intersection:"); System.out.println(intersection); Set difference = new HashSet(s1); difference.removeAll(s2); System.out.println(); System.out.println("s1 ja s2 difference:"); System.out.println(difference); } }
Esimerkki algoritmien käytöstä. Luodaan luokka Apple, joka toteuttaa Comparable-rajapinnan. Omenoiden keskenäinen vertailu tapahtuu niiden painojen mukaan.
public class Apple implements Comparable { int weight = 0; String countryOfOrigin = null; public Apple(String countryOfOrigin,int weight) { this.weight = weight; this.countryOfOrigin = countryOfOrigin; } public int compareTo(Object a) { Apple anotherApple = (Apple)a; if (anotherApple.weight > this.weight) return -1; else if (anotherApple.weight == this.weight) return 0; else return 1; } public int getWeight() { return weight; } public String getCountryOfOrigin() { return countryOfOrigin; } public String toString() { String s = "Country of origin: " + countryOfOrigin + ". Weigth: " + weight; return s; } }Koska Apple-luokka toteuttaa Comparable-rajapinnan, voidaan omenoiden taulukkoon käyttää Arrays-luokan sort-metodia, joka järjestää taulukon alkiot nousevaan järjestykseen.
Apple[] apples = new Apple[5]; apples[0] = new Apple("Finland",20); apples[1] = new Apple("Sweden",1); . . java.util.Arrays.sort(apples);Esimerkkiohjelman koko lähdekoodi: SortApples.java
Esimerkki iteraattoreiden käytöstä erilaisille kokoelmille.
import java.util.*; class IteratorTest { public static void main(String[] args) { IteratorTest it = new IteratorTest(); //it.linkedListTest(); //it.TreeSetTest(); //it.HashMapTest(); it.HashSetTest(); } public void linkedListTest() { List myList = new LinkedList(); myList.add("value1"); myList.add("value2"); ListIterator myIterator = myList.listIterator(); while (myIterator.hasNext()) { System.out.println(myIterator.next()); } } public void TreeSetTest() { SortedSet mySet = new TreeSet(); mySet.add(new MyItem(3)); mySet.add(new MyItem(1)); mySet.add(new MyItem(2)); mySet.add(new MyItem(4)); mySet.add(new MyItem(5)); Iterator myIterator = mySet.iterator(); while (myIterator.hasNext()) { System.out.println(myIterator.next()); } } public void HashMapTest() { Map myHash = new HashMap(); myHash.put("First",new MyItem(1)); Set entries = myHash.entrySet(); Iterator myIterator = entries.iterator(); while (myIterator.hasNext()) { Map.Entry entry = (Map.Entry)myIterator.next(); String key = (String)entry.getKey(); MyItem value = (MyItem)entry.getValue(); System.out.println("key=" + key + ", value=" + value); } } public void HashSetTest() { Set set = new HashSet(); set.add ("eka"); set.add ("toka"); Iterator myIterator = set.iterator(); while (myIterator.hasNext()) { System.out.println(myIterator.next()); } } } class MyItem implements Comparable { public MyItem(int value) { this.value=value; } public String toString() { return "value of this item: "+value; } public boolean equals(Object other) { if (getClass() == other.getClass()) { MyItem otherItem = (MyItem)other; return value == otherItem.value; } else return false; } public int compareTo(Object other) { MyItem otherItem = (MyItem)other; return value - otherItem.value; } private int value; }
Esimerkki HashMap-luokan käytöstä. Toteutetaan tietokilpailu sijoittamalla kysymys ja vastaus pareittain HashMap -instanssiin.
import java.util.*; public class Kilpailu { private Map kysvas = new HashMap(); private String kysymys; private String vastaus; private int laskuriOikein = 0; private int laskuriVaarin = 0; private Scanner sc = new Scanner(System.in).useDelimiter(System.getProperty("line.separator")); public Kilpailu(String[] kv) { for(int i = 0; i < kv.length; i = i+2) { kysvas.put(kv[i], kv[i+1]); } } public void kysele() { for (Iterator i = kysvas.entrySet().iterator(); i.hasNext();) { Map.Entry e = (Map.Entry) i.next(); System.out.print(e.getKey() + ": "); this.vastaus = null; this.vastaus = sc.next(); if (this.vastaus.equals((String) e.getValue())) { System.out.println("Oikein"); laskuriOikein++; } else { System.out.println("Väärin"); laskuriVaarin++; } } } public void tulokset() { System.out.println("Vastasit oikein "+laskuriOikein+" kysymykseen. Vastauksista "+laskuriVaarin+" oli väärin."); } }Luokkaa käyttävän pääohjelman toteutus: Tietokilpailu.java.