java编程思想(读书笔记):8.接口和内隐类

八.接口和内隐类
接口和内隐类为你系统中的对象提供更为方便的组织和控制方式。
**接口:**interface更胜于抽象类,因为我们能够借以撰写出可被向上转型为多个基本类型的class,从而达到像c++一样的多重继承的变形。
interface可以内含数据成员,但是这些数据成员会自动变为static和final。是的,interface所提供的只是形式,不含实现的细目。
interface所陈述的是:“所有实现出本接口的classes,看起来都应该像这样。”
方法:interface中的方法都是public。所以当你实现某个接口时,必须将继承自该接口的所有方法都声明为public!
代码演示:

package test;

public class Test {
    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 h = new Hero();
        t(h);
        u(h);
        v(h);
        w(h);
    }

}
interface CanFight{
    void fight();
}
interface CanSwim{
    void swim();
}
interface CanFly{
    void fly();
}
class ActionCharacter{
    public void fight(){System.out.println("action fight");}
}
class Hero extends ActionCharacter implements CanFight,CanFly,CanSwim{
    public void swim(){System.out.println("swim");};//实现
    public void fly(){System.out.println("fly");};//实现
}

注意,interface CanFight及class ActionCharacter中的fight()的标记式是一致的(这样Hero通过继承ActionCharacter的方法,相当于实现了CanFight接口未实现的方法)

接口存在意义?
1.能够被向上转型为多个基本性别
2.让客户端程序员无法产生其对象,并因此确保这只是一个接口。
interface同时赋予你抽象类和接口的好处,因此你的base class可以不带任何函数定义或任何成员变量,那么应该优先考虑用interface。
扩充interface:利用继承,可以轻易将新的函数加至interface中,也可以通过继承extends将多个interface结合为一个新的interface。
产生产量群:因为interface中的所有数据成员都会自动成为static和final,所以能够方便的产生常量群。
定义于interface中的数据成员都是static和final,但不能是blank finals。但可以被非常量表达式初始化。如:

public interface Rand{
int rint = (int)(Math.random()*10);
}              

嵌套的interface:

package test;

import test.A.DImp2;

class A{
    interface B{//default
        void f();
    }
    public class BImp implements B{//public 
        public void f(){}
    }
    private class BImp2 implements B{//private
        public void f(){}
    }
    public interface C{//public
        void f();
    }
    class CImp implements C{//default
        public void f(){}
    }
    private class CImp2 implements C{//private
        public void f(){}
    }
    private interface D{//private
        void f();
    }
    private class DImp implements D{//private
        public void f(){}
    }
    public class DImp2 implements D{//public 
        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
    public interface H{
        void f();
    }
    void g();
    //错误:成员只能是public
    //!private interface I{}
}

public class Test1 {
    public class BImp implements A.B{
        public void f(){}
    }
    class CImp implements A.C{
        public void f(){}
    }
    //不能实现一个private interface,除非在interface被定义的class中
    //!class DImp implements A.D{
        //public void f(){}
    //}
    class EImp implements E{
        public void g(){}
    }
    class EGImp implements E.G{
        public void f(){}
    }
    class EImp2 implements E{
        public void g(){}
        class EG implements E.G{
            public void f(){}
        }
    }
    public static void main(String[] args) {
        A a = new A();
        //A.D not visible
        //!A.D ad = a.getD();
        A.DImp2 di2 = (DImp2) a.getD();//向下转型
        a.receiveD(a.getD());
    }
}

说明:将interface嵌套在class中,相当直觉。在其中的interface可被定义为正常的可视性。
interface E告诉我们,interface可彼此相互嵌套。但是”所有interface的元素都必须为public“这一条会被严格执行。注意,当你实现某个interface时,你无须实现其中任何嵌套的interface。此外,private interface无法再其所定义的class之外被使用。
内隐类
将某个class的定义置于另一个class定义之中是可行的,这就是所谓的内隐类(inner class)。注意,内隐类和所谓的组合(composition)是截然不同的两回事。
内隐类的典型做法是,外围class有一个函数,可以传回一个reference指向inner class。代码:

package test;

public class Parcel2 {
    //内隐类
    class Contents{
        private int i = 11;
        public int value(){return i;}
        }
    class Destination{
        private String label;
        Destination(String whereTo){
            label = whereTo;
        }
        String readLabel(){
            return label;
        }
    }
    public Destination to(String s){
        return new Destination(s);
    }
    public Contents cont(){
        return new Contents();
    }
    public void ship(String dest){
        Contents c = cont();
        Destination d = to(dest);
        System.out.println(d.readLabel());
    }
    public static void main(String[] args) {
        Parcel2 p = new Parcel2();
        p.ship("mianyang");
        Parcel2 q = new Parcel2();
        //给内隐类分配reference
        Parcel2.Contents c = q.cont();
        Parcel2.Destination d = q.to("tianjin");
        Parcel2.Destination d2 = new Parcel2().new Destination("chengdu");
        System.out.println(d.readLabel());
        System.out.println(d2.readLabel());
    }
}

输出:

mianyang
tianjin
chengdu

内隐类和向上转型
当你开始向上转型至base class,尤其是转型为interface,就能凸显inner class的好处(注意:从某个”实现出interface I“的inner class对象身上产生一个reference指向I,本质上和”向上转型至base class“是一样的),这是因为inner class(也就是I的实现者)可以在接下来的情境中完全不被看见,而且不为任何人所用,这么一来我们就很方便能够”隐藏实现细目“。你所得到的只是”指向base class或interface“的一个reference。
一般的(non-inner)classes无法被声明为private或者protected,只能是public或friendly。
inner class的隐晦使用方式
你可以将inner class置于函数之内或者甚至置于程序范畴(scopes)之内。你这样做可能有两个理由:
1.你想实现某种interface,使你得以产生并返回某个reference。
2.你正在解决某个复杂问题,而你希望在解决方案中设计某个class,又不希望这个class被外界所用。
在任意程序范畴嵌套inner class:

package test;
public class Parcel5 {
    //在任意函数范畴(scope)内嵌套inner class、
    private void internalTracking(boolean b){
        if(b){
            class TrackingSlip{
                private String id;
                TrackingSlip(String s) {
                    id = s;
                }
                String getSlip(){
                    return id;
                }
            }
            TrackingSlip ts = new TrackingSlip("slip");
            String s = ts.getSlip();
            System.out.println(s);
        }
        //超出scope,不能够被使用
        //!TrackingSlip ts1 = new TrackingSlip("slip");
    }

    public static void main(String[] args) {
        Parcel5 p = new Parcel5();
        p.internalTracking(true);
    }

}

注意:class TrackingSlip被嵌套在if语句中,并不意味这该class会随着条件的成立才被产生,事实上,他会和其他classes一起被编译出来。不过它在它所处的范畴之外就不能被使用。
与外围class的连接关系
截止目前,inner class看起来似乎是一种用于名称隐藏和程序代码组织的体制。这并不能完全让人信服,它还有另外一个作用。当你建立一个inner class时,其对象便拥有了与其制造者–那个外围(enclosing)对象–之间的一种连接关系。所以它可以访问外围对象的所有成员而无需添加任何饰词。此外,inner class也可以访问enclosing class的所有元素。

package test;

interface Selector{
    boolean end();
    Object current();
    void next();
}
public class Sequence {
    private Object[] obs;
    private int next = 0;
    public Sequence(int size){
        obs = new Object[size];
    }
    public void add(Object o){
        if(nextprivate class SSelector implements Selector{
        int i = 0;
        public boolean end(){
            return i == obs.length;
    }
        public Object current(){
            return obs[i];
            }
        public void next(){
            if(ipublic Selector getSelector(){
        return new SSelector();
    }

    public static void main(String[] args) {
        Sequence s = new Sequence(10);
        for (int i = 0; i < 10; i++) 
                s.add(Integer.toString(i));
                Selector s1 = s.getSelector();
                while(!s1.end()){
                    System.out.println(s1.current());
                    s1.next();
                }
    }                    
}

sequence只是一个大小固定的Object array,以class形式加以包装。要想取得sequence中的每个对象,有个名为selector的interface,可以让你执行相关操作。由于selector是个interface,所以任何class都可以以自己的形式来实现此一interface,而很多函数都可以接受此一interface作为引数,藉以产生一般化的程序代码。
乍看之下,SSelector只不过是个inner class,但是注意到其函数中都用到了obs,并不是SSelector的一部分,而是外围class的private成员变量。这样就带来了很大的便利。
静态内隐类
如果你不需要inner class对象和enclosing class对象之间的连接关系,你可以将inner class声明为static。一般的inner class(也就是non-static inner class)会自动记录一个reference指向enclosing class的某个对象,而后者也就是此inner class对象的制造者。但是一旦你将inner class声明为static,上述说法不成立。static inner class意味着:
1.产生其对象时,并不需要同时存在一个enclosing class对象。即不需要s.getSelector()中的s
2.你无法在static inner class对象中访问enclosing class对象。
另一方面:non-static inner class内的所有数据和函数都只能位于class的外层(此语只是一种形容,没有实际的技术意义),所以它不能够拥有任何static data,static fields,static inner class。然而static inner class可以拥有这些东西。

package test1;

interface Contents{
    public int value();
    }
interface Destination{
    public String readLabel();
}

public class Parcel10 {
    private static class pContents implements Contents{
        private int i = 11;
        public int value(){return i;}
    }
    protected static class pDestination implements Destination{
        private String label;
        private  pDestination(String whereTo){
            label = whereTo;
        }
        public String readLabel(){
            return label;
        }
        //static class可以包含static elements
        public static void f(){}
        static int x = 10;
        static class AnotherLevel{
            public static void f(){}
            static int x = 10;
        }
    }

    public static Destination dest(String s){
        return new pDestination(s);
    }
    public static Contents cont(){
        return new pContents();
    }

    public static void main(String[] args) {
        Contents c = cont();
        Destination d = dest("tianjin");
    }
}

main函数中完全不需要Parcel10对象,它只要采用一般用来选择static成员的语法,调用”传回reference(指向Contents)和reference(指向Destination)“的函数即可。
一般而言,你不能将任何程序代码置于interface内,但static inner class却可以是interface中的一部分,这是因为class既然被声明为static,那也就不会破坏interface的规则–static inner class只不过是被置于interface的命名空间中罢了。
non-static inner class之中对于外围class对象的连接关系,是通过一个特殊的this reference形成。
注意两点:
1.在需要产生一个reference指向outer class对象时,命名方式便是在outer class名称之后紧接一个句号,然后再接this。举例来说,class Sequence.SSelector内的任何函数都可以通过Sequence.this来产生一个reference指向Sequence。产生出来的reference会自动被设定正确性别。这样,编译期即可得知确切型别并加以检查,所以不会有执行期的额外负担。(inner class内部产生outer class对象)
2.要产生inner class对象,就需要先产生一个outer class对象,否则无法得到该对象。因此,除非你已经拥有了一个outer class对象,否则便无法产生其inner class对象。这是因为inner class对象会被暗中连接到某个outer class对象上,后者即该inner class对象的制造者。但是对于static inner class,那就不需要一个reference指向outer class对象。(outer class中产生inner class对象)
继承inner class:
由于inner class的构造函数必须连接到一个reference指向outer class对象身上(应该是编译器自动完成),所以当你继承inner class时,事情变得复杂些。问题出在“指向outer calss对象”的那个神秘reference必须被初始化,但derived class之内不存有可连接的缺省对象。这个问题的答案是,使用专用语法,明确产生该关联性。
代码如下:

package test1;
class WithInner{
    public WithInner(){
        System.out.println("withinner");
    }
    //内部类
    class Inner{
        public Inner(){
            System.out.println("inner");
        }
    }
}

public class InheritInner extends WithInner.Inner{
    //不能够被编译
    //!public InheritInner() {
    //}
    InheritInner(WithInner wi){
        wi.super();
        System.out.println("inheritinner end");
    }

    public static void main(String[] args) {
        WithInner wi = new WithInner();
        InheritInner ii = new InheritInner(wi);
    }
}

结果如下:

withinner
inner
inheritinner end

InheritInner继承的是inner class而非outer class,但是当编译至构造函数时,default构造函数有问题;而且你也不能够只是传入一个reference指向outer object,你还必须在构造函数中使用一下语法:
enclosingClassReference.super();这么一来便能提供所需的reference,而程序也能顺利编译下去。
如果删除:

InheritInner(WithInner wi){
        wi.super();
        System.out.println("inheritinner end");
    }

在new InheritInner(wi);处会报错,显示:The constructor InheritInner(WithInner) is undefined。

为什么需要inner class?
一般来说,inner class会继承某个class或实现某个interface,而且inner class内的程序代码会操作其outer class对象。可以这样说,inner class所提供的其实是针对outer class的某种窗口。
有个问题直指inner class的核心:如果我只需要”指向某个interface“的reference,为什么我不直接让outer class实现该interface呢?答案是,如果这么做能符合你的需求,你确实应该这么做。那么,”由inner class实现interface“和”由outer class实现interface“两者之间的区别究竟在哪儿?答案是后者将无法总是享受到interface的便利性–有时候你得下探实现细目。关于inner class的存在,最信服的理由就是:
每个inner class都能够各自继承某一实现类。因此,inner class不受限于outer class是否已继承自某一实现类。
从另一个角度看,它是多重继承问题的完美解决方案。interface能够解决其中一部分问题,但inner class才能实际允许你继承自多个实现类。如果你拥有的不是interface,而是抽象或实在的class,你就必须使用inner class来解决”多重继承“的问题。
通过inner class,你可以拥有下列几个额外性质:
1.inner class可以拥有多分实体(instance),每个实体都拥有专属的状态信息,而这些信息和outer class对象的信息是相互独立的。
2.在单一outer class内你可以拥有多个inner class,每个都实现相同的interface,或以不同方式继承同一个class。(???)
3.产生inner class对象的时间点,不见得必须和产生outer class对象同时。
4.outer class和inner class之间不存在is-a的关系,inner class就是一个独立的个体。
举个例子,如果Sequence.java不使用inner class,那么你就得宣称”Sequence是个Selector“,而且对特定某个Sequence而言,你只能拥有单一的Selector。另外,如果你希望拥有第二个函数,getRSelector(),令它产生一个“回头走”的Selector,那么你必须采用inner class,才能有如此弹性。
Closures(终结)和callbacks(回调)
所谓closure是一种可被调用的对象,他会记录一些信息,这些信息来自它的产生地所在的程序范畴(scope)。在callback机制底下,某个对象被赋予一些信息,这些信息允许该对象在稍后某个时间点上调用原先的对象。让inner class提供closure功能,是完美解决方案,比起指针来说,不但更具弹性,而且安全许多。代码示例如下:

package test1;
//用inner class实现callbacks
interface Incrementable{
    void increment();
}
//简单实现接口
class Callee1 implements Incrementable{
    private int i = 0;
    public void increment(){
        i++;
        System.out.println(i);
    }
}

class MyIncrement{
    public void increment(){
        System.out.println("other operation");
    }
    public static void f(MyIncrement mi){
        mi.increment();
    }
}
//如果你的class要以其他方式实现increment(),你应该用inner class
class Callee2 extends MyIncrement{
    private int i =0;
    private void incr(){
        i++;
        System.out.println(i);
    }
    //inner class
    private class Closure implements Incrementable{
        public void increment(){
            incr();
        }
    }
    Incrementable getCallbacksReference(){
        return new Closure();
    }
}
class Caller{
    private Incrementable callbackReference;
    Caller(Incrementable cbh){
        callbackReference = cbh;
    }
    void go(){
        callbackReference.increment();
    }
}


public class Callbacks {
    public static void main(String[] args) {
        Callee1 c1 = new  Callee1();
        Callee2 c2 = new Callee2();
        MyIncrement.f(c2);
        Caller caller1 = new Caller(c1);
        Caller caller2 = new Caller(c2.getCallbacksReference());
        caller1.go();
        caller1.go();
        caller2.go();
        caller2.go();
        for (int i = 0; i < 10; i++) {
            caller1.go();
        }
    }
}

结果:

other operation
1
2
1
2
3
4
5
6
7
8
9
10
11
12

就程序代码而言,Callee1无疑是较简单的方法,Callee2继承自MyIncrement,后者拥有另一个不同的increment(),这个函数会执行一些动作,而这些动作和Incrementable interface预期应该要做的事毫无关联。当MyIncrement被Callee2继承,你无法重写increment()以为Incrementable所用。所以你得利用inner class另行提供一份独立的实现码。请注意,当你撰写inner class时,你并不会将任何东西加入outer class的接口,或修改该接口。
inner class很单纯地藉由“实现Incrementable”来提供与Callee2之间的关联。这个关联很安全。
Caller于其构造函数中接受Incrementable reference作为引数,而且在某段时间之后,它会使用这个reference来“回头调用”Callee class。
callback的价值在于其弹性–你可以在执行时期动态决定究竟调用哪个函数。

你可能感兴趣的:(java编程思想)