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

Java编程中的访问控制符详解

2021-02-287.1k 阅读

Java 访问控制符概述

在 Java 编程中,访问控制符(Access Modifiers)是一种重要的机制,用于控制类、方法和变量的访问级别。通过合理使用访问控制符,可以提高代码的安全性、封装性和可维护性。Java 中有四种访问控制符:publicprotectedprivate 和默认(也称为包访问权限,即不使用任何关键字)。不同的访问控制符决定了类、方法或变量在不同的上下文中是否可访问。

public 访问控制符

  1. 含义与作用public 访问控制符表示具有最广泛的访问权限。被声明为 public 的类、方法或变量可以在任何地方被访问,无论是在同一个包内还是不同的包中。这意味着任何其他类只要能够引用到包含 public 成员的类,就可以访问这些 public 成员。
  2. 类的 public 访问权限
    • 当一个类被声明为 public 时,它可以被任何其他类访问。例如,我们创建一个 publicPublicClass
public class PublicClass {
    public String publicField;
    public void publicMethod() {
        System.out.println("This is a public method.");
    }
}
  • 在其他类中,即使不在同一个包,也可以通过创建 PublicClass 的实例来访问其 public 成员:
package com.example.otherpackage;
public class AnotherClass {
    public static void main(String[] args) {
        com.example.PublicClass publicObj = new com.example.PublicClass();
        publicObj.publicField = "Accessed from another package";
        publicObj.publicMethod();
    }
}
  1. 方法和变量的 public 访问权限public 方法和变量通常用于提供对外的接口。比如,一个工具类可能有一些 public 方法供其他类调用。以 Math 类为例,Math 类中的 public 方法 sqrt 用于计算平方根,任何类都可以调用:
public class MathExample {
    public static void main(String[] args) {
        double result = Math.sqrt(16);
        System.out.println("The square root of 16 is: " + result);
    }
}

private 访问控制符

  1. 含义与作用private 访问控制符表示访问权限最为严格,被声明为 private 的类成员(方法或变量)只能在声明它们的类内部被访问。这有助于实现数据的封装,隐藏类的内部实现细节,防止外部类直接访问和修改类的内部状态。
  2. 变量的 private 访问权限:假设我们有一个 Person 类,其中有一个 privateage 变量来表示人的年龄:
public class Person {
    private int age;
    public void setAge(int newAge) {
        if (newAge > 0 && newAge < 150) {
            age = newAge;
        } else {
            System.out.println("Invalid age value.");
        }
    }
    public int getAge() {
        return age;
    }
}
  • 在这个例子中,外部类不能直接访问 age 变量。如果在另一个类中尝试直接访问 age 变量,会导致编译错误:
public class TestPerson {
    public static void main(String[] args) {
        Person person = new Person();
        // person.age = 30; // 这行代码会导致编译错误
        person.setAge(30);
        int age = person.getAge();
        System.out.println("The person's age is: " + age);
    }
}
  1. 方法的 private 访问权限private 方法通常用于类内部的辅助操作,不希望外部类调用。例如,一个 Calculator 类可能有一个 private 方法用于执行内部的复杂计算:
public class Calculator {
    private int performComplexCalculation(int a, int b) {
        // 复杂的计算逻辑
        return a * a + b * b;
    }
    public int calculateResult(int a, int b) {
        int result = performComplexCalculation(a, b);
        return result;
    }
}
  • 在这个例子中,performComplexCalculation 方法是 private 的,只有 Calculator 类内部的其他方法(如 calculateResult)可以调用它,外部类无法直接调用。

默认访问控制符(包访问权限)

  1. 含义与作用:当一个类、方法或变量没有显式地使用任何访问控制符时,它就具有默认访问权限,也称为包访问权限。具有默认访问权限的成员可以被同一个包内的其他类访问,但不能被不同包中的类访问。这种访问权限在一定程度上实现了包内的封装,使得包内的类可以相互协作,同时对包外隐藏部分实现细节。
  2. 类的默认访问权限:假设有一个包 com.example.package1,其中有一个默认访问权限的类 DefaultClass
package com.example.package1;
class DefaultClass {
    void defaultMethod() {
        System.out.println("This is a default method.");
    }
}
  • 在同一个包内的其他类 SamePackageClass 可以访问 DefaultClass 的默认方法:
package com.example.package1;
public class SamePackageClass {
    public static void main(String[] args) {
        DefaultClass defaultObj = new DefaultClass();
        defaultObj.defaultMethod();
    }
}
  • 然而,如果在不同的包 com.example.package2 中的类 DifferentPackageClass 尝试访问 DefaultClass,会导致编译错误:
package com.example.package2;
public class DifferentPackageClass {
    public static void main(String[] args) {
        // com.example.package1.DefaultClass defaultObj = new com.example.package1.DefaultClass();
        // 这行代码会导致编译错误,因为DefaultClass具有默认访问权限,不同包无法访问
    }
}
  1. 方法和变量的默认访问权限:同样,方法和变量具有默认访问权限时,遵循相同的规则。在一个包内,类之间可以相互访问具有默认访问权限的方法和变量,而不同包则无法访问。例如,在一个图形绘制的包中,可能有一些默认访问权限的方法用于包内的图形绘制辅助操作,这些方法对包外是不可见的。

protected 访问控制符

  1. 含义与作用protected 访问控制符介于 privatepublic 之间。被声明为 protected 的成员可以被同一个包内的其他类访问,也可以被不同包中的子类访问。这一特性在实现继承关系时非常有用,它允许子类访问父类中需要继承和扩展的部分,同时对包外的非子类进行一定程度的隐藏。
  2. 不同包中子类的访问:假设有一个父类 Animalcom.example.animals 包中,其中有一个 protected 方法 makeSound
package com.example.animals;
public class Animal {
    protected void makeSound() {
        System.out.println("The animal makes a sound.");
    }
}
  • 然后在 com.example.pets 包中有一个子类 Dog 继承自 Animal
package com.example.pets;
import com.example.animals.Animal;
public class Dog extends Animal {
    @Override
    protected void makeSound() {
        System.out.println("The dog barks.");
    }
    public void performAction() {
        makeSound();
    }
}
  • Dog 类中,由于它是 Animal 的子类,即使不在同一个包,也可以访问 Animal 类中的 protected 方法 makeSound。同时,Dog 类可以重写这个 protected 方法来实现自己的行为。在 Dog 类的 performAction 方法中就调用了重写后的 makeSound 方法。
  1. 同一个包内非子类的访问:在同一个包内,即使不是子类,也可以访问 protected 成员。例如,在 com.example.animals 包中有一个 ZooKeeper 类:
package com.example.animals;
public class ZooKeeper {
    public void hearAnimalSound(Animal animal) {
        animal.makeSound();
    }
}
  • 在这个例子中,ZooKeeper 类不是 Animal 类的子类,但由于它们在同一个包内,ZooKeeper 类可以访问 Animal 类的 protected 方法 makeSound

访问控制符在类、方法和变量上的使用规则总结

  1. 类的访问控制符使用
    • 一个源文件中最多只能有一个 public 类,并且源文件的名称必须与 public 类的名称完全相同(包括大小写)。
    • 类不能被声明为 privateprotected,因为这两个访问控制符主要用于类的成员(方法和变量)。类只能是 public 或者具有默认访问权限。
  2. 方法和变量的访问控制符使用
    • 方法和变量可以使用 publicprivateprotected 或默认访问控制符。
    • 通常,将类的内部状态(变量)声明为 private,通过 public 的访问器(getter)和修改器(setter)方法来访问和修改这些状态,以实现数据封装。
    • 对于一些辅助方法,不希望外部类调用的,可以声明为 private
    • 如果希望一个方法或变量在包内可见,并且在不同包的子类中也可见,可以声明为 protected

访问控制符与继承的关系

  1. 子类对父类成员的访问
    • 子类可以继承父类的 publicprotected 成员,无论子类与父类是否在同一个包内。
    • 子类不能继承父类的 private 成员。例如,有一个父类 Parent
public class Parent {
    private int privateField;
    protected int protectedField;
    public int publicField;
    private void privateMethod() {
        System.out.println("This is a private method in Parent.");
    }
    protected void protectedMethod() {
        System.out.println("This is a protected method in Parent.");
    }
    public void publicMethod() {
        System.out.println("This is a public method in Parent.");
    }
}
  • 子类 Child
public class Child extends Parent {
    public void accessParentMembers() {
        // privateField = 10; // 编译错误,不能访问父类的private成员
        protectedField = 20;
        publicField = 30;
        // privateMethod(); // 编译错误,不能访问父类的private方法
        protectedMethod();
        publicMethod();
    }
}
  1. 子类重写方法的访问控制符规则
    • 子类重写父类的方法时,重写方法的访问控制符不能比父类中被重写方法的访问控制符更严格。例如,如果父类中的方法是 protected,子类重写该方法时可以是 protectedpublic,但不能是 private 或默认访问权限。
    • 假设父类 Shape 有一个 protected 方法 draw
public class Shape {
    protected void draw() {
        System.out.println("Drawing a shape.");
    }
}
  • 子类 Circle 重写 draw 方法:
public class Circle extends Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a circle.");
    }
}
  • 在这个例子中,Circle 类重写 draw 方法时使用了 public 访问控制符,这是允许的,因为 publicprotected 的访问权限更宽松。

访问控制符的最佳实践

  1. 数据封装:将类的成员变量声明为 private,通过 publicgettersetter 方法来访问和修改这些变量。这样可以控制对数据的访问,确保数据的一致性和安全性。例如,在一个 BankAccount 类中,账户余额 balance 应该是 private 的:
public class BankAccount {
    private double balance;
    public double getBalance() {
        return balance;
    }
    public void setBalance(double newBalance) {
        if (newBalance >= 0) {
            balance = newBalance;
        } else {
            System.out.println("Invalid balance value.");
        }
    }
}
  1. 隐藏实现细节:将类内部的辅助方法声明为 private。这样可以避免外部类意外调用这些方法,同时也使得类的接口更加清晰。比如,在一个加密工具类中,可能有一些 private 方法用于执行内部的加密算法步骤。
  2. 合理使用 protected:在设计继承体系时,当需要子类访问父类的某些成员,但又不想让包外的非子类访问时,使用 protected。例如,在一个图形绘制的继承体系中,父类 GraphicObject 可能有一些 protected 方法用于设置图形的基本属性,子类 RectangleCircle 可以继承并使用这些方法来设置自身的属性。
  3. 使用默认访问权限实现包内协作:对于一些只在包内使用的类、方法和变量,可以使用默认访问权限。这样可以实现包内的协作,同时对包外隐藏这些实现细节。例如,在一个数据库访问包中,可能有一些默认访问权限的工具类用于包内的数据库连接管理等操作。

访问控制符与接口和抽象类

  1. 接口中的访问控制符
    • 接口中的成员变量默认是 public static final 的,不能修改其访问控制符。例如:
public interface MyInterface {
    int DEFAULT_VALUE = 10; // 实际上是public static final int DEFAULT_VALUE = 10;
}
  • 接口中的方法默认是 public abstract 的,同样不能修改其访问控制符。例如:
public interface MyInterface {
    void doSomething(); // 实际上是public abstract void doSomething();
}
  • 当一个类实现接口时,实现的方法必须是 public 的,因为接口方法默认是 public,子类重写时不能比父类(接口)的访问控制符更严格。例如:
public class MyClass implements MyInterface {
    @Override
    public void doSomething() {
        System.out.println("Implementing doSomething method.");
    }
}
  1. 抽象类中的访问控制符
    • 抽象类中的成员变量和方法可以使用各种访问控制符,与普通类类似。抽象方法可以是 protected,这样只有子类(在同一个包或不同包)可以实现它。例如:
public abstract class AbstractShape {
    protected abstract void draw();
}
  • 子类继承抽象类并实现抽象方法时,访问控制符要遵循重写规则。如果抽象方法是 protected,子类可以用 protectedpublic 来实现;如果抽象方法是 public,子类只能用 public 实现。例如:
public class Triangle extends AbstractShape {
    @Override
    public void draw() {
        System.out.println("Drawing a triangle.");
    }
}

通过深入理解和合理使用 Java 中的访问控制符,可以构建出更加健壮、安全和易于维护的程序。不同的访问控制符在不同的场景下发挥着重要作用,它们是 Java 面向对象编程中实现封装、继承和多态等特性的重要基础。无论是小型项目还是大型企业级应用,正确运用访问控制符都是编写高质量代码的关键。