一、InetAddress 是什么
InetAddress 是 Java 网络编程中最基础的类之一,位于 java.net 包下,用于表示一个 IP 地址。它既可以表示 IPv4 地址,也可以表示 IPv6 地址,并且可以携带对应的主机名信息。
需要特别注意的是,InetAddress 本身是一个抽象类,不能直接通过 new 创建实例,只能通过它提供的静态工厂方法获取对象。这种设计把「地址解析」和「地址表示」解耦,屏蔽了底层 DNS 查询的细节。
java
public abstract class InetAddress implements java.io.Serializable {
// 抽象类,无法直接实例化
}
它的两个常用子类是 Inet4Address 和 Inet6Address,分别对应 IPv4 和 IPv6 地址。日常开发中我们几乎不需要直接使用子类,通过 InetAddress 的静态方法即可完成绝大多数操作。
二、创建 InetAddress 实例
InetAddress 没有公开的构造方法,常用的创建方式有以下几种。
2.1 根据主机名获取
java
InetAddress address = InetAddress.getByName("www.example.com");
System.out.println(address);
getByName 会触发一次 DNS 解析,把主机名转换为 IP 地址。如果传入的是 IP 字符串(如 "192.168.1.1"),则不会进行 DNS 查询,直接构造地址对象。
2.2 获取本机地址
java
InetAddress local = InetAddress.getLocalHost();
System.out.println(local.getHostName());
System.out.println(local.getHostAddress());
getLocalHost 返回本机的主机名和地址。在部分容器或云主机环境中,主机名可能无法正确解析,此时会抛出 UnknownHostException,需要做好异常处理。
2.3 获取全部地址
java
InetAddress[] addresses = InetAddress.getAllByName("www.example.com");
for (InetAddress addr : addresses) {
System.out.println(addr.getHostAddress());
}
一个域名可能对应多个 IP(例如负载均衡场景),getAllByName 会返回全部解析结果,适合需要遍历或做故障转移的场景。
2.4 直接由字节数组构造
java
byte[] ip = new byte[] { (byte) 192, (byte) 168, 1, 1 };
InetAddress address = InetAddress.getByAddress(ip);
System.out.println(address.getHostAddress());
getByAddress 不会进行 DNS 查询,适合已知 IP 字节序列的场景。IPv4 需要 4 个字节,IPv6 需要 16 个字节,长度不合法会抛出 UnknownHostException。
三、常用方法
| 方法 | 说明 |
|---|---|
getHostAddress() |
返回 IP 地址字符串,如 192.168.1.1 |
getHostName() |
返回主机名,可能触发反向 DNS 查询 |
getCanonicalHostName() |
返回完全限定域名(FQDN) |
isReachable(int timeout) |
判断地址是否可达,类似 ping |
isLoopbackAddress() |
是否为回环地址(127.0.0.1 或 ::1) |
isAnyLocalAddress() |
是否为通配地址(0.0.0.0 或 ::) |
isMulticastAddress() |
是否为组播地址 |
getAddress() |
返回原始 IP 字节数组 |
其中 getHostName() 需要留意:如果构造时只传入了 IP,调用该方法会触发一次反向 DNS 查询,在网络不通时可能阻塞较长时间。若只需要 IP 字符串,应优先使用 getHostAddress()。
四、地址类型判断
Java 提供了若干便捷方法用于判断地址类型,避免手工解析字符串。
java
InetAddress addr = InetAddress.getByName("127.0.0.1");
System.out.println(addr.isLoopbackAddress()); // true
System.out.println(addr.isAnyLocalAddress()); // false
System.out.println(addr instanceof Inet4Address); // true
在服务端开发中,判断请求来源是否为回环地址、是否为内网地址是常见需求。对于内网判断,Java 标准库没有直接方法,通常需要结合 getAddress() 返回的字节自行判断网段。
java
public static boolean isPrivate(InetAddress addr) {
byte[] bytes = addr.getAddress();
if (bytes.length != 4) {
return false;
}
int first = bytes[0] & 0xFF;
int second = bytes[1] & 0xFF;
return first == 10
|| (first == 172 && second >= 16 && second <= 31)
|| (first == 192 && second == 168);
}
五、可达性检测
isReachable 用于判断目标地址是否可达,底层会尝试使用 ICMP 或 TCP 连接。
java
InetAddress target = InetAddress.getByName("www.example.com");
boolean reachable = target.isReachable(3000);
System.out.println("可达:" + reachable);
需要注意两点:一是该方法在部分系统上依赖 ICMP 权限,普通用户可能无法使用,此时会退化为 TCP 探测;二是超时时间单位为毫秒,设置过短容易误判。生产环境中更推荐使用专门的连接检测手段,而不是依赖该方法做健康检查。
六、常见异常与注意事项
- UnknownHostException:主机名无法解析时抛出,属于受检异常,必须捕获或声明抛出。
- DNS 缓存 :JVM 会缓存 DNS 解析结果,默认缓存时间受
networkaddress.cache.ttl控制,修改域名解析后可能不会立即生效。 - 阻塞风险 :
getByName、getHostName等操作可能阻塞,不建议在高并发主流程中直接调用,可考虑异步或加缓存。 - IPv6 兼容 :优先使用
InetAddress而非硬编码 IPv4 字符串,以便同时兼容 IPv6 环境。 - 不可变性 :
InetAddress实例是不可变的,可以安全地在多线程间共享。
七、小结
InetAddress 是 Java 网络编程的入口类,负责 IP 地址的表示与主机名解析。掌握它的静态工厂方法、常用判断方法以及 DNS 缓存和阻塞特性,能够帮助我们在实际项目中更稳妥地处理地址相关逻辑。对于需要更细粒度控制的场景,可以进一步了解 InetSocketAddress 和 NetworkInterface 等配套类。