你好!让我们谈谈Java中的抽象类。
为什么类被称为“抽象”?
您可能还记得抽象是什么——我们之前讨论过 :) 如果您忘记了,不用担心。请记住, OOP 原则说,在设计类和创建对象时,您应该只表示实体的主要属性并丢弃次要属性。例如,如果我们正在设计一个SchoolTeacher
班级,身高可能不是教师的必要属性。事实上,这个特征对于老师来说并不重要。但是如果我们要创建一个BasketballPlayer
类,那么高度将是最重要的特征之一。好吧,一个抽象类是一组未来类的最抽象、“粗糙的工件”。工件不能直接使用——太“粗糙”了。但它定义了未来类(抽象类的后代)将具有的某些特征状态和行为。
Java中抽象类的例子
考虑一个简单的汽车示例:
public abstract class Car {
private String model;
private String color;
private int maxSpeed;
public abstract void gas();
public abstract void brake();
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getMaxSpeed() {
return maxSpeed;
}
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
}
这就是最简单的抽象类的样子。如您所见,没什么特别的:) 为什么我们需要这个?首先,它提供了我们所需实体的最抽象描述——汽车。abstract关键字在这里意味着什么。在现实世界中,没有“只是一辆车”这样的东西。有卡车、赛车、轿车、轿跑车和 SUV。我们的抽象类只是一个“蓝图”,我们稍后将使用它来创建特定的汽车类。
public class Sedan extends Car {
@Override
public void gas() {
System.out.println("The sedan is accelerating!");
}
@Override
public void brake() {
System.out.println("The sedan is slowing down!");
}
}
在许多方面,这与我们在继承课程中谈到的内容相似。只有在那种情况下,我们才有一个Car
方法不是抽象的类。但是这样的解决方案有几个缺点,这些缺点在抽象类中是固定的。首先,不能创建抽象类的实例:
public class Main {
public static void main(String[] args) {
Car car = new Car(); // Error! The Car class is abstract!
}
}
Java 的创建者是故意制作这个“功能”的。再次记住:抽象类只是未来“常规”类的蓝图。你不需要蓝图的副本,对吧?同样,不需要创建抽象类的实例 :) 如果类Car
不是抽象的,那么我们可以轻松地创建它的实例:
public class Car {
private String model;
private String color;
private int maxSpeed;
public void go() {
// ...some logic
}
public void brake() {
// ...some logic
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car(); // This is okay. The car is created.
}
}
目前,我们的程序中有某种无法理解的汽车。它不是卡车,不是赛车,也不是轿车,但并不清楚它是什么。是现实中不存在的“只是一辆车”。同样的例子也可以用在动物身上。想象一下,如果您的程序有Animal
对象(“只是动物”)。目前尚不清楚它是什么种类,属于哪个家族,或者具有什么特征。在节目中看到一个会很奇怪。自然界中没有“只是动物”。只有狗、猫、狐狸、鼹鼠等。抽象类将我们从“只是对象”中拯救出来。它们为我们提供了基准状态和行为。例如,所有汽车都必须有型号、颜色和最高时速,而且他们还必须能够使用油门和刹车。就是这样。这是一个通用的抽象蓝图,您稍后将使用它来设计您需要的类。注意:抽象类中的两个方法也是抽象的,也就是说它们根本没有实现。原因是一样的:抽象类不会为“只是汽车”创建“默认行为”。它们只是表明每辆车必须能够做什么。也就是说,如果您确实需要默认行为,则可以在抽象类中实现方法。Java 不禁止这样做:
public abstract class Car {
private String model;
private String color;
private int maxSpeed;
public void gas() {
System.out.println("Accelerating!");
}
public abstract void brake();
// Getters and setters
}
public class Sedan extends Car {
@Override
public void brake() {
System.out.println("The sedan is slowing down!");
}
}
public class Main {
public static void main(String[] args) {
Sedan sedan = new Sedan();
sedan.gas();
}
}
控制台输出
Accelerating!
如您所见,我们在抽象类中实现了一个方法,但没有实现另一个。这样一来,我们Sedan
类的行为就分为两部分:如果我们调用它的gas()
方法,行为是从抽象父类“拉上来”的Car
,我们brake()
在Sedan
类中实现方法。这是超级方便和灵活的。但是我们的类现在不那么抽象了,是吗?毕竟,它实际上实现了一半的方法。事实是——这是一个非常重要的特征——即使一个类的方法是抽象的,它也是抽象的. 两种方法中的一种,或千种方法中的一种——都没关系。我们甚至可以实现所有方法,不留下任何抽象。结果将是一个没有任何抽象方法的抽象类。原则上这是可能的——编译器不会产生任何错误——但最好不要这样做,因为它剥夺了抽象这个词的意义。你的程序员伙伴看到这个也会很惊讶:/也就是说,如果一个方法被标记为抽象的,每个后代类都必须实现它或被声明为抽象的。否则编译器会报错. 当然,每个类只能继承一个抽象类,所以抽象类和普通类在继承上没有区别。我们继承抽象类还是普通类并不重要——父类只能有一个。
为什么Java没有多类继承
我们已经说过 Java 中没有多重继承,但我们并没有真正深究为什么。让我们现在尝试这样做。事实上,如果 Java 确实具有多重继承,那么子类将无法决定选择哪种行为。假设我们有两个类:Toaster
和NuclearBomb
:
public class Toaster {
public void on() {
System.out.println("The toaster is on. We're toasting!");
}
public void off() {
System.out.println("The toaster is off!");
}
}
public class NuclearBomb {
public void on() {
System.out.println("Boom!");
}
}
如您所见,两个类都有一个on()
方法。对于烤面包机,该方法开始烤面包,但对于核弹,它会引发爆炸。呃哦:/现在想象一下你已经决定(不要问我为什么!)在两者之间创造一些东西。这是你的课MysteriousDevice
:当然,这段代码是行不通的。我们仅将其作为“可能发生的情况”的示例进行介绍:
public class MysteriousDevice extends Toster, NuclearBomb {
public static void main(String[] args) {
MysteriousDevice mysteriousDevice = new MysteriousDevice();
mysteriousDevice.on(); // And what should happen here? Will we get toast or a nuclear apocalypse?
}
}
让我们看看我们有什么。这个神秘装置同时源自烤面包机和核弹。两者都有一个on()
方法。on()
因此,如果我们调用一个对象,则不清楚应该执行哪个实现MysteriousDevice
。对象不会理解。最重要的是,NuclearBomb 没有off()
方法,所以如果我们没有猜对,就不可能关闭设备。这种“误解”,在不清楚应该执行哪个行为时,正是 Java 的创造者拒绝多重继承的原因。也就是说,您将了解到 Java 类可以实现许多接口。
GO TO FULL VERSION