Wykład 7
equals
Metoda equals()
w Javie jest używana do porównywania dwóch obiektów pod kątem równości. Standardowa implementacja equals()
w klasie Object
porównuje referencje, co oznacza, że dwa obiekty są uznawane za równe, jeśli wskazują na to samo miejsce w pamięci. W praktyce często chcemy porównywać obiekty na podstawie ich stanu wewnętrznego (wartości ich pól), co wymaga nadpisania metody equals()
.
Oto zasady tworzenia dobrze zaprojektowanej metody equals()
:
Użycie słowa kluczowego @Override
: Wskazuje, że metoda nadpisuje metodę z klasy bazowej.
Sprawdzenie, czy obiekt nie jest porównywany sam do siebie: Jeśli tak, metoda powinna zwrócić true
.
Sprawdzenie, czy obiekt do porównania nie jest null
i czy jest tego samego typu.
Rzutowanie obiektu do porównania na odpowiedni typ: Po potwierdzeniu, że obiekty są tego samego typu, można bezpiecznie rzutować.
Porównanie pól obiektów: Porównaj wszystkie istotne pola. Dla pól, które są typami prymitywnymi, użyj operatorów porównania (==
). Dla obiektów użyj equals()
.
Zwrócenie true
, jeśli wszystkie porównania pól są równe: W przeciwnym razie zwróć false
.
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
// Sprawdzenie, czy obiekty są tym samym obiektem
if (this == obj) return true;
// Sprawdzenie, czy obj nie jest null i czy jest typu Person
if (obj == null || getClass() != obj.getClass()) return false;
// Rzutowanie obj do typu Person
Person person = (Person) obj;
// Porównanie pól
if (age != person.age) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
}
equals()
i hashCode()
: Jeśli nadpisujesz equals()
, musisz także nadpisać hashCode()
. Kontrakt między tymi dwiema metodami mówi, że dwa obiekty uznane za równe przez equals()
muszą zwracać tę samą wartość hashCode()
.Objects.equals()
z Java 7 i nowszych w celu uniknięcia NullPointerException
podczas porównywania pól, które mogą być null
.float
użyj Float.compare(float, float)
, a dla double
- Double.compare(double, double)
, aby uniknąć problemów z porównywaniem liczb zmiennoprzecinkowych.class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
// Sprawdzenie, czy obiekty są tym samym obiektem
if (this == obj) return true;
// Sprawdzenie, czy obj jest instancją Person i rzutowanie
if (obj instanceof Person person) {
// Porównanie pól
if (age != person.age) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
return false;
}
// Gettery, settery, hashCode(), toString() itp.
}
class Employee extends Person {
private String department;
public Employee(String name, int age, String department) {
super(name, age);
this.department = department;
}
@Override
public boolean equals(Object obj) {
// Sprawdzenie równości z perspektywy klasy bazowej
if (!super.equals(obj)) return false;
// Rzutowanie obj do typu Employee
Employee employee = (Employee) obj;
// Porównanie dodatkowego pola
return department != null ? department.equals(employee.department) : employee.department == null;
}
// Gettery, settery, hashCode(), toString() itp.
}
byte
, short
, int
, long
, float
, double
, char
, i boolean
==
oraz !=
float
i double
==
i !=
: Używanie tych operatorów jest bezpieczne tylko wtedy, gdy porównujemy konkretne, dobrze zdefiniowane wartości float
lub double
, które nie są wynikiem obliczeń. Na przykład, porównywanie stałych lub przypisanych wartości może być bezpieczne. Jednak w przypadku wartości wynikających z obliczeń matematycznych, nawet niewielkie błędy zaokrąglenia mogą prowadzić do nieoczekiwanych wyników.equals
: Metoda equals
dla obiektów typu Float
i Double
sprawdza, czy dwa obiekty są dokładnie takie same, co oznacza, że również może prowadzić do problemów podobnych do tych związanych z użyciem ==
i !=
, ponieważ również porównuje dokładne wartości bitowe.2a. Metoda compare
: nieco lepsza do porównywania. Zwraca liczbę całkowitą w zależności od relacji między argumentami.
epsilon
, która reprezentuje akceptowalny błąd zaokrąglenia.double epsilon = 0.000001;
double a = 0.1;
double b = 0.1 + 0.0000001;
if (Math.abs(a - b) < epsilon) {
System.out.println("a i b są wystarczająco blisko siebie");
}
Wybór wartości epsilon
zależy od kontekstu i wymaganej precyzji.
BigDecimal
: W niektórych przypadkach, gdzie wymagana jest bardzo wysoka precyzja, można użyć klasy BigDecimal
. Pozwala ona na dokładne porównywanie liczb zmiennoprzecinkowych, ale kosztem wydajności i zwiększonej złożoności kodu.Operator ==
i !=
: Te operatory porównują referencje, a nie faktyczną zawartość napisów. Oznacza to, że ==
zwróci true
tylko wtedy, gdy obie zmienne odnoszą się do tego samego obiektu w pamięci. Jest to zwykle niewłaściwe do porównywania zawartości napisów.
Metoda equals
: Ta metoda porównuje zawartość dwóch napisów i jest właściwym sposobem porównywania napisów pod kątem ich wartości. Metoda equals
jest czuła na wielkość liter.
Metoda equalsIgnoreCase
: Podobnie jak equals
, ale ignoruje różnice w wielkości liter.
Object.equals
: Jest to ogólna metoda porównywania w Javie. Można jej używać do porównywania napisów, gdy oba mogą być null
. To podejście jest bezpieczne, ponieważ unika ryzyka wystąpienia wyjątku NullPointerException
.
public class StringCompareExample {
public static void main(String[] args) {
String str1 = new String("Hello");
String str2 = new String("Hello");
String str3 = "Hello";
String str4 = null;
// Porównywanie za pomocą ==
System.out.println("Porównanie za pomocą == : " + (str1 == str2)); // Zwróci false
System.out.println("Porównanie referencji : " + (str3 == "Hello")); // Zwróci true
// Porównywanie za pomocą equals
System.out.println("Porównanie za pomocą equals : " + str1.equals(str2)); // Zwróci true
// Porównywanie ignorując wielkość liter
System.out.println("Porównanie za pomocą equalsIgnoreCase : " + str1.equalsIgnoreCase("hello")); // Zwróci true
// Bezpieczne porównywanie za pomocą Object.equals
System.out.println("Bezpieczne porównanie : " + java.util.Objects.equals(str1, str4)); // Zwróci false
}
}
hashCode()
Metoda hashCode()
jest fundamentalną częścią klasy Object
i ma kluczowe znaczenie w działaniu kolekcji takich jak HashSet
, HashMap
i Hashtable
, które wykorzystują hashowanie do efektywnego przechowywania i odzyskiwania obiektów.
Metoda hashCode()
zwraca wartość całkowitą, która jest używana jako kod hash obiektu. Ten kod hash służy do określenia miejsca obiektu w strukturze danych opartej na hashowaniu.
Kontrakt hashCode()
:
hashCode()
musi pozostać stała dla danego obiektu podczas jego życia, o ile informacje użyte w porównaniach equals()
na tym obiekcie nie ulegną zmianie.equals()
, wtedy ich kody hash zwrócone przez hashCode()
również muszą być równe.equals()
, preferowane jest, aby ich kody hash były różne. Jednak nie jest to wymóg i różne obiekty mogą mieć tę samą wartość hash (co prowadzi do tzw. kolizji hash).hashCode
dla typów prymitywnychint
, short
, byte
, char
, boolean
: Dla tych typów, wartość prymitywna może być bezpośrednio użyta jako wartość hashcode, ponieważ sama w sobie jest wystarczająco unikalna. Na przykład, dla typu int
, wartość hashcode to po prostu wartość tego int
.long
: Typ long
ma 64 bity, więc nie można go bezpośrednio przypisać jako 32-bitowy hashcode. Można użyć następującej metody:float
i double
:float
, można użyć metody Float.floatToIntBits
:double
, proces jest bardziej skomplikowany, ponieważ double
jest 64-bitowy. Najpierw konwertujesz go na long
za pomocą Double.doubleToLongBits
, a następnie postępujesz podobnie jak w przypadku long
:double doubleValue = 123.456;
long longBits = Double.doubleToLongBits(doubleValue);
int hash = (int)(longBits ^ (longBits >>> 32));
Dla boolean
: Można po prostu przypisać 1 dla true
i 0 dla false
:
Wykorzystanie java.util.Objects.hash
: Jeśli chcesz obliczyć hashcode dla kombinacji kilku wartości, w tym typów prymitywnych, możesz użyć Objects.hash
:
Zadanie 1. Klasa School
(pol. Szkoła)
A. Klasa School
powinna być umieszczona w pakiecie education
.
B. Klasa powinna posiadać prywatne pola:
name
, (nazwa szkoły), typ String
address
, (adres zawierający ulicę, numer posesji, kod pocztowy i miejscowość), typ String
students
, (liczba uczniów), typ int
C. Napisz trzyargumentowy konstruktor tej klasy. Kolejność argumentów powinna być taka sama jak w punkcie B. Zapewnij niezależnie warunki sprawdzające poprawność:
""
) - wtedy ustaw adres WMII czyli "ul. Słoneczna 54, 10-710 Olsztyn
D. Napisz metody typu getter
i setter
dla wszystkich pól. Pamiętaj by sprawdzić kryteria podane w konstruktorze. W przypadku błędny argumentów, metoda ma nic nie robić.
E. Nadpisz metodę toString
tak, aby zwracała napis z reprezentacją obiektu. Na początku powinna być nazwa klasy - potem wartości wszystkich pól. Powinno odbyć się do według schematu (zwróć uwagę na wielkość znaków i znaki interpunkcyjne, wszystko w jednej linii):
[NazwaKlasy]: Name: [name]. Address: [address]. Number of students: [students].
lub jeśli nazwa nie jest ustalona (jest pustym napisem lub nullem):
[NazwaKlasy]: Address: [address]. Number of students: [students].
F. Nadpisz metodę equals
. Dwie szkoły są sobie “równe” wtedy i tylko wtedy, gdy mają ten sam adres. Nadpisz metodę hashCode()
, która generuje kod hash dla odpowiedniego obiektu. Metoda ta powinna być zgodna z metodą equals()
,
G. Napisz metodę (zwykłą) recruitment
(pol. rekrutacja) z argumentem typu int
. Metoda powiększa pole students
o wartość przekazaną przez argument. Jeśli po powiększeniu pole students
będzie większe niż 500, to ustaw je na 500.
Zadanie 2. Klasa University
(pol. uniwersytet)
A. Klasa University
powinna być umieszczona w pakiecie education
w innym pliku niż klasa School
.
B. Klasa University
dziedziczy po klasie School
. Klasa powinna posiadać prywatne pola:
type
typu String
(np. rodzaj np. rolniczy - agricultural, politechnika - university of technology, itp)studies
typu int
(liczba kierunków)C. Napisz pięcio-argumentowy konstruktor tej klasy. Kolejność argumentów powinna być taka sama jak w punkcie B (najpierw z klasy bazowej, potem pochodnej). Zapewnij niezależnie warunki sprawdzające poprawność dodatkowo:
""
) - w przeciwnym wypadku ustaw "university of technology"
D. Napisz metody typu getter
i setter
dla wszystkich pól. Pamiętaj by sprawdzić kryteria podane w konstruktorze. W przypadku błędnych argumentów, metoda ma nic nie robić.
E. Nadpisz metodę toString
tak, aby zwracała napis z reprezentacją obiektu. Na początku powinna być nazwa klasy - potem wartości wszystkich pól. Powinno odbyć się do według schematu (zwróć uwagę na wielkość znaków i znaki interpunkcyjne, zwróć uwagę na łamanie linii):
[NazwaKlasy]: Name: [name]. Address: [address]. Number of students: [students].
Type: [type]. Number of fields of study: [studies].
lub jeśli nazwa nie jest ustalona (jest pustym napisem lub nullem):
[NazwaKlasy]: Address: [address]. Number of students: [students].
Type: [type]. Number of fields of study: [studies].
F. Nadpisz metodę (zwykłą) recruitment
z argumentem typu int
. Metoda powiększa pole students
o wartość przekazaną przez argument. Jeśli po powiększeniu pole students
będzie większe niż 500, to ustaw je na 500. Dodatkowo zwiększ liczbę kierunków o 1/10 przekazanego argumentu (w zaokrągleniu lub obcięciu do liczby całkowitej).
G. Nadpisz metodę equals
. Dwa obiekty są sobie “równe” wtedy i tylko wtedy, gdy mają ten sam adres oraz tą samą liczbę kierunków. Nadpisz metodę hashCode()
, która generuje kod hash dla odpowiedniego obiektu. Metoda ta powinna być zgodna z metodą equals()
,
Zadanie 3. Klasa TestSchool
(pol. klasa testująca dla szkoły)
A. Klasę TestSchool
umieść bezpośrednio w katalogu src
poza pakietami. Umieść w tej klasie tylko metodę main
.
B. Wywołaj wszystkie metody z zadania 1 i 2 (np. zwykłe, statyczne, konstruktory). Wywołanie getter
-ów i setter
-ów nie jest obowiązkowe.
W Javie często, ale nie zawsze:
override
- nadpisywanie - zwykłe metodyhidding
- przesłanie - statyczne metody (brak użycia adnotacji)class BaseClass {
public static void staticMethod() {
System.out.println("Static method in BaseClass");
}
public void instanceMethod() {
System.out.println("Instance method in BaseClass");
}
}
class DerivedClass extends BaseClass {
public static void staticMethod() {
System.out.println("Static method in DerivedClass");
}
@Override
public void instanceMethod() {
System.out.println("Instance method in DerivedClass");
}
}