JDK8为接口添加的两项新功能:默认接口方法;静态方法
JDK8接口新特性
一、默认接口方法
在JDK8之前,接口不能定义任何实现。在意味着在之前所有的Java版本中,接口指定的方法是抽象方法,不包含方法体。这是传统的接口形式。JDK8为接口添加了一种新功能,叫做默认方法。默认方法允许为接口方法定义默认实现。换句话说,通过使用默认方法,现在能够为接口方法提供方法体,使其不再是抽象方法。默认方法仍在开发时,也被称为扩展方法。
开发默认方法的主要动机是提供一种扩展接口的方法,而不破坏现有代码。回忆一下,接口定义的所有方法都必须被实现。在过去,如果一个使用广泛的接口添加一个新方法,那么由于找不到新方法的实现,现有代码会被破坏。默认方法解决了这个问题,它提供了一个实现,当没有显示提供其他实现时就将采用这个实现。因此,添加默认方法不会破坏现有代码。
开发默认方法的另外一个动机是希望在接口中指定本质上可选的方法,根据接口的使用方式选择使用的方法。例如,接口可能定义了操作一系列元素的一组方法。其中一个方法可能叫做remove(),用于从系列中删除元素。然而,如果接口应该同时支持可修改和不可修改的系列,那么remove()本质上就是可选的,因为不可修改的系列不会使用它。过去,实现不可修改系列的类需要定义remove()的默认实现,让它什么都不做(或者抛出异常)。通过提供这种默认实现,就避免了用于不可修改系列的类必须定义自己的、占位符性质的remove()方法。因此,通过提供默认实现,接口让类实现的remove()方法变为可选方法。
需要指出的是,添加默认方法并没有改变接口的关键特性:不能维护状态信息。例如,接口仍然不能有实例变量。因此,接口与类之间决定性的区别是类可以维护状态信息,而接口不能。另外,仍然不能创建接口本身的实例。接口必须被类实现。因此,即使从JKD8开始,接口可以定义默认方法,在想要创建实例时,也仍然必须用类来实现接口。
最后,一般来说,默认方法是一种特殊用途。创建的接口仍然主要用于指定“是什么”,而不是如何实现。但是,包含默认方法,确实带来了额外的灵活性。
1、默认方法的基础知识
为接口定义默认方法,类似于为类定义方法。主要区别在于,默认方法的声明前面带有关键字default。例如,分析下面的简单接口:
public interface MyIF{
//This is a "normal" interface method declaration.
//It does NOT define a default implementation.
int getNumber();
//This is a default method.Notice that it provides a default implementation.
default String getString(){
return "Default String";
}
}
MyIF声明了两个方法。第一个方法是getNumber(),这是一个标准的接口方法声明,没有定义方法实现。第二个方法是getString(),它包含一个默认实现。本例中,它只是简单地返回字符串"Default String"。重点注意getString()的声明方式。它的声明前面带有default修饰符。这种语法可以推而广之。要定义默认方法,需要在其声明的前面加上关键字default。
因为getString()包含默认实现。所以实现接口的类不需要重写这个方法。例如,下面的MyIFImp类是完全合法的:
//Implement MyIF
class MyIFImp implements MyIF{
//Only getNumber() defined by MyIF needs to be implemented.
//getString() can be allowed to default.
public int getNumber(){
return 100;
}
}
下面的代码创建了MyIFImp的一个实例,并使用该实例来调用getNumber()和getString():
//Use the default method.
class DefaultMethodDemo{
public static void main(String args[]){
MyIFImp obj = new MyIFImp();
//Can call getNumber(),because it is explicitly implemented by MyIFImp:
System.out.println(obj.getNumber());
//Can also call getString(),because of default implementation:
System.out.println(obj.getString());
/*
输出结果:
100
Default String
*/
}
}
可以看到,这里自动使用了getString()的默认实现。MyIFImp并非必须定义该方法。因此,对于getString()方法,类的实现时是可选的(当然,如果getString()的默认实现不能满足类的需求,类就必须重新实现该方法)。
实现接口的类可以自己定义默认方法的实现,并且这种做法很常见。例如,MyIFImp2重写了getString()方法:
class MyIFImp2 implements MyIF{
//Here,implementations for both getNumber() and getString() are provided.
public int getNumber(){
return 100;
}
public String getString(){
return "This is a different string.";
}
}
现在,调用getString()方法时,将返回一个不同的字符串。
2、一个更加实用的例子
前面展示了如何使用默认方法,但是没有说明它们在实际环境下的用途。为此我们定义一个IntStack接口,假设IntStack得到广泛应用,许多程序都依赖它。再假设我们现在想向IntStack添加一个清空堆栈的方法,以便能够重用堆栈。也就是说,我们想要演化IntStack接口,使其定义新的功能,但是又不想破坏现有的代码。在过去,根本无法满足这种要求。但是现在,有了默认方法,实现这一点很容易。例如,可以像下面这样增强IntStack接口:
interface IntStack{
void push(int item);//store an item
int pop();//retrieve an item
//Because clear() has a default,it need not be implemented by a preexisting class
//that users IntStack.
default void clear(){
System.out.println("clear() not implemented.");
}
}
这里,clear()的默认行为是简单地显示一条消息,指出它未被实现。这是可以接受的,IntStack之前的版本没有定义clear()方法,那么所有实现了IntStack的类根本不会调用它。但是,实现了新版本的IntStack的新类可以实现clear()方法。另外,新类,只有在使用clear()时,才需要为它定义实现。因此,默认方法提供了以下优点:
1、优雅地随时间演化接口
2、提供可选功能,但是类不必在不需要该功能时提供占位符实现
另外,在真实代码中,clear()会抛出异常,而不是显示错误消息。
二、在接口中使用静态方法
JDK8为接口添加了另一项新功能:定义一个或更多个静态方法。类似于类中的静态方法,接口定义的静态方法可以独立于任何对象调用。因此,在调用静态方法时,不需要实现接口,也不需要接口的实例。相反,通过指定接口名,后跟句点,然后是方法名,就可以调用静态方法。注意,这与调用类的静态方法类似。下面在上面的MyIF接口中添加了一个静态方法,以展示接口中的静态方法。这个静态方法是getDefaultNumber(),它返回0.
public interface MyIF{
//This is a "normal" interface method declaration.
//It does NOT define a default implementation.
int getNumber();
//This is a default method.Notice that it provides a default implementation.
default String getString(){
return "Default String";
}
//This is a static interface method.
static int getDefaultNumber(){
return 0;
}
}
可以像下面这样调用getDefaultNumber()方法:
int defNum = MyIF.getDefualtNumber();
如前所述,由于getDefaultNumber()是一个静态方法,所以调用它时不需要MyIF的实现或实例。
最后一点:实现接口的类或子接口不会集成接口中的静态方法。