类多了要分门别类,成员多了要控制可见性,类里还能再定义类。本篇讲 package 与 import 的组织规则、四种权限修饰符的可见性矩阵,以及成员、局部、匿名三种内部类的写法与场景——匿名内部类正是今天 lambda 表达式的前身。

本篇目标

  • 会用 package 组织类、用 import 引入类
  • 背出四种权限修饰符的可见性矩阵
  • 掌握成员内部类、局部内部类的语法
  • 理解匿名内部类的本质,知道它如何演进到 lambda

package 与 import

为什么要有包:类一多就会重名、难管理,包就是分类存放 .class 的文件夹,也是类的命名空间。分包通常按模块(如 student、teacher 各一包),而不是按动作(add、delete、update 各一包),因为模块的内聚性更高。规则只有几条:package 必须是文件第一条有效语句且只能有一个;多级包用点分隔,包名约定全小写、用公司域名倒置保证唯一;不带 package 的类属于默认包,正式项目里不该出现。

带包编译运行:javac -d . HelloWorld.java 让编译器自动建目录,java com.example.HelloWorld 用全限定名启动。跨包使用类要写全类名,太啰嗦,于是有 import——它只是”免写全名”的快捷方式,可有多条,位于 package 与 class 之间;import java.util.* 虽省事,但容易掩盖类名冲突,工程规范一般要求精确导包。唯一例外是 java.lang 包(String、System、Math 都在里面),它对每个类自动可见,不需要导包。记忆顺序:package(一个)→ import(多个)→ class。

四种权限修饰符:可见性矩阵

修饰符 本类 同包类 不同包子类 不同包无关类
private 可见 不可见 不可见 不可见
默认(不写) 可见 可见 不可见 不可见
protected 可见 可见 可见 不可见
public 可见 可见 可见 可见

protected 最易误解:它 = 默认权限 + 不同包的子类。设计惯例:成员变量几乎一律 private,对外方法用 public,留给子类的用 protected,包内协作用默认——这正是封装思想的权限化表达。常见组合也要眼熟:常量 public static final,工具方法 public static,模板骨架 public final,抽象方法 public abstract。

成员内部类

一个类定义在另一个类的成员位置,就是成员内部类。访问特点:内部类可以直接访问外部类的所有成员,包括私有;外部类要用内部类成员,必须先 new 出内部类对象。创建语法有点怪,记住一次即可:

class Outer {
private int num = 10;
class Inner {
public void show() { System.out.println(num); } // 直访外部私有
}
}
Outer.Inner oi = new Outer().new Inner();

内部类用 private 修饰,就只能在外部类内部使用;用 static 修饰就成了静态内部类,创建不再依赖外部对象:Outer.Inner oi = new Outer.Inner();,但静态内部类只能访问外部的静态成员。两个深入点:每个非静态内部类对象都隐式持有外部类对象的引用,这正是它能直访外部成员的原因;编译后内部类会生成独立的字节码文件,名字形如 Outer$Inner.class。

经典面试题:外部类 num=10、内部类 num=20、方法局部 num=30,在 show() 里依次输出 30、20、10 怎么写?答案是 num、this.num、Outer.this.num——类名.this 是拿到外部类当前对象的固定写法。

局部内部类

定义在方法内部的类,出了方法就无法使用。它在 JDK 8 之前有个著名限制:访问所在方法的局部变量时,该变量必须用 final 修饰。原因是生命周期错位——方法弹栈后局部变量就没了,而内部类对象可能还活着,final 变量会被捕获复制一份,保证对象还能拿到值。JDK 8 起改为”事实 final”(effectively final):变量只要没被二次赋值,不写 final 也能编译通过。语法放宽了,但理解”为什么变量不能被改”依然重要。

匿名内部类:lambda 的前身

某个接口或抽象类只需要”一次性”实现时,专门写一个类太浪费。匿名内部类把”定义子类 + new 对象”合成一步:

interface Inter { void show(); }

Inter i = new Inter() { // 本质:Inter 的匿名实现类对象
@Override
public void show() {
System.out.println("HelloWorld");
}
};
i.show();

它的本质是”继承了该类或实现了该接口的子类对象,只是没有名字”。经典面试题:补全代码让 Outer.method().show(); 输出 HelloWorld——method() 返回一个 Inter 的匿名实现即可,链式调用顺势完成。

匿名内部类最辉煌的舞台是回调与事件监听,比如线程:new Thread(new Runnable() { public void run() { ... } })。但接口只有一个抽象方法时,Java 8 的 lambda 能把它压成一行:new Thread(() -> { ... })。lambda 不是匿名内部类的语法糖(底层是 invokedynamic),却精准接管了它的典型场景。今天还在写匿名内部类,多半是需要实现多方法接口或抽象类、需要持有状态;单方法场合请优先 lambda。

经典练习

  • 权限验证:建两个包,各放一个类,逐项验证可见性矩阵,重点感受 protected 在”不同包子类”中的特殊性。
  • 匿名实现:method(Person p) 只接收抽象类 Person 的子类对象,要求不定义任何具名子类完成调用;再把 Person 换成单抽象方法接口,用 lambda 重写一遍,对比两种写法的代码量。

小结

  • package 分类管理类,import 免写全类名;顺序 package → import → class。
  • 权限记忆:private 本类,默认加同包,protected 再加不同包子类,public 全开。
  • 内部类直访外部私有成员;成员内部类靠 外部对象.new 内部类() 创建,静态内部类脱离外部对象。
  • 局部内部类捕获的局部变量必须”事实 final”;匿名内部类是无名字类对象,单抽象方法场景已被 lambda 接管。