java编程思想笔记4(ch10:内部类)

ch10:内部类(inner class)

定义

将一个类的定义放在另一个类的定义内部,这就是内部类。

创建内部类

创建内部类的方式就如定义一样,放置在外围类里面就行:

public class Parcel1 {
    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;
        }
    }
    // using inner classes looks just like
    // using any other class. withing Parcel1:
    public void ship(String dest) {
        Contents c = new Contents();
        Destination d = new Destination(dest);
        System.out.println(d.readLabel());
    }
    public static void main(String[] args) {
        Parcel1 p = new Parcel1();
        p.ship("Tasmania");
    }
    /**
     * output:
     * Tasmania
     */
}

在ship()方法里面使用内部类时,与普通类没有什么区别。
更常用的是外围类定义一个方法只想内部类,代码如下:

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 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();
        //Defining references to inner class:
        Parcel2.Contents c = q.contents();
        Parcel2.Destination d = q.to("Borneo");
    }
    /**
     * output:
     * Tasmania
     */
}

如果想从外部类的非静态方法之外的任意位置创建某个内部类对象,那么必须指明具体的外部类名:OuterClassName.InnerClassName

链接到外部类

以上讲的内部类还只是一种代码隐藏和组织代码的模式。
当然还有其他用途,当生成一个内部类对象时,此对象与制造它的外围对象(enclosing object)之间就有了一种联系,所以可以随意访问外围对象的所有成员,不需要任何特殊条件。此外,还拥有外围类的所有元素的访问权。
示例代码如下:

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;
        @Override
        public boolean end() {
            return i == items.length;
        }
        @Override
        public Object current() {
            return items[i];
        }
        @Override
        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();
        }
    }
    /**
     * output:
     * 0 1 2 3 4 5 6 7 8 9
     */
}

内部类可以任意访问外部类的元素。

使用.this与.new

如果需要生成对外部对象的引用,可以使用外部类加.this。这样产生的引用自动地具有正确的类型,这一点在编译器知晓识别了,因此没有任何运行时开销。

public class DotThis {
    void f() {
        System.out.println("DotThis.f()");
    }
    public class Inner {
        public DotThis outer() {
            return DotThis.this;
            //a plain "this" would be Inner's "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();
    }
    /**
     * output:
     * DotThis.f()
     */
}

直接创建内部类对象需要提供外部类对象的引用,使用.new语法创建。如下:

public class DotNew {
    public class Inner {}
    public static void main(String[] args) {
        DotNew dn = new DotNew();
        DotNew.Inner dni = dn.new Inner();
    }
}

注意,必须在有外部类的情况下才能创建内部类对象,因为内部类会暗暗地连接到创建它的外部类对象上,对于嵌套类(静态内部类),就可以不需要外部类对象的引用(也就是可以直接创建内部类对象)。

在方法和作用域内的内部类

内部类的位置是灵活的,在外部类里面,外部类的方法里面,外部类的代码块中都可以,只是位置决定了该内部类的作用范围,使用的时候需要注意。

public class Parcel5 {
    public Destination destination(String s) {
        class PDestination implements Destination {
            private String label;
            private PDestination(String whereTo) {
                label = whereTo;
            }
            @Override
            public String readLabel() {
                return label;
            }
        }
        return new PDestination(s);
    }
    public static void main(String[] args) {
        Parcel5 p = new Parcel5();
        Destination d = p.destination("Tasmania");
    }
}

PDestination是destination()方法的一部分,作用点仅在方法内部。返回的是向上转型的Destination。

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();
        }
        //can't use it here! Out of scope:
        //! TrackingSlip ts = new TrackingSlip("x");
    }
    public void track() {
        internalTracking(true);
    }
    public static void main(String[] args) {
        Parcel6 p = new Parcel6();
        p.track();
    }
}

上面的代码,TrackingSlip类被嵌入在if语句的作用域内,并不是说该类的创建是有条件的,它其实与别的类一起编译过了。除了作用域外,与普通类一样。

匿名内部类

创建一个继承自接口或者类的一个匿名类的对象,通过new表达式返回的引用被自动向上转型为对父类(接口或者类)的引用。

class Contents {
    private int i = 11;
    public int value() {
        return i;
    }
}
public class Parcel7 {
    public Contents contents() { // insert a class definition
        return new Contents() {
            private int  i = 12;
            public int value() {
                return i;
            }
        }; // semicolon required in this case
    }
    public static void main(String[] args) {
        Parcel7 p = new Parcel7();
        Contents c = p.contents();
    }
}

以上展示的通过匿名内部类向上转型父类contents,该contents是一个实体类,一般而言,我们创建的匿名内部类以接口居多,上面展示的是对于类也是可以使用该方式。
对于需要有参数构造器的,以下展示的是带构造参数的匿名内部类。

class Wrapping {
    private int x;
    public Wrapping(int x) {
        this.x = x;
    }
    int value() {
        return x;
    };
}
public class Parcel8 {
    //构造器参数x不必是final的
    public Wrapping wrapping(int x) {
        //base constructor call:
        return new Wrapping(x) {
            @Override
            public int value() {
                return super.value() * 47;
            }
        };// semicolon required
    }
    public static void main(String[] args) {
        Parcel8 p = new Parcel8();
        Wrapping w = p.wrapping(10);
    }
}

在匿名内部类末尾的分号,并不是用来标记此内部类结束的,只是该表达式的结束,只不过这个表达式正巧包含了匿名内部类罢了。
在匿名类中定义字段时,还能够对其执行初始化操作:

public class Parcel9 {
    //匿名类对象中初始化的参数dest必须是final的
    public Destination destination(final String dest) {
        return new Destination() {
            private String label = dest;
            @Override
            public String readLabel() {
                return label;
            }
        };
    }
    public static void main(String[] args) {
        Parcel9 p = new Parcel9();
        Destination d = p.destination("Tasmania");
    }
}

匿名内部类构造器方法传入的参数因为是初始化时就定了,所以不必要求是final的,但是对于匿名内部类初始化时的参数,必须是final的。

嵌套类

如果不需要内部类对象与外围类对象有联系,可以将内部类声明为static。这个通常称为嵌套类
普通内部类对象隐式地保存了一个引用,指向创建它的外围类。
使用嵌套类需要注意:
1. 创建嵌套类,不需要外围对象
2. 不能从嵌套类的对象中访问非静态的外围类对象。

示例代码如下:

public class Parcel11 {
    private String value1 = "value1";
    private static String value2 = "value2";
    public static class Inner {
        public String getValue() {
            return "value";
            //无法获取到value1的值,因为对于该嵌套类来说是单独存在的,不能访问非静态变量
            //! return value1;
        }
        //可以获取到静态变量的值
        public String getValue2() {
            return value2;
        }
    }
    public static void main(String[] args) {
        //直接使用new OuterClassName.InnerClassName()创建嵌套类对象,
        // 前提是嵌套类的在这里是可见的
        System.out.println(new Parcel11.Inner().getValue());
    }
}

接口内部类

正常情况下,不能在接口中放置任何代码,但是嵌套类可作为接口的一部分存在,因为实在接口中,所以该类自动的为public和static属性。因为类是static的,所以只是将嵌套类置于接口的命名空间内,并不违反接口的规则。
示例:

public interface ClassInterface {
    void howdy();
    class Test implements ClassInterface {
        @Override
        public void howdy() {
            System.out.println("howdy()");
        }
        public static void main(String[] args) {
            new Test().howdy();
        }
    }
}

多层嵌套类

一个类中可以嵌套很多层,这个没有限制。
示例:

class MNA {
    private void f() {}
    class A {
        private void g() {}
        public class B {
            void h() {
                g();
                f();
            }
        }
    }
}
public class MutiNestingAccess {
    public static void main(String[] args) {
        MNA mna = new MNA();
        MNA.A mnaa = mna.new A();
        MNA.A.B mnab = mnaa.new B();
        mnab.h();
    }
}

为什么需要内部类

每个内部类都能独立地继承一个(接口)的实现,所以无论外围类是否已经继承了某个(接口)的实现,对于内部类是没有影响的。
对于实现多接口,外部类一个即可以实现,内部类也同样可以,但是对于继承(普通类和抽象类),只有通过内部类解决,即内部类比较完美的完善了多继承的问题,相对于c++来说(实现多接口只是一部分上解决了这个问题)。

class D {}
abstract class E {}
class Z extends D {
    E makeE() {
        return new E() {};
    }
}
public class MutiImplementation {
    static void taskD(D d) {}
    static void taskE(E e) {}
    public static void main(String[] args) {
        Z z = new Z();
        taskD(z);
        taskE(z.makeE());
    }
}

如果不需要解决“多重继承”的问题,自然可以使用其他方式解决,可以不使用内部类,但是如果使用内部类,还可以获得其他的一些特性:
1. 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。
2. 在单个外围类,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。
3. 创建内部类对象的时刻并不依赖于外围类对象的创建。
4. 内部类并没有令人迷惑的“is-a”关系;它就是一个独立的实体。

  • 闭包与回调
    闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。
    通过这个定义可以看出,内部类是面向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向外围类对象的引用,在此作用域内,内部类有权限操作所有成员,包括private成员。

内部类的继承

内部类有点特殊,因为其必须持有外围类的引用才能够进行初始化,所以以下示例展示了内部类的继承问题:

class WithInner {
    class Inner {}
}
public class InheritInner extends WithInner.Inner {
    //不能使用默认构造器
    //InheritInner() {}  该构造器不会编译
    InheritInner(WithInner withInner) {
        withInner.super();
    }
    public static void main(String[] args) {
        WithInner wi = new WithInner();
        InheritInner ii = new InheritInner(wi);
    }
}

如上示例代码,继承自内部类,必须在构造器方法中调用外部类中调用
enclosingClassReference.super()提供必要的引用,方法才能编译通过。

内部类可以被覆盖吗

创建一个内部类,然后继承其外围类并重新定义此内部类时,以下为示例代码:

class Egg {
    private Yolk y;
    protected class Yolk {
        public Yolk() {
            System.out.println("Egg.Yolk()");
        }
    }
    public Egg() {
        System.out.println("new Egg()");
        y = new Yolk();
    }
}
public class BigEgg extends Egg {
    //此处被不能“覆盖”基类中的内部类
    public class Yolk {
        public Yolk() {
            System.out.println("BigEgg.Yolk()");
        }
    }
    public static void main(String[] args) {
        new BigEgg();
    }
    /**
     * output:
     * new Egg()
     * Egg.Yolk()
     */
}

内部类可以明确的继承,示例:

class Egg2 {
    protected class Yolk {
        public Yolk() {
            System.out.println("Egg2.Yolk()");
        }
        public void f() {
            System.out.println("Egg2.Yolk.f()");
        }
    }
    private Yolk y = new Yolk();
    public Egg2() {
        System.out.println("New Egg2()");
    }
    public void insertYolk(Yolk yy) {
        y = yy;
    }
    public void g() {
        y.f();
    }
}
public class BigEgg2 extends Egg2 {
    public class Yolk extends Egg2.Yolk {
        public Yolk() {
            System.out.println("BigEgg2.Yolk()");
        }
        public void f() {
            System.out.println("BigEgg2.Yolk.f()");
        }
    }
    public BigEgg2() {
        insertYolk(new Yolk());
    }
    public static void main(String[] args) {
        Egg2 e2 = new BigEgg2();
        e2.g();
    }
    /**
     * output:
     * Egg2.Yolk()
     * New Egg2()
     * Egg2.Yolk()
     * BigEgg2.Yolk()
     * BigEgg2.Yolk.f()
     */
}

局部内部类

在代码块里面也可以使用类,这称之为局部内部类,其作用范围是有限的。
局部内部类不能有访问限制符,因为不是外围类的一部分,但是可以访问当前代码块内的常量,以及此外围类的所有成员。

内部类标识符

由于每个类会产生一个.class文件,其中包含了如何创建该类型的对象的全部信息(此信息产生一个“meta-class”,叫做Class对象),内部类也必须生成一个.class文件包含它们的Class对象信息。这些类文件的命名有严格的规则:外围类的名字,加上“$”,再加上内部类的名字。
如果内部类时匿名的,编译器会简单的产生一个数字作为其标识符。如果内部类时嵌套在别的内部类中,只需直接将它们的名字加载其外围类标识符与“$”的后面。

//简单的获取类名的示例代码
class Test{
    class Inner {
        public class Inner1 {}
    }
}
public class ClassNameTest {
    public static void main(String[] args) {
        Test t= new Test();
        Test.Inner inner1 = t.new Inner();
        Test.Inner.Inner1 inner11 = inner1.new Inner1();
        System.out.println(inner1.getClass().getName());
        System.out.println(inner11.getClass().getName());
    }
    /**
     * output:
     * Test$Inner
     * Test$Inner$Inner1
     */
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章