内部类有哪些?它们存在的意义是什么?

一、内部类的分类及区别

内部类的表现形式为一个类可以在另一个类的内部存在,其中,内部包含其它类的类称为外部类,被包含的类称为内部类。在如下的示例代码中,Outer就是外部类,Inner就是内部类。

public class Outer {
    public class Inner {
    }
}

常见的内部类主要分为以下四种形式:

  • 成员内部类
  • 静态内部类
  • 局部内部类
  • 匿名内部类

值得说明的是,包含内部类的外部类在编译过后会成为两个完全不同的类,分别是Outer.classOuter$Inner.class

1.1 成员内部类

此时的内部类相当于是外部类的一个普通成员,只要当外部类被实例化成一个对象后,借用该对象就可以对内部类进行操作。

通常成员内部类表现为如下的代码形式:

public class Outer {    
    private String outerValue = "outer";
    // 成员内部类,相当于外部类的一个成员变量
    public class Inner {
        private String innerValue = "inner";    
        public void printInner(){
            System.out.println("访问外部类的私有属性:" + outerValue);
            System.out.println("访问内部类的私有属性:" + innerValue);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        // 01使用成员内部类
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.printInner();
    }
}

在使用成员内部类的时候,需要记住以下几点:

  • 内部类可以直接使用外部类的任何属性和方法,即使是private的。
  • 外部类不能直接使用内部类的属性及方法,只能通过内部类的对象来使用。
  • 内部类中不能包含static的内容。
  • 内部类中需要使用外部类的对象时,使用outer.this来指代。

1.2 静态内部类

静态内部类又称为嵌套内部类,是在外部类中具有static关键字声明的内部类,相当于是外部类的类变量。

通常静态内部类表现为如下的代码形式:

public class Outer2 {
    private String outerValue1 = "outerValue1";
    private static String outerValue2 = "outerValue2";
    // 静态内部类/嵌套类
    public static class Inner2{
        private String innerValue1 = "innerValue1";
        public void printInfo(){
            System.out.println("访问外部类中的私有属性:" + new Outer2().outerValue1);
            System.out.println("访问外部类中的静态变量:" + Outer2.outerValue2);
            System.out.println("访问内部类中的私有属性:" + innerValue1);
        }
    }
}
public class Test {
    public static void main(String[] args) {    
        // 02使用静态内部类/嵌套类
        Outer2.Inner2 inner2 = new Outer2.Inner2();
        inner2.printInfo();
}

在使用静态内部类的时候,需要记住以下几点:

  • 静态内部类的内部不能直接访问外部类的非静态成员,只能通过实例化外部类的对象,然后通过该对象进行访问。
  • 创建静态内部类的对象时,不需要像成员内部类一样需要先行创建外部类的对象,而是直接使用内部类的名称即可创建。

1.3 局部内部类

局部内部类是出现在外部类的方法或者某个代码块的作用域范围内的内部类,通常表现为如下的代码形式:

public class Outer3 {
    private String outerValue = "outerValue";
    // 在方法内部定义的局部内部类
    public void outerInfo(){
        final String methodValue = "methodValue";
        class Inner3 {
            String innerValue = "innerValue";
            public void printInner3(){
                System.out.println("访问外部类的属性:" + outerValue);
                System.out.println("访问外部类的方法的常量:" + methodValue);
                System.out.println("访问内部类的属性:" + innerValue);
            }
        }
        Inner3 inner3 = new Inner3();
        inner3.printInner3();
    }
    
    // 在作用域内部定义的局部内部类
    public void outerInfo2(boolean flag){
        if(flag){
            final String methodValue2 = "methodValue2";
            class Inner4{
                String innerValue2 = "innerValue2";
                public void printInner4(){
                    System.out.println("访问外部类的属性:" + outerValue);
                    System.out.println("访问外部类的方法的常量:" + methodValue2);
                    System.out.println("访问内部类的属性:" + innerValue2);
                }
            }
            Inner4 inner4 = new Inner4();
            inner4.printInner4();
        }else{
            System.out.println("outerInfo2:else");
        }
    }
}
public class Test {
    public static void main(String[] args) {
        // 03使用局部内部类
        Outer3 outer3 = new Outer3();
        outer3.outerInfo();
        outer3.outerInfo2(true);
}

在使用局部内部类的时候,需要记住以下几点:

  • 局部内部类仅在作用域范围内有效,超出范围则不再可使用。
  • 局部内部类中可以使用方法或者代码块级别的变量,但是要求它们必须是final类型的常量。

1.4 匿名内部类

匿名内部类没有类名,通常被设计为只使用一次,用以简化代码的书写,通常表现为如下的代码形式:

public class Outer4 {
    private String outerValue = "outerValue";
    // 在方法内部使用匿名内部类
    public Inner5 getInnerInfo(final String name, String value){
        final String methodValue = "methodValue";
        return new Inner5(){
            private String innerValue = name;
            @Override
            public String getInner5() {
                System.out.println("访问外部类的私有属性:" + outerValue);
                System.out.println("访问外部类的方法的常量" + methodValue);
                return innerValue;
            }
        };
    }
}
interface Inner5 {
    String getInner5();
}
public class Test {
    public static void main(String[] args) {
        // 04使用匿名内部类
        Outer4 outer4 = new Outer4();
        Inner5 inner5 = outer4.getInnerInfo("zhangsan", "lisi");
        System.out.println(inner5.getInner5());
    }
}

在使用匿名内部类的时候,需要记住以下几点:

  • 匿名内部类必须继承一个父类或者实现一个接口。
  • 在匿名内部类中需要使用外部类的方法的属性时,要求该属性必须为final类型的常量。
  • 在匿名内部类中需要使用外部类的方法的形参时,要求该形参必须为final类型的常量。

二、内部类的作用及意义

学会使用内部类,是掌握Java高级编程的一部分,能让你更优雅地设计自己的程序结构。

2.1 为了实现封装特性

我们在另一篇文章《面向对象三大特性的总结》中有详细介绍关于封装的常用方法,其中就有使用内部类的方法。

外部类对内部类的封装主要表现为在其它地方想要使用内部类的话需要受到外部类的限制。

public class Animal {
    public class Dog {
    }
    public Dog getDog(){
        return new Dog();
    }
}
public class Test {
    public static void main(String[] args) {
        Animal a = new Animal();
        Dog d = a.new Dog();
        Dog o = a.getDog();
        // 无法直接实例化内部类的对象,如下语句无法编译通过
        Dog g = new Dog();
    }
}

在如上的例子中,内部类虽然是public的,但是想要使用内部类必须借助外部类的对象才能做到,这就是封装。更进一步,如果你只希望在外部类中才能使用内部类,而在其它地方都不能使用的话,只需要将内部类改为private即可。

public class Animal {
    private class Dog {
    }
    public Dog getDog(){
        return new Dog();
    }
    public static void main(String[] args) {
        Animal a = new Animal();
        // 在外部类中可以使用private的内部类,但是仍然只能通过外部类的对象才能访问
        Dog d = a.new Dog();
        Dog o = a.getDog();
    }
}
public class Test {
    public static void main(String[] args) {
        Animal a = new Animal();
        // 下面两行语句都无法编译通过,因为Dog是private的内部类,无法在外部类之外的其它任何地方使用
        Dog d = a.new Dog();
        Dog o = a.getDog();
    }
}

2.2 为了完善多重继承

在Java中一个子类只能继承一个父类,如果想继承多个父类,只能将这些父类改为接口,然后子类实现多个接口。然而接口中的方法都是抽象的,是必须在子类中予以实现的,这就带来了很大的不便,那有没有什么方法能让子类同时继承多个父类中的已经实现好了的方法呢?当然是通过内部类,具体示例如下:

public class Animal {
    public String bark(){
        return "WOW";
    }
}
public class Creature {
    public String drink(){
        return "water";
    }
}
public class Mammal {
    public class Dog extends Creature {
    }
    public class Cat extends Animal {
    }
    public String getDogAction(){
        return new Dog().drink();
    }
    public String getCatAction(){
        return new Cat().bark();
    }
}
public class Test {
    public static void main(String[] args) {
        Mammal m = new Mammal();
        // 对于Mammal来说,其通过内部类Dog和Cat间接地分别继承了Creature和Animal中的方法
        System.out.println(m.getCatAction());
        System.out.println(m.getDogAction());
    }
}

2.3 让Java也有闭包

首先我们需要先理解什么是闭包?

闭包就是能够读取其他函数内部变量的函数。——百度百科

那么映射到Java里面,所谓的闭包就可以指在内部类中可以访问并使用外部类中的所有变量和方法的特性。那么这样的闭包究竟有什么用处呢?别急,我们先来看下面这样一个例子:

public class Fruit {
    private Integer num = 10;
    private void addOneFruit(){
        num++;
        System.out.println("fruit增加到" + num + "个");
    }
    public class Child {
        public void getOneFruit(){
            num--;
            System.out.println("fruit减少到" + num + "个");
        }   
        public void putOneFruit(){
            addOneFruit();
        }
        
    }
}
public class Test {
    public static void main(String[] args) {
        Fruit fruit = new Fruit();
        Child child = fruit.new Child();
        // 通过内部类对象引用来操作外部类的私有属性和方法(称为闭包)
        child.getOneFruit();
        child.putOneFruit();
    }
}

在上面的例子中,main方法中原来是无论如何都不能访问Fruit的私有属性numaddOneFruit的,但是通过内部类Child,这一切就成了可能,从而实现了闭包。下面是列举的关于闭包的好处:

  • 使得环境外部(类、函数)可以有一种途径访问环境内的私有成员。
  • 使得当前环境的对象得以一直保留在内存中,即使将该对象的引用置为null。

关于上述第一点,你是否会有这样的疑问:为什么不使用setter和getter方法对环境内的私有成员进行访问呢?

如果单从能否实现角度看,我们要访问环境内的私有成员,当然可以通过setter和getter方法,但是从面向对象编程角度看,则不能依靠这种方法。比如Fruit的减少只能通过Child的操作来进行,这是模拟现实世界中的操作“吃”,只有Child才能吃掉Fruit达到让Fruit减少的目的,总不能让Fruit自己吃自己吧。

关于上述的第二点,我们可以改下main方法进行说明:

public class Test {
    public static void main(String[] args) {
        Fruit fruit = new Fruit();
        Child child = fruit.new Child();
        // 通过内部类对象引用来操作外部类的私有熟悉和方法(称为闭包)
        child.getOneFruit();
        child.putOneFruit();
        // 即使将外部类的引用置为null,但因其内部类引用仍然存在,内部类需要依赖外部类,所以外部类的对象仍然得以保留,不会被GC回收
        fruit = null;
        child.putOneFruit();
        child.getOneFruit();
        child = null;
        // 内部类的引用被置为null后,内部类的对象和外部类的对象从此就没有引用指向它们,很快会被GC回收
        // 下面两行编译无法通过
        child.getOneFruit();
        child.putOneFruit();
    }
}

三、常见的使用内部类的例子

待补充......

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

推荐阅读更多精彩内容

  • Java 内部类 分四种:成员内部类、局部内部类、静态内部类和匿名内部类。 1、成员内部类: 即作为外部类的一个成...
    ikaroskun阅读 1,219评论 0 13
  • 抽象类 在继承的层次结构中,每个新子类都使类变得越来越明确具体。如果从一个子类追溯到父类,类就会变得更通用和抽象。...
    Steven1997阅读 1,362评论 0 5
  • 书案疾笔,轩窗外尽是些向日葵花,明亮的晃眼。昨夜小雨东风,被踩进泥土中的花瓣,依稀相似小时候贪口的桂花甜糕。 只是...
    蛮小吉阅读 388评论 0 0
  • 走着 走着 就发现 自己不见了 现在的 如同行尸走肉的 不是我 我似乎是 消失在了 平庸的路上
    愚者知之阅读 98评论 0 0