int compareTo(T that)
// used as e1.compareTo(e2)
Tomáš Pitner, Radek Ošlejšek, Marek Šabo, Jakub Čecháček pitner@muni.cz
chceme prvky v kolekci uspořádané, ale nechceme to dělat "ručně"
v kolekci typu String
chceme jména od K po M
uspořádání dáné třídy musí být definováno ve třídě
Comparable<T>
rozhraní slouží k definování přirozeného (defaultního) uspořádání třídy
třída implementuje rozhraní ⇒ objekty jsou vzájemně uspořádatelné
použití zejména u uspořádaných kontejnerů
předepisuje jedinou metodu int compareTo(T o)
T
= typ objektu, název třídy
compareTo
int compareTo(T that)
// used as e1.compareTo(e2)
metoda porovná 2 objekty — this
(e1) a that
(e2)
vrací celé číslo, pro které platí:
číslo je záporné, když e1 < e2
číslo je kladné, když e1 > e2
0, když nezáleží na pořadí
na samotném čísle nezáleží, je v pořádku používat pouze hodnoty -1, 0, 1
Comparable<E>
public class Point implements Comparable<Point> {
private int x;
// ascending order
public int compareTo(Point that) {
return this.x - that.x;
}
}
...
new Point(1).compareTo(new Point(4)); // -3
Existuje i beztypové rozhraní Comparable , to ale nebudeme používat! |
Comparable
jako příklad funkcionálního rozhraníComparable<T> je hezký typický příklad tzv. funkcionálního rozhraní (functional interface); má jedinou metodu compareTo
a lze použít například jako predikát pro filtrování objektů v proudech.
compareTo
vs. equals
chování compareTo
by mělo být konzistentní s equals
pro rovné objekty by compareTo
mělo vrátit 0
není to však nutnost
např. třída BigDecimal
pro přesné hodnoty podmínku porušuje
pro stejné hodnoty s rozdílnou přesností — např. 4.0 a 4.00
compareTo
na rozdíl od equals
nemusí vstupní objekt přetypovávat a může vyhazovat výjimku
Co kdybychom chtěli více typů uspořádání, nebo alternativu k přirozenému uspořádání?
Nemůžeme nadefinovat stejnou metodu víckrát.
rozhraní Comparator<T> slouží k definování uspořádání zvnějšku — pomocí objektu jiné třídy
předepisuje jedinou metodu int compare(T o1, T o2)
uspořádání funguje nad objekty typu T
návratová hodnota compare
funguje stejně jako u compareTo
funguje jako alternativa pro další uspořádání
Třída String
má definované přirozené uspořádání lexikograficky.
Definujme lexikografický komparátor, který ignoruje velikost písmen:
public class IgnoreCaseComparator implements Comparator<String> {
public int compare(String o1, String o2) {
return o1.toLowerCase().compareTo(o2.toLowerCase());
}
}
...
new IgnoreCaseComparator().compare("HI", "hi"); // 0
metody pro uspořádání programátor v kódu obvykle nepoužívá
namísto toho používá uspořádané kolekce, kdy prvky v kolekci jsou řazeny automaticky
je nutno definovat přirozené uspořádání nebo použít komparátor, aby kolekce věděla, podle jakých pravidel prvky seřadit
komparátor se nastavuje při vytváření uspořádané kolekce nebo mapy (viz dále)
Jedná se o použití návrhové vzoru Strategy:
Komparátor implementuje strategii třídění na daném typu objektů. Přitom můžeme mít víc strategií (víc způsobů třídění).
Uspořádaná kolekce nebo mapa pak představuje Context
ve vzoru, kdy říkáme, jaká strategie třídění se má v daném případě použít.
Budeme se zabývat rozhraními SortedSet
a SortedMap
.
SortedSet, SortedMap
SortedSet
rozhraní pro uspořádané množiny
všechny vkládané prvky musí implementovat rozhraní Comparable
(nebo použít komparátor)
implementace TreeSet
SortedMap
rozhraní pro uspořádané mapy
všechny vkládané klíče musí implementovat rozhraní Comparable
(nebo použít komparátor)
implementace TreeMap
TreeSet
TreeSet()
vytvoří prázdnou množinu
prvky jsou uspořádány podle přirozeného uspořádání
TreeSet(Collection<? extends E> c)
vytvoří množinu s prvky kolekce c
prvky jsou uspořádány podle přirozeného uspořádání
TreeSet(Comparator<? super E> comparator)
vytvoří prázdnou množinu
prvky jsou uspořádány podle komparátoru
TreeSet(SortedSet<E> s)
vytvoří množinu s prvky i uspořádáním podle s
TreeSet
IDefinice přirozeného uspořádání:
public class Point implements Comparable<Point> {
...
public int compareTo(Point that) {
return this.x - that.x;
}
}
TreeSet
IIPoužití:
SortedSet<Point> set = new TreeSet<>();
set.add(new Point(3));
set.add(new Point(3));
set.add(new Point(-1));
set.add(new Point(0));
System.out.println(set);
// prints -1, 0, 3
TreeSet
Třída String
má definované přirozené uspořádání lexikograficky.
SortedSet<String> set = new TreeSet<>();
set.add("Bobik");
set.add("ALIK");
set.add("Alik");
System.out.println(set); // [ALIK, Alik, Bobik]
SortedSet<String> set2 = new TreeSet<>(new IgnoreCaseComparator());
set2.addAll(set);
System.out.println(set2); // [ALIK, Bobik]
TreeSet pro porovnávání prvků používá compareTo / compare ,
proto má druhá množina pouze 2 prvky! |
TreeSet
pod lupouimplementována jako červeno-černý vyvážený vyhledávací strom
⇒ operace add, remove, contains
jsou v O(log n)
hodnoty jsou uspořádané
prvky jsou procházeny v přesně definovaném pořadí
TreeMap
množina klíčů je de facto TreeSet
hodnoty nejsou uspořádány
uspořádání lze ovlivnit stejně jako u uspořádané množiny
implementace stromu a složitost operací je stejná
TreeMap
Klíče jsou unikátní a uspořádané, hodnoty nikoliv.
SortedMap<String, Integer> population = new TreeMap<>();
population.put("Brno", -1);
population.put("Brno", 500_000);
population.put("Bratislava", 500_000);
System.out.println(population);
// {Bratislava=500000, Brno=500000}