1. Czym jest interfejs?
Jeśli klasa abstrakcyjna to plan domu, to interfejs jest raczej umową lub „kontraktem” na wykonanie określonych prac. Na przykład, jeśli dzwonisz po hydraulika, oczekujesz, że będzie potrafił „naprawić kran” i „usunąć przeciek”. Jak to zrobi – nie obchodzi cię to; najważniejsze, żeby był rezultat. W Javie interfejs to właśnie taki kontrakt: mówi, jakie metody muszą zostać zaimplementowane, ale nie mówi, jak.
Krótko i na temat:
Interfejs to specjalny typ w Javie, który definiuje zestaw metod, jakie muszą zaimplementować klasy, które się na ten interfejs „piszą”.
- Interfejs opisuje tylko „co robić”, ale nie „jak to robić”.
- Do Java 8 interfejsy nie zawierały implementacji metod.
- Interfejs to czysty kontrakt: jeśli klasa implementuje interfejs, musi zaimplementować wszystkie jego metody.
Dlaczego to wygodne?
- Można „nałożyć” na klasę kilka interfejsów – czyli nadać jej kilka „ról”.
- Pozwala budować elastyczne i rozszerzalne architektury, w których klasy mogą realizować różne możliwości.
- Interfejsy są szeroko używane w standardowej bibliotece Javy (np. Comparable, Serializable, Runnable itp.).
2. Składnia deklaracji interfejsu
Zadeklarować interfejs w Javie jest bardzo prosto. Służy do tego słowo kluczowe interface. Do Java 8 metody interfejsu były domyślnie traktowane jako public abstract, nawet jeśli nie było to napisane jawnie. To znaczy: metoda musi być zaimplementowana w klasie, która implementuje interfejs.
Przykład interfejsu
public interface Movable {
void move(int x, int y);
}
- Tutaj zadeklarowaliśmy interfejs Movable (można przetłumaczyć jako „ruchomy” lub „potrafiący się poruszać”).
- Wewnątrz zadeklarowano metodę move(int x, int y). Nie ma ciała metody – jest tylko sygnatura. To właśnie „kontrakt”: „jeśli implementujesz Movable – zaimplementuj też move”.
Cechy składni
- Metody w interfejsie nie zawierają ciała (do Java 8).
- Wszystkie metody interfejsu są domyślnie public abstract (nie trzeba pisać tego jawnie).
- Interfejs może zawierać tylko stałe (public static final), a nie zwykłe pola.
Przykład ze stałą
public interface Constants {
int MAX_SPEED = 100; // public static final domyślnie
}
3. Różnica między interfejsem a klasą
Interfejs to nie klasa! Zobaczmy, na czym polega różnica.
| Klasa (w tym abstrakcyjna) | Interfejs |
|---|---|
| Może zawierać pola (stan) | Nie może zawierać pól (tylko stałe) |
| Może zawierać implementację metod | Do Java 8 interfejsy nie mogły zawierać implementacji, tylko sygnatury |
| Może być tworzona przez new (jeśli nie jest abstrakcyjna) | Nie można utworzyć bezpośrednio |
| Pojedyncze dziedziczenie (extends) | Klasa może implementować wiele interfejsów (implements) |
| Służy do „czym to jest” | Służy do „co potrafi robić” |
Klasa implementuje interfejs za pomocą implements
public class Robot implements Movable {
@Override
public void move(int x, int y) {
System.out.println("Robot przemieszcza się do punktu (" + x + ", " + y + ")");
}
}
- Klasa Robot implementuje interfejs Movable.
- Słowo kluczowe implements – oznacza „implementuje”.
- Trzeba zaimplementować wszystkie metody interfejsu (w przeciwnym razie będzie błąd kompilacji).
- Nie zapominaj o adnotacji @Override – nie jest obowiązkowa, ale pomaga kompilatorowi i tobie uniknąć błędów.
4. Przykład użycia interfejsu
Napiszmy prosty przykład, aby zobaczyć, jak to działa w praktyce. Załóżmy, że mamy interfejs Movable i chcemy, aby różne klasy mogły się „poruszać”: robot, samochód, zwierzę...
Krok 1. Deklarujemy interfejs
public interface Movable {
void move(int x, int y);
}
Krok 2. Implementujemy interfejs w klasie
public class Robot implements Movable {
@Override
public void move(int x, int y) {
System.out.println("Robot przemieszcza się do punktu (" + x + ", " + y + ")");
}
}
Krok 3. Używamy interfejsu
public class Main {
public static void main(String[] args) {
Movable m = new Robot(); // Zmienna typu interfejsu!
m.move(10, 20); // Wypisze: Robot przemieszcza się do punktu (10, 20)
}
}
Zwróć uwagę
- Możemy zadeklarować zmienną typu interfejsu (Movable m) i przypisać jej obiekt klasy, która implementuje ten interfejs (new Robot()).
- To pozwala pisać uniwersalny kod, który działa z dowolnym „ruchomym” obiektem, nie wiedząc, jakiej klasy on naprawdę jest.
Błąd kompilacji, jeśli nie zaimplementujesz metody
Error: Class 'Robot' must either be declared abstract or implement abstract method 'move(int, int)' in 'Movable'
Na tym polega siła interfejsu – gwarantuje, że wszystkie implementujące klasy będą miały potrzebne metody.
5. Interfejsy w standardowej bibliotece Javy
Interfejsy to nie tylko „szkolny” temat. Są intensywnie wykorzystywane w całej standardowej bibliotece Javy. Oto kilka przykładów:
- Comparable<T> – interfejs do porównywania obiektów (np. podczas sortowania).
- Runnable – interfejs do uruchamiania wątku.
- Serializable – interfejs-znacznik wskazujący, że obiekt można serializować.
- List, Set, Map – interfejsy kolekcji.
Przykład: Comparable
public class Person implements Comparable<Person> {
String name;
int age;
// Konstruktor i inne metody...
@Override
public int compareTo(Person other) {
return this.age - other.age;
}
}
Teraz obiekty Person można sortować, ponieważ implementują interfejs Comparable.
6. Schemat wizualny: jak działa interfejs
+-------------------+ +-------------------+
| interface | | class |
| Movable |<--------| Robot |
|-------------------| |-------------------|
| +move(int, int) | | +move(int, int) |
+-------------------+ +-------------------+
- Strzałka pokazuje, że klasa Robot implementuje interfejs Movable.
- Interfejs określa tylko „co ma być”, klasa – „jak to działa”.
Analogią z życia
Interfejs w Javie jest jak prawo jazdy. Jeśli masz prawo jazdy (implementujesz interfejs „Kierowca”), to znaczy, że umiesz prowadzić samochód. Jak dokładnie to robisz – to już szczegóły: ktoś jeździ ostrożnie, ktoś szybko, ale najważniejsze – podpisałeś się pod tym, że umiesz to robić.
7. Praktyczny przykład: kontynuujemy naszą aplikację
public interface Movable {
void move(int x, int y);
}
public class Animal implements Movable {
protected String name;
public Animal(String name) {
this.name = name;
}
@Override
public void move(int x, int y) {
System.out.println(name + " przemieszcza się do punktu (" + x + ", " + y + ")");
}
}
public class Robot implements Movable {
private String model;
public Robot(String model) {
this.model = model;
}
@Override
public void move(int x, int y) {
System.out.println("Robot " + model + " jedzie do współrzędnych (" + x + ", " + y + ")");
}
}
public class Main {
public static void main(String[] args) {
Movable[] movables = {
new Animal("Kitty"),
new Robot("R2D2")
};
for (Movable m : movables) {
m.move(5, 10);
}
}
}
Wynik:
Kitty przemieszcza się do punktu (5, 10)
Robot R2D2 jedzie do współrzędnych (5, 10)
Utworzyliśmy tablicę obiektów typu Movable. Mogą się w niej znaleźć dowolne obiekty implementujące ten interfejs! Kod w pętli nie wie, z czym konkretnie ma do czynienia – ze zwierzęciem czy robotem – ale wie, że każdy z nich potrafi wywołać move.
8. Typowe błędy przy pracy z interfejsami
Błąd nr 1: próba bezpośredniego utworzenia obiektu interfejsu. Interfejs to kontrakt, a nie konkretny obiekt. Nie można napisać new Movable() – to spowoduje błąd kompilacji. Trzeba tworzyć obiekty klas implementujących interfejs.
Błąd nr 2: nie zaimplementowano wszystkich metod interfejsu. Jeśli klasa implementuje interfejs, ale nie implementuje wszystkich jego metod, kompilator zgłosi błąd: „Class must either be declared abstract or implement abstract method ...”. Jeśli nie chcesz implementować wszystkich metod – oznacz klasę jako abstract.
Błąd nr 3: zapomniano o modyfikatorach dostępu. Metody interfejsu są zawsze public (nawet jeśli nie jest to jawnie napisane). W klasie implementującej nie można obniżyć poziomu dostępu, dlatego metoda również musi być public.
Błąd nr 4: próba dodania zwykłych pól do interfejsu. W interfejsie można deklarować tylko stałe (public static final). Zwykłych (niestatycznych) pól nie można dodać.
GO TO FULL VERSION