Programowanie obiektowe - Wyklad 04

Transkrypt

Programowanie obiektowe - Wyklad 04
Programowanie obiektowe
Wykład 04
Maciej Wołoszyn
mailto:[email protected]∗
17 marca 2009
Spis treści
1
Wyjatki
˛ i obsługa błedów
˛
1.1
1.2
1.3
1.4
1.5
1.6
1.7
1.8
1.9
1.10
1.11
1.12
1.13
2
2
Rzucanie wyjatków
˛
. . . . . . . . . .
Argumenty konstruktorów . . . . . .
Przechwytywanie wyjatków
˛
. . . . .
Tworzenie własnych wyjatków
˛
. . . .
Specyfikacja rzucanych wyjatków
˛
. .
Przechwytywanie dowolnego wyjatku
˛
Ponowne rzucanie wyjatków
˛
. . . . .
Wyjatki
˛ typu RuntimeException . .
Blok finally . . . . . . . . . . . . .
Wyjatki
˛ i dziedziczenie . . . . . . . .
Dopasowywanie wyjatków
˛
. . . . . .
Wyjatki
˛ sprawdzane . . . . . . . . . .
Podsumowanie . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2
3
4
5
7
7
8
8
9
10
11
11
12
Wykrywanie typów
13
2.1
2.2
2.3
14
15
16
Obiekt Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Refleksje (Odzwierciedlenia) . . . . . . . . . . . . . . . . . . . . . . . . . .
Podsumowanie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
∗ Prosz˛
e o przesyłanie na ten adres informacji o znalezionych bł˛edach,
literówkach oraz propozycji zmian i uzupełnień. Dokument przygotowano za pomoca˛ systemu LATEX. Wszelkie prawa zastrzeżone.
1
Programowanie obiektowe. Wykład 04
2
1 Wyjatki
˛ i obsługa błedów
˛
• Java zaleca (a w przypadku wykorzystania klas bibliotecznych praktycznie wymusza)
obsług˛e bł˛edów za pomoca˛ mechanizmu wyjatków
˛
(exceptions) – czyli rezygnacj˛e
z funkcji zwracajacych
˛
kody bł˛edów i ich każdorazowego sprawdzania
• dotyczy to oczywiście tylko bł˛edów, które moga˛ si˛e pojawić dopiero na etapie uruchomienia programu, a nie jego kompilacji
• zalety mechanizmu obsługi wyjatków:
˛
– możliwość przekazania informacji o bł˛edzie w inne miejsce programu – nie zawsze
od razu mamy dość informacji, aby „coś zaradzić” (w przeciwnym wypadku nie
potrzebujemy wcale rzucać wyjatku)
˛
– uporzadkowanie
˛
obsługi bł˛edów – cz˛esto wystarczy obsługiwać dany bład
˛ w jednym miejscu, dzi˛eki czemu oddzielamy rozwiazanie
˛
zasadniczego zagadnienia od
obsługi możliwych problemów
1.1 Rzucanie wyjatków
˛
• instrukcja throw
• nowy wyjatek
˛ (obiekt odpowiedniej klasy! – np. bibliotecznej) jest tworzony w zwykły
sposób – operatorem new
• wykonanie programu w aktualnym miejscu (metodzie, bloku instrukcji) zostaje przerwane – dalszym przebiegiem steruje już procedura obsługi danego wyjatku,
˛
tzw. exception handler (o ile jest przygotowana)
class Mat {
static final double EPSILON = 1e-12;
static double div(double x, double y) {
return x/y;
}
static double divExc(double x, double y) {
if( Math.abs(y) < EPSILON)
throw new ArithmeticException();
return x/y;
}
}
System.out.println("1)"+Mat.div(0,0));
System.out.println("2)"+Mat.divExc(0,0));
Programowanie obiektowe. Wykład 04
3
→
1)NaN
Exception in thread ’’main’’ java.lang.ArithmeticException
at Mat.divExc(P01.java:8)
at P01.main(P01.java:38)
System.out.println("3)"+5/0);
→
Exception in thread ’’main’’ java.lang.ArithmeticException:
at P01.main(P01.java:38) nie zawsze rzucanie wyjatku
˛ ma sens, np.:
/ by zero
static void pokazRozw(int Wx,int Wy,int W){
if(W!=0){
System.out.print("Wyniki: ");
System.out.println(Wx/W+" "+Wy/W);
}
else{
// rozwiazanie na miejscu zamiast wyjatku:
System.out.println("W=0!");
}
}
1.2 Argumenty konstruktorów
• standardowe, biblioteczne klasy wyjatków
˛
posiadaja˛ dwa rodzaje konstruktorów:
– domyślne, np.
ArithmeticException()
– przyjmujace
˛ opis w postaci String-a, np.
ArithmeticException(String s)
static double divExc(double x, double y) {
if( Math.abs(y) < EPSILON)
throw new ArithmeticException("div/0");
return x/y;
}
System.out.println("2)"+Mat.divExc(0,0));
→ Exception in thread ’’main’’ java.lang.ArithmeticException:
at Mat.divExc(P01.java:8)
at P01.main(P01.java:38)
div/0
Programowanie obiektowe. Wykład 04
4
• jako wyjatki
˛ moga˛ być użyte dowolne obiekty klas dziedziczacych
˛
po klasie Throwable
– najcz˛eściej do obsługi każdego typu bł˛edu stosuje si˛e osobna˛ klas˛e
• informacja o bł˛edzie jest przechowywana wewnatrz
˛ obiektu;
czasem jedyna˛ potrzebna˛ informacja˛ jest typ wyjatku
˛
i żadne dodatkowe dane nie sa˛
w takiej klasie przechowywane
1.3 Przechwytywanie wyjatków
˛
• rzucajac
˛ wyjatek
˛ zakładamy, że zostanie on gdzieś odebrany i obsłużony
• aby nie spowodować natychmiastowego opuszczenia bieżacej
˛ metody, użycie kodu,
który może wyprodukować wyjatek
˛ musi być zrealizowane w bloku
try {
instrukcje
}
• po try nast˛epuja˛ bloki catch (handlers) – po jednym dla każdego typu wyjatku,
˛
który
chcemy przechwycić
try {
kod mogacy
˛ generować wyjatki
˛
} catch( A e ) {
obsługa wyjatku
˛ typu A
} catch( B e ) {
obsługa wyjatku
˛ typu B
} catch( C e ) {
obsługa wyjatku
˛ typu C
...
}
• każdy blok catch zachowuje si˛e podobnie do funkcji, która pobiera dokładnie jeden argument (identyfikator musi być podany nawet jeśli nie jest wykorzystywany i do obsługi
wyjatku
˛ wystarcza nam znajomość jego typu)
• bloki catch musza˛ si˛e znaleźć bezpośrednio za try
• po wystapieniu
˛
wyjatku
˛ uruchamiany jest pierwszy handler z pasujacym
˛
typem
• jeśli w bloku try jest kilka metod mogacych
˛
powodować ten sam wyjatek,
˛
to do jego
obsługi potrzeba tylko jednego catch
try {
int m = 1/0;
System.out.println("dalej?");
}
catch(ArithmeticException e){
Programowanie obiektowe. Wykład 04
5
System.out.println("tak nie mozna...");
}
System.out.println("teraz dalej!");
→
tak nie mozna...
teraz dalej!
1.4 Tworzenie własnych wyjatków
˛
• cz˛esto biblioteczne klasy wyjatków
˛
nie opisuja˛ dobrze rodzaju bł˛edu, który może mieć
miejsce w naszym programie
• można utworzyć własna˛ klas˛e reprezentujac
˛ a˛ wyjatek
˛ – dziedziczac
˛ po istniejacej
˛ już
klasie, jeśli to możliwe o podobnym znaczeniu
class AaaException extends Exception {}
class DivByZeroException
extends ArithmeticException {
}
class ZeroByZeroException
extends ArithmeticException {
}
class Mat {
static final double EPSILON = 1e-12;
static double divExc(double x, double y) {
if( Math.abs(y) < EPSILON)
if(Math.abs(x) < EPSILON)
throw new ZeroByZeroException();
else
throw new DivByZeroException();
return x/y;
}
}
for(int k=0;k<2;k++)
try {
double w = Mat.divExc(k,0);
} catch(DivByZeroException e) {
System.err.println("! x/0 !");
Programowanie obiektowe. Wykład 04
6
} catch(ZeroByZeroException e) {
System.err.println("! 0/0 !");
}
→
! 0/0 !
! x/0 !
• można oczywiście dodać do klasy dodatkowe informacje, np. o wartościach, które spowodowały wygenerowanie wyjatku
˛
class Mat {
static final double EPSILON = 1e-12;
static double divExc(double x, double y) {
if( Math.abs(y) < EPSILON)
throw new DivByZeroException(y);
return x/y;
}
}
class DivByZeroException
extends ArithmeticException {
private double x;
public DivByZeroException(double x) {
this.x = x;
}
public double x() { return x; }
}
try {
double w = Mat.divExc(2,1e-13);
} catch(DivByZeroException e) {
System.err.print("! x/0 !");
System.err.println(" : M="+e.x());
e.printStackTrace();
}
→ ! x/0 ! : M=1.0E-13
DivByZeroException
at Mat.divExc(P03.java:5)
at P03.main(P03.java:27)
Programowanie obiektowe. Wykład 04
7
1.5 Specyfikacja rzucanych wyjatków
˛
• nazwy wszystkich wyjatków,
˛
które nie pochodza˛ od klasy RuntimeException musza˛
być wyszczególnione w deklaracji metody, która może je spowodować – służy do tego
słowo kluczowe throws
void f() throws AaaException {
/* ... */
}
• metoda nie musi rzucać wyjatku,
˛
który deklaruje (przydatne np. dla metod abstrakcyjnych)
• przykłady wyjatków
˛
niesprawdzanych na etapie kompilacji (dziedziczacych
˛
po klasie
RuntimeException):
–
–
–
–
ArithmeticException
IndexOutOfBoundsException
NegativeArraySizeException
NullPointerException
(pełna lista dost˛epna w dokumentacji)
1.6 Przechwytywanie dowolnego wyjatku
˛
• ponieważ używane w programach klasy wyjatków
˛
dziedzicza˛ po klasie Exception,
wi˛ec w prosty sposób można spowodować przechwycenie dowolnego z nich:
catch(Exception e) {
System.err.println("JAKIS wyjatek");
}
• jeśli potrzeba przechwytywać dowolny wyjatek,
˛
to handler catch(Exception e)
umieszcza si˛e jako ostatni z bloków catch – inaczej zablokujemy możliwość obsługi
bardziej szczegółowych typów wyjatków
˛
• użycie typu Exception powoduje, że nie mamy informacji o konkretnym rodzaju wyjatku;
˛
możemy jedynie posłużyć si˛e odziedziczonymi metodami
try {
throw new ArithmeticException("BLAD");
} catch(Exception e) {
System.err.println(e.getMessage());
System.err.println(e);
}
Programowanie obiektowe. Wykład 04
8
Object
Throwable
Exception
RuntimeException
...
Error
DataFormatException
ArithmeticException ... NullPointerException
Rysunek 1: Relacje dziedziczenia pomi˛edzy niektórymi klasami obsługujacymi
˛
mechanizm
wyjatków.
˛
→
BLAD
java.lang.ArithmeticException: BLAD
1.7 Ponowne rzucanie wyjatków
˛
• może si˛e zdarzyć, że wskazane b˛edzie rzucenie na wyższy poziom przechwyconego
już wyjatku
˛ (np. przekazanie do dalszej obsługi dowolnego obiektu „złapanego” przez
catch(Exception e))
• wystarczy w tym celu użyć instrukcji throw podajac
˛ referencj˛e do przechwyconego
wyjatku
˛
catch(Exception e) {
System.err.println("przekazuje dalej..");
throw e;
}
• dalsze bloki catch w aktualnym miejscu nie sa˛ już sprawdzane
• wyrzucana jest kompletna informacja o wyjatku
˛ – nawet jeśli przechwycony został jako
Exception, to nie traci si˛e informacji o dokładnym typie
1.8 Wyjatki
˛ typu RuntimeException
• dost˛epna jest pewna ilość automatycznie rzucanych wyjatków,
˛
potomków klasy RuntimeException
Programowanie obiektowe. Wykład 04
9
• nie wymagaja˛ podania na liście rzucanych przez metod˛e wyjatków
˛
– sa˛ to tzw. wyjatki
˛
niesprawdzane
• najcz˛eściej si˛e ich nie przechwytuje (sygnalizuja˛ bł˛edy w programie, które powinny być
usuni˛ete na etapie debuggowania i testowania) – przykład: ArrayIndexOutOfBoundsException
• tak samo jak inne typy wyjatków
˛
moga˛ być rzucane z dowolnego miejsca
• zb˛edne sa˛ instrukcje w rodzaju:
if(x == null)
throw new NullPointerException();
ponieważ użycie nie pokazujacej
˛ na nic referencji x i tak spowoduje wyjatek
˛ NullPointerException
1.9 Blok finally
• pozwala wykonać jakieś instrukcje niezależnie od tego, czy wewnatrz
˛ bloku try wyja˛
tek został rzucony, czy też nie
• gwarantuje wykonanie kodu, do którego mogłoby nie być powrotu jeśli nastapiłby
˛
wyja˛
tek (nawet jeśli brak odpowiedniego dla danego wyjatku
˛ bloku catch, to blok finally
zostanie wykonany przed przekazaniem wyjatku
˛ dalej!) – można np. zamknać
˛ otwarte
pliki albo połaczenia
˛
sieciowe
• pełna postać obsługi wyjatków:
˛
try {
instrukcje „pod specjalnym nadzorem”
(mogace
˛ rzucać wyjatki
˛ typu E1, E2, ...)
} catch(E1 e) {
obsługa wyjatku
˛ E1
} catch(E2 e) {
obsługa wyjatku
˛ E2
} finally {
instrukcje wykonywane zawsze
}
try {
throw new ArithmeticException("BLAD");
} catch(NullPointerException e) {
System.err.println("NullPointer");
} finally {
System.err.println("finally...");
Programowanie obiektowe. Wykład 04
10
}
System.err.println("za pozno...");
→ finally... Exception in thread "main" java.lang.ArithmeticException: BLAD
at P05.main(P05.java:9)
• należy uważać, aby przypadkiem rzucajac
˛ wyjatek
˛ wewnatrz
˛ bloku finally nie spowodować utraty informacji o pierwszym zaistniałym wyjatku,
˛
jeśli nie był on dotad
˛
obsłużony – wyjatek
˛ rzucony wewnatrz
˛ finally pojawi si˛e zanim dojdzie do przekazania dalej pierwotnego wyjatku
˛
1.10 Wyjatki
˛ i dziedziczenie
• przy redefiniowaniu odziedziczonej po klasie podstawowej metody można rzucać tylko
te wyjatki,
˛ na które pozwala deklaracja throws umieszczona w klasie podstawowej –
zapewnia to prawidłowe działanie m.in. polimorfizmu
• zastrzeżenie to nie dotyczy wyjatków
˛
potomnych od zadeklarowanych w klasie podstawowej
• ewentualnie można w redefiniowanej wersji metody zrezygnować z rzucania wyjatków
˛
class XException extends Exception {}
class YException extends Exception {}
class A {
void f() throws Exception {}
void g() throws XException {}
}
class B extends A {
void f() throws YException {}
//void g() throws YException {} /* ZLE */
}
• ograniczenie wyłacznie
˛
do wyjatków
˛
zadeklarowanych w klasie bazowej nie dotyczy
również konstruktorów
• konstruktor klasy potomnej musi deklarować wyjatki
˛ rzucane przez konstruktor klasy
bazowej
• konstruktor klasy potomnej nie może przechwytywać wyjatków
˛
rzucanych przez konstruktor klasy bazowej
Programowanie obiektowe. Wykład 04
11
1.11 Dopasowywanie wyjatków
˛
• po rzuceniu wyjatku
˛ system ich obsługi sprawdza dost˛epne handler-y w kolejności w jakiej sa˛ zapisane;
po znalezieniu pasujacego
˛
do typu wyjatku
˛ uznaje si˛e wyjatek
˛ za obsłużony i nie jest
prowadzone dalsze poszukiwanie handler-ów
• nie jest wymagane dokładne dopasowanie: obiekt klasy potomnej zostanie obsłużony
przez blok catch napisany dla klasy bazowej
class AException extends Exception {}
class BException extends AException {}
try {
throw new BException();
} catch (AException e) {
System.out.println("AException");
}
→
AException
• nie można próbować obsługiwać wyjatku
˛ klasy potomnej po wcześniejszym obsłużeniu
typu podstawowego
try {
throw new BException();
} catch (AException e) {
System.out.println("AException");
}
// catch (BException e) {
// System.out.println("BException");
// } /* spowodowaloby blad kompilacji */
1.12 Wyjatki
˛ sprawdzane
• musza˛ być obsługiwane albo zadeklarowane jako rzucane (przekazywane dalej) przez
metod˛e
• jeśli nie chcemy lub nie możemy obsłużyć na miejscu ani zadeklarować jako rzucane
przez metod˛e, to można je „opakować” wewnatrz
˛ wyjatku
˛ niesprawdzanego (np. typu
RuntimeException)
Programowanie obiektowe. Wykład 04
12
static void f() throws Exception {
throw new Exception();
}
static void g() {
try {
f();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
g();
}
→
Exception in thread "main" java.lang.RuntimeException: java.lang.Exception
at P10.g(P10.java:16)
at P10.main(P10.java:9)
Caused by: java.lang.Exception
at P10.f(P10.java:5)
at P10.g(P10.java:14)
// static void h() {
// f();
// }
/* ZLE - blad kompilacji:
P10.java:21: unreported exception
java.lang.Exception; must be caught
or declared to be thrown
*/
1.13 Podsumowanie
• Nie należy przechwytywać wyjatków,
˛
z którymi nie wiadomo co zrobić!
• spójna organizacja obsługi bł˛edów jest szczególnie ważna przy pisaniu komponentów
(bibliotek), które maja˛ być używane przez inne programy
• Wyjatków
˛
używa si˛e aby np.:
Programowanie obiektowe. Wykład 04
13
1. naprawiać przyczyn˛e kłopotów, a potem ponownie wywoływać metod˛e skad
˛ rzucony został wyjatek
˛
2. zaproponować rozwiazanie
˛
zast˛epcze zamiast wywoływać ponownie „kłopotliwa”
˛
metod˛e
3. zrobić „co si˛e da” w aktualnym miejscu i przekazać wyjatek
˛ (ten sam lub inny)
dalej
4. bezpiecznie zakończyć działanie programu
2 Wykrywanie typów
• wykrywanie dokładnego typu obiektu, jeśli dysponujemy tylko referencja˛ typu bazowego
RTTI = Run-Time Type Identification
• odpowiada za sprawdzanie typu przed rzutowaniem i ew.
ClassCastException
rzucenie wyjatku
˛
class Dane {
String opis(){ return "Dane"; }
}
void f(Object obj) {
System.out.println(
// obj.opis() /* ZLE (blad komp.) */
// (String)obj /* ZLE (wyjatek) */
((Dane)obj).opis()
);
}
Dane d = new Dane();
f(d);
→ Dane
• za pomoca˛ zwracajacego
˛
wartość logiczna˛ operatora instanceof można sprawdzić,
czy obiekt należy do danej klasy
void f(Object obj) {
String s;
if(obj instanceof Dane) {
s=((Dane)obj).opis();
}
Programowanie obiektowe. Wykład 04
14
else s="Cos innego";
System.out.println(s);
}
Dane d = new Dane();
String s = "Napis";
f(d);
f(s);
→
Dane
Cos innego
Uwaga: nie ma to służyć zast˛epowaniu polimorfizmu, ale ułatwieniu np. wywołania metody
specyficznej dla konkretnego typu (a nie dla bazowego)!
2.1 Obiekt Class
• zawiera informacje o klasie/interfejsie
• wykorzystywany do tworzenia obiektów danej klasy
• w wyniku kompilacji umieszczany jest w pliku
NazwaKlasy.class
i ładowany przez JVM przy pierwszym użyciu danego typu (a nie zaraz przy uruchamianiu programu!)
• można si˛e do niego odnieść np. poprzez:
– metod˛e getClass() zdefiniowana˛ w klasie Object i zwracajac
˛ a˛ referencj˛e do
odpodwiedniego obiektu Class
– tzw. literał klasy:
NazwaKlasy.class
dost˛epny również dla interfejsów, tablic i typów prostych, np. Dane.class;
int.class;
dodatkowo dla klas obudowujacych
˛
typy proste dost˛epne sa˛ pola TYPE takie, że
np.
int.class ⇔ Integer.TYPE
• dost˛epne sa˛ m.in. metody:
String getName() – nazwa klasy, interfejsu, typu prostego. . .
boolean isInterface() – sprawdza, czy klasa jest interfejsem
boolean isArray() – sprawdza, czy klasa jest tablica˛
Programowanie obiektowe. Wykład 04
15
boolean isInstance(Object obj) –
dynamiczny
odpowiednik
operatora
instanceof (wynik=true jeśli obj nie jest null i może być zrzutowane do
typu reprezentowanego przez Class)
void wypiszNazweKlasy(Object obj) {
System.out.println(obj + " jest klasy "
+ obj.getClass().getName());
}
Dane d = new Dane();
String s = "Napis";
wypiszNazweKlasy(d);
wypiszNazweKlasy(s);
→ Dane@11b86e7 jest klasy Dane
Napis jest klasy java.lang.String
2.2 Refleksje (Odzwierciedlenia)
• nie zawsze podczas kompilacji dost˛epne sa˛ informacje o klasach (np. gdy używamy
komponentów, klas ładowanych przez sieć itp.)
• mechanizm refleksji pozwala na uzyskanie informacji o typie obiektu oraz jego polach
i metodach
• analizuje obiekty dopiero podczas wykonywania programu, podczas gdy „zwykłe”
RTTI robi to na etapie kompilacji
• klasa Class zawiera m.in. metody:
Method[] getMethods() – zwracajaca
˛ tablic˛e publicznych metod
Constructor[] getConstructors() – zwracajaca
˛ tablic˛e publicznych konstruktorów
Field[] getFields() – zwracajaca
˛ tablic˛e publicznych pól
• potrzebne klasy (Method, Field, Constructor) sa˛ dost˛epne w bibliotece
java.lang.reflect
import java.lang.reflect.*;
class Dane {
public String opis() { return "Dane"; }
}
Programowanie obiektowe. Wykład 04
16
Method[] m = Dane.class.getMethods();
for(int i = 0; i < m.length; i++)
System.out.println(m[i]);
→public
→public
→public
→public
→public
→public
→public
→public
→public
→public
java.lang.String Dane.opis()
native int java.lang.Object.hashCode()
final native java.lang.Class java.lang.Object.getClass()
final native void java.lang.Object.wait(long) throws java.lang.InterruptedEx
final void java.lang.Object.wait(long,int) throws java.lang.InterruptedExcep
final void java.lang.Object.wait() throws java.lang.InterruptedException
boolean java.lang.Object.equals(java.lang.Object)
final native void java.lang.Object.notify()
final native void java.lang.Object.notifyAll()
java.lang.String java.lang.Object.toString()
2.3 Podsumowanie
• tam gdzie si˛e tylko da, należy wykorzystywać polimorfizm
• mechanizm RTTI powinien być używany tylko gdy nie da si˛e użyć polimorfizmu

Podobne dokumenty