【JAVA进化论】LV3-2:内部类
你知道吗?java里允许一个类的声明放在另外一个类的域里面(再次套娃),如果一个类被声明在了另外一个类里面,那么它就是一个内部类,内部类按照自己的修饰符和出现的区域不同,又分成了以下种类:
如图,我们把包含
内部类
的普通类叫做外部类
。内部类又按照静态非静态做了区分,非静态内部类又细分了三类。下面,让我们来走进内部类。⚠️ 注意:本节内容默认你已经对类有很深入的理解,因此本节的例子类命名不再像前几节那么“讲究”,为了方便说名问题,我们把内部类以InnerA/B/C的形式命名,外部类以OuterA/B/C的方式命名。
一、静态内部类
1.1:静态内部类的定义&特性
静态内部类是内部类的一个大分类,它和非静态内部类有着很大的差别。静态内部类的定义方式如下:
1 | public class OuterA { |
静态内部类有哪些特性呢?
事实上静态内部类没有任何与其他类不同的特性,你只需要把它当成平时的类即可,只不过它被定义在另外一个类的域里而已,它的实例化方式需要这样做:
1 | public static void main(String[] args) { |
此外,普通类的定义里,只能把类定义成public类或者干脆不写权限修饰符,且public修饰的类被称为公开类,要求每个java文件里最多只能存在一个公开类,且类名必须要和java文件名一致,但是在静态内部类的世界,是允许加任意权限修饰符的,且限制跟之前讲的那张表中一致:
1 | public class OuterA { |
但是改成protected或default后,可以引用并访问。还有一点,外部类是可以无视静态内部类里资源的访问权限修饰符的,也就是说,外部类甚至可以访问静态内部类的private级别的内容。
静态内部类没什么好说的,你可以理解只是一个类被定义在了另外一个类里头,此外它跟普通类的用法没有任何区别。
1.2:静态内部类的意义
如果你认真看了1.1
,你会发现,既然这东西跟普通类没什么区别,为什么它还要存在呢?是的,确实定义一个普通类避免了静态内部类那么奇怪的实例化语法,更加利于理解,静态内部类存在就只是java支持这种写法而已,一般情况下,当类B仅会被类A使用,不存在别的地方使用它的情况,这时就建议将类B定义为类A的静态内部类使用,这时访问权限建议设置为private ←这是较官方的说法,其实java支持这种规则,在实际开发中往往有更多种多样的写法,比如某些情况下封装成一个负责收集外部类各种属性的内部类:
1 | public class Student { |
上面的Builder内部类负责给它的外部类收集属性值,最终通过build构建一个外部类实例,然后Student的构造器被我们搞成private了,因此Student不可以通过构造器的方式进行实例化了,但是却可以通过Builder这个内部类实例化:
1 | public static void main(String[] args) { |
如果一个对象属性过多,相比那种定义一堆的set、get方法的方式给属性赋值,不如使用这种链式调用的方式来的简洁。
注意,举这个例子的目的不是让你一定要用静态内部类搞这种事情,而是要告诉你,java支持了这种定义方式,你可以利用这种定义方式,来做你自己认为正确的类拆分。
二、非静态内部类
2.1:成员内部类
先来看看成员内部类怎么定义:
1 | public class OuterB { |
成员内部类跟静态内部类就完全不一样了,来看看它是怎么完成实例化的:
1 | public static void main(String[] args) { |
ok,现在来介绍下成员内部类的特性:
- 成员内部类不允许拥有非final的静态属性,不允许拥有任意形式的静态方法。
- 成员内部类必须通过外部类的引用变量通过new关键词去实例化(必须是外部类本类的引用变量才行,外部类的父类引用变量实例化内部类是不允许的)
- 成员内部类可以访问任意外部类的资源(无视访问权限修饰符的访问)
成员内部类的定义跟静态内部类一样拥有权限修饰符,我们一般建议设置成private的,由于非静态内部类的特性,它可以被外部类new出来之后操作外部类里面任意资源,因此它常被用来隔离一些外部类的复杂操作,例如一个外部类过于庞大,功能点很多,你就可以按照不同的功能点设置不同的成员内部类进行逻辑归类。
2.2:局部内部类
局部内部类就是定义在一个方法或者一个作用域里面的类,定义如下:
1 | public class OuterC { |
来总结下局部内部类的特性:
- 无法访问任何外部类的非静态的资源
- 不允许定义访问权限修饰符
- 作为定义在某个作用域内的局部内部类,它可以访问同域的变量,但是同域变量必须加上final关键词
- 允许在任意局部域内定义,定义后即可在下方直接使用
目前针对局部内部类做下了解即可,真正开发中极少用到。
2.3:匿名内部类
匿名内部类即无名称的内部类,我们在前面学过抽象类和接口,它们是无法被实例化的,但是我们现在这样做:
step1
:定义一个接口,让它拥有两个方法,a和b
1 | public interface A { |
step2
:定义一个抽象类,让它拥有抽象方法c
1 | public abstract class B { |
step3
:定义一个普通的类,让其拥有一个接受A和B类型参数的方法,然后触发abc方法
1 | public class C { |
ok,我们前面讲过,接口和抽象类都是无法被直接实例化的,所以我们在调用testInner这个方法的时候一定是传的A的实现类和B的子类的对象,但我现在告诉你,我没有实现A的类,也没有B的子类,该传什么呢?好的,我们开始引入匿名内部类:
1 | public static void main(String[] args){ |
输出如下:
1 | 实现了A接口的匿名内部类a方法逻辑实现 |
可以看到,匿名内部类里的实现逻辑已被成功触发。
这是在干什么呢?为什么好好的实现类和子类你不写,非要用匿名内部类写成这样?其实这是一种偷懒的写法,有些类你不想专门给它写实现和子类的话,别的地方又需要用到它们的对象,那就可以利用匿名内部类来“偷懒”