Java API 入门:从常用类到实际调用

AI资讯1小时前发布 jdkjadmin
0 00
Java API 入门:从常用类到实际调用

Java API 入门:从常用类到实际调用

Java API 是 Java 平台提供的一组类、接口和方法。写程序时,处理字符串可以用 String,管理一组对象可以用集合类,读写文件可以用 Files,发起 HTTP 请求则可以使用 HttpClient。理解这些 API 的关键,不是背下所有方法,而是知道它们解决什么问题、接受什么输入,以及失败时会发生什么。

本文示例以 Java 17 为基准,使用的核心 API 大多在 Java 8 或 Java 11 中已经提供。文中的代码片段除非特别说明,均可放进单独的 .java 文件运行。需要注意,“Java API”也可能指某个服务对外提供的 HTTP 接口;标准库 API 与 HTTP 服务 API 不是同一回事,后文会通过实际请求说明两者如何配合。

先分清类、对象与方法

类定义一类对象可以执行的操作。例如,String 是字符串类,LocalDate 表示不带时区的日期。调用方法时,先看方法属于哪个类、参数是什么、返回值是什么,以及它是否会修改原对象或抛出异常。

String text = "Java";
int length = text.length();

这里 length() 返回字符串长度,类型为 int。Java 的 API 文档(Javadoc)是核对这些细节的首选资料:查清方法是否为静态方法、允许的输入、异常类型和版本要求,能避免依赖猜测编程。

字符串:不可变对象与输入校验

String 适合保存和处理文本。它是不可变对象:调用 trim()、replace() 等方法会返回新字符串,不会修改原来的内容。判断内容是否相等应使用 equals,而不是 ==;后者比较的是对象引用是否相同。

public class StringDemo {
    public static void main(String[] args) {
        String input = "  Java API  ";
        String normalized = input.trim();

        System.out.println(normalized);                 // Java API
        System.out.println(normalized.equals("Java API")); // true
        System.out.println(normalized.length());         // 8
    }
}

可用以下命令编译运行:javac StringDemo.java 和 java StringDemo。

几个常见边界值得留意:

  • 字符串可能为 null。直接调用 value.length() 会触发 NullPointerException。如果业务允许缺省值,应明确处理,而不是假定输入总存在。
  • String.length() 返回 UTF-16 代码单元数量,并不总等于用户看到的字符数。某些表情符号会占用两个代码单元,组合字符也可能由多个代码点组成。
  • trim() 与 strip() 对空白字符的处理规则并不相同;后者从 Java 11 起提供,依据 Unicode 空白字符判断。需要处理国际化文本时,应选择符合业务规则的方法。
  • 不要把用户输入拼接进 SQL、命令行或 HTML 后就认为安全。字符串 API 负责文本处理,不负责上下文相关的转义或防注入。

大量拼接文本时,可以用 StringBuilder,避免循环里反复创建中间字符串:

StringBuilder builder = new StringBuilder();
for (int i = 0; i < 3; i++) {
    builder.append("item-").append(i).append('\n');
}
String result = builder.toString();
System.out.print(result);

集合:List、Set、Map 的选择

集合用于存放多个对象。List 保留顺序且允许重复;Set 用于维护不重复元素;Map 根据键查找值。选型应由数据语义决定,而不只是习惯。

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class CollectionDemo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Lin");
        names.add("Chen");
        names.add("Lin");

        Set<String> uniqueNames = new HashSet<>(names);
        Map<String, Integer> scores = new HashMap<>();
        scores.put("Lin", 90);
        scores.put("Chen", 85);

        System.out.println(names);       // 保留插入顺序,含重复值
        System.out.println(uniqueNames); // 不重复;HashSet 不保证迭代顺序
        System.out.println(scores.get("Lin"));
    }
}

泛型中的 <String> 限定集合元素类型,能让编译器提前发现类型错误。集合可以通过 List.of(...) 等工厂方法快速创建,但这类集合不可修改;对它调用 add 或 remove 会抛出 UnsupportedOperationException。若需要可修改列表,可以创建 new ArrayList<>(List.of(...))。

Map.get(key) 找不到键时会返回 null;但若映射中允许存放 null 值,仅凭返回值无法区分“键不存在”和“值为 null”,此时可用 containsKey 或调整数据约定。HashMap 与 HashSet 不承诺元素顺序;需要稳定顺序时,可考虑 LinkedHashMap 或排序集合。

集合通常不是线程安全的。多个线程并发修改同一个普通 ArrayList 或 HashMap,可能导致数据竞争;仅把变量声明为 final 并不能解决这个问题。并发场景应选择合适的并发集合或使用同步策略。

日期与时间:区分日期、时刻和时区

Java 8 引入的 java.time API 通常优于旧式的 Date 和 Calendar。LocalDate 表示日历日期,不包含时间和时区;Instant 表示时间线上的一个时刻;ZonedDateTime 则带有时区规则。选错类型容易造成跨时区错误。

import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

public class TimeDemo {
    public static void main(String[] args) {
        LocalDate date = LocalDate.of(2025, 3, 8);
        LocalDate nextWeek = date.plusWeeks(1);

        DateTimeFormatter formatter =
                DateTimeFormatter.ofPattern("uuuu-MM-dd");
        System.out.println(nextWeek.format(formatter));

        ZonedDateTime nowInTokyo =
                ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
        System.out.println(nowInTokyo);
    }
}

LocalDate.of 会校验日期,月份或日期超出有效范围时抛出 DateTimeException。面向用户展示时间时,通常要明确采用哪个时区;服务器保存事件时间时,常用 Instant,展示时再转换到用户所在时区。不要把某个服务器的默认时区当成所有用户的时区。

格式化符号也有差别:日期年份一般使用 uuuu,而 YYYY 表示基于周的年份,在年末或年初可能与日历年份不同。解析外部日期文本时,应定义明确格式,并对非法输入做错误处理。

金额计算:避免用 double 表示十进制金额

double 使用二进制浮点表示,许多十进制小数无法精确表示。因此,涉及货币、账目或需要严格十进制规则的数值,通常使用 BigDecimal。用字符串构造可以避免先经过浮点数带来的误差。

import java.math.BigDecimal;
import java.math.RoundingMode;

public class MoneyDemo {
    public static void main(String[] args) {
        BigDecimal price = new BigDecimal("19.95");
        BigDecimal quantity = new BigDecimal("3");
        BigDecimal total = price.multiply(quantity);

        BigDecimal tax = total.multiply(new BigDecimal("0.08"))
                .setScale(2, RoundingMode.HALF_UP);

        System.out.println("合计:" + total.setScale(2, RoundingMode.UNNECESSARY));
        System.out.println("税额:" + tax);
    }
}

new BigDecimal(0.1) 会接收已经近似表示的二进制浮点数,结果可能不是期望的精确 0.1。金额运算还需要业务层规定币种、小数位和舍入方式;不同币种的小数位并不都相同,不能无条件假设保留两位。BigDecimal 能提供精确十进制运算,但它不会替业务自动决定舍入规则。

文件读写:明确编码、路径与异常

Path 表示文件系统路径,Files 提供读写和检查文件的便捷方法。下面的示例以 UTF-8 写入文本,再读回内容:

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class FileDemo {
    public static void main(String[] args) throws IOException {
        Path file = Path.of("notes.txt");
        Files.writeString(file, "Java 文件示例\n", StandardCharsets.UTF_8);

        String content = Files.readString(file, StandardCharsets.UTF_8);
        System.out.print(content);
    }
}

这段代码适合小文件或演示程序。readString 会把文件内容整体读入内存,不适合大小不可控的大文件;大文件应使用缓冲流或逐行处理。文件不存在、权限不足或磁盘出现问题时,读写可能抛出 IOException,实际应用需要根据上下文记录或向调用方报告错误。

文件路径是一个重要安全边界。若路径来自用户输入,不能只靠字符串拼接来判断它是否落在某个目录下;像 ../ 这样的路径片段可能造成目录穿越。应在受控根目录下解析路径,并验证规范化后的目标仍位于允许目录中,同时考虑符号链接和文件系统权限。校验字符串并不能代替操作系统权限控制。

异常处理:区分预期失败与程序错误

Java 的异常分为受检查异常和运行时异常等类型。像文件读写中的 IOException 通常需要在方法签名中声明或捕获;NullPointerException 等运行时异常则可能反映程序逻辑或未处理输入。捕获异常时应针对可恢复的失败采取具体措施,不要用空的 catch 块把问题吞掉。

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public class ExceptionDemo {
    static String readConfig(Path path) {
        try {
            return Files.readString(path, StandardCharsets.UTF_8);
        } catch (IOException e) {
            System.err.println("读取配置失败:" + e.getMessage());
            return "";
        }
    }

    public static void main(String[] args) {
        System.out.println(readConfig(Path.of("config.txt")));
    }
}

示例用空字符串作为失败结果,只适用于“空内容可接受”的场景。真实业务应选择能表达失败的方式,例如返回明确结果、抛出带有上下文的异常,或在入口处报告错误。不要把口令、令牌、完整个人信息等敏感内容直接写进异常日志。

实际调用 HTTP API:Java 11 的 HttpClient

调用远程 HTTP 服务时,标准库提供了 java.net.http.HttpClient。它可以发送请求并接收响应,但不会替应用定义服务的数据结构,也不会自动判断响应是否符合业务预期。以下示例向公开的 https://httpbin.org/get 发起 GET 请求,适用于联网环境下的演示。

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class HttpDemo {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(5))
                .build();

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://httpbin.org/get"))
                .timeout(Duration.ofSeconds(10))
                .header("Accept", "application/json")
                .GET()
                .build();

        HttpResponse<String> response = client.send(
                request,
                HttpResponse.BodyHandlers.ofString());

        System.out.println("状态码:" + response.statusCode());
        System.out.println(response.body());
    }
}

Java 11 起可直接使用这套 API。将代码保存为 HttpDemo.java,运行 javac HttpDemo.java 和 java HttpDemo。调用需要网络连通目标站点;DNS、代理、防火墙、TLS 配置和服务可用性都会影响结果,因此不应把一次演示请求当作生产环境的完整实现。

示例设置了连接超时和请求超时。连接超时限制建立连接所需时间;请求超时限制等待该请求完成的时间。超时并不保证服务端没有处理请求:客户端超时后,远程服务可能已经执行了操作。因此,对有副作用的请求进行重试时,必须考虑重复提交,不能把“客户端没收到响应”简单等同于“服务端没执行”。

收到响应后还要检查状态码。HTTP 4xx 通常表示请求或权限方面的问题,5xx 通常表示服务端处理失败,但具体含义取决于服务约定。send 成功返回响应对象,并不表示状态码一定是 200,也不表示响应体一定是有效 JSON。生产代码应显式处理状态码、响应体、响应大小和错误格式。

默认响应处理器 BodyHandlers.ofString() 会把响应体读入内存。如果对端可能返回很大的内容,应考虑流式处理或限制可接受的数据量。服务端提供的 JSON 也不应盲目反序列化或信任其中的路径、命令、HTML 等内容;解析和校验应按数据格式与使用场景另行设计。

示例地址使用 HTTPS。对于携带身份凭据或敏感数据的调用,应使用 HTTPS 并校验服务端证书,不要通过关闭证书校验来“修复”连接错误。API 密钥不应硬编码进源代码或提交到公开版本库;应通过安全的配置管理方式注入,并避免将令牌放进会被日志记录的 URL 查询参数。

服务端若要求查询参数,应按其接口约定编码参数值。编码不是输入校验,也不等于授权检查;尤其不要把用户提供的任意 URL 直接交给服务器代为访问,否则可能引入服务端请求伪造等风险。请求方法、请求头、鉴权方式和重试策略都应以目标 API 的文档为准。

常见错误速查

  • 用 == 比较两个字符串的内容。改用 equals,并在可能为 null 时先设计好空值处理规则。
  • 修改 List.of 创建的列表。它是不可修改列表;需要修改时复制到可变集合。
  • 认为 HashMap 有固定遍历顺序。若顺序属于业务要求,应明确选择有序结构,不要依赖偶然现象。
  • 用 double 直接累计金额。采用合适的十进制表示,并由业务定义精度和舍入规则。
  • 把没有时区的日期时间当作全球统一时刻。区分“日期”“本地时间”和“绝对时刻”,在需要转换时明确指定时区。
  • 把小文件读取方式照搬到大文件。整体读入内存会增加内存压力,数据规模未知时应选择流式方案。
  • 认为 HTTP 请求返回对象就代表业务成功。还需检查状态码、响应体和接口定义,并处理超时与网络失败。
  • 把转义、加密或参数编码当成输入安全的全部措施。它们解决的问题不同;SQL 参数化、权限校验、路径约束和输出编码都要结合具体上下文使用。

如何进一步使用 Java API 文档

遇到不确定的参数限制或行为时,应查阅对应 Java 版本的官方 API 文档,并核对具体方法的描述、异常、返回值和版本标记。尤其要确认集合是否可变、方法是否允许 null、输入格式是否固定、调用是否阻塞,以及资源是否需要关闭。本文中的示例刻意使用标准库,实际项目还可能需要日志、JSON 解析、连接池或框架;这些属于额外依赖,应根据项目需求和维护成本选择,而不是把它们误认为 Java 标准 API 的一部分。

掌握常用 API 的实用路径,是从数据类型和边界开始:先选对表示,再确认合法输入与失败方式,最后验证性能和安全要求。这样使用字符串、集合、时间、文件和 HTTP 客户端时,代码不仅能运行,也更容易在真实环境中维护。

© 版权声明

相关文章