1. 概述

在 Java 语言中,字段、构造方法、普通方法以及类都可以使用访问修饰符(access modifier)来控制其可见性。本文将重点剖析 protected 这个访问修饰符的实际作用和使用场景,帮你彻底搞懂它的访问规则,避免踩坑。

2. protected 关键字的作用

private 仅限本类访问不同,protected 提供了更宽松的访问权限:✅ 同一个包内的类 或 ✅ 子类(即使跨包) 都可以访问被 protected 修饰的成员。

换句话说,protected 是一种介于包级私有(package-private)和 public 之间的折中方案,常用于设计类继承体系时暴露“受保护的内部实现”,既不让外部随意调用,又方便子类扩展。

⚠️ 注意:很多人误以为“子类都能访问 protected 成员”,但实际规则更精细,后面会重点说明。

3. 声明 protected 成员

我们先定义一个 FirstClass,包含 protected 的字段、构造方法和方法:

public class FirstClass {

    protected String name;

    protected FirstClass(String name) {
        this.name = name;
    }

    protected String getName() {
        return name;
    }
}

通过 protected,我们允许:

  • 同一个包下的类访问这些成员
  • 所有子类(无论是否同包)访问这些成员

接下来我们通过几个典型场景验证访问行为。

4. protected 成员的访问规则

4.1 同包内访问 ✅

创建一个 GenericClass,与 FirstClass 在同一包下:

public class GenericClass {

    public static void main(String[] args) {
        FirstClass first = new FirstClass("random name");
        System.out.println("FirstClass name is " + first.getName());
        first.name = "new name";
    }
}

✅ 编译通过,运行正常。因为 GenericClassFirstClass 在同一个包中,可以自由访问其 protected 成员。

4.2 跨包非子类访问 ❌

现在尝试从另一个包中的普通类访问:

public class SecondGenericClass {

    public static void main(String[] args) {
        FirstClass first = new FirstClass("random name");
        System.out.println("FirstClass name is "+ first.getName());
        first.name = "new name";
    }
}

❌ 编译失败,报错如下:

The constructor FirstClass(String) is not visible
The method getName() from the type FirstClass is not visible
The field FirstClass.name is not visible

原因很清晰:SecondGenericClass 既不在 FirstClass 的包中,也不是其子类,因此无法访问任何 protected 成员。

4.3 跨包子类访问 ✅

重点来了!创建一个跨包的子类 SecondClass

public class SecondClass extends FirstClass {
    
    public SecondClass(String name) {
        super(name);
        System.out.println("SecondClass name is " + this.getName());
        this.name = "new name";
    } 
}

✅ 编译通过。尽管 SecondClassFirstClass 不在同一个包中,但由于它是子类,因此可以访问父类的 protected 构造方法、字段和方法。

这是 protected 最典型的应用场景:支持跨包继承时的受控访问。

5. protected 内部类

除了字段和方法,protected 还可以用于修饰内部类。这是一个容易被忽略但很关键的点。

我们给 FirstClass 添加一个 protected 的静态内部类:

package com.baeldung.core.modifiers;

public class FirstClass {

    // ...

    protected static class InnerClass {

    }
}

protected static class 表示这个内部类只能被:

  • 同包中的类访问
  • 子类访问(但注意:仅当满足访问构造器的条件时)

我们来验证几种情况。

5.1 同包内实例化 ✅

修改 GenericClass

public class GenericClass {

    public static void main(String[] args) {
        // ...
        FirstClass.InnerClass innerClass = new FirstClass.InnerClass();
    }
}

✅ 成功实例化。同包无限制。

5.2 跨包非子类实例化 ❌

SecondGenericClass 中尝试:

public class SecondGenericClass {

    public static void main(String[] args) {
        // ...
        FirstClass.InnerClass innerClass = new FirstClass.InnerClass();
    }
}

❌ 编译报错:

The type FirstClass.InnerClass is not visible

符合预期,跨包且非子类,无法访问。

5.3 跨包子类实例化 ❌(意外情况!)

重点来了!在 SecondClass 中尝试实例化 InnerClass

public class SecondClass extends FirstClass {
    
    public SecondClass(String name) {
        // ...
        FirstClass.InnerClass innerClass = new FirstClass.InnerClass();
    }     
}

❌ 竟然也报错!

The constructor FirstClass.InnerClass() is not visible

为什么子类也无法访问?

这个问题非常经典,很多人在这里踩坑。原因如下:

  1. protected static class InnerClass默认构造器是 protected(隐式继承访问级别)
  2. SecondClassFirstClass 的子类,但不是 InnerClass 的子类
  3. SecondClass 声明在 FirstClass 的包外

因此,尽管 SecondClass 是外层类的子类,但它无法访问另一个包中 protected 类的 protected 构造器。

如何解决?

✅ 解决方案:显式声明一个 public 构造器:

protected static class InnerClass {
    public InnerClass() {
    }
}

这样,即使跨包,子类也能成功实例化:

FirstClass.InnerClass innerClass = new FirstClass.InnerClass(); // ✅ now works

⚠️ 小结:protected 内部类 + 跨包子类访问 = 构造器权限陷阱。建议显式定义 public 构造器以避免问题。

6. 总结

protected 是 Java 中用于控制继承和包访问的关键修饰符,核心规则如下:

场景 是否可访问
同包类
跨包非子类
跨包子类 ✅(成员)
跨包子类实例化 protected 内部类 ❌(除非构造器为 public)

关键点:

  • protected 成员可被子类跨包访问 ✅
  • protected 内部类的默认构造器也是 protected,跨包子类无法直接实例化 ❌
  • 若需子类跨包创建内部类实例,应显式提供 public 构造器 ✅

合理使用 protected,可以在封装性和可扩展性之间取得良好平衡。

示例代码已上传至 GitHub:https://github.com/baeldung/java-tutorials/tree/master/core-java-lang-syntax-2


原始标题:Java ‘protected’ Access Modifier