上一节学习了面向对象。其中学习了面向对象的四大特性,封装还好理解,但是抽象和多态看简单的例子还可以,但是具体到项目中,需要对"为什么"进一步了解。
1.抽象类和接口的差异点?
-
实现方式:
- 抽象类: 可以有实例变量(字段),可以有构造方法,可以包含具体的方法实现(非抽象方法)。使用
abstract
关键字声明抽象方法。 - 接口: 不能包含实例变量(除非是
static
和final
),不能有构造方法,所有的方法都是抽象的,不包含方法的具体实现。
- 抽象类: 可以有实例变量(字段),可以有构造方法,可以包含具体的方法实现(非抽象方法)。使用
-
多继承:
- 抽象类: 一个类只能继承一个抽象类。
- 接口: 一个类可以实现多个接口。
-
构造方法:
- 抽象类: 可以有构造方法,用于初始化实例变量。
- 接口: 不能有构造方法,因为接口不能被实例化。
-
成员类型:
- 抽象类: 可以包含实例变量,普通方法(具体或抽象),静态方法,常量。
- 接口: 可以包含常量,抽象方法,静态方法(Java 8+),默认方法(Java 8+)。
-
访问修饰符:
- 抽象类: 可以有各种访问修饰符的成员(public、private、protected、default/package-private)。
- 接口: 所有成员默认是 public,并且不允许使用其他访问修饰符。
-
使用场景:
- 抽象类: 适用于有一些通用实现的情况,以及需要包含实例变量的情况。
- 接口: 适用于定义规范、实现多继承、以及不包含实例变量的情况。
-
继承与实现:
- 抽象类: 使用
extends
关键字进行继承。 - 接口: 使用
implements
关键字进行实现。
- 抽象类: 使用
-
构造方法调用:
- 抽象类: 在子类构造方法中使用
super
关键字调用父类构造方法。 - 接口: 接口没有构造方法,因此不需要在实现类中调用接口的构造方法。
- 抽象类: 在子类构造方法中使用
2.抽象类和继承的区别?
继承本身就能达到代码复用的目的, 而继承也并不要求父类一定是抽象类,那我们不使用抽象类,照样也可以实现继承和复用。从这个角度上来讲,我们貌似并不需要抽象类这种语法呀。那抽象类除了解决代码复用的问题,还有什么其他存在的意义吗?
js
class Animal {
void makeSound() {
System.out.println("Some generic sound");
}
}
class Dog extends Animal {
void makeSound() {
System.out.println("Bark");
}
}
class Cat extends Animal {
void makeSound() {
System.out.println("Meow");
}
void makeJump() {
System.out.println("Jump");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
Animal cat = new Cat();
dog.makeSound(); // 输出: Bark
cat.makeSound(); // 输出: Meow
//报错!!!!! animal里没有这个方法。
cat.makeJump();
}
}
虽然抽象类和继承都可以实现代码复用,但是无法使用多态特性了。像下面这样编写代码,就会出现编译错误。
3.什么时候应该使用抽象,什么时候使用接口?
如果我们要表示一种 is-a 的关系,并且是为了解决代码复用的问题,我们就用抽象类;
抽象类的应用场景:
-
有一些通用的方法或属性需要被多个子类共享:
- 如果存在一组子类,它们有一些通用的方法或属性,而又有一些需要在每个子类中实现的抽象方法,那么抽象类是一个合适的选择。
csharpjavaCopy code abstract class Shape { int x, y; abstract void draw(); // 子类必须实现的抽象方法 }
-
代码复用性较高:
- 如果你希望在不同的类之间共享一些通用的代码,包括字段和方法的实现,抽象类可以提供一定程度的代码复用。
-
构造方法和初始化块:
- 抽象类可以有构造方法,用于初始化实例变量。这在一些情况下是很有用的。
csharpjavaCopy code abstract class Shape { int x, y; Shape(int x, int y) { this.x = x; this.y = y; } abstract void draw(); }
-
访问修饰符的使用:
- 如果你想使用不同的访问修饰符(public、protected、private)来限定抽象类的成员,抽象类提供了这样的灵活性。
如果我们要表示 一种 has-a
关系,并且是为了解决抽象而非代码复用的问题,那我们就可以使用接口。
-
定义规范而不关心具体实现:
- 如果你希望定义一组规范,而不关心具体的实现细节,接口是更合适的选择。接口只包含抽象方法的声明,没有提供具体的实现。
csharpjavaCopy code interface Drawable { void draw(); // 只有方法的声明,没有实现 }
-
多继承情况:
- 当一个类需要继承多个接口时,接口是更合适的选择,因为Java不支持多继承,而一个类可以实现多个接口。
csharpjavaCopy code interface Shape { void draw(); } interface Color { void fill(); } class Circle implements Shape, Color { // 实现 draw 和 fill 方法 }
-
Java 8+的默认方法:
- 接口从Java 8开始支持默认方法,允许在接口中提供方法的默认实现。这为向现有接口添加新的方法提供了一种方式,而不会影响现有的实现类。
csharpjavaCopy code interface Drawable { void draw(); // 抽象方法 default void resize() { System.out.println("Resizing the drawing"); } }
-
实现组件化设计:
- 接口的灵活性使得它们非常适合实现组件化设计,每个组件只需要实现相应的接口,而不需要关心其他组件的具体实现。
从类的继承层次上来看,抽象类是一种自下而上的设计思路,先有子类的代码重复,然后再抽象成上层的父类(抽象类)。
而接口正好相反,它是一种自上而下的设计思路。我们在编程的时候,一般都是先设计接口,再去考虑具体的实现。
前者是代码优化,后者是在代码设计过程中就应该考虑到的问题,这与经验和知识积累也有直接的关系。 那怎么样在一开始编程的时候就确定是否要使用接口呢? 后面会继续积累。