美文网首页
Java编程思想(八)

Java编程思想(八)

作者: MikeShine | 来源:发表于2019-05-13 22:02 被阅读0次

第10章 内部类

所谓内部类,就是把一个类的定义放在另外一个类的内部。(前面Cha9中的接口就可以这么做,类当然也可以这么做。)
官方定义给的是:内部类允许把逻辑相关的类组织在一起,并控制内部类的可见性

10.1 创建内部类

书上给了两个例子,这里我们看一下第2个例子。
这个例子给出了一个经典的情况,外部类有一个方法,返回一个指向内部类的应用。正如下面代码中的 to() 和 contents() 方法那样。

package innerClasses;

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){   //  to()方法用来生成Des类
        return new Destination(s);
    }
    public Contents contents(){return new Contents();} // 同上
    public void ship(String dest){
        Contents c = contents();
        Destination d = to(dest);
        System.out.println(d.readLabel());
    }

    public static void main(String args[]){
        Parcel2 p = new Parcel2();
        p.ship("Tasmania");
        Parcel2 q = new Parcel2();
        //
        Parcel2.Contents c = q.contents();
        Parcel2.Destination d = q.to("FUxing");
    }
}
/*output
Tasmania
*/

10.2 链接到外部类

生成一个内部类的对象时,内部类可以访问外围类的所有方法和字段。看下面这个例子。

package innerClasses;

interface Selector{
    boolean end();
    Object current();
    void next();
}

public class Sequence {
    private Object[] items;
    private int next = 0;
    public Sequence(int size){ items = new Object[size]; }
    public void add(Object x){
        if(next < items.length)
            items[next++] = x;
    }
    private class SequenceSelector implements Selector{ // 内部类
        private int i = 0;
        public boolean end(){
            return i == items.length;  // 内部类可以访问外围类的方法和字段
        }
        public Object current() { return items[i];}
        public void next(){ if(i<items.length) i++;}
    }
    public Selector selector(){
        return new SequenceSelector();
    }

    public static void main(String args[]){
        Sequence sequence = new Sequence(10);
        for(int i=0;i<10;i++) sequence.add(Integer.toString(i));
        Selector selector = sequence.selector();  // 向上转型
        while(!selector.end()){  // 绑定调用方法
            System.out.print(selector.current() +" ");
            selector.next();
        }
    }
}
/*outputs
0 1 2 3 4 5 6 7 8 9
*/

10.3 使用 .this 与 .new

生成对 外部类对象的引用,可以使用 ClassName.this

package innerClasses;

public class DotThis {
    void f(){System.out.println("DotThis.f()");}
    public class Inner{
        public DotThis outer(){
            return DotThis.this;   // 对外部类对象的引用
        }                          //   外部类名.this
    }
    public Inner inner(){ return new Inner();}

    public static void main(String args[]){
        DotThis dt = new DotThis();
        DotThis.Inner dti = dt.inner();   // 内部类对象
        dti.outer().f();   // 内部类对象的 outer()方法通过 
        // 对外部类对象的引用生成一个外部类,调用其 f()方法
    }
}
/*outputs
DotThis.f()
*/

如果要创建某个内部类对象,需要使用 [外部对象.new 内部类名]

package innerClasses;

public class Parcel3 {
    class Contents{
        private int i = 11;
        public int value(){return i;}
    }
    class Destination{
        private String lable;
        Destination(String whereTo) {lable = whereTo;}
        String readLable(){ return lable;}
    }
    
    public static void main(String args[]){
        Parcel3 p = new Parcel3();
        // 创建 内部类对象 使用  外部类对象.new 内部类对象 语法。
        Parcel3.Contents c = p.new Contents(); 
        Parcel3.Destination d = p.new Destination("Fuxing");
    }
}

10.4 内部类与向上转型

10.2 中的代码例子中就包含向上转型。
这里书上给的代码说明了一下内部私有类的保护类,和私有类与保护类的意思基本一样。

10.5 在方法和作用域内的内部类(局部内部类)

这里说了两个点:

  • 方法内部的类
package innerClasses;

// 局部内部类的 Demo
//  方法内部的类

public class Parcel5 {
    public Destination destination(String s){  // dest() 方法
        class PDestination implements Destination{  // 局部内部类,只能在方法内部访问
            private String label;
            private PDestination(String whereTo){
                label = whereTo;
            }
            public String readLabel(){return label;}
        }
        return new PDestination(s);  // 方法内部访问局部内部类
                                    // 这并不是 dest()方法执行完,局部内部类就不可用了
    }

    public static void main(String args[]){
        Parcel5 p = new Parcel5();
        Destination d = p.destination("FUxing");
    }
}
  • 作用域内部的类,与上面唯一的不同就是不能在范围之外调用。
package innerClasses;

// 在任意作用域内嵌入一个内部类

public class Parcel6 {
    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();
        }
        //  不能在范围外使用
        //! TrackingSlip ts = new TrackingSlip("x")
    }
    public void track(){internalTracking(true);} // 通过调用方法来调用局部内部类

    public static void main(String args[]){
        Parcel6 p = new Parcel6();
        p.track();
    }
}

10.5 匿名内部类

很多时候,我们有些类只是用一次,这时候再去写一个类的定义,岂不是很麻烦。所以就有了匿名内部类的概念,就是说没有名字的内部类,只能使用一次,通常用简化代码编写
将 内部类的 生成定义 结合在一起。
这个东西第一次理解有一点抽象:

package innerClasses;

// 匿名内部类

public class Parcel7 {
    public Contents contents(){  // 匿名内部类
                                    // 将返回值的生成 和 返回值类的定义 结合在一起
        return new Contents(){
            private int i = 11;
            public int value(){return i;}
        };  // 注意这里的分号
    }

    public static void main(String args[]){
        Parcel7 p = new Parcel7();
        Contents c = p.contents();
    }
}

/* 将返回值的生成 和 返回值类的定义 结合在一起
*  上面的代码是下面的简写版本
* public MyContents implements Contents{
*           private int i = 11;
*           public int value(){return i;}
*       }
* public Contents contents(){return new MyContents();}
*/

注意这里的类是匿名的。这种语法好像是说,正在创建一个新的Contents对象,但是(在分号结束之前),你却说:等一等,在这里加入一个类的定义(大括号中的就是匿名类的定义)。

  • 如果匿名内部类定义的内部直接使用外部的参数,那么这个参数需要被定义成为 final 的。
package innerClasses;

// 匿名内部类内部使用到外部的参数,这些参数需要用 final 修饰

public class Parcel10 {
    public Destination destination(final String dest, final float price){
        return new Destination(){
            private int cost;
            { // 匿名内部类中,需要有 花括号括起来这些没有方法名的代码
                cost = Math.round(price);
                if(cost>100)
                    System.out.println("Over budget");
            }
            private String label;
            public String readLabel(){return label;}
        };
    }

    public static void main(String args[]){
        Parcel10 p = new Parcel10();
        Destination d = p.destination("Fuxing",200);
    }
}
/*output
Over budget
*/
  • 使用实例初始化,实现匿名内部类的构造器
package innerClasses;

// 含参构造器的匿名内部类

abstract class Base{
    public Base(int i){
        System.out.println("Base Cons i: " + i);
    }
    public abstract void f();
}

public class AnonymousCons {
    public static Base getBase(int i){
        return new Base(i){   // 这里的 i 没有直接在匿名类内部使用。
                            // 不用加 final 参数
            {System.out.println("内部实例初始化");}
            public void f(){System.out.println("内部匿名f()");}
        };
    }

    public static void main(String args[]){
        Base base = getBase(47); // 静态方法直接调用
        base.f();
    }
}
/*output
Base Cons i: 47
内部实例初始化
内部匿名f()
*/

10.5.1 用匿名内部类实现工厂方法

之前在第9章中说过工厂方法,所谓工厂方法就是用一个方法来生成接口对象。
这里也是一样的,不过用匿名内部类的方式来实现,也是把 内部类 生成 和 定义 结合在一起。看下面的代码(对比之前第9章中的代码)

package innerClasses;

// 用匿名内部类来实现工厂方法

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

class Implementation1 implements Service{
    private Implementation1(){}
    public void method1() {System.out.println("实现1.方法1");}
    public void method2() {System.out.println("实现1.方法2");}
    public static ServiceFact factory =
            new ServiceFact(){   // 将 内部类的生成 和 定义 结合在一起。
                public Service getService(){
                    return new Implementation1();}
            };
}
class Implementation2 implements Service{
    private Implementation2(){}
    public void method1() {System.out.println("实现2.方法1");}
    public void method2() {System.out.println("实现2.方法2");}
    public static ServiceFact factory =  // 这里用 static 声明单一静态变量
            new ServiceFact(){
                public Service getService(){
                    return new Implementation2();
                }
            };
}

public class Factories {
    public static void serviceConsumer(ServiceFact fact){
        Service s = fact.getService();
        s.method1();
        s.method2();
    }

    public static void main(String args[]){
        serviceConsumer(Implementation1.factory);
        serviceConsumer(Implementation2.factory);  // 静态变量
    }
}
/*output
实现1.方法1
实现1.方法2
实现2.方法1
实现2.方法2
*/

10.7 嵌套类

之前我们说到,创建一个内部类对象的时候需要用 [外部类对象.new 内部类名]这样的方法。而当不需要内部类对象和外部类对象之间的关系的时候,我们就需要用到嵌套类。即内部类声明为 static。
嵌套类意味着:

  • 创建嵌套类对象,不需要外围类对象
  • 不能从嵌套类对象中访问非静态的外围类对象
package innerClasses;

//  嵌套类,是 static 的类,可以直接调用,不用外部类对象来生成内部类对象

public class Parcel_11 {
    private static class ParcelContents implements Contents{
        private int i = 11;
        public int value(){return i;}
    }
    protected static class ParcelDestination implements Destination{
        private String label;
        private ParcelDestination(String whereTo){
            label = whereTo;
        }
        public String readLabel(){ return label; }
        // 可以含有 static 元素
        public static void f(){}
        static int x = 10;
    }
    public static Destination destination(String s){
        return new ParcelDestination("Fuxing");
    }
    public static Contents contents(){return new ParcelContents();}

    public static void main(String args[]){
        // 在这个main()函数中,没有任何 Parcell_11对象时必须的
// 这就是 static 类的优势
        Contents c = contents();
        Destination d = destination("Fuxing");
    }
}

10.7.1 接口内部的类

之前说过,接口的内部只能有方法名,而不能有其他代码。这里有一个特例,嵌套类可以存在于接口内部。接口中的类都自动设置为public 和 static。

知道有这个东西就行了

10.7.2 从多层嵌套类中访问外部类的成员

意思是说,不管嵌套多少层,内部类都可以访问外层类的所有方法和数据成员,正如 10.2 所讲的那样。

10.8 为什么需要内部类

官方定义是:
每个内部类都可以独立的继承自一个(接口的)实现,无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
基于上面这个定义,内部类在实现 多继承 结构时,优点就显现出来了。
书上给了两个例子:

  • 这个例子中,使用单一类和内部类都可以实现多继承结构。
package innerClasses;

interface A {}
interface B {}

class X implements A,B{}  // 单一类实现多继承

class Y implements A{  // 内部类实现多继承
    B makeB(){      // 匿名内部类
        return new B(){};
    };
}

public class MultipleInterface {
    static void takesA(A a){}
    static void takesB(B b){}
    public static void main(String args[]){
        X x = new X();
        Y y = new Y();
        takesA(x);
        takesA(y);
        takesB(x);
        takesB(y.makeB());
    }
}

  • 如果是含有抽象的类或者具体的类,而不是接口,就只能用内部类的形式来实现多继承。
package innerClasses;

class D{}
abstract class E{}

class Z extends D{
    E getE(){
        return new E(){}; // 因为这个是没有实现的抽象类,
                        // 所以要加这个大括号
    };
}

public class MultipleImple {
    static void takesD(D d){};
    static void takesE(E e){};

    public static void main(String args[]){
        Z z = new Z();
        takesD(z);
        takesE(z.getE());
    }
}

记住内部类在实现多继承结构时候有着独特的优势。

10.8.1 闭包 closure 与 回调 callback

这一节书上说的东西基本看不懂。给的例子也不懂要说什么意思,只是明白运行过程而已。
之后在别人的博文中大概理解了一下闭包和回调。

  • 闭包:闭包能够将一个 方法 作为一个 变量 去存储,这个方法有能力去访问所在类的自由变量。

具体关于闭包的东西还没看懂

  • 回调:类A的 a()方法调用 类 B 的 b() 方法,然后B中的方法(可以是b方法)再调用 A 的 c() 方法。
    参考 如何理解回调

10.9 内部类的继承

既然是类,就可以继承。但是内部类的继承遵循一定的规则,必须在构造器内使用特殊语法,使得继承内部类指向外围类的引用。

package innerClasses;

class Mike{
    class Inner{}
}

// 继承内部类
public class InheritInnerClass extends Mike.Inner{
    InheritInnerClass(Mike m){  // 构造函数一定写成这样的语法
        m.super(); 
    }

    public static void main(String args[]){
        Mike m = new Mike();
        InheritInnerClass i = new InheritInnerClass(m);
    }
}

10.11 局部内部类

上面10.5小节提到过,对于定义在方法和域内的内部类,叫做局部内部类。书上在这里专门给了一个小节,来对比了一下局部内部类和匿名内部类的创建方式的不同之处。

package innerClasses;

// 对比一下局部内部类和匿名内部类创建方式的区别

interface Counter{
    int next();
}

public class LocalInnerClass {
    private int count = 0;
    Counter getCount(final String name){  // 之前说过这里必须是final,供内部类调用
        // 一个局部内部类
        class LocalCounter implements Counter{
            public LocalCounter(){
                // 局部内部类可以有一个构造器
                System.out.println("LocalCounter()");
            }
            public int next(){
                System.out.println(name);
                return count++;
            }
        }
        return new LocalCounter();
    }
    /*上面是局部内部类的创建方式,下面看匿名类的创建方式*/

    Counter getCount2(final String name){
        return new Counter(){
            {System.out.println("Counter");}
            public int next(){
                System.out.println(name);
                return count++;
            }
        };
    }

    public static void main(String args[]){
        LocalInnerClass lic = new LocalInnerClass();
        Counter
                c1 = lic.getCount("局部内部类"),
                c2 = lic.getCount2("匿名内部类");
        for(int i=0;i<5;i++) System.out.print(c1.next());
        for(int i=0;i<5;i++) System.out.print(c2.next());
    }
}


其实上述代码中的两种情况之前都讲过了。这里大概看一下就行了。

10.13 总结

总的来说,这一项内部类讲了很多东西,感觉有点乱,也不是很有条理,不是很明白作者的逻辑。
除了书上,还可以通过关于内部类的总结来学习一下,该博文将内部类总结为:成员内部类、局部内部类、匿名内部类、静态内部类这四种。其实在本章的读书笔记中都有,不过正如之前所说,很乱。可以参考别人的总结学习一下,事半功倍。

相关文章

网友评论

      本文标题:Java编程思想(八)

      本文链接:https://www.haomeiwen.com/subject/qmnraqtx.html