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

5 komentarzy:

mateusz.fiołka pisze...

Już gdzieś to widziałem ...

Paweł pisze...

No ja tego wczesniej nie znalem i ogladajac na googlach Java Puzzlers bylem tym niezle zaskoczony.

Antoni Jakubiak pisze...

To rozwiązanie w Javie jest zaskakujące. Dzięki, że o nim napisałeś.
Wydaje mi się jednak, że rozwiązanie jest słuszne. Z poziomu URL adresy http://szulce.pl i http://pawel.kn.pl są jednakowe. Wskazują one na ten sam zasób. Dopiero obsługa protokołu HTTP umożliwi pobranie danych z opdowiedniego serwera wirtualnego.

Paweł pisze...

Widzisz nie do konca, zauwaz ze szulce.pl i pawel.kn.pl to rzeczywiscie ta sama witryna, tlyko inna nazwa wskazuje. Wszystko byloby ok, ale jest jedno ale.

Jest cos takiego jak wirtualny hosting, firma ma swoj serwer (jeden adres IP) i sprzedaje miejsce na nim jako swewer www. Teraz masz tam dwie rozne witryny, np www.superkwiarciarnia.pl (sprzedaja kwiaty) oraz www.goracelaskiXXX.pl (sprzedaja... gorace prety stalowe firmy XXX).
Te dwa serwisy stoja na jednym serwerze, maja ten sam adres IP i wirtuany hosting wie jak odroznic je od seibie (calosc po stornie serwera)

Natomiast my w kodzie Java dostaniemy
URL u1 = new URL("www.superkwiarciarnia.pl")
URL u2 = new URL("www.goracelaskiXXX.pl")

(u1 == u2) == true


a to juz przeciez juz prawda nie jest...

Anonimowy pisze...

Jeśli już to u1.equals(u2) == true :). Co wiecej, przez wirtualny hosting dwa URLe plikow o tych samych sciezkach na hoscie (np. www.superkwiarciarnia.pl/index.html i www.goracelaskiXXX.pl/index.html) tak naprawdę dotyczą dwóch różnych plików. Jak dla mnie takie zachowanie URLa (jako klasy) to po prostu bug.