7Effective Java(第3版)

通用编程

日常编程技巧

局部变量循环优化字符串拼接基础类型

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第7章:通用编程(General Programming)

一、导读

1.1 本章学习目标

第7章涵盖了 Java 日常编程中的通用实践和技巧。这些看似琐碎的细节,实际上对代码质量有着深远的影响。本章通过9条经验法则,帮助你掌握日常编程的最佳实践。

本章的核心学习目标包括:

  • 掌握局部变量的最佳使用方式:理解作用域最小化原则。
  • 学会使用 for-each 循环:理解它相比传统 for 循环的优势。
  • 掌握字符串拼接的正确方式:理解 StringBuilder 的使用场景。
  • 理解基本类型与包装类型的区别:避免自动装箱的性能陷阱。
  • 学会使用反射和原生方法:理解它们的适用场景和局限性。

1.2 为什么第七章如此重要

通用编程实践是代码质量的基石。局部变量的作用域、循环的选择、字符串拼接的方式、基本类型的使用——这些看似微小的决策,累积起来会显著影响代码的可读性、性能和正确性。

1.3 核心问题引导

如何最小化局部变量的作用域?

for-each 循环相比传统 for 循环有什么优势?

什么时候应该使用 StringBuilder?什么时候 String 拼接就够了?

自动装箱在什么场景下会导致性能问题?

反射的性能开销有多大?什么场景下可以接受?


二、核心概念详解

7.1 将局部变量的作用域最小化(Item 57)

7.1.1 作用域最小化原则

java
// 反模式:变量作用域过大
Set<String> items = getItems();
Iterator<String> it = items.iterator();
// ... 200 行代码 ...
while (it.hasNext()) {
    System.out.println(it.next());
}

// 正确做法:在需要时才声明
Set<String> items = getItems();
// ... 200 行代码 ...
for (String item : items) {
    System.out.println(item);
}

7.1.2 for 循环的优势

for 循环相比 while 循环,可以将迭代变量的作用域限制在循环体内:

java
// for 循环:迭代变量作用域在循环体内
for (Element e : collection) {
    // e 只在这里可见
    process(e);
}
// e 在这里不可见

// while 循环:迭代变量可能在外部声明
Iterator<Element> it = collection.iterator();
while (it.hasNext()) {
    Element e = it.next();
    process(e);
}
// it 在这里仍然可见

7.1.3 技巧:先使用后初始化

java
// 反模式:在循环外初始化
Element e = null;
for (Iterator<Element> it = collection.iterator(); it.hasNext(); ) {
    e = it.next();
    // ...
}

// 正确做法:在循环头中声明
for (Iterator<Element> it = collection.iterator(); it.hasNext(); ) {
    Element e = it.next();
    // ...
}

7.2 了解和使用库(Item 59)

7.2.1 使用标准库的优势

经过充分测试:标准库的代码经过了大量用户的检验。

性能优化:标准库通常经过精心优化。

可维护性:使用标准库的代码更容易被其他开发者理解。

功能丰富:标准库提供了大量的功能,避免重复造轮子。

java
// 反模式:自己实现随机数
Random random = new Random();
int n = random.nextInt(max);

// 正确做法:使用 ThreadLocalRandom(多线程环境)
int n = ThreadLocalRandom.current().nextInt(max);

// 反模式:自己实现数组复制
int[] dest = new int[src.length];
for (int i = 0; i < src.length; i++) {
    dest[i] = src[i];
}

// 正确做法:使用 System.arraycopy
int[] dest = new int[src.length];
System.arraycopy(src, 0, dest, 0, src.length);

// 更好的做法:使用 Arrays.copyOf
int[] dest = Arrays.copyOf(src, src.length);

7.3 如果需要精确的结果,避免使用 float 和 double(Item 60)

java
// 反模式:使用 double 进行货币计算
System.out.println(1.03 - 0.42);  // 0.6100000000000001
System.out.println(1.00 - 9 * 0.10);  // 0.09999999999999995

// 正确做法1:使用 BigDecimal
BigDecimal a = new BigDecimal("1.03");
BigDecimal b = new BigDecimal("0.42");
System.out.println(a.subtract(b));  // 0.61

// 正确做法2:使用 int 或 long(以分为单位)
int cents = 103 - 42;  // 61 cents
System.out.println(cents);  // 61

7.4 基本类型优于包装类型(Item 61)

7.4.1 基本类型与包装类型的区别

特性基本类型包装类型
值实际值对象的引用
默认值0, false, nullnull
内存栈上堆上
性能快慢(需要装箱拆箱)
java
// 反模式:使用包装类型进行比较
Integer a = 100;
Integer b = 100;
System.out.println(a == b);  // true(缓存范围内)

Integer c = 200;
Integer d = 200;
System.out.println(c == d);  // false(超出缓存范围)

// 正确做法:使用基本类型
int a = 100;
int b = 100;
System.out.println(a == b);  // true

7.4.2 自动装箱的性能陷阱

java
// 反模式:自动装箱创建大量对象
public static void main(String[] args) {
    Long sum = 0L;
    for (long i = 0; i < Integer.MAX_VALUE; i++) {
        sum += i;  // 每次循环都创建一个新的 Long 对象
    }
    System.out.println(sum);
}

// 正确做法:使用基本类型
public static void main(String[] args) {
    long sum = 0L;
    for (long i = 0; i < Integer.MAX_VALUE; i++) {
        sum += i;
    }
    System.out.println(sum);
}

7.5 如果其他语言更适合,就不要使用 Java(Item 67 相关)

这不是说 Java 不好,而是说每种语言都有自己的适用场景。如果你的需求更适合用 Python 做数据分析、用 Rust 做系统编程、用 JavaScript 做前端开发,那就用那些语言。Java 的优势在于企业级应用、大型系统、Android 开发等场景。


三、实战案例

7.1 字符串拼接性能对比

java
public class StringConcatenation {
    public static void main(String[] args) {
        int n = 100_000;

        // 方式1:String 拼接(O(n²))
        long start = System.nanoTime();
        String result = "";
        for (int i = 0; i < n; i++) {
            result += i;  // 每次创建新的 String 对象
        }
        long time1 = System.nanoTime() - start;

        // 方式2:StringBuilder(O(n))
        start = System.nanoTime();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; i++) {
            sb.append(i);
        }
        String result2 = sb.toString();
        long time2 = System.nanoTime() - start;

        // 方式3:String.join(O(n))
        start = System.nanoTime();
        String[] parts = new String[n];
        for (int i = 0; i < n; i++) {
            parts[i] = String.valueOf(i);
        }
        String result3 = String.join("", parts);
        long time3 = System.nanoTime() - start;

        System.out.printf("String +=: %,d ns%n", time1);
        System.out.printf("StringBuilder: %,d ns%n", time2);
        System.out.printf("String.join: %,d ns%n", time3);
    }
}

7.2 局部变量作用域优化

java
// 反模式:变量作用域过大
public class DataProcessor {
    private List<String> buffer = new ArrayList<>();

    public void process() {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader("data.txt"));
            String line;
            while ((line = reader.readLine()) != null) {
                buffer.add(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try { reader.close(); } catch (IOException e) {}
            }
        }
        // buffer 仍然持有引用,无法被 GC
    }
}

// 正确做法:最小化作用域
public class DataProcessor {
    public List<String> process() {
        List<String> buffer = new ArrayList<>();
        try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                buffer.add(line);
            }
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
        return buffer;
    }
}

四、本章小结

第7章系统讲解了通用编程的核心知识:

最小化局部变量作用域:提高代码可读性,减少错误。

了解和使用库:避免重复造轮子,利用经过验证的代码。

避免 float/double 进行精确计算:使用 BigDecimal 或整数。

基本类型优于包装类型:避免自动装箱的性能陷阱。

字符串拼接:循环中使用 StringBuilder,少量拼接用 +。

这些通用编程实践看似简单,却是写出高质量代码的基础。


五、思考与练习

思考题

如何判断一个局部变量的作用域是否过大?

for-each 循环在什么情况下不能使用?

自动装箱的缓存范围是什么?为什么?

BigDecimal 的性能开销有多大?什么场景下需要关注?

标准库有哪些常用的工具类?

编程练习

优化字符串拼接:将一个使用 String += 的循环改为 StringBuilder。

消除自动装箱:找出代码中的自动装箱点并消除。

最小化变量作用域:重构一段代码,将所有变量的作用域最小化。


7/10通用编程
0%