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

Java抽象类的访问修饰符使用

2021-04-042.5k 阅读

Java抽象类访问修饰符概述

在Java编程中,抽象类是一种特殊的类,它不能被实例化,主要用于为其他类提供一个通用的框架。访问修饰符则用于控制对类、方法和变量的访问权限。理解如何在抽象类中正确使用访问修饰符,对于编写健壮、安全且可维护的Java代码至关重要。

Java中有四种访问修饰符:publicprotectedprivate以及默认(不写任何修饰符)。这些修饰符在抽象类中的应用有着独特的规则和场景。

public访问修饰符在抽象类中的应用

  1. 抽象类使用public修饰
    • 当一个抽象类被声明为public时,意味着它可以被任何其他类访问,只要这些类在同一个项目或者可以通过合适的类路径引用到该抽象类。这在开发一些通用的框架或者库时非常有用,其他开发者可以基于这个public的抽象类进行扩展和定制。
    • 代码示例
public abstract class PublicAbstractClass {
    public abstract void publicAbstractMethod();
}

class ImplementingClass extends PublicAbstractClass {
    @Override
    public void publicAbstractMethod() {
        System.out.println("Implementing public abstract method.");
    }
}
  • 在上述示例中,PublicAbstractClass是一个public的抽象类。ImplementingClass可以继承自它并实现其抽象方法,任何其他类只要在合适的作用域内都可以使用ImplementingClass,因为它继承自一个public的抽象类。
  1. 抽象类中的方法使用public修饰
    • 如果抽象类中的抽象方法被声明为public,这表明所有继承该抽象类的子类都必须以public的方式实现这个方法。这是因为public方法具有最广泛的访问权限,子类重写方法时不能降低其访问权限。
    • 代码示例
public abstract class PublicAbstractMethodClass {
    public abstract void publicAbstractMethod();
}

class SubClass extends PublicAbstractMethodClass {
    @Override
    public void publicAbstractMethod() {
        System.out.println("Sub - class implementing public abstract method.");
    }
}
  • 这里PublicAbstractMethodClass中的publicAbstractMethodpublic的抽象方法,SubClass在实现它时必须保持public修饰符,否则会导致编译错误。

protected访问修饰符在抽象类中的应用

  1. 抽象类使用protected修饰
    • 当抽象类被声明为protected时,它的访问权限相对较窄。protected的抽象类可以被同一包内的所有类以及不同包内的子类访问。这种修饰符适用于那些作为内部框架组成部分,但又希望在一定范围内可扩展的抽象类。
    • 代码示例
// 包com.example.protectedabstract
package com.example.protectedabstract;

protected abstract class ProtectedAbstractClass {
    protected abstract void protectedAbstractMethod();
}

class SamePackageClass extends ProtectedAbstractClass {
    @Override
    protected void protectedAbstractMethod() {
        System.out.println("Same package class implementing protected abstract method.");
    }
}
  • 在上述代码中,ProtectedAbstractClassprotected的抽象类,SamePackageClass在同一包内,可以继承并实现其抽象方法。

  • 如果在不同包内的子类想要访问这个protected的抽象类:

// 包com.example.otherpackage
package com.example.otherpackage;

import com.example.protectedabstract.ProtectedAbstractClass;

class DifferentPackageSubClass extends ProtectedAbstractClass {
    @Override
    protected void protectedAbstractMethod() {
        System.out.println("Different package subclass implementing protected abstract method.");
    }
}
  • DifferentPackageSubClass通过继承可以访问ProtectedAbstractClass,因为它是子类,尽管不在同一包内。
  1. 抽象类中的方法使用protected修饰
    • 抽象类中protected的抽象方法,子类必须以protected或更宽松(即public)的访问权限来实现。这是因为子类重写方法时不能降低访问权限。
    • 代码示例
abstract class ProtectedAbstractMethodHolder {
    protected abstract void protectedAbstractMethod();
}

class SubClassForProtectedMethod extends ProtectedAbstractMethodHolder {
    @Override
    protected void protectedAbstractMethod() {
        System.out.println("Sub - class implementing protected abstract method.");
    }
}

class AnotherSubClass extends ProtectedAbstractMethodHolder {
    @Override
    public void protectedAbstractMethod() {
        System.out.println("Another sub - class implementing protected abstract method with public access.");
    }
}
  • SubClassForProtectedMethodprotected方式实现,AnotherSubClasspublic方式实现,这两种方式都是符合规则的。

默认访问修饰符(包访问权限)在抽象类中的应用

  1. 抽象类使用默认访问修饰符
    • 当抽象类没有显式的访问修饰符时,它具有包访问权限。这意味着该抽象类只能被同一包内的其他类访问和继承。这种访问权限适用于一些内部使用的抽象类,不希望在包外被直接访问。
    • 代码示例
// 包com.example.defaultabstract
package com.example.defaultabstract;

abstract class DefaultAbstractClass {
    abstract void defaultAbstractMethod();
}

class SamePackageSubClass extends DefaultAbstractClass {
    @Override
    void defaultAbstractMethod() {
        System.out.println("Same package subclass implementing default abstract method.");
    }
}
  • 在上述代码中,DefaultAbstractClass具有包访问权限,SamePackageSubClass在同一包内可以继承并实现其抽象方法。如果在其他包内尝试访问或继承DefaultAbstractClass,会导致编译错误。
  1. 抽象类中的方法使用默认访问修饰符
    • 抽象类中默认访问权限的抽象方法,子类在同一包内继承时,可以使用默认访问权限或更宽松(protectedpublic)的访问权限来实现。
    • 代码示例
abstract class DefaultAbstractMethodClass {
    abstract void defaultAbstractMethod();
}

class SubClassInSamePackage extends DefaultAbstractMethodClass {
    @Override
    void defaultAbstractMethod() {
        System.out.println("Sub - class in same package implementing default abstract method.");
    }
}

class AnotherSubClassInSamePackage extends DefaultAbstractMethodClass {
    @Override
    public void defaultAbstractMethod() {
        System.out.println("Another sub - class in same package implementing default abstract method with public access.");
    }
}
  • SubClassInSamePackage使用默认访问权限实现,AnotherSubClassInSamePackage使用public访问权限实现,都在同一包内是合法的。

private访问修饰符在抽象类中的应用

  1. 抽象类使用private修饰(不常见但理论上可行)
    • 从理论上来说,抽象类可以被声明为private,但这种情况非常罕见。private的抽象类只能在其所在的类内部被访问和继承。这通常用于一些高度封装的内部类结构,在外部几乎没有任何意义,因为外部类无法直接访问或继承private的抽象类。
    • 代码示例
class OuterClass {
    private abstract class PrivateAbstractClass {
        abstract void privateAbstractMethod();
    }

    class InnerSubClass extends PrivateAbstractClass {
        @Override
        void privateAbstractMethod() {
            System.out.println("Inner sub - class implementing private abstract method.");
        }
    }
}
  • 在上述代码中,PrivateAbstractClassOuterClass内部的private抽象类,只有OuterClass内部的InnerSubClass可以继承并实现其抽象方法。
  1. 抽象类中的方法使用private修饰
    • 抽象类中private的抽象方法是一个相对特殊的概念。因为private方法不能被继承,所以在常规意义上,它不能像普通抽象方法那样被子类实现。然而,private抽象方法可以在抽象类内部被使用,例如用于内部逻辑的封装。
    • 代码示例
abstract class PrivateAbstractMethodClass {
    private abstract void privateAbstractMethod();

    public void publicMethod() {
        privateAbstractMethod();
    }
}

class SubClassForPrivateAbstractMethod extends PrivateAbstractMethodClass {
    // 这里不能直接实现privateAbstractMethod,因为它是private的
    // 但是可以调用publicMethod间接触发privateAbstractMethod的执行逻辑
    @Override
    public void publicMethod() {
        System.out.println("Before calling private abstract method logic.");
        super.publicMethod();
        System.out.println("After calling private abstract method logic.");
    }
}
  • 在上述代码中,PrivateAbstractMethodClass中的privateAbstractMethodprivate的抽象方法,虽然SubClassForPrivateAbstractMethod不能直接实现它,但可以通过调用publicMethod间接触发privateAbstractMethod的执行逻辑,前提是privateAbstractMethodpublicMethod中有具体的调用实现。

不同访问修饰符在抽象类继承中的规则

  1. 访问权限一致性原则
    • 当子类继承抽象类并实现其抽象方法时,重写的方法访问权限不能低于抽象方法的访问权限。例如,如果抽象类中的抽象方法是protected,子类可以用protectedpublic来实现,但不能用private或默认访问权限(如果子类与抽象类不在同一包内)。
    • 代码示例
abstract class ParentAbstractClass {
    protected abstract void protectedAbstractMethod();
}

class ChildClass extends ParentAbstractClass {
    // 以下实现是正确的
    @Override
    protected void protectedAbstractMethod() {
        System.out.println("Child class implementing protected abstract method.");
    }

    // 以下实现也是正确的
    @Override
    public void protectedAbstractMethod() {
        System.out.println("Child class implementing protected abstract method with public access.");
    }

    // 以下实现是错误的(编译错误)
    // private void protectedAbstractMethod() {
    //     System.out.println("This is an incorrect implementation.");
    // }
}
  1. 包访问权限与继承关系
    • 如果抽象类具有包访问权限,其子类在同一包内可以正常继承并实现其抽象方法,访问权限可以是默认、protectedpublic。但如果子类在不同包内,只有当抽象类或抽象方法的访问权限为protectedpublic时,子类才能继承并实现。
    • 代码示例
// 包com.example.packageaccess
package com.example.packageaccess;

abstract class PackageAccessAbstractClass {
    abstract void packageAccessAbstractMethod();
}

// 同一包内的子类
class SamePackageSubClass extends PackageAccessAbstractClass {
    @Override
    void packageAccessAbstractMethod() {
        System.out.println("Same package subclass implementing package access abstract method.");
    }
}

// 不同包内的情况(假设在com.example.otherpackage包内)
// package com.example.otherpackage;
// import com.example.packageaccess.PackageAccessAbstractClass;
// class DifferentPackageSubClass extends PackageAccessAbstractClass {
//     // 编译错误,因为PackageAccessAbstractClass具有包访问权限,在不同包内无法访问
//     @Override
//     void packageAccessAbstractMethod() {
//         System.out.println("This would be an incorrect implementation in a different package.");
//     }
// }
  • 如果将PackageAccessAbstractClass的访问权限改为protected
// 包com.example.packageaccess
package com.example.packageaccess;

protected abstract class ProtectedPackageAccessAbstractClass {
    protected abstract void protectedPackageAccessAbstractMethod();
}

// 不同包内的子类
package com.example.otherpackage;
import com.example.packageaccess.ProtectedPackageAccessAbstractClass;

class DifferentPackageSubClass extends ProtectedPackageAccessAbstractClass {
    @Override
    protected void protectedPackageAccessAbstractMethod() {
        System.out.println("Different package subclass implementing protected package access abstract method.");
    }
}
  • 此时DifferentPackageSubClass可以在不同包内继承并实现抽象方法。

访问修饰符对抽象类多态性的影响

  1. 多态性与访问权限
    • 抽象类作为多态性的重要组成部分,访问修饰符会影响其多态特性的表现。例如,publicprotected修饰的抽象类及其方法在不同的继承和调用场景下,能够更好地展现多态性,因为它们具有更广泛的访问范围。
    • 代码示例
public abstract class Shape {
    public abstract double calculateArea();
}

class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
}

class Rectangle extends Shape {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double calculateArea() {
        return width * height;
    }
}

class ShapeProcessor {
    public void processShape(Shape shape) {
        double area = shape.calculateArea();
        System.out.println("The area of the shape is: " + area);
    }
}
  • 在上述代码中,Shape是一个public的抽象类,CircleRectangle继承自它并实现了calculateArea方法。ShapeProcessor类通过接收Shape类型的参数来处理不同形状,体现了多态性。由于ShapecalculateArea方法都是public的,这种多态性在不同的类之间能够顺利实现。
  1. 限制访问权限对多态性的限制
    • 如果抽象类或其抽象方法的访问权限较低,例如private或包访问权限,多态性的应用范围会受到限制。以private抽象类为例,由于外部类无法继承,它很难在外部展现多态性,只能在其所在的内部类结构中有限地体现。
    • 代码示例
class Outer {
    private abstract class PrivateShape {
        abstract double calculateArea();
    }

    class InnerCircle extends PrivateShape {
        private double radius;

        public InnerCircle(double radius) {
            this.radius = radius;
        }

        @Override
        double calculateArea() {
            return Math.PI * radius * radius;
        }
    }

    class InnerRectangle extends PrivateShape {
        private double width;
        private double height;

        public InnerRectangle(double width, double height) {
            this.width = width;
            this.height = height;
        }

        @Override
        double calculateArea() {
            return width * height;
        }
    }

    public void processInnerShape(PrivateShape shape) {
        double area = shape.calculateArea();
        System.out.println("The area of the inner shape is: " + area);
    }
}
  • 在这个例子中,PrivateShapeprivate的抽象类,多态性只能在Outer类内部通过InnerCircleInnerRectangle来体现,外部类无法利用这种多态性。

访问修饰符在抽象类与接口对比中的体现

  1. 访问修饰符的差异
    • 接口中的方法默认是publicabstract的,不能使用其他访问修饰符(除了在Java 9及之后可以给接口方法添加private修饰符用于内部逻辑封装)。而抽象类中的方法可以使用各种访问修饰符,这是两者的一个重要区别。
    • 代码示例
interface MyInterface {
    void myMethod(); // 隐式public和abstract
}

abstract class MyAbstractClass {
    public abstract void publicAbstractMethod();
    protected abstract void protectedAbstractMethod();
    abstract void defaultAbstractMethod();
    private abstract void privateAbstractMethod();
}
  • 这里MyInterface中的myMethod默认是publicabstract,而MyAbstractClass中的方法可以有不同的访问修饰符。
  1. 访问权限对实现和继承的影响
    • 类实现接口时,必须以public方式实现接口中的方法,因为接口方法默认是public的。而类继承抽象类时,根据抽象类中抽象方法的访问权限,按照相应规则实现。
    • 代码示例
interface AnotherInterface {
    void anotherMethod();
}

class InterfaceImplementingClass implements AnotherInterface {
    @Override
    public void anotherMethod() {
        System.out.println("Implementing interface method.");
    }
}

abstract class ParentAbstract {
    protected abstract void protectedAbstractMethod();
}

class AbstractSubClass extends ParentAbstract {
    @Override
    protected void protectedAbstractMethod() {
        System.out.println("Implementing protected abstract method from abstract class.");
    }
}
  • InterfaceImplementingClasspublic方式实现AnotherInterface的方法,AbstractSubClass按照protected权限实现ParentAbstract的抽象方法。

实际应用场景中访问修饰符的选择

  1. 通用框架开发
    • 在开发通用框架时,通常会使用public修饰抽象类及其抽象方法。这样其他开发者可以方便地继承和扩展框架,实现自己的业务逻辑。例如,在一些开源的图形绘制框架中,可能会有一个public的抽象Shape类,包含public的抽象draw方法,开发者可以继承Shape类并实现draw方法来绘制自定义的图形。
    • 代码示例
public abstract class FrameworkShape {
    public abstract void draw();
}

class CustomTriangle extends FrameworkShape {
    @Override
    public void draw() {
        System.out.println("Drawing a custom triangle.");
    }
}
  1. 内部模块封装
    • 对于内部模块的封装,protected或默认访问权限的抽象类比较合适。如果这些抽象类只希望在同一模块(包)内被使用或扩展,可以使用默认访问权限。如果希望在不同包内的子类也能访问和扩展,则使用protected修饰符。例如,在一个大型项目的内部数据处理模块中,可能有一个protected的抽象DataProcessor类,用于定义一些通用的数据处理逻辑,不同包内的子类可以继承它并根据具体需求实现特定的数据处理方法。
    • 代码示例
// 包com.example.internalpackage
package com.example.internalpackage;

protected abstract class DataProcessor {
    protected abstract void processData();
}

// 同一包内的子类
class SamePackageDataProcessor extends DataProcessor {
    @Override
    protected void processData() {
        System.out.println("Same package data processor.");
    }
}

// 不同包内的子类
package com.example.otherpackage;
import com.example.internalpackage.DataProcessor;

class DifferentPackageDataProcessor extends DataProcessor {
    @Override
    protected void processData() {
        System.out.println("Different package data processor.");
    }
}
  1. 高度封装的内部逻辑
    • 当需要高度封装内部逻辑,不希望外部类直接访问和继承时,可以使用private修饰抽象类或抽象方法。例如,在一个加密算法的内部实现中,可能有一个private的抽象类,用于封装一些基础的加密步骤,只有内部的具体实现类可以继承和使用这些抽象方法,外部无法直接访问,从而保证了加密算法的安全性和内部逻辑的完整性。
    • 代码示例
class EncryptionModule {
    private abstract class PrivateEncryptionStep {
        abstract void performStep();
    }

    class ConcreteEncryptionStep extends PrivateEncryptionStep {
        @Override
        void performStep() {
            System.out.println("Performing a concrete encryption step.");
        }
    }
}

通过合理选择访问修饰符,能够在抽象类的设计中实现良好的封装性、扩展性和安全性,使Java代码更加健壮和易于维护。在实际编程中,需要根据具体的需求和场景,仔细权衡不同访问修饰符的使用,以达到最佳的设计效果。