Java学习day039 接口(接口与抽象类、静态方法、默认方法、解决默认方法冲突)

使用的教材是java核心技术卷1,我将跟着这本书的章节同时配合视频资源来进行学习基础java知识。

day039   接口(接口与抽象类、静态方法、默认方法、解决默认方法冲突)


1.接口与抽象类

为什么Java程序设计语言还要不辞辛苦地引入接口概念?为什么不将Comparable直接设计成如下所示的抽象类。

abstract class Comparable/ /why not?
{
    public abstract int compareTo(Object other);
}

然后,Employee类再直接扩展这个抽象类,并提供compareTo方法的实现:

class Employee extends Comparable //why not?
{
    public int compareTo(Object other){...}
}

非常遗憾,使用抽象类表示通用属性存在这样一个问题:每个类只能扩展于一个类。假设Employee类已经扩展于一个类,例如Person,它就不能再像下面这样扩展第二个类了:

class Employee extends Person,Comparable//Error

但每个类可以像下面这样实现多个接口:

class Employee extends Person implements Comparable//OK

有些程序设计语言允许一个类有多个超类,例如C++。我们将此特性称为多重继承(multipleinheritance)。而Java的设计者选择了不支持多继承,其主要原因是多继承会让语言本身变得非常复杂(如同C++),效率也会降低(如同Eiffel)。

实际上,接口可以提供多重继承的大多数好处,同时还能避免多重继承的复杂性和低效性。


2.静态方法

在JavaSE8中,允许在接口中增加静态方法。

目前为止,通常的做法都是将静态方法放在伴随类中。在标准库中,你会看到成对出现的接口和实用工具类,如Collection/Collections或Path/Paths。

下面来看Paths类,其中只包含两个工厂方法。可以由一个字符串序列构造一个文件或目录的路径,如Paths.getfjdk1.8.0","jre","bin")。在JavaSE8中,可以为Path接口增加以下方法:

public interface Path
{
    public static Path get(String first,String...more)
    {
        return Fi1eSystems.getDefault().getPath(first,more);
    }
    ...
}

这样一来,Paths类就不再是必要的了。

不过整个Java库都以这种方式重构也是不太可能的,但是实现你自己的接口时,不再需要为实用工具方法另外提供一个伴随类。


3.默认方法

可以为接口方法提供一个默认实现。必须用default修饰符标记这样一个方法。

public interface Comparable <T>
{
    default int compareTo(T other){return 0;}
    //By default,all elements are the same
}

当然,这并没有太大用处,因为Comparable的每一个实际实现都要覆盖这个方法。不过有些情况下,默认方法可能很有用。例如,如果希望在发生鼠标点击事件时得到通知,就要实现一个包含5个方法的接口:

public interface MouseListener
{
    void mousedieked(MouseEvent event);
    void mousePressed(MouseEvent event);
    void mouseReleased(MouseEvent event);
    void mouseEntered(MouseEvent event);
    void mouseExited(MouseEvent event);
}

大多数情况下,你只需要关心其中的1、2个事件类型。在JavaSE8中,可以把所有方法声明为默认方法,这些默认方法什么也不做。

public interface MouseListener
{
    default void mousedieked(MouseEvent event){}
    default void mousePressed(MouseEvent event){}
    default void mouseReleased(MouseEvent event){}
    default void mouseEntered(MouseEvent event){}
    default void mouseExited(MouseEvent event){}
}

这样一来,实现这个接口的程序员只需要为他们真正关心的事件覆盖相应的监听器。默认方法可以调用任何其他方法。例如,Collection接口可以定义一个便利方法:

public interface Collection
{
    int size();//An abstract method
    default boolean is Empty()
    {
    return size()=0;
    }
    ...
}

这样实现Collection的程序员就不用操心实现isEmpty方法了。

默认方法的一个重要用法是“‘接口演化”(interfaceevolution)Collection接口为例,这个接口作为Java的一部分已经有很多年了。假设很久以前你提供了这样一个类:

public class Bag implements Collection

后来,在JavaSE8中,又为这个接口增加了一个stream方法。假设stream方法不是一个默认方法。那么Bag类将不能编译,因为它没有实现这个新方法。为接口增加一个非默认方法不能保证“源代码兼容

不过,假设不重新编译这个类,而只是使用原先的一个包含这个类的JAR文件。这个类仍能正常加载,尽管没有这个新方法。程序仍然可以正常构造Bag实例,不会有意外发生。(为接口增加方法可以保证“二进制兼容”)。不过,如果程序在一个Bag实例上调用stream方法,就会出现一个AbstractMethodError。

将方法实现为一个默认方法就可以解决这两个问题。Bag类又能正常编译了。另外如果没有重新编译而直接加载这个类,并在一个Bag实例上调用stream方法,将调用Collection,stream方法。

在JavaAPI中,你会看到很多接口都有相应的伴随类,这个伴随类中实现了相应接口的部分或所有方法,如CoUection/AbstractCollectkm或MouseListener/MouseAdapter。


4.解决默认方法冲突

如果先在一个接口中将一个方法定义为默认方法,然后又在超类或另一个接口中定义了同样的方法,会发生什么情况?诸如Scala和C++等语言对于解决这种二义性有一些复杂的规则。幸运的是,Java的相应规则要简单得多。规则如下:

1)超类优先。如果超类提供了一个具体方法,同名而且有相同参数类型的默认方法会被忽略。

2)接口冲突。如果一个超接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型(不论是否是默认参数)相同的方法,必须覆盖这个方法来解决冲突。

下面来看第二个规则。考虑另一个包含getName方法的接口:

interface Named
{
    default String getName(){return getClass().getName()+"_"+hashCodeO;}
}

如果有一个类同时实现了这两个接口会怎么样呢?

class Student implements Person,Named
{
    ...
}

类会继承Person和Named接口提供的两个不一致的getName方法。并不是从中选择一个,Java编译器会报告一个错误,让程序员来解决这个二义性。只需要在Student类中提供一个getName方法。在这个方法中,可以选择两个冲突方法中的一个,如下所示:

class Student implements Person,Named
{ 
    public String getName(){return Person.super.getName();}
    ...
}

现在假设Named接口没有为getName提供默认实现:

interface Named
{
    String getName();
}

Student类会从Person接口继承默认方法吗?这好像挺有道理,不过,Java设计者更强调一致性。两个接口如何冲突并不重要。如果至少有一个接口提供了一个实现,编译器就会报告错误,而程序员就必须解决这个二义性。

我们只讨论了两个接口的命名冲突。现在来考虑另一种情况,一个类扩展了一个超类,同时实现了一个接口,并从超类和接口继承了相同的方法。例如,假设Person是一个类,Student定义为:

class Student extends Person implements Named{...}

在这种情况下,只会考虑超类方法,接口的所有默认方法都会被忽略。在我们的例子中,Student从Person继承了getName方法,Named接口是否为getName提供了默认实现并不会带来什么区别。这正是“类优先”规则。

“类优先”规则可以确保与Java SE7的兼容性。如果为一个接口增加默认方法,这对于有这个默认方法之前能正常工作的代码不会有任何影响。

千万不要让一个默认方法重新定义Object类中的某个方法。例如,不能为toString或equals定义默认方法,尽管对于List之类的接口这可能很有吸引力,由于“类优先”规则,这样的方法绝对无法超越Object.toString或Objects.equals。


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值