• xianaldai

    本期是Java开发教程系列的第五期,本期主要向各位介绍*封装*和 static 静态成员——把数据保护起来,并认识"属于类而不属于对象"的成员.若你已经熟悉 getter/setter 和 static,可以跳过本期内容.

    *每期叠甲*:

    时间仓促,水平有限,文中难免存在缺点和错误,恳请广大开发者和从业者指正.


    *一、封装:private + getter/setter*

    第 4 期学了 private——只有本类内部能访问.但光把字段设为 private 还不够,外面*完全读不到*也不行.封装的典型写法是:*字段私有,再用公开的方法来读写*```

    public class Student {

    private String name; // 字段私有

    private double score;

    public Student(String name, double score) {

    this.name = name;

    this.score = score;

    }

    // getter:读值

    public String getName() {

    return name;

    }

    public double getScore() {

    return score;

    }

    // setter:改值,可以在里面做校验

    public void setScore(double score) {

    if (score < 0) {

    System.out.println("成绩不能为负数,拒绝修改");

    return; // 直接结束方法,不修改

    }

    this.score = score;

    }

    }
    ``现在外面不能直接写 s.score = -80 了(编译报错),只能通过 setScore() 改——而 setScore()` 里拦住了负数.数据就安全了.

    *命名约定*:字段叫 score,getter 叫 getScore(),setter 叫 setScore();boolean 字段的 getter 叫 isXxx().这个约定 IDE 可以一键生成.

    *为什么说封装是好事*:

    - *能校验*:setter 里可以拦非法值,像上面的负数成绩

    - *能改内部实现*:字段将来换存储方式,只要 getter/setter 签名不变,外面代码不用动

    - *缩小影响面*:别人只依赖你公开的方法,内部怎么改都不怕


    *二、static 静态成员*

    static 修饰的成员属于*类*而不是某个对象.看例子:```

    public class Student {

    public static String school = "第一中学"; // 静态字段

    public String name; // 实例字段

    public static void welcome() { // 静态方法

    System.out.println("欢迎来到 " + school);

    }

    }
    ``- ****静态字段****:所有对象****共享同一份****.改 Student.school = "第二中学"`,所有学生的 school 一起变

    - *实例字段*:每个对象各有一份.s1.name 和 s2.name 互不影响

    - *静态方法*:用*类名直接调*,不用创建对象:```

    Student.welcome(); // 直接调,不需要 new
    ```- 静态方法里*不能直接用非静态的字段和方法*(因为没有"某个对象"可指);反过来,非静态方法可以用静态成员

    什么时候用 static:

    - *工具方法*:像 Math.abs(),做一件和任何对象都无关的事

    - *常量*:public static final 一起用,表示一个不变的全局值

    - *共享状态*:像上面的 school,所有对象要共用

    现在你回头看 System.out.println(...):System 是一个类,out 是它一个 public static 的字段——因为静态,不用创建对象,直接用 System.out 就能拿到.

    *static 与内存:静态成员不参与垃圾回收*

    讲完用法,还得说一个和内存有关的注意事项——static 字段*不会被垃圾回收*.

    回忆一下:实例字段是跟着对象走的,对象没人用了,整个对象(连同它的实例字段)会被垃圾回收(垃圾回收是 JVM 自动做的事:没人再用的对象,占的内存会被收回去,这个机制后面我们会专门学).但静态字段属于*类*,而类从*加载进 JVM 的那一刻起就一直活着*,直到程序结束.所以:

    - 静态字段的引用一直存在 → 垃圾回收器认为它引用的对象"还在被使用" → *永远不回收*

    - 实例字段的对象没人用了 → 引用断开 → 正常回收

    这不一定是坏事(常量本来就该常驻),但有一个经典坑:*把会无限增长的东西放进 static 集合*:```

    public class GlobalData {

    // 全程序共享的日志列表,每来一条就 add 一条,只增不减

    public static List<String> logs = new ArrayList<>();

    }
    ```logs 是 static,垃圾回收器永远认为它"还在用"——里面的每一条日志都回收不掉.运行一天两天看不出来,运行几个月,这个 List 越来越大,内存被悄悄吃掉,程序越来越慢.这就是最常见的*内存泄漏*之一:对象没用了却回收不掉.

    三条使用建议:

    - *static 放常量*(public static final 的数值、字符串)没问题:值小、数量固定,常驻内存也合理

    - *static 集合要当心*:要么限制大小(满了清旧的),要么别用 static 装会增长的数据

    - *static 大对象*(大数组、大缓存)动手前先问一句:它真的需要活到程序结束吗?不需要,就别用 static


    *三、小项目:受保护的成绩*

    把封装和 static 串起来,做一个带校验的成绩管理.

    文件一 Student.java:```

    public class Student {

    private String name;

    private double score;

    // 静态字段:所有学生共享

    public static String school = "第一中学";

    public Student(String name, double score) {

    this.name = name;

    this.score = score;

    }

    public String getName() {

    return name;

    }

    public double getScore() {

    return score;

    }

    // setter:带校验,成绩不能为负

    public void setScore(double score) {

    if (score < 0) {

    System.out.println(name + " 的成绩不能为负数,拒绝修改");

    return;

    }

    this.score = score;

    }

    }
    ``文件二 Main.java:``

    public class Main {

    public static void main(String\[\] args) {

    Student s1 = new Student("小明", 92);

    Student s2 = new Student("小红", 58);

    // 直接改私有字段会编译报错: s1.score = -10;

    s1.setScore(-10); // 走 setter,被校验拦下

    s1.setScore(95); // 合法,修改成功

    System.out.println(s1.getName() + ":" + s1.getScore());

    System.out.println(s2.getName() + ":" + s2.getScore());

    // 静态字段通过类名访问,所有对象共享

    System.out.println("学校:" + Student.school);

    Student.school = "第二中学"; // 改一处,全部对象一起变

    System.out.println(s1.getName() + " 的学校:" + Student.school);

    System.out.println(s2.getName() + " 的学校:" + Student.school);

    }

    }
    ``运行结果:``

    小明 的成绩不能为负数,拒绝修改

    小明:95.0

    小红:58.0

    学校:第一中学

    小明 的学校:第二中学

    小红 的学校:第二中学
    ``重点看两处:负数成绩被 setter 拦下;Student.school` 改一次,两个学生对象读到的学校一起变——静态字段就是"所有对象共享同一份".


    *朝花夕拾:System.out 背后的 static*

    从第 1 期到现在,我们写了无数遍 System.out.println(...),它到底怎么工作的?学完 static,终于能拆开:

    - System 是 java.lang 包里的一个类

    - out 是它内部的一个 public static 字段,类型是 PrintStream(一个负责输出的类)

    - 因为是 static,不需要 new System(),直接用 System.out 就能拿到那个输出对象

    - .println(...) 则是这个输出对象的方法

    一行天天写的代码,背后是"静态字段 + 对象方法"的组合——这正是本期学的概念.另外,第 3 期说过 java.lang 自动导入,System 就是 java.lang 里的类,所以从第 1 期起我们写它从来没写过 import.


    *总结*

    本期完成了"把数据管起来"的关键一步:

    - *封装*:private 字段 + getter/setter,setter 里做校验;命名约定 getXxx/setXxx/isXxx\

    - *static*:属于类不属于对象;静态方法用类名直接调,静态字段所有对象共享;静态方法里不能直接用非静态成员;*静态字段不参与垃圾回收,慎放会增长的数据*

    - 回头看 System.out,静态的活教材

    接下来我们学习*继承与多态*——让类之间可以"父子相传",开始真正发挥面向对象的威力.

    主楼
  • ryuki

    佬不睡觉的么,大半夜更教程,超人来的

    #1
  • xianaldai

    这何尝不是熬夜流coding爱好者

    #2

发表回复

登录后回复