JavaRush /Blog Java /Random-PL /Przerwa kawowa #140. Klasy abstrakcyjne i interfejsy w Ja...

Przerwa kawowa #140. Klasy abstrakcyjne i interfejsy w Javie

Opublikowano w grupie Random-PL
Źródło: InfoWorld Dziś dowiesz się, w jakich przypadkach programista powinien używać klasy abstrakcyjnej, a w jakich – interfejsu. Zidentyfikujemy także różnice pomiędzy tymi elementami języka Java i sposobami ich wykorzystania w programach. Przerwa kawowa #140.  Klasy abstrakcyjne i interfejsy w Javie - 1Klasy abstrakcyjne i interfejsy są dość powszechne w kodzie Java, a nawet w samym zestawie Java Development Kit (JDK). Każdy z tych elementów służy innemu celowi:
  • Interfejs to konstrukcja w języku Java, która pomaga implementować metody abstrakcyjne i stałe statyczne.
  • Klasy abstrakcyjne są podobne do zwykłych klas, z tą różnicą, że mogą zawierać metody abstrakcyjne, czyli metody bez treści. Nie można tworzyć klas abstrakcyjnych.
Wielu programistów uważa, że ​​interfejsy i klasy abstrakcyjne są podobne, jednak w rzeczywistości nie jest to do końca prawdą. Przyjrzyjmy się głównym różnicom między nimi.

Co to jest interfejs

W swojej istocie interfejs jest umową, zatem od implementacji zależy określenie celu jego utworzenia. Interfejs nie może używać modyfikowalnych zmiennych instancji, może używać tylko zmiennych końcowych.

Kiedy używać interfejsów

Interfejsy są bardzo przydatne do oddzielania kodu i implementowania polimorfizmu. Widzimy to w JDK z interfejsem List :
public interface List<E> extends Collection<E> {

    int size();
    boolean isEmpty();
    boolean add(E e);
    E remove(int index);
    void clear();
}
Jak zapewne zauważyłeś, ten kod, choć krótki, ma dość opisowy charakter. Łatwo możemy zobaczyć sygnaturę metody, która zostanie wykorzystana do implementacji metod w interfejsie przy użyciu konkretnej klasy. Interfejs List zawiera kontrakt, który może zostać zaimplementowany przez klasy ArrayList , Vector , LinkedList i inne. Aby użyć polimorfizmu, możemy po prostu zadeklarować typ naszej zmiennej za pomocą Listy , a następnie wybrać dowolną z dostępnych instancji. Oto kolejny przykład:
List list = new ArrayList();
System.out.println(list.getClass());

 List list = new LinkedList();
 System.out.println(list.getClass());
Dane wyjściowe to:
klasa java.util.ArrayList klasa java.util.LinkedList
W tym przypadku metody implementacji ArrayList , LinkedList i Vector są różne, co jest doskonałym scenariuszem wykorzystania interfejsu. Jeśli zauważysz, że wiele klas należy do klasy nadrzędnej z tymi samymi działaniami metod, ale innym zachowaniem. W takich sytuacjach zaleca się skorzystanie z interfejsu. Następnie przyjrzyjmy się kilku opcjom korzystania z interfejsów.

Zastąpienie metody interfejsu

Jak już wiemy, interfejs jest rodzajem kontraktu, który musi zostać zaimplementowany przez konkretną klasę. Metody interfejsu są domyślnie abstrakcyjne i wymagają konkretnej implementacji klasy. Oto przykład:
public class OverridingDemo {
  public static void main(String[] args) {
    Challenger challenger = new JavaChallenger();
    challenger.doChallenge();
  }
}

interface Challenger {
  void doChallenge();
}

class JavaChallenger implements Challenger {
  @Override
  public void doChallenge() {
    System.out.println("Challenge done!");
  }
}
Wniosek:
Wyzwanie wykonane!
Należy pamiętać, że metody interfejsu są domyślnie abstrakcyjne. Oznacza to, że nie musimy jawnie deklarować ich jako abstrakcyjnych.

Stałe zmienne

Kolejną zasadą, o której należy pamiętać, jest to, że interfejs może zawierać tylko zmienne stałe. Oto przykład:
public class Challenger {

  int number = 7;
  String name = "Java Challenger";

}
Tutaj obie zmienne są ukryte final i static . Oznacza to, że są one stałe, niezależne od instancji i nie można ich zmienić. Teraz spróbujemy zmienić zmienne w interfejsie Challengera , powiedzmy tak:
Challenger.number = 8;
Challenger.name = "Another Challenger";
Spowoduje to błąd kompilacji:
Nie można przypisać wartości do zmiennej końcowej „liczba” Nie można przypisać wartości do zmiennej końcowej „nazwa”

Metody domyślne

Kiedy w Javie 8 wprowadzono metody domyślne, niektórzy programiści myśleli, że będą one takie same jak klasy abstrakcyjne. Nie jest to jednak prawdą, ponieważ interfejsy nie mogą mieć stanu. Metoda domyślna może mieć implementację, ale metody abstrakcyjne nie. Metody domyślne są wynikiem innowacji związanych z wyrażeniami lambda i strumieniami, ale musimy z nich korzystać ostrożnie. Metodą w JDK korzystającą z metody domyślnej jest forEach() , która jest częścią interfejsu Iterable . Zamiast kopiować kod do każdej implementacji Iterable , możemy po prostu ponownie użyć metody forEach :
default void forEach(Consumer<? super T> action) {
  // Code implementation here...
Dowolna implementacja Iterable może używać metody forEach() bez konieczności implementowania nowej metody. Możemy następnie ponownie użyć kodu przy użyciu metody domyślnej. Stwórzmy własną metodę domyślną:
public class DefaultMethodExample {

  public static void main(String[] args) {
    Challenger challenger = new JavaChallenger();
    challenger.doChallenge();
  }

}

class JavaChallenger implements Challenger { }

interface Challenger {

  default void doChallenge() {
    System.out.println("Challenger doing a challenge!");
  }
}
Wynik:
Challenger podejmuje wyzwanie!
W odniesieniu do metod domyślnych należy pamiętać, że każda taka metoda wymaga wdrożenia. Metoda domyślna nie może być statyczna. Przejdźmy teraz do klas abstrakcyjnych.

Istota klasy abstrakcyjnej

Klasy abstrakcyjne mogą mieć stan ze zmiennymi instancji. Oznacza to, że zmienna instancji może być używana i modyfikowana. Oto przykład:
public abstract class AbstractClassMutation {

  private String name = "challenger";

  public static void main(String[] args) {
    AbstractClassMutation abstractClassMutation = new AbstractClassImpl();
    abstractClassMutation.name = "mutated challenger";
    System.out.println(abstractClassMutation.name);
  }

}

class AbstractClassImpl extends AbstractClassMutation { }
Wniosek:
zmutowany pretendent

Metody abstrakcyjne w klasach abstrakcyjnych

Podobnie jak interfejsy, klasy abstrakcyjne mogą mieć metody abstrakcyjne. Metoda abstrakcyjna to metoda bez ciała. W przeciwieństwie do interfejsów, metody abstrakcyjne w klasach abstrakcyjnych muszą być jawnie zadeklarowane jako abstrakcyjne. Oto przykład:
public abstract class AbstractMethods {

  abstract void doSomething();

}
A oto próba zadeklarowania metody bez implementacji i bez słowa kluczowego abstrakcyjnego :
public abstract class AbstractMethods {
   void doSomethingElse();
}
Niestety skutkuje to błędem kompilacji:
Brak treści metody lub zadeklaruj streszczenie

Kiedy używać klas abstrakcyjnych

Klasa abstrakcyjna jest zalecana, gdy trzeba zaimplementować stan zmienny. Na przykład środowisko Java Collections Framework zawiera klasę AbstractList , która korzysta ze stanu zmiennych. W przypadkach, gdy nie musisz utrzymywać stanu klasy, zwykle lepiej jest użyć interfejsu.

Różnice pomiędzy klasami abstrakcyjnymi i interfejsami

Z punktu widzenia programowania obiektowego główna różnica między interfejsem a klasą abstrakcyjną polega na tym, że interfejs nie może mieć stanu, podczas gdy klasa abstrakcyjna może mieć stan ze zmiennymi instancji. Inną kluczową różnicą jest to, że klasy mogą implementować więcej niż jeden interfejs, ale mogą rozszerzać tylko jedną klasę abstrakcyjną. Rozwiązanie to opiera się na fakcie, że wielokrotne dziedziczenie (rozszerzenie więcej niż jednej klasy) może prowadzić do zakleszczenia kodu. Twórcy języka Java postanowili tego uniknąć. Inna różnica polega na tym, że interfejsy można implementować za pomocą klas lub rozszerzać za pomocą interfejsów, ale klasy można tylko rozszerzać. Należy zauważyć, że wyrażeń lambda można używać tylko z interfejsem funkcjonalnym (co oznacza interfejs z tylko jedną metodą), natomiast klasy abstrakcyjne z tylko jedną metodą abstrakcyjną nie mogą używać wyrażeń lambda. Oto więcej różnic między klasami abstrakcyjnymi i interfejsami. Interfejs:
  • Może mieć tylko końcowe zmienne statyczne. Interfejs nigdy nie może zmienić swojego stanu.
  • Klasa może implementować wiele interfejsów.
  • Można go zaimplementować za pomocą słowa kluczowego implements. Interfejs może rozszerzać inny interfejs.
  • Metody mogą używać tylko statycznych pól końcowych, parametrów lub zmiennych lokalnych.
  • Tylko interfejsy funkcjonalne mogą używać funkcji lambda w Javie.
  • Nie może mieć konstruktora.
  • Może mieć metody abstrakcyjne.
  • Może mieć metody domyślne i statyczne (wprowadzone w Javie 8).
  • Może mieć metody prywatne z implementacją (wprowadzone w Javie 9).
Klasy abstrakcyjne:
  • Może mieć dowolną instancję lub zmienne statyczne, zmienne lub niezmienne.
  • Klasa może rozszerzać tylko jedną klasę abstrakcyjną.
  • Może mieć instancję modyfikowalnych pól, parametrów lub zmiennych lokalnych.
  • Klasy abstrakcyjne posiadające tylko jedną metodę abstrakcyjną nie mogą używać wyrażeń lambda.
  • Może mieć konstruktora.
  • Może mieć dowolne metody.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION