MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java的访问控制符对继承的影响

2022-03-156.5k 阅读

Java 访问控制符基础

在 Java 中,访问控制符用于控制类、变量、方法和构造函数的访问权限。这有助于实现数据封装和信息隐藏,是面向对象编程的重要特性。Java 中有四种访问控制符:publicprotecteddefault(也称为包访问权限,没有关键字)和 private

public 访问控制符

public 访问控制符是最宽松的访问级别。被 public 修饰的类、变量、方法或构造函数可以从任何其他类访问,无论这些类是否在同一个包中,甚至可以跨不同的 Java 项目访问。

以下是一个 public 类和 public 方法的示例:

public class PublicClass {
    public void publicMethod() {
        System.out.println("This is a public method.");
    }
}

在其他类中可以这样调用:

public class AnotherClass {
    public static void main(String[] args) {
        PublicClass publicClass = new PublicClass();
        publicClass.publicMethod();
    }
}

protected 访问控制符

protected 访问控制符主要用于继承相关的场景。被 protected 修饰的成员(变量、方法)可以在同一个包内的任何类中访问,同时,在不同包中的子类也可以访问。

考虑以下示例:

package com.example.package1;

public class ProtectedClass {
    protected void protectedMethod() {
        System.out.println("This is a protected method.");
    }
}

在同一个包中的类可以访问这个 protected 方法:

package com.example.package1;

public class SamePackageClass {
    public static void main(String[] args) {
        ProtectedClass protectedClass = new ProtectedClass();
        protectedClass.protectedMethod();
    }
}

如果是不同包中的子类,也可以访问:

package com.example.package2;

import com.example.package1.ProtectedClass;

public class SubClass extends ProtectedClass {
    public void callProtectedMethod() {
        protectedMethod();
    }
}

default 访问控制符

default 访问控制符没有关键字,它表示包访问权限。被 default 修饰的成员(类、变量、方法)只能在同一个包内的类中访问。

例如:

package com.example.package1;

class DefaultClass {
    void defaultMethod() {
        System.out.println("This is a default method.");
    }
}

在同一个包中的类可以访问:

package com.example.package1;

public class AnotherClassInSamePackage {
    public static void main(String[] args) {
        DefaultClass defaultClass = new DefaultClass();
        defaultClass.defaultMethod();
    }
}

但在不同包中的类无法访问 DefaultClass 及其 defaultMethod

private 访问控制符

private 访问控制符是最严格的访问级别。被 private 修饰的成员(变量、方法、构造函数)只能在声明它们的类内部访问,其他任何类,即使是子类,也无法直接访问。

例如:

public class PrivateClass {
    private int privateVariable;

    private void privateMethod() {
        System.out.println("This is a private method.");
    }

    public void accessPrivateMembers() {
        privateVariable = 10;
        privateMethod();
    }
}

在其他类中无法直接访问 privateVariableprivateMethod

public class OtherClass {
    public static void main(String[] args) {
        PrivateClass privateClass = new PrivateClass();
        // 以下两行代码会报错
        // privateClass.privateVariable = 20;
        // privateClass.privateMethod();
    }
}

Java 访问控制符对继承的影响

继承中的 public 成员

当一个类继承自另一个类时,父类中的 public 成员会完全被子类继承,并且访问权限不变,仍然是 public。这意味着子类可以在任何地方访问这些 public 成员,就像在父类中定义的一样。

例如:

public class Parent {
    public void publicMethod() {
        System.out.println("This is a public method in Parent.");
    }
}

public class Child extends Parent {
    public void callPublicMethod() {
        publicMethod();
    }
}

在其他类中:

public class Main {
    public static void main(String[] args) {
        Child child = new Child();
        child.callPublicMethod();
        child.publicMethod();
    }
}

在上述代码中,Child 类继承了 Parent 类的 publicMethod,并且在 Main 类中可以直接通过 Child 类的实例访问 publicMethod

继承中的 protected 成员

父类中的 protected 成员在子类中的访问权限也会保留为 protected。这意味着在同一个包内,子类和其他类都可以访问这些 protected 成员;而在不同包中,只有子类及其子类的实例可以访问。

考虑以下代码结构:

package com.example.parentpackage;

public class ParentWithProtected {
    protected void protectedMethod() {
        System.out.println("This is a protected method in Parent.");
    }
}

在同一个包中的子类:

package com.example.parentpackage;

public class ChildInSamePackage extends ParentWithProtected {
    public void callProtectedMethod() {
        protectedMethod();
    }
}

在不同包中的子类:

package com.example.childpackage;

import com.example.parentpackage.ParentWithProtected;

public class ChildInDifferentPackage extends ParentWithProtected {
    public void callProtectedMethod() {
        protectedMethod();
    }
}

在同一个包中的其他类:

package com.example.parentpackage;

public class OtherClassInSamePackage {
    public static void main(String[] args) {
        ChildInSamePackage child = new ChildInSamePackage();
        child.callProtectedMethod();
    }
}

在不同包中的非子类无法访问 protectedMethod

package com.example.childpackage;

public class NonSubClass {
    public static void main(String[] args) {
        // 以下代码会报错
        // ChildInDifferentPackage child = new ChildInDifferentPackage();
        // child.callProtectedMethod();
    }
}

继承中的 default 成员

父类中的 default 成员(具有包访问权限)在子类中,如果子类与父类在同一个包内,子类可以正常访问这些 default 成员。但如果子类在不同包中,子类无法访问父类的 default 成员。

例如:

package com.example.parentpackage;

class ParentWithDefault {
    void defaultMethod() {
        System.out.println("This is a default method in Parent.");
    }
}

在同一个包中的子类:

package com.example.parentpackage;

public class ChildInSamePackage extends ParentWithDefault {
    public void callDefaultMethod() {
        defaultMethod();
    }
}

在不同包中的子类:

package com.example.childpackage;

import com.example.parentpackage.ParentWithDefault;

public class ChildInDifferentPackage extends ParentWithDefault {
    // 以下方法会报错,因为无法访问父类的 default 方法
    // public void callDefaultMethod() {
    //     defaultMethod();
    // }
}

继承中的 private 成员

父类中的 private 成员不会被子类继承。虽然子类从概念上继承了父类的所有成员,但 private 成员在子类中是不可见的,无法直接访问。

例如:

public class ParentWithPrivate {
    private void privateMethod() {
        System.out.println("This is a private method in Parent.");
    }

    public void callPrivateMethod() {
        privateMethod();
    }
}

public class ChildOfPrivateParent extends ParentWithPrivate {
    // 以下方法会报错,因为无法访问父类的 private 方法
    // public void callParentPrivateMethod() {
    //     privateMethod();
    // }
}

不过,子类可以通过父类中公开的方法间接访问父类的 private 成员,就像在 ParentWithPrivate 类中通过 callPrivateMethod 方法调用 privateMethod 一样。

访问控制符在多层继承中的表现

多层继承中的 public 成员

在多层继承结构中,public 成员会一直保持 public 访问权限传递下去。例如,假设有 GrandParentParentChild 三个类,Parent 继承自 GrandParentChild 继承自 Parent。如果 GrandParent 中有一个 public 方法,那么 ParentChild 都可以在任何地方访问这个方法,并且访问权限始终为 public

public class GrandParent {
    public void publicMethod() {
        System.out.println("This is a public method in GrandParent.");
    }
}

public class Parent extends GrandParent {
    // 可以重写 publicMethod,重写后仍然是 public
    @Override
    public void publicMethod() {
        System.out.println("This is a public method in Parent.");
    }
}

public class Child extends Parent {
    public void callPublicMethod() {
        publicMethod();
    }
}

在其他类中:

public class MainForMultiLevel {
    public static void main(String[] args) {
        Child child = new Child();
        child.callPublicMethod();
        child.publicMethod();
    }
}

多层继承中的 protected 成员

在多层继承中,protected 成员同样会保持 protected 访问权限传递。如果 GrandParent 中的一个方法是 protected,那么在同一个包内的 ParentChild 以及它们的其他同包类都可以访问该方法。在不同包中,只有 ParentChild 及其子类可以访问。

package com.example.multilevel;

public class GrandParentWithProtected {
    protected void protectedMethod() {
        System.out.println("This is a protected method in GrandParent.");
    }
}

package com.example.multilevel;

public class Parent extends GrandParentWithProtected {
    // 可以重写 protectedMethod,重写后仍然是 protected
    @Override
    protected void protectedMethod() {
        System.out.println("This is a protected method in Parent.");
    }
}

package com.example.multilevel;

public class Child extends Parent {
    public void callProtectedMethod() {
        protectedMethod();
    }
}

在同一个包中的其他类:

package com.example.multilevel;

public class OtherClassInSamePackage {
    public static void main(String[] args) {
        Child child = new Child();
        child.callProtectedMethod();
    }
}

在不同包中的子类:

package com.example.otherpackage;

import com.example.multilevel.Child;

public class SubChild extends Child {
    public void callProtectedMethodFromSubChild() {
        protectedMethod();
    }
}

多层继承中的 default 成员

对于 default 成员,在多层继承中,如果所有类都在同一个包内,那么 default 成员可以正常传递和访问。但如果有类在不同包中,由于 default 成员的包访问限制,在不同包的子类无法访问。

package com.example.multilevelpackage;

class GrandParentWithDefault {
    void defaultMethod() {
        System.out.println("This is a default method in GrandParent.");
    }
}

package com.example.multilevelpackage;

public class Parent extends GrandParentWithDefault {
    // 可以重写 defaultMethod,重写后仍然是 default 访问权限
    @Override
    void defaultMethod() {
        System.out.println("This is a default method in Parent.");
    }
}

package com.example.multilevelpackage;

public class Child extends Parent {
    public void callDefaultMethod() {
        defaultMethod();
    }
}

在不同包中的子类:

package com.example.otherpackage;

import com.example.multilevelpackage.Parent;

public class ChildInDifferentPackage extends Parent {
    // 以下方法会报错,因为无法访问父类的 default 方法
    // public void callDefaultMethod() {
    //     defaultMethod();
    // }
}

多层继承中的 private 成员

在多层继承中,private 成员仍然不会被继承。即使是多层继承结构,子类也无法直接访问父类及其祖先类中的 private 成员。

public class GrandParentWithPrivate {
    private void privateMethod() {
        System.out.println("This is a private method in GrandParent.");
    }

    public void callPrivateMethod() {
        privateMethod();
    }
}

public class Parent extends GrandParentWithPrivate {
    // 无法访问 GrandParent 的 privateMethod
    // public void callGrandParentPrivateMethod() {
    //     privateMethod();
    // }
}

public class Child extends Parent {
    // 同样无法访问 GrandParent 和 Parent 的 private 方法
    // public void callGrandParentAndParentPrivateMethod() {
    //     privateMethod();
    // }
}

访问控制符与方法重写

public 方法的重写

当子类重写父类的 public 方法时,子类中的重写方法必须也是 public。这是因为重写方法不能降低访问权限,否则会破坏多态性和继承的基本原则。

例如:

public class ParentForOverride {
    public void publicMethod() {
        System.out.println("This is a public method in Parent.");
    }
}

public class ChildForOverride extends ParentForOverride {
    @Override
    public void publicMethod() {
        System.out.println("This is a public method in Child.");
    }
}

如果子类试图将重写的 public 方法的访问权限降低为 protecteddefaultprivate,都会导致编译错误。

protected 方法的重写

子类重写父类的 protected 方法时,重写方法的访问权限可以是 protectedpublic,但不能是 defaultprivate。因为 defaultprivate 会降低访问权限。

package com.example.overridepackage;

public class ParentWithProtectedForOverride {
    protected void protectedMethod() {
        System.out.println("This is a protected method in Parent.");
    }
}

package com.example.overridepackage;

public class ChildWithProtectedOverride extends ParentWithProtectedForOverride {
    @Override
    protected void protectedMethod() {
        System.out.println("This is a protected method in Child.");
    }
}

package com.example.otheroverridepackage;

import com.example.overridepackage.ParentWithProtectedForOverride;

public class ChildInDiffPackageWithProtectedOverride extends ParentWithProtectedForOverride {
    @Override
    public void protectedMethod() {
        System.out.println("This is a public method in Child in different package.");
    }
}

default 方法的重写

当子类重写父类的 default 方法时,如果子类与父类在同一个包内,重写方法的访问权限可以是 defaultprotectedpublic。但如果子类在不同包中,由于 default 方法的包访问限制,重写方法只能是 protectedpublic

package com.example.defaultpackage;

class ParentWithDefaultForOverride {
    void defaultMethod() {
        System.out.println("This is a default method in Parent.");
    }
}

package com.example.defaultpackage;

public class ChildInSamePackageWithDefaultOverride extends ParentWithDefaultForOverride {
    @Override
    void defaultMethod() {
        System.out.println("This is a default method in Child in same package.");
    }
}

package com.example.otherdefaultpackage;

import com.example.defaultpackage.ParentWithDefaultForOverride;

public class ChildInDiffPackageWithDefaultOverride extends ParentWithDefaultForOverride {
    @Override
    public void defaultMethod() {
        System.out.println("This is a public method in Child in different package.");
    }
}

private 方法的重写

严格来说,private 方法不能被重写,因为 private 方法不会被继承。然而,子类中可以定义一个与父类 private 方法签名相同的方法,但这不是重写,而是一个全新的方法。

public class ParentWithPrivateForOverride {
    private void privateMethod() {
        System.out.println("This is a private method in Parent.");
    }
}

public class ChildWithPrivateOverride extends ParentWithPrivateForOverride {
    // 这不是重写,而是一个新方法
    private void privateMethod() {
        System.out.println("This is a private method in Child.");
    }
}

访问控制符在接口和抽象类中的应用

接口中的访问控制符

接口中的成员(方法和常量)默认都是 publicabstract(对于方法)或 publicstaticfinal(对于常量)。虽然可以显式地使用 public 修饰,但即使不写,它们的访问权限也是 public

例如:

public interface MyInterface {
    // 隐式 public static final
    int CONSTANT = 10;

    // 隐式 public abstract
    void abstractMethod();
}

实现接口的类必须实现接口中的所有 public 抽象方法,并且实现方法也必须是 public

public class InterfaceImplementation implements MyInterface {
    @Override
    public void abstractMethod() {
        System.out.println("Implementing the public abstract method.");
    }
}

抽象类中的访问控制符

抽象类中的方法可以使用各种访问控制符。抽象方法必须使用 publicprotected 修饰,因为抽象方法需要被子类实现,privatedefault 会限制子类的访问,导致无法实现。

public abstract class AbstractClass {
    public abstract void publicAbstractMethod();

    protected abstract void protectedAbstractMethod();

    // 以下代码会报错,因为抽象方法不能是 private
    // private abstract void privateAbstractMethod();

    // 以下代码会报错,因为抽象方法不能是 default(在不同包的子类无法实现)
    // void defaultAbstractMethod();

    public void nonAbstractPublicMethod() {
        System.out.println("This is a non - abstract public method in AbstractClass.");
    }

    protected void nonAbstractProtectedMethod() {
        System.out.println("This is a non - abstract protected method in AbstractClass.");
    }

    void nonAbstractDefaultMethod() {
        System.out.println("This is a non - abstract default method in AbstractClass.");
    }

    private void nonAbstractPrivateMethod() {
        System.out.println("This is a non - abstract private method in AbstractClass.");
    }
}

子类继承抽象类时,必须实现抽象类中的 publicprotected 抽象方法,并且实现方法的访问权限不能低于原抽象方法的访问权限。

public class AbstractClassSubclass extends AbstractClass {
    @Override
    public void publicAbstractMethod() {
        System.out.println("Implementing public abstract method.");
    }

    @Override
    protected void protectedAbstractMethod() {
        System.out.println("Implementing protected abstract method.");
    }
}

访问控制符对代码维护和可扩展性的影响

代码维护方面

合理使用访问控制符有助于代码的维护。例如,将一些内部使用的方法或变量设置为 privatedefault,可以防止外部类意外修改它们,从而减少潜在的错误。当需要对类的内部实现进行修改时,只要保持 publicprotected 接口不变,就不会影响到其他依赖该类的代码。

假设一个类有一些复杂的内部计算逻辑,通过 private 方法实现:

public class Calculator {
    private int internalCalculation(int a, int b) {
        // 复杂的内部计算逻辑
        return a + b;
    }

    public int add(int a, int b) {
        return internalCalculation(a, b);
    }
}

如果需要修改 internalCalculation 的计算逻辑,只要 add 方法的接口不变,其他使用 Calculator 类的代码就不会受到影响。

可扩展性方面

访问控制符也影响代码的可扩展性。protected 成员在继承体系中提供了一种平衡,既允许子类扩展和定制功能,又限制了外部类的随意访问。通过继承和重写 protected 方法,子类可以根据自身需求进行功能扩展,同时保持与父类的一致性。

例如,一个图形绘制的基类 Shape 有一些 protected 方法用于基本的图形绘制设置:

public abstract class Shape {
    protected int x;
    protected int y;

    protected void setPosition(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public abstract void draw();
}

子类 CircleRectangle 可以继承 Shape 类并利用 protected 方法进行扩展:

public class Circle extends Shape {
    private int radius;

    public Circle(int x, int y, int radius) {
        setPosition(x, y);
        this.radius = radius;
    }

    @Override
    public void draw() {
        System.out.println("Drawing a circle at (" + x + ", " + y + ") with radius " + radius);
    }
}

public class Rectangle extends Shape {
    private int width;
    private int height;

    public Rectangle(int x, int y, int width, int height) {
        setPosition(x, y);
        this.width = width;
        this.height = height;
    }

    @Override
    public void draw() {
        System.out.println("Drawing a rectangle at (" + x + ", " + y + ") with width " + width + " and height " + height);
    }
}

这样,通过合理使用 protected 访问控制符,使得代码在可扩展性方面表现良好。

综上所述,Java 的访问控制符在继承中起着至关重要的作用,深刻理解它们的影响对于编写高质量、可维护和可扩展的 Java 代码是必不可少的。无论是在简单的继承结构还是复杂的多层继承和接口实现场景中,访问控制符都有助于实现数据封装、信息隐藏以及合理的代码组织和扩展。