• xianaldai

    本期是Java开发教程系列的第二期,本期主要向各位介绍 Java 面向对象的基础知识——*类与对象*.若你已经了解类和对象的基本概念,可以跳过本期内容.

    *每期叠甲*:

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


    *一、为什么需要类和对象*

    第 1 期我们学了变量、方法、数组和集合.但如果要描述"一堆学生",每个学生的姓名、成绩都单独定义变量,代码很快会变成灾难:```

    String name1 = "小明";

    double score1 = 92;

    String name2 = "小红";

    double score2 = 88;

    // 如果有一百个学生呢?这样写根本没法维护,也就是俗称的史山代码.
    ```我们需要一种把"相关的数据 + 相关的操作"打包在一起的方式.Java 用*类*(class)和*对象*(object)来做这件事:

    - *类* = 一张*图纸*,规定了这一类东西"长什么样、能干什么"

    - *对象* = 按图纸*造出来的具体东西*;同一张图纸,可以造出无数个对象

    打个比方:Student 类是"学生"的模板,而"小明""小红"是按这个模板造出来的具体对象——每个对象有自己的姓名、成绩,但结构(有哪些字段、哪些方法)都一样.


    *二、定义类:字段与方法*

    *2.1 一个最简单的类*

    用代码定义一个"学生"类:```

    public class Student {

    // 字段(也叫属性):描述学生"是什么样"

    String name; // 姓名

    double score; // 成绩

    // 方法:描述学生"能干什么"

    void sayHello() {

    System.out.println("你好,我是 " + name);

    }

    }
    ``- public class Student 声明一个公开的类,类名是 Student`

    - 类里面的 name、score 叫*字段*,保存这个对象的"状态"

    - sayHello() 是*方法*,定义这个对象的"行为"

    - 注意:这些代码目前是*定义*,什么都没运行

    *2.2 使用对象:new*

    题外话:这也是有时候说找不到对象就new一个的原因.

    要在程序里真正用上,得先*造一个对象*出来,用 new:```

    public class Main {

    public static void main(String[] args) {

    Student s1 = new Student(); // 用 new 造出一个学生对象

    s1.name = "小明"; // 给字段赋值

    s1.score = 92;

    s1.sayHello(); // 调用方法

    System.out.println(s1.score);

    }

    }
    ``运行结果:``

    你好,我是 小明

    92.0
    ``- new Student() 就是****创建对象****,s1` 是一个指向这个对象的引用

    - 通过 s1.字段名 读/写字段,通过 s1.方法名() 调方法

    - 一个类可以造出任意多个互不干扰的对象:再写 Student s2 = new Student();,s2 是另一个学生


    *三、构造方法*

    *3.1 为什么需要构造方法*

    上面的写法要 s1.name = "小明"; s1.score = 92; 两行才设置好初始值,麻烦.

    用*构造方法*(constructor)可以在*创建对象的同时*把初始值填好:```

    public class Student {

    String name;

    double score;

    // 构造方法:方法名和类名一模一样,而且不写返回类型

    Student(String name, double score) {

    this.name = name;

    this.score = score;

    }

    }
    ``使用方式立刻清爽了:``

    Student s1 = new Student("小明", 92); // 创建的同时就初始化好了

    Student s2 = new Student("小红", 88);
    ```构造方法的三条规则:

    - 方法名*必须和类名一样*(包括大小写)

    - *不写返回类型*(连 void 都不写)

    - 通过 new 调用,不需要也不能手动调用

    *3.2 无参构造与有参构造*

    - 如果你*一个构造方法都没写*,编译器会免费送一个*无参构造*,它什么都不干,字段保持默认值(数字是 0、引用是 null)

    - 一旦你*自己写了构造方法*,免费的无参构造就*消失*了.此时再写 new Student() 会编译报错——除非你显式再补一个无参构造:```

    public class Student {

    String name;

    double score;

    // 无参构造

    Student() {

    }

    // 有参构造

    Student(String name, double score) {

    this.name = name;

    this.score = score;

    }

    }
    ``这样 new Student() 和 new Student("小明", 92)` 都能用.两个构造方法名字相同、参数不同,这正是第 1 期讲过的*方法重载*的典型应用.


    *四、this 关键字*

    this 只有一个含义:*“当前这个对象”*.你可以把它想成对象的名片——谁在跑这段代码,this 就是谁.

    *4.1 它在构造方法里做什么*

    再看刚才的构造方法:```

    Student(String name, double score) {

    this.name = name;

    this.score = score;

    }
    ``执行 new Student("小明", 92) 时,this 就是那个新造出来的"小明"对象.这行代码里出现了****两个**** name`,它们不是一回事:

    - 右边的 name:是*参数*.参数就在最近的括号里,所以不带 this 的 name 默认指它

    - 左边的 this.name:是*字段*.前面加了 this,明确说"这个对象的字段"

    所以 this.name = name; 的意思就是:*把传进来的参数 name,存进当前对象的 name 字段里*.

    这里藏着一条新手最容易懵的规则: *就近原则* :不带 this 的 name,先找参数,找到就指参数,参数里没有才去找字段.构造方法里参数和字段恰好同名,所以必须靠 this 才能指到字段.

    *4.2 为什么参数要和字段同名*

    其实换成别的名字也能写:```

    Student(String n, double s) {

    this.name = n;

    this.score = s;

    }
    ``参数叫 n/s 当然可以,但这样看不出"这个参数是给哪个字段用的".更常见的习惯是****参数和字段同名****,再用 this 区分——一眼就知道 name 要存进 name,score 要存进 score`.所以这不是必须的语法,而是一种让代码更好读的写法.

    *4.3 新手速记*

    this 不只出现在构造方法里,但新手阶段只需要记住构造方法里这一个用法——参数和字段同名时,this.字段 指字段.普通方法里没有同名参数,this 通常不用写,现在先别管它.


    *五、一个文件一个公开类*

    这里有个新手常搞混的点,先划重点:*被限制的是"public 类"的个数,不是"public 成员"的个数*.

    - 一个 .java 文件里*只能有一个 public 类*,而且这个类的名字*必须和文件名一模一样*(大小写也要一致)

    - 但*一个类内部可以有任意多个 public 成员*——多个 public 字段、多个 public 方法都没问题:```

    public class Student { // 文件里唯一的 public 类,必须和文件名 Student.java 一致

    public String name; // public 字段,可以有多个

    public double score;

    public void sayHello() { // public 方法,也可以有多个

    System.out.println("你好,我是 " + name);

    }

    }
    ``- 同一个文件里还能放****不加 public 的辅助类****,但不能再放第二个 public 类:``

    public class Main {

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

    Helper h = new Helper();

    h.go();

    }

    }

    // 同一个文件里可以放非 public 的类,但不能再加一个 public 的

    class Helper {

    void go() {

    System.out.println("我是辅助类");

    }

    }
    ```如果文件名和 public 类名不一致,编译器会直接报错.

    为什么这么规定?为了让编译器能快速找到类:它要找 Student,直接去 Student.java 找就行,不用把整个项目的文件都扫一遍.这个习惯要记牢,后面写工程都是这个规则.


    *六、三元运算符*

    接下来介绍一个第 1 期没讲过的小语法——*三元运算符* 条件 ? 值A : 值B:```

    String result = score >= 60 ? "及格" : "不及格";
    ``意思是:条件成立(为 true)就取 ? 后面的值,不成立就取 : 后面的值.上面这行等价于:``

    String result;

    if (score >= 60) {

    result = "及格";

    } else {

    result = "不及格";

    }
    ```看到区别了吗?三元运算符就是把最简单的 if/else 压缩成一行,适合"根据一个条件选一个值"的场合.分支一多就不要用它,可读性会变差.接下来的小项目你就可以试一试三元运算符.


    *七、小项目:学生成绩统计*

    把本期内容串成一个能运行的小程序,共两个文件.

    文件一 Student.java:```

    public class Student {

    String name;

    double score;

    // 构造方法:创建对象时初始化

    Student(String name, double score) {

    this.name = name;

    this.score = score;

    }

    // 判断是否及格

    boolean passed() {

    return score >= 60;

    }

    // 格式化打印自己

    void printInfo() {

    System.out.println(name + " 成绩 " + score

    + (passed() ? " 及格" : " 不及格"));

    }

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

    public class Main {

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

    // 创建三个学生对象

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

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

    Student c = new Student("小刚", 76);

    // 把对象装进数组,逐个打印

    Student\[\] students = {a, b, c};

    for (Student s : students) {

    s.printInfo();

    }

    // 计算平均分

    double sum = 0;

    for (Student s : students) {

    sum += s.score;

    }

    System.out.println("平均分:" + sum / students.length);

    // 找出最高分

    Student best = students\[0\];

    for (Student s : students) {

    if (s.score > best.score) {

    best = s;

    }

    }

    System.out.println("最高分:" + best.name);

    }

    }
    ``运行结果:``

    小明 成绩 92.0 及格

    小红 成绩 58.0 不及格

    小刚 成绩 76.0 及格

    平均分:75.33333333333333

    最高分:小明
    ``这个小程序用到了第 1 期的数组和增强 for,加上本期的类和对象.注意看 students 数组里装的****不是数据,而是对象的引用****——数组 students[0]、students[1]、students[2] 分别指向三个 Student` 对象.


    *朝花夕拾:现在能看懂第 1 期的 File 了*

    第 1 期我们写过这样一行:```

    File myFile = new File(new xxxxxx);
    ```当时说它"有问题",但没说清问题在哪.学完本期,你能看懂一大半了:

    - new 是*创建对象*,new File(...) 就是创建一个 File 对象

    - new xxxxxx 之所以写不出来,是因为 File 的构造方法接收的是一个*路径字符串*,不是另一个对象.正确写法是:```

    File myFile = new File("D:\\mc\\mod\\config.txt");
    ``其实 File 本身就是一个类,myFile 就是一个 File` 对象——对象,正是本期学的核心概念.


    *总结*

    本期我们迈出了面向对象的第一步:

    - *类与对象*:类是图纸,对象是按图纸造出来的实体;一个类能造出无数个对象

    - *字段与方法*:字段描述对象"是什么样",方法描述对象"能干什么"

    - *构造方法*:创建对象时初始化;方法名和类名相同、不写返回类型;一旦写了有参构造,无参构造就没了

    - *this*:区分字段和同名参数

    - *一个文件一个公开类*:public 类名必须和文件名一致

    掌握这些,就能用"类"把相关的数据和操作组织起来了.下一期我们将学习*分包、注释与访问控制*——讲清楚类与类之间怎么组织、谁能访问谁.

    主楼
  • wintsa

    此帖子已被社区举报,现已被临时隐藏。

    #1
  • 晚上要做白日梦

    好学 ^-^,爱学 ^-^,期待下一节 ^-^

    #2
  • 冷月映秋风

    对象?哪里有对象?包分配吗?

    开玩笑,感谢做这类教程

    #3
  • 此门不通

    我很难想象这种发言会出现在L站里 ^-^

    #4
  • 当且仅当

    我去,梦回十年前,好有感觉,我的青春回来了

    #5
  • tero

    今夕是何年,竟然还能在这看到这么熟悉又陌生的东西

    #6
  • xianaldai

    佬友提了一个很有意思的问题,我觉得您的问题可以换成另一种说法:大家都在用模型生成大段代码的时代,传统的编程语言教学还有意义吗?

    我觉得是有的.

    既然大家都在用模型来生成代码,没有谁比谁出彩,因而大家都只是在所谓的平均水平徘徊.

    这个时候,如果你能发现模型生成代码存在的某些小错误,甚至是某些安全功能:限流/熔断/数据清洗等等做的不到位的地方,有安全隐患的地方,这就是你的能力了

    换一个方面,模型生成代码不总是对的,人类永远是模型代码最后的面壁人.ai写的代码含有安全漏洞的概率,统计学上比人类从业者要高得多.

    不积跬步,无以至千里,我认为打好基础是迈向ai新时代的必要一步

    #7
  • 纸雨

    死去的回忆又出现了,我不想想起来呀,我有gpt了

    #8
  • xianaldai

    可以自己new一个喵

    不过不建议new很多因为占用内存捏

    #9
  • 752

    老师老师能不能讲讲八股文这些对于我现在来说太遥远了 ^-^

    #10
  • 纸雨

    支持楼主哦,有人会需要的,谁不是看这玩意过来的

    #11
  • xianaldai

    你指的是各个框架还有23种设计模式吗?估计比较靠后了,先得教完全部基础 ^-^

    #12
  • 51烦

    依旧是小明,但有种时代变了的感觉 ^-^

    #13
  • xianaldai

    本来想写张三的但是感觉不太好,最后还是用数学书上的命名吧

    #14
  • taku

    分配对象需要空间的,如果你没有大house, 分配的对象也是会被回收的

    #15

发表回复

登录后回复