接口
1.1接口概念
在java中,接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。
“如果类遵从某个特定接口,那么就履行这个服务”。
例子:Arrays类中的sort方法承诺可以对对象数组排序,但要求满足:对象所属的类必须实现了Comparable接口。
下面是Comparable接口的代码:
public interface Comparable
{
int compareTo(Object other);
}
任何实现Comparable接口的类都需要包含compareTo方法,且参数是一个Object对象,返回一个整形数值。
注:在Java SE 5.0中,Comparable接口已经改进为泛型类型。
public interface Comparable<T>
{
int compareTo(T other); //参数拥有T类型
}
例如,在实现Comparable接口的类中,必须提供下列方法
int compareTo(Employee other);
接口中所有方法为public.因此在接口中声明方法时不必提供关键字public
此外,在调用x.compareTo(y)时,这个方法必须确实比较两个对象的内容,返回比较的结果。当x<y,返回一个负数,当x>y时,返回整数;否则返回0.
一个接口中可以包含多个方法。还可以定义常量。
接口中绝对不能含有实例域
提供实例域和方法实现的任务应该由实现接口的那个类完成。 所以,将接口看成是没有实例域的抽象类。
假设希望使用Arrays类的sort方法对Employee对象数组排序,Employee类就必须实现Comparable接口。
类实现接口的步骤:
- 将类声明为实现给定的接口。
- 对接口中的所有方法进行定义。
要将类声明为实现某个接口,需要使用关键字 implements:
class Employee implements Comparable
这里的Employee类需要提供compareTo方法。根据雇员的薪水进行比较的compareTo方法的实现:
public int compareTo(Object otheObject)
{
Employee other=(Employee) otherObject;
return Double.compare(salary,other.salary);
}
在这里,使用了静态Double.compare方法,如果第一个参数小于第二个参数返回一个负值,相等返回0,否则返回正值。
在接口声明中不必讲compareTo方法声明为public,所有都是public;
在实现接口时,必须把方法声明为public;
改进,可以为泛型Comparable接口提供一个类型参数。
class Employee implements Comparable<Employee>
{
public int compareTo(Employee other)
{
return Double.compare(salary,other.salary);
}
....
}
所以要让一个类使用排序服务必须让它实现compareTo方法。但是为什么不能再Employee类直接提供一个compareTo方法,而必须实现Comparable接口呢?
原因是java是一种强类型(strongly typed)语言。在调用方法的时候,编译器将会检查这个方法是否存在。在sort方法中可能存在下面语句:
if (a[i].compareTo(a[j])>0)
{
//rearrange a[i] and a[j]
...
}
为此编译器必须确认a[i]一定有compareTo方法。如果a是一个Comparable对象的数组,就可以确保拥有compareTo方法,因为每个实现Comparable接口的类都必须提供这个方法的定义。
下面给出了对一个Employee类实例数组进行排序的完整代码和运行结果,用于对一个员工数组的排序。
下面是Employee类定义:
package interfaces;
public 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 name;
}
public double getSalary()
{
return salary;
}
public void raiseSalary(double byPercent)
{
double raise=salary*byPercent/100;
salary+=raise;
}
public int compareTo(Employee other)
{
return Double.compare(salary, other.salary);
}
}
运行结果:
下面是几个常用的API
API java.lang.Comparable<T> 1.0
int compareTo(T other)
用这个对象与other进行比较。如果这个对象小于other则返回负值;如果相等返回0,否则返回正值。
API java.util.Arrays 1.2
static void sort(Object[] a)
使用mergesort算法对数组a中的元素进行排序。要求数组中的元素必须属于实现了Comparable接口的类,并且元素之间必须是可以比较的。
API java.lang.Interger 1.0
static int compare(int x,int y) 7
如果x<y则返回一个负值,相等返回0,否则返回正值。
API java.lang.Double 1.0
static int compare(double x,double y) 1.4
如果x<y则返回一个负值,相等返回0,否则返回正值。
1.2 接口的特性
接口不是类,不能用new运算符实例化一个接口:
x=new Comparable(...); //error
但是能声明接口的变量:
Comparable x; //ok
接口变量必须引用实现了接口的类对象:
x=new Employee(...); //在Employee类满足Comparable接口的时候是可以的
也可以使用instance检查一个对象是否实现了某个特定接口:
if(anObject instanceof Comparable) {...}
与类的继承一样,接口也可以被扩展。允许存在多条从具有较高通用性的接口到较高专用性的接口的链。 例如,假设有一个Moveable的接口:
public interface Moveable
{
void move(double x,double y);
}
可以为它基础扩展一个叫做Powered的接口:
public interface Powered extends Moveable
{
double milesPerGallon();
}
虽然接口中不能包含实例域或静态方法,但可以包含常量,例如:
public interface Powered extends Moveable
{
double milesPerGallon();
double SPEED_LIMIT=95; //一个公共静态常量
}
尽管每个类只能用于一个超类,但是可以实现多个接口。
例如,内置接口Cloneable。如果某个类实现了Cloneable接口,Object类中的clone方法就可创建类对象的一个拷贝。如果希望自己设计的类拥有克隆和比较能力,只要实现这两个接口就可以了。使用逗号将实现的各个接口分隔。
class Employee implements Cloneable,Comparable
1.3 接口与抽象类
为什么Java程序设计语言要不辞辛苦地引入接口概念?而不是将Comparable 直接设计成下面的抽象类?
abstract class Comparable //why not?
{
public abstract int compareTo(Object other);
}
然后,Employee类再直接扩展这个抽象类,并提供compareTo方法的实现:
class Employee extends Comparable //why not?
{
public int compareTo(Object other){.....}
}
假设Employee类已经扩展于一个类,例如Person,它就不能再像下面这样扩展第二个类了:
class Employee extends Person,Comparable //error
但是每个类可以像下面这样实现多个接口:
class Employee extends Person implements Comparable //ok
有些程序设计语言允许一个类有多个超类,例如C++。我们将此特性称为多重继承(multiple inheritance)。而Java的设计者选择了不支持多继承,原因是多继承会让语言本身变得复杂(C++),效率也会降低。
实际上,接口可以提供多重继承的大多数好处,同时还能避免多重继承的复杂性和低效性。
1.4 静态方法
在Java SE 8中,允许在接口中增加静态方法。
目前为止,通常的做法都是将静态方法放在伴随类中。在标准库中,你会看到成对出现的接口和实用工具类,如Collection/Collections或者Path/Paths。
如Paths类,其中只包含两个工厂方法。可以由一个字符串序列构造一个文件或目录的路径,如Paths.get(“jdk1.8.0”,“jre”,“bin”).在java SE 8中,可以为Path接口增加以下方法:
public interface Path
{
public static Path get(String first,String...more)
{
return FileSystems.getDefault().getPath(first,more);
}
...
}
这样一来,Paths类就不再是必要的了。
1.5 默认方法
可以为接口方法提供一个默认实现。必须用default修饰符标记。
public interface Comparable<T>
{
default int compareTo(T other){ return 0;}
//通过default,所以元素都是相同的
}
实际上这样没有太大用处,因为Comparable的每一个实例实现都要覆盖这个方法。不过有些情况下,默认方法可能很有用。例如,如果希望在发生鼠标点击事件时得到通知,就要实现一个包含5个方法的接口:
public interface MouseListener
{
void mouseClicked(MouseEvent event);
void mousePressed(MouseEvent event);
void mouseReleased(MouseEvent event);
void mouseEntered(MouseEvent event);
void mouseExited(MouseEvent event);
}
大多数情况下,你只需要关心其中的1、2个事件类型。在Java SE 8中,可以把所有方法声明为默认方法,这些默认方法什么也不做。
public interface MouseListener
{
default void mouseClicked(MouseEvent event) {};
default void mousePressed(MouseEvent event) {};
default void mouseReleased(MouseEvent event) {};
default void mouseEntered(MouseEvent event) {};
default void mouseExited(MouseEvent event) {};
}
这样实现这个接口的程序员只需要为他们真正关系的事件覆盖相应的监听器。
默认方法可以调用任何其他方法。例如,Collection 接口可以定义一个便利方法:
public interface Collection
{
int size(); //一个抽象方法
default boolean isEmpty()
{
return size()==0;
}
....
}
这样实现Collection的程序员就不用操心实现isEmpty方法了。
1.6 解决默认方法冲突
假如先在一个接口中将一个方法定义为默认方法,然后又在超类或者另一个接口中定义了同样的方法,会发生什么情况?
- 超类优先。如果超类提供了一个具体方法,同名而且有相同参数类型的默认方法会被忽略。
- 接口冲突。如果一个超接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型相同的方法,必须覆盖这个方法来解决冲突。
比如下面的情况,另一个包含getName方法的接口:
interface Named
{
default String getName(){ return getClass().getNanme()+"_"+hashCode();}
}
如果有一个类同时实现了这两个接口怎么样呢?
class Student implements Person,Named
{
....
}
类会继承Person和Named接口提供的两个不一致getName方法。并不是从中选择一个,Java编译器会报告一个错误,让程序员来解决这个二义性。只需要在Student类中提供一个getName方法。在这个方法中,可以选择两个冲突方法的一个:
class Student implements Person,Named
{
public String getName(){ return Person.super.getName();}
...
}