Skip to content

封装与访问修饰符

概述

封装(Encapsulation)是面向对象三大特性之一,核心思想是隐藏对象的内部实现细节,只通过公开的方法与外界交互。在 Java 中,通过访问修饰符(Access Modifier)控制类、成员变量、方法的可见范围,从而实现封装:把不想被外部直接访问的成员设为 private,对外提供受控的 getter/setter 或业务方法。

掌握封装与访问修饰符,是写出可维护、可扩展代码的基础,也是学习 继承多态 的前提(子类可见性依赖 protected 等修饰符)。

前置建议

已掌握 类与对象构造方法,能定义类、成员变量、方法并创建对象。


为什么需要封装

  • 数据安全:防止外部随意修改内部数据(如年龄设为负数),可在 setter 中做校验。
  • 可维护性:内部实现(如存储方式)变更时,只要对外方法签名不变,调用方无需修改。
  • 易用性:对外只暴露「该暴露」的操作,接口更清晰。

四种访问修饰符

Java 提供四种访问级别,从严格到宽松依次为:

修饰符同类同包子类任意位置
private
默认(不写)
protected
public
  • 同类:同一类内部。
  • 同包:同一包(package)下的其他类。
  • 子类:继承该类的子类(可不在同包)。
  • 任意位置:任何能引用到该类型的代码。

提示

「默认」即不写任何访问修饰符,也称包私有(package-private),只有同包内的类能访问。


基本语法

修饰类

  • public:该类可被任意包使用;文件名必须与 public 类名一致。
  • 默认(不写):仅同包可访问,常用于包内辅助类。

修饰成员变量与方法

java
public class Example {
    private int secret;       // 仅本类可访问
    int packagePrivate;       // 同包可访问(默认)
    protected int forSubclass; // 本类、同包、子类可访问
    public int open;          // 任意位置可访问

    public int getSecret() { return secret; }
    public void setSecret(int v) { secret = v; }
}

使用示例

示例 1:用 private + getter/setter 实现封装

不直接暴露成员变量,通过方法读写,便于在 setter 中做校验。

java
public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        setAge(age);  // 通过 setter 赋值,统一走校验逻辑
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    // setter 中校验,避免无效数据
    public void setAge(int age) {
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("年龄应在 0~150 之间");
        }
        this.age = age;
    }
}

// 使用
Person p = new Person("张三", 20);
p.setAge(25);   // 合法
// p.setAge(-1); // 抛出 IllegalArgumentException

示例 2:只读(无 setter)与不可变

若某字段不应被外部修改,可只提供 getter,不提供 setter;若整个对象创建后不再变,可设计为不可变对象

java
public class Product {
    private final String id;   // final 保证引用不变
    private String name;

    public Product(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getId() {
        return id;
    }
    // 不提供 setId,id 创建后不可改

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

示例 3:protected 在继承中的使用

子类需要访问父类成员时,可将该成员设为 protected(本类、同包、子类可见)。详见 继承

java
// 父类
public class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }
}

// 子类(可在另一包)
public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    public void bark() {
        System.out.println(name + " says: Wang!");  // 可访问 protected name
    }
}

封装实践要点

getter/setter 命名

  • getterget + 首字母大写的属性名,布尔类型也可用 is + 首字母大写的属性名
  • setterset + 首字母大写的属性名
java
private String userName;
public String getUserName() { return userName; }
public void setUserName(String userName) { this.userName = userName; }

private boolean active;
public boolean isActive() { return active; }
public void setActive(boolean active) { this.active = active; }

何时用哪种修饰符

  • 成员变量:若无特殊需求,建议 private,通过方法暴露。
  • 仅包内使用的类或成员:用默认(不写修饰符)。
  • 需要子类访问的成员:用 protected
  • 对外 API(类、方法):用 public

注意

对成员变量使用 public 会破坏封装,外部可随意修改,一般只在不关心数据安全的简单数据载体或常量(public static final)时使用。


注意事项

  • 重写(override)时:子类重写父类方法时,访问权限不能比父类更严格(例如父类 protected,子类不能改为 private),可以相同或更宽松。
  • 局部变量:方法或块内的局部变量不能加访问修饰符,它们只在当前方法/块内有效。
  • 构造方法:可以是 private(如单例)、protected(允许子类调用)或 public(常见)。

提示

优先用 private 字段 + 公开方法的方式暴露能力,在 setter 或业务方法中做校验与逻辑,避免「裸」的 public 成员变量。


相关链接

基于 VitePress 构建