Zajęcia laboratoryjne nr 4 z programowania w języku Java - jedne zajęcia 1,5h. |
Podejście obiektowe umożliwia ponowne wykorzystanie (ang. reusing) już gotowych klas przy tworzeniu klas nowych, co znacznie oszczędza pracę przy kodowaniu, a także czyni programowanie mniej podatne na błędy.
Istnieją dwa sposoby ponownego wykorzystania klas:
-
kompozycja,
-
dziedziczenie.
1. Kompozycja
Z koncepcyjnego punktu widzenia kompozycja oznacza, że "obiekt jest zawarty w innym obiekcie" . Jest to relacja "całość – część" ( B "zawiera" A). Np. obiekty typu Pojazd zawierają obiekty typu Rozmiar, Koła, Silnik itd.. Kompozycję uzyskujemy poprzez definiowanie w nowej klasie pól, które są obiektami istniejących klas.
// Klasa Size
public class Size {
//...
}
// Klasa Wheels
public class Wheels {
//...
}
// klasa Engine
public class Engine {
//...
}
// Klasa Vehicle
public class Vehicle {
String color;
int speed;
Size size;
Engine engine
public Vehicle(String color, int speed, Size size, Engine engine) {
//...
}
//...
public void vehicleAttributes() {
System.out.println("Color : " + color);
System.out.println("Speed : " + speed);
System.out.println("Size : " + size.toString());
System.out.println("Engine : " + engine.toString());
}
}
2. Dziedziczenie
Dziedziczenie jest jednym z podstawowych mechanizmów programowania obiektowego. Mechanizm ten umożliwia definiowanie nowych klas na bazie istniejących.
|
Dziedziczenie - podobnie jak kompozycja (a nawet w większym stopniu) - pozwala na zmniejszanie nakładów na kodowanie (reusing). Jest to również odzwierciedlenie naturalnych sytuacji. |
Dziedziczenie polega na przejęciu właściwości i funkcjonalności obiektów innej klasy i ewentualnej modyfikacji tych właściwości i funkcjonalności w taki sposób, by były one bardziej wyspecjalizowane. Jest to relacja, nazywana generalizacją-specjalizacją: B "jest typu" A, "B jest A", a jednocześnie B specjalizuje A. A jest generalizacją B.
public class Car extends Vehicle {
String model;
int seats;
public Car(String model, int seats , String color, int speed, Size size, Engine engine) {
super(color, speed, size, engine);
this.seats = seats;
this.model = model;
//...
}
public void carAttributes() {
System.out.println("Model of Car : " + model);
System.out.println("Seats in Car : " + seats);
// The subclass refers to the members of the superclass
System.out.println("Color of Car : " + color);
System.out.println("Speed of Car : " + speed);
System.out.println("Size of Car : " + size.toString());
System.out.println("Engine of Car : " + engine.toString());
}
}
|
Jeśli nie chcemy aby dana klasa nie była dziedziczona przez inne klasy podczas
definicji klasy możemy użyć specyfikatora final
. Wiele klas standardowych jest
zabezpieczonych przed dziedziczeniem np. klasa String
.
3. Przedefiniowanie (przesłanianie, nadpisywanie) metod
Przedefiniowanie metody (ang. overriding) nadklasy w klasie pochodnej oznacza dostarczenie w klasie pochodnej definicji nieprywatnej i niestatycznej metody z taką samą sygnaturą (czyli nazwą i listą parametrów) jak sygnatura nieprywatnej i niestatycznej metody nadklasy, ale z ew. inną definicją ciała metody (innym kodem, który jest wykonywany przy wywołaniu metody), przy czym:
-
typy wyników tych metod muszą być takie same lub kowariantne (co oznacza m.in., że typ wyniku metody z podklasy może być podtypem wyniku metody nadklasy),
-
przedefiniowanie nie może ograniczać dostępu: specyfikator dostępu metody przedefiniowanej w podklasie musi być taki sam lub szerszy (np. public zamiast protected) niż metody przedefiniowywanej,
-
metoda przedefiniowana (z podklasy) nie może zgłaszać więcej lub bardziej ogólnych wyjątków kontrolowanych niż metoda przedefiniowywana (z nadklasy).
Przedefiniowując metody w podklasach warto używać adnotacji @Override
. Daje ona sygnał
kompilatorowi, że intencją programisty jest nadpisanie metody z klasy bazowej.
class Car extends Vehicle {
//...
@Override
public void vehicleAttributes() {
//...
}
}
Słowo kluczowe final, użyte w deklaracji metody zabrania jej przedefiniowania w klasie pochodnej (dziedziczącej). |
4. Rzutowanie obiektów, stwierdzanie typu
Rzutowanie (ang. cast) jest to operacja polegająca na zmianie zmiennej referncyjenj jednego typu na zmienna referencyjną innego typu. W Javie wyróżniamy dwa rodzaj rzutowania obiektów:
-
rzutowanie w górę (ang. upcasting) – bezpieczne,
-
rzutowanie w dół (ang. downcasting) – wymaga testowania (stwierdzenia typu instancji obiektu).
Stwierdzeniu jakiego typu jest referencja służy operator instanceof
.
ref instanceof T
Przy tym:
-
wyrażenie
null instanceof dowolny_typ
zawsze ma wartość false, -
wyrażenie
x instanceof T
, będzie błędne składniowo (wystąpi błąd w kompilacji), jeśli typ referencjix
i typT
nie są związane stosunkiem dziedziczenia, -
wyrażenie
x instanceof T
będzie miało wartość false, jeśli faktyczny typ referencjix
jest nadtypem typuT
.
Car car = new Car();
Vehicle veh = (Vehicle) car; //rzutowanie w gore Car -> Vehicle
veh.vehicleAttributes();
Vehicle veh = (Vehicle) new Car();
if (veh instanceof Car) { //sprawdzenie przed rzutowaniem
Car car = (Car) veh; //rzutowanie w dol Vehicle -> Car
car.carAttributes();
}
5. Bibliografia
-
Bruce Eckels, "Thinking in Java. Edycja polska. Wydanie IV", wydawnictwo Helion.
-
Cay S. Horstmann, Gary Cornell, "Java. Podstawy. Wydanie IX", wydawnictwo Helion.
-
Cay S. Horstmann, Gary Cornell, "Java. Techniki zaawansowane. Wydanie IX", wydawnictwo Helion.
-
Krzysztof Barteczko, "Podstawy programowania w języku Java, PJWSTK", http://edu.pjwstk.edu.pl/wyklady/ppj/scb/
-
Konrad Kurczyna, "Laboratorium Java", Politechnika Świętokrzyska w Kielcach.
-
Mariusz Lipiński, "Nauka Javy", http://www.naukajavy.pl/