🐸 青蛙编程网
首页 / Java / 面向对象 / 1. 类与对象

类与对象:从图纸到实例

这是什么:类(Class) = 给「一类东西」做的图纸;对象(Object) = 照着图纸造出来的那个具体实例。

为什么用:学完Java 基础的八个章节,你已经能用变量、循环、方法写小程序了。但要写更大的程序,得靠 Java 的主战场——面向对象:把数据和操作数据的行为打包在一起,程序才不至于乱成一锅粥。

如何用:本章先分清「图纸」和「实例」,再动手定义类、创建对象,最后搞懂「对象变量里存的其实是遥控器」。

类是图纸,对象是实例

  • 类(class):一张"图纸",规定了一类东西有哪些属性、哪些行为
  • 对象(object):用图纸造出来的一个个具体"实例"

类 Dog 规定狗有名字、年龄,会叫;你家的旺财和邻居的大黄,就是两个对象。一张图纸,造无数条狗。

类和对象的关系,一张表看清:

对比项 类 对象
是什么 图纸、模板 按图纸造出来的具体个体
有几份 代码里只写一份 new 一次造一个,想要多少造多少
举例 Dog 这段代码本身 旺财、大黄
能不能直接用 不能,只是描述 能,d1.bark() 让它干活

再往深处看一眼:执行 new Dog("旺财", 2) 时,Java 会在一块叫堆内存的区域里开辟空间,把这只狗的名字、年龄都放进去;而变量 d1 里存的并不是狗本身,是一张指向它的"地址条"(术语叫引用)。变量像遥控器,对象像电视机:一台电视可以配好几只遥控器,一只遥控器同一时刻只对一台电视。这个模型先混个脸熟,后面"对象变量存的是遥控器"一节会亲手验证它。

定义类与创建对象

public class Dog {
    // 成员变量:每只狗自己的属性
    String name;
    int age;

    // 构造方法:new 的时候自动调用,方法名必须和类名相同
    Dog(String name, int age) {
        this.name = name;   // this.name 是成员变量,name 是参数
        this.age = age;
    }

    // 方法:狗的行为
    void bark() {
        System.out.println(name + ":汪汪!我 " + age + " 岁了");
    }

    // 新增:过生日,年龄加 1
    void birthday() {
        age++;
        System.out.println(name + " 过生日啦!现在 " + age + " 岁了");
    }

    public static void main(String[] args) {
        Dog d1 = new Dog("旺财", 2);   // new 创建对象
        Dog d2 = new Dog("大黄", 3);
        d1.bark();
        d2.bark();
        d1.birthday();   // 旺财 过生日啦!现在 3 岁了
        d1.bark();       // 旺财:汪汪!我 3 岁了
    }
}

运行后输出:

旺财:汪汪!我 2 岁了
大黄:汪汪!我 3 岁了
旺财 过生日啦!现在 3 岁了
旺财:汪汪!我 3 岁了

两只狗互不打扰:d1.name 是"旺财",d2.name 是"大黄"——同一张图纸,各造各的狗。

成员变量有出厂默认值

值得较真一个细节:如果不在构造方法里赋值,成员变量会是什么值?答案是 Java 会自动发"出厂默认值",不会是乱码,也不会报错。

public class DefaultDemo {
    String name;     // 引用类型默认 null:啥也不指
    int age;         // 整数默认 0
    boolean hungry;  // 布尔默认 false

    void show() {
        System.out.println("name=" + name);
        System.out.println("age=" + age);
        System.out.println("hungry=" + hungry);
    }

    public static void main(String[] args) {
        new DefaultDemo().show();
    }
}

你会看到:

name=null
age=0
hungry=false
成员变量类型 出厂默认值
int、long 等整数 0
double、float 0.0
boolean false
char 空字符
String 及一切对象类型 null(不指向任何对象)

注意:这个待遇只有成员变量有,方法里的局部变量没有——局部变量不初始化就用,编译直接报错,下面的"翻车现场"会演示。

构造方法也能重载

一个类可以写好几个构造方法,只要参数列表不同——这叫重载(overload):同名不同参,各干各的活。

public class Dog2 {
    String name;
    int age;

    Dog2() {                      // 无参构造:造一只"白板狗"
        this.name = "无名";
        this.age = 1;
    }

    Dog2(String name, int age) {  // 全参构造:造一只信息齐全的狗
        this.name = name;
        this.age = age;
    }

    void bark() {
        System.out.println(name + ":汪汪!我 " + age + " 岁了");
    }

    public static void main(String[] args) {
        new Dog2().bark();
        new Dog2("大黄", 3).bark();
    }
}

你会看到:

无名:汪汪!我 1 岁了
大黄:汪汪!我 3 岁了

new 后面括号里传几个参数、什么类型,Java 就自动挑最匹配的那个构造方法——像奶茶店点单:说"随便来一杯"走默认配方,报具体需求就按你的来。

小心一个坑:只要你亲手写了带参构造,Java 就不再白送默认的无参构造,这时 new Dog2() 会编译报错——想两种都要,就得像上面这样两个都写。记住口令:"写了一个构造,其余自己管。"

对象变量存的是"遥控器"

回头验证开头的引用模型。先自己猜猜下面代码的输出:

public class AliasDemo {
    public static void main(String[] args) {
        Dog d1 = new Dog("旺财", 2);
        Dog d3 = d1;        // 注意:没有 new!d3 和 d1 存的是同一个地址
        d3.birthday();      // 通过 d3 过生日
        d1.bark();          // 再看 d1——它也"长大"了!
        System.out.println(d1 == d3);   // true:它俩指向同一个对象
    }
}

你会看到:

旺财 过生日啦!现在 3 岁了
旺财:汪汪!我 3 岁了
true

意外吗?通过 d3 改的状态,d1 也看得见——因为 Dog d3 = d1; 只是复制了地址条,堆里始终只有那一只狗,两个变量是同一台电视的两只遥控器。想真造第二条狗,老老实实 new 一遍。

写法 内存里发生了什么
Dog d3 = d1; 复制引用,一只狗挂两只遥控器
Dog d4 = new Dog("旺财", 2); 新开一块堆内存,真·第二只狗
d1 == d3 比的是地址 → true
d1 == d4 哪怕名字年龄全一样,地址不同 → false

== 对对象永远比地址不比内容——想要"内容一样就算相等",得用 equals 方法,这是后面学集合、字符串比较时的高频考点。

逐行解读

代码 作用
String name; int age; 成员变量:每个对象各自的属性,存在对象内部
Dog(String name, int age) 构造方法:new 时自动执行,负责赋初值;名字和类名相同,不写返回类型
this.name = name this 指"当前这个对象";左边是成员变量,右边是参数——不加 this 它俩重名分不清
void bark() 方法:对象的行为,可以直接用本对象的成员变量
void birthday() 新增方法:修改对象自身状态(年龄 +1)
new Dog("旺财", 2) new 在内存里造出一个对象,并调用构造方法
d1.bark() 用 . 让某个具体对象干活

记忆口诀

类是图纸,对象是实例,new 来造,this 指自己。

❓ 常见问题

Q1:构造方法为什么不写返回类型?

构造方法是特殊方法,名字必须和类完全一样,故意不写返回类型(连 void 都不写),因为它不"返回"任何东西,它的工作是"把新对象初始化好"。

Q2:this 必须写吗?

不一定。如果成员变量和参数不重名,可以省略 this。但写 this.name 能明确区分"成员变量"和"参数",避免歧义,是好习惯。

Q3:一个文件里能定义多个 public class 吗?

不能。一个 .java 文件里只能有一个 public class,且文件名必须和这个 public 类名一致。非 public 的类可以有多个。

Q4:成员变量和局部变量到底差在哪?

名字像、脾气不同,一张表分清楚:

对比项 成员变量 局部变量
写在哪 类里面、方法外面 方法(或代码块)里面
住哪 对象内部(堆内存) 方法自己的栈空间
默认值 有(0、false、null) 没有,不初始化编译报错
作用范围 全类通用,所有方法都能用 从定义那一行到所在大括号结束

方法里想用成员变量,直接写名字就行;一旦局部变量和成员变量重名,"离得近"的局部变量优先——所以构造方法里才需要 this. 来指明成员变量,这正是 Q2 说的好习惯。

Q5:d1.name 这种"点"出来的写法,什么时候能用?

对象在外部访问成员,都用 对象.成员。不过等下一章学了封装你会看到:正经项目里成员变量都藏成 private,外部只能通过方法访问——"点"出来的自由会收回大半,现在先享受,也要有心理准备。

⚠️ 新手翻车现场

翻车一:忘了 new,直接用类名当对象

// 错误:Dog 是图纸,不是狗本尊
Dog.bark();  // ❌ 编译报错

问题:类是图纸,不能直接调方法;必须先 new 出对象,再用对象调方法。

修正:Dog d1 = new Dog("旺财", 2); d1.bark();

翻车二:构造方法参数个数对不上

Dog d = new Dog("旺财");  // ❌ 构造方法要求 (String name, int age),少传了 age

问题:构造方法定义了两个参数,new 时必须传两个值。

修正:new Dog("旺财", 2),参数个数和类型都要对上。

翻车三:this 漏写导致赋值没生效

Dog(String name, int age) {
    name = name;   // ❌ 左右都是参数,成员变量 name 没被赋值!
    age = age;
}

问题:左右都是局部参数 name、age,成员变量 this.name、this.age 没被赋值,对象的名字和年龄都是默认值。

修正:this.name = name; this.age = age;,用 this 指明是成员变量。

翻车四:局部变量不初始化就用

void test() {
    int count;
    System.out.println(count);  // ❌ 编译报错:可能尚未初始化变量 count
}

问题:局部变量没有出厂默认值,不先赋值就使用,编译器直接拦下。错误信息形如 error: variable count might not have been initialized。

修正:先 int count = 0; 再用。对比记忆:成员变量有默认值所以不报错——但"靠默认值活着"不算好习惯,成员变量也应在构造方法里明确赋初值。

🎯 什么时候用类

  • 一组数据和一组操作天然绑定(如"狗"有名字年龄,会叫、过生日)
  • 需要创建多个拥有相同结构但数据不同的对象
  • 想把相关数据和行为封装在一起,代码更整洁

🛠 写类的几个好习惯

  • 构造方法里把成员变量全部赋上初值:别靠默认值碰运气,对象一出生就该是"完整状态"
  • 一个类干一类事:Dog 管狗的事,别把"算账""发邮件"都塞进来;类越专一,越好复用
  • 成员变量命名用名词(name、age),方法用动词(bark、birthday),读代码像读句子
  • 先画图纸再造对象:动手前先在纸上列"这类东西有什么属性、会做什么",类结构自然清晰

📝 本章小结

  • 类是图纸,对象是实例;new 按图纸造对象
  • 构造方法与类同名、无返回类型,new 时自动执行;可以重载多个,new 时按参数自动挑
  • this 指当前对象本尊,用来和参数区分
  • 成员变量存数据,方法定行为,用 . 访问对象成员
  • 每个对象数据独立,互不干扰;成员变量有出厂默认值(0 / false / null),局部变量没有
  • 对象变量存的是引用(地址条),d3 = d1 是复制遥控器不是复制狗;== 比地址
  • 记忆口诀:类是图纸,对象是实例,new 来造,this 指自己。

动手改

  1. 给 Dog 加一个 String color 成员变量,构造方法也接收它,让 bark 打印"旺财是白色的狗"
  2. 再加一个 birthday() 方法,让 age 加 1 并打印新年龄(上面示例已加,可自行修改逻辑)
  3. 创建三只狗试试——它们的名字、年龄互不干扰,因为每个对象都有自己的成员变量

下一章:封装,给数据上把"安全锁" 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。