Java开发教程2-类与对象

本期是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 类名必须和文件名一致

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

27 个赞

好学 :grin:,爱学 :laughing:,期待下一节 :laughing:

对象?哪里有对象?包分配吗?
开玩笑,感谢做这类教程

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

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

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

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

我觉得是有的.

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

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

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

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

2 个赞

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

2 个赞

可以自己new一个喵

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

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

2 个赞

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

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

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

1 个赞

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

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

1 个赞

前端来学习下,看起来和前端class 语法差不多呀

1 个赞

感觉像回到了大学的时候学java

我觉得佬在社区里面分享知识挺好的,希望佬能坚持 让我们这些小白学习

很多编程语言都有相似之处…现在你已经是强大的全栈工程师了 :shortcake:

温故而知新,慢慢来,后续更完求更springBoot:bili_026:

1 个赞