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