73. Java 嵌套类 - 局部类如何访问封装类的成员

98 阅读4分钟

73. Java 嵌套类 - 局部类如何访问封装类的成员

局部类如何访问封装类的成员?

在上一个例子里,我们定义了一个 局部类 PhoneNumber 来验证电话号码,它成功访问了 封装它的外部类 LocalClassExampleregularExpression 变量。这就涉及到 局部类如何访问封装类的成员 的问题。


局部类可以访问哪些外部成员?

局部类(Local Class)在定义的时候,有一些特殊的访问规则:

  1. 可以访问封装它的类的 static 变量和实例变量

  2. 可以访问封装方法中的局部变量,但这些变量必须是:

    • final 变量(Java 7 及以前要求必须是 final)。
    • "实际上是 final"(effectively final) 变量(Java 8 及以后)。
    • 不能修改局部变量的值,否则编译会报错。

注意:‌effectively final是指那些虽然没有被声明为final,但是在使用过程中并没有被修改的(引用地址)局部变量。这些变量在初始化后不会被再次修改,因此编译器认为它们是effectively final


示例解析

我们来看一个例子,演示局部类访问外部类成员和方法参数

代码
public class LocalClassExample {
    
    static String regularExpression = "[^0-9]"; // 匹配非数字字符的正则表达式

    public static void validatePhoneNumber(String phoneNumber1, String phoneNumber2) {
        
        final int numberLength = 10; // 规定电话号码长度(必须是 final 或 effectively final)

        // 局部类 PhoneNumber
        class PhoneNumber {
            String formattedPhoneNumber = null;

            // 构造方法
            PhoneNumber(String phoneNumber) {
                // 访问封装类的 static 变量 regularExpression
                String currentNumber = phoneNumber.replaceAll(regularExpression, "");
                
                // 访问封装方法的局部变量 numberLength
                if (currentNumber.length() == numberLength) {
                    formattedPhoneNumber = currentNumber;
                } else {
                    formattedPhoneNumber = null;
                }
            }

            public String getNumber() {
                return formattedPhoneNumber;
            }

            // 访问方法参数(Java 8+ 才允许)
            public void printOriginalNumbers() {
                System.out.println("Original numbers are " + phoneNumber1 + " and " + phoneNumber2);
            }
        }

        // 创建局部类的实例
        PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1);
        PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2);

        // 打印原始号码(Java 8+)
        myNumber1.printOriginalNumbers();

        // 输出结果
        if (myNumber1.getNumber() == null) {
            System.out.println("First number is invalid");
        } else {
            System.out.println("First number is " + myNumber1.getNumber());
        }

        if (myNumber2.getNumber() == null) {
            System.out.println("Second number is invalid");
        } else {
            System.out.println("Second number is " + myNumber2.getNumber());
        }
    }

    public static void main(String... args) {
        validatePhoneNumber("123-456-7890", "456-7890");
    }
}

运行结果

Original numbers are 123-456-7890 and 456-7890
First number is 1234567890
Second number is invalid

代码解析

1️⃣ 局部类访问封装类的 static 变量

String currentNumber = phoneNumber.replaceAll(regularExpression, "");

这里 regularExpression外部类的静态变量局部类可以直接访问不需要 static 关键字


2️⃣ 局部类访问方法的局部变量

if (currentNumber.length() == numberLength)
  • numberLengthfinal int 类型,局部类可以直接访问。
  • Java 8 以前:局部类只能访问 final 变量。
  • Java 8 以后:即使 numberLength 没有 final 修饰符,只要它的值 没有在方法中被修改,局部类 仍然可以访问("effectively final")。
如果我们修改 numberLength,会报错

比如:

PhoneNumber(String phoneNumber) {
    numberLength = 7;  // ❌ 这里修改了局部变量
}

这样会报错:

从内部类引用的局部变量必须是 final 或 effectively final

原因:Java 需要保证 局部变量的值不会因为局部类的存在而改变,否则会影响变量的生命周期和线程安全。


3️⃣ 局部类访问方法参数

public void printOriginalNumbers() {
    System.out.println("Original numbers are " + phoneNumber1 + " and " + phoneNumber2);
}
  • 这个方法直接访问了 validatePhoneNumber() 方法的参数 phoneNumber1phoneNumber2
  • Java 7 及以前:不允许访问方法参数,除非它们是 final
  • Java 8 及以后:可以访问 effectively final 的参数(即 方法参数值在方法体内没有被修改)。

局部类的变量遮蔽问题

局部类内部的变量 可能会遮蔽 封装类或封装方法中的同名变量。比如:

public void printOriginalNumbers() {
    String phoneNumber1 = "999-999-9999"; // 局部变量
    System.out.println("Original numbers are " + phoneNumber1 + " and " + phoneNumber2);
}
  • 这里 phoneNumber1 在方法 printOriginalNumbers 里被重新声明,这个 局部变量 会遮蔽 外部 phoneNumber1,导致打印出来的值不一样。
  • 解决方法:
    • 避免使用相同的变量名
    • 如果必须要访问外部类变量,可以用 外部类.this.变量名(但局部类无法这样访问方法的局部变量)。

总结

访问类型Java 7 及以前Java 8 及以后
访问外部类的成员变量✅ 允许✅ 允许
访问方法的 final 变量✅ 允许✅ 允许
访问方法的 effectively final 变量❌ 不允许✅ 允许
访问方法的 参数❌ 不允许✅ 允许

局部类的核心规则

  1. 局部类可以访问外部类的成员变量(包括 static 变量)
  2. 局部类可以访问封装方法的局部变量,但必须是 final 或 effectively final(Java 8+)。
  3. 局部类可以访问封装方法的参数,但参数也必须是 final 或 effectively final(Java 8+)。
  4. 局部类的变量可能会遮蔽外部作用域的同名变量,需注意变量名冲突问题

适用场景

局部类适合用于 方法内部的临时逻辑封装,适用于:

  • 数据格式转换(如电话号码、日期格式)
  • 小型计算工具(如订单计算、汇率换算)
  • 数据过滤或筛选(如检查用户输入是否合法)