欢迎光临
我们一直在努力

Java学习笔记09:Java封装详解(private、getter、setter、数据校验与实际应用)

前言

在前面的 Java 学习中,我们已经接触了面向对象编程的一些基础内容:

  • 类和对象

  • 构造方法

  • this 关键字

  • 对象属性初始化

学到这里,我们已经可以创建一个 Student 类,并通过对象访问其中的属性。

例如:

Student stu = new Student();

stu.name = "洋洋";
stu.age = 20;

这样的代码看起来非常简单。

但问题也很明显:

如果对象的属性可以被外部代码随意修改,那么下面这样的代码同样可能出现:

stu.age = -100;

从 Java 语法角度来看,这并不一定报错。

但是从业务逻辑来看,学生年龄显然不应该是负数。

这就引出了 Java 面向对象中的一个核心思想:

封装(Encapsulation)。

本文是《Java学习笔记》第09篇,主要学习:

  • 什么是 Java 封装

  • 为什么成员变量通常使用 private

  • getter 和 setter 方法的作用

  • 如何通过 setter 进行数据校验

  • 构造方法和封装如何配合

  • 为什么不是所有属性都必须提供 setter

  • 封装在实际 Java 后端开发中的应用


一、什么是封装?

封装是 Java 面向对象三大基本特征之一。

面向对象常见的三个核心特征包括:

  • 封装

  • 继承

  • 多态

  • 封装可以简单理解为:

    隐藏对象内部的实现细节,通过规定好的方法访问和修改对象中的数据。

    例如现实生活中的手机。

    我们使用手机拍照时,只需要点击“拍照”按钮。

    至于内部:

    • 摄像头如何工作

    • 图像如何处理

    • 数据如何保存

    普通用户并不需要直接操作这些内部结构。

    用户只需要通过手机提供的功能使用设备。

    Java 中的封装也是类似的思想。

    对象内部的数据不应该让外部代码随意修改,而应该通过合理的方法控制访问。


    二、为什么需要封装?

    先看一个没有进行封装的类。

    public class Student {

    String name;

    int age;
    }

    创建对象:

    public class Main {

    public static void main(String[] args) {

    Student stu = new Student();

    stu.name = "洋洋";
    stu.age = 20;

    System.out.println(stu.name);
    System.out.println(stu.age);
    }
    }

    运行结果:

    洋洋
    20

    目前没有问题。

    但是现在修改年龄:

    stu.age = -100;

    从程序设计角度来看,这就出现了问题。

    因为外部代码可以直接修改 Student 对象内部的数据。

    如果对象中保存的不是年龄,而是:

    • 用户密码

    • 银行余额

    • 商品库存

    • 订单状态

    • 学生成绩

    那么允许任意代码直接修改这些数据,就会带来很大的维护风险。

    因此,我们需要限制外部代码直接访问属性。

    这时候就会用到:

    private


    三、private关键字

    private 是 Java 中的访问修饰符。

    使用 private 修饰的成员,只能在当前类内部直接访问。

    例如:

    public class Student {

    private String name;

    private int age;
    }

    现在如果在 Main 类中这样写:

    Student stu = new Student();

    stu.name = "洋洋";

    IDE 会直接提示错误。

    原因是:

    name

    已经被声明为 private。

    外部类不能直接访问。


    private的基本作用

    可以简单理解为:

    private = 限制外部直接访问

    这样就可以防止外部代码随意修改对象内部的数据。

    但是问题又来了:

    既然不能直接访问属性,那么应该怎么给对象赋值?

    答案就是:

    getter 和 setter 方法。


    四、什么是setter方法?

    setter 方法主要用于:

    修改对象中的属性值。

    例如:

    public class Student {

    private String name;

    private int age;

    public void setName(String name) {

    this.name = name;
    }

    public void setAge(int age) {

    this.age = age;
    }
    }

    现在外部不能直接写:

    stu.age = 20;

    但是可以通过:

    stu.setAge(20);

    设置年龄。


    setter命名习惯

    一般情况下:

    属性:

    name

    对应:

    setName()

    属性:

    age

    对应:

    setAge()

    因此:

    set + 属性名

    通常就是 setter 方法。


    五、setter真正的价值:控制数据是否合法

    如果 setter 只是这样:

    public void setAge(int age) {

    this.age = age;
    }

    看起来似乎只是把:

    stu.age = 20;

    换成:

    stu.setAge(20);

    代码反而更多了。

    但 setter 最大的价值并不是“换一种赋值方式”。

    而是:

    可以在修改数据之前进行控制。

    例如:

    public void setAge(int age) {

    if (age >= 0 && age <= 150) {

    this.age = age;

    } else {

    System.out.println("年龄必须在0~150之间");
    }
    }

    现在:

    stu.setAge(20);

    可以正常保存。

    但是:

    stu.setAge(-100);

    会输出:

    年龄必须在0~150之间

    错误数据不会直接进入对象。

    这就是封装非常重要的作用之一:

    保护对象内部状态。


    六、什么是getter方法?

    如果属性使用 private 修饰,那么外部不仅不能修改,也不能直接读取。

    例如:

    private int age;

    外部不能这样读取:

    System.out.println(stu.age);

    如果需要获取数据,可以提供 getter 方法。

    例如:

    public int getAge() {

    return age;
    }

    调用:

    System.out.println(stu.getAge());

    即可获取年龄。


    同样:

    private String name;

    对应:

    public String getName() {

    return name;
    }


    getter和setter可以这样记

    get → 获取

    set → 设置

    所以:

    getName()

    表示获取姓名。

    setName()

    表示设置姓名。


    七、getter和setter完整写法

    完整的 Student 类:

    public class Student {

    private String name;

    private int age;

    public void setName(String name) {

    this.name = name;
    }

    public String getName() {

    return name;
    }

    public void setAge(int age) {

    if (age >= 0 && age <= 150) {

    this.age = age;

    } else {

    System.out.println("年龄必须在0~150之间");
    }
    }

    public int getAge() {

    return age;
    }
    }

    使用:

    public class Main {

    public static void main(String[] args) {

    Student stu = new Student();

    stu.setName("洋洋");

    stu.setAge(20);

    System.out.println(stu.getName());

    System.out.println(stu.getAge());
    }
    }

    运行结果:

    洋洋
    20


    八、this关键字在setter中的作用

    在 setter 中经常会看到:

    this.name = name;

    这里两个 name 的含义不同。

    例如:

    public class Student {

    private String name;

    public void setName(String name) {

    this.name = name;
    }
    }

    其中:

    this.name

    表示:

    当前对象的成员变量 name。

    右边的:

    name

    表示:

    setName方法接收到的参数。

    因此:

    this.name = name;

    完整理解就是:

    把传入的name参数保存到当前对象的name属性中。


    九、封装和构造方法如何结合?

    上一篇学习了构造方法。

    例如:

    public Student(String name, int age) {

    this.name = name;

    this.age = age;
    }

    创建对象:

    Student stu = new Student("洋洋", 20);

    但是这里存在一个问题。

    如果写:

    Student stu = new Student("洋洋", -100);

    错误的数据仍然可以进入对象。

    如果 setAge() 中已经存在年龄校验:

    public void setAge(int age) {

    if (age >= 0 && age <= 150) {

    this.age = age;

    } else {

    System.out.println("年龄必须在0~150之间");
    }
    }

    那么构造方法可以直接复用:

    public Student(String name, int age) {

    this.name = name;

    setAge(age);
    }

    这样无论:

    通过构造方法:

    new Student("洋洋", 20);

    还是通过 setter:

    stu.setAge(20);

    年龄都使用同一套校验规则。

    这样可以减少重复代码,也避免多个地方的校验规则不一致。


    十、完整可运行案例

    下面通过一个完整案例理解 Java 封装。

    Student.java

    public class Student {

    private String name;

    private int age;

    public Student() {

    }

    public Student(String name, int age) {

    this.name = name;

    setAge(age);
    }

    public void setName(String name) {

    this.name = name;
    }

    public String getName() {

    return name;
    }

    public void setAge(int age) {

    if (age >= 0 && age <= 150) {

    this.age = age;

    } else {

    System.out.println("年龄必须在0~150之间");
    }
    }

    public int getAge() {

    return age;
    }

    public void introduce() {

    System.out.println(
    "大家好,我叫" + name + ",今年" + age + "岁"
    );
    }
    }

    Main.java

    public class Main {

    public static void main(String[] args) {

    Student stu = new Student();

    stu.setName("洋洋");

    stu.setAge(20);

    System.out.println("姓名:" + stu.getName());

    System.out.println("年龄:" + stu.getAge());

    stu.introduce();
    }
    }

    运行结果:

    姓名:洋洋
    年龄:20
    大家好,我叫洋洋,今年20岁

    如果修改为:

    stu.setAge(-20);

    运行:

    年龄必须在0~150之间

    错误数据不会被直接保存。


    十一、为什么不直接使用public属性?

    例如:

    public class Student {

    public String name;

    public int age;
    }

    这样外部可以直接:

    stu.age = -100;

    对象完全无法阻止。

    如果项目规模较大:

    某个属性可能会在几十个甚至上百个位置被修改。

    一旦数据出现问题,很难定位到底是哪段代码造成的。

    而通过封装:

    private int age;

    再统一通过:

    setAge()

    修改。

    就可以把所有规则集中管理。


    十二、是不是所有private属性都必须有getter和setter?

    不是。

    这是学习 Java 封装时非常容易形成的误区。

    很多初学教程会写:

    private String name;
    private int age;

    然后自动生成:

    getName()
    setName()
    getAge()
    setAge()

    但真正的封装并不是机械地生成 getter 和 setter。

    应该根据业务需求决定:

    • 属性是否允许读取

    • 属性是否允许修改

    • 修改时需要满足什么条件


    示例:只读属性

    假设用户ID创建以后不允许修改:

    public class User {

    private int id;

    public User(int id) {

    this.id = id;
    }

    public int getId() {

    return id;
    }
    }

    这里:

    提供:

    getId()

    但是没有:

    setId()

    因此外部可以读取ID。

    但不能随意修改ID。

    这才是封装真正的价值:

    控制对象对外暴露的能力。


    十三、封装并不等于getter和setter

    这一点非常重要。

    封装真正的思想不是:

    private + getter + setter

    而是:

    隐藏内部状态,通过合理的接口控制对象行为。

    例如银行账户:

    public class BankAccount {

    private double balance;
    }

    如果简单提供:

    public void setBalance(double balance) {

    this.balance = balance;
    }

    其实仍然存在问题。

    因为外部可以:

    account.setBalance(100000000);

    对于银行余额来说,这显然不是合理的业务设计。

    更好的方式可能是:

    public void deposit(double amount) {

    if (amount > 0) {

    balance += amount;
    }
    }

    以及:

    public void withdraw(double amount) {

    if (amount > 0 && amount <= balance) {

    balance -= amount;
    }
    }

    外部不能直接“设置余额”。

    只能通过:

    存款。

    取款。

    修改余额。

    这才体现了封装真正的设计思想。


    十四、封装在Java后端开发中的应用

    Java后端开发中,封装非常常见。

    例如用户实体:

    public class User {

    private Long id;

    private String username;

    private String password;
    }

    通常不会直接把所有成员变量暴露给外部。

    因为不同数据具有不同的业务规则。

    例如:

    用户名:

    可能不能为空。

    密码:

    可能需要加密。

    用户ID:

    可能由数据库自动生成。

    账号状态:

    可能只能通过特定业务流程修改。

    因此,在实际开发中:

    对象不仅负责保存数据,还应该负责维护自己的有效状态。


    十五、封装的主要优点

    1. 保护数据

    防止外部代码直接修改对象内部状态。


    2. 增加数据校验

    例如:

    if (age >= 0 && age <= 150)

    可以阻止错误数据进入对象。


    3. 降低代码耦合

    外部代码只需要调用公开方法。

    不需要关心内部具体实现。


    4. 提高维护性

    以后修改内部逻辑时:

    只需要修改类内部代码。

    调用者可以保持不变。


    5. 控制对象行为

    根据业务需要决定:

    哪些数据可以读取。

    哪些数据可以修改。

    哪些数据完全不允许外部访问。


    十六、Java封装常见问题

    1. private属性一定安全吗?

    private 可以限制其他类直接访问成员变量。

    但它不是安全机制的全部。

    如果提供了没有限制的 setter:

    public void setAge(int age) {

    this.age = age;
    }

    外部仍然可以传入错误数据。

    所以:

    真正的数据安全还需要配合业务校验。


    2. setter一定要有吗?

    不一定。

    如果属性不允许外部修改,就不需要提供 setter。


    3. getter一定要有吗?

    也不一定。

    如果某个属性属于内部实现细节,不应该暴露给外部,也可以不提供 getter。


    4. private只能修饰属性吗?

    不是。

    private 还可以修饰方法、构造方法以及嵌套类型等成员。

    例如:

    private void checkAge() {

    }

    表示这个方法只能在当前类内部使用。


    十七、总结

    本文学习了 Java 面向对象中的封装。

    核心知识包括:

  • 什么是封装

  • private 关键字的作用

  • getter方法的作用

  • setter方法的作用

  • 如何使用setter进行数据校验

  • 构造方法如何复用setter逻辑

  • 为什么不是每个属性都需要getter和setter

  • 封装在实际Java开发中的作用

  • 最重要的一点是:

    封装并不是简单地把属性改成private,然后生成getter和setter。

    真正的封装应该是:

    隐藏对象内部状态,并通过合理的方法控制外部如何访问和修改数据。

    学习到这里,Java面向对象也开始从:

    “如何创建一个类”

    逐渐进入:

    “如何设计一个合理的类”。

    这也是理解后续继承、多态以及Java后端开发中对象设计的重要基础。

    赞(0)
    未经允许不得转载:171主机测评 » Java学习笔记09:Java封装详解(private、getter、setter、数据校验与实际应用)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址