Java编程思想拾遗(8) 接口

651 阅读4分钟

抽象类

如果我们只有一个像Instrument这样的抽象类,那么该类的对象几乎没有任何意义,我们创建抽象类是希望通过这个通用接口操纵一系列类。因此,Instrument只是表示了一个接口,没有具体的实现内容,创建一个Instrument对象没有什么意义,并且我们可能还想阻止使用者这样做,通过让Instrument中的所有方都产生错误就可以实现这个目的,但是这样做会讲错误信息延迟到运行时才获得,并且需要在客户端进行可靠、详尽的测试,所以最好在编译时捕获这些问题。

为此,Java提供了一个叫做抽象方法的机制,这种方法是不完整的,仅有声明而没有方法体,如果一个抽象类不完整,那么当我们试图产生该类的对象时,由于为抽象类创建对象时不安全的,所以我们会从编译器那里得到一条出错信息。

接口

interface关键字使抽象的概念更向前迈进了一步,一个接口表示:“所有实现了该特定接口的类看起来都像这样”,因此任何使用某特定接口的代码都知道可以调用该接口的哪些方法,而且仅需知道这些。接口被用来建立类与类之间的协议

  • 接口里的方法默认都是public,毕竟协议是需要公开暴露的。
  • 接口里的域默认都是static的,在多接口实现场景可以区分同名域属于哪个接口。
  • 接口里的域默认都是final的,如果域可以修改,那么就没有继承意义,接口要的就是协议化,另外一个角度:因为是static的所以一个接口的域只能有一个实例,所以需要用的话只能在接口先初始化,对于实现类对象没有任何修改的意义。
  • 因为枚举enum的出现,所以用接口单纯描述域就显得过时了。

多重继承

接口不仅仅只是一种更纯粹形式的抽象类,它的目标比这更高,因为接口是根本没有任何具体实现的,也没有任何与接口相关的存储,因此也就无法阻止多个接口的组合。

在导出类中,不强制要求有一个是抽象的或具体的基类,如果要从一个非接口的类继承,那么只能从一个类去继承,其余的基元素必须使接口。可以继承任意多个接口,并可以向上转型为每个接口,因为每个接口都是一个独立的类型。

interface CanFight {
    void fight();
}

interface CanSwim {
    void swim();
}

interface CanFly() {
    void fly();
}

class ActionCharacter {
    public void fight() {}
}

class Hero extends ActionCharater implements CanFight, CanSwim, CanFly {
    public void swim() {}
    public void fly() {}
}

public class Adventure {
    public static void t(CanFight x) { x.fight(); }
    public static void u(CanSwim x) { x.swim(); }
    public static void v(CanFly x) { x.fly(); }
    public static void w(ActionCharacter x) { x.fight(); }
    public static void main(String[] args) {
        Hero h = new Hero();
        t(h);
        u(h);
        v(h);
        w(h);
    }
}

注意,CanFight接口与ActionCharater类中的fight()方法的特征签名是一样的,而且在Hero中并没有提供fight()的定义,其定义就是来自于ActionCharater,但如果两者返回值类型不同,编译器会执行拒绝。

多重继承是可以对原有类库接口进行适配的重要方式。

public interface Generator<T> {
    T next();
}

public class Fibonacci implements Generator<Integer> {
    private int count = 0;
    public Integer next() {
        return fib(count++);
    }
    private int fib(int n) {
        if (n < 2) {
            return 1;
        }
        return fib(n-2) + fib(n-1);
    }
}

public class IterableFibonacci extends Fibonacci implements Iterable<Integer> {
    pirvate int n;
    public IterableFibonacci(int count) {
        n = count;
    }
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            public boolean hasNext() {
                return n > 0; 
            }
            public Integer next() {
                n--;
                return IterableFibonacci.this.next();
            }
            public void remove() {
                throw new UnsupportedOperationException();
            }
        };
    }
    
    public static void main(String[] args) {
        for(int i : new IterableFibonacci(18) {
            System.out.print(i + " ");
        }
    }
}

此例中假设Fibonacci是无法控制和修改的代码,而通过多重继承适配可以实现迭代器功能,因为Iterable是个接口。

接口本身可以进行多重继承。

interface Monster {
    void menace();
}
interface DangerousMonster extends Monster {
    void destroy();
}
interface Lethal {
    void kill();
}
interface Vampire extends DangerousMonster, Lethal {
    void drinkBlood();
}

嵌套接口

接口可以嵌套在类或者其他接口中。

  • 作用于接口的各种规则,特别是所有的接口元素都必须是public,因此嵌套在另一个接口中的接口自动就是public的,而不能声明为private的,除非内部刚好有对应的实现类和对这个private接口的public访问方法。
  • 当实现某个接口时,并不需要实现嵌套在其内部的任何接口,此时嵌套接口相当于内部成员变量,未必要被实现,可以单独实现外部接口或者嵌套接口。

我们应该使用接口还是抽象类?恰当的原则应该是优先选择类而不是接口,从类开始,如果接口的必需性变得非常明确,那么就进行重构。