接口用来描述类应该做什么,而不指定它们具体应该如何做
接口的概念
Java程序设计语言中,接口不是类,而是对希望符合这个接口的类的一组需求,例如Arrays类中的sort承诺可以对对象数组进行排序,但是要求满足一个条件:对象所属的类必须实现Comparable接口
public interface Comparable
{
int compareTo(Object other);
}
接口中的所有方法都自动是public方法,因此,在接口中声明方法时,不必提供关键字public
可以将接口看成是没有实力字段的抽象类
为了让类实现一个接口,通常需要完成下面两个步骤:
- 将类声明为实现给定的接口
- 将接口中的所有方法提供定义
要将类声明为实现某个接口,需要使用关键字implements
class Employee implements Comparable;
在实现该接口时,实现的方法必须是public
例如,Arrays类中的方法sort方法要求必须Arrays中存储的类实现了Camparable接口中的compareTo方法,所以,要想在自定义类中实现利用Arrays.soert排序,就必须实现Comparable接口
public class Employee implements Comparable<Employee>{
......
public int compareTo(Employee other)
{
return Double.compare(salary,other.salary);
}
}
接口的属性
接口不是类,所以不能通过new运算符实例化一个接口,但是,接口可以被作为变量声明,而且该接口变量可以引用实现了这个接口的类对象
Comparable x;
x=new Employee(...);//Employee类已经实现了Compareable接口
接口是可以被扩展的,类似于类的继承,这里允许有多条接口链,从通用性较高的接口扩展到专用性较高的接口
public interface Moveable
{
void move(double x,double y);
}
public interface Powered entends Moveable
{
double milesPerGallon();
double SPEED_LIMIT=95;
}
这里注意,接口类中没有特殊声明作用域的方法总是被设置为public,而接口类中的变量总是被设置为public static final,类似于C语言中的宏定义,任何实现了带有字段变量的接口的类,都会自动继承这些变量
尽管每个类只能有一个父类,但是可以实现多个接口
接口与抽象类
为什么Java程序设计语言的设计者要如此麻烦的引入接口的概念,而不将接口类直接设计成一个抽象类呢
使用抽象类表示通用属性有一个严重的问题,就是每个类只能继承一个父类,假设Employee以及扩展了一个抽象类,那它就不能再扩展第二个类了
有些程序设计语言,比如C++,它允许一个类有多个父类,但Java不允许多重继承,其主要原因是因为多重继承会让语言变得复杂,并且语言的效率会降低
换句话说,接口可以提供多重继承的大多数好处,同时还能避免因为多重继承的复杂性和低效性
默认实现(default)
可以使用default修饰符为接口方法提供一个默认实现,在后续类实现这些接口时如果没有覆盖这些方法,就会采用这些方法的默认实现
在某些情况下,默认实现很有用
例如
在Collection接口中,isEmppty的功能是判断集合是否为空,这个方法不管在任何数据接口中,实现的具体过程都一样,所以,可以将isEmprty()作为默认实现
public interface Collection
{
int size();
default boolean isEmpty()
{
return size()==0;
}
}
除此之外,如果想在一个接口中添加一个新的方法,那么所有实现了这个接口的类都需要重新进行代码补充,这样对于代码的维护是很不利的,所以,可以将新编写的方法作为默认方法,这样就不需要对所有实现了该接口的类进行补充了
如果在一个接口中定义了一个方法为默认方法,而在父类或者另一个接口定义了同样的方法,会发什么情况
这种情况下,遵循以下规则
- 父类优先,如果父类提供了一个具体方法,同名且具有相同参数类型的默认方法会被忽略
- 接口冲突,如果在同时扩展的两个接口中定义了两个同名且参数类型相同的方法(不管是不是默认),那么,编译器会报告一个错误,让程序员来解决这个问题,程序员这时只需要在扩展这两个接口的类的的定义中重写这个方法即可
public interface Person
{
default String gerName(){...}
}
public interface Named
{
default String getName(){...}
}
public class Student implements Person,Named
{
public String getName
{
return Person.super.getName();
}
}
回调
回调是一种常见的程序设计模式,在这种模式中,可以指定某个特定的时间发生时应该采取的动作
例如
在Java.swing包中有一个Timer类,如果希望经过一定时间间隔就得到通知,Timer类就很有用,假如程序中有一个时钟,可以请求每秒通知一次,以便更新时钟的表盘,所以构造定时器时,需要设置一个时间间隔,并告诉定时器经过这个时间间隔时需要做些什么
你可以向定时器传入某个类的对象,然后,定时器调用这个对象的方法,由于这个对象可以携带一些附加的信息,传入一个对象比传入一个函数要灵活的多
当然,定时器要知道具体该调用哪一个方法,并要求传递的对象所属的类实现了ActionListener接口
class TimerPrinter implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
System.out.println("At the tone,the time is"=Instant.ofEpochMilli(event.getWhen()));
Toolkit.getDefaultToolkit().beep();
}
}
这里的参数ActionEvent,这个参数提供了事件的相关信息,例如,发生这个事件的时间
接下来,构造这个类的一个对象,并将它传递给Timer构造器
TimePrinter listener=new TimePrinter();
Timer t=new Timer(1000,listener);
Timer构造器的第一个参数是一个时间间隔(单位是毫秒),第二个参数是一个实现了ActionListener接口的监听对象
最后,启动定时器
t.start();
每隔一秒钟就会执行actionPerfirmed中的方法