1. 概述
接口( interface) 技术, 主要用来描述类具有什么功能,而并不给出每个功能的具体实现。
接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。
接口的特点:
(1)一个类可以实现( implement) 一个或多个接口。Java中 每个类只能扩展于一个类。
(2)接口不是类,尤其不能使用 new 运算符实例化一个接口,却能声明接口的变量。
(3)接口变量必须引用实现了接口的类对象。
(4)接口中的所有方法自动地属于 public。 因此,在接口中声明方法时,不必提供关键字 public o。
(5)与接口中的方法都自动地被设置为 public —样,接口中的变量将被自动设为 public static final。
(6)可以用 instance 检查某对象是否实现了某个特定的接口: if (anObject instanceof Comparable) { . . . }。
(7)接口也可以被扩展,与可以建立类的继承关系一样。
(8)Java8中的新特性,为接口方法提供一个默认实现。 必须用 default 修饰符标记这样一个方法;如果实现类重写了该方法,就覆盖这个方法。接口中还可以有静态方法,可以通过接口名来访问静态方法。
2. 接口的使用
2.1 接口的简单使用
关键字 interface声明一个接口,声明一个接口,IStudy.java如下所示:
package interfaceStudy;
public interface IStudy {
public void study();
}
类使用implements 关键字去实现一个接口。
package interfaceStudy;
public class IStudyImpl implements IStudy {
public static void main(String[] args) {
IStudy iStudy = new IStudyImpl();
iStudy.study();
}
@Override
public void study() {
System.out.println("studying ...");
}
}
运行截图如下所示:
2.2 接口的继承
首先第一个接口,IStudy.java:
package interfaceStudy;
public interface IStudy{
public void learn();
public void study();
}
第二个接口,IStudent接口,继承IStudy接口。
package interfaceStudy;
public interface IStudent extends IStudy {
public void study();
public void play();
}
实现类需要重写IStudent接口中方法和IStudent接口继承IStudy接口的方法,如下三个方法,缺少会报错。
@Override
public void learn() {
System.out.println("learning ...");
}
@Override
public void study() {
System.out.println("studying ...");
}
@Override
public void play() {
System.out.println("playing ...");
}
完整测试代码如下所示:
package interfaceStudy;
public class IStudentImpl implements IStudent {
public static void main(String[] args) {
IStudent iStudent = new IStudentImpl();
iStudent.play();
iStudent.study();
iStudent.learn();
}
@Override
public void learn() {
System.out.println("learning ...");
}
@Override
public void study() {
System.out.println("studying ...");
}
@Override
public void play() {
System.out.println("playing ...");
}
}
运行截图如下所示:
2.3 实现多个接口
两个接口,IStudent 和 IStudy 。
IStudent 接口如下所示:
package interfaceStudy;
public interface IStudent {
public void learn();
public void play();
}
IStudy 接口如下所示:
package interfaceStudy;
public interface IStudy{
public void learn();
public void study();
}
完整测试代码,需要重写IStudent接口中方法和IStudy接口的方法,缺少会报错:
package interfaceStudy;
public class IStudentImpl implements IStudent, IStudy {
public static void main(String[] args) {
IStudent iStudent = new IStudentImpl();
iStudent.play();
iStudent.learn();
((IStudentImpl) iStudent).study();
IStudy iStudy = new IStudentImpl();
((IStudentImpl) iStudy).play();
iStudy.learn();
iStudy.study();
}
@Override
public void learn() {
System.out.println("learning ...");
}
@Override
public void study() {
System.out.println("studying ...");
}
@Override
public void play() {
System.out.println("playing ...");
}
}
运行截图如下所示:
3. Java8 中的接口新特性
3.1 接口中的默认方法
3.1.1 默认方法简单例子
使用默认方法可以用于解决接口的实现类都需要添加一个相同方法的问题,必须用 default 修饰符标记默认方法。
package interfaceStudy;
public interface IStudy{
default void study() {
System.out.println("studying ...");
}
}
测试代码:
package interfaceStudy;
public class IStudentImpl implements IStudy {
public static void main(String[] args) {
IStudy iStudy = new IStudentImpl();
iStudy.study();
}
}
运行截图:
3.1.2 实现类重写了默认方法
如果实现类重写了该方法,就覆盖接口中的这个方法。
测试代码如下:
package interfaceStudy;
public class IStudentImpl implements IStudy {
public static void main(String[] args) {
IStudy iStudy = new IStudentImpl();
iStudy.study();
}
public void study() {
System.out.println("studying ~~~");
}
}
运行截图:
3.1.3 接口继承中的默认方法
父接口中有一个默认方法:
package interfaceStudy;
public interface IStudy{
default void study() {
System.out.println("IStudy: studying ...");
}
}
子接口继承父接口,并重写该方法:
package interfaceStudy;
public interface IStudent extends IStudy{
default void study() {
System.out.println("IStudent : studying ...");
}
}
实现类如果调用 study 方法,使用的子接口的方法,测试代码如下所示:
package interfaceStudy;
public class IStudentImpl implements IStudent {
public static void main(String[] args) {
IStudent iStudent = new IStudentImpl();
iStudent.study();
}
}
运行截图:
3.2 接口中的静态方法
接口中的静态方法声明如下所示:
package interfaceStudy;
public interface IStudy{
static void study() {
System.out.println("studying ...");
}
}
通过接口名来访问静态方法:
package interfaceStudy;
public class IStudentImpl implements IStudy {
public static void main(String[] args) {
IStudy.study();
}
}
运行截图:
4. 接口回调
接口回调:把实现某一接口的类创建的实例,赋值给声明的接口变量,接口就可以调用该实例实现的接口方法。
举一个例子,一个 Mood 接口 :
package interfaceStudy;
public interface Mood {
public void say();
}
Mood 接口的两个实现类:
GoodMood.java:
package interfaceStudy;
public class GoodMood implements Mood {
@Override
public void say() {
System.out.println("GoodMood");
}
}
BadMood.java:
package interfaceStudy;
public class BadMood implements Mood {
@Override
public void say() {
System.out.println("BadMood");
}
}
测试类,实现Mood接口的类创建的实例,赋值给声明的Mood接口变量,接口就可以调用该实例实现的接口方法。
package interfaceStudy;
public class Test {
public void say(Mood mood) {
mood.say();
}
public static void main(String[] args) {
BadMood badMood = new BadMood();
GoodMood goodMood = new GoodMood();
Test test = new Test();
test.say(badMood);
test.say(goodMood);
}
}
运行截图:
5. Comparator接口
对一个对象数组排序,前提是这些对象是实现了 Comparable 接口的类的实例。
String 就实现了Comparable 接口:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
现在我们希望按长度递增的顺序对字符串进行排序,要处理这种情况,ArrayS.Sort 方法中, 可以通过一个数组和一个比较器 ( comparator ),作为参数, 比较器是实现了 Comparator 接口的类的实例,如下所示。
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a);
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
如下一个比较字符串长度的比较器:
class LengthComparator implements Comparator<String> {
public int compare(String first, String second) {
return first.length() - second.length();
}
}
测试代码:
package interfaceStudy;
import java.util.Arrays;
import java.util.Comparator;
class LengthComparator implements Comparator<String> {
public int compare(String first, String second) {
return first.length() - second.length();
}
}
public class Test {
public static void main(String[] args) {
String[] friends = {"Peter", "Paul", "Mary"};
Arrays.sort(friends, new LengthComparator());
for (String friend : friends) {
System.out.println(friend);
}
}
}
运行截图:
6. 接口入参保护,这种场景常见的是用作批量操作的接口
下列情形,需要进行参数校验:
1) 调用频次低的方法。
2) 执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,那得不偿失。
3) 需要极高稳定性和可用性的方法。
4) 对外提供的开放接口,不管是 RPC/API/HTTP 接口。
5) 敏感权限入口。
下列情形,不需要进行参数校验:
1) 极有可能被循环调用的方法。但在方法说明里必须注明外部参数检查要求。
2) 底层调用频度比较高的方法。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。一般 DAO 层与 Service 层都在同一个应用中,部署在同一台服务器中,所以 DAO 的参数校验,可以省略。
3) 被声明成 private 只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参数已经做过检查或者肯定不会有问题,此时可以不校验参数。
7. 接口定义的规范
接口:接口类中的方法和属性不要加任何修饰符号,默认为 public,属性默认 public static,保持代码的简洁 性,并加上有效的 Javadoc 注释。
尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。
接口和实现类:
(1)对于 Service 和 DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部的实现类用 Impl 的后缀与接口区别。 正例:CacheServiceImpl 实现 CacheService 接口。
(2)如果是形容能力的接口名称,取对应的形容词为接口名(通常是–able 的形式)。 正例:AbstractTranslator 实现 Translatable 接口。
参考文献:
- Java核心技术 卷1 基础知识(原书第10版)/(美)S.霍斯特曼(Cay S. Horstmann)著;周立新译. ——北京:机械工业出版社,2016.8