Pokazywanie postów oznaczonych etykietą java killers. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą java killers. Pokaż wszystkie posty

czwartek, 22 stycznia 2009

Java Killers #10

Dziejszy Java Killer prosty i niezbyt wyrafinowany. Ależ jak wiele bólu może dostarczyć, gdy taki kod dostanie się na produkcje, wie tylko programista, który to przeżył...
No dobra, nie jest, aż tak tragicznie, ale mimo wszystko boli. Pytanie na dziś, co wypisze na konsoli działanie poniższego programu:


public static void main(String[] args) {

SortedSet names = new TreeSet();

names.add("Gąskalska Anna");
names.add("Enigmatyczna Jola");
names.add("Ambroży Tomasz");
names.add("Ędward Ącki");

System.out.println(names);

}



Odpowiedzi możliwych kilka, a prawdziwa jak zwykle tylko jedna:

A) [Gąskalska Anna]
B) [Ambroży Tomasz, Enigmatyczna Jola, Gąskalska Anna, Ędward Ącki]
C) [Ambroży Tomasz, Enigmatyczna Jola, Ędward Ącki, Gąskalska Anna ]
D) [Ambroży Tomasz, Ędward Ącki, Enigmatyczna Jola, Gąskalska Anna ]
E) java.util.SortedSet@122cdb6
F) błąd kompilacji



Otóż okazuje się, że prawidłowa odpowiedź to:
B) [Ambroży Tomasz, Enigmatyczna Jola, Gąskalska Anna, Ędward Ącki]

Jak to!? Przecież miała być lista posortowana alfbatecznie! Co jest grane? Cóż, program zachowuje się zgodnie ze specyfikacją, gdzie wyraźnie czytamy:

Method comparesTo (...) compares two strings lexicographically. The comparison is based on the Unicode value of each character in the strings. (...) Method compareTo returns the difference of the two character values at position k in the two string -- that is, the value this.charAt(k)-anotherString.charAt(k).


A jak przyjrzymy się znakom Unicode, polskie krzaczki mają wartości o niebo większe od standardowych literek z alfabetu łacińskiego. I tak ę, ą, ć, ń, ż, ź, ś, ó oraz ł będzie zawsze stać za całą resztą abecadła.
Miejscie to prosze na uwadze, sortując swoje listy :)

poniedziałek, 10 listopada 2008

Java Killers #009

Jakiś czas temu odezwał się do mnie dobry znajomy Paweł Badeński, który uhahany rozpoczął rozmowę na gadu z tekstem "Patrz na to: ..." po czym wkleił kawałek kodu. Kod przerzuciłem na szybko do ulubionego IDE i już wtedy wiedziałem, że te kilka lini ma predyspozycje na kolejny odcinek killersów. Problem pozostawał jednak wciąż, gdyż za diabła nie wiedziałem, czemu kod zachowywał się właśnie tak a nie inaczej. Szybkie przestudiowania Java Language Specification nie dało odpowiedzi, google na szybko też nie były takie cwane, jak zwykle. Dopiero po dłuższej chwili grzebania udało mi się dotrzeć do buga na bugs.sun.com, gdzie po dogłebnej lekturze i ponownym otworzeniu Java Language Specification, wszystko stało się jasne. Jestem ciekaw czy i dla Was dziewiąta odsłona Java Killers będzie taką samą łamigłówką jak była dla mnie. A oto ona:

Mając poniższy kod, jakiego spodziewamy się outputu:


public class Foo
{
public int loo(List<String> a)
{
return 1;
}

public double loo(List<Integer> a)
{
return -1;
}

public static void main(String[] args)
{
List list = new ArrayList<Integer>();
System.out.println("output: " + new Foo().loo(list));
}

}
Odpowiedź poprawna tylko jedna, a kilka do wyboru:


A. Klasa nie skompiluje się, javac poinformuje nas, że "name clash: loo(java.util.List) and loo(java.util.List) have the same signature and erasure"
B. Klasa nie skompiluje się, javac poinformuje nas, że "reference to loo is ambiguous"
C. Skompiluje się, ale wyrzucony zostanie RuntimeException po uruchomieniu programu
D. Program skompiluje się i uruchomi się bez wyjątków i zostanie wypisane na ekranie 'output: -1'
E. Program skompiluje się i uruchomi się bez wyjątków i zostanie wypisane na ekranie 'output: 1'



I uwaga uwaga, poprawna odpowiedź to B!

Osoby, które przekonane były, że oby dwie metody loo miały taką samą sygnaturę i przez to kod się nie kompilował, nie martwcie się, nie byliście jedyni, którzy zaznaczyli odpowiedź A. Otóż okazuje się, że jednak sygnatury obu metod się różnią i występuje najprostszy w świecie method overloading. Później w czasie wywołania metody loo w ciele metody main, następuje konsternacja, gdyż kompilator widzi zmienną niegenneryczną List list i nie ma pojęcia do której metody loo ma się odwołać, stąd jego krzyk
"reference to loo is ambiguous".
Ok, ktoś się spyta, jak to metody loo mają taką samą sygnaturę? Patrząc na Java Language Specification punkt 8.4.9, widzimy, że:

* obie metody mają taką samą nazwę
* obie metody mają taką samą liczbę formalnych parametrów oraz parametrów typów prostych
* ALE typy tych parametrów się różnią, List<String> to co innego niż List<Integer>! Mimo, że przed samą kompilacją wszystkie generyki zostają zrzucone i nie są brane pod uwagę podczas kompilacji (pisałem o tym w poprzednich killersach) o tyle są uwzględniane podczas operacji zwanej "type erasure" - dlatego właśnie List<String> to co innego niż List<Integer>.

No i to tyle na dziś. Kto znał poprawną odpowiedź?

PS. Wspomniana strona do której dotarłem (i musiałem się nieźle nagooglać) znajduje się tutaj: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6182950

sobota, 21 czerwca 2008

Java Killers #008

Witam po dłuższej przerwie w kolejnej odsłonie cyklu Java Killers. Standardowo bez dłuższych wstępów, przejdziemy od razu do kodu, by główkować, dziwić się i przeklinać.

Oto on:


package javakillers.part008;

/**
* @author pawel zainspirowany koziolekweb
*/
public class Main
{
public static void main( String[] args )
{
foo(null);
foo((Object)null);
}

public static void foo(Object object )
{
System.out.print("object ");
}
public static void foo(Object ... objects )
{
System.out.print("objects ");
}

}


Pytanie, jaki będzie wynik uruchomienia powyższego programu, pozostaje wciąż otwarte. Odpowiedzi do wyboru (do koloru):

A) program się nie skompiluje
B) program się skompiluje, ale wyrzuci java.lang.ClassCastException
C) program się skompiluje, ale wyrzuci java.lang.NullPointerException
D) program się skompiluje, uruchomi i wyświetli "object object"
E) program się skompiluje, uruchomi i wyświetli "objects objects"
F) program się skompiluje, uruchomi i wyświetli "objects object"
G) program się skompiluje, uruchomi i wyświetli "object objects"

Zanim przeczytasz rozwiązanie, spróbuj pomyśleć nad odpowiedzią.


Prawidłowa odpowiedź to F) program się skompiluje, uruchomi i wyświetli "objects object". Czemu? Żeby się dowiedzieć, musicie odwiedzić blog koziołka, bo właśnie tam przeczytałem post, który zainspirował mnie, do dzisiejszego odcinka cyklu Java Killers. Polecam!

niedziela, 1 czerwca 2008

Java Killers #007

Witam na kolejnej odsłonie cyklu Java Killers. Dzisiejszy przykład jest hybyrdą przykładu nadesłanego przez Damiana Szczepanika oraz małego triku, który znalazłem na sieci (niestety źródło zapodziałem :( ). Myślę, że dzisiejszy JK nie powinien sprawić kłopotu nikomu, kto zdawał SCJP, ale kto wie, moze się mylę?
Zatem spojrzmy na naszych bohaterów, trzy klasy: Matcher1, Matcher2 oraz Matcher3.


1 package javakillers.part007;
2
3 /**
4 * @author pawel
5 */
6 public class Matcher1
7 {
8 void g(int x)
9 {
10 System.err.print("int ");
11 }
12
13 void g(long x)
14 {
15 System.err.print("long ");
16 }
17
18 void g(Integer x)
19 {
20 System.err.print("Integer ");
21 }
22 }
23
24


1 package javakillers.part007;
2
3 /**
4 * @author pawel
5 */
6 public class Matcher2
7 {
8 void g(short x)
9 {
10 System.err.print("short ");
11 }
12
13 void g(byte x)
14 {
15 System.err.print("byte ");
16 }
17
18 void g(long x)
19 {
20 System.err.print("long ");
21 }
22
23 void g(Integer x)
24 {
25 System.err.print("Integer ");
26 }
27 }
28
29


1 package javakillers.part007;
2
3 /**
4 * @author pawel
5 */
6 public class Matcher3
7 {
8 void g(int x)
9 {
10 System.err.print("short ");
11 }
12
13 void g(Integer x)
14 {
15 System.err.print("Integer ");
16 }
17
18 void g(Object x)
19 {
20 System.err.print("Object ");
21 }
22 }
23
24


I na koniec metoda, która to wszystko uruchomi:


9 public static void main(String[] args)
10 {
11 int prim = 10;
12 Object obj = prim;
13 Integer intObj = (Integer) obj;
14
15 new Matcher1().g(prim);
16 new Matcher2().g(prim);
17 new Matcher3().g(obj);
18 new Matcher3().g(intObj);
19 }

Pytanie, które pozostaje otwarte, to co zwróci nam wywołanie tej metody? Odpowiedzi do wyboru aż sześć:

A) int Integer Object Integer
B) int long Object Integer
C) int Integer Integer Integer
D) int long Integer Integer
E) Integer Integer Integer Integer
E) Integer Integer Object Integer

Zanim przeczytasz rozwiązanie, spróbuj pomyśleć nad odpowiedzią.

Prawidłowa odpowiedź to oczywiście B) int long Object Integer. Aby zrozumieć czemu tak a nie inaczej należy wiedzieć, że przekazując metodzie wartość typu innego niż się spodziewała, dokonana zostaje konwersja typów (method-call conversion jeśli chcemy się trzymać terminologii). Konwersja jest możliwa tylko, jeśli typ metody jest "szerszy" (ang. widening conversion) od typu przekazywanego. Tak więc int'a przekażemy do metody z longiem ale nie na odwórt. Typy od "najwęższych" do "najszerszych" uszeregowane są następująco:

byte -> short -> int -> long -> float -> double

Ot i cała filozofia. Odnośnie ostatniego Matchera3, w pierwszym przypadku spodziewamy się obiektu klasy Object i ją dostajemy, w następnym wywołaniu obiektu klasy Integer i ją dostajemy. Mimo że jest to wciąż jeden i ten sam obiekt, to jednak kontekst wywołania jest różny, stąd i inne dopasowanie typów podczas wywoływania metody.

niedziela, 18 maja 2008

Java Killers #006

Po dłuższej przerwie, spowodowanej moją niekończącą się walką z pracą magisterską, mam przyjemność przedstawić wam koleją odsłonę cyklu Java Killers. Jak zwykle, aby nie przedłużać, spójrzmy na poniższy kod:


5 package javakillers.part006;
6
7 /**
8 *
9 * @author pawel
10 */
11 public class Main
12 {
13 public static void main(String[] args)
14 {
15 Main m = new Main();
16 m.run();
17 }
18
19 public void run()
20 {
21 int a = 0;
22 try
23 {
24 while(a < Integer.MAX_VALUE + 1)
25 {
26 a++;
27 }
28
29 if(a >= 0)
30 {
31 System.exit(0);
32 }
33 }
34 finally
35 {
36 System.out.println("a wynosi: " + a);
37 }
38 }
39 }
40
41


I standardowe pytanie: co zwróci powyższy kod. Dziś do wyboru aż pięć możliwych odpowiedzi, z czego jak zwykle tylko jedna jest prawdziwa:

A) a wynosi: 2147483647
B) a wynosi: -2147483648
C) a wynosi: 0
D) program się zapętli i sam z siebie nie zakończy działania
E) program się zakończy, ale nic nie napisze

Zanim przeczytasz rozwiązanie, spróbuj pomyśleć nad odpowiedzią.





Prawidłowa odpowiedź to oczywiście E) program się zakończy, ale nic nie napisze. Rozwiązanie jest trywialne, jeśli znane są nam dwie cechy języka Java:

1. Integer.MAX_VALUE + 1 zwróci nam liczbę ujemną [nie wiesz czemu? sprawdź w googlach, warto wiedzieć!]
2. Wywołanie System.exit(0); powoduje natychmiastowe zabicie procesu, przez co blok finally { ... } nigdy się nie wykona, mimo, że w oficjalnym tutorialu do języka Java możemy przeczytać " The finally block always executes when the try block exits". Jak widać nie zawsze i warto mieć tego świadomość.

I to koniec na dziś! Na zakończenie pytanie jeszcze dla ambitnych: kiedy jeszcze nie wykona się blok finally { ... }? Innymi słowy co, w try {... } musi się pojawić, żeby zawartość finally { ... } nigdy się nie wykonała?

wtorek, 6 maja 2008

Java Killers #005

Witam w kolejnym cyklu z serii Java Killers, dziś sprawdzimy naszą wiedzę z zakresu prostego operatora ?. Wszyscy znamy ta konstrukcje prawda? Dla przypomnienia wygląda ona tak:

a = b ? c : d

, gdzie zależnie od wartości zmiennej b, zmienna a przyjmuje wartość c lub d. Znane? Znane! No to przejdźmy zatem do dzisiejszego przykładu:


1 package javakillers.part005;
2
3 /**
4 * @author pawel
5 */
6 public class Main
7 {
8 public static void main(String[] args)
9 {
10 Main m = new Main();
11 m.run();
12 }
13 public void run()
14 {
15 Object o = true ? new Integer(0) : new Double(0.0);
16 System.out.println("Klasa " + o.getClass().getName() + ", wartość " + o.toString());
17 }
18 }
19


Odpowiedzi standardowo do wyboru są cztery:

A) Błąd kompilacji
B) Klasa java.lang.Integer, wartość 0
C) Klasa java.lang.Double, wartość 0.0
D) Klasa java.lang.Object, wartość java.lang.Object@87816d //przy czym @87816d może mieć dowolną inną wartość

Zanim przeczytasz rozwiązanie, spróbuj pomyśleć nad odpowiedzią.



No to jak, gotowi? Otóż poprawna odpowiedź to C) Klasa java.lang.Double, wartość 0.0 . Tak tak moi mili Object o = true ? new Integer(0) : new Double(0.0) zwróci nam obiekty typu Double. Pytanie "ale czemu?" pozostaje wciąż otwarte, gdyż na logikę widzimy, że referencja powina wskazywać na obiekt typu Integer!

Żeby zrozumieć dokładnie co stało się za naszymi plecami w czasie wykonywania kodu, należy zajrzeć do specyfikacji języka Java, a konkretnie do rozdziału '15.25 Conditional Operator ?', gdzie czytamy, że:

" (...) if the second and third operands have types that are convertible to numeric types, then (...) binary numeric promotion is applied to the operand types, and the type of the conditional expression is the promoted type of the second and third operands. Note that binary numeric promotion performs unboxing conversion and value set conversion."

Czy widzicie już zło jakie czai się za tym zdaniem? Kompilator orientuje się, że zwrócony wynik będzie zawsze typem numerycznym (Integer lub Double). W czasie działania programu dokonuje unboxingu do wartości prymitywnych, następuje promocja typu (w tym wypadku z int'a na double) i ponowny boxing do obiektu już typu Double a nie Integer!
Żeby to lepiej zobrazować, zmieńmy w naszym kodzie new Integer(0) na new Integer(1)


13 public void run()
14 {
15 Object o = true ? new Integer(1) : new Double(0.0);

Po uruchomieniu dostaniemy odpowiedź Klasa java.lang.Double, wartość 1.0. Widzicie co się dzieje?

Co ciekawe jeśli uruchomicie poniższe przykłady pod Eclipsem, wyskoczy Wam błąd kompilacji! Eclipse używa swojego własnego kompilatora (innego niż javac dostarczony z JDK), który jak tylko zobaczy Object o = true ? new Integer(1) : new Double(0.0); krzyczy "Ja nic nie wiem, ja nic nie chce, ja nic nie umiem!!!" Na innych IDE, takich jak Netbeans czy IntelliJ IDEA, kompilacja przebiega poprawnie, a wyniki działania programów są takie jak opisane powyżej.

Teraz się przyznać, ile osób znało poprawną odpowiedź? :)



sobota, 3 maja 2008

Java Killers #004

Witam w kolejnym odcinku Java Killers. Odcinek ten pojawić się miał troszkę później (jest na tyle ciekawy, że chciałem z nim poczekać do połowy maja, aż wszyscy bez wyjątku wytrzeźwieją po weekendzie majowym), ale ponieważ pojawił się na jdn.pl jego preview w komentarzach do jednego z poprzednich JK, postanowiłem umieścić go teraz, aby pozostał jeszcze jakiś element zaskoczenia. A więc, aby jak zwykle nie przedłużać wstępów, zabierzmy się do dzieła.

Dziś porównamy sobie obiekty typu Integer, ot takie najzwyklejsze ==. Przejdźmy więc do kodu:


1 package javakillers.part004;
2
3 /**
4 *
5 * @author pawel
6 */
7 public class Main
8 {
9 public static void main(String[] args)
10 {
11 Main m = new Main();
12 m.run();
13 }
14 public void run()
15 {
16 check(2, 2);
17 check(2000, 2000);
18 }
19
20 void check(Integer a,Integer b)
21 {
22 System.out.print((a==b) + " ");
23 }
24 }
25
26



Proste prawda? No więc teraz pytanie, jaka będzie odpowiedź? Standardowo 4 możliwe odpowiedzi do wyboru:

A) true true
B) false false
C) false true
D) true false




Okazuje się, że prawidłowa odpowiedź to D) true false. Ale, że co? Jak to możliwe?? Ja rozumiem A lub B ale, że D? Żeby zrozumieć czemu odpowiedź jest taka a nie inna należy zajrzeć do specyfikacji, autoboxing opisany jest w JSR-201, gdzie w rozdziale piątym czytamy:

"If the value p being boxed is true, false, a byte, a char in the range \u0000 to \u007f, or an int or short number between -128 and 127, then let r1 and r2 be the results of any two boxing conversions of p. It is always the case that r1 == r2."

Wszystko rozchodzi się o zarządzanie pamięcią. Jeśli jakąś wartość prymitywną możemy zmieścić w jednym bajcie (czyli wszystkie boolean, byte oraz char'y od \u0000 do \u007f oraz short i int w przedziale -128 do 127), wtedy boxowane obiekty znajdują się w jednym miejscu pamięci, a więc sprawdzanie ich referencji da nam zawsze true.

Miażdzące prawda?

Pytanie teraz, jak się przed tym chronić? Zasada podstawowa nakazuje przede wszystkim sprawdzanie obiektów poprzez sprawdzanie ich zawartości (używając metody equals), a nie referencji. Takie podejście uratuje Wam nie raz życie i sprawdza się nie tylko przy zabawach z obiektami typu wrapper.
Druga nauka to taka, że jak okazuje się żadna książka nigdy nie zastąpi nam do końca specyfikacji czy dokumentacji. Polecam Unixową mantrę RTFM, chociaż z drugiej strony, kto ma na to czas ;)

wtorek, 29 kwietnia 2008

Java Killers #003

Dzisiejsze Java Killers krótkie i proste z pozoru. Nie będziemy deklarować klas, tworzyć złożonych pętli, definiować rzadko używanych obiektów czy używać niespotykanych metod. Dziś po prostu dodamy do siebie dwie liczby: 221 i 7. Tak po prostu, zwykła suma i nic więcej. Czy może być coś prostszego? A więc jak zwykle, żeby nie przedłużać przejdźmy od razu do kodu:


1 package javakillers.part003;
2
3 /**
4 * @author pawel
5 */
6 public class Main {
7
8 public static void main(String[] args)
9 {
10 int val = (int) (22l + 7.0);
11 System.out.println("Value: " + val);
12 }
13 }
14
15

Proste prawda? No bo chyba prościej się nie da. No więc teraz najważniejsza sprawa. Co wypisze nam na ekran ten program? Odpowiedzi do wyboru macie cztery:

A) Value: 29
B) Value: 228
C) Program się nie skompiluje
D) Program rzuci wyjątek w czasie uruchomienia

Odpowiedź to oczywiście... A) Value: 29 No co, przecież to prosta matematyka 221 + 7 = 29! A tak poważnie problem tkwi w formatowaniu. Jeśli dobrze się przyjrzymy to zobaczymy, że nasza zmienna val obliczana jest na podstawie dwóch wartości: longa 22L oraz doubla 7.0. Tak właśnie, to było 22 a nie 221. Istota problemu jest taka, że wiele czcionek w bardzo podobny sposób renderuje nam literke l oraz cyfre 1. Kilka lat temu sławna była spora afera, gdzie ktoś zarejestrował domenę pay-pa1.com zamiast pay-pal.com i rozkradł ileś tam kont użytkowników PayPal'a. Ponieważ w javie definiując longa możemy explicite pokazać, że ten nasz long to rzeczywiście long (a nie żaden tam int czy inny) poprzez dodanie literki L na końcu, napotykamy jako programiści na ten sam problem, który swojego czasu wstrząsnął społecznością PayPal'a.

Prawda jest taka, że wszystko byłoby pięknie gdybym mógł pisać tylko tak:


long someVal = 22L;


Problem pojawia się, gdyż mogę tą zmienną zdefiniować również tak:


long someVal = 22l;


Duże czy małe "eL", dla Javy nie ma różnicy. Niby taka niewinna rzecz, ale problemów może przysporzyć... oczywiście do czasu uruchomienia debugera, kiedy wszystko staje się jasne :).

Jak się przed tym uchronić? Po prostu pisać L dużymi literami lub w IDE (czy notatniku) zmienić sobie czcionkę na jakąś porządną, na przykład taki Monospaced.


Dzisiejsze Java Killers nie było może jakimś specjalnym mykiem w języku, który zadziwił nas swoją funkcjonalnością, czy też specjalną, niszczącą funkcją zaszytą gdzieś w API. Dziś było inaczej, gdyż chodziło mi bardziej o pokazanie, że dokuczliwe błędy, mogą nas zaskoczyć w najmniej oczekiwanych sytuacjach, w miejscach kodu najbardziej dla nas wydałoby się oczywistych.

Pozostaje już tylko na zakończenie odpowiedzieć na dwa pytania: czy znaliście odpowiedź oraz czy cykl JK się Wam podoba? :)

piątek, 25 kwietnia 2008

Hitman

Skontaktowałem się z Duńskim producentem gier Io Interactive i uzyskałem już oficjalną zgodę na wykorzystywanie rysunków z gry "Hitman" do cyklu Java Killers.
Wszystko jest już legalne ;)

czwartek, 24 kwietnia 2008

Java Killers #002



Ponieważ Java Killers spotkał się z bardzo pozytywnym przyjęciem, zapraszam bardzo serdecznie na kolejną odsłonę cyklu. Dziś pobawimy się klasą z pakietu java.net służącą do przechowywania url'i. Tak tak, właśnie mowa o klasie URL.

Ok a więc bez dłuższego przedłużania, spójrzmy na poniższy kod:


6 package javakillers.part002;
7
8 import java.net.MalformedURLException;
9 import java.net.URL;
10 import java.util.HashSet;
11 import java.util.Set;
12
13 /**
14 *
15 * @author pawel
16 */
17 public class Main
18 {
19 public static void main(String[] args) throws MalformedURLException
20 {
21 String[] urlNames = {
22 "http://szulce.pl",
23 "http://paulszulc.blogspot.com",
24 "http://pawel.kn.pl",
25 "http://paulszulc.blogspot.com"
26 };
27
28 Set<URL> set = new HashSet<URL>();
29
30 for (String urlName : urlNames)
31 {
32 set.add(new URL(urlName));
33 }
34
35 System.out.println("Set size: " + set.size());
36 }
37 }
38
39


Ok, więc co tu mamy? Tworzona jest tablica stringów z nazwami url'i, przy czym zauważamy, że jeden z napisów ("http://paulszulc.blogspot.com") pojawia się podwójnie. Następnie w pętli for do HashSet'a wkładane są po kolei obiekty klasy URL tworzone na podstawie wspomnianych stringów z tabeli.
Sprawa banalna, prawda? Otwarte pytanie pozostaje tylko jedno, co wypisze nam ten program po uruchomieniu?

Odpowiedzi do wyboru macie cztery:

A) Set size: 4
B) Set size: 3
C) Set size: 2
D) Set size: 1


Gotowi, a więc uwaga, odpowiedź brzmi... zależy. Wynik końcowy działania programu zależy głównie od tego czy jesteście podłączeni do Internetu czy też nie.
Jeśli macie aktywne połączenie z siecią odpowiedź brzmi C) Set size: 2, jeśli takiego połączenia nie ma, poprawna odpowiedź brzmi B) Set size: 3.

Ok, no więc jak to możliwe? Sprawa jest dość prosta, klasa URL ma totalnie skopane metody equals i hashCode. Jak przeczytamy w dokumentacji:

"Two URL objects are equal if they have the same protocol, reference equivalent hosts, have the same port number on the host, and the same file and fragment of the file."
Jak dotąd logiczne prawda, wydaje się wszystko poprawne (względnie). Dwa obiekty URL są takie same jeśli zgadzają się pod względem protokołu (http, ftp, itp.), portu (hm.. no niech będzie) oraz wskazują na tego samego hosta. Wszystko gra? No niby tak, ale czytamy dalej:

"Two hosts are considered equivalent if both host names can be resolved into the same IP addresses; else if either host name can't be resolved, the host names must be equal without regard to case; or both host names equal to null."

I tu jest właśnie pies pogrzebany. Dwa hosty uważane są za równe, jeśli wskazują na ten sam adres IP. Jeśli nie można potwierdzić adresu IP przynajmniej jednego z host'ów, wtedy brane pod uwagę są nazwy hostów (czyli robiony jest zwykły equals na stringach).

Rozwiązanie niszczące nieprawdaż? Ale klasa URL jest z nami od JDK1.0, to były początki powstawania języka. Wybaczmy jej twórcom, każdy może mieć gorszy dzień ;)

Pozostaje więc pytanie, czemu przy połączeniu do sieci poprawna odpowiedź była C) Set size: 2. Otóż okazuje się, że "http://szulce.pl" oraz "http://pawel.kn.pl" wskazują na ten sam adres IP! Teraz sprawa powinna wydawać się już prosta. Znając ogólne działanie Setów, bogatsi o wiedzę przedstawioną powyżej, powinniście rozumieć już, czemu odpowiedzi są takie a nie inne.

Zatem na koniec pozostaje pytanie, jak przed czymś takim się ustrzec. W końcu przydatność tej klasy jest dość spora, a powyższy mankament może powodować bugi w programie, które trudno wykryć. Najlepsze rozwiązanie to używać klasy URI, którą możemy używać na tej samej zasadzie co klasę URL. Jedną z różnic jest właśnie poprawne zaimplementowanie metod equals i hashCode i niespodzianki takie jak ta pokazana dziś nie wystąpią.

No to jak znaliście poprawną odpowiedź? Poprawne były w sumie dwie, więc strzelając mieliście sporą szansę trafić :)

piątek, 18 kwietnia 2008

Java Killers #001

Witam w pierwszym (i mam nadzieje nie ostatnim) odcinku z cyklu
Java Killers. JK to światowa organizacja, która na cel bierze sobie zbicie z tropu każdego programistę, który uważa się za Javowca. Java Killers przyjmują wiele form, spotkacie ich w http://javapuzzlers.com/ czy też na rozmowie kwalifikacyjnej o pracę. To elitarna grupa, która chce udowodnić tylko jedno - że nie znasz Javy.
Wasza broń to te posty! Poznajcie wroga lepiej teraz niż na przykład na rozmowie o pracę. W cyklu tym na światło dzienne wyjdą pytania (oraz odpowiedzi), których pewnie nie spotkacie na Sun Certificated Java Programmer. To pytania wytrącające z pantałyku, powodujące salwy śmiechu i panicznej histerii. Niemniej jednak powinny Wam również dostarczyć zabawy oraz uciechy. Mimo wszystko potraktujcie je jak najbardziej poważnie, są zabójcze...

Dostatecznie zaintrygowani? Ok, a więc bez zbędnych wstępów przejdźmy do odcinka dzisiejszego. Poznajmy dwie klasy o wdzięcznych nazwach: Foo i Bar, gdzie klasa Bar dziedziczy po Foo:

package javakillers.part001;

public class Foo
{
public int a;

public Foo()
{

a = 0;
}

public void add()
{
a = a + 10;

}

}

package javakillers.part001;

public class Bar extends Foo
{
public int a;


public Bar()
{
a = 2;
}


@Override
public void add()
{
a = a + 5;
}
}

Wszystko wydaje się być proste i intuicyjne prawda? Klasa Bar nadpisuje metodę add z klasy Foo, dodatkowo sama w konstruktorze inicjalizuje zmienną a. Zatem pytanie brzmi, co zwróci poniższy kod:

public static void main(String[] args)
{
Foo f = new Bar();
f.add();
System.out.println("a = " + f.a);
System.out.println("a w Bar = " + ((Bar)f).getA());
}

Odpowiedzi do wyboru macie cztery:

A) a = 0
B) a = 7
C) a = 5
D) a = 12





Ok, gotowi? A więc prawidłowa odpowiedź to:


A) a = 0


Zadacie pewnie pytanie "Że co proszę?" Poprawna odpowiedź wydaję się każda, tylko nie zero. Przecież wywołujemy metode add() i niezależnie czy wywołana zostanie metoda z klasy Foo czy nadpisująca ją metoda z klasy Bar, zawsze zmienna a ma dodaną do siebie jakąś wartość (10 lub 5). Zatem czemu wynik końcowy zero?

Diabeł jak zwykle tkwi w szczegółach. Tworząc w klasie Bar ponownie zmienną a

public class Bar extends Foo
{
public int a;

...

ukrywamy zmienną a z klasy Foo. W kodzie klasy Bar mamy de facto dwie zmienne a. Ponieważ tylko do jednej możemy się odwołać, domyślnie jest to zmienna, która zdeklarowana była ostatnia. Innymi słowy tracimy bezpośredni dostęp do zmiennej a, którą odziedziczyliśmy po klasie Foo.
Teraz już wszystko wydawać powinno się jasne. W konstruktorze klasa Bar inicjaliuzję tą drugą zmienną a do wartości 2. Wywołanie metody add powoduje uruchomienie kodu

a = a + 5;

a zatem wartość zmiennej a w obiekcie klasy Bar przyjmuje wartość 7. Zauważamy, że wartość a z klasy Foo zostaje wciąż niezmieniona, a poniważ jest polem publicznym i mamy do niej pełny dostęp, kod

System.out.println("a = " + f.a);

drukuje wartość 'a = 0' (zwróćmy uwagę na fakt, że deklaracja obiektu new Bar wygląda następująco: Foo f = new Bar();).

Zatem pozostaje pytanie jak się przed tym bronić? Dwie ważne sprawy:

  • Nie używać zmiennych publicznych
Generalnie jest to całkowite pogwałcenie zasady enkapsulacji, co jak widać może mieć poważne konsekwancje.
  • Nie używać nazw zmiennych z klas nadrzędnych
Użycie w deklaracji zmiennej nazwy innej zmiennej, odziedziczonej od rodzica, przysłania tą oryginalną zmienną. Dobre środowisko developerskie powinno wykryć tą zależność i powiadomić w warningach o zaistniałej sytuacji.
Nie mniej nie powinniśmy w 100% polegać na IDE, dlatego zawsze warto zwrócić szczególną uwagę przy nadawaniu nazw zmiennym naszej klasy.

I to już koniec na dziś odcinka z serii Java Killers. Znaliście poprawną odpowiedź?