CodeGym /Blog Java /Poland /Klasy abstrakcyjne w Java
Autor
Vasyl Malik
Senior Java Developer at CodeGym

Klasy abstrakcyjne w Java

Opublikowano w grupie Poland
Cześć! Na ubiegłych lekcjach poznaliśmy interfejsy i zorientowaliśmy się, do czego służą. Dzisiejszy temat będzie przypominał poprzedni. Porozmawiajmy o przykładach klas abstrakcyjnych w Java.

Dlaczego klasy nazywane są "abstrakcyjnymi"

Prawdopodobnie pamiętasz, czym jest "abstrakcja" - to już omówiliśmy. :) Jeśli zapomniałeś, nie obawiaj się. Pamiętaj: jest jedna z zasad OOP: podczas projektowania klas i tworzenia obiektów powinniśmy identyfikować tylko podstawowe właściwości jednostki, a odrzucać te, które są nieistotne. Na przykład, jeśli projektujemy klasę SchoolTeacher, praktycznie nie potrzebujemy właściwości "height". W rzeczy samej, ta wartośc nie jest istotna dla nauczyciela. Ale jeśli tworzylibyśmy klasę BasketballPlayer, to wzrost byłby już ważną cechą. Posłuchaj więc. Klasa abstrakcyjna jest tak abstrakcyjna, jak to tylko możliwe - to niedokończone "puste miejsce" dla grupy przyszłych klas. Pustego miejsca nie można użyć w takiej postaci, w jakiej jest. Jest zbyt "surowe". Za to opisuje pewien stan i ogólne zachowania, jakie będą posiadały przyszłe klasy, które odziedziczą klasę abstrakcyjną.Klasy abstrakcyjne w Java - 1

Przykłady klas abstrakcyjnych Javy

Rozważ prosty przykład z samochodami:

public abstract class Car {

   private String model;
   private String color;
   private int maxSpeed;

   public abstract void gas();

   public abstract void brake();

   public String getModel() {
       return model;
   }

   public void setModel(String model) {
       this.model = model;
   }

   public String getColor() {
       return color;
   }

   public void setColor(String color) {
       this.color = color;
   }

   public int getMaxSpeed() {
       return maxSpeed;
   }

   public void setMaxSpeed(int maxSpeed) {
       this.maxSpeed = maxSpeed;
   }
}
Tak właśnie wygląda super prosta klasa abstrakcyjna. Jak widzisz, bułka z masłem :) Po co nam to? Po pierwsze, opisuje ona naszą wymaganą jednostkę, samochód, w sposób najbardziej abstrakcyjny z możliwych. Jest powód, dla którego używamy słowa abstract. W prawdziwym świecie nie ma "abstrakcyjnych samochodów". Istnieją ciężarówki, samochody wyścigowe, sedany, coupe i SUV-y. Nasza klasa abstrakcyjna jest po prostu "schematem", którego użyjemy później do tworzenia klas samochodów

public class Sedan extends Car {

   @Override
   public void gas() {
       System.out.println("The sedan is accelerating!");
   }

   @Override
   public void brake() {
       System.out.println("The sedan is slowing down!");
   }

}
Jest to bardzo podobne do tego, o czym rozmawialiśmy na lekcjach dotyczących dziedziczenia. Tylko na tych lekcjach mieliśmy klasę Car, a jej metody nie były abstrakcyjne. Lecz to rozwiązanie ma wiele wad, które zostały naprawione w klasach abstrakcyjnych. Przede wszystkim, nie można utworzyć instancji klasy abstrakcyjnej:

public class Main {

   public static void main(String[] args) {

       Car car = new Car(); // Error! The Car class is abstract!
   }
}
Twórcy Javy celowo zaprojektowali tę "funkcję". Jeszcze raz, dla przypomnienia: klasa abstrakcyjna to tylko schemat przyszłych "normalnych" klas. Nie potrzebujesz kopii projektu, prawda? Więc nie tworzysz instancji klasy abstrakcyjnej :) W przypadku gdyby klasa Car nie była abstrakcyjna, moglibyśmy z łatwością utworzyć jej instancje:

public class Car {

   private String model;
   private String color;
   private int maxSpeed;

   public void gas() {
       // Some logic
   }

    public void brake() {
       // Some logic
   }
}


public class Main {

   public static void main(String[] args) {

       Car car = new Car(); // Everything is fine. A car is created.
   }
}
Teraz nasz program ma jakiś niezrozumiały samochód - to nie jest ciężarówka, nie samochód wyścigowy, ani sedan i nie jest dokładnie jasne, co to jest. Jest to bardzo "abstrakcyjny samochód", który tak naprawdę nie istnieje w prawdziwym życiu. Możemy pokazać taki sam przykład używając zwierząt. Wyobraź sobie klasę Animal (zwierzęta abstrakcyjne). Nie wiadomo, jakiego rodzaju jest to zwierzę, do jakiej rodziny należy i jakie ma cechy charakterystyczne. Byłoby dziwne zobaczyć to w twoim programie. W naturze nie ma "zwierząt abstrakcyjnych". Tylko psy, koty, lisy, krety itp. Klasy abstrakcyjne w Java uwalniają nas od obiektów abstrakcyjnych. Dają nam podstawowy stan i zachowanie. Na przykład wszystkie samochody powinny mieć model, kolor i prędkość maksymalną, a ich użytkownicy powinni mieć możliwość włączenia gazu oraz hamulca. Otóż to. To jest ogólny plan abstrakcji klasy. Następnie projektujesz potrzebne klasy. Klasy abstrakcyjne w Java - 2Uwaga: dwie metody w klasie abstrakcyjnej są również oznaczone jako abstrakcyjne, a nie mają żadnej implementacji. Powód jest ten sam: abstrakcyjne klasy nie tworzą domyślnego zachowania dla abstrakcyjnych samochodów. Wskazują tylko, co powinien umieć każdy samochód. Jeśli jednak potrzebujesz domyślnego zachowania, możesz zaimplementować metody w klasie abstrakcyjnej. Java tego nie zabrania:

public abstract class Car {

   private String model;
   private String color;
   private int maxSpeed;

   public void gas() {
       System.out.println("Gas!");
   }

   public abstract void brake();

   // Getters and setters
}


public class Sedan extends Car {

   @Override
   public void brake() {
       System.out.println("The sedan is slowing down!");
   }

}

public class Main {

   public static void main(String[] args) {

       Sedan sedan = new Sedan();
       sedan.gas();
   }
}
Wydruk konsoli:
Gazu!
Jak widać, zaimplementowaliśmy pierwszą metodę w klasie abstrakcyjnej, lecz nie drugą. W rezultacie zachowanie naszej klasy Sedan jest podzielone na dwie części: jeśli wywołasz metodę gas(), wywołanie ''pójdzie w górę'' do abstrakcyjnej klasy nadrzędnej Car, ale my nadpisaliśmy metodę brake() w klasie Sedan. Okazuje się to bardzo wygodne i elastyczne. Więc teraz nasza klasa nie jest aż taka abstrakcyjna? W końcu zaimplementowana została połowa jej metod. W rzeczywistości jest to bardzo ważna cecha - klasa jest abstrakcyjna, jeśli przynajmniej jedna z jej metod jest abstrakcyjna. Jedna z dwóch metod, a nawet jedna na tysiąc - to bez znaczenia. Możemy nawet zaimplementować wszystkie metody, nie pozostawiając żadnej abstrakcyjnej. Wtedy byłaby to klasa abstrakcyjna bez metod abstrakcyjnych. W zasadzie jest to możliwe, a kompilator nie wygeneruje błędów, ale lepiej tego unikać: Słowo abstract straci swoje znaczenie, a twoi koledzy programiści będą bardzo zaskoczeni :/ Jednocześnie jeśli metoda jest oznaczona słowem abstract, każda klasa potomna musi ją zaimplementować lub zadeklarować jako abstrakcyjną. W przeciwnym razie kompilator wygeneruje błąd. Oczywiście każda klasa może dziedziczyć tylko jedną klasę abstrakcyjną, więc pod względem dziedziczenia nie ma różnicy między klasami abstrakcyjnymi i zwykłymi. Nie ma znaczenia, czy dziedziczymy klasę abstrakcyjną, czy zwykłą, może być tylko jedna klasa nadrzędna.

Dlaczego w Javie nie ma wielokrotnego dziedziczenia klas?

Mówiliśmy już, że Java nie ma wielokrotnego dziedziczenia, ale tak naprawdę nie zbadaliśmy dlaczego. Spróbujmy zrobić to teraz. Faktem jest, że gdyby Java miała dziedziczenie wielokrotne, klasy potomne nie byłyby w stanie zdecydować, które konkretne zachowanie wybrać. Załóżmy, że mamy dwie klasy — Toaster i NuclearBomb:

public class Toaster {


 public void on() {

       System.out.println("The toaster is on. Toast is being prepared!");
   }

   public void off() {

       System.out.println("The toaster is off!");
   }
}


public class NuclearBomb {

   public void on() {

       System.out.println("Boom!");
   }
}
Jak widać, obydwie mają metodę on(). W przypadku tostera rozpoczyna ona proces opiekania. W przypadku bomby atomowej wywołuje eksplozję. Ups: / Teraz wyobraź sobie, że zadecydowałeś (nie pytaj mnie dlaczego!) stworzyć coś pomiędzy. A zatem masz klasę MysteriousDevice! Ten kod oczywiście nie działa i podajemy go tylko jako przykład, ale mógłby wyglądać tak:

public class MysteriousDevice extends Toaster, NuclearBomb {

   public static void main(String[] args) {

       MysteriousDevice mysteriousDevice = new MysteriousDevice();
       mysteriousDevice.on(); // So what should happen here? Do we get toast or a nuclear apocalypse?
   }
}
Przyjrzyjmy się temu, co mamy. Tajemnicze urządzenie dziedziczy jednocześnie toster i bombę nuklearną. Oba urządzenia mają metody on(). W rezultacie, jeśli wywołasz metodę on(), nie jest jasne, która z nich powinna zostać wywołana na obiekcie MysteriousDevice. Nie ma sposobu, aby obiekt mógł kiedykolwiek wiedzieć. Dodatkowo, klasa NuclearBomb nie ma metody off(), więc gdybyśmy nie odgadli prawidłowo, wyłączenie urządzenia byłoby niemożliwe. Właśnie z powodu tej "niepewności", w której obiekt nie wie, jakie zachowanie ma pokazać, twórcy Javy porzucili wielokrotne dziedziczenie. Pamiętaj jednak, że klasy Java mogą implementować wiele interfejsów. Przy okazji, w trakcie swojej nauki, zetknąłeś się już z co najmniej jedną klasą abstrakcyjną! Choć może nawet tego nie zauważyłeś :)

public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar>
To twoja stara przyjaciółka, klasa Calendar. Jest abstrakcyjna i ma kilka klas podrzędnych. Jedną z nich jest GregorianCalendar. Używałeś jej już na lekcjach dotyczących dat. :) Wszystko jest wystarczająco jasne, tak? Pozostało jedno pytanie: jaka jest w ogóle podstawowa różnica między klasami abstrakcyjnymi a interfejsami? Dlaczego do Javy dodano oba, zamiast ograniczać język do jednego? W końcu byłoby to w pełni wystarczające, prawda? Porozmawiamy o tym na następnej lekcji! Do zobaczenia :)
Ten artykuł przeczytasz także po angielsku.
Read the English version of this article to explore an abstract class in Java. Abstraction in Java is super important and super cool!
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION