在java中,接口不是类,而是对希望符合这个接口的类的一组需求。
如果你的类符合某个特定的接口,我们会履行这项服务。如Arrays
类中的sort
方法承诺可以对对象数组进行排序,但要求对象所属的类必须实现Comparable
这个接口。
下面是Comparable
接口中的代码
public interface Comparable<T> {
public int compareTo(T o);
}
Comparable
接口要求你实现compareTo
方法。
接口中的所有方法都默认是public
的,可以显示地将其声明为default
。
接口中的字段默认被public static final
所修饰。
为了让一个类实现一个接口,通常有以下两个步骤 :
在接口中的方法可以不写修饰符,但在实现接口的类中必须显式地写出public
,因为在类中的默认权限是包访问权限。
下面是一个实现Comparable
接口的类进行排序的例子 :
package interfaces;
import java.util.Arrays;
class Dog implements Comparable<Dog>{
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
@Override
public int compareTo(Dog o) {
if (o == null) {
return -1;
}
return Integer.compare(this.age, o.getAge());
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test02 {
public static void main(String[] args) {
Dog[] dogs = new Dog[3];
dogs[0] = new Dog("Tom", 30);
dogs[1] = new Dog("Jack", 20);
dogs[2] = new Dog("Bob", 40);
Arrays.stream(dogs).forEach(System.out::println);
Arrays.sort(dogs);
System.out.println("===============================");
Arrays.stream(dogs).forEach(System.out::println);
}
}
要让一个类使用Arrays.sort
方法需要让这个类实现Comparable
接口,为什么不让这个类直接实现compareTo
方法呢?
因为Java是一种强类型(strongly typed
)语言,在调用方法时,编译器需要检查compareTo
方法确实存在。
注意 :
语言标准规定 : 对于任意的x和y, 实现者必须能保证sgn(x.compareTo(y)) == sgn(y.compareTo(x))
sgn是一个函数,当n < 0时,sgn(n) = -1, n == 0时,sgn(n) = 0, n > 0时, sgn(n) = 1。
如果翻转compareTo的参数,那么结果的符号也应翻转。
接口不是类,不能直接对接口实例化,但可以定义接口的变量。
接口的变量只能引用实现接口的对象。
package interfaces;
interface B {
}
class Cat implements B {
}
public class Test03 {
public static void main(String[] args) {
// B b = new B(); 错误
B b;
b = new Cat();
}
}
接口之间也可以相互继承
interface B {
}
interface C extends B {
}
一个类也可以实现多个接口
interface B {
}
interface C extends B {
}
class Cat implements B, C {
}
在接口的方法默认被public
修饰,字段则被public static final
修饰。
为什么不将Comparable
设置成抽象类而设置成接口呢?
因为在Java中是单继承的,每个类都只能继承一个类,但每个类可以实现多个接口。
在C++等语言中,一个类可以继承多个类,这称为多重继承(multiple inheritance)
,Java不支持多重继承,因为这会让语言变得复杂或让效率降低。
Java通过接口提供了多重继承的大多数好处,同时还能避免多重继承的复杂性和效率。
在
《java的编程逻辑》
一书中提到使用抽象类主要的用途是提醒程序员,例如希望父类中的某一方法被子类重写,可以将方法定义为抽象方法,这样可以强制要求程序员重写此方法。
在Java8中允许在接口中增加静态方法。
通常的做法是将静态方法放在伴随类中。在标准库中,你会看到成对出现的接口和实用工具,如Collection/Collections
和 Path/Paths
。
但现在你可以将静态方法直接写在接口中,从而取消了对伴随类的依赖。
在Java9中,方法也可以是private
, 但局限性比较强,因为只能在接口内部使用它。
可以为接口提供一个默认default
方法, 必须使用default
修饰符。
默认方法的一个重要用法是"接口演化"interface evolution
,可以扩展接口的功能而不使以前写的代码造成错误。
如果先在一个接口中将一个方法定义为默认方法,然后又在超类或另一个接口中定义一个同样的方法,会如何呢?
回调callback
是一种常见的程序设计模式。在这种模式中,可以指定某个特定事件发生时应该采取的动作。
如果你想实现按字符串长度对String类进行排序,你不能改写String类中的CompareTo
方法。但你可以写一个比较器(Comparator)
。
下面是一个简单的代码 :
package interfaces;
import java.util.Arrays;
import java.util.Comparator;
public class Test04 {
public static void main(String[] args) {
String[] strs = new String[]{"abc", "cascav", "qa"};
Arrays.sort(strs, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
});
}
}
这里使用了一个匿名内部类
, 来实现接口中的方法。
Cloneable
接口指示了一个类提供了一个安全的clone
方法。
默认的克隆操作是“浅拷贝”,并没有克隆对象中引用的其他对象,也就是说克隆的对象和被克隆的对象共享了同一个引用的对象。当克隆对象修改引用对象时,被克隆的对象的引用对象也会发生更改。这样是不安全的。
要想实现安全的克隆操作必须建立一个深拷贝(deep copy
), 同时克隆所有子对象。
对于每一个类,需要确定 :
clone
方法是否满足要求。clone
来修补默认的clone
方法。clone
。Cloneable
接口。clone
方法,并指定public
访问修饰符。Cloneable
接口是一个标记接口(tagging interface)
,里面并没有要实现的方法,clone
方法是从Object
类中继承过来的。但如果一个对象请求克隆却没有实现这个接口,就会生成一个检查型异常
。
标记接口不包含任何方法,它唯一的作用是允许在类型查询中使用instanceof
。
下面是一个例子 :
package clone;
class Cat implements Cloneable {
private String name;
private int age;
private Hobby hobby;
public Cat(String name, int age, Hobby hobby) {
this.name = name;
this.age = age;
this.hobby = hobby;
}
@Override
protected Cat clone() throws CloneNotSupportedException {
Cat cloned = (Cat) super.clone();
// cloned.hobby = hobby.clone();
return cloned;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
", hobby=" + hobby +
'}';
}
}
class Hobby implements Cloneable {
private String name;
public Hobby(String name) {
this.name = name;
}
@Override
public Hobby clone() throws CloneNotSupportedException {
return ((Hobby) super.clone());
}
}
public class Test01 {
public static void main(String[] args) throws CloneNotSupportedException {
Cat cat = new Cat("jcjc", 16, new Hobby("eat"));
Cat clone = cat.clone();
System.out.println(cat);
System.out.println(clone);
}
}
输出的结果为 :
Cat{name='jcjc', age=16, hobby=clone.Hobby@12edcd21}
Cat{name='jcjc', age=16, hobby=clone.Hobby@12edcd21}
可以看见最后一项的值是一样的,因为默认的方法是浅拷贝,会共享引用的对象。
如果将cloned.hobby = hobby.clone();
"解放"出来,程序的结果为 :
Cat{name='jcjc', age=16, hobby=clone.Hobby@12edcd21}
Cat{name='jcjc', age=16, hobby=clone.Hobby@34c45dca}
可以发现二者的结果不一样了。