1.引言
halo,大家好!本篇文章是我在学习Java se的内部类所带来的体会和总结,内部类是OOP中比较综合和难以轻松拿下的一个模块,它涉及到之后我们手撕在后端学习中遇到的各种框架源码,十分重要,也是面向对象技术编程的一个分水岭。
2.基本概念
由字面意思可清楚的知道,一个类内部又完整的嵌套了另外一个类,被嵌套的那个类就叫作内部类(inner class),显然外部的类就叫着外部类(outer class)。基本定义的语法如下:
class Outer{ //----->外部类
class inner{ //----->内部类
}
}
3.内部类的分类
这一章节将总结不同的内部类的语法以及一些简单的案例来帮助我们进一步对内部类进行学习,我们知道,在我们定义变量的时候,会根据它的作用域分为全局变量以及局部变量,相似的,在类中也同样存在局部位置(代码块,成员方法),成员位置可近似类中的全局变量。而在局部位置定义的内部类,又分为有类名和无类名(及匿名内部类 ),其中匿名内部类为高频用点和重难点。定义在成员(全局)位置上的类又可以分为静态和非静态。为方便一目了然,下面给出一个思维导图进行理解。
3.1局部内部类的使用
关于局部内部类的使用,一般有以下几点要求:
- 局部内部类可以直接访问外部类所有访问权限的成员
- 由于局部内部类本质是一个局部变量,所以不能添加访问修饰符,但可以添加final,限制局部内部类的进行继承时来重写或者修改
- 作用域:仅用在它所属的方法或者代码块当中
- 局部内部类访问外部类的成员方式:直接访问
- 外部类访问局部内部类的成员方式:先创建对象,再访问
- 外部其他类不能访问局部内部类
- 若外部类和内部类有同名变量时,遵循就近原则,如果还想再内部类中访问外部类同名变量(非静态),则使用(外部类名.this.成员)进行访问
3.1.1有名局部内部类
/**
演示局部内部类的使用
*/
//定义一个测试类
public class LocalInnerClassTest{
public static void main(String[] args){
Outer outer = new Outer();
outer.f2();
}
}
class Outer{ //外部类
private int n1 = 100;
private void f1(){
System.out.println("Outer f1()....");
}
public void f2(){
class Inner{ //定义在方法的 局部内部类
private int n1 = 200;
public void f3(){//定义内部类的一个方法
System.out.println("n1=" + this.n1 + "外部类的n1=" + Outer.this.n1);//输出同名变量
System.out.println("Outer.this hashcode=" + Outer.this); //调用f2()的对象的地址
f1();//可直接访问外部类的方法
}
}
Inner inner = new Inner();//在外部类的方法中,创建Inner对象,调用对应方法
inner.f3();
}
}
由上面控制台的输出结果可以验证,Outer.this这个对象其实就是外部类outer的对象(及调用f2()方法的对象),this指的是Inner对应的实例对象。
3.1.2匿名局部内部类(重难点)
顾名思义,匿名局部内部类其实就是定义在局部位置(代码块,外部类成员方法中)无名称的类,它本质上是一个类也是一个对象 。基本实现的语法如下图:
下面演示一个匿名局部内部类的实例:
假如目前我们定义了一个接口,若要实现接口的方法则一般我们需要重新定义一个类,接着把类进行实例化,最后调用对象的相应方法即可。但是,有这样一个问题,如果实现接口的这个类我们在实际工作中只使用一次,或者实现的方法重写率较高,会导致我们在工作中定义了很多相似的类并且使用率极低,无疑浪费了资源,同时代码也显得冗余。为解决这样一个问题,java中提供了匿名内部类这一机制,下面代码演示如何解决刚才提出的问题。
public class AnonymousInnerClass {
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}
}
class Outer{ //外部类
private int n1 = 10; //外部类字段
public void method(){//外部类普通方法
//实现基于接口的匿名内部类
Animal animal = new Animal(){
@Override
public void shout() {
System.out.println("嘤嘤嘤...");
}
};
animal.shout();
}
}
interface Animal{ //接口
void shout();
}
代码分析:
在之前我们学习OOP三大特征之多态的时候,知道通过对象调用对应方法会根据动态绑定机制进行选择相应的方法,在创建匿名内部类实例中,引用变量animal的编译类型是Aniaml,而调用方法是根据对象的运行类型进行选择的,那么问题来了,animal的运行类型是什么呢,是Animal吗,但接口同抽象类一样怎么可以实例化(new)呢?
不要着急,我们一步步来解释,首先new Animal()后的中括号{}一添加上去其实就已经对接口进行实现了或者继承一个类,括号中只需要实现接口的方法或者新增子类的成员和父类方法重写即可,而且同时对实现了接口的类或者继承得到的子类进行实例化,相信大家听会比较抽象,其实底层本质如下图所示:
class Outer$1 implements Animal{
@Override
public void shout() {
System.out.println("嘤嘤嘤...");
}
}
其实jdk 底层在创建匿名内部类 Outer$1,立即马上就创建了 Outer$1 实例。所以它匿名了,但好像又没匿名,这个名称如果你不使用对应getClass()方法,是看不到的,在实例后这个匿名类就被回收了,后面再使用会出现找不到这个类的情况。但这样满足了我们只使用一次的需求,不是吗?
System.out.println(animal.getClass());
相信大家对目前对局部匿名类大概有了一个新的认识,于是你们和我一样也是纳闷起来了,这奇葩的语法不利于阅读啊,感觉用处也不大。没事我们再来看一个实例
public class InnerClassExe2 {
public static void main(String[] args) {
new CellPhone().alarmclock(new Bell(){
public void ring(){
System.out.println("懒猪起床啦..");
}
});
}
}
interface Bell{
void ring();
}
class CellPhone{
public void alarmclock(Bell bell){
System.out.println(bell.getClass());
bell.ring();
}
}
是不是多多少少看起来代码简洁多了,不会显得臃肿和冗余,这种用法再框架源码中会经常出现,而综合了面向对象的继承、多态、动态绑定机制、内部类这些知识,所以我们还是很有必要进行学习的,这样我们会站在OOP一个更高的台阶。
3.2成员内部类的使用
哟呼!其实各位秃头宝贝能看到这里,恭喜你,最烦最难拿下的已经过去了,没错,就是匿名内部类,接下来其实就是对内部类的一个收尾了,这一节我们会继续来了解成员内部类的使用。
成员内部类是定义在外部类的成员位置,并且没有static修饰。同样,它有以下几点使用规则:
- 可以直接访问外部类的所有访问权限的成员
- 可以添加访问修饰符,因为本质上它就是一个成员
- 作用域与外部类的成员一致,即整个类体
- 成员内部类访问外部类成员方式:直接访问
- 外部类访问成员内部类方式:创建内部类对象,再访问
- 同样遇到重名变量遵循就近原则,内-->外,使用(外部类名.this.成员变量)进行访问
3.2.1非静态成员内部类
class Outer{
private int id = 100; //成员变量
private void say(){ //成员方法
System.out.println("say....");
}
public class Inner{ //成员内部类
private int id = 2005666;
private String name = "zkk";
public void introduce(){
say();//内部类方法直接调用外部类方法
//如果成员内部类的成员和外部类的成员重名,会遵守就近原则.
//可以通过 外部类名.this.属性 来访问外部类的成员
System.out.println("内部类的id=" + this.id + "外部类的id=" + Outer.this.id);
}
}
//返回一个内部类实例
public Inner getInnerInstance() {
return new Inner();
}
}
外部其他类,使用成员内部类的方式如下:
public class MemberInnerClass {
public static void main(String[] args) {
//1.方式一
Outer.Inner inner = Outer.new Inner();
inner.say();
//2.方式二
Outer outer = new Outer();
Outer.Inner inner1 = outer.getInnerInstance();
inner1.introduce();
}
}
3.2.2静态成员内部类
静态内部类是定义在外部类的成员位置,并且有static修饰,同样得遵守以下几点规则:
- 可以直接访问外部类所有访问权限的静态成员
- 可以添加访问修饰符,因为本质上就是一个普通的成员
- 作用域:整个类体
- 静态内部类访问外部静态成员方式:直接访问
- 外部类访问静态内部类方式:先创建对象,再访问
- 若外部类和静态内部类有重名成员时,还是遵循就近原则,又内部类访问外部类(外部类名.静态成员)
4.小结
芜湖,终于结束了!!!希望大家能对内部类有了一个整体大概的认知,本章是OOP的大Boss,如果小伙伴们难以理解,很大原因就是OOP的三大特性(封装、继承、多态)学得不够扎实,se是Java的基石,希望要从事后端的小伙伴能一起放下浮躁的心,打好地基,不要一味追求主流的框架,用的迷迷糊糊,会给以后阅读代码带来很多麻烦。如果本章有任何问题,希望大家留言一起学习讨论鸭!!
我亦无他,唯手熟尔。