为什么需要类
假设要管理 100 个学生,每人有姓名、年龄、成绩。用数组你得开三个:
String[] names = new String[100];
int[] ages = new int[100];
double[] scores = new double[100];
问题很明显:这三个数组必须永远保持同步。删掉第 5 个学生要同时删三处,漏一处数据就全错位了。而且 names[5] 和 scores[5] 属于同一个人这件事,只存在于你的脑子里,代码里没有任何东西保证它。
类就是来解决这个问题的:把属于同一个东西的数据绑在一起。
类是图纸,对象是造出来的东西
public class Student {
String name;
int age;
double score;
}
这就是一张图纸,描述"学生这种东西有哪些属性"。图纸本身不是学生,照着它造出来的才是:
Student s1 = new Student();
s1.name = "小明";
s1.age = 18;
s1.score = 88.5;
Student s2 = new Student();
s2.name = "小红";
System.out.println(s1.name); // 小明
System.out.println(s2.name); // 小红
new Student() 就是"照图纸造一个",造出来的叫对象(object)或实例。s1 和 s2 是两个完全独立的对象,改一个不影响另一个。
有了类,100 个学生就是:
List<Student> students = new ArrayList<>();
students.add(s1);
一个列表搞定,再也不会错位。
构造方法:造的时候就填好
一行行赋值太啰嗦,而且容易漏。用构造方法:
public class Student {
String name;
int age;
// 构造方法:名字必须和类名完全一样,且没有返回类型
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
Student s = new Student("小明", 18); // 一行搞定
this.name 指"这个对象自己的 name 属性",右边的 name 是传进来的参数。两者同名时必须用 this 区分,否则 name = name 是自己赋给自己,属性永远是 null——这是个安静的、不报错的经典 bug。
注意:一旦你写了构造方法,Java 就不再免费提供无参数的那个。想两种都支持,就都写出来(这叫重载)。
方法:让对象自己会干活
类里不只能放数据,还能放行为:
public class Student {
String name;
double score;
public Student(String name, double score) {
this.name = name;
this.score = score;
}
public boolean isPassed() {
return score >= 60;
}
public String describe() {
return name + ":" + score + " 分," + (isPassed() ? "及格" : "不及格");
}
}
Student s = new Student("小明", 88);
System.out.println(s.describe()); // 小明:88.0 分,及格
方法里可以直接用本对象的属性,不用传参——因为它本来就"住在"这个对象里。
toString:让打印有意义
直接打印对象会得到 Student@1b6d3586。重写 toString 就好了:
@Override
public String toString() {
return "Student{name='" + name + "', score=" + score + "}";
}
System.out.println(s); // Student{name='小明', score=88.0}
@Override 是给编译器的提示:"我是在覆盖父类的方法"。方法名拼错时它会报错提醒你,白送的保险,一定要写。
一句话总结面向对象
把数据和操作这些数据的行为,装进同一个盒子里。 后面所有的封装、继承、多态,都是从这句话长出来的。
10 秒自测
public Student(String name) {
name = name;
}
这段有什么问题?
少了 this.。name = name 是参数赋值给自己,对象的 name 属性从头到尾是 null。编译器不会报错,只会在你运行时给你一个空指针。