为什么想谈谈抽象类和接口呢?
当我百度抽象类和接口的区别时,网上会有一堆千篇一律的回答,就像下面:
- 接口的方法默认是public,所有的方法在接口中不能有实现(JDK8开始方法可以实现,不过要增加static或者default修饰符);而抽象类可以有非抽象的方法,也就是可以实现方法,也就是方法可以有方法体。
- 一个类能实现多个接口,但是一个类只能继承一个抽象类。
- 从方法上来说,接口中的方法默认以public abttact 修饰,而抽象类中的抽象方法可以有 public、protected 和 default 这些修饰符(抽象方法就是为了被重写所以不能使用 private 关键字修饰)。
- 从属性上来说,抽象类中的属性可以用各种各样的修饰符去修饰,而接口中的属性默认是public static final。
- 抽象类可以含有构造方法,而接口不可以有构造方法。
JDK7~JDK9期间,接口发生的变化:
- 在 jdk7或更早版本中,接口里面只能有常量变量和抽象方法。这些接口方法必须由选择实现接口的类实现。
- jdk8的时候接口可以有默认方法和静态方法功能。
- Jdk9在接口中引入了私有方法和私有静态方法。
以上回答都是基于接口和抽象类使用上的区别来说明的,当看到这么多区别的时候,我又觉得这两者是那么的相似,为什么有了接口还会有抽象类呢?什么时候会用到抽象类,什么时候又会用到接口呢?
于是有了接口和抽象类在设计层面上的区别。
先来举一个简单的例子:
学生和老师都有吃饭、写字的本领吧,就下面这样:
class Student {
public void eat() { }
public void sleep() { }
}
class Teacher {
public void eat() { }
public void sleep() { }
}
这样Student和Teacher都有重复的代码,那么我们有两个办法:
- 可以定义一个接口,接口里面包含了eat()和sleep()方法,然后让Student和Teacher都实现这个方法就好了。
- 可以定义一个抽象类,抽象类里面包含了eat()和sleep()方法,然后让Student和Teacher都继承这个抽象类就好了。
这样一看,抽象类和接口似乎没什么区别。
但是如果给Teacher增加一个教书的方法teach(),而Student是不能教书的,也就没有teach()方法,那要怎么解决呢?
- 如果在接口中增加teach(),那学生实现了这个接口的话,也就具备了teach(),不可行!
- 如果在抽象类中增加teach(),那学生继承了这个抽象类的话,也就具备了teach(),不可行!
试想一下eat()和sleep()都是学生和老师共有的方法,那我们可以将eat()和sleep()都写进一个抽象类Person,然后让老师和学生都继承Person,teach()是老师独有的方法,也就是对Person的一种拓展和延伸,那我们可以再定义一个teach接口,然后让老师去实现这个teach接口,总体解决方案如下:
abstract class Person {
public abstract void eat();
public abstract void write();
}
interface teach {
void teach();
}
class Student extends Person {
@Override
public void eat() {
}
@Override
public void write() {
}
}
class Teacher extends Person implements teach {
@Override
public void eat() {
}
@Override
public void write() {
}
@Override
public void teach() {
}
}
写到这,应该大部分人能够明白接口和抽象类的区别了,总的来说:
继承是一个 "是不是"的关系,而 接口实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如一个人是否能教书,能则可以实现这个接口,不能就不实现这个接口。
你以为这样就完了吗?
细心的人会说,那为什么有抽象类,我用一个普通类Person也可以呀,然后让Student和Teacher继承这个Person就好了呀,是的, 没错,就像这样:
class Person {
public void eat() {
}
public void write() {
}
}
interface teach {
void teach();
}
class Student extends Person {
}
class Teacher extends Person implements teach {
@Override
public void teach() {
}
}
不知道你有没有忘,抽象类中既可以定义未实现的方法,也可以定义实现了的方法。
当不同的类具有某些相同的行为时,且实现的方法一致时,比如说see()都是用眼睛看,hear()都是用耳朵听,那么see()和hear()都可以在抽象类中实现,其他的类都派生于这个抽象类,避免了see()和hear()的重复实现,反正大家see()和hear()都是一样的,这就达到了代码复用的目的。
当不同的类具有某些相同的行为时,且实现的方法不一致时,比如说eat(),老师可能在教师餐厅吃,而学生可能在学生食堂吃,这就使得eat()的实现方式是不同的,所以eat()可以作为抽象类中的抽象方法,所有的派生类自己去实现这个eat()方法!
这些,普通的类能做到吗?如果是普通的类,它的方法必须有方法体,那么所有派生于普通类的类的方法实现都是集成自父类的(除非方法重写)!
abstract class Person {
public void see() {
System.out.println("see by eyes");
}
public abstract void eat();
}
interface teach {
void teach();
}
class Student extends Person {
@Override
public void eat() {
System.out.println("eat in student's restaurant");
}
}
class Teacher extends Person implements teach {
@Override
public void teach() {
}
@Override
public void eat() {
System.out.println("eat in teacher's restaurant");
}
}
接口的设计目的,是对类的行为进行约束(更准确的说是一种“有”约束,因为接口不能规定类不可以有什么行为),也就是提供一种机制,可以强制要求不同的类具有相同的行为。它只约束了行为的有无,但不对如何实现行为进行限制。
抽象类的实现目的,是代码复用,可以让这些类都派生于一个抽象类。在这个抽象类中实现了共有的方法,避免让所有的子类来实现这个共有的方法,这就达到了代码复用的目的。
以上就是我对抽象类和接口的一些理解,如果有错误之处欢迎大家指出。