73. Java 嵌套类 - 局部类如何访问封装类的成员
局部类如何访问封装类的成员?
在上一个例子里,我们定义了一个 局部类 PhoneNumber 来验证电话号码,它成功访问了 封装它的外部类 LocalClassExample 的 regularExpression 变量。这就涉及到 局部类如何访问封装类的成员 的问题。
局部类可以访问哪些外部成员?
局部类(Local Class)在定义的时候,有一些特殊的访问规则:
-
可以访问封装它的类的
static变量和实例变量。 -
可以访问封装方法中的局部变量,但这些变量必须是:
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)
numberLength是final 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()方法的参数phoneNumber1和phoneNumber2。 - 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 变量 | ❌ 不允许 | ✅ 允许 |
| 访问方法的 参数 | ❌ 不允许 | ✅ 允许 |
局部类的核心规则
- 局部类可以访问外部类的成员变量(包括
static变量)。 - 局部类可以访问封装方法的局部变量,但必须是
final或 effectively final(Java 8+)。 - 局部类可以访问封装方法的参数,但参数也必须是
final或 effectively final(Java 8+)。 - 局部类的变量可能会遮蔽外部作用域的同名变量,需注意变量名冲突问题。
适用场景
局部类适合用于 方法内部的临时逻辑封装,适用于:
- 数据格式转换(如电话号码、日期格式)
- 小型计算工具(如订单计算、汇率换算)
- 数据过滤或筛选(如检查用户输入是否合法)