接口
接口概念
接口不提供实例字段和实现,但可以声明变量;接口是对希望符合这个接口的类的一组需求。
如果要调用Arrays类中的sort方法,那么该类需要满足以下条件:
interface Comparable<T> {
int compareTo(T other);
}
class Employee implements Comparable<Employee> { // 泛型
... // method, fields
public int compareTo(Employee other) {
// 用该对象和other比较。小于返回负数,大于返回整数,相等返回0
return Double.compare(salary, other.salary);
}
public static void main(String[] args) {
var staff = new Employee[3];
...
Arrays.sort(staff); // 排序
...
}
实现接口的原因及其解释
要让staff使用sort,必须让Employee类实现compareTo方法,因为要向sort方法提供对象的比较方式。因为Java是强类型的语言,在调用方法的时候,编译器要能检查这个方法确实存在;staff是一个Employee数组,而只有Employee实现了comparable接口,才可以确定有compareTo方法,因为每个实现Comparable接口的类都必须提供compareTo的定义,所以该方法存在,才可以调用。
接口的属性
接口变量必须引用 实现了这个接口的类对象:
Comparable x = new Employee(...);
// 使用instanceof检查对象引用是否实现了接口
if (x instanceof Comparable) {...}
接口不能包含实例字段,但可以包含常量:
interface Moveable {
void move(double x, double y);
}
interface Powered extends Moveable {
double milesPerGallon();
double SPEED_LIMIT = 95; // 接口常量都是public static final
}
默认方法
可以为接口方法提供一个默认实现:
interface Comparable<T> {
default int compareTo(T other) { return 0;}
}
用法:
- 比如Iterator接口,在传入的迭代器是只读无法修改的情况下,那么Iterator接口内的修改操作(方法)如果是default的,就不用再去实现修改操作的方法
interface Iterator<E> {
boolean hasNext();
E next();
default void remove() { throw new UnsupportedOperationException("remove");}
...
}
- 默认方法可以调用其他方法
interface Collection {
int size(); // abstract method
default boolean isEmpty() { return size == 0;} // 就不用操心实现isEmpty方法
- 接口演化
当以前你写的类实现一个接口,现在接口又新增了新的方法导致你的类现在没办法编译(因为新的方法没有在你以前写的类里面实现),这个时候如果设新的方法为default,就解决问题了。
解决默认方法冲突
二义性:已有一个接口将一个方法定位default,然后又有父类或另一个接口中定义同样的方法
- 父类优先:如果父类提供了具体方法,那签名相同的默认方法会被忽略;
public abstract Person {
public String character();
public String getName() { return "";} // got implementation!
}
interface NickName {
default String getName() { return "little" + "";} // ignored!
}
class Student extends Person implements NickName {...}
- 接口冲突:如果一个接口提供了一个默认方法,另一个接口提供签名相同(default or not)的方法,必须覆盖掉这个方法来解决冲突。Student类会继承Person和Named接口提供的两个不一致的getName,只要在Student类中实现掉其中一个接口的getName方法即可。不管Person和Named两个类中的相同方法是否是默认,只要至少有一个接口提供了一个实现,就要解决冲突
interface Person {
default String getName() { return "";}
}
interface Named {
default String getName() { return getClass().getName() + "_" + hashCode();}
}
interface NickName {
String getName(); // no default
}
class Student implements Person, Named, NickName { // solution
public String getName() { return Person.super.getName();}
// or Named.super.getName() or NickName.super.getName()
}
设计模式:回调(callback)
一种设计模式:可以指定某个特定事件发生时应该采取的动作
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Instant;
import static toolDIY.Print.*;
public class TimerTest {
public static void main(String[] args) {
var listener = new TimePrinter(); // 声明一个监听器对象
// 构造一个定时器隔一定时间调用监听器对象
var timer = new Timer(1000, listener);
// lambda表达式:
var timer = new Timer(1000, event ->
pln("The time is " + new Date()));
timer.start();
// 在swing包里的方法,显示对话框
JOptionPane.showMessageDialog(null, "Quit program?");
System.exit(0);
}
}
class TimePrinter implements ActionListener { // 实现监听接口
public void actionPerformed(ActionEvent event) {
// 获取事件发生时的时间, 返回1970-1-1以来的毫秒数传入Instant.ofEpochMilli
println("At the tone, the time is "
+ Instant.ofEpochMilli(event.getWhen()));
// awt的工具箱包含有关GUI环境的信息,awt是创建用户界面和绘制图形的包
Toolkit.getDefaultToolkit().beep();
}
}
Comparator接口
Arrays.sort提供第二个比较版本,一个数组和一个比较器作为参数,比较器实现Comparator接口:
import java.util.Arrays;
import java.util.Comparator;
import static toolDIY.Print.*;
public class Test implements Comparator<String> { // 实现Comparator接口
public int compare(String first, String second) {
return first.length() - second.length();
}
public static void main(String[] args) {
String[] friend = {"Peter", "Mike", "Amy"};
Arrays.sort(friend, new Test()); // 排序
// lambda 表达式:
Arrays.sort(friend, (first, second) ->
first.length() - second.length());
println(Arrays.toString(friend));
}
}
对象克隆
有原对象a,新对象b;如果要让b初始状态和a相同,但之后它们各自会有自己不同的状态:
var original = new Employee(...);
Employee copy = original.clone();
copy.raiseSalary(); // original unchanged
但是如果对象包含子对象引用,拷贝字段就会得到相同子对象的另一个引用,这样一来,原对象和克隆对象仍然会共享一些信息;默认的克隆操作是“浅拷贝”,没有克隆对象中引用的其他对象。要实现Cloneable接口,重新定义clone方法,指定public访问修饰符。Cloneable接口是标记接口(tagging interface),不包含任何方法,唯一的作用就是允许使用instanceof。
深拷贝的一个例子:
class Employee implements Cloneable {
private String name; // 基本类型是不可变的,默认拷贝不影响
private Double salary;
private Date hireDay; // Date是可变的,localDate是不可变的
public Employee clone() throws CloneNotSupportedException {
// call Object.clone()
Employee cloned = (Employee) super.clone();
// 深拷贝,拷贝可变字段
cloned.hireDay = (Date) hireDay.clone();
return cloned;
}
}
所有数组类型都有一个公共的clone方法,可以用它建立一个新数组,且新数组状态不影响原数组状态:
public class Test {
public static void main(String[] args) {
int[] original = {2, 3, 4, 6, 7};
int[] copy = original.clone();
copy[3] = 5;
println(Arrays.toString(original)); // [2, 3, 4, 6, 7]
println(Arrays.toString(copy)); // [2, 3, 4, 5, 7]
}
}