Wykład 10
W Javie można podpiąć (implementować) kilka interfejsów do jednej klasy. Jest to jedna z fundamentalnych cech języka Java, która pozwala na wielokrotną implementację interfejsów, co umożliwia wielokrotne dziedziczenie zachowań. W przeciwieństwie do dziedziczenia klas, gdzie Java pozwala na dziedziczenie tylko z jednej klasy bazowej, klasa może implementować dowolną liczbę interfejsów.
public interface InterfaceA {
void methodA();
}
public interface InterfaceB {
void methodB();
}
public class MyClass implements InterfaceA, InterfaceB {
@Override
public void methodA() {
// Implementacja metody z InterfaceA
}
@Override
public void methodB() {
// Implementacja metody z InterfaceB
}
}
Jeśli dwie metody abstrakcyjne w różnych interfejsach mają tę samą sygnaturę, klasa implementująca te interfejsy musi dostarczyć tylko jedną implementację tej metody. Język Java traktuje je jako jedną i tę samą metodę.
Jeśli dwa interfejsy definiują metody domyślne (default) o tej samej sygnaturze, klasa implementująca te interfejsy musi przesłonić tę metodę, aby rozwiązać konflikt.
public interface InterfaceA {
default void metoda() {
System.out.println("InterfaceA metoda");
}
}
public interface InterfaceB {
default void metoda() {
System.out.println("InterfaceB metoda");
}
}
public class MyClass implements InterfaceA, InterfaceB {
@Override
public void metoda() {
InterfaceA.super.metoda(); // Wywołanie konkretnej metody domyślnej
// lub własna implementacja
}
}
interface MyInterfaceA {
void methodA();
}
// Interfejs dziedziczący
interface MyInterfaceB extends MyInterfaceA {
void methodB();
}
// Klasa implementująca InterfejsB
class Klasa implements MyInterfaceB {
public void methodA() {
// Implementacja metody z InterfejsA
}
public void methodB() {
// Implementacja metody z InterfejsB
}
}
Rzutowanie obiektów na interfejs w Javie to proces, w którym obiekt klasy, która implementuje dany interfejs, jest traktowany jako instancja tego interfejsu. Jest to często używane, gdy chcemy skorzystać z metod określonych w interfejsie, nie zwracając uwagi na konkretną klasę obiektu. Rzutowanie jest szczególnie ważne w kontekście polimorfizmu, gdzie obiekty różnych klas mogą być traktowane jako instancje wspólnego interfejsu.
sort
z klasy Arrays
import java.util.Arrays;
public class TestArray {
public static void main(String[] args) {
int[] intArray = {5, 2, 8, -3, 1};
Arrays.sort(intArray);
System.out.println(Arrays.toString(intArray));
double[] doubleArray = {3.14, -1.59, 2.65, 3.58};
Arrays.sort(doubleArray);
System.out.println(Arrays.toString(doubleArray));
String[] stringArray = {"Banana", "apple", "Cherry", "Date"};
Arrays.sort(stringArray);
System.out.println(Arrays.toString(stringArray));
}
}
Interfejs Comparable
w Javie jest używany do definiowania naturalnego porządku obiektów danej klasy. Kiedy klasa implementuje interfejs Comparable
, oznacza to, że obiekty tej klasy mogą być porównywane ze sobą, co jest szczególnie użyteczne do sortowania.
Na wykładzie będzie omawiana generyczna wersja Comparable<T>
.
Interfejs Comparable
zawiera jedną metodę, którą należy zaimplementować:
Metoda compareTo(T o)
zwraca:
o
.o
.Metoda sort
z klasy Arrays
może być użyta do sortowania tablic obiektów, które implementują interfejs Comparable
. Sortowanie jest wtedy przeprowadzane zgodnie z naturalnym porządkiem określonym przez metodę compareTo
.
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int compareTo(Person other) {
return this.age - other.age;
}
@Override
public String toString() {
return name + " - " + age;
}
// Gettery, settery, inne metody...
}
Comparable
jest ważne?Definiowanie Naturalnego Porządku: Implementacja Comparable
pozwala klasom określić, co znaczy, że jeden obiekt jest „większy”, „mniejszy” lub „równy” innemu.
Ułatwia Sortowanie i Porównywanie: Dzięki Comparable
, możemy używać metod takich jak Arrays.sort
lub kolekcji, które automatycznie sortują elementy (np. TreeSet
), bez konieczności określania dodatkowego komparatora.
Większa Elastyczność i Czytelność Kodu: Implementacja Comparable
w klasie sprawia, że porównanie i sortowanie obiektów tej klasy staje się bardziej intuicyjne i zintegrowane z naturalnymi operacjami Javy.
int
public class TestMyNumber {
public static void main(String[] args) {
MyNumber[] numbers = {
new MyNumber(5),
new MyNumber(2),
new MyNumber(8),
new MyNumber(-3),
new MyNumber(1)
};
Arrays.sort(numbers);
System.out.println(Arrays.toString(numbers));
}
}
class MyNumber implements Comparable<MyNumber> {
private int value;
public MyNumber(int value) {
this.value = value;
}
@Override
public String toString() {
return String.valueOf(value);
}
@Override
public int compareTo(MyNumber other) {
//
}
}
@Override
public int compareTo(MyNumber o) {
if (this.value < o.value) return -1;
if (this.value > o.value) return 1;
return 0;
}
Integer.compare
Objects.compare
: W Javie 7 i nowszych, można użyć Objects.compare
w połączeniu z Comparator
:@Override
public int compareTo(MyNumber o) {
return Objects.compare(this.value, o.value, Integer::compare);
}
double
import java.util.Arrays;
public class TestMyDouble {
public static void main(String[] args) {
MyDouble[] numbers = {
new MyDouble(5),
new MyDouble(2),
new MyDouble(8),
new MyDouble(-3),
new MyDouble(1)
};
Arrays.sort(numbers);
System.out.println(Arrays.toString(numbers));
}
}
class MyDouble implements Comparable<MyDouble> {
private double value;
public MyDouble(double value) {
this.value = value;
}
@Override
public String toString() {
return String.valueOf(value);
}
@Override
public int compareTo(MyDouble o) {
return Double.compare(this.value, o.value);
}
}
String
import java.util.Arrays;
public class TestMyString {
public static void main(String[] args) {
MyString[] strings = new MyString[3];
strings[0] = new MyString("Hello");
strings[1] = new MyString("World");
strings[2] = new MyString("Java");
System.out.println(Arrays.toString(strings));
Arrays.sort(strings);
System.out.println(Arrays.toString(strings));
}
}
class MyString implements Comparable<MyString> {
private String value;
public MyString(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
@Override
public int compareTo(MyString o) {
return value.compareTo(o.value);
}
}
Inne sposoby:
equals
i compareTo
W Javie, gdy implementujesz metody equals
i compareTo
w konkretnej klasie, istnieje ważna zasada, która powinna być zachowana, znana jako zgodność equals i compareTo. Zgodność ta oznacza, że wynik metody equals
powinien być zgodny z wynikiem metody compareTo
.
compareTo
zwraca 0 (co wskazuje, że obiekty są równe pod względem porządku), to equals
powinno również zwrócić true
, sugerując, że obiekty są równoważne.equals
zwraca true
(wskazując, że obiekty są równoważne), to compareTo
powinno zwrócić 0, wskazując, że są one równe pod względem porządku.equals
.public class Person implements Comparable<Person> {
private String name;
private int age;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Person person = (Person) obj;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int compareTo(Person other) {
int nameComparison = name.compareTo(other.name);
if (nameComparison != 0) {
return nameComparison;
}
return Integer.compare(age, other.age);
}
}
Uwaga! null
nie należy do żadnej klasy. obj.compareTo(null)
wyrzuca wyjątek, ale obj.equals(null)
zwraca false
.
Większość implementujących interfejs Comparable klas API Javy honoruje tę zasadę, ale jednym z ważnych wyjątków jest klasa BigDecimal
.
import java.math.BigDecimal;
import java.util.Arrays;
public class TestBigDecimal {
public static void main(String[] args) {
BigDecimal[] numbers = new BigDecimal[3];
numbers[0] = new BigDecimal("1.0");
numbers[1] = new BigDecimal("1.00");
numbers[2] = new BigDecimal("1.000");
System.out.println(Arrays.toString(numbers));
Arrays.sort(numbers);
System.out.println(Arrays.toString(numbers));
}
}
Comparable<T>
a dziedziczenieJeśli klasa bazowa X
implementuje interfejs Comparable<X>
, to klasa pochodna Y
dziedzicząca po X
nie może implementować interfejsu Comparable<Y>
.
Można dodać w klasie pochodnej Y
implementację interfejsu Comparable<X>
.
Wyrzucanie ClassCastException
, jeśli typy są różne
public class Employee implements Comparable<Employee> {
private String name;
private int salary;
// Konstruktory, gettery, settery itp.
@Override
public int compareTo(Employee other) {
// Porównanie na podstawie nazwiska
int nameComparison = this.name.compareTo(other.name);
if (nameComparison != 0) {
return nameComparison;
}
// Porównanie na podstawie wynagrodzenia
return Integer.compare(this.salary, other.salary);
}
}
public class Manager extends Employee {
private int bonus;
// Konstruktory, gettery, settery itp.
@Override
public int compareTo(Employee other) {
if (other.getClass() != Manager.class) {
throw new ClassCastException("Nie można porównać Managera z innym typem Employee");
}
Manager otherManager = (Manager) other;
int baseComparison = super.compareTo(otherManager);
if (baseComparison != 0) {
return baseComparison;
}
// Porównanie na podstawie bonusu
return Integer.compare(this.bonus, otherManager.bonus);
}
}
Obiekty klasy pochodnej są uzupełniane o dodatkowe pole.
instanceof
używane jest do zachowania hierarchii w porządku.
public class Employee implements Comparable<Employee> {
private String name;
private int salary;
// Konstruktory, gettery, settery itp.
@Override
public int compareTo(Employee other) {
// Porównanie na podstawie nazwiska
int nameComparison = this.name.compareTo(other.name);
if (nameComparison != 0) {
return nameComparison;
}
// Porównanie na podstawie wynagrodzenia
return Integer.compare(this.salary, other.salary);
}
}
public class Manager extends Employee {
private int bonus;
// Konstruktory, gettery, settery itp.
@Override
public int compareTo(Employee other) {
if (other instanceof Manager) {
Manager otherManager = (Manager) other;
int baseComparison = super.compareTo(otherManager);
if (baseComparison != 0) {
return baseComparison;
}
// Porównanie na podstawie bonusu
return Integer.compare(this.bonus, otherManager.bonus);
}
return super.compareTo(other);
}
}