6.1 接口

6.1.1 接口的概念


在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的参数,那么结果的符号也应翻转。

6.1.2 接口的属性


接口不是类,不能直接对接口实例化,但可以定义接口的变量。
接口的变量只能引用实现接口的对象。

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修饰。

6.1.3 接口与抽象类


为什么不将Comparable设置成抽象类而设置成接口呢?
因为在Java中是单继承的,每个类都只能继承一个类,但每个类可以实现多个接口。
在C++等语言中,一个类可以继承多个类,这称为多重继承(multiple inheritance),Java不支持多重继承,因为这会让语言变得复杂或让效率降低。
Java通过接口提供了多重继承的大多数好处,同时还能避免多重继承的复杂性和效率。

《java的编程逻辑》一书中提到使用抽象类主要的用途是提醒程序员,例如希望父类中的某一方法被子类重写,可以将方法定义为抽象方法,这样可以强制要求程序员重写此方法。

6.1.4 静态和私有方法


在Java8中允许在接口中增加静态方法。
通常的做法是将静态方法放在伴随类中。在标准库中,你会看到成对出现的接口和实用工具,如Collection/CollectionsPath/Paths
但现在你可以将静态方法直接写在接口中,从而取消了对伴随类的依赖。
在Java9中,方法也可以是private, 但局限性比较强,因为只能在接口内部使用它。

6.1.5 默认方法


可以为接口提供一个默认default方法, 必须使用default修饰符。
默认方法的一个重要用法是"接口演化"interface evolution,可以扩展接口的功能而不使以前写的代码造成错误。

6.1.6 解决默认方法冲突


如果先在一个接口中将一个方法定义为默认方法,然后又在超类或另一个接口中定义一个同样的方法,会如何呢?

  • 超类优先。接口中的默认方法会被忽略。
  • 接口冲突。此时必须实现接口中的方法,来解决这个冲突。
    类优先,也在一定程度上保护以前的代码。

6.1.7 接口与回调


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

6.1.8 Comparator 接口


如果你想实现按字符串长度对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();  
            }  
        });  
    }  
}

这里使用了一个匿名内部类, 来实现接口中的方法。

6.1.9 对象克隆


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}

可以发现二者的结果不一样了。

你可能感兴趣的:(Java核心技术卷1读书笔记,java,开发语言,笔记)