Java 面向对象:接口

禁止码迷,布布扣,豌豆代理,码农教程,爱码网等第三方爬虫网站爬取!

接口

接口用于描述类应该做什么,而不是指定类具体要怎么实现,一个类中可以实现多个接口。在有些情况下,我们的需求符合这些接口的描述,就可以使用实现这个接口的类的对象。
例如 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 接口,实现的步骤为:

  1. 将类声明为要实现的接口;
  2. 定义接口内的所有方法。

将类声明为实现某种接口,需要用到 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 修饰会报编译错误)。接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
接口和类的不同之处有:

  1. 接口不能用于实例化对象。
  2. 接口没有构造方法。
  3. 接口中所有的方法必须是抽象方法。
  4. 接口不能包含成员变量,除了 static 和 final 变量。
  5. 接口支持多继承。

接口和继承的不同之处有:

  1. 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
  2. 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
  3. 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
  4. 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

默认方法

接口可以默认地提供一种实现方式,默认方法使用 default 关键字标注。

public interface Cokparable<T>{
      default int compareTo(T other){
            return 0;
      }
}

默认方法的重要用途是实现接口演化,即你之前使用的接口若引入的新方法。若该方法不是默认方法,当你没有为新方法写上新代码的话就无法编译,而如果将新引入的方法实现为默认方法就可以实现兼容。
如果在一个接口中将一个方法定义为默认方法,有在超类或另一个接口中又定义了同样的方法,这种二义性要怎么解决?规则如下:

  1. 超类优先,如果超类提供了具体的方法,则同名同参数类型的默认方法会被忽略;
  2. 接口冲突,如果出现 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 接口

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

  1. 默认的 clone 方法是否满足需求;
  2. 是否可以在可变的子对象上调用 clone 来修补默认的 clone 方法;
  3. 是否不需要使用 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 著,林琪 苏钰涵 等译,机械工业出版社

posted @ 2020-08-11 08:42  乌漆WhiteMoon  阅读(33)  评论(0编辑  收藏