Porovnávání v Javě 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 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 • 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í 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 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 1 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 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á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é = odkazy obsahují stejnou adresu objektu. Porovnávání objektů dle obsahu 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 , obvykle je potřeba vrátit false. • Pak se porovnají jednotlivé vlastnosti objektů a jestli jsou stejné, metoda vráti true. 2 Porovnávání objektů — 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;   } } 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á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). Porovnávání objektů — osoba II 3 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;   } } 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říde Object: public boolean equals(Object obj) {   return (this == obj); } Původní equals funguje přísným způsobem — rovné jsou jen identické objekty. 4 Jak porovnat typ třídy 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. 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 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 Příklad hashCode I 5 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;   } 6 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í metodu compareTo Repl.it demo k porovnávání primitivních hodnot a objektů • https://repl.it/@tpitner/PB162-Java-Lecture-06-vs-equals 7