内部类

一.基本概念

内部类:将一个类的定义放在另一个类的定义内部。内部类机制可以把逻辑相关的类组织在一起,并控制位于内部的类的可视性。

内部类与组合是完全不同的概念。

内部类不仅是一种代码隐藏机制(将类置于其他类的内部),还能与外围类通信。

 

二.成员内部类

类似于外围类的成员的内部类。

2-1 链接到外部类

内部类可以访问其外围类的所有字段和方法(所有成员)创建一个普通内部类对象需要先创建其外围类的对象,然后使用该外围类对象取创建内部类对象。当某个外围类创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用。然后就可使用这个引用来访问外围类对象的成员。

package com.hutao.page.page192;

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 {
        //内部类访问外围类的items成员
        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(i);
        }
        Selector selector = sequence.selector();
        while (!selector.end()) {
            System.out.print(selector.current() + " ");
            selector.next();
        }
    }
}

运行结果为:

0 1 2 3 4 5 6 7 8 9 
Process finished with exit code 0

 

2-2 使用 外围类名.this

如果需要在内部类中获取外围类对象的引用,可以使用 外围类名.this 来获取

package com.hutao.page.page193;

public class DoThis {
    void f() {
        System.out.println("DoThis.f()");
    }

    public class Inner {
        public DoThis outer() {
            //使用外围内类名.this获取外围类对象的引用
            return DoThis.this;
        }
    }

    public Inner inner() {
        return new Inner();
    }

    public static void main(String[] args) {
        DoThis doThis = new DoThis();
        DoThis.Inner inner = doThis.inner();
        DoThis outer = inner.outer();
        outer.f();
    }
}

运行结果为:

DoThis.f()

Process finished with exit code 0

2-3 使用 外围类对象.new

要直接创建普通内部类对象,必须使用外围类的对象来创建该内部类对象。这也解决了内部类名字作用域的问题,即使用外围类对象只能创建定义在外围类中的内部类的对象。

package com.hutao.page.page193;

public class DotNew {
    public class Inner{}

    public static void main(String[] args) {
        DotNew dot = new DotNew();
        DotNew.Inner inner = dot.new Inner();
    }
}

因此,就不必声明(也不能声明)外围类的类名,如inner = dot.new DotNew.Inner();

在拥有外围类对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗地连接到创建它的外围类对象上。但是,如果你创建的是嵌套类(静态内部类),那就不需要先创建外围类对象。

2-4 外围类访问内部类

先看一下内部类访问外围类成员的方式:如果想要访问外围类的某个成员,直接使用外围类这个成员的名字就可以。

class Outer{
    private String outerName = "Outer";
    class Inner{
        private String innerName = "Inner";
        //内部类访问外围类的private成员
        String getOuterName(){
            //直接使用外围类成员的名字
            return outerName;
        }
    }
}

但是,如果外围类要访问内部类的成员的话就不能使用这种方式(即直接使用内部类成员的名字):

class Outer{
    private String outerName = "Outer";
    class Inner{
        private String innerName = "Inner";
        String getOuterName(){
            return outerName;
        }
    }

    String getInnerName(){
        /**
         * 尝试直接使用内部类成员的名字
         * 报错:Cannot resolve symbol 'innerName'
         */
        return innerName;
    }
}

正确的方式应该先创建内部类的对象再通过此对象去访问:

package com.hutao.test;

class Outer{
    private String outerName = "Outer";
    class Inner{
        private String innerName = "Inner";
        String getOuterName(){
            return outerName;
        }
    }

    String getInnerName(){
        //在外围类内部创建内部类对象就正常创建就好了,不需要先创建一个外围类对象。
        Inner inner = new Inner();
        return inner.innerName;
    }
}

public class Test {
    public static void main(String[] args) {
        Outer outer = new Outer();
        String innerName = outer.getInnerName();
        System.out.println(innerName);
    }
}

内部类可以直接通过外围类的成员名去访问外围类成员,而外围类需要通过内部类的对象去访问内部类成员。造成这种差别的原因可能是:

  在使用内部类对象时,可保证其外围类对象一定被创建了,且内部类对象一定有一个指向其外围类对象的引用,所以内部类可以通过成员名直接访问(可能编译器会在成员名前自动拼接 外围类对象引用.);而在使用外部类对象时,并不能保证内部类对象一定被创建了,所以需要先创建内部类对象,然后通过这个内部类对象去访问内部类成员。

 

三.在方法中和作用域中的内部类

可以在一个方法里面或在任意作用域(方法的作用域:if(isOk){作用域}、类的作用域)中定义内部类。这么做有两个理由:

  1. 在方法中实现了某个接口,可以创建并返回对其的引用;
  2. 要解决一个复杂的问题,想要创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的。

3-1定义在方法中的类

在方法的作用域内(而不是在其他类的作用域内)创建一个完整的类,这被称作局部内部类。

package com.hutao.page.page194;

public interface Destination {
    String  readLabel();
}
package com.hutao.page.page195;

import com.hutao.page.page194.Destination;

public class Parcel5 {
    public Destination destination(String s) {
        class PDestination implements Destination {
            private String label;

            private PDestination(String whereTo) {
                label = whereTo;
            }

            public String readLabel() {
                return label;
            }
        }
        return new PDestination(s);
    }

    public static void main(String[] args) {
        Parcel5 p = new Parcel5();
        Destination d = p.destination("Tasmania");
        System.out.println(d.readLabel());
    }
}

运行结果为:

Tasmania

Process finished with exit code 0

注意:

  1.PDestination类是destination方法的一部分,而不是Parcel5的一部分。所以,在destination()之外不能访问PDestination;

  2.在destination()方法中定义了内部类PDestination,并不意味着一旦destination()方法执行完毕,PDestination就不可用了。

3-2定义在方法内作用域中的类

package com.hutao.page.page196;

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 trackingSlip = new TrackingSlip("slip");
            String s = trackingSlip.getSlip();
            System.out.println(s);
        }
        //Can't use TrackingSlip here, Out of scope.
        //!TrackingSlip trackingSlip = new TrackingSlip("x");
    }

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

TrackingSlip类被嵌入在if语句的作用域内,这并不是说该类本身的创建是有条件的,它其实会与其他类一起被编译。然而,在定义TrackingSlip的作用域之外,它是不可用的;除此之外,它与普通类一样。

3-3实现了接口的匿名内部类

package com.hutao.page.page194;

public interface Contents {
    int value();
}
package com.hutao.page.page197;

import com.hutao.page.page194.Contents;

public class Parcel7 {
    public Contents contents() {
        return new Contents() {
            private int i = 11;

            public int value() {
                return i;
            }
        };//Semicolon required in this case.
        //这里需要分号的原因可理解为:return语句的结束。
    }

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

return语句的含义为:创建一个实现(继承)了Contents接口(类)的匿名类对象。

3-4扩展了有非默认构造器的类的匿名内部类

有非默认构造器的类Wrapping

package com.hutao.page.page197;

public class Wrapping {
    private int i;

    public Wrapping(int x) {
        i = x;
    }

    public int value() {
        return i;
    }
}

匿名类Parcel8扩展Wrapping类

package com.hutao.page.page197;

public class Parcel8 {
    public Wrapping wrapping(int x){
        return new Wrapping(x){
            public int value(){
                return super.value()*47;
            }
        };
    }

    public static void main(String[] args) {
        Parcel8 p = new Parcel8();
        Wrapping w = p.wrapping(10);
        System.out.println(w.value());
    }
}

运行结果为:

470

Process finished with exit code 0

对定义匿名内部类的分析:

目前来看,定义匿名内部类的方式为:

return new 接口名(){
    //实现类的内容
};   
return new 基类名(基类构造器的参数){
    //子类的内容
};   

1.当是 new 接口名()时,创建的就是一个实现类对象;

2.当是 new 基类名(基类构造器的参数)时,创建的是一个子类对象,但在创建子类对象中包含的基类对象时调用的就是new关键字后指定的基类构造器

3-5执行字段初始化的匿名内部类

在匿名类中定义字段时,还能够对其执行初始化操作。

package com.hutao.page.page198;

import com.hutao.page.page194.Destination;

public class Parcel9 {

    public Destination destination(final String dest) {
        return new Destination() {
            private String label = dest;

            public String readLabel() {
                return label;
            }
        };
    }

    public static void main(String[] args) {
        Parcel9 p = new Parcel9();
        Destination d = p.destination("Tasmania");
        System.out.println(d.readLabel());
    }
}

运行结果为:

Tasmania

Process finished with exit code 0

如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象(可以是方法传入的参数,也可以是外围类的成员),那么编译器会要求这个对象是final的。

注:匿名内部类(子类)本身使用外部对象才要求是final的,但如果只是基类使用而匿名内部类自身不使用,则不需要是final的。

上面的例子中的匿名内部类使用的在其外部定义的对象是通过方法的参数传入的,也可以直接使用外围类的成员(private成员默认是final的)。

package com.hutao.page.page198;

import com.hutao.page.page194.Destination;

public class Parcel9 {
    private String dest = "Tasmania";

    public Destination destination() {
        return new Destination() {
            private String label = dest;

            public String readLabel() {
                return label;
            }
        };
    }

    public static void main(String[] args) {
        Parcel9 p = new Parcel9();
        Destination d = p.destination();
        System.out.println(d.readLabel());
    }
}

运行结果为:

Tasmania

Process finished with exit code 0

3-6通过实例化实现构造的匿名内部类

匿名类因为没有名字,所以不可能有构造器。

通过实例初始化(本质就是通过普通代码块),就能达到为匿名内部类创建一个构造器的效果。

package com.hutao.page.page199;

import com.hutao.page.page194.Destination;

public class Parcel10 {
    public Destination destination(final String dest, final float price) {
        return new Destination() {
            private int cost;

            //使用代码块初始化cost字段。cost字段可直接被初始化,但是却不能像构造器一样对成员做其他处理(如if判断等)。
            {
                cost = Math.round(price);
                if (cost > 100) {
                    System.out.println("over budget!");
                }
            }

            private String label = dest;

            public String readLabel() {
                return label;
            }
        };
    }

    public static void main(String[] args) {
        Parcel10 p = new Parcel10();
        p.destination("Tasmania", 101.395f);
    }
}

运行结果为:

over budget!

Process finished with exit code 0

匿名内部类与正规的继承相比有些受限,因为匿名内部类既可以扩展类,也可以实现接口,但是不能两者兼备。而且如果是实现接口,也只能实现一个接口

 

四.嵌套类(静态内部类)

如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为static。这通常称之为嵌套类或者静态内部类。

普通的内部类对象隐式地保存了一个引用,指向它的外围类对象。而嵌套类对象就没有这个引用,这意味着:

  1. 要创建嵌套类对象,不需要其外围类对象;
  2. 不能从嵌套类对象中访问非静态的外围类对象。

普通的内部类中不能有static数据和static字段,也不能包含嵌套类(也是static的),但是嵌套类可以包含这些静态的东西。其原因可认为是:

普通内部类可看作是外围类的非静态成员,要求它的初始化必须在其外围类对象创建之后进行。在创建内部类对象的时候,会先加载外围类,再加载内部类。类加载完成后就会初始化类的静态成员,如果内部类有静态成员,那么就会在类加载后初始化内部类的静态成员,但这个时候其外围类对象还未创建,这与要求冲突。所以不允许普通内部类包含静态成员。

package com.hutao.page.page201;

import com.hutao.page.page194.Contents;
import com.hutao.page.page194.Destination;

public class Parcel11 {
    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;
        }

        //Nested classes can contain other static elements.
        public static void f() {
        }

        static int x = 10;

        static class AnotherLevel {
            public static void f() {
            }

            static int x = 10;
        }
    }

    public static Destination destination(String s) {
        //外围类直接访问内部类的私有成员
        return new ParcelDestination(s);
    }

    public static Contents contents() {
        return new ParcelContents();
    }

    public static void main(String[] args) {
        Contents c = contents();
        Destination d = destination("Tasmania");
        System.out.println(d.readLabel());
    }
}

这个例子中还有一个需要注意的地方:外围类能直接访问静态内部类的静态私有成员(构造器)。所以总结一下外围内访问内部类的规律:

  1. 外围类可通过创建内部类对象访问内部类的所有成员(包括私有成员);
  2. 外围类可直接访问(构造器或者类名.静态成员名)静态内部类的所有静态成员(包括私有静态成员)。

4-1接口中的类

接口中的变量默认是public static final的;

接口中的方法默认是public的;

接口中的类默认是public static的,也就是说,接口中的类默认是静态内部类。

package com.hutao.exercise.page203;

interface I {
    void f();

    void g();

    //默认是public static的
    class Nested {
        static void call(I impl) {
            System.out.println("Calling I.f()");
            impl.f();
            System.out.println("Calling I.g()");
            impl.g();
        }
    }
}

public class E_21 {
    public static void main(String[] args) {
        I impl = new I() {
            public void f() {}

            public void g() {}
        };
        I.Nested.call(impl);
    }
}

运行结果为:

Calling I.f()
Calling I.g()

Process finished with exit code 0

 

posted @ 2021-06-28 16:36  certainTao  阅读(92)  评论(0)    收藏  举报