首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 謎題精選(二):URL.equals() 陷阱與虛擬主機副作用

     編輯:Java 相關知識

謎題代碼

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());
    }
}

選項

  • (a) 4
  • (b) 5
  • (c) 6
  • (d) None of the above

答案與深度解析

正確答案:(d) None of the above

關鍵原因:URL.equals() 基於 IP 地址而非主機名

根據 java.net.URL 文檔,兩個 URL 相等需滿足:

  1. 協議相同
  2. 主機等價(解析為同一 IP 地址)
  3. 端口相同
  4. 文件/片段相同

場景模擬

網絡環境DNS 解析結果Set 大小匹配選項
聯網,兩域名解析為同一 IP (虛擬主機)javapuzzlers.com = apache2-snort... = 同 IP4 (去重 2 個 + google + findbugs + bianceng)(a) 4
聯網,所有域名解析為不同 IP6 個不同 IP5 (去重 1 個重複)(b) 5
離線/無 DNS所有主機名無法解析 → 視為等價1 (全部視為同一)(d) None

結論:程序輸出**依賴運行時網絡環境**,屬於不可接受的非確定性行為。

根源分析:URL 設計歷史包袱

  • 1995 年 URL 類加入 JDK 1.0,當時**虛擬主機技術尚未普及** (HTTP 1.1 1997 年標準化)
  • equals() 基於 IP 比較,無法區分同 IP 下的不同虛擬主機
  • 此行為**違反直覺**:開發者預期 new URL("https://a.com").equals(new URL("https://b.com")) == false

最佳實踐:棄用 URL,改用 URI

// 修正版:使用 URI
Set<URI> favorites = new HashSet<URI>();
for (String urlName : URL_NAMES)
    favorites.add(URI.create(urlName));  // 靜態工廠,無異常拋出
System.out.println(favorites.size());    // 穩定輸出 5 (僅去重字符串相同項)

URI 優勢

  • equals() **純字符串比較**,不涉及網絡解析
  • 不可變、線程安全、符合 RFC 3986
  • 不拋出 MalformedURLException (檢查型異常),URI.create() 拋運行時異常
  • 支持相對路徑解析、規範化、組件訪問

避坑檢查清單

  1. ❌ **不要在集合鍵/Set 元素中使用 URL**
  2. ❌ **不要依賴 URL.equals()/hashCode() 進行業務邏輯判斷**
  3. ✅ **資源標識統一使用 URI**
  4. ✅ **需發起 HTTP 請求時,轉換為 URL 或直接用 HttpClient/HttpURLConnection**
  5. ✅ **序列化傳輸使用 String 或 URI,反序列化時轉換**

擴展思考

  • java.net.URI 修正了 URL 的設計缺陷,但 JDK 為兼容性保留了 URL
  • 類似陷阱:InetAddress.getByName() 緩存導致 DNS 變更不生效、URLClassLoader 依賴 URL.equals() 可能錯誤去重 JAR
  • 現代框架 (Spring、Netty、OkHttp) 內部均使用 URI/String 作為資源標識

總結

這道謎題揭示了 **API 設計滯後於協議演進** 的經典教訓。URL.equals() 基於 IP 的語義在虛擬主機時代失效,導致非確定性行為。作為開發者,**識別標識用 URI、網絡通信用專用客戶端**,是避免此類陷阱的鐵律。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved