PHP późne łączenie statyczne. Polimorfizm w Javie. Wiązanie dynamiczne i statyczne. Inicjalizacja obiektów. Zachowanie metod polimorficznych wywoływanych z konstruktorów. Różnice między wczesnym i późnym wiązaniem w Javie

05.04.2024 Oprogramowanie

23 marca 2010 5 grudnia

PHP 5.3 wprowadziło interesującą funkcję zwaną późnym wiązaniem statycznym. Poniżej znajduje się nieco swobodne tłumaczenie opisu z oficjalnej instrukcji.

Od PHP 5.3.0 język wprowadził funkcję zwaną późnym wiązaniem statycznym, której można używać do odwoływania się do wywoływalnej klasy w kontekście dziedziczenia statycznego.

Ta funkcja została nazwana „późnym wiązaniem statycznym”. „Późne wiązanie” oznacza, że ​​static:: nie zostanie rozwiązany względem klasy, w której zdefiniowano metodę, ale zostanie oceniony w czasie wykonywania. „Wiązanie statyczne” oznacza, że ​​można go używać w wywołaniach metod statycznych (ale nie wyłącznie).

Ograniczenia własne::

Przykład nr 1: Używanie siebie:

Przykład wyświetli:

Korzystanie z późnego wiązania statycznego

Późniejsze wiązanie statyczne próbuje rozwiązać to ograniczenie, wprowadzając słowo kluczowe odwołujące się do klasy pierwotnie wywołanej w czasie wykonywania. Oznacza to, że słowo kluczowe umożliwi odwołanie do B z funkcji test() w poprzednim przykładzie. Zdecydowano nie wprowadzać nowego słowa, ale użyć już zarezerwowanego statycznego .

Przykład nr 2: Proste użycie statyki:

Przykład wyświetli:

Uwaga: static:: nie działa w ten sposób w przypadku metod statycznych! $this-> przestrzega reguł dziedziczenia, ale static:: nie. To rozróżnienie wyjaśniono poniżej.

Przykład nr 3: Używanie static:: w kontekście niestatycznym

test(); ?>

Przykład wyświetli:

Uwaga: późne wiązanie statyczne zatrzymuje proces rozwiązywania połączeń. Wywołania statyczne wykorzystujące słowa kluczowe parent:: lub self:: przekazują informacje o połączeniu.

Przykład nr 4: Przekazywanie i nieprzekazywanie połączeń

Przykład wyświetli wynik

Przypadki Edge

Istnieje wiele różnych sposobów wywoływania metod w PHP, takich jak wywołania zwrotne lub metody magiczne. Ponieważ późne wiązanie statyczne jest rozwiązywane w czasie wykonywania, może to prowadzić do nieoczekiwanych wyników w tak zwanych przypadkach brzegowych.

Przykład #5 Późne wiązanie statyczne w metodach magicznych

bla; ?>

Łączenie statyczne jako optymalizacja

W niektórych przypadkach głównym wymaganiem jest wydajność i nawet niewielki narzut wspomniany powyżej jest niepożądany. W tym przypadku można zauważyć, że nie zawsze są one uzasadnione. Dzwonić x.f (a, b, c...) nie wymaga dynamicznego wiązania w następujących przypadkach:

1 F nie jest nadpisany w żadnym miejscu systemu (posiada tylko jedną deklarację);

2 X nie jest polimorficzny, to znaczy nie jest celem żadnego przywiązania, którego źródło jest innego typu.

W każdym z tych przypadków, wykrytych przez dobry kompilator, wygenerowany dla x.f (a, b, c...) kod może być taki sam, jak kod wygenerowany przez kompilatory C, Pascal, Ada lub Fortran do wywołania fa (x, a, b, c...). Nie będą wymagane żadne koszty ogólne.

Kompilator ISE, będący częścią środowiska opisanego na ostatnim wykładzie, aktualnie wykonuje optymalizację (1), do której planowane jest dodanie (2) (analiza (2) jest de facto konsekwencją analizy typów mechanizmy opisane w wykładzie o pisaniu).

Chociaż (1) jest interesujący sam w sobie, jego bezpośrednia użyteczność jest ograniczona przez stosunkowo niski koszt wiązania dynamicznego (patrz statystyki powyżej). Prawdziwy zysk z tego jest pośredni, ponieważ (1) pozwala na trzecią optymalizację:

4. Używaj, kiedy tylko jest to możliwe automatyczne podstawienie kodu procedury.

Podstawienie to polega na rozszerzeniu treści programu o tekst wywoływanej procedury w miejscu jej wywołania. Na przykład do procedury

set_a(x:SOME_TYPE) to

Ustaw x jako nową wartość atrybutu a.

kompilator może wygenerować, aby wywołać s.set_a(pewna_wartość) ten sam kod, który kompilator Pascala wygeneruje do przypisania s.a:= jakaś_wartość(oznaczenie nie do przyjęcia dla nas, ponieważ narusza ukrywanie informacji). W tym przypadku nie ma żadnego narzutu, ponieważ wygenerowany kod nie zawiera wywołania procedury.

Podstawianie kodu jest tradycyjnie postrzegane jako optymalizacja, którą należy określić programiści. Ada zawiera pragmę (instrukcję dla tłumacza) wbudowane, C i C++ oferują podobne mechanizmy. Ale to podejście ma nieodłączne ograniczenia. O ile w przypadku małego, statycznego programu kompetentny programista jest w stanie określić, które procedury można zastąpić, o tyle w przypadku dużych projektów deweloperskich jest to niemożliwe. W tym przypadku kompilator z przyzwoitym algorytmem wyznaczania podstawień znacznie przekroczy domysły programistów.

Dla każdego wywołania, do którego stosuje się automatyczne łączenie statyczne (1), kompilator OO może określić, na podstawie analizy kompromisu czas-pamięć, czy opłacalne jest automatyczne podstawienie kodu procedury (3). Jest to jedna z najbardziej niesamowitych optymalizacji - jeden z powodów, dla których można osiągnąć, a czasami w dużych systemach, wydajność ręcznie tworzonego kodu C lub Fortran.

Do wzrostu wydajności, który rośnie wraz z rozmiarem i złożonością programu, automatyczne podstawianie kodu dodaje korzyść w postaci większej niezawodności i elastyczności. Jak zauważono, podstawienie kodu jest semantycznie poprawne tylko w przypadku procedury, która może być ograniczona statycznie, jak na przykład w przypadkach (1) i (2). Jest to nie tylko dopuszczalne, ale także całkiem spójne z metodą OO, w szczególności z zasadą Otwórz-Zamknij, jeśli programista w połowie rozwoju dużego systemu doda przesłonięcie jakiegoś komponentu, który w tym momencie miał tylko jeden realizacja. Jeśli kod procedury zostanie wstawiony ręcznie, efektem może być program z błędną semantyką (ponieważ w tym przypadku wymagane jest łączenie dynamiczne, a wstawienie kodu oznacza oczywiście łączenie statyczne). Programiści powinni skupić się na budowaniu poprawnych programów, a nie na żmudnych optymalizacjach, które wykonywane ręcznie prowadzą do błędów, ale w rzeczywistości można je zautomatyzować.

Ostatnia uwaga dotycząca wydajności. Opublikowane statystyki dotyczące języków obiektowych wskazują, że od 30% do 60% wywołań faktycznie korzysta z wiązania dynamicznego. Zależy to od tego, jak intensywnie programiści wykorzystują określone właściwości metod. W systemie ISE wskaźnik ten kształtuje się na poziomie bliskim 60%. Dzięki właśnie opisanym optymalizacjom płacisz tylko za dynamiczne łączenie tylko tych połączeń, które rzeczywiście tego potrzebują. W przypadku pozostałych wywołań dynamicznych narzut jest nie tylko niewielki (ograniczony do stałej), ale także logicznie konieczny - w większości przypadków, aby osiągnąć wynik równoważny dynamicznemu wiązaniu, będziesz musiał użyć instrukcji warunkowych ( Jeśli następnie... Lub przypadek...), co może być droższe niż powyższy prosty mechanizm oparty na tablicach. Nic więc dziwnego, że programy OO skompilowane za pomocą dobrego kompilatora mogą konkurować z ręcznie napisanym kodem C.

Z książki Wzmocnij swoją witrynę autor Matsiewski Nikołaj

Archiwizacja statyczna w akcji Istnieje sposób, aby obejść się bez kilku wierszy w pliku konfiguracyjnym (httpd.conf lub .htaccess, preferowany jest pierwszy), jeśli poświęcisz kilka minut i samodzielnie zarchiwizujesz wszystkie niezbędne pliki. Załóżmy, że tak

Z książki The C++ Reference Guide autor Stroustrapa Bjarne’a

R.3.3 Program i łączenie Program składa się z jednego lub większej liczby plików połączonych ze sobą (§R.2). Plik składa się z ciągu opisów. Nazwa o zasięgu pliku, która jest jawnie zadeklarowana jako statyczna, jest lokalna w stosunku do swojej jednostki translacyjnej i może

Z książki Język programowania C# 2005 i platforma .NET 2.0. przez Troelsena Andrew

Wiązanie dynamiczne Mówiąc najprościej, wiązanie dynamiczne lub wiązanie dynamiczne to podejście, dzięki któremu można tworzyć instancje danego typu i wywoływać ich składowe w czasie wykonywania oraz w warunkach, w których w czasie kompilacji nie wiadomo jeszcze nic o typie.

Z książki ArchiCAD 11 autor Dnieprow Aleksander G

Łączenie widoków Wśród narzędzi wizualizacyjnych ArchiCADa istnieje mechanizm, którego zadaniem jest jednoczesne wyświetlanie dwóch różnych widoków. Jaki jest tego sens? Potrzeba tego pojawia się dość często. Na przykład, aby wizualnie połączyć obiekty

Z książki Podstawy programowania obiektowego przez Meyera Bertranda

Wiązanie dynamiczne Połączenie dwóch ostatnich mechanizmów, nadpisywania i polimorfizmu, bezpośrednio implikuje następujący mechanizm. Załóżmy, że istnieje wywołanie, którego celem jest jednostka polimorficzna, na przykład jednostka typu BOAT wywołuje komponent turn.

Z książki Programowanie systemu w środowisku Windows przez Harta Johnsona M

Łączenie z ADT Klasa, jak już wielokrotnie mówiono, jest implementacją ADT, określoną formalnie lub pośrednio. Na początku wykładu zauważono, że stwierdzenia można potraktować jako sposób na wprowadzenie do zajęć właściwości semantycznych, które leżą w

Z książki Architektura TCP/IP, protokoły, implementacja (w tym IP w wersji 6 i IP Security) przez Faith Sydney M

Wiązanie dynamiczne Dynamiczne wiązanie uzupełni nadpisywanie, polimorfizm i typowanie statyczne, tworząc podstawową tetralogię

Z książki VBA dla opornych przez Steve’a Cummingsa

Przycisk o innej nazwie: kiedy łączenie statyczne jest nieprawidłowe Główny wniosek z zasad dziedziczenia przedstawionych w tym wykładzie powinien być już jasny: Zasada wiązania dynamicznego Kiedy wynik łączenia statycznego nie jest zgodny z wynikiem

Z książki System operacyjny UNIX autor Robaczewski Andriej M.

Pisanie i wiązanie Chociaż jako czytelnik tej książki prawdopodobnie będziesz w stanie odróżnić pisanie statyczne od wiązania statycznego, są ludzie, którzy nie potrafią tego zrobić. Może to częściowo wynikać z wpływu Smalltalk, który opowiada się za dynamicznym podejściem do obu problemów

Z książki C++ dla początkujących przez Lippmana Stanleya

Łączenie niejawne Łączenie niejawne, czyli łączenie w czasie ładowania, jest prostszą z dwóch technik łączenia. Procedura korzystania z Microsoft C++ jest następująca: 1. Po zebraniu wszystkich funkcji niezbędnych dla nowej biblioteki DLL,

Z książki Rozwój jądra systemu Linux przez Love Roberta

Jawne łączenie Jawne łączenie lub łączenie w czasie wykonywania wymaga, aby program podał szczegółowe instrukcje dotyczące tego, kiedy załadować lub zwolnić bibliotekę DLL. Następnie program otrzymuje żądany adres

Z książki autora

11.9.3 Powiązanie Serwer DHCP utrzymuje tabelę mapowań pomiędzy klientami i ich parametrami konfiguracyjnymi. Wiązanie polega na przypisaniu każdemu klientowi adresu IP i zestawu konfiguracji

Z książki autora

Static State Słowo kluczowe Static w deklaracji zmiennej powinno być użyte, gdy chcemy, aby zmienna pozostała w pamięci - tak, aby można było wykorzystać jej wartość - nawet po zakończeniu działania procedury. W poniższym przykładzie zmienna

Z książki autora

Powiązanie Zanim klient będzie mógł wywołać procedurę zdalną, musi zostać powiązany z systemem zdalnym, na którym znajduje się wymagany serwer. Zatem wiążące zadanie dzieli się na dwie części:? Znalezienie zdalnego hosta z wymaganym serwerem? Odkrycie

Z książki autora

9.1.7. Bezpieczne wiązanie A Podczas stosowania przeciążenia wydaje się, że program może mieć kilka funkcji o tej samej nazwie z różnymi listami parametrów. Jednak ta wygoda leksykalna istnieje tylko na poziomie tekstu źródłowego. W większości

Z książki autora

Statyczna alokacja pamięci na stosie W przestrzeni użytkownika wiele operacji alokacji pamięci, szczególnie niektóre z przykładów omówionych wcześniej, można wykonać przy użyciu stosu, ponieważ rozmiar przydzielonego obszaru pamięci jest znany z góry. W

Począwszy od PHP 5.3.0 dostępna była funkcja zwana późnym wiązaniem statycznym, której można użyć do uzyskania odniesienia do klasy wywoływalnej w kontekście dziedziczenia statycznego.

Dokładniej, późne wiązanie statyczne zachowuje nazwę klasy określoną w ostatnim „nieprzekierowanym wywołaniu”. W przypadku wywołań statycznych jest to jawnie określona klasa (zwykle po lewej stronie operatora :: ); w przypadku wywołań niestatycznych jest to klasa obiektu. „Połączenie przekierowane” to połączenie statyczne rozpoczynające się od samego siebie::, rodzic::, statyczny:: lub, jeśli przejdziemy w górę hierarchii klas, forward_static_call(). Funkcjonować get_ Call_class() można użyć do uzyskania ciągu znaków z nazwą wywoływanej klasy i statyczny:: reprezentuje jego zakres.

Sama nazwa „późne wiązanie statyczne” odzwierciedla wewnętrzną implementację tej funkcji. „Późne wiązanie” odzwierciedla fakt, że trafia statyczny:: nie będzie obliczana względem klasy, w której zdefiniowano wywoływaną metodę, ale będzie obliczana na podstawie informacji uzyskanych w czasie wykonywania. Ta funkcja została również nazwana „wiązaniem statycznym”, ponieważ może być używana (ale nie musi) w metodach statycznych.

Ograniczenia samego siebie::

Przykład nr 1 Zastosowanie samego siebie::

klasa A (
echo __KLASA__ ;
}
publiczna funkcja statyczna
test()(
siebie::kto();
}
}

klasa B rozszerza A (
publiczna funkcja statyczna who() (
echo __KLASA__;
}
}

B::test();
?>

Korzystanie z późnego wiązania statycznego

Późniejsze wiązanie statyczne próbuje pokonać to ograniczenie, dostarczając słowo kluczowe odwołujące się do klasy wywoływanej bezpośrednio w czasie wykonywania. Mówiąc najprościej, słowo kluczowe, które pozwoli Ci na linkowanie B z test() w poprzednim przykładzie. Zdecydowano nie wprowadzać nowego słowa kluczowego, ale go używać statyczny, który jest już zarezerwowany.

Przykład nr 2 Łatwy w użyciu statyczny::

klasa A (
publiczna funkcja statyczna who() (
echo __KLASA__ ;
}
publiczna funkcja statyczna
test()(
statyczny::kto(); // Obowiązuje tutaj późne wiązanie statyczne
}
}

klasa B rozszerza A (
publiczna funkcja statyczna who() (
echo __KLASA__;
}
}

B::test();
?>

Wynik uruchomienia tego przykładu:

Komentarz:

W kontekście niestatycznym wywoływaną klasą będzie ta, do której należy instancja obiektu. Ponieważ $to-> spróbuje wywołać metody prywatne z tego samego zakresu, używając statyczny:: może dawać różne wyniki. Kolejna różnica polega na tym statyczny:: może odnosić się tylko do pól statycznych klasy.

Przykład nr 3 Zastosowanie statyczny:: w kontekście niestatycznym

klasa A (
funkcja prywatna foo() (
echo "sukces!\n" ;
}
test funkcji publicznej() (
$to -> foo();
statyczny::foo();
}
}

klasa B rozszerza A (
/* foo() zostanie skopiowana do B, stąd jej zakres nadal wynosi A,
i połączenie zakończy się sukcesem*/
}

klasa C rozszerza A (
funkcja prywatna foo() (
/* zastąpiono oryginalną metodę; zakres nowej metody C */
}
}

$b = nowe B();
$b -> test();
$c = nowe C();
$c -> test(); //nie prawda
?>

Wynik uruchomienia tego przykładu:

powodzenie! powodzenie! powodzenie! Błąd krytyczny: wywołanie metody prywatnej C::foo() z kontekstu „A” w /tmp/test.php w linii 9

Komentarz:

Rozwiązujący region późnego wiązania statycznego zostanie ustalony przez wywołanie statyczne, które je oblicza. Z drugiej strony wywołania statyczne korzystające z dyrektyw takich jak rodzic:: Lub samego siebie:: przekierowanie informacji o połączeniu.

Przykład #4 Połączenia przekierowane i nie przekierowane

Wiązanie w C++

Dwoma głównymi celami przy opracowywaniu języka programowania C++ była wydajność pamięci i szybkość wykonywania. Miał on stanowić ulepszenie języka C, szczególnie w zastosowaniach obiektowych. Podstawowa zasada C++: żadna właściwość języka nie powinna prowadzić do dodatkowego obciążenia (zarówno w zakresie pamięci, jak i szybkości), jeśli ta właściwość nie zostanie wykorzystana przez programistę. Na przykład, jeśli cała orientacja obiektowa C++ zostanie zignorowana, reszta powinna być tak szybka jak tradycyjne C. Nic więc dziwnego, że większość metod w C++ jest łączona statycznie (w czasie kompilacji), a nie dynamicznie (w czasie wykonywania).

Wiązanie metod w tym języku jest dość złożone. W przypadku zwykłych zmiennych (nie wskaźników ani referencji) odbywa się to statycznie. Kiedy jednak obiekty są wyznaczane za pomocą wskaźników lub odniesień, stosowane jest wiązanie dynamiczne. W tym drugim przypadku decyzja o wyborze metody typu statycznego lub dynamicznego jest podyktowana tym, czy odpowiednia metoda jest zadeklarowana przy użyciu słowa kluczowego virtual. Jeżeli jest to zadeklarowane w ten sposób, to metoda wyszukiwania komunikatów bazuje na klasie dynamicznej; jeżeli nie, to na klasie statycznej. Nawet w przypadkach, gdy używane jest wiązanie dynamiczne, ważność każdego żądania jest określana przez kompilator na podstawie klasy statycznej odbiorcy.

Rozważmy na przykład następujący opis klas i zmiennych globalnych: class Mammal

printf("nie mogę mówić");

klasa Pies: publiczny Ssak

printf("wouf, wow");

printf("wouf wouf też");

Ssak *fido = nowy Pies;

Wyrażenie fred.speak() wyświetla komunikat „nie mogę mówić”, ale wywołanie fido->speak() spowoduje również wydrukowanie komunikatu „nie mogę mówić”, ponieważ odpowiednia metoda w klasie Mammal nie jest zadeklarowana jako wirtualna. Wyrażenie fido->bark() nie jest dozwolone przez kompilator, nawet jeśli typem dynamicznym fido jest Pies. Jednakże statycznym typem zmiennej jest po prostu klasa Mammal.

Jeśli dodamy słowo wirtualny:

wirtualna pustka mówi()

printf("nie mogę mówić");

następnie otrzymujemy oczekiwany wynik na wyjściu wyrażenia fido->speak().

Stosunkowo nową zmianą w języku C++ jest dodanie możliwości rozpoznawania klasy dynamicznej obiektu. Tworzą system RTTI (Run-Time Type Identification).

W systemie RTTI każda klasa ma powiązaną strukturę typu typeinfo, która koduje różne informacje o klasie. Pole danych nazwy, jedno z pól danych tej struktury, zawiera nazwę klasy w postaci ciągu tekstowego. Funkcji typeid można używać do analizowania informacji o typie danych. Dlatego poniższe polecenie wydrukuje ciąg „Pies” jako dynamiczny typ danych dla fido. W tym przykładzie konieczne jest wyłuskanie zmiennej wskaźnika fido, tak aby argumentem była wartość, do której odnosi się wskaźnik, a nie sam wskaźnik:

cout<< «fido is a» << typeid(*fido).name() << endl;

Możesz także zapytać, używając funkcji before member, czy jedna struktura zawierająca informacje o typie danych jest podklasą klasy powiązanej z inną strukturą. Na przykład poniższe dwa stwierdzenia dają prawdę i fałsz:

if (typeid(*fido).before (typeid(fred)))…

if (typeid(fred).before (typeid(lassie)))…

Przed systemem RTTI standardową sztuczką programistyczną było jawne zakodowanie w hierarchii klas metod, które miały być instancjami. Na przykład, aby przetestować wartość zmiennej typu Animal i sprawdzić, czy jest ona typu Kot, czy typu Pies, można zdefiniować następujący system metod:

wirtualny int isaDog()

wirtualny int isaCat()

klasa Pies: publiczny Ssak

wirtualny int isaDog()

klasa Kot: Ssak publiczny

wirtualny int isaCat()

Możesz teraz użyć polecenia fido->isaDog(), aby określić, czy bieżąca wartość fido jest wartością typu Pies. Jeśli zwrócona zostanie wartość różna od zera, typ zmiennej można rzutować na żądany typ danych.

Zwracając wskaźnik zamiast liczby całkowitej, łączymy testowanie podklas i rzutowanie typów. Przypomina to inną część systemu RTTI zwaną dynamic_cast, którą pokrótce opiszemy. Jeżeli funkcja w klasie Mammal zwraca wskaźnik do Dog, to klasa Dog musi być wcześniej zadeklarowana. Wynikiem przypisania jest wskaźnik zerowy lub prawidłowe odwołanie do klasy Dog. Zatem wynik nadal musi zostać sprawdzony, ale eliminujemy potrzebę rzutowania typu. Pokazano to w następującym przykładzie:

klasa Pies; //opis wstępny

wirtualny Pies* isaDog()

wirtualny kot* isaCat()

klasa Pies: publiczny Ssak

wirtualny Pies* isaDog()

klasa Kot: Ssak publiczny

wirtualny kot* isaCat()

Operator lassie = fido->isaDog(); Teraz zawsze będziemy to robić. W rezultacie lassie ma ustawioną wartość różną od zera tylko wtedy, gdy fido ma dynamiczną klasę Dog. Jeśli fido nie jest własnością Dog, wówczas lassie zostanie przypisany wskaźnik zerowy.

lassie = fido->isaDog();

... // fido rzeczywiście jest typu Pies

... //przypisanie nie zadziałało

... // fido nie jest typu Pies

Chociaż programista może użyć tej metody do odwrócenia polimorfizmu, wadą tej metody jest to, że wymaga dodania metod zarówno do klasy nadrzędnej, jak i podrzędnej. Jeśli wiele dzieci pochodzi z jednej wspólnej klasy nadrzędnej, metoda staje się nieporęczna. Jeśli zmiany w klasie nadrzędnej nie są dozwolone, technika ta nie jest w ogóle możliwa.

Ponieważ takie problemy występują często, znaleziono ogólne rozwiązanie. Funkcja szablonu dynamic_cast przyjmuje typ jako argument szablonu i, podobnie jak funkcja zdefiniowana powyżej, zwraca albo wartość argumentu (jeśli rzutowanie typu jest dozwolone), albo wartość null (jeśli rzutowanie typu jest niedozwolone). Przypisanie równoważne temu wykonanemu w poprzednim przykładzie można zapisać w następujący sposób:

// konwertuj tylko jeśli fido jest psem

lassie = dynamic_cast< Dog* >(fido);

// następnie sprawdź, czy rzutowanie się powiodło

Do C++ dodano jeszcze trzy typy rzutowania (static_cast, const_cast i reinterpret_cast), ale są one używane w specjalnych przypadkach i dlatego nie są tutaj opisywane. Zachęcamy programistów do korzystania z nich jako bezpieczniejszej opcji zamiast mechanizmu rzutowania starego typu.

2. Część projektowa

Ten akapit, pomimo swojej zwięzłości, jest bardzo ważny - prawie całe profesjonalne programowanie w Javie opiera się na wykorzystaniu polimorfizmu. Jednocześnie temat ten jest jednym z najtrudniejszych do zrozumienia dla studentów. Dlatego zaleca się uważne i kilkukrotne przeczytanie tego akapitu.

Metody klas nie bez powodu są oznaczone modyfikatorem static - dla nich podczas kompilacji kodu programu łączenie statyczne. Oznacza to, że w kontekście jakiej klasy w kodzie źródłowym wskazana jest nazwa metody, w skompilowanym kodzie umieszczony jest odnośnik do metody tej klasy. Oznacza to, że jest realizowany powiązanie nazwy metody w miejscu wezwania z kodem wykonywalnym Ta metoda. Czasami nazywa się łączenie statyczne wczesne wiązanie, ponieważ ma to miejsce na etapie kompilacji programu. Wiązanie statyczne w Javie ma zastosowanie jeszcze w jednym przypadku - gdy zadeklarowana jest klasa z modyfikatorem final („final”, „final”),

Metody obiektowe w Javie są dynamiczne, czyli podlegają dynamiczne łączenie. Ma to miejsce na etapie wykonywania programu bezpośrednio podczas wywołania metody, a na etapie pisania tej metody nie wiadomo z góry, z której klasy zostanie wykonane wywołanie. Decyduje o tym typ obiektu, dla którego działa ten kod - do jakiej klasy należy obiekt, z jakiej klasy wywoływana jest metoda. To powiązanie występuje długo po skompilowaniu kodu metody. Dlatego często nazywa się ten rodzaj wiązania późne wiązanie.

Kod programu bazujący na wywoływaniu metod dynamicznych posiada właściwość wielopostaciowość– ten sam kod działa inaczej w zależności od tego, jaki typ obiektu go wywołuje, ale robi to samo na poziomie abstrakcji związanym z kodem źródłowym metody.

Aby wyjaśnić te słowa, które na pierwszy rzut oka nie są zbyt jasne, rozważmy przykład z poprzedniego akapitu – działanie metody moveTo. Niedoświadczeni programiści uważają, że tę metodę należy zastąpić w każdej klasie potomnej. Rzeczywiście da się to zrobić i wszystko będzie działać poprawnie. Ale taki kod będzie wyjątkowo redundantny - w końcu implementacja metody będzie dokładnie taka sama we wszystkich pochodnych klasach rysunku:

public void moveTo(int x, int y)(

Co więcej, w tym przypadku nie wykorzystano polimorfizmu. Więc nie zrobimy tego.

Często zastanawiające jest również, dlaczego implementacja tej metody powinna być napisana w abstrakcyjnej klasie Figura. Przecież wywołania zastosowanych w nim metod hide and show na pierwszy rzut oka powinny być wywołaniami metod abstrakcyjnych - czyli wydaje się, że w ogóle nie mogą działać!

Metody hide and show są jednak dynamiczne, co jak już wiemy oznacza, że ​​skojarzenie nazwy metody z jej kodem wykonywalnym następuje na etapie wykonywania programu. Zatem to, że metody te są określone w kontekście klasy Rysunek, nie oznacza, że ​​zostaną wywołane z klasy Rysunek! Co więcej, możesz zagwarantować, że metody ukrywania i pokazywania nigdy nie będą wywoływane z tej klasy. Miejmy zmienne dot1 typu Dot i koło1 typu Circle i przypiszmy im referencje do obiektów odpowiednich typów. Przyjrzyjmy się, jak zachowują się wywołania dot1.moveTo(x1,y1) i Circle1.moveTo(x2,y2).

Po wywołaniu dot1.moveTo(x1,y1) wywoływana jest metoda moveTo z klasy Rysunek. Rzeczywiście, ta metoda w klasie Dot nie jest zastępowana, co oznacza, że ​​jest dziedziczona z Figury. W metodzie moveTo pierwszą instrukcją jest wywołanie metody dynamicznego ukrywania. Implementacja tej metody pochodzi z klasy, której instancją jest obiekt dot1 wywołujący tę metodę. To znaczy z klasy Dot. Zatem sprawa jest ukryta. Następnie zmieniane są współrzędne obiektu, po czym wywoływana jest metoda dynamicznego pokazu. Implementacja tej metody pochodzi z klasy, której instancją jest obiekt dot1 wywołujący tę metodę. To znaczy z klasy Dot. W ten sposób w nowym miejscu zostanie wyświetlona kropka.

W przypadku wywołania koła1.moveTo(x2,y2) wszystko jest zupełnie podobne - metody dynamiczne hide i show wywoływane są z klasy, której instancją jest obiekt Circle1, czyli z klasy Circle. Zatem to okrąg ukryty w starym miejscu i pokazany w nowym.

Oznacza to, że jeśli obiekt jest punktem, punkt się porusza. A jeśli obiektem jest okrąg, okrąg się porusza. Co więcej, jeśli ktoś kiedyś napisze np. klasę Ellipse będącą potomkiem Circle i utworzy obiekt Ellipse ellipse=new Ellipse(...), to wywołanie ellipse.moveTo(...) przesunie elipsę do nową lokalizację. A stanie się to zgodnie ze sposobem, w jaki zaimplementowano metody hide and show w klasie Ellipse. Należy zauważyć, że kod polimorficzny klasy Figura, który został skompilowany dawno temu, będzie działał. Polimorfizm zapewnia fakt, że odniesienia do tych metod nie są umieszczane w kodzie metody moveTo na etapie kompilacji - są one konfigurowane do metod o takich nazwach z klasy obiektu wywołującego natychmiast w momencie wywołania metody moveTo.

W obiektowych językach programowania wyróżnia się dwa rodzaje metod dynamicznych – właściwie dynamiczne i wirtualny. Zgodnie z zasadą działania są one całkowicie podobne i różnią się jedynie cechami wykonawczymi. Wywoływanie metod wirtualnych jest szybsze. Wywoływanie metod dynamicznych jest wolniejsze, ale tablica metod dynamicznych (DMT – Dynamic Methods Table) zajmuje nieco mniej pamięci niż tablica metod wirtualnych (VMT – Virtual Methods Table).

Może się wydawać, że wywoływanie metod dynamicznych nie jest czasochłonne ze względu na czas potrzebny na wyszukiwanie nazw. Tak naprawdę w trakcie wywołania nie jest wykonywane żadne wyszukiwanie nazw, lecz wykorzystywany jest znacznie szybszy mechanizm wykorzystujący wspomnianą tabelę metod wirtualnych (dynamicznych). Ale nie będziemy rozwodzić się nad specyfiką implementacji tych tabel, ponieważ w Javie nie ma rozróżnienia między tego typu metodami.

Obiekt klasy bazowej

Klasa Object jest klasą bazową dla wszystkich klas Java. Dlatego wszystkie jego pola i metody są dziedziczone i zawarte we wszystkich klasach. Klasa Object zawiera następujące metody:

public Boolean równa się (obiekt obiektu)– zwraca wartość true w przypadku, gdy wartości obiektu, z którego wywoływana jest metoda, oraz obiektu przekazanego przez referencję obj na liście parametrów są równe. Jeśli obiekty nie są równe, zwracana jest wartość false. W klasie Object równość traktowana jest jako równość referencyjna i jest równoważna operatorowi porównania „==”. Ale w przypadku potomków tę metodę można zastąpić i można porównywać obiekty według ich zawartości. Dzieje się tak na przykład w przypadku obiektów klas numerycznych powłoki. Można to łatwo sprawdzić za pomocą takiego kodu:

Podwójne d1=1,0,d2=1,0;

System.out.println("d1==d2 ="+(d1==d2));

System.out.println("d1.equals(d2) ="+(d1.equals(d2)));

Pierwsza linia wyniku wyświetli d1==d2 =false, a druga d1.equals(d2) =true

publiczny int hashCode()- kwestie kod skrótu obiekt. Kod skrótu to warunkowo unikalny identyfikator numeryczny powiązany z elementem. Ze względów bezpieczeństwa nie można podać adresu obiektu aplikacji. Dlatego w Javie kod skrótu zastępuje adres obiektu w przypadkach, gdy z jakiegoś powodu konieczne jest przechowywanie tabel adresów obiektów.

chroniony obiekt clone() zgłasza wyjątek CloneNotSupportedException– metoda kopiuje obiekt i zwraca link do utworzonego klonu (duplikatu) obiektu. W potomkach klasy Object konieczne jest jej przedefiniowanie, a także wskazanie, że klasa implementuje interfejs Clonable. Próba wywołania metody z obiektu, który nie obsługuje klonowania, powoduje zgłoszenie wyjątku CloneNotSupportedException. Interfejsy i sytuacje wyjątkowe zostaną omówione później.

Wyróżnia się dwa rodzaje klonowania: płytkie (płytkie), gdy wartości pól oryginalnego obiektu są kopiowane jeden do jednego do klona oraz głębokie (głębokie), w którym tworzone są nowe obiekty dla pól typ referencyjny, klonujący obiekty, do których odnoszą się oryginalne pola. W przypadku płytkiego klonowania zarówno oryginał, jak i klon będą odwoływać się do tych samych obiektów. Jeśli obiekt ma pola tylko typów pierwotnych, nie ma różnicy pomiędzy klonowaniem płytkim i głębokim. Implementacją klonowania zajmuje się programista rozwijający klasę; nie ma mechanizmu automatycznego klonowania. I to już na etapie tworzenia klasy należy podjąć decyzję, jaką opcję klonowania wybrać. W zdecydowanej większości przypadków wymagane jest głębokie klonowanie.

publiczna klasa końcowa getClass()– zwraca referencję do metaobiektu typu class. Za jego pomocą można uzyskać informację o klasie do której należy obiekt oraz wywołać jego metody i pola klas.

chroniona pustka finalize() rzuca Throwable– Wywoływany przed zniszczeniem obiektu. Musi zostać zastąpiony w tych potomkach Obiektu, w których konieczne jest wykonanie pewnych czynności pomocniczych przed zniszczeniem obiektu (zamknięcie pliku, wyświetlenie komunikatu, narysowanie czegoś na ekranie itp.). Metodę tę opisano bardziej szczegółowo w odpowiednim akapicie.

publiczny ciąg toString()– zwraca ciąg znaków reprezentujący obiekt (najbardziej adekwatnie jak to możliwe). W klasie Object metoda ta generuje ciąg znaków zawierający pełną nazwę obiektu (wraz z nazwą pakietu), po którym następuje znak „@”, a następnie szesnastkowy kod skrótu obiektu. Większość klas standardowych zastępuje tę metodę. Dla klas numerycznych zwracana jest ciąg znaków reprezentujący liczbę, dla klas łańcuchowych – zawartość ciągu, dla klas znakowych – sam znak (a nie ciąg znaków reprezentujący jego kod!). Na przykład następujący fragment kodu

Obiekt obj=nowy obiekt();

System.out.println(" obj.toString() daje "+obj.toString());

Double d=nowy Double(1.0);

System.out.println(" d.toString() daje "+d.toString());

Znak c="A";

System.out.println("c.toString() daje "+c.toString());

przedstawi konkluzję

obj.toString() daje Java.lang.Object@fa9cf

d.toString() daje 1,0

c.toString() daje A

Istnieją również metody notyfikować(), powiadom wszystkich() i kilka przeciążonych wariantów metody Czekać, przeznaczony do pracy z wątkami. Zostały one omówione w części poświęconej strumieniom.


Powiązana informacja.