JAVA内部类详解

最近在看hystrix源码的时候,发现Netflix的牛人们,使用了大量的匿名内部类。以便更好的能够阅读,于是又回头翻了翻《java编程思想》,书中所讲、自己理解,记录如下。

内部类的定义:

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

为什么需要内部类

    如果只是将一个类定义在另一个类的内部,那不就是相当于把两个类简单的组合在一块。其实内部类实现的功能远不止如此。它可以有以下优点:

  1. 内部类可以与外部类通信、当生成一个内部类对象的时候,此对象与制造它的外围对象之间就有了一种联系,也就是内部类中隐氏的包含了外围对象的this引用。所以它可以无条件的访问外围对象的所有成员

  2. 众所周知,JAVA是单继承,而内部类的出现、恰好从另一个思路巧妙的解决了这个问题、因为内部类其实是一个独立的实体,所以外围类是否已经继承了某个类,对于内部类是没有影响的。内部类可以独立的继承任何类。又因为其特性1,可以说,内部类是多重继承的变向实现。

  3. 内部类具有足够的灵活性、而且它可以使你的代码表现的更加优雅。

内部类的分类:

  • 成员内部类
  • 局部内部类
  • 匿名内部类
  • 嵌套内部类(静态内部类)

内部类详解


(一)成员内部类

public class Out {
    private int outValue;
    public Out(int outValue){
        this.outValue = outValue;
    }

    class Inner{
        private int innerValue;
        public Inner(int innerValue){
            this.innerValue=innerValue;
        }
        public void printOutValue(){
            System.out.println(outValue);
        }
        public void printInnerValue(){
            System.out.println(innerValue);
        }
    }

    public static void main(String[] args){
        Out out = new Out(1);
        Out.Inner inner = out.new Inner(2);
        inner.printOutValue();
        inner.printInnerValue();
    }
}

    如果想创建内部类,你必须指明这个对象的类型:Out.Inner,在new表达式中提供对外部类对象的引用,这是需要.new语法。如示例中那样创建。当然你也可以如此创建:Out.Inner inner = new Out(1).new Inner(2);
    当你生成了内部类的对象时,此对象与制造它的外围对象之间就有了一种联系,也就是此对象隐式的包含了外围对象的引用,你也可以在内部类中,用外部类的名称.this【Out.this】这样主动的获取此引用,而且此引用在编译器便被知晓并受到检查,因此没有任何运行时的成本。示例中虽然Out对象的outValue属性是私有的,但是内部类也可以无条件访问。这都归功于指向外围类的引用。正因为它的存在,内部类自动拥有其对外围类所有成员的访问权。对于内部类、有一点我们应该知道,内部类被编译后,会生成一个单独的类。对应示例中外围类、内部类分别被编译为:Out.class、Out$Inner.class
    在《java编程思想》一书以及开源的代码中(比如hystrix),大家更倾向于把内部类定义为私有的、对外暴漏的是内部类向上转型的接口(向上转型为其基类,尤其转型为一个接口的时候,内部类就有了用武之地),或者只是把内部类的构造方法声明为私有的。但无一例外的是内部类的创建都封装在了外部类当中。通过这些代码,能够开拓你以后在类设计时的思维方式。以下代码为迭代器设计模式的一个例子,从中可见良好的设计模式以及内部类的合理运用可以使你的代码变得如此优雅,犹如一件精美的艺术品,使人惊讶编程原来也可以如此美。
        示例2

public 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;
        private SequenceSelector(){//禁止主动创建

        }
        @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(i);
        }
        Selector selector = sequence.selector();
        while(!selector.end()) {
            System.out.println(selector.current());
            selector.next();
        }

    }
}

(二)局部内部类

在方法和作用域内的类被称为局部内部类。
内部类的使用理由如下:
1)你实现了某类型的接口,于是可以创建并返回对其的引用
2)你要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的。

  • 在方法内定义:

        示例3

public class Parcel5 {
    public Destionation destionation(String str){
        class PDestionation implements Destionation{
            private String label;
            private PDestionation(String whereTo){
                label = whereTo;
            }
            public String readLabel(){
                return label;
            }
        }
        return new PDestionation(str);
    }

    public static void main(String[] args) {
        Parcel5 parcel5 = new Parcel5();
        Destionation d = parcel5.destionation("chenssy");
    }
}

需要注意的一点:在destionation()方法内定义了PDestionation,
但并不意味着一旦destionation()方法执行完成,PDestionation就不可用了。

  • 在作用域内定义

            示例4

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("qcx");
            String string = ts.getSlip();
        }
    }

    public void track(){
        internalTracking(true);
    }

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

需要注意的一点:TrackingSlip类被嵌入到if语句的作用域内,这并不是说该类的创建是有条件的,它其实与别的类一起编译过了。只是,在作用域之外,它是不可用的,除此之外,它与普通的类一样。

(三)匿名内部类

        示例5

public 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;
    }

    public Selector selector(){
        return new 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 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.println(selector.current());
            selector.next();
        }
    }
}

selector()方法将返回值的生成与表示这个返回值的类的定义结合在一起!另外这个类是匿名的,它没有名字。你也可以这样理解:创建一个实现Selector接口的匿名类对象。(当然,要基于Selector接口或类存在的前提)通过new表达式返回的引用自动向上转型为对Selector的引用。其实他就是示例2代码的一种简化形式。不过有一点是需要引起大家注意的匿名类缺少了访问修饰符
在这个匿名内部类中,使用了默认的构造器来生成了Selector对象,如果你的基类需要一个有参构造参数,应该怎么办。示例6:构造器将要求传递一个参数。

        示例6

public abstract class Selector {
    protected int current;
    public Selector(int current){
        this.current=current;
    }
    abstract boolean end();
    abstract Object current();
    abstract 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;
    }

    public Selector selector(int current){
        return new Selector(current){
            @Override
            public boolean end() {
                return current == items.length;
            }
            @Override
            public Object current() {
                return items[current];
            }
            @Override
            public void next() {
                if(current<items.length)
                    current++;
            }
        };
    }

    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(0);
        while(!selector.end()) {
            System.out.println(selector.current());
            selector.next();
        }
    }
}

如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器将会要求其参数引用是final的。就像示例7中selector(final int i)的参数一样。如果你忘记了,将会得到一个编译时的错误提示。
        示例7

public 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;
    }

    public Selector selector(final int i){
        return new Selector(){
            private int current=i;
            @Override
            public boolean end() {
                return current == items.length;
            }
            @Override
            public Object current() {
                return items[current];
            }
            @Override
            public void next() {
                if(current<items.length)
                    current++;
            }
        };
    }

    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(1);
        while(!selector.end()) {
            System.out.println(selector.current());
            selector.next();
        }
    }
}

如果只是简单地给一个字段赋值,那么此例中的方法是很好的,但是想做一些构造器的行为呢?在匿名内部类中不可能有命名构造器(因为它根本没有名字),但通过示例初始化,就能够达到为匿名内部类构建一个构造器的效果,就像下面 示例中一样。
        示例8

public abstract class Selector {
    protected int current;
    public Selector(int current){
        this.current=current;
    }
    abstract boolean end();
    abstract Object current();
    abstract 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;
    }

    public Selector selector(int current){
        return new Selector(current){
            {//实例初始化
                this.current=current*2;
            }
            @Override
            public boolean end() {
                return this.current == items.length;
            }
            @Override
            public Object current() {
                return items[this.current];
            }
            @Override
            public void next() {
                if(this.current<items.length)
                    this.current++;
            }
        };
    }

    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(2);
        while(!selector.end()) {
            System.out.println(selector.current());
            selector.next();
        }
    }
}

在此示例中,selector(int current)方法中的参数,不被要求为final的。因为current被传递给匿名类的基类构造器,它并不会在匿名类内部被直接使用。

(四)嵌套内部类

如果不需要内部类对象与外围类对象之间有联系,那么可以将内部类声明为static。这通常称为嵌套类。普通的内部类对象隐式地保存了一个引用,指向创建它的外围类对象。而内部类却没有此引用。这意味着:

  • 要创建嵌套类对象,并不需要外围类对象。
  • 不能从嵌套类的对象中访问非静态的外围类对象
  • 普通的内部类不能有static数据与static字段,也不能包含嵌套类。但是嵌套类可以包含所有这些东西。
  • 普通类的创建方式:new 外围类.new 内部类
    嵌套类的创建方式:new 外围类.内部类

         示例9

public interface Selector {
    boolean end();
    Object current();
    void next();
}
public class Sequence {
    private static 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;
    }

    public static 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 static void main(String[] args){
        Sequence sequence = new Sequence(10);
        for(int i=0;i<10;i++){
            sequence.add(i);
        }
        SequenceSelector selector = new Sequence.SequenceSelector();        

        while(!selector.end()) {
            System.out.println(selector.current());
            selector.next();
        }
    }
}

4.1接口内部类

正常情况下,不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分。你放到接口中的任何类都自动地是public和static的。而嵌套类正式static的,这并不违反接口的规则。更酷的是,你甚至可以在嵌套类中实现这个接口。

public interface ClassInInterface {
    void howdy();
    class Test implements ClassInInterface {
        @Override
        public void howdy() {
            System.out.println("Howdy!");
        }
    }
}

如果你想创建某些公共代码,使得它们可以被某个接口的所有不同实现所公用,那么使用接口内部的嵌套类会显得很方便。

4.2多层嵌套类

一个内部类被嵌套多少层并不重要——它能透明地访问所有它所嵌入地外围类地所有成员。

public class MNA {
    private void f(){}
    class A{
        private void g(){}
        public class B {
            void h() {
                g();
                f();
            }
        }
    }

    public static void main(String[] args) {
        MNA mna = new MNA();
        MNA.A mnaa = mna.new A();
        MNA.A.B mnaab = mnaa.new B();
        mnaab.h();
    } 
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章