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
Tomáš Pitner, Radek Ošlejšek, Marek Šabo, Jakub Čecháček
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 pořadí určeny napevno a nelze je změnit.
U objektů lze porovnání i uspořádání programově určovat.
Rovnost primitivních hodnot zjišťujeme pomocí operátorů:
==
(rovná se)
!=
(nerovná se)
U integrální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
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 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
==
vrací true
při rovnosti odkazů, tj. když oba odkazy ukazují na tentýž objekt
equals
vrací true
pri logické ekvivalenci objektů (musí být explicitně nadefinované)
Person pepa1 = new Person("Pepa");
Person pepa2 = new Person("Pepa");
Person pepa3 = pepa1;
pepa1 == pepa2; // false
pepa1 == pepa3; // true
==
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é = odkazy obsahují stejnou adresu objektu. |
Rovnost obsahu, metoda equals
:
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ě:
@Override
public boolean equals(Object o)
Parametrem je objekt typu Object
.
Jestli parametr není objekt typu <class-name>
, obvykle je potřeba vrátit false
.
Pak se porovnají jednotlivé vlastnosti objektů a jestli jsou stejné, metoda vráti true
.
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;
}
}
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áme yearBorn
).
String
je objekt, proto pro porovnání musíme použít metodu equals
.
Klíčové slovo instanceof
říká "mohu pretypovat na daný typ".
Metoda equals musí být reflexivní, symetrická i tranzitivní
(javadoc). |
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;
}
}
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;
}
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
equals
Co když zavolám metodu equals
aniž bych ji přepsal?
Použije se původní metoda equals
ve tříde Object
:
public boolean equals(Object obj) {
return (this == obj);
}
Původní equals
funguje přísným způsobem — rovné jsou jen identické objekty.
Je this.getClass() == o.getClass()
stejné jako o instanceof Person
?
Ne! Jestli třída Manager
dědí od Person
, pak:
manager.getClass() == person.getClass() // false
manager instanceof Person // true
Co tedy používat?
instanceof
porušuje symetrii x.equals(y) == y.equals(x)
getClass
porušuje tzv. Liskov substitution principle
Záleží tedy na konkrétní situaci.
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 metodu hashCode
.
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 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
hashCode
Ipublic class ComplexNumber {
private int real;
private int imag;
...
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() {
return 31*real + imag;
}
}
hashCode
IIpublic 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;
}
}
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á :-))
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! |
Budeme probírat později
V Javě neexistuje přetěžování operátorů <, ⇐, >, >=
Třída musí implementovat rozhraní Comparable
a její metodu compareTo