作者: 不洗碗工作室 - Marklux
Java 内部类
基本定义
很简单, 无非是在类的内部再定义一个类, 这被称为成员内部类:
- public class OuterClass {
- private String name;
- private int age;
- class InnerClass { public InnerClass(){
- name = "mark";
- age = 20;
- }
- public void echo() {
- System.out.println(name + " " + age);
- }
- }
- }
复制代码
问题思考
上面这个很简单的例子中, 也包含了很多应该思考的问题:
内部类如何被实例化?
内部类能否改变外围类的属性, 两者之间又是什么一种关系?
内部类存在的意义是什么?
在回答这三个问题之前, 必须要明确一个点, 那就是内部类是依附于外围类而存在的, 其实也就是内部类存在着指向外围类的引用. 明白了这个之后, 上面的问题就好解答了.
实例化与数据访问
内部类与外围类之间形成了一种联系, 使得内部类可以无限制地访问外围类中的任意属性. 正如上面的例子中, InnerClass 内部可以随意访问 OuterClass 中的 private 属性.
同样的, 因为内部类依赖与外围类的存在, 所以无法在外部直接将其实例化, 而是必须先实例化外围类, 才能够实例化内部类(注意, 在外围类的成员方法里仍然是可以直接实例化内部类的):
- public static void main(String[] args) {
- InnerClass inner = new OuterClass().new InnerClass();
- inner.echo();
- }
复制代码
使用外围类的. new 来创建外部类.
我们也知道, 内部类和外围类的联系是通过内部类所持有的外部类的引用来实现的, 想要获取这个引用, 可以使用外围类的. this 来实现, 可以参考下面这个测试用例
- public class OuterClass {
- private String name;
- private int age;
- class InnerClass {
- public InnerClass(){
- name = "mark";
- age = 20;
- }
- public void echo() {
- System.out.println(name + " " + age);
- }
- public OuterClass getOuter() {
- return OuterClass.this;
- }
- }
- @Test
- public void test() {
- OuterClass outer = new OuterClass();
- InnerClass inner = outer.new InnerClass();
- Assert.assertEquals(outer, inner.getOuter());
- }
- }
复制代码
内部类的作用
内部类创建起来很麻烦, 使用起来也令人困扰, 那么内部类存在的意义是什么呢?
实现多重继承
这可能是内部类存在的最重要的意义, 参考Thinking in Java中的解释:
使用内部类最吸引人的原因是: 每个内部类都能独立地继承一个 (接口的) 实现, 所以无论外围类是否已经继承了某个 (接口的) 实现, 对于内部类都没有影响.
我们都知道, Java 中取消了 C++ 中类的多重继承(但是允许接口的多重实现), 但是在实际编程中, 又不免会遇到同一个类需要同时继承自两个类的情况, 这时候就可以使用内部类来实现了.
比如有两个抽象类
- public abstract class AbstractFather {
- protected int number;
- protected String fatherName;
- public abstract String sayHello();
- }
- public abstract class AbstractMother {
- protected int number;
- protected String motherName;
- public abstract String sayHello();
- }
复制代码
如果想要同时继承这两个类, 势必会引起 number 变量的冲突, 以及 sayHello 方法的冲突, 这些问题在 C++ 中是以一种复杂的方案来实现的, 如果使用内部类, 就可以使用两个不同的类来继承不同的基类, 并且可以根据自己的需要来组织数据的访问:
- public class TestClass extends AbstractFather {
- @Override
- public String sayHello() {
- return fatherName;
- }
- class TestInnerClass extends AbstractMother {
- @Override
- public String sayHello() {
- return motherName;
- }
- }
- }
复制代码
其他
(摘自Think in Java)
内部类可以用多个实例, 每个实例都有自己的状态信息, 并且与其他外围对象的信息相互独立.
在单个外围类中, 可以让多个内部类以不同的方式实现同一个接口, 或者继承同一个类.
创建内部类对象的时刻并不依赖于外围类对象的创建.
内部类并没有令人迷惑的 "is-a" 关系, 他就是一个独立的实体.
内部类提供了更好的封装, 除了该外围类, 其他类都不能访问.
内部类的分类
上面的例子中创建的内部类, 都属于成员内部类, 实际上 Java 中还有三种其他的内部类:
局部内部类
嵌套在方法里或者是某个作用域内, 通常情况下不希望这个类是公共可用的, 相比于成员内部类, 局部内部类的作用域更加狭小了, 出了方法或者作用域就无法被访问. 一般用于在内部实现一些私有的辅助功能.
定义在方法里:
- 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");
- }
- }
复制代码
定义在作用域内:
- 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("chenssy");
- String string = ts.getSlip();
- }
- }
- public void track(){
- internalTracking(true);
- }
- public static void main(String[] args) {
- Parcel6 parcel6 = new Parcel6();
- parcel6.track();
- }
复制代码
} ```
静态内部类
使用了 static 修饰的内部类即为静态内部类, 和普通的成员内部类最大的不同是, 静态内部类没有了指向外围类的引用. 因此, 它的创建不需要依赖于外围类, 但也不能够使用任何外围类的非 static 成员变量和方法.
静态内部类一个很好的用途是, 用来创建线程安全的单例模式:
- public class Singleton {
- private static class SingletonHolder {
- private static final Singleton INSTANCE = new Singleton();
- }
- private Singleton (){}
- public static final Singleton getInstance() {
- return SingletonHolder.INSTANCE;
- }
- }
复制代码
这是利用了 JVM 的特性: 静态内部类时在类加载时实现的, 因此不会受到多线程的影响, 自然也就不会出现多个实例.
匿名内部类
匿名内部类就是没有被命名的内部类, 当我们需要快速创建多个 Thread 的时候, 经常会使用到它:
- new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println("hello");
- }
- }).start();
复制代码
当然现在也可以用λ表达式来实现了, 我们暂且不提这两者之间的关系, 先来看一下使用匿名内部类时要注意哪些事情:
匿名内部类没有访问修饰符, 也没有构造方法
匿名内部类依附于接口而存在, 如果它要继承的接口并不存在, 那这个类就无法被创建
如果匿名内部类要访问局部变量, 那这个数据必须是 final 的
熟悉函数式编程的人会发现匿名内部类和函数闭包有些类似(这也是为什么能够用λ表达式来代替它), 但实际上两者还是有着一些区别的, 下面的部分中我们就来对比闭包和内部类.
- func Add(y int) {
- return func(x int) int {
- return x + y
- }
- }
- a := Add(10)
- a(5) // return 15
- type Closure struct {
- F func(x int) int
- y *int
- }
- public class OuterClass {
- private int y = 10;
- private class Inner {
- public int innerAdd(int x) {
- return x + y;
- }
- }
- public static void main(String[] args) {
- OuterClass outer = new OuterClass();
- Inner inner = outer.new Inner();
- System.out.println(inner.innerAdd(5)); //result: 15
- }
- }
- interface AnnoInner {
- int innerAdd(int x);
- }
- public class OuterClass {
- private int y = 100;
- public AnnoInner getAnnoInner() {
- int z = 10;
- return new AnnoInner() {
- @Override
- public int innerAdd(int x) {
- // z = 20; 报错
- return x + y + z;
- }
- };
- }
- public static void main(String[] args) {
- OuterClass outer = new OuterClass();
- AnnoInner inner = outer.getAnnoInner();
- System.out.println(inner.innerAdd(5)); //result: 115
- }
- }
- return new AnnoInner() {
- @Override
- public int innerAdd(int x) {
- int copyZ = z; // 创建 z 的值拷贝
- return x + y + copyZ;
- }
- };
来源: https://juejin.im/post/5b70db05e51d4566381883a1