接口(interface)

学习《Java Core》ed.11 读书笔记

概念

Java语言中,接口不是一个类,而是对于要实现这个接口的类的一系列要求(requirements)。如果要实现这个接口,那么其中要求的所有方法都要实现。接口中所有的方法都自动改为public

接口可以定义常量(static final),但是不能定义实例成员变量。JDK8之后允许接口中存在实现的方法,这些方法也没办法访问实例成员变量(不存在)。在实现类中,接口的方法要声明为public,否则编译器会报错(方法提供权限限制--默认包内访问)

接口的必要性:如果一个服务需要接收一些实现了某个方法的对象,那么强制这些对象实现特定方法的机制就是接口

Comparable接口的例子。如果父类实现了这个接口(泛型),那么子类和父类比较的时候可能会出现问题。解决问题有两种方式:

  1. 如果子类的比较含义和父类不同,那么在比较子类对象的时候必须要进行类型判断
  2. 如果比较子类有一个共有的算法,那么可以在父类中声明compareTo方法并声明为final,在这个方法中把共有的逻辑写好(符合继承的意义)

注:Comparable接口有泛型实现,建议使用

注:整数比较使用Integer.compare()方法,浮点数使用Double.compare()

注:Comparable接口文档中需要compareTo()方法和equals()方法兼容。BigDecimal是一个例外

接口的属性

接口不是类,所以不能使用new关键字来实例化对象。但是可以声明接口变量,并且这个变量只能引用实现了这个接口的类的对象。可以使用instanceof来判断一个实例是否实现了某个接口

接口可以继承,这允许构建接口链,从范围最大的接口到最详细的接口

接口中不能定义实例变量,但是可以提供常量(public static final修饰)

根据阿里开发手册以及Java官方建议,不在接口中的方法上加任何其它的修饰符

接口中的常量在实现类中可以直接使用而不需要使用类名.constant的方式,但是这种方式和接口的意义不符,不建议使用(这种情况用常量类就好了)

类可以实现多个接口,这样使得类的行为的实现灵活度很高(没有多继承)

接口和抽象类

为什么不用抽象类而用接口,其原因还在于Java的单继承机制,如果用抽象类来表示一些多种类型泛用的方法,那么这些继承的子类就没办法再继承别的父类。接口的实现避免Java变成C++这种复杂的语言,也避免了Eiffel的低效率多继承实现

静态和私有方法

JDK8之后,在接口中可以添加静态方法。这在技术原因上没有不能实现的理由,只是简单地认为它违背了接口是描述抽象行为规范的意义。这里的意义是,当你实现自己的接口时,没必要再另外写一个类来实现一些工具方法,可以直接在接口中定义好。例如标准库中的(Paths类和Path接口)

JDK9之后,接口中可以定义private方法,这些方法可以是static的,主要作为接口中其它方法的辅助方法

Default方法

可以在接口中提供一些方法的默认实现,通过在方法签名的开头带上default修饰符

方法的默认实现在那些实现类必须要实现的方法上意义不大,但是在某些场景是有意义的,以Iterator为例

interface Iterator {
      boolean hasNext();
      E next();
      // 默认实现
      default remove() {throw new UnSurpportedOperationException();}
}

一般我们实现一个迭代器,如果让它是只读的,那么我们可以忽略这个remove方法

默认方法中是可以调用接口中其他的方法的

使用接口最重要的场景是interface evolution(接口进化),很久之前一个实现类实现了某个接口,如果在之后某个版本这个接口新添的方法不是默认方法,那么实现类将无法通过编译。添加非默认方法到接口的方式是源码不兼容的。如果不重新编译这个类,而使用包含这个类的老的jar包,那么没有问题(给接口中添加方法是binary compatible),但是如果调用这个类的实例的新接口方法会报错。默认方法将上面两种情况都解决了,无论是否重新编译那个实现类,使用实现类的实例调用新方法都不会报错

解决默认方法冲突

如果在接口中和父类中有相同签名的方法存在,那么Java中解决冲突的方式是:

  1. 父类和接口中存在相同签名的方法,子类继承父类并实现接口,则接口中的方法被忽略
  2. 两个接口中存在相同签名的方法(default),编译器会报错,即子类必须重新实现这个方法

重新实现方法时可以通过InterfaceName.super.methodName()来调用任意接口的默认实现

接口和回调

回调(callback)是一种常用的编程模式,即设置一个执行器,无论何时一个特殊事件发生这个执行器代码就开始执行

举例:在程序强制退出前,每过1s,控制台就打印一下当前的时间。Java中可以使用javax.swing包中的Timer对象来实现,只要给它的构造器中第二个参数传递一个实现了ActionListener接口的对象即可,代码如下:

Timer timer = new Timer(1000, event -> {
      System.out.println("Current time is : " + Instant.ofEpochMilli(event.getWhen()));
      Toolkit.getDefaultToolkit().beep();
      });
timer.start();

上面代码中的Timer会新开一个线程,所以需要把主线程阻塞这个定时器才会持续运行,否则主线程会很快结束,定时器也不会打印任何信息

Comparator接口

Arrays.sort(T[])方法中,因为String实现了Comparator接口(按字符字典顺序排序),如果我们希望用别的方式排序,在没办法改动String类的情况下,可以使用Arrays.sort(T[], Comparator)方法,即自己写一个实现了Comparator接口的类

posted on 2020-11-14 17:26  老鼠不上树  阅读(180)  评论(0)    收藏  举报