Java的访问控制符对继承的影响
Java 访问控制符基础
在 Java 中,访问控制符用于控制类、变量、方法和构造函数的访问权限。这有助于实现数据封装和信息隐藏,是面向对象编程的重要特性。Java 中有四种访问控制符:public
、protected
、default
(也称为包访问权限,没有关键字)和 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();
}
}
在其他类中无法直接访问 privateVariable
和 privateMethod
:
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
访问权限传递下去。例如,假设有 GrandParent
、Parent
和 Child
三个类,Parent
继承自 GrandParent
,Child
继承自 Parent
。如果 GrandParent
中有一个 public
方法,那么 Parent
和 Child
都可以在任何地方访问这个方法,并且访问权限始终为 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
,那么在同一个包内的 Parent
和 Child
以及它们的其他同包类都可以访问该方法。在不同包中,只有 Parent
和 Child
及其子类可以访问。
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
方法的访问权限降低为 protected
或 default
或 private
,都会导致编译错误。
protected
方法的重写
子类重写父类的 protected
方法时,重写方法的访问权限可以是 protected
或 public
,但不能是 default
或 private
。因为 default
和 private
会降低访问权限。
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
方法时,如果子类与父类在同一个包内,重写方法的访问权限可以是 default
、protected
或 public
。但如果子类在不同包中,由于 default
方法的包访问限制,重写方法只能是 protected
或 public
。
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.");
}
}
访问控制符在接口和抽象类中的应用
接口中的访问控制符
接口中的成员(方法和常量)默认都是 public
和 abstract
(对于方法)或 public
、static
和 final
(对于常量)。虽然可以显式地使用 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.");
}
}
抽象类中的访问控制符
抽象类中的方法可以使用各种访问控制符。抽象方法必须使用 public
或 protected
修饰,因为抽象方法需要被子类实现,private
和 default
会限制子类的访问,导致无法实现。
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.");
}
}
子类继承抽象类时,必须实现抽象类中的 public
和 protected
抽象方法,并且实现方法的访问权限不能低于原抽象方法的访问权限。
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.");
}
}
访问控制符对代码维护和可扩展性的影响
代码维护方面
合理使用访问控制符有助于代码的维护。例如,将一些内部使用的方法或变量设置为 private
或 default
,可以防止外部类意外修改它们,从而减少潜在的错误。当需要对类的内部实现进行修改时,只要保持 public
和 protected
接口不变,就不会影响到其他依赖该类的代码。
假设一个类有一些复杂的内部计算逻辑,通过 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();
}
子类 Circle
和 Rectangle
可以继承 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 代码是必不可少的。无论是在简单的继承结构还是复杂的多层继承和接口实现场景中,访问控制符都有助于实现数据封装、信息隐藏以及合理的代码组织和扩展。