Wykład 7
equalsMetoda 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 Stringaddress, (adres zawierający ulicę, numer posesji, kod pocztowy i miejscowość), typ Stringstudents, (liczba uczniów), typ intC. 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 OlsztynD. 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");
}
}