目录
- Java接口示例
- 实现一个接口
- 接口实例
- 实现多个接口
- 方法签名重叠
- 接口变量
- 接口方法
- 接口默认方法
- 接口与继承
- 继承与默认方法
- 接口与多态性
在Java中,接口是一个抽象类型,有点类似于类,但Java接口只能包含方法签名与属性,不能包含括方法的实现。
Java接口示例
public interface MyInterface {
public String hello = "hello";
public void sayHello();
}
如上所示,java接口是使用关键词interface
声明的。就像类一样,Java接口可以被声明为public
或者包范围(无修饰符)。
上面的接口包含了一个变量和一个方法。这个变量可以直接通过这个接口访问,就像这样:
System.out.println(MyInterface.hello);
如上所示,访问接口中的变量与从类中访问静态变量非常相似。(接口中的变量被默认声明为:public static final
)
但是,在访问接口中的方法时,必须先从类中实现该方法,下面将解释如何实现该方法。
实现一个接口
在真正使用接口之前,你必须在Java类中实现该接口。下面的MyInterfaceImpl
类实现了上面的MyInterface
接口:
public class MyInterfaceImpl implements MyInterface {
public void sayHello() {
System.out.println(MyInterface.hello);
}
}
注意上面MyInterface
接口的声明部分,其中implements
标识告诉了Java编译器MyInterfaceImpl
类实现了MyInterface
接口。
实现某接口的类必须实现该接口中声明的所有方法,实现接口方法时必须使用和接口声明中完全一样的签名( 名称 + 参数 ),接口中的变量不需要类来实现,仅仅需要实现方法。
接口实例
一旦一个Java类实现了一个Java接口,你就可以使用Java类的实例作为该接口的实例。请看下面的示例:
MyInterface myInterface = new MyInterfaceImpl();
myInterface.sayHello();
注意创建MyInterface
类型接口实例时,引用了MyInterfaceImpl
类创建的对象。Java之所以允许这么做,是因为类MyInterfaceImpl
实现了MyInterface
接口。你可以将MyInterfaceImpl
类的实例作为MyInterface
接口的实例。
你不能直接创建Java接口的实例,你必须始终先创建实现某个接口的类的实例,并引用该实例作为接口的实例。
实现多个接口
一个Java类中可以实现多个Java接口。在这种情况情况下,该类必须实现所有所实现接口声明中的所有方法。这里有个示例:
public class MyInterfaceImpl
implements MyInterface, MyOtherInterface {
public void sayHello() {
System.out.println("Hello");
}
public void sayGoodbye() {
System.out.println("Goodbye");
}
}
这个类实现了MyInterface
和MyOtherInterface
两个接口,在implements
关键词之后列出需要实现的接口的名称,使用逗号分隔。
如果接口与实现接口的类不在同一个包中,你还需要先导入接口。Java接口是使用import
标识导入的,就像类一样。例如:
import com.jenkov.package1.MyInterface;
import com.jenkov.package2.MyOtherInterface;
public class MyInterfaceImpl implements MyInterface, MyOtherInterface {
...
}
下面是由上面的类实现的两个Java接口:
public interface MyInterface {
public void sayHello();
}
public interface MyOtherInterface {
public void sayGoodbye();
}
如你所见,每个接口都包含一个方法,这些方法由类MyInterfaceImpl
实现。
方法签名重叠
如果一个Java类实现了多个接口,那么有些接口可能含有相同的方法签名(名称+参数)的风险,由于Java类中一个签名只能实现一次,所以这可能会导致一些问题。
Java规范中没有给出解决这个问题的解决方案,在这种情况下该怎么做由你自己决定。
接口变量
Java接口可以包含变量和常量。然而,通常在Java接口中包含变量是没有意义的。在某些情况下,在Java接口中定义常量是有意义的。特别是这些常量被实现接口的类使用,例如在计算中,或者作为接口中某些方法的参数。然而,我的建议是,如果可以的话,避免在Java接口中放置变量。
所有的变量在接口中都是公共的,即便你在变量中省略了public
关键词。
接口方法
一个Java接口中可以包含一个或多个方法的声明。如前面所述,Java接口不能为这些方法指定任何实现。它由实现接口的类来指定实现。
所有的方法在接口中都是公共的,即便你在方法中省略了public
关键词。
接口中可以包含静态方法。
接口默认方法
在Java 8之前,Java接口不能包含接口的实现,只能包含方法签名。但是,当API需要向一个接口添加一个方法时,这就会导致一些问题。如果API只是将该方法添加到所需的接口中,则实现该接口的所有类都必须实现该新方法。如果所有实现类都位于该API中,那当然没有问题。但是,如果某些实现接口的类位于使用该API的客户端代码中,则该代码会被中断。
让我们举例来说明这一点。看看下面这个接口,想象它是一个开源API的一部分,许多应用程序都在内部使用它。
public interface ResourceLoader {
Resource load(String resourcePath);
}
现在假设一个项目使用了这个API,并通过下面的FileLoader
类实现了ResourceLoader
接口:
public class FileLoader implements ResourceLoader {
public Resource load(String resourcePath) {
// 这里是实现 +
// 一个返回语句.
}
}
如果该API的开发人员想在ResourceLoader
接口中添加一个新方法,当项目升级到新版本的API时,FileLoader
类将被破坏。
为了缓解Java接口中的扩展问题,Java 8中新增了"接口默认方法"这个概念。接口的默认方法可以包含接口的默认实现。实现了该接口的类,但不包含默认接口方法的类,将自动获得默认方法的实现。
使用default
关键词将方法标记为默认方法,下面是一个向ResourceLoader
接口添加默认方法的示例:
public interface ResourceLoader {
Resource load(String resourcePath);
default Resource load(Path resourcePath) {
// 提供默认实现
// 以从指定路径加载资源
// 并返回对象中的内容
}
}
这个示例添加了默认方法load(Path)
,这个实现省略了实际实现(在方法内部),因为这并不重要,重要的是告诉你如何声明接口中的默认方法。
类可以通过显示实现接口默认方法来覆盖接口默认方法的实现,正如在类中实现接口中的其他方法一样,类中的任何方法实现都优先于接口默认方法实现。
接口与继承
Java接口可以从另外一个Java接口中继承,就像类可以从其他类中继承一样。你可以使用extends
来指定继承。下面是一个简单的接口继承示例:
public interface MySuperInterface {
public void saiHello();
}
public interface MySubInterface extends MySuperInterface {
public void sayGoodbye();
}
MySubInterface
接口继承于MySuperInterface
接口。这意味着,MySubInterface
将继承MySuperInterface
所有的属性和方法,实现接口的类必须实现MySubInterface
和MySuperInterface
接口中所有的方法。
在子接口中可以定义与父接口中具有相同签名(名称+参数)的方法。
与类的继承不同是,子接口可以继承多个父接口。列出所有你想要继承的接口名称,以逗号分隔。要实现继承多个父接口的子接口,实现接口的类必须实现子接口与所有父接口中的所有方法。
下面是一个继承多个父接口的示例:
public interface MySubInterface extends
SuperInterface1, SuperInterface2 {
public void sayItAll();
}
在实现多个接口时,多个父接口具有相同的签名时,没有规则来说明这种情况。
继承与默认方法
接口默认方法为接口继承增加了复杂性。虽然通常一个类可以实现多个接口,即使接口之间具有相同签名的方法,但是如果这些方法中的一个或多个是默认方法,若没有在类中覆盖此方法则会报错。换言之,如果两个接口包含相同的方法签名(名称+参数),而其中一个接口将此方法声明为默认方法,则类不能自动实现这个方法(可以在实现接口的类中显示覆盖该默认方法)。
如果一个接口继承自多个接口,并且其中一个或多个接口包含具有相同签名的方法,并且其中一个父接口将重叠的方法声明为默认方法,则情况和上面相同。
在上述两种情况下,编译器要求实现接口的类需要显示的实现导致问题的方法。这样的话,这个类的实现就没有问题了。类中的实现优先于任何默认实现。
接口与多态性
Java接口是实现多态性的一种手段。多态性是一个需要实践和思考才能掌握的概念。基本上,多态性意味着类(对象)的实例可以作为不同的类型去使用。在这里,类型指的是一个类或接口。
看看这个简单的类图:
在同一个应用程序中使用两个并行的类层次结构
上面的类模型代表不同类型的车辆和司机,使用属性和方法来描述它们,这就是类的责任——从现实生活中对这些实体进行建模。
现在假设你需要将这些对象存储在数据库中,并将他们序列化为XML、JSON或其他格式。你现在希望在轿车、卡车或车辆对象中使用相同的方法进行操作。这时候就需要实现store()
、serializeToXML()
、serializeToJSON()
这三个方法操作所有对象。
请先忘记上面这些,假如这些功能直接使用对象中的方法来实现的话,可能会导致混乱的类层次结构。这并不是你希望的方法。
在上面的图表中,你会把这三种方法放在哪里,以便在所有类上都可以访问。
解决这个问题的一种常见方法是为车辆和司机这两个类创建一个超类,它具有存储和序列化的方法。然而,这将导致概念上的混淆,类层次结构不再为车辆和司机建模,而且还会使车辆和司机与“存储和序列化机制”相关联。
更好的解决方案是创建一些存储和序列化方法有关的接口,并让类实现这些接口。下面是此类接口的示例:
public interface Storable {
// 存储
public void store();
}
public interface Serializable {
// 序列化
public void serializeToXML(Writer writer);
public void serializeToJSON(Writer writer);
}
当每个类需要实现这两个接口及其方法时,可以通过将对象强制转化为该接口类型的实例来访问这些接口的方法。你不需要确切的知道给定对象的类型是什么,只需要知道它实现的接口,下面是一个示例:
Car car = new Car();
Storable storable = (Storable) car;
storable.store();
Serializable serializable = (Serializable) car;
serializable.serializeToXML (new FileWriter("car.xml"));
serializable.serializeToJSON(new FileWriter("car.json"));
正如你希望的那样,在类中,接口提供了一个比继承更干净的实现跨服务功能的方法。