6.1 类与对象:模板与实例
类是图纸,对象是按图纸造出来的具体东西——这是面向对象最重要的一句话。
// ① 定义类(图纸)
public class Student {
// 成员变量(属性)—— 描述"有什么"
String name;
int age;
// 成员方法(行为)—— 描述"能做什么"
void sayHi() {
System.out.println("大家好,我是" + name + ",今年" + age + "岁");
}
}
// ② 创建对象(按图纸造东西)
class Test {
public static void main(String[] args) {
Student s = new Student(); // new:开辟空间,返回引用
s.name = "小明"; // 给成员变量赋值
s.age = 20;
s.sayHi(); // 调用方法
Student t = new Student(); // 再造一个,各自独立
t.name = "小红";
t.sayHi();
}
}
内存里发生了什么
s 里放的是对象在堆中的地址s里存的不是对象本身,而是对象在堆里的地址(引用)。Student a = s;后 a 和 s 指向同一对象,改 a.name 会影响 s.name。
易错
- 成员变量有默认值(
0 / false / null),但局部变量没有——这是第 2 章的坑,别忘了。 - 一个
.java文件里可以有多个类,但只能有一个 public 类,且文件名要与它同名。
课堂练习(3 题)
- 类和对象的关系怎么一句话说清? 答:类是对象的模板/图纸,对象是类的一个具体实例;一个类可以 new 出任意多个对象,彼此数据独立。
- 写出创建 Student 对象并调用 sayHi 的两行代码。
答:
Student s = new Student(); s.sayHi(); - 成员变量不赋值就使用,会怎样? 答:不会报错,取默认值(int→0、double→0.0、boolean→false、引用类型→null)。
6.2 构造方法:创建对象时就初始化
构造方法是 new 的时候自动调用的那段代码。
public class Student {
String name;
int age;
// 构造方法:① 与类同名 ② 不写返回值类型(连 void 都不写)
public Student(String name, int age) {
this.name = name; // this.name 指成员变量,右边的 name 是参数
this.age = age;
}
// 无参构造(重载)
public Student() {
this.name = "未命名";
this.age = 18;
}
}
Student a = new Student("小明", 20); // 调有参构造
Student b = new Student(); // 调无参构造
易错(几乎必考)
- 只要写了任意构造方法,系统就不再赠送默认无参构造。上面的例子如果只写有参构造,
new Student()会编译报错。要保留无参构造就显式写出来。 - 构造方法不能写返回值类型,写了就变成普通方法(且名字与类同名的普通方法是合法的,但会让人困惑)。
this(...)可以在构造里调用另一个构造,但必须放在第一行。
课堂练习(2 题)
- 类里已经写了
Student(String n),还能new Student()吗? 答:不能。只要定义了构造方法,默认无参构造就不再自动生成,需要自己补一个public Student() {}。 - 构造方法和普通方法在写法上最明显的两个区别? 答:① 名称必须与类名相同;② 不写返回值类型(连 void 都不能写)。
6.3 this 与 static
this 指"当前对象",static 指"属于类、不属于某个对象"。
this 的三种用法
this.成员:区分同名的成员变量和参数(最常见)。this.方法():调用本对象的其他方法(通常省略)。this(...):构造里调用另一个构造(必须第一行)。
static(静态)
public class Student {
String name; // 实例变量:每个对象一份
static int count = 0; // 静态变量:全类共享一份,所有对象共用
public Student(String name) {
this.name = name;
count++; // 每造一个对象就 +1
}
static int getCount() { // 静态方法
return count; // 只能访问静态成员
// return name; ← 错!静态方法里不能直接访问非静态成员
}
}
Student.getCount(); // 静态方法用「类名.方法」调用,不用 new
- 静态成员属于类,随类加载而存在,被所有对象共享;实例成员属于对象。
- 静态方法里不能用 this,也不能直接访问非静态成员(因为不需要对象就能调用)。
- 常见用途:计数器、工具类方法(
Math.max、Arrays.sort都是静态方法)。
易错
- 在 static 方法(比如 main)里直接调用本类的非静态成员 → 编译报错,必须先
new出对象。 - 把"所有对象共享的数据"误写成实例变量,结果每个对象都从 0 开始。
课堂练习(3 题)
static变量和实例变量在内存上的区别? 答:static 变量在类装载时分配、只有一份、被所有对象共享;实例变量每个对象各一份。- 为什么 main 方法必须是 static 的? 答:程序启动时还没有任何对象,JVM 需要直接用「类名.main()」调用,所以 main 必须是静态的。
- 静态方法里能不能用
this?为什么? 答:不能。this 代表当前对象,而静态方法可以在没有对象的情况下被调用,没有"当前对象"可言。
6.4 封装与访问修饰符
封装 = 把数据藏起来,只留受控的入口——这是面向对象三大特性之一。
public class Student {
private String name; // private:只能在本类里访问
private int age;
// getter / setter:受控的读写入口
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) {
if (age >= 0 && age <= 150) { // ← 校验!这就是封装的价值
this.age = age;
} else {
System.out.println("年龄不合法");
}
}
// 重写 toString:打印对象时自动调用
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + "}";
}
}
| 修饰符 | 同类 | 同包 | 子类(不同包) | 其他包 |
|---|---|---|---|---|
public | ✓ | ✓ | ✓ | ✓ |
protected | ✓ | ✓ | ✓ | ✗ |
| 默认(不写) | ✓ | ✓ | ✗ | ✗ |
private | ✓ | ✗ | ✗ | ✗ |
为什么要封装
- 能拦住非法数据:如上例,年龄 −5 会被 setter 拒绝;直接
s.age = -5就拦不住。 - 内部实现可改:字段改名或换存储方式,只要 getter/setter 签名不变,外部代码不用动。
易错
- 直接打印对象默认输出地址,重写
toString()才能看到内容(很多 IDE 能自动生成)。 - boolean 的 getter 通常写成
isXxx()而不是getXxx()。
课堂练习(3 题)
- private 成员怎么在别的类里读写?
答:通过 public 的 getter/setter 方法访问,不能直接
对象.字段。 - 四种访问修饰符中,范围最大和最小的分别是? 答:最大 public(到处可访问),最小 private(仅本类)。
- 直接
System.out.println(student)打印出地址,怎么办? 答:在类里重写 toString() 方法返回想要的字符串,println 会自动调用它。
6.5 综合示例:学生成绩管理(对象数组)
把类、构造、封装、toString 串起来,这是课内实验的典型题。
import java.util.Arrays;
public class Student {
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
setScore(score); // 复用 setter 里的校验
}
public String getName() { return name; }
public int getScore() { return score; }
public void setScore(int score) {
this.score = (score >= 0 && score <= 100) ? score : 0;
}
@Override
public String toString() {
return name + ":" + score + " 分";
}
public static void main(String[] args) {
// 对象数组:一格装一个对象
Student[] cls = {
new Student("小明", 85),
new Student("小红", 92),
new Student("小刚", 58)
};
for (Student s : cls) {
System.out.println(s); // 自动调 toString
}
// 找出最高分的学生
Student best = cls[0];
for (Student s : cls) {
if (s.getScore() > best.getScore()) best = s;
}
System.out.println("最高分:" + best);
}
}
这段代码体现的三件事
- 对象数组的每个元素都是引用:
Student[] cls里存的是地址,new Student(...)出来的对象在堆上。 - 构造里复用 setter:校验逻辑只写一遍。
- 重写 toString 让打印可读:
System.out.println(s)直接输出"小明:85 分"。
复制到 Java 在线运行环境 跑一遍,再加一个"求平均分"的静态方法试试。
课堂练习(2 题)
- 对象数组
Student[] cls = new Student[3];创建后,数组元素是什么?能直接用吗? 答:元素默认是 null(引用类型默认值),直接用会抛 NullPointerException;必须逐个cls[i] = new Student(...)后才能用。 - 给上面程序加一个静态方法
static double average(Student[] cls),大致怎么写? 答:遍历数组累加s.getScore(),最后返回sum * 1.0 / cls.length;主方法里用Student.average(cls)调用。