内部类

一、为什么要使用内部类

1、内部类可以访问其外围类所有元素。
2、每个内部类都能独立继承一个接口的实现,或继承一个类,或者说外部类通过这种方式来实现“多重继承”。

学习了集合之后,现在我们有一个想法就是来创建一个属于自己的集合数组类MyArrayList,我们都知道集合类为了可以使用增强for,都必须实现Iterable接口。

public class MyArrayList<AnyType> implements Iterable<AnyType> {

    private static final int DEFAULT_CAPACITY = 10;     // 数组的容量
    private int theSize;                                   // 数组中的当前项数
    private AnyType[] theItems;                         // 基础数组


    @Override
    public Iterator<AnyType> iterator() {
        return null;
    }
}

而实现Iterable接口必须实现iterator方法,该方法返回一个Iterator(迭代器模式的例子)接口的实例。我们一般想的就是那我们再写一个Iterator接口的实现类,然后再iterator方法中返回null改为该类实例就好啦。

// Iterator接口
public void Iterator<AnyType>{
  boolean hasNext();
  AnyType next();
}
public class ArrayListIterator implements Iterator {

    private int current = 0;                // 迭代序列中下一项的下标
    MyArrayList list;                       // 数组集合的引用

    @Override
    public boolean hasNext() {
        return current < list.theSize;
    }

    @Override
    public Object next() {
        if (!hasNext())
            throw new NoSuchElementException();
        return list.theItems[current++];
    }
}

可是上面都报错了,原因是ArrayListIterator类中就算有数组集合的引用也访问不了MyArrayList类中的theSize和theItems啊,因为这些属性都是私有的。ok我改

  • 把theSize和theItems改为public,但是这样破坏了封装性。pass
  • 在数组类里开口子(get方法),这样迭代器类就可以访问了,但是如果要访问的变量很多那岂不是得写很多get方法。pass
  • 不是,为什么数组类不能自己实现这个迭代器接口,然后返回自己的实例呢?是可以的,但是如果这个迭代器接口是抽象类呢?一个类可不能继承多个类,你这不是抬杠吗,Iterator明明是一个接口,ok,那如果Iterable接口和Iterator接口中有相同的方法呢,这在java中是不可以的。因为编译器根本就不知道该实现哪个方法。pass
那有什么方法既可以访问其他类的私有属性并且没有上面的烦恼呢?使用内部类

我们把ArrayListIterator作为MyArrayList的一个内部类,如下:

public class MyArrayList<AnyType> implements Iterable<AnyType> {

    private static final int DEFAULT_CAPACITY = 10;     // 数组的容量
    private int theSize;                                // 数组中的当前项数
    private AnyType[] theItems;                         // 基础数组


    //内部类
    public class ArrayListIterator implements Iterator {

        private int current = 0;                        // 迭代序列中下一项的下标
        @Override
        public boolean hasNext() {
            return current < theSize;
        }

        @Override
        public AnyType next() {
            if (!hasNext())
                throw new NoSuchElementException();
            return theItems[current++];
        }
    }


    @Override
    public Iterator<AnyType> iterator() {
        return new ArrayListIterator();
    }
}

可以看到将一个类的定义放在另一个类中这就是内部类。内部类可以访问外部类的私有属性,因为上面没有报错。为了测试我们给数组集合类添加add方法和构造器。(注意,这里并不是真的一个数组集合,因为不能动态改变数组的容量)

    public MyArrayList(int size) {
        // 使用泛型类型限界并且对类型数组进行转换
        theItems = (AnyType[]) new Object[size];
    }

    public void add(AnyType e) {
        if (theSize < theItems.length)
            theItems[theSize++] = e;
    }

测试:

public static void main(String[] args) {
        MyArrayList list = new MyArrayList(5);
        for (int i = 0; i < 5; i++) {
            list.add(i);
        }
        Iterator iterator = list.iterator();

        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
          //foreach内部也是使用上面这种原理
//        for (Object o : list) {
//            System.out.println(o);
//        }
    }

输出:
0
1
2
3
4

那么,为什么内部类会自动拥有对其外部类所有成员的访问权呢?

原因是当new MyArrayList(5)实例化一个外部类对象的时候,再调用iterator方法new ArrayListIterator()再实例化一个内部类对象时,这个内部类对象必定会秘密捕获一个指向那个外部类的引用。然后当内部类访问外部类的成员时,就是用的这个外部类引用。

二、内部类的使用

  • 如果想在内部类中获取一个外部类的引用,可以使用外部类.this,例如我们在内部类中创建一个返回外部类引用的方法:
public MyArrayList outer(){
     return MyArrayList.this;
 }
  • 如果告诉某些对象去创建其内部类的对象时,可以是用.new如下:
public void main(String[] args){
  MyArrayList list = new MyArrayList(5);
  MyArrayList.ArrayListIterator iterator = list.new ArrayListIterator();
} 
所以想要创建内部类对象,必须使用外部类的对象来创建内部类,而不是使用外部类,如果你的内部类是嵌套类(静态内部类),那么它就不需要外部类对象的引用。

三、嵌套内部类

如果不希望内部类对象与外部类对象之间有联系,那么可以将内部类声明为static,想要理解static嵌套类的含义就必须记住:

  • 现在要创建一个嵌套内部类对象并不需要外部类对象而是使用外部类来创建。(注意没有了括号,而还是得是关键字new)
  • 不能在嵌套内部类中创建外部非静态类对象
  • 普通内部类不能有static数据与static字段或者嵌套类,但是嵌套内部类可以有。
    看下面的例子:
class Out {
    private static int age = 12;

    static class In {
        static int age = 13;                        //3、嵌套内部类可以有static数据
        public void print() {
            int age = 14;
            System.out.println("局部变量:" + age);  //输出14
            System.out.println("内部类变量:" + this.age);    //输出13
            //下面报错,2、不能再嵌套内部类中创建非静态类对象
           // System.out.println("外部类变量:" + Out.this.age);  //输出12
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        Out.In in = new Out.In();           //1、不再需要外部类对象,没有了new Out()
        in.print();
    }
}
  • 如果你想创建某些公共代码,使得他们可以被实现该接口的不同实现所共用,那么就可以接口中创建嵌套内部类。
public interface IntefaceDemo {
    void howdy();

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

        public static void main(String[] args){
            new  Test().howdy();
        }
    }
}
输出:
howdy!
  • 嵌套类又可以理解为在内部类里再嵌套一个内部类。可以看到就算你嵌套多少层,还是可以访问外部类的所有成员。
class A {
    private void f() {
        System.out.println("f()");
    }

    class B {
        private void h() {
            System.out.println("h()");
        }

        class C {
            public void g() {
                f();
                h();
                System.out.println("j()");
            }
        }
    }

}

public class ABC {
    public static void main(String[] args){
        A a = new A();
        A.B b = a.new B();
        A.B.C c = b.new C();
        c.g();
    }
}
输出:
f()
h()
j()

四、私有内部类

如果一个内部类只希望被外部类中的方法操作,那么可以使用private声明内部类,下面的代码中,我们必须在Out类里面生成In类的对象进行操作,而无法再使用Out.In in = new Out().new In() 生成内部类的对象

也就是说,此时的内部类只有外部类可控制

class Out {
    private int age = 12;

    private class In {
        public void print() {
            System.out.println(age);
        }
    }
    public void outPrint() {
        new In().print();
    }
}

public class Demo {
    public static void main(String[] args) {
        //此方法无效
        /*
        Out.In in = new Out().new In();
        in.print();
        */
        Out out = new Out();
        out.outPrint();
    }
}

但在java中凡事并非绝对的,我们可以通过内部类向上转型来访问private或protect内部类。

class Out {
    private class In implements InIn {              //私有内部类实现某接口
        public void print() {
            System.out.println("你好");
        }
    }

    public InIn outPrint() {                      
        return new In();
    }
}

public class Demo {
    public static void main(String[] args) {

        InIn in = new Out().outPrint();
        in.print();

    }
}

通过这种方式可以完全组织任何依赖于类型的编码,并且完全隐藏了细节,而且也不能扩展接口中的方法。

五、方法内部类

我们也可以把内部类的定义写在方法里:

只是如果想往外部类的方法中传入参数,并且该参数会被内部类使用,那么该参数必须是final
class Out {
    private int age = 12;
 
    public void Print(final int x) {
        class In {
            public void inPrint() {
                System.out.println(x);
                System.out.println(age);
            }
        }
        new In().inPrint();
    }
}
 
public class Demo {
    public static void main(String[] args) {
        Out out = new Out();
        out.Print(3);
    }
}

六、匿名内部类

改写上面的例子,把内部类写在方法里并且使用匿名内部类,
这里需要注意的是:由于匿名内部类没有名字,所以不能写有参构造方法,只能往new Iterator(形参),这里的形参可以不需要final,因为该形参是传递给其基类的构造器的。

public Iterator<AnyType> iterator() {
        return new Iterator() {
            private int current = 0;

            @Override
            public boolean hasNext() {
                return current < theSize;
            }

            @Override
            public AnyType next() {
                if (!hasNext())
                    throw new NoSuchElementException();
                return theItems[current++];
            }
        };

    }

参考:
https://www.cnblogs.com/nerxious/archive/2013/01/24/2875649.html
Java编程思想

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容