禁止码迷,布布扣,豌豆代理,码农教程,爱码网等第三方爬虫网站爬取!
接口
接口用于描述类应该做什么,而不是指定类具体要怎么实现,一个类中可以实现多个接口。在有些情况下,我们的需求符合这些接口的描述,就可以使用实现这个接口的类的对象。
例如 Arrays 类中的 sort 方法可以对对象数组进行排序,但是 sort 应该按照什么规则进行排序呢?这个时候就可以使用 Comparable 接口,这个接口能够用于描述某个类比较大小的规则。
public interface Comparable{
int compareTo(Object other);
}
任何实现 Comparable 接口的类都需要包含 compareTo 方法,该方法需要传入一个 Object 参数且返回一个整数。接口中的方法默认都是 public,因此声明的时候可以省略不写,不过在具体实现方法时还是要加上 “public”。接口绝对不能有实例字段,因为实例字段和方法的实现应该有实现接口的类实现。
Comparable 接口的 compareTo() 方法需要实现比较大小的任务,调用 “x.compareTo(y)” 的时候,若 x > y 时方法返回一个正数,若 x < y 时方法返回一个负数,相等就返回 0。例如希望使用 Arrays 类的 sort 方法对 Employee 对象数组排序,所以 Employee 类必须实现 Comparable 接口,实现的步骤为:
- 将类声明为要实现的接口;
- 定义接口内的所有方法。
将类声明为实现某种接口,需要用到 implements 关键字,定义时可以为泛型接口提供个参数。
public class Employee implements Comparable<Employee>{
public int compareTo(Employee other){
return Double.compare(salary, other.salary);
}
}
不过为什么不能在 Employee 类中实现 compareTo 方法,而必须实现 Comparable 接口呢?这是因为 Java 是一个强类型语言,调用方法时需要判断这个方法确实存在,如果是一个 Comparable 对象数组就可以保证有 compareTo 方法。
自定义接口
有的时候仅靠现有的接口是无法完成需求的,或者说希望得到某种接口实现自己的特殊需求,这时就需要自定义接口。interface 关键字可以用来声明一个接口,自定义接口的代码框架为:
[可见度] interface 接口名称 [extends 其他的接口名] {
// 声明变量
// 抽象方法
}
接口是隐式抽象的,且接口中每一个方法也是隐式抽象的,因此声明时不需要 abstract 关键字,同时接口中的方法都是公有的。下面自定义一个栈接口:
interface IntgerStack{
//如果 item 为 null,则不入栈直接返回 null;如果栈满,也返回 null;如果插入成功,返回 item
public Integer push(Integer item);
//出栈,如果为空,则返回 null
public Integer pop();
//获得栈顶元素,如果为空,则返回 null
public Integer peek();
//栈空判断,如果为空返回 true
public boolean empty();
//栈容量判断,返回栈中元素个数
public int size();
}
接口特性
接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
接口和类的不同之处有:
- 接口不能用于实例化对象。
- 接口没有构造方法。
- 接口中所有的方法必须是抽象方法。
- 接口不能包含成员变量,除了 static 和 final 变量。
- 接口支持多继承。
接口和继承的不同之处有:
- 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
- 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
默认方法
接口可以默认地提供一种实现方式,默认方法使用 default 关键字标注。
public interface Cokparable<T>{
default int compareTo(T other){
return 0;
}
}
默认方法的重要用途是实现接口演化,即你之前使用的接口若引入的新方法。若该方法不是默认方法,当你没有为新方法写上新代码的话就无法编译,而如果将新引入的方法实现为默认方法就可以实现兼容。
如果在一个接口中将一个方法定义为默认方法,有在超类或另一个接口中又定义了同样的方法,这种二义性要怎么解决?规则如下:
- 超类优先,如果超类提供了具体的方法,则同名同参数类型的默认方法会被忽略;
- 接口冲突,如果出现 2 个接口都有一个同名同参数的方法,则编译器返回一个错误,让程序员自己处理这个问题。
如果是一个类扩展了一个超类,同时也实现了一个接口,子类从超类和接口继承了相同的方法怎么办?此时会优先考虑超类的方法,接口的默认方法会被忽略。
- 不要让一个默认方法重新定义为 Object 类中的某个方法。
回调
回调是一种程序设计模式,该模式可以在指定的某个特定事件发生时,自动采取相应的动作。例如 Timer 类可以用于构造定时器,可以实现每隔一定的时间间隔完成系列操作。Java 中实现时可以向定时器传入某个类的对象,定时器自动调用这个对象的方法,而且类可以附带更多的信息。
定时器需要明确调用类的哪一个方法,因此可以通过 ActionListener 接口实现,每隔一定的时间间隔就自动调用 actionPerformed 方法。
public interface ActionListener{
void actionPerformed(ActionEvent event);
}
其中 ActionEvent 参数提供了事件的相关信息,例如发生这个时间的时间。例如:
class TimePrinter implements ActionListener{
public void actionPerformed(ActionEvent event){
System.out.println("At the tone, the time is " + Instant.ofEpochMilli(event.getWhen()));
Toolkit.getDefaultToolkit().beep();
}
}
使用这个方法时,就需要先构造这个类的对象,然后再传递给 Timer 的构造器并启动。
TimePrinter listener = new TimePrinter();
Timer timer = new Timer(1000, listener);
timer.start();
对象克隆
clone 方法
当一个包含对象引用的变量建立副本时,原变量和副本都是对同一个对象的引用,也就是说任何一个对象的改变都会影响另一个变量。
var original = new Employee("John Public", 50000);
Employee copy = original;
如果希望复制一个新对象,它的初始状态和旧对象相同,但是对新对象的修改不会影响旧对象,这就需要使用 clone 方法。
Employee copy = original.clone();
不过 clone 方法是 Object 类的 protected 方法,不能被直接调用,因此只有同类的对象可以克隆。
深拷贝、浅拷贝
对象克隆是逐个字段进行拷贝,若对象包含子对象的引用,拷贝字段就会得到相同子对象的另一个引用,此时原对象和克隆对象还是会共享一些信息。
默认的克隆是浅拷贝,浅拷贝没有克隆对象中引用的其他对象。如果原对象和浅克隆对象共享的子对象是不变的,则这种共享是安全的。但是通常情况下子对象都是可变的,因此必须重新定义 clone 方法建立一个深拷贝,以克隆所有子对象。
Cloneable 接口
对于每一个类,需要确定:
- 默认的 clone 方法是否满足需求;
- 是否可以在可变的子对象上调用 clone 来修补默认的 clone 方法;
- 是否不需要使用 clone。
如果确定默认的 clone 方法不符合需求,则需要实现 Cloneable 接口,并且用 public 重新定义新的 clone 方法。Cloneable 接口并没有指定 clone 方法,因为这个方法是从 Object 类继承的,该接口起到一个标记作用,指示这个类覆盖了 clone 方法。
即使是默认的浅拷贝,也需要实现 Cloneable 接口,将 clone 定义为 public 再来调用。
class Employee implements Cloneable{
public Employee clone() throws CloneNotSupportedException{
return (Employee) super.clone();
}
}
如果是深拷贝,就需要在 clone 方法中克隆对象的可变实例字段。
class Employee implements Cloneable{
private String name;
private double salary;
private Date hireDay;
public Employee clone() throws CloneNotSupportedException{
Employee cloned = (Employee) super.clone();
cloned.hireDay = (Date) hireDay.clone();
return cloned;
}
}
参考资料
菜鸟教程
《Java 核心技术 卷Ⅰ》,[美]Cay S.Horstmann 著,林琪 苏钰涵 等译,机械工业出版社