Porovnávání a pořadí (uspořádání)
-
Obecně rozlišujeme, zda chceme zjišťovat shodnost (rovnost, ekvivalenci):
-
mezi dvěma primitivními hodnotami
-
mezi dvěma objekty
-
-
U primitivních hodnot jsou rovnost i uspořádání určeny napevno a nelze je změnit.
-
U objektů lze porovnání i uspořádání programově určovat.
Rovnost primitivních hodnot
-
Rovnost primitivních hodnot zjišťujeme pomocí operátorů:
-
==
(rovná se) -
!=
(nerovná se)
-
-
U integrálních (celočíselných) typů funguje bez potíží.
-
U čísel floating-point (
double
,float
) je třeba porovnávat s určitou tolerancí. -
U FP navíc existují hodnoty jako
+0.0
vedle-0.0
a tyto by měly být rovny.
1 == 1 // true
1 == 2 // false
1 != 2 // true
1.000001 == 1.000001 // true
1.000001 == 1.000002 // false
Math.abs(1.000001 - 1.000002) < 0.001 // true
Porovnávání metodami třídy Objects
-
java.util.Objects
je poměrně nová utilitní třída (obsahující jen statické metody) od Javy 8 -
mimo jiné obsahuje metody
Objects.equals(o1, o2)
na porovnávání objektů -
i ve variantě deep - hluboké porovnání - pro struktury
public static boolean equals(Object a, Object b)
public static boolean deepEquals(Object a, Object b)
Uspořádání primitivních hodnot
-
Uspořádání primitivních hodnot funguje pomocí operátorů
<
,⇐
,>=
,>
-
U primitivních hodnot nelze koncept uspořádání ani rovnosti programově měnit.
Uspořádání není definováno na typu boolean , tj. neplatí false < true !
|
1.000001 <= 1.000002 // true
Jak chápat rovnost objektů
- Identita objektů,
==
-
vrací
true
při rovnosti odkazů, tj. když oba odkazy ukazují na tentýž objekt - Rovnost obsahu, metoda
equals
-
vrací
true
při obsahové ekvivalenci objektů, což musí být explicitně nadefinované
Příklad ==
Person pepa1 = new Person("Pepa");
Person pepa2 = new Person("Pepa");
Person pepa3 = pepa1;
pepa1 == pepa2; // false
pepa1 == pepa3; // true
Porovnávání objektů pomocí ==
-
Porovnáme-li dva objekty prostřednictvím operátoru
==
dostaneme rovnost jen v případě, jedná-li se o dva odkazy na tentýž objekt. -
Jedná-li se o dva byť obsahově stejné objekty, ale existující samostatně, pak
==
vrátífalse
.
Objekty jsou identické = jedná se o jeden objekt = odkazy obsahují stejnou adresu objektu. |
Porovnávání objektů dle obsahu
-
Dva objekty jsou rovné (rovnocenné), mají-li stejný obsah.
-
Na zjištění rovnosti se použije metoda
equals
, kterou je potřeba překrýt. -
Pro nadefinování rovnosti bude hlavička metody vždy vypadat následovně:
Metoda equals
@Override
public boolean equals(Object o)
-
Parametrem je objekt typu
Object
. -
Jestli parametr není objekt typu
<class-name>
, obvykle je potřeba vrátitfalse
. -
Pak se porovnají jednotlivé vlastnosti objektů a jestli jsou stejné, metoda vráti
true
.
Příklad s equals
: komplexní číslo
Dvě komplexní čísla jsou stejná, když mají stejnou reálnou i imaginární část.
public class ComplexNumber {
private int real, imag;
public ComplexNumber(int r, int i) {
real = r; imag = i;
}
@Override
public boolean equals(Object o) {
if (this.getClass() != o.getClass()) return false;
ComplexNumber that = (ComplexNumber) o;
return this.real == that.real
&& this.imag == that.imag;
}
}
Mírně odlišné equals
public class ComplexNumber {
private int real, imag;
public ComplexNumber(int r, int i) {
real = r; imag = i;
}
@Override
public boolean equals(Object o) {
// instanceof type pattern, we'll see later
if (o instanceof ComplexNumber that)
return this.real == that.real
&& this.imag == that.imag;
else return false;
}
}
Porovnávání objektů — osoba I
Popis kódu na následujícím slajdu:
-
Dvě osoby budou stejné, když mají stejné jméno a rok narození.
-
Rovnost nemusí obsahovat porovnání všech atributů (porovnání
age
je zbytečné, když mámeyearBorn
). -
String
je objekt, proto pro porovnání musíme použít metoduequals
. -
Klíčové slovo
instanceof
říká "mohu pretypovat na daný typ".
Metoda equals musí být reflexivní, symetrická i tranzitivní
(javadoc).
|
Porovnávání objektů — osoba II
public class Person {
private String name;
private int yearBorn, age;
public Person(String n, int yB) {
name = n; yearBorn = yB; age = currentYear - yB;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof Person)) return false;
Person that = (Person) o;
return this.name.equals(that.name)
&& this.yearBorn == that.yearBorn;
}
}
Technické zjednodušení instanceof
-
Počínaje Javou 14 lze využít tzv. instanceof pattern.
-
Odkaz na objekt se kromě běhové typové kontroly rovnou přiřadí do
person
.
if (o instanceof Person person) {
return this.name.equals(person.name)
&& this.yearBorn == person.yearBorn;
} else {
return false;
}
Porovnávání objektů — použití
ComplexNumber cn1 = new ComplexNumber(1, 7);
ComplexNumber cn2 = new ComplexNumber(1, 7);
ComplexNumber cn3 = new ComplexNumber(1, 42);
cn1.equals(cn2); // true
cn1.equals(cn3); // false
Person karel1 = new Person("Karel", 1993);
Person karel2 = new Person("Karel", 1993);
karel1.equals(karel2); // true
karel1.equals(cn1); // false
cn2.equals(karel2); // false
Chybějící equals
-
Co když zavolám metodu
equals
aniž bych ji přepsal? -
Použije se původní metoda
equals
ve třídeObject
: -
Původní
equals
funguje přísným způsobem — rovné jsou jen identické objekty:
public boolean equals(Object obj) {
return (this == obj);
}
Jak porovnat typ třídy
Je this.getClass() == o.getClass()
stejné jako o instanceof Person
?
-
Ne! Jestli třída
Manager
dědí odPerson
, pak:
manager.getClass() == person.getClass() // false
manager instanceof Person // true
-
Co tedy používat?
-
instanceof
porušuje symetriix.equals(y) == y.equals(x)
-
getClass
porušuje tzv. Liskov substitution principle
-
-
Záleží tedy na konkrétní situaci.
Metoda hashCode
-
Při překrytí metody
equals
nastává dosud nezmíněný problém. -
Jakmile překryjeme metodu
equals
, měli bychom současně překrýt i metoduhashCode
. -
Metoda
hashCode
je také ve tříděObject
, tudíž ji obsahuje každá třída.
@Override
public int hashCode()
-
Metoda vrací celé číslo pro daný objekt tak, aby:
-
pro dva stejné (
equals
) objekty musí vždy vrátit stejnou hodnotu -
jinak by metoda měla vracet různé hodnoty
-
není to ani nezbytné a ani nemůže být vždy splněno
-
složité třídy mají více různých objektů než je všech hodnot typu
int
Příklad hashCode
I
public class ComplexNumber {
private int real;
private int imag;
...
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() {
return 31*real + imag;
}
}
Příklad hashCode
II
public class Person {
private String name;
private int yearBorn, age;
...
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() {
int hash = name.hashCode();
hash += 31*hash + yearBorn;
return hash;
}
}
Obecný hashCode
Nejlépe je vytvářet metodu následujícím způsobem (31 je prvočíslo):
@Override
public int hashCode() {
int hash = attribute1;
hash += 31 * hash + attribute2;
hash += 31 * hash + attribute3;
hash += 31 * hash + attribute4;
return hash;
}
A nebo ji generovat (pokud víte, co to dělá :-))
Proč hashCode
-
Metoda se používá v hašovacích tabulkách, využívá ji například množina
HashSet
. -
Při zjištění, jestli se prvek X nachází v množině, metoda vypočítá její haš (hash).
-
Pak vezme všechny prvky se stejným hašem a zavolá
equals
(haš mohl být stejný náhodou). -
Jestli má každý objekt unikátní haš, pak je tato operace konstantní.
-
Jestli má každý objekt stejný haš, pak je operace
contains
lineární!
Jestli se hashCode napíše špatně (nevrací pro stejné objekty stejný haš)
nebo zapomene — množina nad danou třídou přestane fungovat!
|
Uspořádání objektů
-
Budeme probírat později
-
V Javě neexistuje přetěžování operátorů
<, ⇐, >, >=
-
Třída musí implementovat rozhraní
Comparable
a její metoducompareTo