Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00261 008601 10442130 na godz. na dobę w sumie
Efektywne programowanie w języku Java - książka
Efektywne programowanie w języku Java - książka
Autor: Liczba stron: 216
Wydawca: Helion Język publikacji: polski
ISBN: 83-7197-989-4 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> programowanie >> java - programowanie
Porównaj ceny (książka, ebook, audiobook).

Java to wspaniałe narzędzie w rękach programisty. Ale nawet najlepsze narzędzie może zostać źle użyte. Istnieje wiele książek, które opisują ten język programowania, skupiając się na przedstawieniu jego składni. Ta książka jest zupełnie inna. Adresowana do osób znających już Javę, przedstawia 57 praktycznych zasad pisania efektywnego, poprawnego kodu.

Każda wskazówka to oddzielny podrozdział, przedstawiający opis problemu, przykłady poprawnego (i błędnego!) kodu, a także historie wzięte z doświadczenia autora. Dzięki książce zapoznasz się z idiomami właściwymi językowi Java oraz z istotnymi z praktycznego punktu widzenia wzorcami projektowymi.

Wskazówki dotyczą między innymi:

O autorze:
Joshua Bloch jest współautorem wielu bibliotek języka Java, takich jak Java Collections Framework i java.math, a także autorem licznych prac naukowych. [więcej...\

Znajdź podobne książki Ostatnio czytane w tej kategorii

Darmowy fragment publikacji:

Efektywne programowanie w jêzyku Java Autor: Joshua Bloch T³umaczenie: Pawe³ Gonera ISBN: 83-7197-989-4 Tytu³ orygina³u: Effective Java Programming Language Format: B5, stron: 214 Przyk³ady na ftp: 48 kB Java to wspania³e narzêdzie w rêkach programisty. Ale nawet najlepsze narzêdzie mo¿e zostaæ ĥle u¿yte. Istnieje wiele ksi¹¿ek, które opisuj¹ ten jêzyk programowania skupiaj¹c siê na przedstawieniu jego sk³adni. Ta ksi¹¿ka jest zupe³nie inna. Adresowana jest do osób znaj¹cych ju¿ Javê i przedstawia praktyczne zasady pisania efektywnego, poprawnego kodu. Ka¿da wskazówka omówiona jest w osobnym podrozdziale opisuj¹cym dany problem, przyk³ady poprawnego (i b³êdnego!) kodu, a tak¿e historie zaczerpniête z bogatego doġwiadczenia autora. Ta ksi¹¿ka zapozna Ciê z idiomami w³aġciwymi jêzykowi Java oraz z istotnymi z praktycznego punktu widzenia wzorcami projektowymi. IDZ DO IDZ DO PRZYK£ADOWY ROZDZIA£ PRZYK£ADOWY ROZDZIA£ SPIS TREĎCI SPIS TREĎCI KATALOG KSI¥¯EK KATALOG KSI¥¯EK KATALOG ONLINE KATALOG ONLINE ZAMÓW DRUKOWANY KATALOG ZAMÓW DRUKOWANY KATALOG TWÓJ KOSZYK TWÓJ KOSZYK DODAJ DO KOSZYKA DODAJ DO KOSZYKA CENNIK I INFORMACJE CENNIK I INFORMACJE ZAMÓW INFORMACJE ZAMÓW INFORMACJE O NOWOĎCIACH O NOWOĎCIACH ZAMÓW CENNIK ZAMÓW CENNIK CZYTELNIA CZYTELNIA FRAGMENTY KSI¥¯EK ONLINE FRAGMENTY KSI¥¯EK ONLINE Wydawnictwo Helion ul. Chopina 6 44-100 Gliwice tel. (32)230-98-63 e-mail: helion@helion.pl Spis treści Słowo wstępne ...................................................n............................... 7 Przedmowa...................................................n..................................... 9 Wprowadzenie ...................................................n.............................. 11 Rozdział 1. Tworzenie i usuwanie obiektów ...................................................n..... 15 Temat 1. Tworzenie statycznych metod factory zamiast konstruktorów..........................15 Temat 2. Wymuszanie właściwości singleton za pomocą prywatnego konstruktora .......18 Temat 3. Wykorzystanie konstruktora prywatnego w celu uniemożliwienia utworzenia obiektu ...................................................o...............20 Temat 4. Unikanie powielania obiektów...................................................o........................21 Temat 5. Usuwanie niepotrzebnych referencji do obiektów.............................................24 Temat 6. Unikanie finalizatorów...................................................o....................................27 Rozdział 2. Metody wspólne dla wszystkich obiektów......................................... 31 Temat 7. Zachowanie założeń w trakcie przedefiniowywania metody equals .................31 Temat 8. Przedefiniowywanie metody hashCode wraz z equals ......................................39 Temat 9. Przedefiniowywanie metody toString...................................................o.............44 Temat 10. Rozsądne przedefiniowywanie metody clone..................................................46 Temat 11. Implementacja interfejsu Comparable ...................................................o..........53 Rozdział 3. Klasy i interfejsy ...................................................n........................... 59 Temat 12. Ograniczanie dostępności klas i ich składników..............................................59 Temat 13. Zapewnianie niezmienności obiektu ...................................................o.............62 Temat 14. Zastępowanie dziedziczenia kompozycją ...................................................o.....69 Temat 15. Projektowanie i dokumentowanie klas przeznaczonych do dziedziczenia ......74 Temat 16. Stosowanie interfejsów zamiast klas abstrakcyjnych ......................................78 Temat 17. Wykorzystanie interfejsów jedynie do definiowania typów............................83 Temat 18. Zalety stosowania statycznych klas składowych .............................................84 Rozdział 4. Odpowiedniki konstrukcji języka C ...................................................n 89 Temat 19. Zastępowanie struktur klasami...................................................o......................89 Temat 20. Zamiana unii na hierarchię klas ...................................................o....................91 Temat 21. Zastępowanie konstrukcji enum za pomocą klas .............................................94 Temat 22. Zastępowanie wskaźników do funkcji za pomocą klas i interfejsów...................103 Rozdział 5. Metody...................................................n....................................... 107 Temat 23. Sprawdzanie poprawności parametrów ...................................................o......107 Temat 24. Defensywne kopiowanie ...................................................o.............................109 Temat 25. Projektowanie sygnatur metod...................................................o....................112 Temat 26. Rozsądne korzystanie z przeciążania ...................................................o..........114 Temat 27. Zwracanie pustych tablic zamiast wartości null ............................................118 Temat 28. Tworzenie komentarzy dokumentujących dla wszystkich udostępnianych elementów API...................................................o........120 6 Efektywne programowanie w języku Java Rozdział 6. Programowanie...................................................n........................... 125 Temat 29. Ograniczanie zasięgu zmiennych lokalnych ..................................................125 Temat 30. Poznanie i wykorzystywanie bibliotek ...................................................o.......128 Temat 31. Unikanie typów float i double, gdy potrzebne są dokładne wyniki...............131 Temat 32. Unikanie typu String, gdy istnieją bardziej odpowiednie typy ......................133 Temat 33. Problemy z wydajnością przy łączeniu ciągów znaków ................................135 Temat 34. Odwoływanie się do obiektów poprzez interfejsy .........................................136 Temat 35. Stosowanie interfejsów zamiast refleksyjności..............................................137 Temat 36. Rozważne wykorzystywanie metod natywnych ............................................140 Temat 37. Unikanie optymalizacji ...................................................o...............................141 Temat 38. Wykorzystanie ogólnie przyjętych konwencji nazewnictwa .........................144 Rozdział 7. Wyjątki ...................................................n...................................... 147 Temat 39. Wykorzystanie wyjątków w sytuacjach nadzwyczajnych .............................147 Temat 40. Stosowanie wyjątków przechwytywalnych i wyjątków czasu wykonania.......149 Temat 41. Unikanie niepotrzebnych wyjątków przechwytywalnych .............................151 Temat 42. Wykorzystanie wyjątków standardowych ...................................................o..153 Temat 43. Zgłaszanie wyjątków właściwych dla abstrakcji ...........................................155 Temat 44. Dokumentowanie wyjątków zgłaszanych przez metodę ...............................157 Temat 45. Udostępnianie danych o błędzie ...................................................o.................158 Temat 46. Zachowanie atomowości w przypadku błędu ................................................159 Temat 47. Nie ignoruj wyjątków...................................................o..................................161 Rozdział 8. Wątki ...................................................n......................................... 163 Temat 48. Synchronizacja dostępu do wspólnych modyfikowalnych danych................163 Temat 49. Unikanie nadmiarowej synchronizacji...................................................o........168 Temat 50. Nigdy nie wywołuj wait poza pętlą...................................................o.............172 Temat 51. Unikanie korzystania z systemowego szeregowania wątków........................174 Temat 52. Dokumentowanie bezpieczeństwa dla wątków..............................................177 Temat 53. Unikanie grup wątków ...................................................o................................180 Rozdział 9. Serializacja...................................................n................................. 181 Temat 54. Implementowanie interfejsu Serializable...................................................o....181 Temat 55. Wykorzystanie własnej postaci serializowanej..............................................185 Temat 56. Defensywne tworzenie metody readObject ...................................................o191 Temat 57. Tworzenie metody readResolve...................................................o..................196 Dodatek A Zasoby ...................................................n....................................... 199 Skorowidz...................................................n................................... 203 Rozdział 4. Odpowiedniki konstrukcji języka C Język programowania Java posiada wiele podobieństw do języka C, ale kilka jego kon- strukcji zostało pominiętych. W większości przypadków oczywiste jest, dlaczego dana konstrukcja została pominięta i w jaki sposób można sobie bez niej radzić. W rozdziale tym zaproponujemy zamienniki dla kilku pominiętych konstrukcji języka C, których zastąpienie nie jest tak oczywiste. Najczęstszym wątkiem, który przewija się przez cały ten rozdział, jest twierdzenie, że wszystkie pominięte konstrukcje były zorientowane na dane, a nie zorientowane obiek- towo. Język programowania Java zawiera bardzo wydajny system typów i propono- wane zamienniki w pełni korzystają z tego systemu w celu zapewnienia wyższego stopnia abstrakcji niż konstrukcje języka C, które sgą przez nie zastępowane. Nawet jeżeli zdecydujesz się na pominięcie tego rozdziału, warto zapoznać się z tema- tem 21., poświęconym typowi wyliczeniowemu, który zastępuje konstrukcję GPWO, dostęp- ną w języku C. Wzorzec ten nie był powszechnie znany w czasie pisania tej książki, a posiada znaczną przewagę nad obecnie stosowanymi rozwiązaniami tego problemu. Temat 19. Zastępowanie struktur klasami Konstrukcja UVTWEV języka C została usunięta z języka Java, ponieważ za pomocą klasy można zrealizować wszystko to, co potrafi struktura, a nawet więcej. Struktura jedy- nie grupuje kilka pól danych w jeden obiekt — klasa zawiera operacje wykonywane na wynikowym obiekcie i pozwala na ukrycie pól danych przed użytkownikiem obiektu. Inaczej mówiąc, klasa hermetyzuje dane w obiekcie i umożliwia dostęp do nich jedy- nie za pomocą metod, co pozwala twórcy klasy na swobodną zmianę reprezentacji danych w późniejszym czasie (temat 12.). W czasie pierwszych pokazów języka Java niektórzy programiści korzystający z języka C uważali, że klasy są zbyt obszerne, aby w pewnych sytuacjach zastąpić struktury. 90 Efektywne programowanie w języku Java Nie będziemy się zajmowali tym problemem. Zdegenerowane klasy składające się jedynie z pól danych są pewnym przybliżeniem struktur z językga C: 6CMKG\FGIGPGTQYCPGMNCU[PKGRQYKPP[D[èRWDNKEI\PG ENCUU2QKPV] RWDNKEHNQCVZ RWDNKEHNQCV[ _ Ponieważ takie klasy pozwalają na dostęp do swoich pól, nie pozwalają na skorzysta- nie z zalet hermetyzacji. Nie można zmienić reprezentacji danych w takiej klasie bez zmiany API, nie można wymuszać żadnych ograniczeń oraz nie można podejmować dodatkowych zadań podczas modyfikacji pola. Ortodoksyjni programiści obiektowi uważają, że takie klasy są zakazane i powinny zawsze być zastępowane klasami z polami prywatnymi oraz publicznymi metodami je udostępniajgącymi: *GTOGV[\QYCPCMNCUCQHWPMELCEJUVTWMVWT[ ENCUU2QKPV] RTKXCVGHNQCVZ RTKXCVGHNQCV[ RWDNKE2QKPV HNQCVZHNQCV[ ] VJKUZZ VJKU[[ _ RWDNKEHNQCVIGV: ]TGVWTPZ_ RWDNKEHNQCVIGV; ]TGVWTP[_ RWDNKEHNQCVUGV: ]VJKUZZ_ RWDNKEHNQCVUGV; ]VJKU[[_ _ Oczywiście twierdzenie to jest prawdziwe w odniesieniu do klas publicznych — jeżeli klasa jest dostępna spoza swojego pakietu, rozważny programista powinien zabezpie- czyć sobie możliwość zmiany wewnętrznej reprezentacji danych. Jeżeli klasa publiczna udostępnia swoje pola, nie ma możliwości zmiany reprezentacji danych, ponieważ kod klientów, korzystający z klasy publicznej, może być jugż rozesłany po całym świecie. Jeżeli jednak klasa jest prywatna w ramach pakietu lub jest to prywatna klasa zagnież- dżona, nie ma nic złego w bezpośrednim udostępnieniu pól danych — zakładając, że naprawdę opisują abstrakcję definiowaną przez klasę. Podejście to generuje mniej kodu niż wykorzystanie metod dostępowych, zarówno w definicji klasy, jak i w kodzie klientów ją wykorzystujących. Ponieważ kod klientów jest ściśle związany z wewnętrzną repre- zentacją klasy, jest on ograniczony do pakietu, w którym klasa ta jest zdefiniowana. W przypadku, gdy konieczna jest zmiana reprezentacji danych, możliwe jest wpro- wadzenie zmian bez konieczności zmiany kodu poza pakietem. W przypadku prywat- nej klasy zagnieżdżonej zasięg zmian jest ograniczongy do klasy nadrzędnej. Kilka klas w bibliotekach języka Java nie dotrzymuje zalecenia, aby klasy publiczne nie udostępniały bezpośrednio swoich pól. Przykładami takich klas są klasy 2QKPV i KOGPUKQP z pakietu LCXCCYV. Przykłady te nie powinny być naśladowane — należałoby raczej Rozdział 4. ♦ Odpowiedniki konstrukcji języka C 91 wskazywać je jako przykład negatywny. W temacie 37. opisany został przykład poka- zujący, jak udostępnienie pól w klasie KOGPUKQP spowodowało problemy z wydajno- ścią. Problemy te nie mogą zostać usunięte bez wpływaniga na kod klientów. Temat 20. Zamiana unii na hierarchię klas Konstrukcja języka C — WPKQP — jest najczęściej wykorzystywana do definiowania struktur, umożliwiających przechowywanie więcej niż jednego typu danych. Struktura taka zwykle posiada co najmniej dwa pola — unię i znacznik. Pole znacznika jest zwykłym polem, wykorzystywanym do wskazywania aktualnego typu danych, prze- chowywanego przez unię. Znacznik jest najczęściej typu wyliczeniowego (GPWO). Struktura zawierająca unię i znacznik jest czasami ngazywaną unią z dyskryminatorem. Poniżej przedstawiamy przykład definicji typu UJCRGAV, zapisany w języku C. Jest to unia z dyskryminatorem, reprezentująca prostokąt lub koło. Funkcja CTGC na podstawie wskaźnika do struktury UJCRGAV zwraca pole figury lub , jeżeli struktura zawiera nieprawidłowe dane.  7PKC\F[UMT[OKPCVQTGO  KPENWFGOCVJJ V[RGFGHGPWO]4 6#0).  +4 . _UJCRG6[RGAV V[RGFGHUVTWEV] FQWDNGNGPIVJ FQWDNGYKFVJ _TGEVCPING KOGPUKQPUAV V[RGFGHUVTWEV] FQWDNGTCFKWU _EKTENG KOGPUKQPUAV V[RGFGHUVTWEV] UJCRG6[RGAVVCI WPKQP] TGEVCPING KOGPUKQPUAVTGEVCPING EKTENG KOGPUKQPUAVEKTENG _FKOGPUKQPU _UJCRGAV FQWDNGCTGC UJCRGAV UJCRG ] UYKVEJ UJCRG VCI ] ECUG4 6#0). ] FQWDNGNGPIVJUJCRG FKOGPUKQPUTGEVCPINGNGPIVJ FQWDNGYKFVJUJCRG FKOGPUKQPUTGEVCPINGYKFVJ TGVWTPNGPIVJ YKFVJ _ ECUG +4 . ] FQWDNGTUJCRG FKOGPUKQPUEKTENGTCFKWU TGVWTP/A2+  T T  _ FGHCWNVTGVWTP 0KGRTCYKFđQY[\PCE\PKM  _ _ 92 Efektywne programowanie w języku Java Projektanci języka Java postanowili nie wprowadzać konstrukcji WPKQP, ponieważ ist- nieje dużo lepszy mechanizm definiowania typów umożliwiających reprezentowanie różnych typów — dziedziczenie. Unie z dyskryminatorem są jedynie mało wydajną imitacją hierarchii klas. Aby zamienić unię z dyskryminatorem na hierarchię klas, należy zdefiniować klasę abstrakcyjną zawierającą metody abstrakcyjne dla każdej operacji, której działanie jest zależne od wartości znacznika. We wcześniejszym przykładzie przedstawiona była tylko jedna taka operacja — CTGC. Ta klasa abstrakcyjna staje się korzeniem hie- rarchii klas. Jeżeli istnieją inne operacje, niezależne od wartości znacznika, należy zdefiniować odpowiednie metody w klasie bazowej. Podobnie, jeżeli w unii z dyskrymi- natorem istnieją pola danych poza znacznikiem i unią, reprezentujące typy danych wspólne dla wszystkich typów, powinny być one dodane do klasy bazowej. W naszym przykładzie nie było żadnych składników niezależnych god typów. Następnie dla każdego typu reprezentowanego w unii z dyskryminatorem definiujemy klasy, dziedziczące z klasy bazowej. W naszym przykładzie typami tymi są: koło i prosto- kąt. W każdej z klas podrzędnych należy umieścić pola danych odpowiednie dla jej typu. W naszym przykładzie dla koła jest to promień, a dla prostokąta długość i szerokość. Na koniec definiujemy odpowiednie implementacje metod abstrakcyjnych z klasy bazowej. Poniżej przedstawiamy hierarchię klas dla naszego prgzykładu unii z dyskryminatorem: CDUVTCEVENCUU5JCRG] CDUVTCEVFQWDNGCTGC  _ ENCUU KTENGGZVGPFU5JCRG] HKPCNFQWDNGTCFKWU  KTENG FQWDNGTCFKWU ]VJKUTCFKWUTCFKWU_ FQWDNGCTGC ]TGVWTP/CVJ2+ TCFKWU TCFKWU_ _ ENCUU4GEVCPINGGZVGPFU5JCRG] HKPCNFQWDNGNGPIVJ HKPCNFQWDNGYKFVJ 4GEVCPING FQWDNGNGPIVJFQWDNGYKFVJ ] VJKUNGPIVJNGPIVJ VJKUYKFVJYKFVJ _ FQWDNGCTGC ]TGVWTPNGPIVJ YKFVJ_ _ Hierarchia klas posiada wiele zalet w porównaniu z unią z dyskryminatorem. Najważ- niejszą z nich jest ta, że hierarchia klas zapewnia kontrolę typów. W naszym przykładzie każdy obiekt 5JCRG może być jedynie prawidłowym obiektem KTENG lub 4GEVCPING. Bardzo prosto wygenerować strukturę UJCRGAV, która będzie całkowicie nieprzydatna, ponieważ połączenie pomiędzy znacznikiem i unią nie jest wymuszane przez język programowania. Jeżeli znacznik wskazuje, że UJCRGAV reprezentuje prostokąt, ale unia Rozdział 4. ♦ Odpowiedniki konstrukcji języka C 93 została zainicjowana danymi koła, wszystko może się zdarzyć. Nawet, gdy unia z dys- kryminatorem zostanie prawidłowo zainicjowana, możliwe jest omyłkowe przekazanie jej do funkcji nieodpowiedniej dla danej wartości zngacznika. Drugą zaletą hierarchii klas jest łatwość rozszerzania, nawet o wiele niezależnie działa- jących części. Aby rozszerzyć hierarchię klas, wystarczy dodać nową kasę pochodną. Jeżeli zapomnisz zdefiniować jednej z metod abstrakcyjnych, natychmiast wskaże Ci to kompilator. Aby rozbudować unię z dyskryminatorem, należy mieć dostęp do kodu źródłowego. Musisz dodać nową wartość do typu GPWO oraz nową gałąź do instrukcji UYKVEJ w każdej funkcji operującej na unii. Na koniec musisz skompilować kod. Jeżeli w którejś funkcji zapomnisz dodać nowego przypadku, kompilator nie będzie w stanie tego wykryć. Pozostaje umieszczenie w kodzie kontroli niespodziewanych wartości znacznika i generowanie w takich sytuacjach komunikgatów błędów. Czwartą zaletą hierarchii klas jest możliwość odwzorowania naturalnych relacji hierar- chicznych pomiędzy typami, co pozwala na zwiększenie elastyczności i lepszej kontroli typów w czasie kompilacji. Załóżmy, że do naszego oryginalnego przykładu chcemy dodać obsługę kwadratów. W hierarchii klas możemy odwzorować fakt, że kwadrat jest specjalnym rodzajem prostokąta (zakładając, że obga są niezmienne): ENCUU5SWCTGGZVGPFU4GEVCPING] 5SWCTG FQWDNGUKFG ] UWRGT UKFGUKFG  _ FQWDNGUKFG ] TGVWTPNGPIVJOQľGD[èTÎYPKGľYKFVJ _ _ Przedstawiona hierarchia klas nie jest jedynym rozwiązaniem naszego problemu. Hierar- chia ta powstała po podjęciu kilku decyzji, o których warto wspomnieć. Klasy w hierar- chii, poza 5SWCTG, udostępniają swoje pola, nie oferując metod dostępowych. W przy- padku klas publicznych jest to nie do zaakceptowania, ale nam zależało na zwięzłości kodu (temat 19.). Klasy te są niezmienne. Czasami nie jest to najlepsze, jednak naj- częściej właśnie takie rozwiązanie jest właściwe (temagt 13.). Ponieważ język Java nie zawiera konstrukcji WPKQP, można uważać, że nie ma niebez- pieczeństwa utworzenia unii z dyskryminatorem. Możliwe jest jednak napisanie kodu, który będzie posiadał te same wady. Jeżeli kiedykolwiek będziesz chciał napisać klasę z polem znacznikowym, należy pomyśleć o eliminacji pola znacznikowego przez mo- dyfikację hierarchii klas. Innym zastosowaniem konstrukcji WPKQP w języku C, całkowicie niezwiązanym z uniami z dyskryminatorem, jest możliwość oglądania wewnętrznej reprezentacji danych poprzez umyślne omijanie systemu typów. Metoda ta demonstrowana jest przez poniższy fragment kodu w języku C, który drukuje wewnętrzną postać liczgby HNQCV w postaci szesnastkowej: WPKQP] HNQCVH KPVDKVU 94 Efektywne programowanie w języku Java _UNGC\G UNGC\GHG 7OKGU\E\GPKGFCP[EJYLGFP[O\RÎNWPKK  RTKPVH Z PUNGC\GDKVU  KQFE\[VCPKG\FTWIKGIQ  Choć może być to użyteczne, szczególnie dla programistów systemowych, takie nieprze- nośne zastosowanie nie ma odpowiednika w języku Java. Działanie takie nie może być dopuszczalne w języku, który gwarantuje bezpieczgeństwo typów i nieomal izoluje programistów od wewnętrznej reprezentacji danych. Pakiet LCXCNCPI zawiera metody pozwalające przekształcić liczby zmiennoprzecin- kowe na ich bitową reprezentację, ale działanie tych metod jest bardzo dokładnie zde- finiowane w celu zapewnienia ich przenośności. Poniższy fragment kodu jest luźnym odpowiednikiem przedstawionego kodu w języku C, ale gwarantuje uzyskanie iden- tycznych wyników bez względu na platformę, na której gjest uruchomiony: 5[UVGOQWVRTKPVNP +PVGIGTVQ*GZ5VTKPI (NQCVHNQCV6Q+PV$VKVU GH  Temat 21. Zastępowanie konstrukcji enum za pomocą klas Konstrukcja GPWO również nie została przeniesiona do języka Java. Konstrukcja ta służy do definiowania typu wyliczeniowego — typu, składającego się ze stałego zbioru wartości. Niestety, konstrukcja ta nie jest zbyt zaawansowana. Definiuje ona tylko zbiór nazwanych stałych typu KPVGIGT, nie zapewniając żadnego mechanizmu kontroli typów. W języku C można wykonać następujące wyrażenia: V[RGFGHGPWO](7,+2+22+0)4#00;A5/+6*_CRRNGAV TQF\CLGLCDđGM  V[RGFGHGPWO]0#8 .6 /2. $.11 _QTCPIGAV TQF\CLGRQOCTCēE\[V  QTCPIGAVO[(CXQTKVG2+22+0 /KGU\CPKGLCDđGM\RQOCTVCēE\COK  ale takie jest nieprawidłowe: QTCPIGAVZ (7,+2+22+0 6 /2.  Konstrukcja GPWO nie zawiera przestrzeni nazw dla tworzących ją stałych. Dlatego poniż- sza deklaracja, zawierająca użytą już nazwę, pozostaje w konflikcie z deklaracją typu QTCPIGAV: V[RGFGHGPWO]$.11 59 #66 #45_HNWKFAV Typy definiowane za pomocą konstrukcji GPWO są niepewne. Dodanie stałych do takiego typu bez ponownej kompilacji klientów powoduje nieprzewidywalne działanie, nie- zależnie od tego, jak dokładnie są sprawdzane istniejące wartości stałych. Poszcze- gólne zespoły nie mogą niezależnie dodawać stałych do tych typów, ponieważ nowe typy wyliczeniowe bardzo często są ze sobą w konflikcie. Konstrukcja GPWO nie zapew- nia żadnego mechanizmu, ułatwiającego zamianę stałych wyliczanych na ciągi znaków lub przeglądanie stałych w typie. Rozdział 4. ♦ Odpowiedniki konstrukcji języka C 95 Niestety, najczęściej spotykany sposób emulowania typu wyliczeniowego w języku Java posiada wszystkie wady konstrukcji GPWO z języka C: URQUÎDGOWNCELKGOWP\CRQOQEæKPVRTQDNGOCV[E\P[ RWDNKEENCUU2NC[KPI CTF] RWDNKEUVCVKEHKPCNKPV57+6A .7$5 RWDNKEUVCVKEHKPCNKPV57+6A +#/10 5 RWDNKEUVCVKEHKPCNKPV57+6A* #465 RWDNKEUVCVKEHKPCNKPV57+6A52# 5  _ Możesz się również spotkać z odmianą tego wzorca, wykorzystującą stałe typu 5VTKPI. Wariant taki nigdy nie powinien być używany. Choć pozwala na bezpośrednie dru- kowanie nazw stałych, może powodować obniżenie wydajności, ponieważ korzysta z porównywania ciągów. Dodatkowo niedoświadczeni użytkownicy mogą wbudować stałe w kod zamiast korzystania z odpowiednich nazw pól. Jeżeli taka stała posiada błąd (literówkę), to błąd ten nie będzie wykryty w czasie kompilacji i będzie powodował powstanie błędów wykonania. Na szczęście język Java pozwala na utworzenie innej metody emulacji typu GPWO, która nie posiada wszystkich wad użycia poprzedniej metody z wartościami KPV lub 5VTKPI, a ponadto ma kilka dodatkowych zalet. Jest ona nazywana bezpiecznym typem wyli- czeniowym. Typ ten nie jest niestety zbyt dobrze znany. Pomysł jest prosty — należy zdefiniować klasę, reprezentującą pojedynczy element typu wyliczeniowego, nie defi- niując publicznego konstruktora. Zamiast tego należy udostępnić publiczne pola sta- tyczne typu HKPCN, po jednym dla każdej ze stałych typu wyliczeniowego. Wzorzec ten w najprostszej postaci wygląda następująco: DG\RKGE\P[V[RY[NKE\GPKQY[ RWDNKEENCUU5WKV] RTKXCVGHKPCN5VTKPIPCOG RTKXCVG5WKV 5VTKPIPCOG ]VJKUPCOGPCOG_ RWDNKE5VTKPIVQ5VTKPI ]TGVWTPPCOG_ RWDNKEUVCVKEHKPCN5WKV .7$5PGY5WKV ENWDU V RWDNKEUVCVKEHKPCN5WKV +#/10 5PGY5WKV FKCOQPFU  RWDNKEUVCVKEHKPCN5WKV* #465PGY5WKV JGCTVU V RWDNKEUVCVKEHKPCN5WKV52# 5PGY5WKV URCFGU V _ Ponieważ klienci nie mogą utworzyć obiektów tej klasy ani jej rozszerzać, nie mogą istnieć inne obiekty tego typu poza udostępnianymi przez pola statyczne. Choć klasa nie jest zadeklarowana jako HKPCN, nie można po niej dziedziczyć — konstruktor klasy pochodnej musi wywołać konstruktor klasy bazowej, ag on jest niedostępny. Jak można się domyślić na podstawie nazwy, ten wzorzec pozwala na sprawdzanie typów w czasie kompilacji. Jeżeli zadeklarujesz metodę z parametrem typu 5WKV, masz pewność, że każda referencja różna od PWNN będzie prawidłowym obiektem, repre- zentującym jedną ze stałych. Wszystkie próby przekazania obiektu o niewłaściwym typie zostaną wykryte w czasie kompilacji, podobnie jak próby przypisania wyrażenia 96 Efektywne programowanie w języku Java jednego typu wyliczeniowego do zmiennej innego typu. Można tworzyć wiele typów wyliczeniowych z identycznie nazywającymi się stałymi, ponieważ każda klasa posiada swoją przestrzeń nazw. Do takiej reprezentacji typu wyliczeniowego można dodawać kolejne stałe i nie powo- duje to konieczności rekompilacji klientów, ponieważ publiczne statyczne pola, zawiera- jące referencje do stałych, izolują klientów od klasy, realizującej typ wyliczeniowy. Same stałe nie są wkompilowywane w kod klienta, tak jak często zdarza się to w przypadku realizacji konstrukcji GPWO za pomocą rozwiązania, korzystającego z pól KPV lub 5VTKPI. Ponieważ przedstawiona realizacja typu wyliczeniowego jest zwykłą klasą, może ona przedefiniować metodę VQ5VTKPI, co pozwoli na zmianę wartości zmiennych na postać nadającą się do wydrukowania. Jeżeli jest to potrzebne, można również zwracać komu- nikaty w odpowiednim języku. Zwróć uwagę, że ciągi znaków są wykorzystywane jedy- nie przez metodę VQ5VTKPI. Nie są używane do porównywania obiektów, ponieważ odziedziczona po klasie 1DLGEV metoda GSWCNU porównuje referencje do obiektów. Można również dodać do klasy implementującej typ wyliczeniowy dowolne metody, jakie mogą być potrzebne. W naszej klasie 5WKV może przydać się metoda zwracająca kolor lub rysunek, skojarzony z odpowiednią stałą. Klasa była początkowo prostą reali- zacją typu wyliczeniowego i z czasem zaczęła się zamieniać w bogatą w funkcję abs- trakcję opisywanego obiektu. Ponieważ do klasy implementującej typ wyliczeniowy można dodawać dowolne metody, może ona implementować interfejsy. Na przykład załóżmy, że chcemy, aby nasza klasa 5WKV implementowała interfejs QORCTCDNG, co pozwoli klasom sortować karty według koloru. Przedstawimy teraz modyfikację oryginalnego wzorca, która imple- mentuje ten interfejs. Zmienna statyczna PGZV1TFKPCN używana jest do przypisywania numeru kolejnego dla każdego z tworzonych obiektów. Numery te są używane przez metodę EQORCTG6Q do porządkowania obiektów. 6[RY[NKE\GPKQY[PWOGTQYCP[ RWDNKEENCUU5WKVKORNGOGPVU QORCTCDNG] RTKXCVGHKPCN5VTKPIPCOG 0WOGTMQNGLP[VYQT\QPGIQMQNQTW RTKXCVGUVCVKEKPVPGZV1TFKPCN 2T\[RKUCPKGPWOGTWMQNGLPGIQFQMQNQTW RTKXCVGHKPCNKPVQTFKPCNPGZV1TFKPCN  RTKXCVG5WKV 5VTKPIPCOG ]VJKUPCOGPCOG_ RWDNKE5VTKPIVQ5VTKPI ]TGVWTPPCOG_ RWDNKEKPVEQORCTG6Q 1DLGEVQ ] TGVWTPQTFKPCN 5WKV Q QTFKPCN _ RWDNKEUVCVKEHKPCN5WKV .7$5PGY5WKV ENWDU V RWDNKEUVCVKEHKPCN5WKV +#/10 5PGY5WKV FKCOQPFU  RWDNKEUVCVKEHKPCN5WKV* #465PGY5WKV JGCTVU  RWDNKEUVCVKEHKPCN5WKV52# 5PGY5WKV URCFGU V _ Rozdział 4. ♦ Odpowiedniki konstrukcji języka C 97 Ponieważ stałe typu wyliczeniowego są obiektami, można umieszczać je w kolekcjach. Załóżmy na przykład, że chcemy w klasie 5WKV udostępnić niezmienną listę kolorów w standardowym porządku. Wystarczy dodać do klasy deklgarację dwóch klas pól. RTKXCVGUVCVKEHKPCN5WKV=?24+8#6 A8#.7 5 ] .7$5 +#/10 5* #46552# 5_ RWDNKEUVCVKEHKPCN.KUV8#.7 5  QNNGEVKQPUWPOQFKHKCDNG.KUV #TTC[UCU.KUV 24+8#6 A8#.7 5  W przeciwieństwie do najprostszej postaci typu wyliczeniowego klasy w wersji korzy- stającej z numerów kolejnych mogą być serializowane (rozdział 9.) z zachowaniem szczególnej ostrożności. Nie wystarczy dodać do deklaracji klasy klauzuli KORNGOGPVU 5GTKCNK\CDNG. Wymagane jest również utworzenie metody TGCF4GUQNXG (temat 57.). RTKXCVG1DLGEVTGCF4GUQNXG VJTQYU1DLGEV5VTGCO ZEGRVVKQP] TGVWTP24+8#6 A8#.7 5=QTFKPCN? OKCPCPCRQUVCèMCPQPKEV\Pæ _ Metoda ta, wywołana automatycznie przez system serializacji, zabezpiecza przed powie- laniem istniejących stałych, powstałych w procesie deserializacji. Zapewnia ona, że będzie istniał tylko jeden obiekt dla każdej stałej typu wyliczeniowego, co pozwala uniknąć konieczności przedefiniowywania metody 1DLGEVGSWCNU. Bez tej gwarancji metoda 1DLGEVGSWCNU będzie zwracała nieprawidłowe wyniki, jeżeli będzie porównywała dwie równe stałe, ale o różnych kolejnych numerach. Zwróć uwagę, że metoda TGCF4G UQNXG korzysta z tablicy 24+8#6 A8#.7 5, więc musisz zadeklarować tę tablicę, nawet jeżeli nie chcesz udostępniać kolekcji 8#.7 5. Należy również zwrócić uwagę, że pole PCOG nie jest wykorzystywane przez metodę TGCF4GUQNXG, więc jest ono nietrwałe i takie powinno pozostać. Wynikowa klasa jest jednak krucha — konstruktor dla dowolnej nowej wartości musi występować po wszystkich istniejących wartościach, dzięki czemu zapewniamy, że wszystkie serializowane wcześniej obiekty nie zmienią swoich wartości w trakcie dese- rializacji. Dzieje się tak, ponieważ postać serializowana stałych (temat 55.) opiera się jedynie na ich kolejnych numerach. Jeżeli stała, będąca składnikiem typu wyliczenio- wego, zmieni swój numer kolejny, stała o tym numerze poddana serializacji zmieni swoją wartość podczas procesu deserializacji. Może istnieć jedna lub więcej operacji skojarzonych z każdą ze stałych, które są wyko- rzystywane jedynie wewnątrz pakietu, zawierającego klasę realizującą typ wyliczeniowy. Operacje takie najlepiej implementować jako metody prywatne w ramach pakietu. Każda stała typu wyliczeniowego zawiera ukrytą kolekcję operacji, pozwalających reagować odpowiednio dla odpowiednich stałych. Jeżeli klasa implementująca typ wyliczeniowy posiada metody, których działanie znacznie się różni w zależności od wartości stałej, powinieneś skorzystać z osobnych klas pry- watnych lub anonimowych klas, zagnieżdżonych dla każdej ze stałych. Pozwala to każdej ze stałych posiadać własną implementację danej metody i automatycznie wywoływać odpowiednią implementację. Alternatywą jest tworzenie wielościeżkowych rozgałęzień, które wybierają odpowiednią metodę w zależności od stałej, na rzecz której wywołu- jemy tę metodę. Jest to rozwiązanie niechlujne, podatne na błędy i często wydajność tego rozwiązania jest niższa od rozwiązania, korzystającego z automatycznego wybie- rania metod przez maszynę wirtualną. 98 Efektywne programowanie w języku Java Obie techniki opisane w poprzednim akapicie są zilustrowane jeszcze jedną klasą, reali- zującą typ wyliczeniowy. Klasa ta, 1RGTCVKQP, reprezentuje działania wykonywane przez prosty kalkulator czterodziałaniowy. Poza pakietem, w którym zdefiniowana jest ta klasa, można skorzystać ze stałych klasy 1RGTCVKQP do wywołania metod klasy 1DLGEV (VQ5VTKPI, JCUJ QFG, GSWCNU itd.). Wewnątrz pakietu można dodatkowo wykonywać operacje matematyczne związane ze stałymi. Przypuszczalnie pakiet może eksportować obiekt kalkulatora udostępniający jedną lub więcej metod, które jako parametrów ocze- kują stałych klasy 1RGTCVKQP. Zwróć uwagę, że sama klasa 1RGTCVKQP jest klasą abstrak- cyjną, zawierającą jedną prywatną w ramach pakietu metodę abstrakcyjną — GXCN, która wykonuje odpowiednią operację matematyczną. Dla każdej stałej zdefiniowana jest wewnętrzna klasa anonimowa, więc każda stała może zdefiniować własną wersję metody GXCN. 6[RY[NKE\GPKQY[\QRGTCELCOKUMQLCT\QP[OK\GUVCđ[OK RWDNKECDUVTCEVENCUU1RGTCVKQP] RTKXCVGHKPCN5VTKPIPCOG 1RGTCVKQP 5VTKPIPCOG ]VJKUPCOGPCOG_ RWDNKE5VTKPIVQ5VTKPI ]TGVWTPVJKUPCOG_ 9[MQPCLQRGTCELúOCVGOCV[E\PæFNCDKGľæEGIQMQPVGMUVW CDUVTCEVFQWDNGGXCN FQWDNGZFQWDNG[  RWDNKEUVCVKEHKPCN1RGTCVKQP2.75PGY1RGTCVKQP V  ] FQWDNGGXCN FQWDNGZFQWDNG[ ]TGVWTPZ [V_ _ RWDNKEUVCVKEHKPCN1RGTCVKQP/+075PGY1RGTCVKQP V ] FQWDNGGXCN FQWDNGZFQWDNG[ ]TGVWTPZ[_ _ RWDNKEUVCVKEHKPCN1RGTCVKQP6+/ 5PGY1RGTCVKQP V  ] FQWDNGGXCN FQWDNGZFQWDNG[ ]TGVWTPZ [V_ _ RWDNKEUVCVKEHKPCN1RGTCVKQP +8+ A$; PGY1RGTCVKQP  ] FQWDNGGXCN FQWDNGZFQWDNG[ ]TGVWTPVZ[_ _ _ Przedstawiony typ wyliczeniowy ma wydajność porównywalną do wydajności klasy korzystającej ze stałych wyliczeniowych typu KPV. Dwa różne obiekty klasy repre- zentującej typ wyliczeniowy nigdy nie reprezentują tej samej wartości, więc porówna- nie referencji, które jest bardzo szybkie, wystarczy do sprawdzenia równości logicznej. Klienci klasy mogą nawet użyć operatora  zamiast metody GSWCNU — wynik będzie identyczny, a operator  może nawet działać szybciej. Jeżeli klasa typu wyliczeniowego jest przydatna, może być klasą najwyższego poziomu — jeżeli jest związana z inną klasą najwyższego poziomu, powinna być statyczną klasą zagnieżdżoną tej klasy (temat 18.). Na przykład klasa LCXCOCVJ$KI GEKOCN zawiera zbiór stałych wyliczeniowych typu KPV, określających tryby zaokrąglania Rozdział 4. ♦ Odpowiedniki konstrukcji języka C 99 części dziesiętnych. Te tryby zaokrąglania stanowią użygteczny model abstrakcji, który nie jest zasadniczo przywiązany do klasy $KI GEKOCN — byłoby lepiej utworzyć osobną klasę LCXCOCVJ4QWPFKPI/QFGU. Udostępnienie takiej klasy wszystkim programistom korzystającym z trybów zaokrągleń pozwoliłoby na zwiększenie spójności między różnymi API. Podstawowa implementacja wzorca bezpiecznego typu wyliczeniowego, zilustrowana za pomocą dwóch implementacji klasy 5WKV, jest zamknięta. Użytkownicy nie mogą dodawać nowych elementów typu wyliczeniowego, ponieważ klasa nie udostępnia im konstruktora. Powoduje to, że klasa zachowuje się tak, jakby została zdefiniowana jako HKPCN. Najczęściej jest to najlepsze rozwiązanie, ale istnieją przypadki, w których chcemy utworzyć rozszerzalną klasę typu wyliczeniowego. Może być to potrzebne na przykład do reprezentowania formatów kodowania rysunków, gdy chcesz, aby inni pro- gramiści mogli umożliwiać obsługę nowych formatów. Aby umożliwić rozszerzanie typu wyliczeniowego, wystarczy udostępnić zabezpie- czony konstruktor. Użytkownicy będą mogli dzięki temu dziedziczyć po istniejącej klasie, dodając w podklasach własne stałe. Nie musisz się obawiać, że stałe typu wyli- czeniowego spowodują konflikt, tak jak było to w przypadku typu wyliczeniowego, korzystającego ze stałych KPV. Rozszerzalny wariant wzorca bezpiecznego typu wyli- czeniowego korzysta z przestrzeni nazw pakietu do tworzenia „magicznie admini- strowanych” przestrzeni nazw dla rozszerzalnych wyliczeń. Różne zespoły mogą nie- zależnie rozszerzać wyliczenia i nie pozostaną one niggdy w konflikcie. Dodanie elementu do rozszerzalnego typu wyliczeniowego nie gwarantuje jeszcze, że nowe elementy będą w pełni obsługiwane. Metody operujące na elementach typu wyli- czeniowego muszą przewidywać ewentualność przekazania elementu nieznanego pro- gramiście. Wielokrotne rozgałęzienia są dyskusyjne w przypadku zamkniętego typu wyliczeniowego, a w przypadku typu rozszerzalnego są nie do przyjęcia, ponieważ nie mogą samoczynnie dodawać nowej gałęzi dla typu, dodanego przez programistę rozszerzającego klasę. Jedynym sposobem radzenia sobie z tym problemem jest wyposażenie klasy bezpiecz- nego typu wyliczeniowego we wszystkie metody niezbędne do opisania zachowania się stałych tej klasy. Metody niezbyt użyteczne dla klientów powinny być zadeklaro- wane jako RTQVGEVGF w celu ich ukrycia, natomiast klasy pochodne mogą je przedefi- niować. Jeżeli metoda taka nie ma rozsądnego działania domyślnego, oprócz RTQVGEVGF powinna być również zadeklarowana jako CDUVTCEV. Dla rozszerzalnych klas bezpiecznego typu wyliczeniowego dobrze jest przedefinio- wać metody GSWCNU i JCUJ QFG jako metody finalne, wywołujące odpowiednie metody z klasy 1DLGEV. Zapewni to, że żadna podklasa przypadkowo nie przedefiniuje tych metod, co zagwarantuje, że wszystkie równe obiekty są również identyczne (CGSWCNU D tylko wtedy, gdy CD): /GVQF[\CDG\RKGE\CLæEGRT\GFRT\GUđCPKCPKGO RWDNKEHKPCNDQQNGCPGSWCNU 1DLGEVVJCV ] TGVWTPUWRGTGSWCNU VJCV  _ 100 Efektywne programowanie w języku Java RWDNKEHKPCNKPVJCUJ QFG ] TGVWTPUWRGTJCUJ QFG  _ Trzeba pamiętać, że wariant rozszerzalny nie jest zgodny z wariantem implementują- cym interfejs QORCTCDNG — jeżeli będziesz próbował je ze sobą połączyć, porządko- wanie elementów w podklasach będzie działało w porządku inicjalizacji podklas, co może się zmieniać w różnych programach i w różnych wywogłaniach. Rozszerzalny wariant wzorca bezpiecznego typu wyliczeniowego jest zgodny z warian- tem implementującym interfejs 5GTKCNK\CDNG, ale łączenie tych wariantów wymaga nieco uwagi. Każda podklasa musi przypisywać własne numery kolejne i napisać wła- sną metodę TGCF4GUQNXG. Zasadniczo każda klasa jest odpowiedzialna za serializację i deserializację swoich obiektów. Przedstawiamy teraz kolejną wersję klasy 1RGTCVKQP, która jest rozszerzalna i może być serializowana. 5GTKCNK\QYCNP[TQ\U\GT\CNP[DG\RKGE\P[V[RY[NKEI\GPKQY[ RWDNKECDUVTCEVENCUU1RGTCVKQPKORNGOGPVU5GTKCNK\CDNVG] RTKXCVGHKPCNVTCPUKGPV5VTKPIPCOG RTQVGEVGF1RGTCVKQP 5VTKPIPCOG ]VJKUPCOGPCOG_ RWDNKEUVCVKE1RGTCVKQP2.75PGY1RGTCVKQP   ] RTQVGEVGFFQWDNGGXCN FQWDNGZFQWDNG[ ]TGVVWTPZ [_ _ RWDNKEUVCVKE1RGTCVKQP/+075PGY1RGTCVKQP  ] RTQVGEVGFFQWDNGGXCN FQWDNGZFQWDNG[ ]TGVVWTPZ[_ _ RWDNKEUVCVKE1RGTCVKQP6+/ 5PGY1RGTCVKQP   ] RTQVGEVGFFQWDNGGXCN FQWDNGZFQWDNG[ ]TGVVWTPZ [_ _ RWDNKEUVCVKE1RGTCVKQP +8+ PGY1RGTCVKQP  ] RTQVGEVGFFQWDNGGXCN FQWDNGZFQWDNG[ ]TGVVWTPZ[_ _ 9[MQPCLQRGTCELúOCVGOCV[E\PæFNCDKGľæEGIQMQPVGMUVW RTQVGEVGFCDUVTCEVFQWDNGGXCN FQWDNGZFQWDNG[  RWDNKE5VTKPIVQ5VTKPI ]TGVWTPVJKUPCOG_  CDNQMQYCPKGOQľNKYQħEKRT\GFGHKPKQY[YCPKC1DLGEVGISWCNURT\G\MNCU[ RQEJQFPG RWDNKEHKPCNDQQNGCPGSWCNU 1DLGEVVJCV ] TGVWTPUWRGTGSWCNU VJCV  _ RWDNKEHKPCNKPVJCUJ QFG ] TGVWTPUWRGTJCUJ QFG  _ RQPKľU\GFGMNCTCELGUæPKG\DúFPGFNCUGTKCNKI\CELK RTKXCVGUVCVKEKPVPGZV1TFKPCN RTKXCVGHKPCNKPVQTFKPCNPGZV1TFKPCN  RTKXCVGUVCVKEHKPCN1RGTCVKQP=?8#.7 5]2.75/+0756+/ 5V +8+ _ 1DLGEVTGCF4GUQNXG VJTQYU1DLGEV5VTGCO ZEGRVKQP] TGVWTP8#.7 5=QTFKPCN? OKCPCPCRQUVCèMCPQPKE\PVæ _ _ Rozdział 4. ♦ Odpowiedniki konstrukcji języka C 101 Przedstawimy również podklasę klasy 1RGTCVKQP, która dodaje operacje logarytmowa- nia i podnoszenia do potęgi. Ta klasa pochodna może być zdefiniowana poza pakietem zawierającym klasę bazową. Powinna być ona publiczna i umożliwiać dziedziczenie. Możliwe jest utworzenie wielu niezależnych podklas, współistniejących bez żadnych konfliktów. 2QFMNCUCUGTKCNK\QYCNPGIQTQ\U\GT\CNPGIQDG\RKGEI\PGIQV[RWY[NKE\GPKQYGIQ CDUVTCEVENCUU ZVGPFGF1RGTCVKQPGZVGPFU1RGTCVKQP]  ZVGPFGF1RGTCVKQP 5VTKPIPCOG ]UWRGT PCOG _ RWDNKEUVCVKE1RGTCVKQP.1)PGY ZVGPFGF1RGTCVKQP NVQI ] RTQVGEVGFFQWDNGGXCN FQWDNGZFQWDNG[ ] TGVWTP/CVJNQI [ /CVJNQI Z  _ _ RWDNKEUVCVKE1RGTCVKQP :2PGY ZVGPFGF1RGTCVKQP VGZR ] RTQVGEVGFFQWDNGGXCN FQWDNGZFQWDNG[ ] TGVWTP/CVJRQY Z[  _ _ RQPKľU\GFGMNCTCELGUæPKG\DúFPGFNCUGTKCNKI\CELK RTKXCVGUVCVKEKPVPGZV1TFKPCN RTKXCVGHKPCNKPVQTFKPCNPGZV1TFKPCN  RTKXCVGUVCVKEHKPCN1RGTCVKQP=?8#.7 5].1) :2_ 1DLGEVTGCF4GUQNXG VJTQYU1DLGEV5VTGCO ZEGRVKQP] TGVWTP8#.7 5=QTFKPCN? OKCPCPCRQUVCèMCPQPKE\PVæ _ _ Zwróć uwagę, że metoda TGCF4GUQNXG w przedstawionej klasie nie jest prywatna, a tylko prywatna w ramach pakietu. Jest to niezbędne, ponieważ obiekty 1RGTCVKQP i ZVGP FGF1RGTCVKQP są właściwie obiektami podklas anonimowych, więc prywatna metoda TGCF4GUQNXG nie może zostać zastosowana (temat 57.). Wzorzec bezpiecznego typu wyliczeniowego posiada jednak nieco więcej wad w porów- naniu do typu wyliczeniowego, korzystającego z wartości KPV. Prawdopodobnie jedyną poważną niedogodnością jest niewygodne łączenie bezpiecznych stałych wyliczeniowych w zbiory. W przypadku typu wyliczeniowego, korzystającego z wartości KPV, jest to zwykle realizowane przez nadanie stałym wyliczeniowym wartości będących potęgami dwójki i reprezentowanie zbioru jako bitowej sumy lgogicznej odpowiednich stałych: 1FOKCPCV[RWY[NKE\GPKQYGIQMQT\[UVCLæEGIQ\DKVQIY[EJYCTVQħEKNKE\DKPV RWDNKEUVCVKEHKPCNKPV57+6A .7$5 RWDNKEUVCVKEHKPCNKPV57+6A +#/10 5 RWDNKEUVCVKEHKPCNKPV57+6A* #465 RWDNKEUVCVKEHKPCNKPV57+6A52# 5 RWDNKEUVCVKEHKPCNKPV57+6A$.# -57+6A .7$5^57+6A52# 5 Reprezentowanie w ten sposób zbiorów stałych typu wyliczeniowego jest zwięzłe i nie- zmiernie szybkie. W przypadku zbiorów stałych bezpiecznego typu wyliczeniowego możesz skorzystać z implementacji zbioru z biblioteki QNNGEVKQPU, ale nie jest to tak zwięzłe i szybkie. 102 Efektywne programowanie w języku Java 5GVDNCEM5WKVUPGY*CUJ5GV  DNCEM5WKVUCFF 5WKV .7$5  DNCEM57+65CFF 57+652# 5  Choć zbiory stałych prawdopodobnie nie mogą być zrealizowane ani tak zwięźle, ani tak szybko, jak zbiory stałych wyliczeniowych typu KPV, możliwe jest zmniejszenie tej różnicy przez utworzenie specjalnej implementacji klasy 5GV, która akceptuje jedynie elementy jednego typu i wewnętrznie reprezentuje zbiór jako wektor bitów. Taki zbiór najlepiej zdefiniować w tym samym pakiecie, co klasa reprezentująca przechowywane elementy, co umożliwia dostęp do pól lub metod prywatnych w ramach pakietu. Roz- sądne jest utworzenie publicznych konstruktorów, które jako parametry akceptują krótkie sekwencje elementów, dzięki czemu możliwe jest tworzgenie następujących wyrażeń: JCPFKUECTF PGY5WKV5GV 5WKV .7$55WKV52# 5  Mniejszą niedogodnością bezpiecznego typu wyliczeniowego, w porównaniu do typu korzystającego z typu KPV, jest to, że bezpieczny typ wyliczeniowy nie może być wyko- rzystany w instrukcji UYKVEJ, ponieważ nie są to wartości całkowite. Zamiast tego należy skorzystać z instrukcji KH: KH UWKV5WKV .7$5 ]  _GNUGKH UWKV5WKV +#/10 5 ]  _GNUGKH UWKV5WKV* #465 ]  _GNUGKH UWKV5WKV52# 5 ]  _GNUG] VJTQYPGY0WNN2QKPVGT ZEGRVKQP 2WUV[MQNQT UWKVVPWNN _ Instrukcja KH może nie wykonywać się tak szybko jako UYKVEJ, ale różnica nie powinna być zauważalna. Tworzenie wielokrotnych rozgałęzień w przypadku bezpiecznego typu wyliczeniowego powinno być rzadkie, ponieważ rozgałęzianie kodu należy zastę- pować automatycznym wyborem metod przez maszynę wirtualną, tak jak w przyto- czonej klasie 1RGTCVKQP. Kolejny niewielki problem wiąże się ze spadkiem wydajności, ponieważ potrzeba czasu i miejsca w pamięci na załadowanie klasy typu wyliczeniowego i utworzenie obiek- tów stałych. Problem ten nie powinien być zauważalny w praktyce, poza niektórymi urządzeniami o ograniczonych zasobach, jak na przykgład telefony komórkowe czy tostery. Podsumujmy. Przewagi bezpiecznego typu wyliczeniowego nad typem korzystającym z wartości KPV są bezapelacyjne i żadna z wad nie może być przyczyną zaprzestania korzystania z niego, chyba że jest on głównie używany jako element zbioru lub w środo- wisku o ograniczonych zasobach. Dlatego bezpieczny typ wyliczeniowy powinien być jako pierwszy brany pod uwagę, jeśli konieczne jest zastosowania typu wylicze- niowego. API korzystające z bezpiecznego typu wyliczeniowego jest dużo łatwiejsze w użyciu dla programistów niż to, które korzysta z typu KPV. Jedynym powodem, dla którego bezpieczny typ wyliczeniowy nie jest intensywniej wykorzystywany w bibliote- kach platformy Java, jest fakt, że wzorzec ten nie był jeszcze znany w czasie tworzenia Rozdział 4. ♦ Odpowiedniki konstrukcji języka C 103 większości klas tego API. Na koniec należy przypomnieć, że stosowanie typu wyli- czeniowego powinna być dosyć rzadkie, ponieważ w większości zastosowań tego typu z powodzeniem można skorzystać z dziedziczenia (tematg 20.). Temat 22. Zastępowanie wskaźników do funkcji za pomocą klas i interfejsów Język C pozwala na stosowanie wskaźników do funkcji, które umożliwiają zapamięty- wanie i przesyłanie odwołań do określonych funkcji. Wskaźniki do funkcji są najczę- ściej wykorzystywane w celu umożliwienia funkcji wywołującej modyfikacji swojego działania poprzez przekazanie wskaźnika do drugiej funkcji, czasami nazywanej funkcją wywołania zwrotnego. Na przykład, funkcja SUQTV ze standardowej biblioteki języka C wymaga jako parametru wskaźnika do funkcji, której zadaniem jest porównywanie ele- mentów. Funkcja porównująca wymaga podania dwóch parametrów — wskaźników do elementów. Zwraca ona ujemną liczbę całkowitą, jeżeli element wskazywany przez pierwszy element jest mniejszy od drugiego, zero, gdy elementy są równe i dodatnią liczbę całkowitą, jeżeli pierwszy element jest większy od drugiego. Można uzyskać różny porządek sortowania, przekazując inną funkcję porównującą. Jest to przykład wzorca Strategy (strategia) [Gamma95, str. 315] — funkcja porównująca określa strategię sortowania elementów. Wskaźniki do funkcji nie znalazły się w języku Java, ponieważ korzystając z referencji do obiektów, można uzyskać takie samo działanie. Wywołując metodę obiektu, zwykle wykonuje się operacje na tym obiekcie. Można również zdefiniować obiekt, którego metody wykonują operacje na innym obiekcie, przekazanym jawnie do metody. Obiekt klasy udostępniającej tylko jedną taką metodę jest, efektywnie, wskaźnikiem do tej metody. Obiekty takie są zwane obiektami funkcjonalnymi. Przyjrzyjmy się takiej właśnie klasie: ENCUU5VTKPI.GPIVJ QORCTCVQT] RWDNKEKPVEQORCTG 5VTKPIU5VTKPIU ] TGVWTPUNGPIVJ UNGPIVJ  _ _ Klasa ta udostępnia jedną metodę, która oczekuje dwóch ciągów znaków jako parame- trów. Zwraca ona liczbę ujemną w przypadku, gdy pierwszy ciąg jest krótszy od dru- giego, zero, gdy ciągi są równe i liczbę dodatnią, gdy pierwszy ciąg jest dłuższy od drugiego. Metoda ta jest komparatorem, porównującym długości ciągów. Referencja do obiektu 5VTKPI.GPIVJ QORCTCVQT służy jako „wskaźnik do funkcji”, umożliwiając wywołanie metody dla dwóch podanych ciągów. Bezstanowość klasy 5VTKPI.GPIVJ QORCTCVQT jest typowa dla klas strategii — klasa ta nie posiada pól, przez co wszystkie jej obiekty są funkcjonalnie identyczne. Aby uniknąć tworzenia niepotrzebnych obiektów, możemy utworzyć tę klasę jako klasę typu singleton (temat 4., temat 2.). 104 Efektywne programowanie w języku Java ENCUU5VTKPI.GPIVJ QORCTCVQT] RTKXCVG5VTKPI.GPIVJ QORCTCVQT ]_ RWDNKEUVCVKEHKPCN5VTKPI.GPIVJ QORCTCVQT +056#0 PGY5VTKPI.GPIVJ QORCTCVQT  RWDNKEKPVEQORCTG 5VTKPIU5VTKPIU ] TGVWTPUNGPIVJ UNGPIVJ  _ _ Aby przekazać obiekt 5VTKPI.GPIVJ QORCTCVQT do metody, potrzebujemy odpowied- niego typu dla parametrów. Skorzystanie z typu 5VTKPI.GPIVJ QORCTCVQT nie będzie odpowiednie, ponieważ klienci nie będą mogli przekazywać innych strategii porów- nywania. Można natomiast zdefiniować interfejs QORCTCVQT i zmienić klasę 5VTKPI .GPIVJ QORCTCVQT, aby implementowała ten interfejs. Inaczej mówiąc, definiujemy interfejs strategii, implementowany przez konkretne klasy strategii: +PVGTHGLUUVTCVGIKK RWDNKEKPVGTHGCEG QORCTCVQT] RWDNKEKPVEQORCTG 1DLGEVQ1DLGEVQ  _ Ta definicja interfejsu QORCTCVQT znajduje się w pakiecie LCXCWVKN, ale równie dobrze możesz ją utworzyć samemu. W przypadku obiektów innych niż ciągi możliwe jest, że ich metody EQORCTG będą oczekiwały parametrów typu 1DLGEV, a nie 5VTKPI. Dlatego w celu prawidłowej implementacji interfejsu QORCTCVQT przytoczona wcześniej klasa 5VTKPI.GPIVJ QORCTCVQT musi być nieco zmodyfikowana. W celu wywołania metody NGPIVJ parametry typu 1DLGEV muszą być rzutowane na 5VTKPI. Klasy strategii są często definiowane jako klasy anonimowe (temat 18.). Poniższe wyra- żenie powoduje posortowanie tablicy ciągów według ich długości. #TTC[UUQTV UVTKPI#TTC[PGY QORCTCVQT ] RWDNKEKPVEQORCTG 1DLGEVQ1DLGEVQ ] 5VTKPIU 5VTKPI Q 5VTKPIU 5VTKPI Q TGVWTPUNGPIVJ UNGPIVJ  _ _  Ponieważ interfejsy strategii służą jako typy dla wszystkich obiektów strategii, w celu udostępnienia strategii jej klasa nie musi być publiczna. Zamiast tego „klasa główna” może udostępniać publiczne statyczne pole (lub statyczną metodę factory) typu określa- nego przez interfejs strategii, a klasa strategii może być prywatną klasą składową klasy głównej. W poniższym przykładzie pokazujemy zastosowanie statycznej klasy zagnież- dżonej, tworzącej klasę strategii do implementacji drugiego interfejsu, 5GTKCNK\CDNG. 7FQUVúRPKCPKGUVTCVGIKK ENCUU*QUV] YKúMU\QħèMNCU[RQOKPKúVC RTKXCVGUVCVKEENCUU5VT.GP OR KORNGOGPVU QORCTCVQT5GTKCNK\CDNG] Rozdział 4. ♦ Odpowiedniki konstrukcji języka C 105 RWDNKEKPVEQORCTG 1DLGEVQ1DLGEVQ ] 5VTKPIU 5VTKPI Q 5VTKPIU 5VTKPI Q TGVWTPUNGPIVJ UNGPIVJ  _ _  YTCECP[MQORCTCVQTOQľPCUGTKCNK\QYCè RWDNKEUVCVKEHKPCN QORCTCVQT 564+0)A. 0)6*A 1/2#4#614PGY5VT.GP OR  _ Klasa *QUV korzysta z tego wzorca do udostępnienia za pomocą pola 564+0)A. 0)6*A 1/2#4#614 komparatora porównującego długości ciągów. Podsumujmy. Podstawowym zastosowaniem wskaźników do funkcji jest utworzenie wzorca 5VTCVGIKC. W języku Java wzorzec ten można utworzyć, deklarując interfejs reprezentujący strategię i klasę implementującą interfejs dla konkretnej strategii. Gdy strategia jest zastosowana tylko raz, jej klasa deklarowana jest najczęściej jako klasa anonimowa. Gdy strategia jest udostępniana, jej klasa jest zwykle prywatną statyczną klasą zagnieżdżoną i jest udostępniana poprzez publiczne statyczne pole HKPCN, którego typ jest zgodny z interfejsem strategii.
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

Efektywne programowanie w języku Java
Autor:

Opinie na temat publikacji:


Inne popularne pozycje z tej kategorii:


Czytaj również:


Prowadzisz stronę lub blog? Wstaw link do fragmentu tej książki i współpracuj z Cyfroteką: