General 的个人博客

你一定会喜欢那个因为喜欢她而发光的自己!

人间忽晚,山河已秋。

  menu
48 文章
0 浏览
ღゝ◡╹)ノ❤️

Interface 接口详解

简介

接口主要用来描述类具有哪些功能,并不给出每个功能的具体实现方式。一个类可以实现一个或多个接口,并在需要接口的地方,随时使用实现了响应接口的对象。

在 Java 程序设计语言中,接口不是类,而是对类的一组需求描述,这些类要遵循接口描述的统一格式进行定义。

接口中的所有方法自动属于 public,所以在接口中声明方法可以不用提供关键字 public 。

类实现接口,通常有两个步骤:

  1. 将类声明为实现指定的接口
  2. 对接口中的所有方法进行定义

要将类声明为实现某个接口,需要使用关键字 implements :

public interface Comparable{ int compareTo(Object other);}
class Employee implements Comparable{}

接口特性

接口不是类,尤其不能使用 new 运算符实例化一个接口。但是可以声明接口的变量,接口变量必须引用实现该接口的类对象。

X = new Comparable(...); //Error

Comparable x; // OK
x = new Employee(); // OK

与可以建立类的继承关系一样,接口也可以被扩展。这里允许存在多条从较高通用性的接口道较高专用型的忌口的链。

public interface Moveable{ void move(double x,double y);}
public interface Powerd extends Moveable{ double milesPerGallon();}

虽然在接口中不能包含实例域或静态方法,但可以包含常量。

public interface Powerd extends Moveable{ 
    double milesPerGallon();
    double SPEED_LIMIT = 95;
    }

与接口中的方法都自动地被设置为 public 一样,接口中的域将自动设为 public static final 。

接口与抽象类

使用抽象类表示通用属性存在一个问题 :每个类只能扩展一个类。但是每个类可以实现多个接口。

静态方法

在 Java SE 8 中,允许在接口中增加静态方法。理论上,没有任何理由认为这是不合适的,只是这有违于将接口最为抽象规范的初衷。

public interface Path{
    public static Path get(String first,String... more){
        return FileSystems.getDefault().getPath(first,more);
    }
    ...
}

默认方法

可以为接口方法提供一个默认实现。必须用 default 修饰符标记这样的方法。

public interface Comparable<T>{
    default int compareTo(T other){ 
        return 0; // 默认情况下,所有的数据都相等
    }
}

这样可能没有什么用,因为 Comparable 的每一个实现都要覆盖这个方法。但是在某些情况下,默认方法可能很有用。例如:

public interface Collection{
    int size();
    default boolean isEmpty(){
        return size() == 0;
    }
    ...
}

这样在实现 Collection 的程序员就不用操心实现 isEmpty 方法了。

默认方法的一个重要用法是 “接口演化”。例, Collection 接口作为 Java 的一部分已经很多年了,在 Java SE 8 中,为这个接口添加了一个 stream 方法。假设 stream 不是默认方法,那么引用 Collection 接口的类将不能编译,因为没有实现这个新方法。为接口增加一个非默认方法不能保证 “源代码兼容”。

不过,假设不重新编译这个类,而只是使用原先的一个包含这个类的 JAR 文件。这个类仍可以正常加载,尽管没有这个新方法。但是,如果程序在一个该类的实例上调用 stream 方法,就会出现一个 AbstractMethodError。

默认方法可以解决这些问题。如果没有重新编译而直接加载这个类,并在一个实例上调用 stream 方法,将调用 Collection stream 方法。

解决默认方法冲突

如果现在一个接口中将一个方法定义为默认方法,然后又在超类或者另一个接口中定义了同样的方法。会发生什么?Java 对应的规则比较简单:

  1. 超类优先。如果超类提供了一个具体方法,接口中同名并且有相同参数类型的默认方法会被忽略。
  2. 接口冲突。如果一个超接口提供了一个某人方法,另一个接口提供了一个同名而且参数类型相同的方法,必须覆盖这个方法来解决冲突。
public interface Named{
    default String getName(){
        return getClass().getName()+"_"+hashCode();
    }
}

Class Student implements Person,Named{
    ...
}

类会继承 Person 和 Named 接口提供的两个不一致的 getName 方法。并不是从中选择一个,Java 编译器会报告一个错误,让程序员来解决这个二义性。只需要在 Student 类中提供一个 getName 方法,在这个方法中,可以选择两个冲突方法中的一个。

Class Student implements Person,Named{
    public String getName(){ return Person.super.getName();}
    ...
}

接口示例

接口与回调

回调是一种常见的程序设计模式。在这种模式中,可以指出某个特定事件发生时应该采取的行动。

例如:程序中有一个时钟,每隔十秒钟报一次时。

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;
import javax.swing.*;

public class TimerTest {
    public static void main(String[] args) {
        ActionListener listener = new TimePrinter();

        Timer t = new Timer(10000, listener);
        t.start();
        JOptionPane.showMessageDialog(null, "Quit program?");
        System.exit(0);
    }
}

class TimePrinter implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("At the tone, the time is " + new Date());
        Toolkit.getDefaultToolkit().beep();
    }
}

对象克隆

Cloneable 接口指示一个类提供了一个安全的 clone 方法。clone 产生的对象是一个新对象,它的初始状态与源对象相同,但是之后他们各自会有自己不同的状态。

不过, clone 方法是 Object 的一个 protected 方法,这说明你的代码不能直接调用这个方法。只有 Employee 类可以克隆 Employee 对象。

默认的克隆操作是“浅拷贝”,并没有克隆对象中引用的的其他对象。如果源对象和浅克隆对象共享的子对象是不可变的,这种共享就是安全的。如果子对象属于一个不可变的类,如 String ,这种共享是安全的。或者在对象的声明周期中,子对象一只包含不变的常量,没有更改器方法会改变它,也没有方法会生成它的引用,这种情况下同样是安全的。

image-20201027204938433

不过,通常子对象都是可变的,必须重新定义 clone 方法来建立一个深拷贝,同时克隆所有子对象。

对于每一个类,需要确定:

  1. 默认的 clone 方法是否满足要求
  2. 是否可以在可变的子对象上调用 clone 来修补默认的 clone 方法
  3. 是否不改使用 clone

实际上第 3 个选项是默认选项。如果选择第 1 项或第 2 项,类必须:

  1. 实现 Cloneable 接口
  2. 重新定义 clone 方法,并指定 public 访问修饰符

Tips:

Object 类中的 clone 方法声明为 protected,子类只能调用受保护的 clone 方法来克隆自己的对象。必须重新定义 clone 为 public 才能允许所有方法克隆对象。

Cloneable 接口没有指定 clone 方法,这个方法是从 Object 类继承的。这个接口指示作为一个标记,指示类设计者了解克隆过程。标记接口不包含任何方法,它的唯一作用就是允许在类型查询中使用 instanceof。

即使 clone 的默认(浅拷贝)实现能满足要求,还是需要实现 Cloneable 接口,将 clone 重新定义为 public,再调用 super.clone()。

class Employee implements Cloneable{
    public Employee clone() throws CloneNotSupportedException{
        return (Employee) super.clone();
    }
    ...
}

与 Object.clone 提供的浅拷贝相比,这个 clone 方法并没有为它添加任何工嗯呢该。只是让这个方法是公有的。要建立深拷贝,还需要做很多工作,克隆对象中可变的实例域。

class Employee implements Cloneable{
    public Employee clone() throws CloneNotSupportedException{
        Employee cloned =  (Employee) super.clone();
        cloned.hireDay = (Date) hireDay.clone();
        return cloned;
    }
    ...
}

如果在一个对象上调用 clone,但这个对象的类并没有实现 Cloneable 接口,Object 类的 clone 方法就会抛出一个 CloneNotSupportedException。

捕捉这个异常是不是更好一些?这非常适合 final 类。否则,还是保留 throws 说明符。这样就允许子类在不支持克隆时选择抛出一个 CloneNotSupportedException。

要不要在自己的类中实现 clone 呢?如果你的客户端需要建立深拷贝,可能就需要实现这个方法。

Tips:

所有数组类型都有一个 public 的 clone 方法,而不是 protected。可以用这个方法建立一个新数据,包含原数组所有元素的副本。


标题:Interface 接口详解
作者:General
地址:https://ZZUGEneral.github.io/articles/2020/10/27/1603809416593.html
一个没有得到Java开发工作的测试员!