JAVA基础之三-接口和抽象类
java提供了抽象类和接口,总体是好事。
有的OOP语言并没有接口的概念,但相当一部分其实用其它方式实现了JAVA中接口类似的功能。
如果不太清楚二者的区别,难免在面临具体业务的时候,在二者之间摇摆。
---
实际上,关于抽象类和接口的共同点和不同点没有什么可以写的。
设计原则原则让我们尽量基于接口编程(IOP-Interface-Oriented Programming),而不是基于具体类,所以多用接口。
如果需要继承(通常就存在属性),则多用抽象类。
---
一、基本概念
这里有个难点:java的抽象类和接口已经不如发明者期望的那么纯粹了。
所以,以下的概念主要适用于J17及其以上版本。
2.1、抽象类
核心概念:无法实例化的类
用处:为子类提供模板,限定子类的实现,并为子类提供一些公共且已经实现的方法/属性。
可以包含内容:各种可见范围的都可以,最典型的就是抽象方法。除了不能用default,好像就没有不行的。
如果只定义了抽象方法,那么它的作用基本等同于接口。
2.2、接口
核心概念:必须被实现的类,否则无用(但实际不是那么回事,自从J9之后可以工具化)
用处:抽象以实现工程上的多态,从而实现模块化;为函数式编程服务;作为注解
可以包含内容:除了不能定义只包含public修饰符的方法,好像都可以。
二、比较
总体来说,java的抽象类和接口越发地同化,除了几个不能互相替代,好像没有不能互换的:
a.抽象类的可以实现各种修饰符的方法或者属性 -- 接口做不到,至少接口不能定义只有public修饰符的方法
b.接口可以用于实现函数式编程;接口可以用于实现注解
抽象类更适合于定义带有属性的类,接口更适合用于只有实现方法的类(虽然可以带静态属性)。
因为接口的这个特性,所以,接口很适合用于定义spring中bean,因为我们用bean的最主要目的就是为了实现单例的bean。
我们通常不关注特定的bean示例到底有什么特性,只想知道某个bean示例能干什么。
所以,java这种和spring结合很深的语言,接口有大用处。
当然,在spring中,如果bean继承抽象类来是实现bean的定义和注入也是可以的,不过这个时候,就不要为抽象类定义什么属性了。
以下就是这个演示-基于抽象类定义bean(已经省略了包和import部分):
package study.base.oop.classes.modifier.abstractbean; /** * 抽象类,用于演示基于抽象类的 bean */ public abstract class AbstractLiterature { public abstract String writePoem(); } @Component public class ChineseLiteratureService extends AbstractLiterature { @Override public String writePoem() { return """ 两个黄鹂鸣翠柳, 一行白鹭上青天。 醉里挑灯看剑,梦回吹角连营.八百里分麾下炙人,五十先翻塞外声,沙场秋点兵。 """; } } @RequestMapping("/spring/bean/") @Controller public class BeanController { @Autowired private AbstractLiterature literatureService; @GetMapping("/abstract-bean") @ResponseBody public Object testAbstractBean() { String poem= this.literatureService.writePoem(); return poem; } }
编译不会报错,结果也可以看到:
所以,用接口还是用抽象类来接口定义,真是一个问题。
三、适用场景
3.1 想想要不要个性化的属性
如前,如果想保留自有属性,那么用抽象类更好一些;如果不需要保留自有属性,用接口更好一些。
实现一个bean的时候为什么都是基本是实现一个接口,而不是扩展一个抽象类?
也许仅仅处于两个考虑:
1.即使偏好抽象类,也需要避免团队的其他人往抽象类中添加属性,变成不稳定的bean
2.仅仅基于一个朴素的想法:单例bean还要什么属性,所以用理论上干净一点的接口更好一些。
因为第2个因素的缘故,所以我们都会再三强调,不要在bean中定义可变的属性/成员。即使要定义了属性(通常指注入的其它bean),我们也是为了用这些
成员的能力而不是成员的数据。这样做的最重要的目的是为了保证获得方法的某种程度幂等性。
一旦实现类的属性(作为数据)参与方法计算,那么由于单例的特性,可能会造成难于预料的结果,通常那不是我们期望的结果。
为什么单例bean的属性会被共享? 这和JVM的内存模型有不可分割的关系。此处略。
3.2 接口特有的
至于注解和函数式编程,现在只能求助于接口。
如果要实现多继承,现在也只能求助于接口。
3.3默认使用接口的
具体到JAVA的经典应用场景-开发信息应用系统后台,那么还是偏多使用接口。这是因为现在javaEE基本和Spring深度绑定,
spring和Bean深度绑定,Bean基本上就是单例bean,单例意味着不要特性化的属性,不要属性,那么用接口无疑是最好不过了。
3.4 不用bean的地方-也许抽象类可以排上用场
当我们不用bean的时候,通常意味着要写一些相对复杂一些的功能,这个时候抽象类可能更加方便一些。
如果需要定义公共的描述,并基于这个定义构建大模块,可以考虑用抽象类。或者普通的类也可以。
四、小结
随着JAVA版本的变更,接口和抽象类越发相像。
让接口专享注解和函数式编程,使得接口显得有点独大。
不过当你不想单例的时候,应该更多考虑抽象类。