概述
参考自–《Java核心技术卷1》
接口(interface)
- 1 接口简述
- 在这里插入图片描述
- 2 接口的特性
- 3 接口与抽象类
- 4 静态方法
- 5 默认方法
- 6 解决默认方法冲突
接口技术主要用来描述具有什么功能,而并不给出每个功能的具体实现。一个类可以实现(
implements
)一个或多个接口,并在需要接口的地方,随时使用实现了相应接口的对象。
1 接口简述
接口概念:接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。
接口中的所有方法自动地属于 public,因此,在接口中声明方法时,不必提供关键字 public。
接口中可以定义常量。但,接口绝不能含义实例域,在 Java SE 8之前,也不能在接口中实现方法,现在已经可以在接口中提供简单方法了(当然,这些方法不能引用实例域—接口中没有实例)。
提供实例域和方法实现的任务应该由实现接口的那个类来完成。
现在,假设使用 Arrays 类的 sort 方法对 Employee 对象数组进行排序,Employee 类就必须实现接口 Comparable
接口:
为了让类实现一个接口,通常需要两个步骤:
1)将类声明为实现给定的接口
2)对接口中的所有方法进行定义
Comparable
接口:
public interface Comparable<T>{
int compareTo(T o); //参数类型为T
}
Employee
类:需要使用关键字 implements
public class Employee implements Comparable {
private String name;
private double salary;
...
@Override
public int compareTo(Object o) {
//比较 salary
Employee other = (Employee)o;
return Double.compare(salary,other.salary);
}
}
这里使用了静态的 Double.compare
方法。如果第一个参数小于第二个参数,返回一个负值,;如果二者相等,返回0;否则返回一个正值。
注:在接口声明中,没有将 compareTo
方法声明为 public,这是因为接口中的所有方法都自动是 public 的。不过在实现接口时,必须把方法声明为 public。
对于上述 compareTo
方法的实现,还有更好的设计。可以为泛型 Comparable 接口提供一个类型参数:
public class Employee implements Comparable<Employee> {
private String name;
private double salary;
...
@Override
public int compareTo(Employee o) {
//比较 salary
return Double.compare(salary,o.salary);
}
}
注:Comparable 接口中的 compareTo
方法将返回一个整型数值。如果两个对象不相等,则返回一个正值或者一个负值。在对两个整数域进行比较,这点非常有用。例如,假设每个雇员都有一个唯一正数 id,并希望根据 id 对雇员进行重新排序,那么就可以返回 id-other.id
。如果第一个 id 小于另一个 id,则返回一个负值,如果两个 id 相等,则返回0,否则返回一个正值。但需要注意整数的范围不能过大,以避免造成减法运算的溢出。若可能溢出,则调用静态 Integer.compare
方法。当然,整型数值的运算不适用于浮点值,浮点值运算:x<y时,Double.compare(x,y)
返回 -1;如果 x>y,则返回 1.
要使一个类使用排序服务必须让它实现 compareTo
方法,此方法向 sort
方法提供对象的比较方式。
2 接口的特性
接口不是类,尤其不能使用 new 运算符实例化一个接口;然而,尽管不能构造接口对象,却能声明接口的变量;而且,接口变量必须引用实现了接口的类对象:
Comparable x = new Comparable(...); //错误
Comparable x; //ok
x = new Employee(...); //ok , Employee是实现了Comparable的类
如同使用 instanceof
检查一个对象是否属于某个特定类一样,也可以使用 instanceof
检查一个对象所属类是否实现了某个特定的接口:
if(obj instanceof Comparable) {
//obj是否实现了Comparable接口,实现了返回true,否则返回false
...
}
与可以建立类的继承关系一样,接口也可以被扩展,且允许存在多条从具有较高通用性的接口到较高专用性的接口的链,例如:
public interface Moveable{
void move(double x,double y);
}
然后,可以以它为基础扩展一个叫 Powered 的接口:
public interface Powered extends Moveable{
double milesPerGallon();
double SPEED_LIMIT = 95; //权限为 public static final
}
接口中虽然不能包含实例域或静态方法,但是可用包含常量,与接口中的方法都自动被设置为 public 一样,接口中的域将被自动设为 public static final
.
尽管每个类只能够拥有一个超类,但却可以实现多个接口,例如:
public Employee implements Cloneable,Comparable{
...
}
Employee 类同时继承了 Cloneable
,Comparable
两个接口,拥有克隆和比较的能力。
3 接口与抽象类
抽象类也可以实现类似接口的功能:
public abstract class Comparable{
public abstract int compareTo(object obj);
}
再由 Employee 类扩展这个抽象类,并提供 compareTo
的方法实现:
public class extends Comparable{
...
public int compareTo(Object obj){
...
}
...
}
但,使用抽象类表示通用属性,方法存在这样一个问题:每个类只能扩展一个类。如果 Employee 此前已经扩展了一个类,它就不能扩展第二个类了。但它还能实现其他接口。
注:有些语言(如C++)允许一个类有多个超类,此特性称为多重继承。但 Java 并不支持多重继承,其主要原因是多继承会让语言本身变得非常复杂,效率也会降低。
实际上,接口可以提供多重继承的大多数好处,同时还能避免多重继承的复杂性和低效性。
4 静态方法
Java SE 8中,允许在接口中增加静态方法。理论上,没有任何理由认为这是不合法的。只是这样有违将接口作为抽象规范的初衷。
目前为止,通常的做法都是将静态方法放在伴随类中。在标准库中,可以看到成对出现的接口和实用工具类,如 Collection/Collections 或 Path/Paths。
Paths 类是 Path 接口的伴随类,在 Path 接口可以增加以下方法:
public interface Path{
public static Path get(String first,String... more){
return FileSystems.getDefault().getPath(first,more);
}
...
}
get
方法是 Paths 伴随类实现的方法,若如上给 Path 接口增加了 get
方法,那么,Paths 类就不再是必要的。
在实现自己的类时,不再需要为实用工具方法另外提供一个伴随类,而是直接在接口中增加 静态方法。
5 默认方法
可以为接口方法提供一个默认实现。必须使用 default 修饰符标记这样一个方法。
public interface Comparable<T>{
default int compareTo(T other){
return 0;
}
}
当然,这样并没有多大用处,因为 Comparable
的每一个实际实现都要覆盖 compareTo
方法。不过,有些情况下,默认方法可能很有用:在接口的实现类中不用必须实现接口的默认方法,程序员可以选择自己关心的方法并覆盖实现它。
默认方法可以调用任何其他方法。例如:Collection 接口定义一个便利方法
public interface Collection{
int size();
default boolean isEmpty(){
return size()==0;
}
...
}
注:在 Java API 中,可以看到很多接口都有相应的伴随类,这些伴随类实现了相应接口的部分或所有方法。在 Java SE 8中,这个技术已经过时。现在可以直接在接口中实现方法。
默认方法的一个重要用法是“接口演化”。以 Collection 接口为例,假设很久以前编写了这样一个类:
public class Bag implements Collection
后来随着 JDK 的更新,又为 Collection 接口增加了 stream
方法。
假设 stream
方法不是一个默认方法,那么 Bag 类将不能编译,因为它没有实现这个新增加的方法。为接口增加一个非默认方法不能保证“源代码兼容”。
不过,假设不重新编译这个类(Bag),而是使用原先的一个包含这个类的 JAR 文件。尽管没有实现这个新方法,这个类仍能正常加载。程序仍能正常构造 Bag 实例,但如果在一个 Bag 实例上调用 stream
,就会出现一个 AbstractMethodError
.
将方法实现为一个默认方法就可以解决这2个问题。Bag 类能正常编译,且若没有重新编译而直接加载这个类,并在 Bag 实例上调用 stream
方法,将调用 Collection.stream
方法。
6 解决默认方法冲突
如果先在一个接口中将一个方法定义为默认方法,然后在超类或另一个接口中定义了同样的方法,会发生什么情况?对于这种二义性的问题,Java 有相应的解决规则:
1.超类优先。如果超类提供了一个具体方法,接口中存在的同名且有相同参数类型的默认方法会被忽略。
2.接口冲突。如果一个超接口提供了一个默认方法,另一个接口提供了一个同名且参数类型(不论是否是默认参数)相同,必须覆盖这个方法来解决冲突。
下面看看这两种情况是如何处理的:
一:假设存在 Person 超类:
public class Person{
private name;
...
//具体方法
public String getName(){
return name;
}
}
另外一个包含 getName
方法的接口:
interface Named{
//默认方法
default String getName(){
return ...;
}
}
有一个继承了 Person 且实现了 Named 接口的类 Student:
public class extends Person implements Named{
...
}
在这种情况下,只会考虑超类方法,接口的所有默认方法都会被忽略。即 Student 从 Person 继承了 getName
方法,Named 接口是否为 getName
提供了默认实现并不会带来什么区别。这正是“类优先”规则。
“类优先”规则可以确保与之前的 JDK 版本保持兼容性。如果为一个接口增加默认方法,这对于有这个默认方法之前能够正常工作的代码不会有任何影响。
二:假设 Person 是一个接口:
public interface Person{
...
//默认方法
default String getName(){
return ...;
}
}
若存在一个 Student 类同时实现了 Person 接口和 Named 接口,需要程序员去选择两个冲突方法中的一个,如下所示:
public Student implements Person,Named{
public String getName(){
return Person.super.getName();
}
}
或者,在实现接口的类中覆盖冲突方法。
@Override
public String getName() {
return null;
}
在 Java 中,两个接口方法的冲突并不重要。如果至少有一个接口提供了默认实现,编译器就会报告错误,而程序员就必须解决这个二义性;但若两个接口都没有为同名且参数类型相同的类型提供默认参数,则不存在冲突,这时,实现类有两个选择:实现这个方法,或者不实现。
public Student implements Person,Named{
public String getName(){
return Person.super.getName();
}
}
或者,在实现接口的类中覆盖冲突方法。
@Override
public String getName() {
return null;
}
在 Java 中,两个接口方法的冲突并不重要。如果至少有一个接口提供了默认实现,编译器就会报告错误,而程序员就必须解决这个二义性;但若两个接口都没有为同名且参数类型相同的类型提供默认参数,则不存在冲突,这时,实现类有两个选择:实现这个方法,或者不实现。
最后
以上就是落寞凉面为你收集整理的18.接口【Java温故系列】的全部内容,希望文章能够帮你解决18.接口【Java温故系列】所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复