1. 引言
在 Java 中,就像在现实生活里,并非一切都应该对所有人随时开放。想象一下你的公寓:你肯定不希望邻居随便进你的卧室,对吧?在程序中我们也常常希望“关上某些门”,让外部无法访问某些变量或方法。
为此,Java 提供了访问修饰符——一些特殊的关键字,用来指明某个变量或方法可以被使用的范围:例如 public 或 private。
主要访问修饰符
| 修饰符 | 可见范围 |
|---|---|
|
在类可见的任何地方(包括其他包、其他文件) |
|
仅在同一个类内部 |
|
仅在同一个包内(即同一目录下的类中) |
还有其他修饰符,但等我们讲到继承时再详细讨论。
实践示例
public class User
{
public String name; // 对所有人可见
private int age; // 仅在 User 类内部可见
public void sayHello()
{
System.out.println("你好,我叫 " + name);
}
private void secretMethod()
{
System.out.println("这是一个秘密方法!");
}
}
说明:
- name — 在任何可以使用 User 类的地方都可见。
- age — 仅在 User 类内部可见。
- sayHello() — 公有方法,可从任何其他类调用。
- secretMethod() — 私有方法,无法从外部调用。
在真实任务中如何工作?
假设我们有一个表示银行账户的类。我们显然不希望任何人都能直接修改余额!因此余额变量应为 private,并提供专门的方法来操作它。
为什么不应该把一切都设为 public?
你可能会想:“干脆把一切都设为 public,省心!”但这会走向混乱。想象一下,任何人都能修改你的余额、用户名,甚至调用本应仅供内部使用的方法。在大型程序中,这会导致错误和“魔法”般的 bug。
Java 开发者的黄金法则:
先把一切设为 private,然后只对外暴露真正需要的部分。
2. 变量的作用域
作用域是变量“存在”并且可以被使用的代码区域。一旦离开这个区域,变量就“消失”了——仿佛它从未存在过。
在 Java 中,按作用域可以将变量分为:
- 局部变量 — 在方法或代码块 { ... } 中声明。
- 方法参数 — 在方法的圆括号中声明。
- 类字段 — 在类中、但在方法之外声明。
如果变量在 { 块 } 内声明,
它只在该块内可见,
在块外不可访问。
局部变量
局部变量只在其声明的方法或代码块内存活。
void printSum(int a, int b)
{
int sum = a + b; // 局部变量
System.out.println(sum);
}
// 此处 sum 已不存在!
在方法之外访问 sum 会导致编译错误:“无法找到变量 sum”。
方法参数
参数也是变量,但它们只在方法内部存活。
void greet(String name)
{
System.out.println("你好," + name);
}
// 此处 name 已不存在!
类字段(成员变量)
类字段在类内但在方法之外声明。它们在该类的所有方法中可见。
public class Counter
{
private int count = 0; // 类字段
public void increment()
{
count++; // 可以使用该字段
}
public int getCount()
{
return count; // 同样可以使用该字段
}
}
3. 变量遮蔽(Shadowing)
遮蔽(shadowing)是指在某个作用域内声明了一个与外部作用域同名的变量(或参数)。在这个内层作用域中,“新”的名字会遮蔽外层的名字,无法直接访问外层的值。
遮蔽示例:
class ShadowDemo
{
int value = 10; // 类字段
void printValue()
{
System.out.println(value); // 10 — 输出类字段
int value = 5; // 局部变量遮蔽了类字段
System.out.println(value); // 输出 5,而不是 10
}
}
在这个示例中,当我们写下 int value = 5; 时,声明了一个新的局部变量,它优先于同名的类字段。在 printValue() 方法内部访问 value 时,将使用局部变量而不是类字段。
如果确实需要访问类的静态字段,请使用类名作为前缀:
class ShadowDemo
{
static int value = 10; // 静态类字段
void printValue()
{
System.out.println(value); // 10 — 类字段
int value = 5;
System.out.println(value); // 5 — 局部变量
System.out.println(ShadowDemo.value); // 10 — 静态类字段,通过 'ShadowDemo'
}
}
如果需要访问非静态类字段,请使用关键字 this。它指向当前对象实例。
class ShadowDemo
{
int value = 10;
void printValue()
{
System.out.println(value); // 10 — 类字段
int value = 5;
System.out.println(value); // 5 — 局部变量
System.out.println(this.value); // 10 — 类字段,通过 'this'
}
}
4. 实践:访问修饰符与作用域
让我们继续开发教学应用:一个简单的学生管理系统。我们将为字段和方法设置不同的访问修饰符。
示例:Student 类
public class Student
{
public String name; // 学生姓名(对所有人可见)
private int age; // 年龄(仅在类内部可见)
public Student(String name, int age)
{
this.name = name;
this.age = age;
}
public void sayHello()
{
System.out.println("你好,我叫 " + name);
}
private void printSecret()
{
System.out.println("我的年龄: " + age);
}
public void revealSecret()
{
printSecret(); // 在类内部可以调用私有方法
}
}
使用 Student 类
public class Main
{
public static void main(String[] args)
{
Student s = new Student("Jimmy", 20);
s.sayHello(); // OK:公有方法
s.revealSecret(); // OK:公有方法,它在内部调用私有方法
s.age = 30; // 错误!字段 age 是私有的
s.printSecret(); // 错误!方法 printSecret 是私有的
}
}
5. 使用访问修饰符与作用域时的常见错误
错误 1:全部设为 public —— 混乱从此开始!
如果把所有字段和方法都设为公有,你就会失去对数据被谁、以何种方式修改的控制。请将字段设为 private,只对外暴露必要内容。
错误 2:尝试在变量作用域之外使用它。
例如,在方法内声明了变量,却试图在外部访问——会得到编译错误。局部变量只在自己的 { ... } 块中存活。
错误 3:命名冲突(变量遮蔽)。
如果在方法中声明了与类字段同名的变量,很容易混淆你实际在使用哪个变量。使用 this 来明确指向类字段:this.value。
错误 4:从其他类访问 private 方法或字段。
如果方法或字段被标记为 private,它们只在同一个类内可访问。从外部访问会导致编译错误。
错误 5:忽略了循环或代码块中的作用域。
在循环或任何 { } 块内声明的变量只在该块内存活。块外它不存在。
GO TO FULL VERSION