Java 包、权限修饰符与内部类
类多了要分门别类,成员多了要控制可见性,类里还能再定义类。本篇讲 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 修饰,就只能在外部类内部使用;用 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(); } |
它的本质是”继承了该类或实现了该接口的子类对象,只是没有名字”。经典面试题:补全代码让 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 接管。

