前言
本文是Java基础回炉文集的第三篇,关于文集可通过《Java基础回炉和提升暨文集开篇》了解。
今天我们将从接口的基本概念出发,详细罗列了接口的基本语法特点,对比了接口和抽象类的异同点,并在此基础上总结了接口的意义以及应用实例,最后探讨了java8中对接口添加的新特性及其目的。
1. 定义
(1)什么是接口:
- 接口是对行为的抽象,它是抽象方法的集合,利用接口可以达到API定义和实现分离的目的。接口,不能实例化;不能包含任何非常量成员,任何
field
都是隐含着public static final
的意义;同时,没有非静态方法实现,也就是说要么是抽象方法,要么是静态方法(静态方法是jdk1.8
新特性,jdk1.8
中还增加了default
修饰的默认方法)。 - 注意:接口是一种引用数据类型(类、接口和数组),它并不是一个类类型。
(2)接口的定义格式
//定义一个接口
interface 接口名{
public static final String MSG = "hello";//全局常量
public abstract void print();//抽象方法
}
(3)定义接口时应注意:
- 接口中的所有属性 默认的修饰符是
public static final
,所以必须要初始化。 - 接口中的所有方法 默认的修饰符是
public abstract
。
也就是说,对于接口,里面的组成只有抽象方法和全局常量,所以很多时候为了书写简单,可以不用写public abstract
或者public static final
。并且,接口中的访问权限只有一种:public
,即:定义接口方法和全局常量的时候就算没有写上public
,那么最终的访问权限也是public
。
2. 接口的语法特点
类实现接口可以通过implements实现,实现接口的时候必须把接口中的所有方法实现,一个类可以实现多个接口。
接口中定义的所有的属性默认是public static final的,即静态常量既然是常量,那么定义的时候必须赋值。
接口中定义的方法不能有方法体。接口中定义的方法默认添加public abstract
有抽象函数的不一定是抽象类,也可以是接口类。
由于接口中的方法默认都是抽象的,所以不能被实例化。
对于接口而言,可以使用子类来实现接口中未被实现的功能函数。
如果实现类中要访问接口中的成员,不能使用super关键字。因为两者之间没有显示的继承关系,况且接口中的成员属性是静态的。可以使用接口名直接访问。
接口没有构造方法。
3. 接口和抽象类的异同
3.1 语法层面异同
在《深入类和对象——深入Java基础系列(二)》中博主已经总结了抽象类的特点,下面将列表将接口和抽象类进行详细的对比。
(1)相同点:
- 都不能被实例化
- abstract class 和interface是支持抽象类定义的两种机制
(2)异同点:
3.2 设计层面异同
(1)抽象层次不同
- 抽象类是对类抽象,抽象类是对整个类整体进行抽象,包括属性、行为。归根结底它还是一个类,它描述的是一类事物共有的,但是不确定的行为。
- 接口是对行为的抽象,是对类局部(行为)进行抽象,是抽象方法的集合。
(2)跨域不同 - 抽象类是从子类中发现公共部分,然后泛化成抽象类,子类继承该父类即可。也就是说子类和抽象类“is-a”关系。
- 接口与实现它的子类可以不存在任何关系,只是添加一个额外的行为。也就是说实现类“has-a”接口的行为。
例如,在Animal这个抽象类中,提取的动物eat()和sleep()的行为,那么子类Dog、Bird和Cat各自重写了两个方法,这里我们可以看到三者都是属于动物的。而我们知道并不是所有的动物都会飞,我们把飞的行为抽象为Ifly接口,这个飞的接口可以被Bird实现也可以被飞机实现,这里飞机和Bird之间没有什么关系。
也就是说,抽象类(父类)中定义的该对象的基本功能,而接口中定义的是该对象的扩展功能。
4. 接口的意义及应用实例
4.1 接口存在的意义
(1)重要性:在Java语言中, abstract class 和interface 是支持抽象类定义的两种机制。正是由于这两种机制的存在,才赋予了Java强大的面向对象能力(封装、继承、多态)。接口是多态的一种实现方式。
(2)简单、规范性:如果一个项目比较庞大,那么就需要一个能理清所有业务的架构师来定义一些主要的接口,这些接口不仅告诉开发人员你需要实现那些业务,而且也将命名规范限制住了(防止一些开发人员随便命名导致别的程序员无法看明白)。
(3)维护、拓展性:比如你要做一个画板程序,其中里面有一个面板类,主要负责绘画功能,然后你就这样定义了这个类。
可是在不久将来,你突然发现这个类满足不了你了,然后你又要重新设计这个类,更糟糕是你可能要放弃这个类,那么其他地方可能有引用他,这样修改起来很麻烦。
如果你一开始定义一个接口,把绘制功能放在接口里,然后定义类时实现这个接口,然后你只要用这个接口去引用实现它的类就行了,以后要换的话只不过是引用另一个类而已,这样就达到维护、拓展的方便性。
(4)安全、严密性:接口是实现软件松耦合的重要手段,它描叙了系统对外的所有服务,而不涉及任何具体的实现细节。这样就比较安全、严密一些(一般软件服务商考虑的比较多)。
4.2 应用实例
(1)原始代码:下面这段代码,来自阿里旗下虾米音乐产品代码,曾因为犀利注释而大火。
public class VIPCenter {
void serviceVIP(T extend User user>) {
if (user instanceof SlumDogVIP) {
// 穷B VIP,活动抢的那种
// do somthing
} else if(user instanceof RealVIP) {
// do somthing
}
// ...
}
(2)缺陷:这段代码的一个问题是,业务逻辑集中在一起,当出现新的用户类型时,比如他们添加一个“金主VIP”,“爸爸VIP”,这就需要直接去修改服务方法代码实现,这可能会意外影响不相关的某个用户类型逻辑。
(3)改进方法:这时候可以把上述代码中通过条件判断语句判断用户类型而提供对应服务的方法进行抽象,把业务逻辑的紧耦合关系拆开,实现代码的隔离保证了方便的扩展。
//一种类型的用户,对应自己的服务提供者类
private Map<User.TYPE, ServiceProvider> providers;
void serviceVIP(T extend User user) {
//获取用户类型,并调用具体服务
providers.get(user.getType()).service(user);
}
}
//服务行为的抽象
interface ServiceProvider{
void service(T extend User user) ;
}
class SlumDogVIPServiceProvider implements ServiceProvider{
void service(T extend User user){
// do somthing
}
}
class RealVIPServiceProvider implements ServiceProvider{
void service(T extend User user) {
// do something
}
}
//注释:上述应用实例借鉴《Java核心技术36讲》——杨晓峰
5. Java8中接口的新特性
(1)Java8新特性——接口的默认方法:简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。
- 默认方法分为default修饰的默认方法和static修饰的静态默认方法:
public interface JDK8Interface {
// static修饰符定义静态方法
static void staticMethod() {
System.out.println("接口中的静态方法");
}
// default修饰符定义默认方法
default void defaultMethod() {
System.out.println("接口中的默认方法");
}
}
注意:静态方法,只能通过接口名调用,不可以通过实现类的类名或者实现类的对象调用。default方法,只能通过接口实现类的对象来调用。
public class JDK8InterfaceImpl implements JDK8Interface {
//实现接口后,因为默认方法不是抽象方法,所以可以不重写,但是如果开发需要,也可以重写
//静态方法不能被重写
}
public class Main {
public static void main(String[] args) {
// static方法必须通过接口类调用
JDK8Interface.staticMethod();
//default方法必须通过实现类的对象调用
new JDK8InterfaceImpl().defaultMethod();
}
(2)为什么要有这个特性:
首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的java 8之前的集合框架没有foreach方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。
(3)带来的问题:由于类可以实现多个接口,也可以继承类,当接口或类中有相同函数签名的方法时,这个时候到底使用哪个类或接口的实现呢?
这里有三个规则可以进行判断:
- 类中的方法优先级最高。类或父类中声明的方法的优先级高于任何声明为默认方法的优先级。
interface IA {
default void print() {
System.out.println("default method of IA");
}
}
class ClassA {
public void print() {
System.out.println("method of ClassA");
}
}
//类B继承了类A,且实现了接口A,且接口A的默认方法名和类A的方法名一样
class ClassB extends ClassA implements IA {
}
public class DefaultMethodTest {
@Test
public void test(){
ClassB classB=new ClassB();
classB.print();
}
}
// output:method of ClassA
// 解析:类中的方法优先级最高
- 如果无法依据第一条进行判断,那么子接口的优先级更高:函数签名相同时,优先选择拥有最具体实现的默认方法的接口,即如果B继承了A,那么B就比A更加具体。
interface IA {
default void print() {
System.out.println("default method of IA");
}
}
interface IB extends IA{
default void print() {
System.out.println("default method of IB");
}
}
//类A实现了接口A,接口B,接口B继承了接口A,且接口A和接口B有相同的方法
class ClassA implements IA,IB{
}
public class DefaultMethodTest {
@Test
public void test(){
ClassA classA=new ClassA();
classA.print();
}
}
//output:default method of IB
//解析:B接口继承了A接口,那么B就比A更加具体
- 最后,如果还是无法判断,继承了多个接口的类必须通过显式覆盖和调用期望的方法,显式地选择使用哪一个默认方法的实现。不然编译都会报错。
interface IA {
default void print() {
System.out.println("default method of IA");
}
}
interface IB {
default void print() {
System.out.println("default method of IB");
}
}
class ClassA implements IA, IB {
@Override
public void print() {
IA.super.print();//显示调用期望的方法或者覆盖默认方法
}
}
//output:default method of IA
//这里我有个疑问IA.super代表的什么意思
6. 总结
本文是Java基础回炉文件的第三篇,这篇文章主要介绍了java接口的基本特性,接口和抽象类的异同点,以及java8中接口的新特性。读完这篇文章的收获应该是:
- 接口的定义,意义及优点
- 接口与抽象类的异同点
- java8 中接口的新特性及注意问题