第六章——接口、lambda表达式与内部类(一)

本文深入讲解Java接口的概念、特性和应用场景,包括接口与抽象类的区别、默认方法和静态方法的使用,以及接口在实际编程中的应用案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1、接口

接口概念

接口的特性

接口与抽象类

静态方法

默认方法

解决默认方法冲突

2、接口示例

接口与回调

Comparator接口

对象克隆

 


 

前五章为Java的全部基本知识。本章讲开始介绍几种常用的高级技术。

接口(interface)技术,主要用来描述类具有什么功能,而并不给出每个功能的具体实现。一个类可以实现(implement)一个或多个接口,并在需要的地方,可以随时使用实现了相应接口的对象

lambda表达式是一种表示可以在将来某个时间点执行的代码块的简洁方法(一个表示可执行代码块的简洁方法)。通过使用lambda表达式,可以用一种精巧而简洁的方式表示使用回调或变量行为的代码(用一种精巧的方式表示代码)。

内部类(inner class)机制,内部类包含在另一个类中,该内部类中的方法可以访问其外部类的域。内部类技术主要用于设计具有相互协作关系的类集合。

代理(proxy)是一种实现任何接口的对象。它可以用来构建系统级别的工具。

1、接口

接口概念

接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。

任何实现某一接口的类都需要包含该接口中的方法。

接口中的所有方法自动地属于public。因此,在接口中声明方法时,不必提供关键字public,但在接口的调用类中需要public。除此之外,接口的所有域被自动设为public static final。

接口中可以包含多个方法,还可以定义常量。然而,接口绝不能包含实例域。接口没有实例。提供实例域和实现方法的应该由实现该接口的那个类来完成。因此,接口可以近似看成是没有实例域的抽象类。

为了让类实现一个接口,需要以下步骤:

  1. 将类声明为实现给定的接口。通过关键字implements
  2. 对接口中的所有方法进行定义

示例:若想使用Arrays类中的sort方法进行排序,需要实现接口Comparable,其中只有一个方法compareTo();

package com.company;

import java.util.Arrays;

/**
 * This program demonstrates the use of the Comparable interface
 */
public class Main {
    public static void main(String[] args){
        Employee[] staff = new Employee[3];

        staff[0] = new Employee("Harry",35000);
        staff[1] = new Employee("Carl",45000);
        staff[2] = new Employee("Tony",38000);

        Arrays.sort(staff);

        //print out all employees
        for(Employee e : staff){
            System.out.println("name:"+e.getName()+" salary:"+e.getSalary());
        }
    }
}

class Employee implements Comparable<Employee> {
    private String name;
    private double salary;

    public Employee(String name,double salary){
        this.name = name;
        this.salary = salary;
    }

    public String getName(){
        return this.name;
    }

    public double getSalary(){
        return this.salary;
    }

    public void raiseSalary(double byPercent){
        double raise = salary * byPercent / 100;
        salary += raise;
    }

    /**
     * Compares employees by salary
     * @param other another Employee object
     */
    @Override
    public int compareTo(Employee o) {
        return Double.compare(salary,o.salary); /*if x<y, return -1; x=y,return 0;                                   x>y, return 1;*/
    }
}
//result:

name:Harry salary:35000.0
name:Tony salary:38000.0
name:Carl salary:45000.0

Process finished with exit code 0

compareTo方法为sort方法提供对象的比较方式。但为什么不可以直接在Employee中定义一个compareTo方法,而要实现Comparable接口呢?主要原因是Java是一种强类型(strongly typed)语言。

强类型(strongly typed)语言也称为强类型定义语言。是一种总是强制类型定义的语言,要求变量的使用要严格符合定义,所有变量都必须先定义后使用。

sort方法种可能包含以下代码:

...
if a[I].compareTo(a[j]) > 0)
{
    // rearrange a[I] and a[j];
    ...
}
...

这就传入sort方法的a[i]一定要有compareTo方法。若a为Comparable对象的数组,就可以确保拥有compareTo方法

接口的特性

接口不是类,不能使用new实例化一个接口

但接口可以声明接口的变量:Comparable c;

接口变量只能引用实现了接口的类对象:c = new Employee(. . .);

可以像检查一个对象是否属于某个类一样,使用instanceof检查一个对象是否实现了一个接口:

if (anObject instanceof Conparable)
{
    ...
}

类似类的继承,接口也可以被扩展。

public interface Moveable{
    
    void move(double x, double y);

}

public interface Powered extends Moveable{

    double milesPerGallon();
    double SPEED_LIMIT = 95;

}

注意:在接口中不能有实例域,但可以有常量。

一个类还能有一个超类,但可以实现多个接口。

class Employee implements Cloneable,Comparable

接口与抽象类

前文讲到,接口可以近似看作没有实例域的抽象类,因为不需要定义方法的实现过程

虽然接口可以以抽象类的过程近似实现,但是存在以下的问题:一个类只能有一个超类。

在C++中允许继承多个类,此特性称为:多重继承(multiple inheritance),而Java并不支持,因为多继承会导致语言的复杂性和降低运行效率。而接口可以提供多重继承的大多数好处,并避免了复杂性和低效性。

静态方法

在SE 8中允许在接口中增加静态方法。这种做法合法但是有违将接口作为抽象规范的初衷。

默认方法

为接口定义默认方法:必须使用default修饰符

public interface Comparable<T>{

    default int compareTo(T other){
        return 0;
    }
}

接口中的默认方法不需要被实现它的类定义,其他所有方法必须在实现它的类中定义。

默认方法可以让程序员只关注他们所需,而不用考虑将所有方法定义,在他们只需要接口中的部分方法时。

除此之外,默认方法的另一个用法是接口演化(interface evolution)。若在Java更新时,向某些接口中添加了新方法,则原先实现这些接口的类将编译不通过,因为都没有定义添加的新方法,除非新方法在添加时为默认方法,这样,对于旧版本的代码也可以正常编译,增加了代码版本的兼容性。

解决默认方法冲突

1、超类优先。如果超类提供了一个具体方法,同名而且有相同参数类型的默认方法会被忽略。

2、接口冲突。如果一个超接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型相同的方法,必须覆盖这个方法来解决冲突。

若一个类实现了两个包含同名同参数方法的接口,需要程序员在代码中提供一个同名同参数方法,在这个方法中,可以选择冲突的方法中的一个。

class Main implements interface1,interface2
{
    public String functionInCommonName(){
        return interface1.super.functionInCommonName();
    }
}

3、“类优先”规则。一个类继承的超类和实现的接口包含相同方法时,只会考虑超类的方法。

 

2、接口示例

接口与回调

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

在java.swing包中有一个Timer类,可以使用它在到达给定的时间间隔时发出通告。如何告知定时器Timer要执行什么呢?在Java中,它将一个对西那个传递给定时器,然后,定时器调用这个对象的方法。当然,定时器需要知道具体执行那一个方法,并要求传递的对象所属的类实现了ActionListener接口(java.awt.event包中)

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.Timer;
//消除了javax.swing.Timer与javax.util.TImer之间的二义性。

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

        //construct a timer that calls the listener
        //once every 5 seconds
        Timer t = new Timer(5000, listener); //Timer构造器有两个参数,第一个是时间间隔,第二个是监听器对象
        t.start(); //启动定时器
        
        JOptionPane.showMessageDialog(null, "Quit program?");
        System.exit();
    }
}

class TimePrinter implements ActionListener{
    @Override
    public void actionPerformed(ActionEvent event){
        System.out.println("The time is: " + new Date());
        Toolkit.getDefaultToolkit().beep();
    }
}

Comparator接口

在前文中,我们已经知道通过实现Comparable接口进行自定义类对象的数组排序,一些类已经实现了该接口,可以直接使用sort进行数组排序,例如String类、Integer类等。

现在假设我们不想按字典顺序给数组排序,我们希望按字符串长度进行排序,要处理这种情况,就需要使用Arrays.sort()的第二种形式,有两个参数:对象数组和比较器(comparator)对象

package com.company;

import java.util.Arrays;
import java.util.Comparator;

/**
 * This program demonstrates the use of the ActionListener interface
 */
public class Main {
    public static void main(String[] args) {
        String[] friends = {"Amy","Peter","John","Tifany","Jonathan"};
        Arrays.sort(friends, new LengthComparator());

        for(String s : friends){
            System.out.println(s);
        }
    }
}

class LengthComparator implements Comparator<String>{
    @Override
    public int compare(String first,String second){
        return first.length() - second.length();
    }
}

执行结果:

Amy
John
Peter
Tifany
Jonathan

Process finished with exit code 0

若将first.length() - second.length()更改为:second.length() - first.length(),执行结果如下:

Jonathan
Tifany
Peter
John
Amy

Process finished with exit code 0

在进行数组排序时,Arrays.sort()有两种形式:

1、Arrays.sort (byte []):若该对象数组中的对象类实现了Comparable<TYPE>接口,则可以直接使用该方法进行排序,若为自定义类实例化的对象,需要自行实现Comparable<ClassName>接口,并定义compareTo方法。该方法会根据字典顺序进行排序。

2、Arrays.sort (byte [], Comparator):第二个参数为比较器实例,该实例的类需要实现了Comparator接口,然后在具体完成比较时,需要建立一个实例

Comparable和Comparator实现的异同

1、两者都是用于数组比较的接口

2、方法调用对象不同。Comparable接口的compareTo方法是通过该类的对象实例进行调用的,但Comparator接口的compare方法要在比较器对象上调用,而不是在类对象本身上调用。

Comparator<String> cmp = new LengthComparator();
if(cmp.compare(words[I], words[j] > 0). . .

//而Comparable的方法调用是:
words[I].compareTo(words[j]);

对象克隆

在建立一个对象的副本的时候,会发生什么?

Employee original = new Employee(. . .);
Employee copy = original;
copy.raiseSalary(10); //oops, also change original

两者为同一个引用,改变任意一个会相互影响。

若想要只拷贝初始状态,后面的改变互不影响,则需要使用到克隆。

Employee copy = original.clone();
copy.raiseSalary() //OK! original unchanged

clone()方法是Object类的一个protected方法,虽然按道理其子类可以访问该方法,但受保护的规则比较微妙,子类只能调用受保护的clone方法克隆它自己的对象。必须重新定义clone为public才能允许所有方法克隆对象。

在普通克隆的过程中,若克隆的对象中还包含子对象,则拷贝域就会得到相同子对象的同一个引用,这样,原对象和克隆对象还是有部分信息共享,这是浅克隆。通常子对象是可变的,所以必须重新定义clone方法来建立一个深克隆,同时克隆所有子对象。

对于一个类,需要确定:

1、默认的clone方法是否满足要求,有无子对象,子对象是否可变,子对象是否包含不变的常量活没有更改器方法会改变它

2、是否可以在可变的子对象上调用clone来修补默认的clone方法

3、是否不该使用clone。

第三项为默认选项。若选择第一/第二项,类必须:

1、实现Cloneable接口

2、重新定义clone方法,并制定public访问修饰符

此处的Cloneable接口域接口的正常使用没有关系。具体来说,它没有clone方法,该方法是从Object继承来的。这个接口只是作为一个标记,只是类设计者了解克隆的过程,并且坚定的需要进行类克隆。若没有实现该接口,就会生成一个受查异常。

Cloneable接口是Java提供的一组标记接口(tagging interface)的一个。这些接口的通常用途是确保一个类实现了一个或一组特定的方法。标记接口不包含任何方法;他唯一的作用就是允许在类型查询中使用instanceof

if (obj instanceof Cloneable)

clone方法返回的Object,现在可以显示地强制转换为合适的类类型

以下为代码示例:

package com.company;

import java.util.Date;
import java.util.GregorianCalendar;

public class CloneTest {
    public static void main(String[] args){
        //在一个对象上调用clone方法,因为不确定其类是否有实现Cloneable接口,所以需要声明/捕获异常。
        //声明如下:throws CloneNotSupportedException;
        //或捕获如下:
        try{
            Employee original = new Employee("Henry",35000);
            original.setHireDay(2000,1,1);

            Employee copy = original.clone();
            copy.setHireDay(2002,12,31);
            copy.raiseSalary(10);

            System.out.println("original:" + original);
            System.out.println("copy:" + copy);
        }
        catch(CloneNotSupportedException e){
            e.printStackTrace();
        }
    }
}

class Employee implements Cloneable {
    private String name;
    private double salary;
    private Date hireDay;

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
        this.hireDay = new Date();
    }

    /**
     * 即使默认clone方法可以满足要求,还是需要实现Cloneable接口,将clone重新定义为public,再调用super.clone()
     */
    @Override
    public Employee clone() throws CloneNotSupportedException {
        //call Object.clone()
        Employee cloned = (Employee) super.clone(); //返回的是Object,需要显式得声明对象类型。

        //clone mutable fields
        cloned.hireDay = (Date) hireDay.clone(); //克隆对象的可变实例域

        return cloned;
    }

    public void setHireDay(int year, int month, int day) {
        Date newHireDay = new GregorianCalendar(year, month - 1, day).getTime();
        //Example of instance field mutation
        hireDay.setTime(newHireDay.getTime());
    }

    public void raiseSalary(double byPercent) {
        double raise = salary * byPercent / 100;
        salary += raise;
    }

    @Override
    public String toString() {
        return "Employee[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]";
    }
}

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JonathanRJt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值