30 maja 2008

JAVArsovia w Polskim Radiu Euro

Parę dni temu kapituła organizacyjna konferencji JAVArsovia 2008 otrzymała zaproszenie do Polskiego Radia Euro (od 26 maja 2008 tak właśnie nazywa się stare Polskie Radio BIS). Pisał o tym Jacek w artykule "3 dni do JAVArsovii 2008, audycja w Polskim Radiu". I byliśmy. Nadawaliśmy przez blisko godzinę, na żywo(!) od 9 do 10 dziś rano, naturalnie z przerwami na muzyczkę. Zaszczyt reprezentowania kapituły przypadł Jackowi Laskowskiemu, Michałowi Margielowi no i Mi. Jak było? Bardzo ciekawie! :) A oto my przed mikrofonami.

28 maja 2008

Nowości w MavenIDE dla NetBeans

Pamiętam, że ostatnio jak otworzyłem NetBeans'a (wersja 6.0) zainstalowała mi się aktualizacja MavenIDE (wersja 3.1.1), ale dopiero dziś zobaczyłem, że zmiany są istotne i ciekawe. A to co mnie urzekło, to dostępna lista archetypów. Nie dość, że lista "wbudowana" w plugin jest teraz większa, to jeszcze mamy do dyspozycji archetypy wprost z repozytoriów Mavena. Spójrzcie tylko na poniższy obrazek.

27 maja 2008

SCJP – Obiekty typu tablicowego

Dziś będzie parę słów wstępu o tablicach – deklaracje i podstawy konstrukcji; inicjalizacja w następnym artykule. Zanim opuścisz ten rozdział upewnij się, że wiesz na temat tablic w Javie wszystko; nie jest to bowiem temat aż tak banalny, jakim mógłby się wydawać.

Tablice w języku Java są obiektami przechowującymi listę zmiennych pewnego ustalonego typu. Deklarując tablicę podajemy właśnie typ zmiennych, które będzie ona przechowywała oraz wymiar (ang. dimension) – ale nie rozmiar(!) – tablicy. Więcej o deklarowaniu tablic w artykule "SCJP - Deklaracja zmiennych i stałych". Deklaracja tablicy, czyli mówiąc precyzyjniej deklaracja referencji na obiekt typu tablicowego to jednak tylko deklaracja referencji – nie powoduje utworzenia żadnego obiektu. Deklarując referencję typu tablicowego nie podajemy rozmiaru tablicy, bo jest zbędny. Jest on natomiast wymagany, gdy tworzymy właściwy obiekt tablicy; musi być bowiem wiadomym, jak duży obszar pamięci trzeba zaalokować. Poniżej dwa przykłady pokazujące deklaracje i utworzenie obiektów tablic.

int[] nums = new int[16];

Thread[] threads = new Thread[4];

Jak pisałem w artykule dotyczącym inicjalizacji zmiennych "SCJP - Wartości domyślne zmiennych" elementy tablic są zawsze inicjowane domyślnymi wartościami. Zauważmy jednak, że wartością domyślną dla zmiennej referencyjnej jest ‘null’, tak więc druga z pokazanych deklaracji tablic bynajmniej nie spowoduje utworzenia czterech instancji obiektów klasy ‘Thread’. Elementy tej tablicy to referencje a nie obiekty i będą one miały wartość ‘null’.

Tablice wielowymiarowe to de facto tablice tablic. Zdaje się, że stwierdzenie to jest banałem i zupełnie nic nie wnosi, ale jest wprost przeciwnie. Tablica dwu-wymiarowa dla przykładu nie musi być bowiem tablicą "prostokątną" a trójwymiarowa "kostką”. Wszystko powinien wyjaśnić poniższy przykład.

public class Test {
public static void main(String[] args) {
int[][] myTable = new int[4][];

myTable[0] = new int[2];
myTable[1] = new int[4];
myTable[2] = new int[6];
myTable[3] = new int[8];
}
}

Powyższy kod jest jak najbardziej poprawny – kompiluje się i wykonuje, choć nic ciekawego nie robi. Zacznijmy analizę od pierwszej linii. Deklarujemy w niej referencję na obiekt tablicy. Jak już powiedzieliśmy, tablica przechowuje zmienne pewnego ustalonego typu – w naszym przypadku jest to typ tablicowy, przechowujący z kolei zmienne typu ‘int’. Tworzymy też obiekt tablicy i mówimy, że obiekt ten ma zawierać cztery referencje do obiektów typu ‘int[]’. Referencje te póki co mają wartość ‘null’; nie określiliśmy bowiem "drugiego wymiaru" tablicy i kompilator nie wiedział jakie obiekty utworzyć. Obiekty te tworzymy sami w kolejnych liniach programu. Naturalnie zadanie to możemy powierzyć kompilatorowi – wystarczy, że określimy "drugi wymiar", tak jak pokazuje poniższy przykład.

public class Test {
public static void main(String[] args) {
int[][] myTable = new int[4][2];

myTable[1] = new int[4];

for(int[] subTable : myTable) {
for(int i : subTable)
System.out.print(i + " ");

System.out.println();
}
}
}

Żeby przykład nie był zbyt banalny oraz żeby pokazać, że w istocie tablica ‘myTable’ zawiera nic więcej jak tylko cztery referencje na obiekty typu ‘int[]’ w drugiej linii kodu przypisuje do jednej z tych referencji nowy obiekt – tablicę zawierającą cztery zmienne typu ‘int’, w miejsce tablicy zawierającej dwie zmienne. Efektem uruchomienia powyższego programu będzie pokazana poniżej mozaika zer – domyślnych wartości zmiennych typu ‘int’.

0 0
0 0 0 0
0 0
0 0

Na koniec pytanie. Wiemy, że tablice w języku Java indeksowane są począwszy od zera, ale co się stanie, gdy odwołamy się w kodzie (explicite) do elementu tablicy o indeksie ujemnym? Czy wszystko będzie w porządku, o ile modulo z tej wartości jest poprawnym indeksem? A może zostanie zgłoszony wyjątek w czasie wykonania? A może kompilator wie, że niezależnie od tego, jaka tablica by nie była duża, to i tak indeks ujemny jest niepoprawny a więc zgłosi wyjątek już w trakcie kompilacji?

26 maja 2008

SCJP - Parametry metod

W ostatnim artykule z serii SCJP, tj. "SCJP – Zmienne typów prostych i referencyjnych" napisałem parę słów wstępu do tego, co będzie dzisiaj. Temat jest względnie prosty, więc nie rekomenduję usilnie uprzedniej lektury owego wstępu, ale jeśli coś z artykułu dzisiejszego okaże się być niejasne to pewnie tamten tekst pomoże. Dziś będzie o przekazywaniu parametrów do wywołań metod a zwłaszcza parametrów typów prostych kontra referencji.

Wywołując metodę przekazujemy pewną listę – potencjalnie pustą – parametrów typu prostego i/lub referencji do obiektów. Pierwszą i w zasadzie jedyną rzeczą, jaką w tym kontekście trzeba wiedzieć jest, że w języku Java wszystkie parametry są przekazywane przez wartość – reszta to już czysta konsekwencja tego prostego faktu. Przeanalizujmy to na przykładach.

public class Test {
public static void main(String[] args) {
int x = 2;

triple(x);

System.out.println("x: " + x);
}

static void triple(int x) {
x *= 3;
}
}

Efektem uruchomienia powyższego programu będzie wyświetlenie napisu "x: 2". Przeanalizujmy krok po kroku jak to się dzieje. W pierwszej linii metody ‘main(…)’ deklarujemy zmienną ‘x’ i przypisujemy jej wartość literału ‘2’. Następnie wywołujemy metodę ‘triple(…)’ przekazując jako argument zmienną ‘x’. Co to dokładnie znaczy przekazać zmienną ‘x’ jako argument? Zerknijmy na funkcję ‘triple(…)’. Widzimy, że deklaruje ona argument typu ‘int’ o nazwie ‘x’ – naturalnie zbieżność nazwy ze zmienną zadeklarowaną w metodzie ‘main(…)’ nie ma żadnego znaczenia. Zmienna zadeklarowana jako argument metody traktowana jest dokładnie tak samo jak zmienne zadeklarowane w jej ciele – jest to zmienna lokalna metody. Mamy zatem zmienną typu ‘int’ o nazwie ‘x’ w metodzie ‘main(…)’ oraz w metodzie ‘triple(…)’ i mimo, że nazywają się one tak samo to są to zupełnie inne zmienne, tj. odpowiadają innym obszarom pamięci. Jak już sobie powiedzieliśmy, zmienne w języku Java zawsze przekazywane są przez wartość, zatem wywołanie metody ‘triple(…)’ powoduje skopiowanie wartości zmiennej ‘x’ z metody ‘main(…)’ do zmiennej ‘x’ w metodzie ‘triple(…)’. I właśnie to skopiowanie jest przekazaniem parametru wywołania! Jaki zatem efekt ma wykonanie przypisania "x *= 3" w metodzie ‘triple(…)’? Naturalnie, potrojenie wartości zmiennej ‘x’ zadeklarowanej w metodzie ‘triple(…)’, ale nie zmiennej ‘x’ z metody ‘main(…)’. Nic więc dziwnego, że program wypisze "x: 2" – wypisujemy przecież wartość zmiennej lokalnej dla metody ‘main(…)’ a ta nie zmieniła się od czasu inicjacji liczbą 2. W powyższym przykładzie celowo nazwałem obie zmienne tak samo, w końcu moją intencją było wprowadzenie w błąd, zupełnie tak samo jak to ma miejsce na egzaminie SCJP, na co trzeba się wyczulić. Zerknijmy na kolejny przykład.

public class Test {
public static void main(String[] args) {
SomeClass obj = new SomeClass();

triple(obj);

System.out.println("x: " + obj.getX());
}

static void triple(SomeClass obj) {
obj.setX(obj.getX() * 3);
}
}

class SomeClass {
int x = 2;

public int getX() {
return x;
}

public void setX(int x) {
this.x = x;
}
}

Tym razem przekazujemy referencję do obiektu, a dokładniej, kopię tej referencji – jest to bardzo duża różnica, co pokażę w kolejnym przykładzie. Jak pisałem w artykule wspomnianym we wstępie – referencje to nic magicznego, zwykła zmienna przechowująca pewną wartość, tyle że ta wartość to "wskaźnik" na obiekt. Przeanalizujmy, co się dzieje w przedstawionym powyżej programie. W pierwszej linii metody ‘main(…)’ deklarujemy zmienną referencyjną oraz tworzymy nowy obiekt. Zmienna ta inicjowana jest wartością reprezentującą wskaźnik na ten obiekt. Wywołując metodę ‘triple(…)’ nie przekazujemy bynajmniej obiektu, tylko referencję. Przekazanie referencji oznacza skopiowanie wartości zmiennej ‘obj’ zadeklarowanej w metodzie ‘main(…)’ do zmiennej o tej samej nazwie zadeklarowanej w metodzie ‘triple(…)’. Obie zmienne nazywają się tak samo i mają tą samą wartość, jednak są to zupełnie inne zmienne, których wartości możemy zmieniać niezależnie, analogicznie jak wartości zmiennych typów prostych. Ale co oznacza instrukcja "obj.setX(…)" umieszczona w metodzie ‘triple(…)’? Nie jest to bynajmniej przypisanie nowej wartości zmiennej ‘obj’. Jest to wywołanie metody obiektu "wskazywanego" przez tą zmienną. Ale chwileczkę, przecież zmienna ‘obj’ lokalna dla metody ‘triple(…)’ ma tą samą wartość, co zmienna ‘obj’ lokalna dla metody ‘main(…)’, zatem "wskazuje" dokładnie ten sam obiekt. Nic więc dziwnego, że uruchomienie programu spowoduje wyświetlenie napisu "x: 6". Zmodyfikowaliśmy przecież dokładnie ten obiekt, który utworzyliśmy w metodzie ‘main(…)’ i którego stan następnie wyświetlamy. I jeszcze jeden przykład. Klasa SomeClass pozostała bez zmian, więc nie pokazuję jej ponownie.

public class Test {
public static void main(String[] args) {
SomeClass obj = new SomeClass();

triple(obj);

System.out.println("x: " + obj.getX());
}

static void triple(SomeClass obj) {
SomeClass localObj = new SomeClass();

localObj.setX(obj.getX() * 3);

obj = localObj;
}
}

Nadal przekazujemy referencję do obiektu, metoda ‘main(…)’ wcale się nie zmieniła, a mimo to program wypisuje "x: 2". Dlaczego? Właśnie dlatego, że referencje zupełnie tak jak zmienne typów prostych przekazywane są przez wartość, tj. przekazywane są kopie referencji, zatem przypisanie "obj = localObj" modyfikuje zmienną lokalną dla metody ‘triple(…)’ i nie ma żadnego wpływu na zmienną ‘obj’ z metody ‘main(…)’.