访问权限

核心概念

访问权限是面向对象编程中实现封装的重要机制。通过控制类、方法和字段的可见性,我们可以隐藏实现细节,提供更安全和更清晰的接口。


一、访问权限修饰符

1.1 四种访问权限

public class AccessDemo {
    // 四种访问权限
    public int publicField;     // 公共的,任何地方可访问
    protected int protectedField;  // 同包 + 子类可访问
    int defaultField;           // 包私有(默认),同包可访问
    private int privateField;   // 仅类内部可访问
}

1.2 可访问性矩阵

修饰符 类内部 同包 子类 任何地方
private
default(包私有)
protected
public

1.3 详细示例

package com.example.access;

public class Parent {
    public int publicField = 1;
    protected int protectedField = 2;
    int defaultField = 3;
    private int privateField = 4;

    public void publicMethod() {}
    protected void protectedMethod() {}
    void defaultMethod() {}
    private void privateMethod() {}
}

// 同包子类
class Child extends Parent {
    void test() {
        publicField = 10;       // ✅ 可访问
        protectedField = 20;    // ✅ 可访问
        defaultField = 30;      // ✅ 可访问
        // privateField = 40;   // ❌ 不可访问

        publicMethod();         // ✅ 可访问
        protectedMethod();      // ✅ 可访问
        defaultMethod();        // ✅ 可访问
        // privateMethod();     // ❌ 不可访问
    }
}

// 不同包的类
public class OtherClass {
    void test() {
        Parent parent = new Parent();
        parent.publicField = 10;     // ✅ 可访问
        // parent.protectedField = 20;  // ❌ 不可访问
        // parent.defaultField = 30;    // ❌ 不可访问
        // parent.privateField = 40;    // ❌ 不可访问
    }
}

二、封装原则(Encapsulation)

2.1 封装的目的

// ❌ 错误的设计:暴露内部细节
public class User {
    public String username;
    public String password;
}

// ✅ 正确的设计:封装
public class User {
    private String username;
    private String password;

    // 提供受控的访问方法
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        // 可以添加验证逻辑
        if (username != null && !username.isEmpty()) {
            this.username = username;
        }
    }
}

2.2 封装的好处

好处 说明 示例
隐藏实现细节 内部实现可以随时改变 修改私有字段不影响外部代码
数据验证 控制数据的合法性 检查年龄、密码等
只读/只写控制 精细控制属性访问 只提供getter不提供setter
计算属性 动态计算属性值 年龄根据出生日期计算

2.3 高级封装示例

public class BankAccount {
    private double balance;
    private List<Transaction> transactions = new ArrayList<>();

    // 私有内部类
    private class Transaction {
        private double amount;
        private Date timestamp;

        // 仅在BankAccount内部使用
    }

    // 受控的存款方法
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            recordTransaction(amount);
        }
    }

    // 受控的取款方法
    public void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            recordTransaction(-amount);
        }
    }

    // 只读属性
    public double getBalance() {
        return balance;
    }

    // 私有方法,仅在类内部使用
    private void recordTransaction(double amount) {
        transactions.add(new Transaction(amount, new Date()));
    }
}

三、继承中的访问权限

3.1 继承与访问权限

public class Parent {
    private int privateField;
    protected int protectedField;

    private void privateMethod() {}
    protected void protectedMethod() {}
}

public class Child extends Parent {
    void test() {
        // protectedField = 10;  // ✅ 可以访问
        // protectedMethod();    // ✅ 可以调用

        // privateField = 20;    // ❌ 不能访问
        // privateMethod();      // ❌ 不能调用
    }
}

3.2 重写方法的访问权限

public class Parent {
    protected void method() {}
}

public class Child extends Parent {
    // ✅ 可以扩大访问权限
    @Override
    public void method() {}

    // ❌ 不能缩小访问权限
    // @Override
    // private void method() {}  // 编译错误
}

重写方法访问权限规则:

  • 可以扩大访问权限
  • 不能缩小访问权限
  • 遵循"不能降低可见性"原则

四、常见陷阱与最佳实践

4.1 常见错误

// ❌ 错误:过度暴露
public class User {
    public String password;  // 安全风险
}

// ❌ 错误:不恰当的getter/setter
public class Person {
    private int age;

    // 没有任何验证的setter
    public void setAge(int age) {
        this.age = age;  // 可以设置负数年龄
    }
}

4.2 最佳实践

// ✅ 正确的封装
public class User {
    // 1. 私有字段
    private String username;
    private String password;

    // 2. 安全的getter/setter
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        // 添加验证逻辑
        if (username != null && !username.isEmpty()) {
            this.username = username;
        }
    }

    // 3. 只读属性
    public boolean isAdmin() {
        return "admin".equals(username);
    }

    // 4. 防御性拷贝
    public List<String> getRoles() {
        return new ArrayList<>(roles);  // 返回副本
    }
}

五、高频面试题

Q1: 四种访问权限修饰符的区别?

答:

修饰符 类内部 同包 子类 任何地方
private
default
protected
public

Q2: 为什么要使用private?

答:

  • 隐藏实现细节
  • 防止外部直接修改内部状态
  • 提供更可控的访问方式
  • 实现封装的基本原则

Q3: protected和default有什么区别?

答:

  • default:仅同包可访问
  • protected:同包 + 子类可访问
  • protected更灵活,支持继承场景

Q4: 可以在子类中访问父类的private成员吗?

答:

  • 不能直接访问
  • 只能通过父类提供的public/protected方法间接访问

Q5: 为什么要遵循最小权限原则?

答:

  • 降低耦合
  • 提高安全性
  • 隐藏实现细节
  • 便于后期重构

六、最佳实践

✅ 推荐做法

// 1. 尽量使用private
public class User {
    private String username;

    // 提供受控的访问方法
    public String getUsername() {
        return username;
    }
}

// 2. 使用final防止过度继承
public final class ImmutableClass {
    private final int value;
}

// 3. 接口方法默认public
public interface MyInterface {
    void method();  // 默认public
}

❌ 避免做法

// 1. 避免public字段
public class BadDesign {
    public int value;  // 不好
}

// 2. 不要过度开放方法
public class Service {
    public void internalMethod() {  // 不应该是public
        // 内部实现细节
    }
}

相关链接


记忆口诀

私有最严格,默认同包行
保护子类用,公共天下宽
封装隐细节,getter setter来护航
最小权限牢记,安全是根本

相关笔记

访问控制修饰符 是本篇的精简对照版(站在"Java 优势/OOP 特性"视角),内容互有详略:本篇按"概念→四类范围→常见坑"系统展开,那边更像面试速记。两边可对照读,示例代码互补。


⬅️ 抽象类与接口 🏠 00-Java ➡️ 重写与重载