CodeGym /课程 /JAVA 25 SELF /访问修饰符

访问修饰符

JAVA 25 SELF
第 15 级 , 课程 1
可用

1. 访问修饰符概览

Java 中,类、字段、方法与构造器有四种访问级别:

修饰符 可访问范围
public
到处都可见:类内、其他类、其他包
protected
类内、子类(继承者)、同一包中的其他类
(package-private)
仅在包内(当未显式指定修饰符时)
private
仅在当前类内部

下面逐一分析它们——配合示例、一些小笑话和意想不到的转折。

public — 公共访问

public 就像向全世界张贴的公告:“人人可进!”。如果类、字段、方法或构造器被声明为 public,就可以从任何其他类访问,甚至来自其他包。

示例:

public class Cat {
    public String name;
    public void sayMeow() {
        System.out.println("喵!");
    }
}

该类及其字段/方法可以从任何地方访问。这在你希望类对所有人可用时很方便,例如在编写库时。

但是!公共字段并不总是好主意(见上一讲)。通常只将需要对外可见的方法设为 public,而字段几乎总是保持为 private

private — 仅在类内部可见

private 就像带密码的保险箱:除了类自身,谁也无法访问这些成员。即使是子类也看不到私有字段和方法!

示例:

public class Cat {
    private String secretName;

    public void setSecretName(String name) {
        secretName = name;
    }

    public String getSecretName() {
        return secretName;
    }
}

这里 secretName 无法被其他类直接读取或修改。只有 Cat 自身(或它的方法)可以这样做。这是封装的基础:我们隐藏内部细节,并只通过方法提供访问。

protected — 受保护的访问

protected 有点像 VIP 通行证:类自身、它的子类(即使在其他包中)以及当前包内的所有类都可访问。

示例:

public class Animal {
    protected int age;

    protected void growOlder() {
        age++;
    }
}

现在,任何继承自 Animal 的类都可以访问字段 age 和方法 growOlder()

public class Cat extends Animal {
    public void haveBirthday() {
        growOlder();
        System.out.println("猫已经 " + age + " 岁了!");
    }
}

同一包中的所有类也可以访问 protected 成员。

(package-private) — 包内可见

如果你完全未指定访问修饰符,成员就被视为 package-private(也称“默认访问”)。这就像一扇没有锁的门,但只对自己人开放:仅同一包的类可访问。

示例:

class Dog {
    String name; // package-private
    void bark() { // package-private
        System.out.println("汪!");
    }
}

Dog、它的字段 name 以及方法 bark() 仅在同一包内可用。从其他包访问会导致编译错误。

2. 在字段与方法上应用访问修饰符

为什么字段几乎总是设为 private

类的字段代表其内部状态。如果将它们设为公共的,任何外部代码都可以随时修改它们。这就像让陌生小孩把你的家具当乐高玩:某天醒来,你会发现冰箱倒置在浴室里。

糟糕的封装示例:

public class Person {
    public String name;
    public int age;
}

良好封装的示例:

public class Person {
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }
}

通过方法(getter 与 setter)可以控制外部类如何修改字段。例如,你可以禁止出现负年龄。

何时将方法设为 publicprotectedpackage-private

  • public —— 当方法需要对所有人可见时。通常用于类的对外核心功能。
  • protected —— 当方法只供子类或包内使用(例如可能在子类中有用的辅助方法)。
  • package-private —— 当方法只在包内需要,但不应对外可见(例如实现细节)。
  • private —— 当方法仅供类内部使用(例如内部逻辑的辅助方法)。

示例:

public class BankAccount {
    private double balance;

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }

    protected void applyInterest() {
        balance *= 1.05;
    }

    void internalAudit() {
        // package-private: 仅供同一包内的类使用
    }

    private void logAction(String action) {
        // 仅供类的内部使用
    }
}

3. 代码示例:含不同访问级别的类

我们来创建一个包含公共、私有、受保护与包级成员的类。顺便从其他类尝试访问它们,看看会发生什么。

package zoo;

public class Animal {
    public String publicName = "对所有人可见";
    protected String protectedName = "仅对子类和同一包可见";
    String packageName = "仅对同一包可见";
    private String privateName = "仅对 Animal 可见";

    public void publicMethod() {
        System.out.println("公共方法");
    }

    protected void protectedMethod() {
        System.out.println("受保护的方法");
    }

    void packageMethod() {
        System.out.println("包级方法");
    }

    private void privateMethod() {
        System.out.println("私有方法");
    }
}

现在在同一包中的另一个类里尝试访问这些成员:

package zoo;

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
        System.out.println(animal.publicName);      // OK
        System.out.println(animal.protectedName);   // OK
        System.out.println(animal.packageName);     // OK
        System.out.println(animal.privateName);  // 错误:private
        animal.publicMethod();                      // OK
        animal.protectedMethod();                   // OK
        animal.packageMethod();                     // OK
        animal.privateMethod();                  // 错误:private
    }
}

再从另一个包中尝试访问:

package other;

import zoo.Animal;

public class Test {
    public static void main(String[] args) {
        Animal animal = new Animal();
        System.out.println(animal.publicName);      // OK
        System.out.println(animal.protectedName); // 错误:protected
        System.out.println(animal.packageName);   // 错误:package-private
        System.out.println(animal.privateName);   // 错误:private
        animal.publicMethod();                      // OK
        animal.protectedMethod();                // 错误:protected
        animal.packageMethod();                  // 错误:package-private
        animal.privateMethod();                  // 错误:private
    }
}

结论:

  • public —— 到处都可访问。
  • protected —— 在包内和子类中可访问(即使子类位于其他包,通过继承仍可访问)。
  • package-private —— 仅在包内可访问。
  • private —— 仅在类内部可访问。

4. 最佳实践:如何选择访问修饰符

尽量缩小可见范围

能看到你字段或方法的代码越少越好。只对外暴露确有必要的内容。这被称为最小特权原则(principle of least privilege)。

  • 字段几乎总是应该是 private。例外仅限真正的常量(public static final),细节将在后续讲解。
  • 只有当方法属于类的对外接口时 才将其设为 public
  • 辅助方法(内部逻辑)—— private
  • 供子类使用的方法 —— protected
  • 包内的内部服务方法 —— package-private

为什么这很重要?

  • 一旦暴露实现细节,任何改动都可能破坏他人的代码。
  • 类会变得更难测试与维护。
  • 偶发错误(如对字段的错误修改)可能导致缺陷。

有时新人会想:“何必这么麻烦,让一切都 public 不就好了吗!” 但当项目变大时,你可能不得不重写半个程序,只因为有人直接修改了类的字段。

5. 使用访问修饰符时的常见错误

错误 1:把字段保留为 public 或默认的 package-private。
如果不指定修饰符,字段或方法将对包内所有类可见。这可能导致意外情况——例如有人直接修改了你的字段。

错误 2:尝试从其他类访问 private 成员。
编译器不会允许——你会得到一个错误。若你试图通过 reflection(反射)绕过它——欢迎来到充满缺陷与各种崩溃的世界。

错误 3:过度使用 public。
如果把所有东西都声明为 public,这个类就像一盒裸露的电线——谁都可能随手一拉把它弄坏。

错误 4:对只给子类用的方法没有使用 protected。
如果某个方法仅用于在子类中扩展,请将其 protected,而不是 public

错误 5:无意间成为 package-private 可见。
有时会忘记写修饰符,结果方法对整个包可见。如果你以为它是 private,这会让人措手不及。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION