Android面试题(6): 什么是内部类,内部类的作用

《Effective Java》 第22条
《Thinking In Java》 第10章
知乎问答——Java 中引入内部类的意义?

1. 什么是内部类

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

内部类隐式的持有外部类的引用,,可以访问外部类的所有成员,即使该成员是私有的。它无法被单独创建,必须先创建外部类实例,如下:

Dog dog = new Dog();
Dog.Inner inner = dog.new Inner();

如果内部类不要求访问外部类的实例,请用static修饰,否则就是消耗时间和内存。在Android中,这种情况甚至可能导致内存溢出。

内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可见性。然而必须要了解,内部类与组合是完全不同的概念,这一点很重要。

上面这这段话是从java编程思想中抄下来的,一度让我迷茫。因为这里的内部类与组合是完全不同的概念说的太绝对了,因为内部类和组合看起来都是has-a的关系。
虽然概念完全不同,但是内部类与外部类确实是一种组合关系,emmmmm……强组合?内部类与组合的不同点主要在于两个地方:
1. 内部类隐式持有外部类引用,并且可以访问外部类的所有成员,这是组合做不到的。
2. 内部类可以被隐藏起来,外部无法单独创建,进一步提高了封装性。

2. 为什么需要内部类,内部类的作用

2.1 完善多重继承

这段知乎上的描述真的精辟,不仅解答了为什么Java不使用多重继承,为什么出现接口。还解答了为啥会有内部类!

  1. C++ 作为比较早期的面向对象编程语言,摸着石头过河,不幸的当了炮灰。比如多重继承,Java是不太欢迎继承的。因为继承耦合度太高。比如你是一个人,你想会飞,于是就继承了鸟这个类,然后你顺便拥有了一对翅膀和厚厚的羽毛,可这些玩意你并不需要。所以Java发明了接口,以契约的方式向你提供功能。想想看,你的程序里成员变量会比函数多吗?况且多重继承会遇到死亡菱形问题,就是两个父类有同样名字的函数,你继承谁的呢?其实C++也可以做到这些,那就是定义没有成员变量的纯虚类,而且所有函数都是纯虚函数。可是这些都是要靠程序员自己把握,并没有把这些功能集成到类似Interface这样的语法里。

  2. 所以Java只支持单重继承,想扩展功能,去实现接口吧。很快Java的设计者就发现了他们犯了矫枉过正的错误,多重继承还是有一定用处的。比如每一个人都是同时继承父亲和母亲两个类,要不然你的身体里怎么能留着父母的血呢?Java内部类应运而生。

    扫描二维码关注公众号,回复: 857401 查看本文章

那么,内部类到底是怎么完善了Java多重继承。说完善或者解决都可能不太适合,应该说是多重继承的一个替代方案。
在《Think in java》中有这样一句话:使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。

class D {}

abstract class E {}

class Z extends D {
    E makeE() {
        return new E() {};
    }
}

public class MultiImplementation {
    public static void main(String[] args) {
        Z z = new Z();
        takeD(z);
        takeE(z.makeE());
    }

    private static void takeD(D d) {}

    private static void takeE(E e) {}
}

就像上面的代码,间接的解决了Java无法多重继承的设计。

但是你可能会有疑问,这不就是组合的关系吗?为什么需要非要使用内部类?普通的组合关系好像也能做到吧。
但是内部类因为和外部类保持着特殊的联系,所以可以将内部类看作是比组合更加耦合的一个关系。内部类可以看作外部类的一个扩展,用它继承来继承其他非接口类可以有效的解决多重继承问题。而内部类可以完全的隐藏起来,只有外部类能访问,进一步提高了封装性。总的来说就是,用内部类的方式解决多重继承的问题,比起组合来说,更体现内聚这个特征。软件工程一直围绕着高内聚,低耦合,而内部类的作用或许是更加体现高内聚的一种方式。

2.2 闭包与回调

内部类是面向对象的闭包,因为它不仅包含创建内部类的作用域的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括private成员。
所以,内部类更多时候就像闭包一样,被当作回调对象使用。

另外,当一个接口和当前对象拥有同名函数时,你不能直接实现该接口,这时候也可以使用内部类。

class Library {

    interface Callback {
        void foo();
    }

    void do(Callback cb) {
        cb.foo();
    }
}
class My {
    void foo() {}

    void bar() {}

    void do() {
        Library lib = new Library();
        lib.do(new Library.Callback() {
            @Override
            void foo() {
                bar();
            }
        });
    }
}

不可否认没有内部类的时候,也可用其它方法实现回调,只是没那么方便。但是,滥用内部类也是会造成代码污染进入回调地狱的,一个方法嵌套个三四层也不是不可能……

2.3 lambda表达式

java1.8的lambda表达式,本质上就是一个接口的子类对象,一个匿名内部类。它是java中为数不多的语法糖之一。(还有其它语法糖:自动拆装箱、可变参数、增强for循环等)

interface Foo {
    void bar();
}
class Anything {
    // 这是一个接口的子类对象,它没有定义名字,所以它也是一个匿名内部类。
    Foo foo = () -> {}; 

    // 上面的例子同等于
    Foo foo2 = new Foo {
        @Override
        public void bar() {

        }
    }

    // 或者runnable的例子
    new Thead(() -> {

    });
}

3. 其他得形式的内部类

3.1 静态成员类(静态内部类)

静态内部类使用static修饰,不会持有外部类的引用,只能访问外部类的静态成员。所以,你可以很简单的将它看成普通的成员类,实际上除了访问时需要加上外部类名(Outter.Inner)确实没有区别。

3.2 匿名类(匿名内部类)

简单来说就是没有名字的类。创建方式很简单:

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {

    }
});

例如创建一个线程的时候,Runnable是一个接口,是没有办法直接创建它的实例的。但是我们有不想因为这个原因而去写一个新的类去实现它,这时候就可以用到匿名类了。实际上匿名类就是对应的类的一个子类实例,注意看new Runnable()后面跟着一对大括号,并且实现了run方法。

new xxx() {}; 的意思其实就是继承xxx,并且创建xxx的实例。

匿名类还有一个作用,就是可以访问某个不在当前包的类的protected方法(protected只能当前包或者其子类可以访问),因为匿名类本质上是一个子类实例,例如:

在test包下有个Dog类,有个保护的run方法:

package com.aitsuki.test;

public class Dog {

    protected void run() {
        System.out.println("溜了溜了");
    }
}

在其他包中,可以通过匿名类访问run方法。实际上就是new了一个Dog的子类实例,然后子类实例去调用父类的方法。

package com.aitsuki.test.other;

import com.aitsuki.test.Dog;

public class Master {

    public void walkTheDog() {
        new Dog() {
            @Override
            protected void run() {
                super.run();
            }
        }.run();
    }
}

3.3 局部类(局部内部类)

几乎用不上,我也不知道有什么用处,能在方法内部定义类,应该说,只要可以声明局部变量的地方都可以声明局部类。例如:

public void run() {
    class Cat {
        private void run() {
            System.out.println("轻轻的溜了溜了");
        }
    }
    Cat cat = new Cat();
    cat.run();
}

猜你喜欢

转载自blog.csdn.net/u010386612/article/details/79875302