1. 可变参数:灵活应对方法参数不确定的场景
在Java开发中,我们经常会遇到方法参数个数不确定的情况。传统做法是使用数组,但每次调用都需要手动创建数组对象,代码显得冗长。Java 5引入的可变参数特性完美解决了这个问题。
1.1 基本语法与使用规范
可变参数的声明方式是在方法参数类型后加三个点(...),实际上编译器会将其处理为数组。一个典型的使用示例如下:
java复制public static void printNumbers(int... numbers) {
for (int num : numbers) {
System.out.println(num);
}
}
// 调用方式
printNumbers(1); // 单个参数
printNumbers(1, 2, 3); // 多个参数
printNumbers(); // 无参数
重要提示:一个方法中只能有一个可变参数,且必须是方法的最后一个参数。违反这个规则会导致编译错误。
1.2 底层实现原理
虽然可变参数使用起来像普通参数,但编译器在背后做了转换工作。上面的printNumbers方法实际上会被编译为:
java复制public static void printNumbers(int[] numbers) {
// 方法体不变
}
当调用printNumbers(1, 2, 3)时,编译器会自动将参数打包成一个数组。这种语法糖让代码更简洁,但要注意性能影响——每次调用都会创建一个新数组。
1.3 实际应用中的注意事项
- 空参数处理:可变参数可以接受0个参数,此时传入的是长度为0的数组而非null。但为了健壮性,建议显式检查:
java复制if (numbers == null || numbers.length == 0) {
throw new IllegalArgumentException("至少提供一个数字");
}
- 方法重载问题:当可变参数方法与固定参数方法重载时,固定参数版本会优先匹配:
java复制void process(String... strs) { /*...*/ }
void process(String s1, String s2) { /*...*/ }
process("a", "b"); // 调用固定参数版本
- 性能敏感场景:在循环或高频调用的方法中使用可变参数可能产生大量临时数组,这种情况下使用固定参数或对象池可能更合适。
2. Collections工具类:集合操作的瑞士军刀
java.util.Collections类提供了一系列静态方法,用于对集合进行各种操作。这些方法涵盖了排序、查找、同步化等常见需求,是日常开发中的得力助手。
2.1 常用方法深度解析
2.1.1 排序与查找
java复制List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9);
// 自然排序
Collections.sort(numbers); // [1, 1, 3, 4, 5, 9]
// 自定义排序
Collections.sort(numbers, (a, b) -> b - a); // 降序排列
// 二分查找(必须先排序)
int index = Collections.binarySearch(numbers, 4);
实际经验:对于ArrayList,Collections.sort()底层调用的是List的sort()方法,性能与直接调用list.sort()相当。但对于LinkedList,直接使用Collections.sort()效率更高。
2.1.2 不可变集合
创建不可变集合是防御性编程的重要手段:
java复制List<String> immutableList = Collections.unmodifiableList(new ArrayList<>(Arrays.asList("a", "b")));
Set<Integer> immutableSet = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(1, 2)));
Map<String, Integer> immutableMap = Collections.unmodifiableMap(new HashMap<>() {{
put("key", 1);
}});
2.1.3 同步集合
虽然Vector和Hashtable是线程安全的,但它们的同步粒度太粗,性能较差。更好的做法是使用Collections的同步包装方法:
java复制List<String> syncList = Collections.synchronizedList(new ArrayList<>());
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
踩坑提醒:同步集合只保证单个操作是原子的,复合操作仍需额外同步。例如下面的代码仍然不是线程安全的:
java复制if (!syncList.contains(item)) {
syncList.add(item); // 这两步操作之间可能被其他线程插入
}
2.2 实际开发中的最佳实践
- 集合初始化:使用Collections.addAll()比多次调用add()更高效:
java复制List<String> list = new ArrayList<>();
Collections.addAll(list, "a", "b", "c");
- 空集合处理:返回空集合而非null可以避免NPE:
java复制public List<String> getItems() {
return Collections.emptyList(); // 而不是返回null
}
- 单元素集合:Collections.singletonList()创建不可变的单元素集合,比新建ArrayList更节省内存:
java复制List<String> single = Collections.singletonList("唯一元素");
3. Stream流:声明式集合处理新范式
Java 8引入的Stream API彻底改变了集合处理的方式,它允许以声明式的方式处理数据,并支持并行操作。Stream不是数据结构,而是对数据源的计算操作的抽象。
3.1 Stream操作的三阶段
Stream操作分为三个阶段:创建、中间操作和终端操作。
3.1.1 创建Stream
java复制// 从集合创建
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream1 = list.stream();
// 从数组创建
String[] array = {"a", "b", "c"};
Stream<String> stream2 = Arrays.stream(array);
// 直接创建
Stream<String> stream3 = Stream.of("a", "b", "c");
// 无限流
Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 1);
3.1.2 中间操作
中间操作是惰性的,只有终端操作才会触发实际计算:
java复制List<String> filtered = list.stream()
.filter(s -> s.startsWith("a")) // 过滤
.map(String::toUpperCase) // 映射
.sorted() // 排序
.distinct() // 去重
.skip(1) // 跳过前n个
.limit(10) // 限制数量
.collect(Collectors.toList()); // 终端操作
3.1.3 终端操作
终端操作会触发实际计算并关闭流:
java复制// 聚合操作
long count = list.stream().count();
Optional<String> max = list.stream().max(String::compareTo);
// 收集结果
List<String> resultList = list.stream().collect(Collectors.toList());
Set<String> resultSet = list.stream().collect(Collectors.toSet());
String joined = list.stream().collect(Collectors.joining(","));
// 遍历
list.stream().forEach(System.out::println);
// 匹配检查
boolean anyMatch = list.stream().anyMatch(s -> s.startsWith("a"));
boolean allMatch = list.stream().allMatch(s -> s.length() > 0);
3.2 并行流与性能考量
Stream的parallel()方法可以轻松实现并行处理:
java复制long count = list.parallelStream()
.filter(s -> s.length() > 3)
.count();
性能提示:并行流不一定总是更快。对于小数据集或计算简单的操作,并行化的开销可能超过收益。建议:
- 数据量大于1万条时考虑使用并行流
- 避免在并行流中使用有状态的操作(如sorted)
- 确保操作是无副作用的
3.3 实际应用中的陷阱与技巧
- 流只能消费一次:尝试重复使用已关闭的流会抛出IllegalStateException:
java复制Stream<String> stream = list.stream();
stream.count(); // 终端操作
stream.count(); // 抛出异常
- 异常处理:Stream API中的Lambda表达式不允许抛出受检异常。处理方式:
java复制list.stream()
.map(s -> {
try {
return doSomething(s);
} catch (IOException e) {
throw new RuntimeException(e);
}
})
.forEach(System.out::println);
- 调试技巧:peek()方法可以在不修改流的情况下查看元素:
java复制list.stream()
.peek(System.out::println) // 调试输出
.map(String::toUpperCase)
.collect(Collectors.toList());
4. 方法引用:Lambda表达式的语法糖
方法引用是Java 8引入的另一种简化Lambda表达式的语法,它直接指向已有方法,使代码更加简洁。
4.1 四种方法引用形式
4.1.1 静态方法引用
java复制// Lambda形式
Function<String, Integer> parser1 = s -> Integer.parseInt(s);
// 方法引用形式
Function<String, Integer> parser2 = Integer::parseInt;
4.1.2 实例方法引用
java复制String str = "example";
// Lambda形式
Supplier<Integer> lambda1 = () -> str.length();
// 方法引用形式
Supplier<Integer> methodRef1 = str::length;
4.1.3 任意对象的实例方法引用
java复制// Lambda形式
Function<String, Integer> lambda2 = s -> s.length();
// 方法引用形式
Function<String, Integer> methodRef2 = String::length;
4.1.4 构造方法引用
java复制// Lambda形式
Supplier<List<String>> lambda3 = () -> new ArrayList<>();
// 方法引用形式
Supplier<List<String>> methodRef3 = ArrayList::new;
4.2 方法引用与Lambda的选择
虽然方法引用更简洁,但并非所有情况都适用。当需要复杂逻辑时,Lambda表达式更合适:
java复制// 适合方法引用
list.forEach(System.out::println);
// 需要Lambda表达式
list.forEach(s -> {
if (s.length() > 3) {
System.out.println(s.toUpperCase());
}
});
4.3 实际开发中的最佳实践
- 可读性优先:当方法引用使代码更清晰时使用它,否则保留Lambda表达式。例如:
java复制// 更清晰
map.replaceAll(String::toUpperCase);
// 不如Lambda直观
map.merge("key", 1, Integer::sum); // 可能让人困惑
- 与Stream API结合:方法引用特别适合与Stream API一起使用:
java复制List<String> upperCaseNames = names.stream()
.filter(Objects::nonNull)
.map(String::toUpperCase)
.collect(Collectors.toList());
- 自定义函数式接口:可以创建自己的函数式接口来使用方法引用:
java复制@FunctionalInterface
interface TriFunction<T, U, V, R> {
R apply(T t, U u, V v);
}
TriFunction<Integer, Integer, Integer, Integer> sum = Math::addExact;
5. 综合应用案例:从集合到报表生成
让我们通过一个完整的例子,将今天学习的内容综合运用起来。假设我们需要从一个员工列表中生成部门薪资统计报表。
java复制public class Employee {
private String name;
private String department;
private int salary;
// 构造方法、getter/setter省略
}
List<Employee> employees = Arrays.asList(
new Employee("张三", "研发部", 8000),
new Employee("李四", "研发部", 9000),
new Employee("王五", "市场部", 7000),
new Employee("赵六", "市场部", 7500)
);
// 使用Stream处理数据
Map<String, Double> report = employees.stream()
.filter(e -> e.getSalary() > 0) // 过滤无效数据
.collect(Collectors.groupingBy(
Employee::getDepartment, // 按部门分组
Collectors.averagingInt(Employee::getSalary) // 计算平均薪资
));
// 输出报表
report.forEach((dept, avgSalary) ->
System.out.printf("%s部门的平均薪资:%.2f元%n", dept, avgSalary)
);
这个例子展示了如何组合使用Stream API、方法引用和集合操作来完成一个实际任务。通过这种方式,我们可以写出既简洁又易于维护的代码。
