1、抽象类:
概念:抽象类是一种类,不能被实例化,其中包含了抽象方法(只有方法签名,没有方法体)和具体方法的定义。子类继承抽象类后,必须实现其中的抽象方法。
使用场景:适用于具有共同特征和行为的类,提供了一种模板或基础结构,具有一定的实现。
优点:提供了一种模板化的实现,可以包含成员变量和具体方法,便于代码复用和维护。
缺点:类与类之间的耦合较强,一旦继承了抽象类,就无法再继承其他类。
继承抽象类时的注意事项:
- 抽象方法:必须实现抽象类中的所有抽象方法,除非子类也声明为抽象类。
抽象类中有一些关键字:
abstract
:用于定义抽象类和方法,抽象方法没有方法体,必须在子类中实现。public
:修饰方法或成员变量,使其对所有类可见。protected
:访问修饰符,用于限制访问权限只在当前类和其子类中。private
:修饰方法或成员变量,使其只能在当前类内部访问。final
:修饰方法或类,防止方法被重写或类被继承。- 这些关键字在普通类中也可以使用,具有相似的功能和用法。
- 构造函数:子类必须调用父类的构造函数。
- 单继承:只能继承一个抽象类,但可以实现多个接口。
- 灵活性:抽象类可以包含非抽象方法的实现,提供一些通用功能。
- 设计层次:抽象类通常用于设计类的层次结构,提供共享的行为和结构。
2、接口:
概念:接口是一种抽象类型,定义了一组抽象方法,但不包含具体实现。类实现接口时,必须实现接口中定义的所有方法。
- 使用场景:适用于定义类之间的契约,实现多态性,允许多个类实现同一个接口。
- 优点:降低了类之间的耦合度,支持多重继承,适用于需要多态性的场景。
- 缺点:不能包含成员变量和具体方法的实现,实现类需要重复编写相同的方法。
- 实现接口时的注意事项:
- 方法实现:必须实现接口中定义的所有方法。但是当一个抽象类实现一个接口时,并不需要实现接口中的所有方法。
接口中方法有一些关键字包括:
public
:修饰接口中的方法或常量,使其对所有类可见。不过接口中的方法都是隐式public
的。default
:用于定义接口的默认方法,提供方法的默认实现。static
:修饰接口中的方法,使其成为静态方法,可以直接通过接口名调用。abstract
:接口中的方法默认为抽象方法,可以省略不写。private
:Java 9 中引入的关键字,用于修饰接口中的私有方法,只能在接口内部调用。
- 多继承:可以实现多个接口,但只能继承一个类。
- 灵活性:提供更大的灵活性,允许类实现多个不相关的接口。
- 强制性:接口中的方法是强制性的,必须在实现类中进行具体实现。
注意:
- 抽象类适用于具有共同特征和行为的类,而接口适用于定义契约和实现多态性。
- 在设计时,应优先考虑使用接口,以提高灵活性和降低耦合度。
- 可以使用抽象类作为接口的补充,提供一些通用的实现。
3、区别:
抽象类:
- 可以包含成员变量和具体方法的实现。
- 可以有构造函数。
- 子类只能继承一个抽象类。
- 用于定义类之间的共性特征和行为。
- 抽象类中的非抽象方法必须有实现body。
- 不能使用
default
关键字修饰方法。
接口:
- 只能包含抽象方法,不包含具体实现。
- 不能有构造函数。
- 类可以实现多个接口。
- 用于定义契约,支持多态性。
- 不能使用
final
关键字,因为接口的方法默认是抽象的,无法被final修饰。也不能使用protected
关键字。
总结:
- 抽象类用于表示一种"is-a"关系,即子类是父类的一种特殊类型,具有相同的特征和行为。
- 接口用于表示一种"has-a"关系,即类具有某种特定的能力或行为。
用动物做比喻,我们可以使用抽象类和接口来解释这两种关系的区别:
抽象类表示一种"is-a"关系:
想象一下,我们有一个抽象类Animal
,它定义了一些共性特征和行为,比如呼吸、移动和食物来源。现在我们创建了具体的子类,比如Cat
和Dog
,它们都继承自Animal
类。这里,Cat
和Dog
都是动物,它们具有共同的特征和行为,因此适合使用抽象类来表示这种"is-a"关系。
abstract class Animal {
abstract void breathe();
abstract void move();
abstract void eat();
}
class Cat extends Animal {
void breathe() {
// 实现猫的呼吸行为
}
void move() {
// 实现猫的移动行为
}
void eat() {
// 实现猫的进食行为
}
}
class Dog extends Animal {
void breathe() {
// 实现狗的呼吸行为
}
void move() {
// 实现狗的移动行为
}
void eat() {
// 实现狗的进食行为
}
接口表示一种"has-a"关系:
现在,我们创建一个接口Flying
,定义了飞行的能力。然后我们有一个类Bird
,它实现了Flying
接口。在这种情况下,Bird
并不是飞行本身,但它有飞行的能力,因此更适合使用接口来表示这种"has-a"关系。
interface Flying {
void fly();
}
class Bird implements Flying {
void fly() {
// 实现鸟的飞行行为
}
}
通过这个生动的动物世界的比喻,我们可以更直观地理解抽象类和接口在代码设计中的不同用途和含义。
抽象类和接口各有其适用的场景和优缺点,根据具体需求选择合适的方式来设计和构建程序,以提高代码的可维护性和扩展性。