Java 包、作用域与访问控制

笔记/Java/Java基础/面向对象编程 (OOP)/Java 包、作用域与访问控制

背景与动机#

Java 程序一旦不再只是一个小文件,就会遇到三个问题:

  • 类放在哪里更清楚
  • 变量在什么范围内可见
  • 一个字段或方法应该开放给谁访问

这三个问题分别对应:package、作用域、访问控制。

核心原理拆解#

1. package 用来组织类#

package com.example.demo;

包的作用主要有两个:

  • 给类分组,方便按功能组织代码
  • 避免不同模块里出现同名类冲突

例如两个不同包可以同时有 User 类:

com.example.user.User
com.example.admin.User

在日常开发里,还可以继续往前理解一步:

  • package 不只是“名字前缀”
  • 它同时在帮助你划分模块边界

比如:

  • com.example.user 可以放用户领域相关类
  • com.example.order 可以放订单领域相关类

这样做的好处,不只是目录更整齐,更重要的是后面的访问控制也会跟着更清晰。很多“默认权限”“同包可见”的语义,都是建立在 package 边界上的。

再补一个常见规则:源码目录结构通常会和包名保持一致,比如:

src/com/example/demo/User.java

它通常对应:

package com.example.demo;

这不是 Java 语法强制你背诵的“形式要求”,而是为了让类路径、编译和项目结构保持一致。

2. import 用来引入其他包的类#

import java.util.List;
import java.util.ArrayList;

用了 import 之后,就可以直接写 ListArrayList,不用每次都写完整类名。

补一个容易忽略的小点:

  • 同一个包里的类,通常不需要互相 import
  • java.lang 包下的很多类,比如 StringSystem,默认就能直接使用

2.1 同名类冲突时,import 也不是万能的#

如果两个包下恰好有同名类,就不能靠同时 import 它们来解决:

import com.example.user.User;
import com.example.admin.User;

这种写法会产生冲突,因为编译器无法判断你后面写的 User 到底指哪一个。

这时通常只能保留一个 import,另一个写全限定类名:

import com.example.user.User;
public class Demo {
User user1 = new User();
com.example.admin.User user2 = new com.example.admin.User();
}

所以 import 的作用是“简化类名书写”,不是“创造新的命名空间”。

2.2 import static 和普通 import 不一样#

普通 import 导入的是类名:

import java.util.List;

import static 导入的是类里的静态成员:

import static java.lang.Math.PI;
import static java.lang.Math.max;

这样后面就可以直接写:

double r = PI;
int m = max(3, 5);

初学阶段先知道它的存在即可。大多数业务代码里,普通 import 更常见。

3. 变量作用域:在哪声明,就在哪生效#

先看局部变量:

void test() {
int x = 10;
System.out.println(x);
}

这里的 x 只在 test 方法内部有效。出了方法,就看不到它了。

代码块里的变量作用域还会更小:

if (true) {
int y = 20;
}
// y 在这里不可见

for 循环里的变量作用域也一样:

for (int i = 0; i < 3; i++) {
System.out.println(i);
}
// i 在这里不可见

方法参数本身也属于局部作用域的一部分:

void print(int n) {
System.out.println(n);
}

这里的 n 只在 print 方法内部有效。

3.1 变量遮蔽:局部变量可能“挡住”成员变量#

class User {
String name;
void setName(String name) {
this.name = name;
}
}

这里方法参数也叫 name,它会暂时遮蔽成员变量 name

所以:

  • 左边的 this.name 是成员变量
  • 右边的 name 是方法参数

这也是为什么前面讲 this 时,总会和作用域问题连在一起。很多时候并不是语法复杂,而是同名变量出现在了不同作用域中。

3.2 作用域越小,变量越容易控制#

一个很实用的经验是:变量能放在小作用域里,就尽量不要提前放大作用域。

原因很直接:

  • 可见范围越大,越容易被误用
  • 生命周期越长,越不容易判断当前值是否可靠
  • 代码阅读时,也更难快速看出“这个变量到底在哪些地方会变”

所以“在哪声明,就在哪生效”不只是语法规则,也是在帮助我们控制复杂度。

作用域嵌套图#

img
img

4. 成员变量和局部变量的区别#

class User {
String name; // 成员变量
void print() {
int age = 18; // 局部变量
System.out.println(name);
System.out.println(age);
}
}

可以先记:

  • 成员变量属于对象,方法里通常都能访问
  • 局部变量只在声明它的方法或代码块里有效

如果再说细一点,它们还有几个常见区别:

  • 成员变量跟着对象存在,对象创建后它就有机会存在
  • 局部变量通常随着方法调用开始创建,方法结束后离开作用域
  • 成员变量可以有默认值,局部变量在使用前通常需要先显式赋值

比如成员变量:

class User {
int age; // 默认是 0
}

而局部变量如果没赋值就直接用,通常会编译失败。

所以它们的区别不只是“写在类里还是写在方法里”,还包括生命周期和初始化规则。

5. Java 的四种访问控制级别#

从“最封闭”到“最开放”可以记成这四层:

  • private
  • 默认(不写修饰符,也叫 package-private)
  • protected
  • public

它们的大致区别可以先这样记:

  • private:只能本类访问
  • 默认:同包可访问
  • protected:同包可访问,不同包的子类也可访问
  • public:任何地方都可访问

img
img

这张表要真正记住,最好不要死背,而是抓住“开放范围逐层扩大”这条主线:

  • private:先只允许类自己访问
  • 默认权限:先扩大到同一个包
  • protected:再额外照顾继承体系
  • public:最后完全公开

5.1 每种修饰符最典型的使用场景#

  • private 最适合字段和内部实现细节。默认先收紧,再决定要不要放开。
  • 默认权限 最适合同包内协作的辅助类、工具方法、内部组件。
  • protected 最适合父类留给子类复用的扩展点。
  • public 最适合真正要对外暴露的 API。

所以访问修饰符不是“哪个更高级”,而是“你想把这个成员开放给谁”。

5.2 构造方法也有访问控制#

很多人只盯着字段和方法,但构造方法同样可以受访问控制约束:

class User {
private User() {
}
}

这会直接限制“谁可以创建对象”。

比如:

  • public 构造方法:任何能看到这个类的人都能 new
  • 默认权限构造方法:通常只有同包类能直接 new
  • private 构造方法:类外不能直接 new

这也是单例模式、工具类模式经常会用到的手段。

6. 顶层类通常只能是 public 或默认权限#

写在文件最外层的普通类,通常只能用两种可见性:

  • public
  • 默认权限

也就是说,顶层类不能直接写成 private class Userprotected class Userprivateprotected 更多是给成员、方法、内部类这些位置准备的。

7. 默认访问权限其实很常见#

class User {
void print() {
}
}

这里类和方法都没有写访问修饰符,它们的默认权限就是“同包可见”。

很多初学者只记 publicprivate,但实际项目里,默认权限也是 Java 用来收紧暴露面的重要工具。

如果一个类、方法、构造方法本来就只是给同包内部协作用,直接写成默认权限,往往比一上来 public 更合理。

这背后的思路其实很朴素:

  • 能少暴露,就少暴露
  • 能只给包内用,就不要先放成全局公开

很多项目后面越改越难收口,就是因为一开始把太多东西写成了 public

7.1 默认权限和 protected 最容易混#

这两个修饰符初学时最容易一起记乱,可以先这样分:

  • 默认权限:核心关键词是“同包”
  • protected:核心关键词是“子类”

也就是说:

  • 默认权限不会因为你是“子类”就额外放开
  • protected 则会对继承关系额外放宽一层

8. protected 的重点不是“半公开”,而是“对子类友好”#

protected 最主要的意义,是让子类能访问父类中某些本来不适合完全公开、但又需要继承复用的成员。

它最容易和默认权限混淆,所以学习时先抓住一句话:

  • 默认权限更偏“同包”
  • protected 更偏“继承体系”

8.1 同包访问和跨包子类访问,不是一回事#

先看同包:

  • 如果两个类在同一个包里,protected 成员通常可以访问

再看跨包:

  • 如果不在同一个包里,只有“子类这条继承链上的访问”才是重点

所以 protected 不是“所有不同包类都半开放”,而是“在继承关系下额外开放”。

8.2 为什么不同包的普通对象不能随便访问 protected#

很多人会误以为:

  • “既然不同包子类能访问”
  • “那是不是不同包里拿到父类对象也能访问”

这通常不对。

protected 关注的是“子类复用父类能力”,不是“所有外部对象都能顺手访问父类内部成员”。

所以学习时要把重点放在:

  • 你是不是子类
  • 你是不是在合适的继承访问上下文中

而不是只看“是不是不同包”。

8.3 protected 最典型的使用场景#

比如父类里有一个字段或方法:

  • 不适合完全 public
  • 但子类扩展时又确实需要用到

这时就可以考虑 protected

例如模板方法、可扩展父类、框架基类,都很常见。

protected 跨包继承访问图#

img
img

最小可运行代码示例#

package demo;
public class ScopeAccessDemo {
public static void main(String[] args) {
User user = new User();
user.name = "Tom";
user.print();
}
}
class User {
String name; // 默认权限
private int age = 18; // 私有字段
public void print() {
int local = 1;
System.out.println(name);
System.out.println(age);
System.out.println(local);
}
}

这个例子里:

  • name 是成员变量,默认同包可见
  • age 是私有字段,只能在 User 类内部访问
  • local 是局部变量,只在 print 方法中有效

如果后面你继续补示例,可以考虑再单独加一个“双包 + protected”的小例子。因为这篇当前最抽象、最难靠一句话讲透的地方,就是 protected 的跨包行为。

常见陷阱与错误示例#

1. 误以为不写修饰符等于 public#

不对。不写修饰符通常表示默认权限,也就是同包可见,不是全局公开。

2. 在代码块外访问局部变量#

局部变量只在自己的作用域内有效,超出方法或代码块就不能再访问。

3. 一上来就把所有字段和方法都写成 public#

这会让类的内部细节暴露太多,后续很难约束使用方式,也不利于封装。

4. 误以为顶层类也能随便写成 private#

顶层类只有 public 和默认权限两种常见选择。privateprotected 不能直接用于普通顶层类声明。

5. 把 protected 简单理解成“比默认权限多一点”#

这样记很容易在跨包继承时出错。protected 的关键不只是“范围更大”,而是它把“继承体系”也纳入了访问规则。

6. 忽略变量遮蔽导致读错代码#

如果方法参数、局部变量和成员变量同名,很容易误判到底访问的是哪一个变量。读到这类代码时,要优先看有没有 this.

面试高频问题#

1. package 的作用是什么#

它既能帮助组织代码结构,也能避免不同模块中出现同名类冲突。

2. Java 有哪些访问修饰符#

常见有 private、默认权限、protectedpublic 四种,开放范围从小到大依次扩大。

3. 局部变量和成员变量有什么区别#

局部变量只在方法或代码块内部生效;成员变量属于对象,通常在类的实例方法中都能访问。

4. 顶层类可以用哪些访问修饰符#

普通顶层类通常只能是 public 或默认权限,不能直接声明成 privateprotected

5. protected 和默认权限最本质的区别是什么#

默认权限主要围绕“同包”;protected 则在此基础上,进一步照顾“不同包中的子类访问”。

一句话总结#

package 解决类怎么组织,作用域解决变量在哪可见,访问控制解决成员该暴露给谁,这三者一起决定了 Java 代码的边界感。

文章目录

文章目录