Stronnice Chlebika – Java Blog for Newbies

Październik 5, 2009

Java exPress, a sprawa Chlebika po raz drugi

Zaszufladkowany do: SCJP — chlebik @ 9:35 pm

Tadam! Po miesięcznym poślizgu wydawniczym “nadejszła wielkopomna chwila” jak mówił pewien znany człowiek – pojawił się kolejny numer Java exPress!

Co w tym takiego wielkiego? Ano to, iż znaleźć tam można artykuł, który popełniłem jakiś czas temu, a który jest kompilacją wpisów dotyczących SCJP. Wszystko zebrane w jednym miejscu, ładnie poskładane i wydane. Miłej lektury.

Październik 3, 2009

Michał Piotrowski Sun Certified Java Programmer

Zaszufladkowany do: Life, SCJP — chlebik @ 9:01 am

W końcu po prawie miesiącu od zdania dostałem fajną przesyłkę od firmy Sun. A w niej pierwszy dyplom do zawieszenia na moim ‘Wall of Fame’.

Certyfikaty SUNa mają to do siebie, że dostaje się kilka bajerków po ich zdaniu. Pierwszy z nich to fajna wizytówka w formie karty kredytowej (twardy plastik). Dzięki temu (o ile w ogóle będzie potrzeba) nie ma konieczności targania ze sobą certyfikatu. Zaś sam dyplomik wygląda tak:

mojSCJP

Trochę krzywo się zeskanował, ale przyczyną tego problemu jest… poczta. Otóż dostałem przesyłkę od SUNa zwykłym listem przysłanym z zagranicy (Malmo, 16 września, odebrałem wczoraj zatem trochę im zeszło). Wszystko fajnie, tylko czemu koperta była z gatunku ‘tekturowych’, bez jakiegokolwiek zabezpieczenia w srodku! I właśnie dlatego otrzymana przesyłka w kilku miejsach wygląda jak z gardła wyciągnięta (zakładam, że to wina wilgoci). Nierówna faktura objawia się niemożnością poprawnego zeskanowania niestety. Mam przynajmniej nadzieję, że kiedy włożę ten dyplom do antyramy to się sprasuje i będzie wyglądał cokolwiek lepiej. Pozostaje tylko mieć nadzieję, że kolejne certyfiakty zostaną dostarczone w trochę lepszym stanie.

Wrzesień 11, 2009

SCJP – ZDANY

Zaszufladkowany do: Life, SCJP — chlebik @ 5:47 pm

Tak, tak moi drodzy – w tym pięknym dniu 11 września (rocznica psiamać) Anno Domini 2009 zdałem tenże egzamin. Rzecz ta miała miejsce w Warszawie w centrum egzaminacyjnym BizTech przy ulicy bodajże Wolność. I tyle.

No może nie tyle – wszak wpis byłby zbyt krótki. Konkretnie zdawałem już nową wersję egzaminu o czy pisałem już wcześniej. I co? No i nie wiem. Oczywiście egzamin zdany, ale z wynikiem 71%. Hmmm, trochę kiepsko. Jedakże trzeba spojrzeć na wyniki:

Declarations, Initializations, Scoping 80%
Flow Control 81%
API Contents 42%
Concurrency 100%
OO Concepts 50%
Collections/Generics 75%
Fundamentals 77%
Questions 43/60

Zasadniczo poza API i OO Concepts wszystko poszło mi całkiem dobrze (100% ze współbieżności!!). Kwestia po prostu jest taka – w rzeczach, które podpowiada IDE zasadniczo jestem kiepski. Choć to dziwne, gdyż w moim odczuciu wszystkie pytania o API były całkiem proste ( albo o to właśnie chodziło by tak wyglądało ), zaś koncepcje programowania obiektowego to też zakładam, że rozumiem (nawet posługując się na co dzień PHP). Podsumowując – nie wiem co o tym myśleć.

Jednakże certyfikat już posiadam – zatem można założyć, iż pewną wiedzą o samym języku jestem się w stanie wykazać. Zatem mogę bez wstydu odpowiadać na ogłoszenia o pracę, gdzie “wyższe studia informatyczne” są koniecznie potrzebne. Przynajmniej w dziedzinie samej Javy obronię się bez problemu.

Co dalej? Ano w końcu po pewnym okresie przerwy powrócę do programowania. Czyli kontynuacja ProgramBash, a także zamierzam z drugiej strony rozpocząć naukę w dziedzinie narzędzi Javy, ale nie będących frameworkami. Konkretnie chodzi o web-services, parsery XMLa, narzędzia do zarządzania projektem czy ciągłej integracji. Dwutorowość póki co dobrze mi wychodziła, mam nadzieję, że tym razem będzie tak samo.

Sierpień 26, 2009

I znowu ciekawe zmiany w SCJP

Zaszufladkowany do: SCJP — chlebik @ 9:38 pm

Pisałem nie tak dawno o zmianach w ilości pytań egzaminacyjnych (tym samym progu zdania) dla SCJP w wersji 1.6. Co ciekawe dalszych zmian nie ma końca – w tym jednakże przypadku mogę powiedzieć z całą pewnością – jest lepiej.

Czego konkretnie dotyczą zmiany? Otóż rzecz w wykreśleniu z wymagań dla egzaminu trzech dość istotnych wymagań (no, może dwa są dość istotne):

“Develop code that declares both static and non-static methods, and – if appropriate – use method names that adhere to the JavaBeans naming standards. Also develop code that declares and uses a variable-length argument list.”

“Develop code that serializes and/or de-serializes objects using the following APIs from java.io: DataInputStream, DataOutputStream, FileInputStream, FileOutputStream, ObjectInputStream, ObjectOutputStream and Serializable.”

“Given a scenario, write code that makes appropriate use of wait, notify, or notifyAll.”

Czyli serializację i połowę potencjalnych pytań o wątki szlag trafił. Pewnie są to dość istotne zagadnienia (czy aby na pewno?) z punktu widzenia programisty Java, jednakże raczej nikt nie spodziewa się, iż po zdaniu SCJP nagle człowiek staje się guru programowania. Ja zatem jakoś specjalnie za nimi nie płaczę. Stosowny temat na JavaRanch można znaleźć pod tym adresem.

Sierpień 18, 2009

Best of the best z SCJP

Zaszufladkowany do: SCJP — chlebik @ 11:18 pm

Ostatnio sporo w tym temacie się u mnie dzieje (dlatego wpisy trochę przystopowały). Testowy egzamin robię średnio 2h i po przejrzeniu poprawnych odpowiedzi z definicji jestem tak skonany, że nic już mi się nie chce. Jednakże dziś taka mała gratka dla wszystkich programistów Javy – niezależnie czy przygotowują się do egzaminu czy nie – całkiem spora lista rzeczy, które zapisałem sobie podczas przerabiania testowych egzaminów, a które są dość podchwytliwe i mogą łatwo człowieka wywieść na manowce.

Listę będę starał się cały czas aktualizować. Jak już zdam ten nieszczęsny egzamin to pewnie powstanie kolejna strona na blogu poświęcona SCJP i tam pewnikiem umieszczę wszystkie materiały. Gdyby ktoś chciał dodać do tej listy jakiegoś “onelinera” to proszę o mejla lub komentarz.

  • w przypadku klas Boolean otrzymują one wartość TRUE tylko wówczas, jeśli przekazaną wartością do konstruktora jest “true” – niezależnie od wielkości liter. Wszystko inne (łącznie z NULL) daje FALSE.
  • w przypadku przekazywania do metod/klas pracujących nad tokenizacją tekstu jakichkolwiek wzorców należy pamiętać, że w klasie String należy używać 2 slashy by wprowadzić znak specjalny (np. “\\s”)
  • zsynchronizowane mogą być tylko metody oraz bloki kodu
  • uśpienie obecnie działającego wątku (za pomocą Thread.sleep()) z podaniem wartości czasowej (pamiętajmy – milisekundy) powoduje, że upłynie MINIMUM dany okres czasu zanim wątek ten powróci do gry. Może to być więcej, jeśli np. w tym czasie JVM przydzieli czas innym wątkom będącym na tym samym poziomie uprzywilejowania.
  • wątek kończy swe działanie wówczas, gdy kończy wykonywać się jego metoda run(). Tutaj warto zwrócić uwagę na to, że metoda start() klasy Thread wywołuje swą metodę run().
  • method inner-classes nie mogą korzystać ze zmiennych lokalnych, chyba że są one zadeklarowane jako final
  • skompilowanie programu z użyciem asercji to nie do końca to samo co uruchomienie programu z użyciem asercji
  • warto pamiętać o zasadach dotyczących nadpisywania i przeciążania metod. Zwłaszcza o tej, że metoda statyczna nie może być nadpisana przez niestatyczną i vice versa. Przeciążanie zaś polega na zmianie parametrów oraz na opcjonalnej zmianie zwracanego typu.
  • Unreachable statement to nie wyjątek tylko błąd czasu kompilacji!
  • porównywanie typów prostych z klasami opakowującymi za pomocą metody equals jest jak najbardziej OK. Autoboxing da sobie z tym radę.
  • dzielenie modulo działa dość prosto – zawsze zwraca wartość INT, a z kolei znak jest uzależniony od znaku lewego operandu
  • modyfikator abstract private jest możliwy tylko w przypadku klas wewnętrznych
  • poprzez instrukcje statycznego importu (import static) możemy zaimportować tylko stałe i metody
  • zmienne interfejsu są domyślnie stałę (public, static, final), zaś metody interfejsu są domyślnie public i abstract).
  • kiedy metoda nie zwraca wartości (typ void), wówczas można w ciele takiej metody czystej instrukcji return;. Kod taki się skompiluje i wszystko jest OK. Jeśli jednakże metoda deklaruje, że zwróci jakąś wartość, wówczas zwraca tą wartość lub NULL.
  • konstruktor klasy String tworzy nowy obiekt za każdym razem. Używanie do tworzenia łańcuchów przeciążonego operatora równości powoduje pierwej wyszukanie tworzonej wartości w istniejącej puli łańcuchów.
  • pamiętajmy o porówywaniu typów prostych z klasami opakowującymi i tych samych klas opakowujących. Dotyczy ten temat liczb z przedziału -128 do 127, a także wartości Boolean oraz Char (w pewnym przedziale). Więcej na blogu Michała Mecha.
  • przy metodach operujących na łańcuchach (StringBuilder też się łapie) pamiętajmy, że w metodach dwuargumentowych drugi parametr jest cyfrą, która licząc znaki od 0 jest pierwszą literą, która zostanie z istniejącego obiektu po dokonaniu danej operacji (np. metody substring czy delete).
  • klasa File posiada mało znane (w kotenkście egzaminu) metody canRead() oraz canWrite()), która wskazuje czy dany plik da się odczytać/zapisać.
  • metoda getInstance() z parametrem typu Locale istnieje tylko dla klas Calendar oraz NumberFormat.
  • zmienne zadeklarowane w blokach inicjalizacyjnych nie są dostępne poza nimi.
  • tablice można też tworzyć w ten sposób:
    int[] a = {1};
  • klasy anonimowe (anonymous inner-classes) nie mogą posiadać deklaracji statycznych
  • w przypadku typów wyliczeniowych należy pamiętać, iż wyszczególnienie elementów zbioru musi być pierwszą rzeczą w kodzie tworzącym typ wyliczeniowy.
  • naturalny porządek sortowania łańcuchów tekstowych to:
    • białe znaki
    • “krzaczki” (np. @)
    • cyfry
    • wielkie litery
    • małe litery
  • tablice mogą mieć rozmiar 0
  • liczby typu float oraz double można dzielić przez zero!!! Spowoduje to zwrócenie wartości “Inifinity”
  • dla przypomnienia – wynikiem działań matematycznych jest domyślnie wartość int
  • wywołanie System.gc() nie daje pewności, że GC w ogóle ruszy
  • wartości prymitywne w kolekcjach są sortowane z użyciem własnych zasad. Nie da się do nich zastosować klas implementujących Comparator.
  • przeszukiwanie tablic oraz kolekcji musi odbywać się za pomocą takiego samego Comparatora, co sortowanie.

Sierpień 13, 2009

Zmiany zasad dla SCJP

Zaszufladkowany do: SCJP — chlebik @ 2:33 pm

Anom. Postanowiłem sobie w końcu zakupić voucher na egzamin. Jest on ważny rok, zatem nie obliguje mnie do zapisywania się na egzamin w perspektywie najblizszego tygodnia. Grzecznie odwiedziłem stronę firmy Sun i dotarłem na odpowiednią stronkę. I zonk!

Było trochę późno (w sumie jak zawsze kiedy coś próbuję w javie zdziałać :) i dlatego na początku pomyślałem, że coś pokiełbasiłem. No ale patrzę na oznaczenie 310-065, Sun Certified, itp – czyli wszystko ok. Natomiast piszą mi ni stąd, ni z owąd, iż egzamin ma 60 pytań i do jego zdania wystarcza coś koło 58%. WTF? Udałem się do mądrzejszych ode mnie, a tam odesłano mnie pod ten adres. No i faktycznie – zmiany są oczywiste. Czy lepiej, czy gorzej? Trudno póki co ocenić – niby mniej pytań to szansa na zrobienie mniejszej ilości błędów. Z drugiej jednak strony jak się jakoś krzywo cokolwiek wylosuje/trafi to będzie niefajnie. No cóż, korzystając z faktu, że delikatna niemoc gardła wykluczyła mnie z pracy zawodowej na parę dni douczam się cały czas i mam nadzieję, że niezależnie od ilości pytań wynik będzie satysfakcjonujący.

Lipiec 7, 2009

SCJP, podejście dziesiąte

Zaszufladkowany do: Java, Life, SCJP — chlebik @ 11:23 pm

Jak zwykle po powrocie z urlopu nic się nie chce. Czas w pracy dłuży się niemiłosiernie, do tego ta zwariowana od kilku tygodni pogoda nie polepsza nastroju. Zbierałem się trzy dni zanim postanowiłem znów zawalczyć z tematyką SCJP. Choć to i dobrze, gdyż oto podejście dziesiąte i jak na razie koniec. Ostatni rozdział w podręczniku poświęcony kompilatorowi miałem okazję przerobić już wcześniej. Popełniłem wówczas kilka błędów i temat zostawiłem do późniejszy. Jednakże teraz po powrocie z urlopu ponownie zapoznałem się z zawartością tych kilkunastu stron i tym razem test poszedł o niebo lepiej.

Mimo to należy do pytań dotyczących kompilacji podchodzić ostrożnie, podobnie jak do pytań dotyczących operatorów. Pozornie łatwe potrafią tak naprawdę bardzo łatwo wywieść na manowce. Zwłaszcza dlatego, iż obecnie raczej całą sprawę z kompilacją, classpathem i innymi tego typu rzeczami załatwia nam IDE, zatem raczej nikt nie jest za pan brat z dyrektywami kompilatora czy zabawą z szukaniem klas (choć może się mylę?). Oto lista kilku rzeczy, na które warto zwrócić uwagę:

  • importy statyczne – oczywiście koniecznym jest ich użycie za pomocą słów kluczowych static import (w takiej kolejności). Jednakże mniej oczywistym zapisem jest to, iż możemy importować w ten sposób nawet pojedyncze stałe i metody.
  • asercje – było o nich w rozdziale piątym, ale nie zaznaczyłem tam rzeczy najistotniejszej. Otóż nalezy pamiętać, że asercje zostały wprowadzone już w wersji 1.4! I dlatego też wywoływanie kompilatora i wirtualnej maszyny w ten sposób:
    javac -source 1.4 plik.java
    java -ea plik
    

    Spowoduje, że kod, w którym występują niespełnione asercje (zwracające wartość false) spowoduje wygenerowanie błędu podczas wykonania programu (czyli po prostu asercje będą działały). Wykonania, nie kompilacji! Powtórzmy – błędy wykonania (run), to co innego niż błędy kompilacji (compile).

  • classpath – czego tak naprawdę dotyczy ta dyrektywa dla kompilatora/JVM? Otóż jej zadaniem jest głównie znalezienie wszystkich klas, których kompilowana/uruchamiana klasa będzie potrzebowała. To jest główne zadanie dla classpath. Pamiętać należy również o tym, iż w przypadku kompilacji (polecenie javac) podanej nazwy pliku do kompilacji poszukuje się domyślnie w bieżącym katalogu. W przypadku uruchamiania pliku tak nie jest! No i rzecz ostatnia – podanie wartości dla classpath powoduje nadpisanie zmiennej systemowej (o ile rzecz jasna istnieje)
  • pliki JAR – archiwa są dość proste do zrozumienia, co więcej, na egzaminie nie ma pytań dotyczących ich tworzenia i zarządzania. Natomiast na pewno trzeba wiedzieć, że po utworzeniu pliku JAR z konkretnego katalogu, nawet po dodaniu go do classpath do klas zawartych w archiwum należy odwoływać się w kodzie poprzez podanie pełnej nazwy klasy (łącznie z nazwą pliku JAR). Oto przykład:
    test |
    plik.uzywajacy.klasy.z.jara
    tutaj.utworzymy.plik.jar
    katalog.do.zjarowania |
    podkatalog1
    podkatalog2 |
    plik.java

    Odwołując się do pliku w archiwum JAR, które utworzyliśmy w katalogu test należy podawać pełną ścieżkę. A zatem nasz plik w katalogu test, w którym chcielibyśmy wykorzystać klasę z archiwum musi odwoływać się do niej poprzez zapis PLIK_JAR/katalog.do.zjarowania/podkatalog2/plik – pomimo dodania pliku JAR do classpath.

Czerwiec 26, 2009

SCJP, podejście dziewiąte

Zaszufladkowany do: Java, SCJP — chlebik @ 9:50 am

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:" );
    }
    

    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();
         }
      }
    
     }
    
  • 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() );
    

    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.

Czerwiec 21, 2009

SCJP, podejście ósme

Zaszufladkowany do: Java, SCJP — chlebik @ 7:33 am

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.

Czerwiec 17, 2009

SCJP, podejście siódme

Zaszufladkowany do: Java, SCJP — chlebik @ 10:31 pm

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 :)

Starsze wpisy »

Blog na WordPress.com.