import java.net.*;
import java.util.*;
public class UrlSet {
private static final String[] URL_NAMES = {
"https://javapuzzlers.com",
"https://apache2-snort.skybar.dreamhost.com",
"https://www.google.com",
"https://javapuzzlers.com", // 重複
"https://findbugs.sourceforge.net",
"https://www.bianceng.cn"
};
public static void main(String[] args) throws MalformedURLException {
Set<URL> favorites = new HashSet<URL>();
for (String urlName : URL_NAMES)
favorites.add(new URL(urlName));
System.out.println(favorites.size());
}
}
根據 java.net.URL 文檔,兩個 URL 相等需滿足:
| 網絡環境 | DNS 解析結果 | Set 大小 | 匹配選項 |
|---|---|---|---|
| 聯網,兩域名解析為同一 IP (虛擬主機) | javapuzzlers.com = apache2-snort... = 同 IP | 4 (去重 2 個 + google + findbugs + bianceng) | (a) 4 |
| 聯網,所有域名解析為不同 IP | 6 個不同 IP | 5 (去重 1 個重複) | (b) 5 |
| 離線/無 DNS | 所有主機名無法解析 → 視為等價 | 1 (全部視為同一) | (d) None |
結論:程序輸出**依賴運行時網絡環境**,屬於不可接受的非確定性行為。
URL 類加入 JDK 1.0,當時**虛擬主機技術尚未普及** (HTTP 1.1 1997 年標準化)equals() 基於 IP 比較,無法區分同 IP 下的不同虛擬主機new URL("https://a.com").equals(new URL("https://b.com")) == false// 修正版:使用 URI
Set<URI> favorites = new HashSet<URI>();
for (String urlName : URL_NAMES)
favorites.add(URI.create(urlName)); // 靜態工廠,無異常拋出
System.out.println(favorites.size()); // 穩定輸出 5 (僅去重字符串相同項)
equals() **純字符串比較**,不涉及網絡解析MalformedURLException (檢查型異常),URI.create() 拋運行時異常URL**URL.equals()/hashCode() 進行業務邏輯判斷**URI**URL 或直接用 HttpClient/HttpURLConnection**String 或 URI,反序列化時轉換**java.net.URI 修正了 URL 的設計缺陷,但 JDK 為兼容性保留了 URLInetAddress.getByName() 緩存導致 DNS 變更不生效、URLClassLoader 依賴 URL.equals() 可能錯誤去重 JARURI/String 作為資源標識這道謎題揭示了 **API 設計滯後於協議演進** 的經典教訓。URL.equals() 基於 IP 的語義在虛擬主機時代失效,導致非確定性行為。作為開發者,**識別標識用 URI、網絡通信用專用客戶端**,是避免此類陷阱的鐵律。