类与对象
本教程共 100 篇 · 第 30 篇 · 更新于 2026-08-05 · 约 6 分钟阅读
本节目标:搞懂”类是模板、对象是实例”的关系,学会定义类和用 new 创建对象,并说清对象在内存里长什么样、引用和对象的区别在哪。
类和对象是面向对象的地基。一句话区分:类是图纸,对象是按图纸盖出来的房子。图纸只有一份,房子能盖很多套。
图纸本身不能住人。你没法在图纸上放家具,也没法在图纸上做饭——得先盖出真房子。类和对象就是这个关系。
定义一个类
类用 class 关键字定义,花括号里写两样东西:成员变量(这类东西”有什么”)和方法(这类东西”能做什么”)。
public class Student {
String name; // 成员变量:姓名
int age; // 成员变量:年龄
void study() { // 方法:行为
System.out.println(name + " 在学习");
}
}
这段代码执行时什么也不会发生。它只是告诉编译器:以后每个学生对象都长这样,带一个姓名、一个年龄,会学习。
类名用大驼峰,首字母大写,是 Java 的通行约定。看到 Student 就知道是类,看到 student 就知道是变量。
用 new 创建对象
想要真实数据,必须 new 出一个对象(也叫实例)。
public class Main {
public static void main(String[] args) {
Student s1 = new Student(); // 造第一个学生
s1.name = "小明";
s1.age = 18;
s1.study(); // 输出:小明 在学习
Student s2 = new Student(); // 再造一个,互不影响
s2.name = "小红";
s2.study(); // 输出:小红 在学习
}
}
第一行拆开看,它其实同时干了两件事:
- 等号右边的
new Student()在堆内存里造出一个真实对象。 - 等号左边的
Student s1在栈内存里开一个变量,用来记住那个对象在哪。
访问对象里的东西统一用点号:对象名.成员变量、对象名.方法名()。
new 到底做了什么
new Student() 这一下,JVM 依次做了三件事:
- 分配内存:在堆里划出一块空间,够放 Student 的所有成员变量。
- 赋默认值:所有成员变量先被写成该类型的零值,不会是随机垃圾数据。
- 返回地址:把这块内存的地址交出来,赋给等号左边的变量。
第 2 步的默认值规则要背下来:
| 成员变量类型 | 默认值 |
|---|---|
| byte / short / int / long | 0 |
| float / double | 0.0 |
| char | \u0000(空字符,打印看不见) |
| boolean | false |
| 任何引用类型(String、数组、自定义类) | null |
所以刚 new 出来的学生,名字是 null,年龄是 0。不赋值直接打印不会报错,只是值没意义。
Note只有成员变量享受这个待遇。方法里的局部变量没有默认值,不赋值就用会被编译器当场拦下。这条区别是下一章的重点。
栈里的引用,堆里的对象
很多初学者以为”s1 就是那个学生对象”。准确说法是:s1 是一根指向对象的绳子,对象在堆里。
栈(stack) 堆(heap)
┌──────────────┐ ┌──────────────────┐
│ s1 ● ───────┼───────▶│ name = "小明" │
├──────────────┤ │ age = 18 │
│ s2 ● ───────┼──┐ └──────────────────┘
└──────────────┘ │ ┌──────────────────┐
└────▶│ name = "小红" │
│ age = 0 │
└──────────────────┘
栈里的变量很小,只装一个地址;堆里才是真正的数据。方法执行完,栈上的变量就没了;堆里的对象只要还有人指着,就一直活着,没人指了才等垃圾回收。
多个对象各玩各的
s1 和 s2 是两块独立的堆内存,改一个不影响另一个:
s1.age = 20;
System.out.println(s2.age); // 0,s2 还是默认值,纹丝不动
这正是面向对象”各自管好自己数据”的体现。造一百个学生对象,就有一百份互不干扰的姓名和年龄。
方法却不是这样。study 的代码只在方法区存一份,一百个对象共用同一段指令。区别在于每次调用时,方法内部看到的 name 是哪个对象的那一份。所以准确说法是:数据每个对象一份,代码全类共享一份。
多个引用指向同一个对象
反过来,一个对象可以被好几根绳子拽着:
Student s3 = s1; // 没有造新对象,只是复制了地址
s3.age = 21;
System.out.println(s1.age); // 21,通过 s3 改的,s1 也看得见
赋值传的是地址,不是对象副本。s1 和 s3 是同一栋房子的两把钥匙,谁进去搬家具,另一个都看得到。
判断”是不是同一个对象”用 ==,它比的就是这两个地址:
System.out.println(s1 == s3); // true,同一个对象
System.out.println(s1 == s2); // false,两个不同对象
要比”内容是不是一样”得用 equals,第 54 章讲。参数传递里的坑在第 37 章深挖。
引用是 null 时会炸
如果一根绳子没系在任何对象上,它的值就是 null。这时候点它必炸:
public class Main {
public static void main(String[] args) {
Student s = null;
System.out.println(s.name); // 运行时抛异常
}
}
运行输出:
Exception in thread "main" java.lang.NullPointerException: Cannot read field "name" because "<local1>" is null
at Main.main(Main.java:4)
读这行报错的方法:冒号后面那句是 Java 14 起提供的”详细空指针信息”,它直接点名是谁为 null、你想拿它干什么。Cannot read field "name" 说明你在读字段,because ... is null 说明持有者是空的。
<local1> 是变量在字节码里的槽位编号。加上 -g 参数重新编译,就能看到真实变量名:
javac -g Main.java
java Main
这次报错会写成 because "s" is null,定位更快。
Warning空指针是 Java 里最高频的运行时异常,没有之一。只要看到 NullPointerException,第一反应就是”哪个引用忘了 new”。声明了变量不等于创建了对象,这是两步。
一个文件里能放几个类
// 文件名必须叫 Main.java
public class Main { // public 类,名字必须和文件名一致
public static void main(String[] args) {
Student s = new Student();
s.name = "小明";
s.study();
}
}
class Student { // 非 public 类,可以和上面同一个文件
String name;
void study() {
System.out.println(name + " 在学习");
}
}
规则是:一个 .java 文件里 public 类最多一个,且类名必须和文件名一模一样;非 public 的类想放几个放几个。练习阶段把它们塞进一个文件最省事,真实项目里通常一个类一个文件。
文件名写错的话,编译直接失败:
error: class Main is public, should be declared in a file named Main.java
常见疑问
Q:类名后面那对空括号 new Student() 是什么?能省吗?
A:不能省,它是在调用构造方法。你没写构造方法时,编译器会偷偷送你一个空的无参构造。第 33 章会把这套规则讲透。
Q:对象用完要手动销毁吗?
A:不用。Java 有垃圾回收器,当一个对象再也没有任何引用指向它,就会被自动回收。把引用置成 null 可以提前断开这根绳子,但绝大多数场景不需要你操心。
Q:Student s; 只声明不 new,能用吗?
A:局部变量这么写,不赋值就使用会编译报错;赋成 null 再点字段则是运行时空指针。想安全使用,必须先 new 出真实对象。
Q:直接打印对象为什么是一串乱码似的东西?
A:打印 s1 得到类似 Student@1b6d3586 的结果,这是类名加哈希码的默认格式,来自根类 Object 的 toString 方法。想让它打印得像人话,就在自己的类里重写 toString,第 54 章会演示。
Tip把”类 = 图纸、对象 = 房子、引用 = 钥匙”这三个词的对应关系记牢,后面学继承、多态、参数传递都会轻松很多。下一章拆开对象内部,看看变量放在不同位置有什么不同。