Thinking in Java第九章学习笔记----接口

  接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法。其中内部类将在第十章讲述。

为什么需要抽象类:

  抽象类是普通的类与接口之间的一种中庸之道。尽管在构建具有某些未实现方法的类时,第一想法肯定是接口,但是抽象类仍是用于此目的的一种重要而必须的工具。因此,不可能总是使用纯接口。

  此外,如果一个类只是表示了一个接口,但是没有具体的实现内容,那么创建这样的一个类对象时毫无意义的,并且我们还应该阻止这种行为。通过让这个类的所有方都产生错误,就可以实现这个目的,但是不幸的是错误信息将延迟到运行时才能获得,并且需要在客户端进行详尽的测试。所以,有什么方法可以在编译期就成功避免这些问题呢,Java提供的抽象方法的机制,完美的解决了。

  抽象方法,即仅有声明没有方法体;含有抽象方法的类称为抽象类。如果一个类包含一个或多个抽象方法,该类必须限定为抽象的。否则,编译报错。

  如果从一个抽象类继承,并想创建该新类的对象,那么就必须为基类中的所有抽象方法提供方法定义。如果不这样做,那么导出类便是抽象类,而且编译器会强制我们用abstract关键字来限定这个类。

  一种特殊情况,如果类包含任何abstract方法都无任何意义,而且我们又不想产生这个类的任何对象,这个时候我们可以创建一个没有任何抽象方法的抽象类。

接口:

  我的理解是,接口和抽象类的不同之处在于抽象的程度不一样,抽象类中抽象方法是不确定的,甚至可以没有抽象方法;而接口产生一个完全抽象的类,它根本没有任何具体实现。它允许创建者确定方法名,参数列表和返回类型,但是没有任何方法体。接口只提供了形式,表示所有实现了该特定接口的类看起来都应该像这样。

创建接口:

  要想创建接口,须使用interface来代替class关键字。接口的访问权限和普通类一样,可以添加public、private、protect,默认为包访问权限。接口也可以包含域,但是这些域都是隐式static和final的。

继承接口:

  要让一个类遵循接口,就必须使用implements关键字,它表示接口只是它的外貌,现在由我来声明它是怎么工作的。注意,接口中的方法默认是public的,不管有没有加public关键字。

Java中的多重继承:

  在导出类中,不强制要求必须有一个是抽象的或者是具体的没有抽象方法的基类。如果要从一个非接口的类去继承,那么只能继承一个非接口类,其余继承的都必须是接口,并将接口名置于implements关键字后面,用逗号将他们隔开。可以继承任意多个接口,并且可以向上转型为每个接口,因为每一个接口都是一个独立类型。之所以接口可以无限制的组合,是因为接口没有任何的实现,也就是说没有任何与之相关的存储。

interface CanFight {
    void fight();
}
interface CanSwim {
    void swim();
}
interface CanFly {
    void fly();
}

class ActionCharacter {
    public void fight() {}
}

class Hero extends ActionCharacter implements CanFight, CanSwim, CanFly { //一个具体类,多个接口的继承,具体类须放前面。
    public void swim() {}
    public void fly() {}
}

public class Adventure {
    static void t(CanFight x) { x.fight(); }
    static void u(CanSwim x) { x.swim(); }
    static void v(CanFly x) { x.fly(); }
    static void w(ActionCharacter x) { x.fight(); }
    public static void main(String[] args) {
        Hero i = new Hero();
        t(i); // Treat it as a CanFight
        u(i); // Treat it as a CanSwim
        v(i); // Treat it as a CanFly
        w(i); // Treat it as an ActionCharacter
    }
}

  注意:CanFight和ActionCharacter类中的fight()方法的特征签名是一样的,而且Hero中没有fight()方法的定义。当想要创建对象时,须保证所有的定义首先都存在。即使Hero中没有显示地提供fight()的定义,其定义也因ActionCharacter而随之而来,这样就使得创建Hero对象是可能的。这个现象说明了继承的具体类中的方法可以作为接口的实现,在方法的特征签名完全一样的情况下。

通过继承来扩展接口:

  通过继承,可以很容易在接口中添加新的方法声明,还可以通过继承在新的接口中组合数个接口。这两种接口都可以获得新的接口。

  一个陷阱,在实现多重继承时,具体类的方法和接口的抽象方法名一样,但是返回类型或者参数列表不一样,编译会出错。而且,我们必须尽量避免这样问题出现,因为覆盖、实现和重载搅在一起,够你折腾一下了。

重载、重写(覆盖):

  重载(overload):

  1)发生在同一个类中

  2)方法名必须相同,参数类型、个数、顺序方法返回值和访问修饰符不同,都算重载

  重写(override):

    1)发生在继承中

    2)方法名、参数列表必须完全一致,返回值必须相同或子类

    2)子类异常不能大于父类异常

    3)子类访问级别不能低于父类访问级别

    4)父类方法为private,子类方法即使符合以上三点,也不算重写,是一个新方法

    5)如果子类方法中还想使用父类的方法,可以用super关键字

  访问级别补充:

接口中的域:

  因为接口中的域自动设置为static和final,所以在Java SE5之前,这是产生enum枚举类型的唯一途径。Java在标识具有常量初始化值为final和static时,会使用大写字母的风格,用下划线来分隔多个单词。

  注意,前面在叙述接口可以任意结合的时候说过,接口是没有任何存储的,可以看出域是不属于接口的一部分的,因为他们保存在接口的静态存储域中。

嵌套接口:

  接口可以嵌套在类和其他接口中。以下三点是针对代码的理解与困惑:

  1)在类中嵌套接口,接口可以拥有public、protect、private三种访问权限,就像下面的接口A.D,private嵌套类的接口不但可以被实现为一个private内部类,也可以实现为public内部类。但是A.DImp2只能被自身所使用,而且无法说A.DImp2真正意义上实现了一个private接口D。因为实现接口只是一种方式,它可以强制该接口中的方法定义不要添加任何类型信息,也就是说不能向上转型。这是书中的原话,若是哪位朋友对此有深刻的理解,还望不吝赐教。我的初步理解是,private接口的实现,包括两部分,一是方法的具体实现,二是将接口变为public,因为大部分情况下,一个私有的接口是毫无意义的。这里的A.DImp2实现了具体的方法,而且表面上看起来访问权限变为public。但是这种public并不能被其他类公有,仅自身A.DImp2可以访问,所以说并没有实现一个private接口。

  2)关于getD方法,更是神乎其神,不能窥其奥秘。希望在后面的学习中能有所理解。欢迎大家评论处讨论,相互促进。

  3)接口间同样可以嵌套。但是,作用于接口的所有规则,特别是所有的接口元素都必须是public的,都会被严格执行。实现嵌套接口时,并不需要实现在其内部的任何接口。关于这一点,也是很迷。

  其实,上面的种种困惑都是因为对嵌套接口的意义没有清晰的认识。不知道这是Java的炫技,徒有其表,还是Java语言特性的美妙之处。

class A {      
  interface B {          
    void f();      
  }  
    public class BImp implements B {  
        @Override  
        public void f() {}  
    }  
    private class BImp2 implements B {  
        @Override  
        public void f() {    
        }  
    }  
    public interface C {  
        void f();  
    }  
    class CImp implements C {  
        @Override  
        public void f() {  
        }  
    }  
    private class CImp2 implements C {  
        @Override  
        public void f() {  
        }  
    }  
    private interface D {  
        void f();  
    }  
    private class DImp implements D {  
        @Override  
        public void f() {}  
    }  
    public class DImp2 implements D {  
        @Override  
        public void f() {}  
    } 
    public D getD() {  
        return new DImp2();  
    }    
    private D dRef;  
    public void receiveD(D d) {  
        dRef = d;  
        dRef.f();  
    }  
}  
  
interface E {  
    interface G {  
        void f();  
    }  
    public interface H {  
        void f();  
    }  
    void g();  
    // Cannot be private within an interface:  
    // !private interface I{}
}  

public class NestingInterfaces {  
    public class BImp implements A.B { 
        @Override  
        public void f(){}  
    }  
    class CImp implements A.C {  
        @Override  
        public void f() {}  
    }  
    class EImp implements E {  
        @Override  
        public void g(){}  
    }  
    class EGImp implements E.G {  
        @Override  
        public void f() {}  
    } 
    class EImp2 implements E {  
        @Override  
        public void g() {} 
        class EG implements E.G {  
            @Override  
            public void f() {}  
        }  
    }  
    public static void main(String[] args) {  
        A a = new A();  
        // Can't access A.D  
        // !A.D ad=a.getD();  
        // Doesn't return anything but A.D  
        //!A.DImp2 di2 = a.getD();  
        //Cannot access a member of the interface  
        //!a.getD().f();  
        //Only another A can do anything with getD():  
        A a2=new A();  
        a2.receiveD(a.getD());  
    }  
} 

接口与工厂:

  接口是实现多重继承的途径,而生成遵循某个接口的对象的典型方式就是工厂方法设计模式,这与直接调用构造器不同,我们在工厂对象上调用的是创建方法,而该工厂对象将产生接口的某个实现的对象。理论上,通过这种方式,我们的代码将完全与接口的实现分离,这使得我们可以透明地将某个实现替换为另一个实现。

interface Service {
    void method1();
    void method2();
}

interface ServiceFactory {
    Service getService();
}    

public class ServiceImpl1 implements Service {
    void method1(){}
    void method2(){}                
}        

public class ServiceFactoryImpl1 implements ServiceFactory {
    Service getSerivce() {
        return new ServiceImpl1();
    }    
}                                                       

public class ServiceImpl2 implements Service() {
    void method1(){}
    void method2(){}      
}

public class ServiceFactoryImpl2 implements ServiceFactory {
   Service getService() {
      return new ServiceImpl2();  
    } 
}

public class Factories{
    public static void serviceConsumer(ServiceFactory serviceFactory) {
        Service service = serviceFactory.getService();
        service.method1();
        service.method2();
    }

    public static void main(String[] args) {
        serviceConsumer(new ServiceFactoryImpl1());
        serviceConsumer(new ServiceFactoryImpl2());
    }
}    

总结:

  "确定接口是理想选择,因而应该总是选择接口而非具体的类",这其实是一种诱惑。对于创建类,几乎在任何时候,都可以替代为创建一个接口和一个工厂。

  恰当的原则是,优先选择类而不是接口。从类开始,如果接口的必需性变得非常明确,那么就进行重构。

  接口是一种重要的工具,但容易被滥用。

 

重载(overload)

1)发生在同一个类中

2)方法名必须相同,参数类型、个数、顺序方法返回值和访问修饰符不同,都算重载

重写(override)

1)发生在继承中

2)方法名、参数列表必须完全一致返回值必须相同或子类

2)子类异常不能大于父类异常

3)子类访问级别不能低于父类访问级别

4)父类方法为private,子类方法即使符合以上三点,也不算重写,是一个新方法

5)如果子类方法中还想使用父类的方法,可以用super关键字

posted @ 2018-04-08 20:22  丶岑夫子  阅读(195)  评论(0编辑  收藏  举报