带你玩转 Java 内部类

 2022-09-25

概述

不知道大家在平时的开发过程中或者源码里是否留意过内部类,那有思考过为什么要有内部类,内部类都有哪几种形式,静态内部类和普通内部类有什么区别呢?本篇文章主要带领大家理解下这块内容。

内部类介绍和分类

顾名思义,内部类是指一个类在另外一个类的内部,是定义在另一个类中的类。根据类的位置和属性不同,可以分为下面几种。

常规内部类

    @Data
    public class Tree {
    
        private String treeName;
    
        private String treeType;
    
        private List<Leaf> leafs;
    
    
        @Data
        public class Leaf {
            private String color;
    
            private String leafSize;
    
            public void show() {
                System.out.println("tree name: " + Tree.this.treeName);
                System.out.println("tree name: " + treeName);
            }
        }
    
        public static void main(String[] args) {
            Tree tree = new Tree();
            Leaf leaf = tree.new Leaf();
        }
    }

上面就是一个内部类的例子,Leaf是Tree的内部类。

  • 内部类中可以直接访问外部类的数据,包括私有数据。
  • 常规内部类中的方法或者字段不能是静态的。
  • 内部类中可以使用如下的语法Tree.this.treeName。
  • 内部类创建对象需要外部对象的存在,如下:
    Tree tree = new Tree();
    Leaf leaf = tree.new Leaf();

字节码分析:

我们查看下内部类的字节码,如下图:

202209252323233181.png

我们看到内部类Leaf的构造方法init,实际上会隐式地将外部对象传入,初始化,这样才能在内部类中访问。

局部内部类

局部内部类,比如只有在一个方法内部创建。

    @Data
    public class Tree {
    
        private String treeName;
    
        private String treeType;
    
        private List<Leaf> leafs;
    
        public void treeRoot(int height) {
            class TreeRoot {
                private int rootHeight;
    
                public void showRootHeight() {
                    this.rootHeight = height;
                    System.out.println("root height " + height);
                }
            }
            TreeRoot root = new TreeRoot();
            root.showRootHeight();
        }
    }
  • 局部类不能用public或private访问说明符进行声明,因为作用域只在这个局部,没有必要。
  • 局部内部类可以完全将自己隐藏起来,体现良好的封装性。
  • 局部内部类可以直接访问方法的变量。

匿名内部类

匿名内部类,就更加简洁了,连类名都省略了, 这个配合lambda食用,非常简便,想必大家也经常使用了。

    public static void main(String[] args) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println("runnable");
                }
            };
    
            Runnable runnable2 = () -> System.out.println("runnable");
        }

匿名内部类也体现了良好的封装性和简洁性。

静态内部类

最后,再介绍下静态内部类,这个也使用的非常频繁。java源码中也有很多这样的例子,比如HashMap中,Node节点就是一个静态内部类。

202209252323259972.png

如果用static来修饰一个内部类,那么就是静态内部类。这个内部类属于外部类本身,但是不属于外部类的任何对象。因此使用static修饰的内部类称为静态内部类。静态内部类有如下规则:

  • 静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。
  • 外部类可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象访问其实例成员。

静态内部类和普通内部类的区别

我们可以通俗按下面的方式理解:

内部类:就是我是你的一部分,我了解你,我知道你的全部,没有你就没有我。(所以内部类对象是以外部类对象存在为前提的)

静态内部类:就是我跟你没关系,自己可以完全独立存在,但是我就借你的壳用一下,来隐藏自己。

从字节码角度上看,静态内部类和非静态内部类最大的区别是:非静态内部类编译后隐式保存着外部类的引用(就算外部类对象没用了也GC不掉),但是静态内部类没有。

内部类的作用

通过上面的了解,我想内部类还是有一定的意义的。

  1. 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。
  2. 内部类可以对同一个包中的其他类隐藏起来。

其实内部类更多的时候,不想把这个类暴露出去,它可能只是外部类的一个逻辑组成部分,不需要其他地方知道什么,这时候我们用内部类更加的清楚。