Monthly Archives: June 2009

Czy się stoi, czy się leży po raz drugi

Rok niedługo minie od czasu kiedy pisałem posta o tym samym tytule. Sporo bajtów popłynęło w internecie i oto znów pora udać się na urlop. Jednakże w tym roku w związku z szeregiem okazji (uroczystości rodzinnych) jak i zawirowań życiowych (przeprowadzka mnie w tym roku czeka), urlop został skrócony jak i również ograniczony tylko do wizyty w domu rodzinnym.

Zatem ogłaszam wszem i wobec, że od jutra mnie nie ma. Dostęp do internetu będę miał, ale pewnie nie za bardzo będę się kwapił by za często przesiadywać przed komputerem. Oby tylko pogoda dopisała (choć jadę 400 na zachód od Warszawy) – ostatnio pod tym względem cała Polska za bardzo rozpieszczana nie jest. Choć w sumie by porelaksować się z książką w dłoni nie potrzeba tropików, do spacerów po lesie wystarczy by tylko nie padało, a picie browara można uskuteczniać w każdych warunkach pogodowych 🙂

A co po powrocie? Pewnikiem koniec relacji z nauki do SCJP. Ale to nie koniec poruszania tematyki certyfikacyjnej! Dzięki porozumieniu z pewnymi mądrymi ludźmi powstaje zbiorczy materiał ze wszystkich wpisów okraszony również dodatkowymi komentarzami czy przemyśleniami. Na pewno też wrócę do poważniejszego kodowania – zapominam powoli jak to jest robić cokolwiek – cały czas nie wychodzę poza przykłady operujące na maksymalnie dwóch klasach. Pora to zmienić i tutaj wspomniany już JSF i Richfaces podlane lekką nutką Hibernate. No ale to dopiero za tydzień, na razie życzcie mi udanego urlopu.

Advertisements

SCJP, podejście dziewiąte

Przedostatni rozdział podręcznika dotyczy zagadnienia równie rozbudowanego i skomplikowanego jak kolekcje. Konkretnie chodzi o wątki – rzecz, której za bardzo nie można się nauczyć “chałupniczo”, gdyż prawdziwą potęgę wielowątkowej aplikacji widać w… no… wielowątkowej aplikacji 🙂 A takich raczej nikt nie klepie po godzinach ku radości własnej i pracy u podstaw.

Autorzy zaznaczają, że to, co przedstawili w tym rozdziale jest zaledwie wierzchołkiem góry lodowej jeśli chodzi o aplikacje wielowątkowe, zatem i nalezy podchodzić do zdobytej wiedzy ostrożnie. Jednakże i tak jest się czego nauczyć, jak i jest z czego stworzyć dziesiątki podchwytliwych pytań. Generalnie do pozytywnego uporania się z zadaniami potrzebne są dwie rzeczy – bliska znajomość z API wątków, konstruktorów oraz wywoływanych metod, wyrzucanych wyjątków i takich tam. Z drugiej zaś strony należy zrozumieć blokady (locki), aby wiedzieć kiedy i jaki wątek jest dostępny, uśpiony, ma możliwość odwołania się do wskazanego obiektu, czy co tam jeszcze w ogóle przyjdzie do głowy. Oto lista rzeczy, z którymi lepiej pozostawać w bliskiej komitywie.

  • usypianie wątku – o metodzie sleep trzeba wiedzieć dwie rzeczy. Raz – jest metodą statyczną klasy Thread. Dlatego też zawsze usypia działanie bieżącego wątku. Do tego wyrzuca ona InterruptedException, zatem musimy wywołanie metody zawrzeć w klauzuli try..catch, albo też przekazać obsługę wyjątku wyżej.
  • metoda join() – podobnie jak powyższa wyrzuca wyjątek InterruptedException. Nie jest jednakże metodą statyczą. Spójrzmy na taki kod:
    public static void main( String[] args ) {
    Thread t = new Thread( new Runnable() {
    public void run() {
    System.out.println( “Początek pętli” );
      for( double i = 0; i < 1000000000; i++ ) { } System.out.println( "Koniec pętli" ); } } ); System.out.println( "Chlebik 1:" ); t.start(); System.out.println( "Chlebik 2:" ); try { t.join(); } catch( Exception e ) { } System.out.println( "Chlebik 2:" ); } [/sourcecode] Obecnie wykonywany wątek (ten z metody main) nie wyświetli napisu Chlebik 2: dopóki nie zostanie zakończone działanie wątku reprezentowanego przez obiekt t!
  • metoda wait() – odstaje troszeczkę od powyższego towarzystwa, gdyż jest ona właściwa dla wszystkich obiektów w Javie (pochodzi z klasy Object). Nie jest statyczna, zaś jej wywołanie nie powoduje wyrzucenia wyjątku. W parze z nią idą dwie inne metody z klasy Object – notify() i notifyAll(). Wszystkie są oznaczone jako final, zatem nie potrzeba w ich przypadku karkołomnych zabaw jak z np. metodą equals.

    Idąc dalej – metody te mogą być wywołane tylko i wyłącznie w kontekście synchronized! Próby użycia poza tymże kontekstem skutkują wyrzuceniem IllegalMonitorStateException (i to nie jest sprawdzalny wyjątek więc nie trzeba definiować jego łapania). Metody te służą do zarządzania blokadami obiektu (dlatego są elementami klasy Object). Metoda wait pozwala na wstrzymanie działania wątku, który posiada blokadę obiektu, aż do wywołania przez ten obiekt metody notify, albo notifyAll. Nie za bardzo podejmuję się więcej tłumaczyć to pisząc – myślę, że kod powie więcej (wzięty z podręcznika):

    class ThreadA {
    public static void main(String [] args) {
    ThreadB b = new ThreadB();
    b.start();

    synchronized(b) {
    try {
    System.out.println(“Waiting for b to complete…”);
    b.wait();
    } catch (InterruptedException e) {}
    System.out.println(“Total is: ” + b.total);
    }
    }
    }

    class ThreadB extends Thread {
    int total;

    public void run() {
    synchronized(this) {
    for(int i=0;i<100;i++) { total += i; } notify(); } } } [/sourcecode]

  • kiedy blokada istnieje, a nie kiedy nie – to temat bardzo istotny. Oto cytat z Thinking in Java w wersji 4.

    Ważne jest, aby zrozumieć, że wywołanie metody sleep() nie zwalnia blokady obiektu, tak samo jak nie czyni tego wywołanie yield(). Z drugiej strony, wywołanie wait() zainicjowane w obrębie synchronizowanej metody wymusza zawieszenie wątku i zwolnienie blokady danego obiektu.

  • metoda getId() – na to się trochę wkurzyłem. Ni stąd, ni zowąd wyskoczyły mi pytania o tę metodę. I co? Jajco! (cytat ze znanego polskiego filmu). Może to i metoda, aby w pytaniach testowych poruszać zagadnienia, których nie było w konkretnym rozdziale. Ale to nie lepiej by było po prostu dać listę metod, o które potencjalnie jeszcze mogą paść pytania? Uczenie się całego API na pamięć to chyba nie jest cel egzaminacyjny? No nic, koniec narzekania – rzecz w metodzie getId().

    Dokumentacja mówi, że zwraca ona unikatowy identyfikator wątku (prymityw typu long). Spójrzmy na ten kod:

    // Obiekty a-a3 są tego samego typu jak ten poniżej

    Thread a4 = new Thread( new Runnable() {
    public void run() {
    for( int i = 0; i < 100000; i++ ) { if( i == 99999 ) System.out.println('.' + Thread.currentThread().getId()); }} } ); System.out.println( Thread.currentThread().getId() ); a.start(); System.out.println( a.getId() ); a2.start(); System.out.println( a2.getId() ); a3.start(); System.out.println( a3.getId() ); a4.start(); System.out.println( a4.getId() ); [/sourcecode] Oto efekt działania: 1
    8
    9
    10
    11
    54
    55
    56
    57

    I tak raz za razem – kolejność cyfr oczywiście bywa różna (poza pierwszymi trzema-czterema) – wiadomo, nieokreśloność wywoływania wątków. Tak to wygląda. Dwie kwestie – jak widać wątek metody main() ZAWSZE (przynajmniej u mnie) ma numer 1. Pozostałe – w miarę równo i oczywiście idąc w górę. Pytania na testowym egzaminie dotyczyły ewentualnego wyniku na wyjściu programu podobnego do powyższego. Ciekawe, ale jednak to w wielu momentach jest po prostu loteria.

Dlaczego dobrze jest czytać innych

Ano dlaczego? Bo zawsze można dowiedzieć się czegoś nowego. I tak też się stało, kiedy w dniu dzisiejszym trafiłem na bloga jadept, gdzie oprócz dobrze zapowiadającego się bloga, znalazłem rozwiązanie trapiących mnie problemów.

Jak wiadomo zmieniłem ostatnio wygląd bloga tylko po to, aby dać choć namiastkę lepszego formatowania kodu. Jak to wygląda każdy widzi – efekt nie jest do końca zadowalający. Natomiast na wspomnianym blogu patrzę – domena wordpress.com, standardowy wygląd i… pięknie sformatowany kod! Pomyślałem, że może autor dokupił możliwość zabawy z CSSem, ale okazuje się, że nie! Otóż na serwerach WordPressa jest tak, że formatowanie kodu odbywa się poprzez wrzucenie go w znaczniki sourcecode podane w nawiasach kwadratowych oraz z własnością language. Pomimo prób nie udało mi się jakoś tego ogarnąć encjami czy cytatem by pokazać konkretnie konstrukcję, ale sami popatrzcie jak ładnie wygląda to poniżej:

  public void paint(Graphics g) {
        Graphics2D graphics2 = (Graphics2D) g;
        RoundRectangle2D roundedRectangle = new RoundRectangle2D.Float(100, 100, 240, 160, 10, 10);
        graphics2.draw(roundedRectangle);
    }

Pewnie powoli w ramach wolnego czasu będę się starał istotne wpisy poprawić i sprawić by kod zaczął wyglądać tak jak powinien.

SCJP, podejście ósme

Wielkimi krokami zbliżam się do końca podręcznika. Jakoś nie czuję się o wiele mądrzejszy, ale to głupie odczucie i próbuję z nim walczyć. W końcu coś z tej wiedzy w głowie zostaje – im więcej w sumie tym lepiej – SCJP jednak tych kilka setek kosztuje. Lepiej nie podchodzić dwa razy.

Dziś na tapecie krótki rozdzialik o klasach wewnętrznych. Przyznam szczerze, że popełniłem w teście raptem 2 błędy zatem i pisać nie za bardzo mam o czym. Sam bowiem rozdział poświęcony temu zagadnieniu ma może 20 stron – naprawdę nie za wiele można na tylu stronach zmieścić. Jednakże oto lista:

  • dostęp – zasadniczo właśnie dostępności/widoczności poszczególnych klas czy metod dotyczy większość pytań. Tutaj napiszę tylko, że w przypadku method-local inner class ma ona nielimitowany dostęp do klasy otaczającej. Taki kod:

    public class Tester {
    final String lancuch = new String("Chlebik");
    public void pokazKlaseWewnetrzna()
    {
    class takaSobieKlasa {
    void hej()
    {
    System.out.println( lancuch );
    }
    }
    }
    }

    jest jak najbardziej w porządku i zadziała bez problemu.

  • pamiętaj o statykach – zarówno o tym, że metody niestatyczne nie mogą być wywoływane ze statycznych metod, a także, że statyczne zmienne trzymają się twardo i mają za nic tworzenie instancji klasy.
  • widoczność klas wewnętrznych – kod z pytania testowego:
    class A { void m() { System.out.println("outer"); } }
    public class TestInners {
    public static void main(String[] args) {
    new TestInners().go();
    }
    void go() {
    new A().m();
    class A { void m() { System.out.println("inner"); } }
    }
    class A { void m() { System.out.println("middle"); } }
    }

    Pytanie brzmi – co zostanie wyświetlone na wyjściu programu? Odpowiedź: middle. Dlaczego? Ano bo klasa wewnątrz metody jest zadeklarowana dopiero po wywołaniu (czyli po fragmencie new A().m();, w związku z czym jest niewidoczna. Klasa A, która jest w pierwszej linijce kodu jest “poziom wyżej” niż klasa dająca na wyjściu “middle” i dlatego też nie zostanie wykorzystana.

25 lat minęło – na pewno nie jak jeden dzień

Tytuł posta wyjaśnia wszystko. Dziś oficjalnie ( o 6.23 czasu naszego ) skończyłem 25 lat. Fakt, że urodziłem się tak wcześnie rano chyba tłumaczy mój niemożliwy wręcz wstręt do wczesnego wstawania. Z tej okrągłej okazji postanowiłem trochę powspominać, co też udało mi się w tym życiu osiągnąć:

Urodzić się, nauczyć się chodzić, mieć pierwszą dziewczyne (przedszkole), próbować podpalić szkołę, być w klasie sportowej, nauczyć się Basica na ZX Spectrum, całkiem nieźle kopać piłkę, ćwiczyć karate, poznać Pascala, dostać się do klasy autorskiej, być na nartach i wrócić w całości, skończyć klasę autorską, nauczyć się ciekawych rzeczy i języków, pierwszy raz poważnie się zakochać, dać się upić własnej rodzinie na imieninach u babci, uzależnić się od pewnej gry, wypalić wagon papierosów (rzuciłem), być finalistą ogólnopolskiej olimpiady przedmiotowej, pisać opowiadania, zdać maturę, dostać się na studia, zakochać się na naprawdę poważnie, dostać stypendium naukowe, pójść do pierwszej pracy, w tejże pracy mieć 30h maratony biegania z tacą na bankietach, zamieszkać z dziewczyną, przejść kilkadziesiąt km szlaków górskich, nauczyć się PHP i paru innych rzeczy (podczas długich nocy po pracy), ożenić się, skończyć studia, znaleźć świetną pracę no i na końcu nawet pisać bloga.

Ufff, trochę się działo w tym życiu, a i tak nie wypisałem masy istotnych rzeczy. Muszę przyznać, że 25 lat to jednak jest tam jakaś cenzura, człowiek po studiach, z jakimiś planami na życie, wiedzy też już zdążył nabyć. Jedyną rzeczą jakiej sam sobie z tej okazji życzę, to by dalej to życie potrafiło mnie (pozytywnie rzecz jasna) zaskakiwać – oby do samego końca.

SCJP, podejście siódme

Siódmy rozdział podręcznika do SCJP to kolekcje i generyki. Jest to zmora wszystkich programistów, zresztą nie dziwię się – sam rozdział ma koło 100 stron i do lekkich nie należy. Końcowy test wyszedł mi “dobrze-niedobrze” – zasadniczo większość odpowiedzi udzielałem “półpoprawnie”, czyli obejmowałem poprawną odpowiedź, ale też przy okazji wpisywałem czegoś za dużo lub za mało. Jednakże zawziąłem się i postanowiłem przerobić zagadnienie jeszcze raz dość gruntownie, posiłkując się innymi swoimi książkami – Blochem oraz Koffmanem przedstawię kilka rzeczy, które z pewnością się przydadzą. Ten wpis będzie inny i dłuższy niż poprzednie, ale jestem pewny, że jego lektura na pewno nikomu nie zaszkodzi.

Zacznijmy od podstawowej rzeczy, jaką jest wiedza dotycząca metod equals oraz hashCode. Equals służy do porównywania dwóch obiektów i sprawdzenia, czy tym samym są one sobie znaczeniowo równe.

String s1 = new String("chlebik");
String s2 = new String("chlebik");s1.equals(s2); // To daje TRUE

Oczywiście w przypadku klasy String o równości mówić jest łatwo. Podobnie jak w przypadku liczb – albo dwie zmienne reprezentują tę samą liczbę, albo i nie. Co natomiast zrobić w przypadku klas napisanych przez programistę? Ano trzeba by nadpisać tę metodę. Domyślnie w klasie Object metoda ta używa do porównania dwóch obiektów operatora ==, co nie jest raczej dobrym rozwiązaniem dla klas reprezentujących…hmmm…no nigdy nie jest dobre.

Przedstawię wpierw klasę, na których będziemy pracować:

class Bloger { }class WPBloger extends Bloger {
Integer age = 24;
String nick = new String("Chlebik");
}

Załóżmy, że chcemy sprawdzić równość dwóch obiektów klasy WPBloger. Co należy porównać by mieć pewność, że jedna i druga zmienna znaczeniowo reprezentuje ten sam obiekt? Ano jeśli zgadza się wiek, zgadza się nick, no to mamy identyczne obiekty. Super. Oto jak to zaimplementujemy:

public boolean equals( Object o ) {if( (o instanceof WPBloger) &&
( ((WPBloger)o).nick == this.nick ) &&
( ((WPBloger)o).age == this.age ) ) {
return true;
} else {
return false;
}
}

Oto o czym należy pamiętać:

  • parametr metody – musi być typu Object. I kropka.
  • sprawdzanie typu – owszem, parametr jest typu Object, ale jeśli chcemy porównywać nick czy wiek należy potraktować nasz parametr jako obiekt konkretnej klasy. Jednakże zanim to uczynimy, należy zabezpieczyć się przed ewentualnym błędem, jeśli do metody przekazano obiekt innej klasy. Stąd sprawdzenie w warunku instanceof.
  • rzutowanie parametru – po pomyślnym przejściu testu z operatorem instanceof możemy swobodnie (bez obawy o błędy) rzutować parametr na konkretną klasę, a następnie odwoływać się do jego pól/metod.
  • porównywanie wartości – dla zmiennych instancji będącymi obiektami, należy wywoływać ich metody equals. W przypadku typów prostych (prymitywów) rzecz jasna wystarcza operator ==. Wyjątek! W przypadku wartości float i double dobrze jest je wpierw przerobić na inne typy. Float na integer (metoda Float.floatToIntBits), zaś double na long (metoda Double.doubleToLongBits ). Jest to podyktowane tym, że w przypadku tych prymitywów mogą one przyjmować wartości takie jak Float.NaN i parę innych, co przy porównaniu może prowadzić do nieścisłości. Tablice porównujemy w całości, pole po polu (lub za pomocą Arrays.equals)

I to na razie tyle. Czasem warto też zapoznać się z kontraktem tej metody, jednakże jest to dość oczywiste i nie będę przepisywał manuala. Teraz przejdziemy do metody hashCode. Wpierw troche uśmiechu od autorów podręcznika:

For the exam you do not need to understand the deep details of how the collection classes that use hashing are implemented, but you do need to know which collections use them (but, um, they all have “hash” in the name so you should be good there)

Generalnie hashe są używane po to, aby polepszyć efektywność składowania i wyszukiwania elementów w kolekcjach. Im nasza klasa będzie generowała bardziej unikalne i różnorodne hashe tym lepiej (wyszukiwanie będzie szybsze). Wartość zwracana przez metodę hashCode to int! Autorzy podręcznika pokazali działanie haszowania na bardzo przystępnym przykładzie.

Załóżmy, że mamy klasę reprezentującą zawodników pewnej drużyny. Hashe są generowanie na takiej zasadzie, że bierzemy po kolei litery z imienia zawodnika i zamieniamy ich wartości na liczby w rosnącej kolejności:

Imię Haszowanie Wynik
Alex A(1)+L(12)+E(5)+X(24) 42
Bob B(2)+O(15)+B(2) 19
Dirk D(4)+I(9)+R(18)+K(11) 42

Jak widać dla imion Alex i Dirk zostaną wygenerowane te same wartości. I teraz na przykładzie koszyków z imionami – oba imiona zostaną włożone do tego samego koszyka. Gdybyśmy próbowali znaleźć imię w takim koszyku, wówczas musielibyśmy przetrząsnąć taki koszyk w poszukiwaniu odpowiedniego. To może zająć sporo czasu! W użyciu mamy wówczas metodę equals, która po kolei przejdze po imionach w koszyku w poszukiwaniu odpowiedniego. Hmmm, trochę to mało efektywne. Na przykładzie 2 imion to może tego nie widać, ale gdybyśmy w takim koszyku mieli owych imion 50, już zacząłby się narzut.

Co zatem zrobić? Najlepiej by było w ogóle zrezygnować z przeszukiwania koszyka. Jak to osiągnąć? Pisząc na tyle oryginalne metody hashCode, aby każde imię miało swój własny koszyk. Najlepiej tak przegrzebać w wartościach, aby były one dość oryginalne (haszowania). Dzięki temu wyszukiwanie naszych obiektów będzie z pewnością szybsze. Metoda hashCode również posiada swój kontrakt, ale podobnie jak przy equals nie zamierzam przepisywać manuala.

Dlaczego piszę o tych metodach? Ano dlatego, że kilka zadań jest w swej idei prosta – mamy dwie kolekcje, coś tam dodamy, coś odejmiemy i mamy linię, w którą możemy wsadzić jakiś kod. Jaki będzie wynik działania programu jeśli ta linia będzie bla bla bla bla. Generalnie bez znajomości działania powyższych metod oraz tego jak zachowają się dodawane elementy będzie dość trudno odpowiedzieć na tego typu pytania. Dla pamięci i na zakończenie tego wątku – wpierw lecimy po hashCode, dopiero potem wyszukiwanie obejmuje equals! (zatem należy uwzględnić sytuacje kiedy np. metoda hashCode w ogóle nie jest nadpisywana).

Jeśli chodzi o samo API kolekcji to raczej nie ma z nim większych problemów. Należy uważać na cztery rzeczy:

  • interfejsySet, Map i List są właśnie interfejsami. Można na nie rzutować, ale nie da się stworzyć ich instancji!.
  • różnice zawartości w zbiorachHashMap może posiadać jeden klucz będący wartością NULL, jak i również wiele wartości NULL jako zawartość, na którą klucze wskazują. Natomiast w przypadku Hashtable (patrz: różnica w wielkości liter) nie ma mowy o jakichkolwiek NULLAch.
  • TreeMap i TreeSet – to są ciekawe kolekcje, gdyż charakteryzują się posiadanym porządkiem i sortowaniem. Nie będę znowu przeklejał manuala( TreeMap i TreeSet ), ale z pewnością wypada zwrócić uwagę na metody, które obejmuje egzamin ( Key w znaczeniu nie parametru ale odmiany tej metody dla Map):
    • ceiling (Key)
    • higher (Key)
    • floor (Key)
    • lower( Key)

    najlepiej sobie wbić do głowy, co i kiedy zostanie zwrócone.

  • “backed collections” – czyli dziwne rozczłonkowanie kolekcji poprzez wywołanie metody subMap (wraz z jej przeciążonymi wersjami).

No i przyszedł czas na generyki. Wpierw trochę historii. Otóż obsługa generyków została dodana w Javie 1.5 i w związku z tym (albo przede wszystkim dlatego, że to była wersja 1.5) musiała w racjonalny sposób umożliwić działanie starego kodu, który był napisany bez istnienia czegoś takiego jak typy generyczne. Dlatego też całe to zagadnienie jest trochę “walnięte”, gdyż jest kompromisem pomiędzy nowoczesnością, a przenośnością kodu. No i dlatego mamy taki bajzel.

Po kilku słowach krytyki czas na konkrety. Standardowo w wersjach przed wprowadzeniem generyków istniała możliwość zrobienia czegoś takiego:

List lista = new ArrayList();lista.add(new Integer(23));
lista.add(new String("chlebik"));

I to nawet działało. Problemem było oczywiście porównywanie elementów, a także konieczność ciągłego rzutowania wyciąganych obiektów z listy (metody dostępowe zwracały Object). I głównie po to powstały generyki – by umożliwić kontrolę typów na poziomie samej struktury danych, bez konieczności dokonywania ciągłych rzutowań i obawy o ClassCastException. Gdyby zagadnienie kończyło się w tym miejscu, byłoby ono jednym z najłatwiejszych w certyfikacji SCJP. Oczywiście tak różowo nie jest.

Generalnie kod napisany w wersjach sprzed generyków daje spore szanse na działanie. Oto przykład:

public void zrobCos() {
List lista = new ArrayList();
lista.add(23); // zwroc uwage na autoboxing
lista.add(44);
List drugaLista = zrobCosZLista( lista );
}public List zrobCosZLista( List lista ) {
// dodajemy cos do listy i tak dalej
}

Jeżeli w metodzie zrobCosZLista grzecznie będzie istniał kod sprawdzający czy elementy listy są Integerami, wówczas problemu nie ma. Dodatkowe rzutowania nikomu nigdy nie zaszkodziły (poza pewnie narzutem na wydajności). Co jednakże się stanie jeśli postanowimy coś dodać do takiej listy? Ano mamy problem, gdyż oficjalnie dla kompilatora mamy doczynienia ze zwykłą listą, która może przyjmować dowolne wartości! Co z tego, że “piętro wyżej” nasz argument nie był zwykłą listą, ale listą z predefiniowanym typem?

public List zrobCosZLista( List lista ) {
lista.add( new Integer(29) );
}

Działa! Oj jak fajno. Zwracamy listę, ale z elegancko dodaną kolejną wartością Integer. Kompilator jest szczęśliwy, programista też. Co jednakże jeśli do listy dodamy np. łańcuch tekstowy?

public List zrobCosZLista( List lista ) {
lista.add( new String("chlebik") );
}

Dodać możemy, w końcu to zwykła lista. I co? Ano wszystko dobrze – taki kod skompiluje się i nawet uruchomi. Nie będzie błędów podczas działania programu!!!. Jedyną rzeczą będzie wygenerowanie ostrzeżenia przez kompilator (w poprzednim przypadku dodawania Integera takie ostrzeżenie również zostanie wygenerowane). Przy dodawaniu trefnego elementu do kolekcji nic się nie stanie! Problem najczęściej pojawia się wówczas, gdy próbujemy potraktować nasz element o typie String jako Integer. Co się dzieje w takiej sytuacji chyba nie muszę tłumaczyć.

Powyższe przykłady nie są tragiczne i da się je zrozumieć. Najgorszą rzeczą w przypadku generyków jest polimorfizm i różne jego odcienie. To na tym zagadnieniu polega większość zdających (no i oczywiście ja :). Problemu z polimorfizmem w przypadku “typu kolekcji” nie ma. Czyli możemy rzutować ArrayList na List i wszystko będzie działało. Co zaś z takim kodem (kalka z podręcznika)?

class Parent { }
class Child extends Parent { }
List<Parent> myList = new ArrayList<Child>();

Takie coś nie zadziała. Zadziała zaś z kolei zasada mówiąca o tym, że zadeklarowany typ generyczny jest jedynym typem, który dana kolekcja może przyjąć. Koniec kropka. Czyli w powyższym przypadku tylko Child, albo tylko Parent. Próby napisania innego kodu skończą się błędem kompilacji.
Podobna sytuacja ma również miejsce w momencie, kiedy np ArrayList jest przekazywana jako parametr do metody, która przyjmuje np: List. Nie, nie i jeszcze raz nie. Tak się nie da.Co zatem zrobić w takim przypadku? Zrezygnować z polimorfizmu się raczej nie da, pisanie przeciążonych metod dla każdej z podklas Parent też nie wchodzi w grę. Z pomocą w takich przypadkach przychodzi specjalny “operator” (tzw. wildcard) – <? extends Parent>. Znaczy on mniej więcej tyle – pozwalaj na przekazywanie obiektów, które rozszerzają podaną klasę (no i obiekty tej klasy też) i operowanie na takiej kolekcji. Obiecuję, że nie będę tam niczego wsadzał. Dzięki takiej konstrukcji znaczna część istniejącego już kodu mogła zadziałać – wystarczyło tylko nie wsadzać niczego do kolekcji w ten sposób przekazanej.

W tym miejscu wypada wskazać, że podobnie jak w kilku innych przypadkach w Javie – parametryzowanie w przypadku powyższym oznacza nie tylko klasę, ale również implementację interfejsu. Czyli taki kod jest jak najbardziej poprawny:

public zrobCos( List<? extends Runnable> ) {}

Istnieje też odwrotność tego zapisu. Jak już wspomniałem <? extends Class> dotyczy konkretnej klasy/interfejsu oraz jej dzieci/implementacji. A co jeśli chcemy rzutować w górę? Ależ proszę bardzo:

public zrobCos( List<? super Class>) {}

Taki zapis mówi kompilatorowi – przyjmij jako argument listę z obiektami typu Class i wszystkimi jego rodzicami. Dzięki temu mamy możliwość ograniczenia potencjalnych obiektów, które można przekazać jako parametr, ale z drugiej strony jesteśmy w stanie doprowadzić stary kod do stanu używalności z generykami.

Ostatnią rzeczą, na którą wypada zwrócić uwagę są deklaracje z użyciem generyków. Jak podkreślają sami autorzy podręcznika jest to dla generyków zastosowanie bardzo rzadkie, jednakże wypada się z nim zapoznać (bo w sumie tylko z takich zagadnień układają pytania :). Uogólnione bowiem typy możemy stosować w przypadku klas:

public class mojaKlasa<T extends jakasKlase> {
T[] tabela;
T innaZmienna;
// i reszta kodu klasy, gdzie pod T zostanie podłożona nazwa klasy
}

Albo też i metod:

public <T> int zwrocInt( T obiekt ) {
// owo T reprezentuje jeszcze nam nieznany typ obiektu
}

To tyle, co postanowiłem pokrótce przedstawić. Zagadnienie kolekcji jako takich jest olbrzymie – bo nie tylko rzecz w powyższej wiedzy – w ogóle bowiem nie dotknąłem zagadnienia efektywności kolekcji (jaką i do czego najlepiej wykorzystać), własnych implementacji, bardziej rozbudowanego API. Ale w tej serii postów skupiam się na zagadnieniach poruszanych na egzaminie – zainteresowani niech poszukają dla siebie odpowiedniej literatury 🙂

Nowy widoczek

Ano jak co jakiś czas ponownie przejrzałem dostępne motywy w WordPressie. Tym razem głównym wyznacznikiem było formatowanie kodu. No i wybrałem nowy motyw – Neat. Choć ucina mi trochę obrazki z poprzednich wpisów (ale ciutke, nie przeszkadza to w czytelności), ma póki co najlepsze formatowanie kodu spośród wszystkich dostępnych 2-kolumnowych layoutów.