zoukankan      html  css  js  c++  java
  • 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 著,林琪 苏钰涵 等译,机械工业出版社

  • 相关阅读:
    使用C#中的DirectorySearcher来获得活动目录中的组织结构与用户等信息,并在展示成树形结构(附源代码)
    oracle的简单操作和要注意的地方
    lambda匿名函数
    Linux查看系统信息(版本、cpu、raid)
    chmod 777后,目录权限不可写解决方法
    linux /boot空间满了如何清理
    k3s
    IDEA项目编译参数Werror设置
    minicube 安装
    ubuntu安装docker
  • 原文地址:https://www.cnblogs.com/linfangnan/p/13425641.html
Copyright © 2011-2022 走看看