本文章参考B站 Java入门基础视频教程,java零基础自学首选黑马程序员Java入门教程(含Java项目和Java真题),仅供个人学习使用,部分内容为本人自己见解,与黑马程序员无关。
简单来说,面向对象就是找东西(找对象),设计东西(设计对象)过来解决问题(编程)
类
代码演示
输出结果:
多个对象的内存图
- 对象放在哪个位置?
- 堆内存中
- Car c = new Car(); c变量名中存储的是什么?
- 存储的是对象在堆内存中的地址。
- 成员变量(name、price)的数据放在哪里,存在于哪个位置?
- 对象中,存在于堆内存中。
代码演示
输出结果:
两个变量指向同一个对象的内存图
代码演示
输出结果:注意 s1.hobby 已经发生变化
数组Array和集合的区别:
- 数组是大小固定的,并且同一个数组只能存放类型一样的数据(基本类型/引用类型)
- JAVA集合可以存储和操作数目不固定的一组数据。
- 若程序时不知道究竟需要多少对象,需要在空间不足时自动扩增容量,则需要使用容器类库,array不适用。
补充:对象数组里面的每一个对象的值也是引用地址
通过构造器可以创建对象
代码演示
输出结果:
this:代表当前对象
代码演示
输出结果:
没使用this的情况
输出结果:
可以发现名字相同的话是就就近赋值,把传过来的值赋值给自己,并没有修改对象的属性值
加了 this 后效果演示
输出结果:
this应用
面向对象三大特征:封装、继承、多态 对象代表什么,就得封装对应的数据,并提供数据对应的行为
- 人画圆,画圆的方法应该在圆对象里面 (圆是圆自己画出来的,我们只是调用了画圆的方法)
- 人关门,关门的方法应该在门对象里面(门是门自己关的,我们只是提供了作用力,相当于调用了关门的方法)
封装就是指将成员变量私有,提供方法暴露
封装的好处演示
输出结果:
String 类代表字符串,Java 程序中的所有字符串文字(例如“abc”)都被实现为此类的实例。也就是说,Java 程序中所有的双引号字符串,都是 String 类的对象。String 类在 java.lang 包下,所以使用的时候不需要导包!
String 对象创建出来是不会改变的
2.1.1、创建 String 字符串的方式
代码演示
输出结果:
2.1.2、创建 String 字符串的内存原理 (常见面试题)
通过 “” 定义字符串内存原理
通过new构造器得到字符串对象内存原理
2.1.3、Java String 面试题
Java存在编译优化机制,程序在编译时: “a” + “b” + “c” 会直接转成 “abc”,原理可见编译后的 Class 文件
总结
总之,记住只有从常量池中取的才是相同的。
2.1.4、String equals() 方法
字符串比较用 “equals” 比较,不能直接用 “",因为 "” 是判断地址的
-
String xx = “xx” 在常量池中定义
-
用变量接收的字符串则在堆内存中
代码演示
输出结果:
拓展
基本类型比较才用 “==”
2.1.4、String 类常用方法
代码演示
输出结果:
2.1.5、String 类使用案例
String类开发验证码功能
输出结果:
模拟用户登录功能
输出结果:
手机号码屏蔽
输出结果:
2.2.1、数组和 ArrayList的区别
1、数组和集合的元素存储的个数问题?
- 数组定义后类型确定,长度固定
- 集合类型可以不固定,大小是可变的。
2、数组和集合适合的场景
-
数组适合做数据个数和类型确定的场景
-
集合适合做数据个数不确定,且要做增删元素的场景
重点:数组跟 ArrayList 变量里面存的都是引用地址,只是获取的时候Java做了优化。
2.2.2、ArrayList 类入门 (构建ArrayList)
输出结果:
2.2.3、ArrayList 类对于泛型的支持
泛型:
Java泛型是J2 SE1.5中引入的一个新特性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
2.2.4、ArrayList 类常用方法
代码演示
输出结果:
2.2.5、ArrayList 类使用案例
遍历并删除元素值(从后往前扫描)
输出结果:
注意:应该从后往前扫描,如果要从前往后扫描,在移除元素后应该执行 “i–”
存储自定义类型的对象 (结合数组)
- 集合
直接输出集合是一串内存地址
- 数组
学生信息系统的数据搜索
输出结果:
此项目可以深刻理解面向对象、参数传递、定义方法的好处
定义账户类
ATM 系统基本业务实现
static关键字的作用
-
static 是静态的意思,可以修饰成员变量和成员方法。
-
static 修饰成员变量表示该成员变量只在内存中只存储一份,可以被共享访问、修改。
-
非 static 修饰成员变量是属于对象的,需要创建对象才能使用
成员变量
代码演示
输出结果:
内存原理:
总结
- 成员变量的分类和访问分别是什么样的?
-
静态成员变量(有static修饰,属于类、加载一次,可以被共享访问)
-
实例成员变量(无static修饰,属于对象):
- 两种成员变量各自在什么情况下定义?
-
静态成员变量:表示在线人数等需要被共享的信息。
-
实例成员变量:属于每个对象,且每个对象信息不同时(name,age,…等)
成员方法的分类
-
静态成员方法(有static修饰,属于类),建议用类名访问,也可以用对象访问。
-
实例成员方法(无static修饰,属于对象),只能用对象触发访问。
使用场景
-
表示对象自己的行为的,且方法中需要访问实例成员的,则该方法必须申明成实例方法。
-
如果该方法是以执行一个共用功能为目的,则可以申明成静态方法。
代码演示
输出结果:
内存原理
- 工具类中定义的都是一些静态方法,每个方法都是以完成一个共用的功能为目的
- 工具类的好处:一是调用方便,二是提高了代码复用(一次编写,处处可用)
验证码工具类
数组工具类
测试
输出结果:
工具类的定义注意
-
建议将工具类的构造器进行私有,工具类无需创建对象。
-
里面都是静态方法,直接用类名访问即可。
代码块概述
-
代码块是类的5大成分之一(成员变量、构造器,方法,代码块,内部类),定义在类中方法外。
-
在Java类下,使用 { } 括起来的代码被称为代码块
代码块分类
- 静态代码块:
-
格式:static{}
-
特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发、只执行一次
-
使用场景:在类加载的时候做一些静态数据初始化的操作,以便后续使用。
- 构造代码块(了解,用得少):
-
格式:{}
-
特点:每次创建对象,调用构造器执行时,都会执行该代码块中的代码,并且在构造器执行前执行
-
使用场景:初始化实例资源。
静态代码块作用
-
如果要在启动系统时对数据进行初始化。
-
建议使用静态代码块完成数据的初始化操作,代码优雅。
静态代码块优先执行
输出结果:
静态代码块应用
输出结果:
构造代码块(实例代码块)
- 构造器代码块优先于构造器先执行
输出结果
饿汉式
输出结果:
懒汉式(线程不安全)
挖坑:后面会具体分析
输出结果:
-
静态方法只能访问静态的成员,不可以直接访问实例成员。
-
实例方法可以访问静态的成员,也可以访问实例成员。
-
静态方法中是不可以出现this关键字的。
总结:总之,记住静态方法无需创建对象就能使用,一切就很好理解了。
什么是继承?
-
Java中提供一个关键字extends,用这个关键字,我们可以让一个类和另一个类建立起父子关系。
-
Student称为子类(派生类),People称为父类(基类 或超类)。
使用继承的好处
- 当子类继承父类后,就可以直接使用父类公共的属性和方法了。因此,用好这个技术可以很好的我们提高代码的复用性。
继承展示
- 子类继承父类,子类可以得到父类的属性和行为,子类可以使用。Java中子类更强大。
内存原理
继承的特点
- 子类可以继承父类的属性和行为,但是子类不能继承父类的构造器。
- Java是单继承模式:一个类只能继承一个直接父类。
- Java不支持多继承、但是支持多层继承。
- Java中所有的类都是Object类的子类。
注意事项
1、子类是否可以继承父类的构造器?
- 不可以的,子类有自己的构造器,父类构造器用于初始化父类对象。
2、子类是否可以继承父类的私有成员?
- 可以的,只是不能直接访问。(挖坑,后面填)
3、子类是否可以继承父类的静态成员?
- 有争议的知识点。
- 子类可以直接使用父类的静态成员(共享)
- 但个人认为:子类不能继承父类的静态成员。(共享并非继承)
4、在子类方法中访问成员(成员变量、成员方法)满足:就近原则
-
先子类局部范围找
-
然后子类成员范围找
-
然后父类成员范围找,如果父类范围还没有找到则报错
补充:
成员变量:
1、成员变量定义在类中,在整个类中都可以被访问。
2、成员变量随着对象的建立而建立,随着对象的消失而消失,存在于对象所在的堆内存中。
3、成员变量有默认初始化值。
局部变量:
1、局部变量只定义在局部范围内,如:函数内,语句内等,只在所属的区域有效。
2、局部变量存在于栈内存中,作用的范围结束,变量空间会自动释放。
3、局部变量没有默认初始化值
- 可以通过super关键字,指定访问父类的成员。
输出结果:
什么是方法重写?
- 在继承体系中,子类出现了和父类中一模一样的方法声明,我们就称子类这个方法是重写的方法。
方法重写的应用场景
-
当子类需要父类的功能,但父类的该功能不完全满足自己的需求时。
-
子类可以重写父类中的方法。
案例演示
-
旧手机的功能只能是基本的打电话,发信息
-
新手机的功能需要能够:基本的打电话下支持视频通话。基本的发信息下支持发送语音和图片。
父类
子类
测试类
输出结果:
@Override重写注解
-
@Override是放在重写后的方法上,作为重写是否正确的校验注解。
-
加上该注解后如果重写错误,编译阶段会出现错误提示。
-
建议重写方法都加@Override注解,代码安全,优雅!
方法重写注意事项和要求
-
重写方法的名称、形参列表必须与被重写方法的名称和参数列表一致。
-
私有方法不能被重写。
-
子类重写父类方法时,访问权限必须大于或者等于父类 (暂时了解 :缺省 < protected < public)
-
子类不能重写父类的静态方法,如果重写会报错的
-
开发中一般重写方法的名称、形参列表,访问权限和被重写方法保持一致,只是更改方法里面内容
5.4.1、子类构造器的特点
特点
子类中所有的构造器默认都会先访问父类中无参的构造器,再执行自己。
为什么?
-
子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据。
-
子类初始化之前,一定要调用父类构造器先完成父类数据空间的初始化。
怎么调用父类构造器的?
- 子类构造器的第一行语句默认都是:super(),不写也存在。
代码演示
父类
子类
测试类
输出结果:
5.4.1、子类构造器访问父类有参构造器
super调用父类有参数构造器的作用:
- 初始化继承自父类的数据。
如果父类中没有无参数构造器,只有有参构造器,会出现什么现象呢?
- 会报错。因为子类默认是调用父类无参构造器的。
如何解决?
- 子类构造器中可以通过书写 super(…),手动调用父类的有参数构造器
代码演示
父类
子类
测试类:
输出结果
this() 访问本类构造器案例分析
this(…)和super(…)使用注意点:
-
子类通过 this (…)去调用本类的其他构造器,本类其他构造器会通过 super 去手动调用父类的构造器,最终还是会调用父类构造器的。
-
注意:this(…) super(…) 都只能放在构造器的第一行,所以二者不能共存在同一个构造器中。
- 包是用来分门别类的管理各种不同类的,类似于文件夹、建包利于程序的管理和维护。
导包
- 相同包下的类可以直接访问,不同包下的类必须导包,才可以使用!导包格式:import 包名.类名;
- 假如一个类中需要用到不同类,而这个两个类的名称是一样的,那么默认只能导入一个类,另一个类要带包名访问。(面试可能会问到)
什么是权限修饰符?
- 权限修饰符:是用来控制一个成员能够被访问的范围的。
- 可以修饰成员变量,方法,构造器,内部类,不同权限修饰符修饰的成员能够被访问的范围将受到限制。
权限修饰符的分类和具体作用范围
- 权限修饰符:有四种作用范围由小到大(private -> 缺省 -> protected - > public )
自己定义成员(方法,成员变量,构造器等)一般满足如下要求
- 成员变量一般私有。
- 方法一般公开。
- 如果该成员只希望本类访问,使用private修饰。
- 如果该成员只希望本类,同一个包下的其他类和子类访问,使用protected修饰。
在子类中应该用子类对象,而不是用父类对象去访问父类对象中的 protected 方法
final的作用
-
final 关键字是最终的意思,可以修饰(方法,变量,类)
-
修饰方法:表明该方法是最终方法,不能被重写。
-
修饰变量:表示该变量第一次赋值后,不能再次被赋值(有且仅能被赋值一次)。
-
修饰类:表明该类是最终类,不能被继承。
-
工具类一般可以使用 Final 修饰
final修饰变量的注意
- final修饰的变量是基本类型:那么变量存储的数据值不能发生改变。
- final修饰的变量是引用类型:那么变量存储的地址值不能发生改变,但是地址指向的对象内容是可以发生变化的。
参考链接
https://www.cnblogs.com/cainiao-chuanqi/p/11073993.html
1.定义的位置不一样【重点】
- 局部变量:在方法的内部
- 成员变量:在方法的外部,直接写在类当中
2.作用范围不一样【重点】
- 局部变量:只有方法当中才可以使用,出了方法就不能再用了
- 成员变量:整个类都可以通用
3.默认值不一样【重点】
- 局部变量:没有默认值,如果要想使用,必须手动进行赋值
- 成员变量:如果没有赋值,会有默认值,规则和数组一样
4.内存的位置不一样(了解)
- 局部变量:位于栈内存
- 成员变量:位于堆内存
5.生命周期不一样(了解)
- 局部变量:随着方法进栈而诞生,随着方法出栈而消失
- 成员变量:随着对象的创建而诞生,随着对象被垃而消失
概述
- 常量是使用了public static final修饰的成员变量,必须有初始化值,而且执行的过程中其值不能被改变。
- 常量的作用和好处:可以用于做系统的配置信息,方便程序的维护,同时也能提高可读性。
- 常量命名规范:英文单词全部大写,多个单词下划线连接起来。
常量的执行原理
- 在编译阶段会进行“宏替换”,把使用常量的地方全部替换成真实的字面量。
- 这样做的好处是让使用常量的程序的执行性能与直接使用字面量是一样的。
常量做信息标志和分类
概述
- 枚举是Java中的一种特殊类型
- 枚举的作用:“是为了做信息的标志和信息的分类”。
反编译后观察枚举的特征
枚举改进超级玛丽代码
选择常量做信息标志和分类
- 虽然可以实现可读性,但是入参值不受约束,代码相对不够严谨。
枚举做信息标志和分类
- 代码可读性好,入参约束严谨,代码优雅,是最好的信息分类技术!建议使用!
拓展:向枚举中添加新方法
测试
输出内容
在Java中abstract是抽象的意思,如果一个类中的某个方法的具体实现不能确定,就可以申明成abstract修饰的抽象方法(不能写方法体了),这个类必须用abstract修饰,被称为抽象类。
抽象的使用总结与注意事项
- 抽象类可以理解成类的不完整设计图,是用来被子类继承的。
- 一个类如果继承了抽象类,那么这个类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
抽象类的作用是什么样的?
- 可以被子类继承、充当模板的、同时也可以提高代码复用。
抽象方法是什么样的?
- 只有方法签名,没有方法体,使用了abstract修饰。
继承抽象类有哪些要注意?
- 一个类如果继承了抽象类,那么这个类必须重写完抽象类的全部抽象方法。
- 否则这个类也必须定义成抽象类。
抽象父类
实现子类
测试类
输出结果
- 抽象类得到了抽象方法,失去了创建对象的能力。
- 类有的成员(成员变量、方法、构造器)抽象类都具备
- 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
- 一个类继承了抽象类必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
- 不能用abstract修饰变量、代码块、构造器。
抽象类为什么不能创建对象?
如果抽象类可以创建对象,那么就可以调用里面的抽象方法,方法还没被实现就被调用,程序就崩了。
互斥关系
- abstract定义的抽象类作为模板让子类继承,final定义的类不能被继承。
- 抽象方法定义通用功能让子类重写,final定义的方法子类不能重写。
使用场景
- 当系统中出现同一个功能多处在开发,而该功能中大部分代码是一样的,只有其中部分可能不同的时候。
模板方法模式实现步骤
- 把功能定义成一个所谓的模板方法,放在抽象类中,模板方法中只定义通用且能确定的代码。
- 模板方法中不能决定的功能定义成抽象方法让具体子类去实现。
案例:银行利息结算系统
需求:
- 某软件公司要为某银行的业务支撑系统开发一个利息结算系统,账户有活期和定期账户两种,
- 活期是0.35%,定期是 1.75%,定期如果满10万额外给予3%的收益。
- 结算利息要先进行用户名、密码验证,验证失败直接提示,登录成功进行结算
使用模板方法模式前
缺点:代码冗余,有相当多的实现方法
改进策略分析
- 创建一个抽象的账户类Account作为父类模板,提供属性(卡号,余额)
- 在父类Account中提供一个模板方法实现登录验证,利息结算、利息输出。
- 具体的利息结算定义成抽象方法,交给子类实现。
- 定义活期账户类,让子类重写实现具体的结算方法
- 定义定期账户类,让子类重写实现具体的结算方法
- 创建账户对象,完成相关功能。
使用模板方法模式后
板方法我们是建议使用final修饰的,这样会更专业,那么为什么呢?
模板方法是给子类直接使用的,不是让子类重写的,一旦子类重写了模板方法就失效了。
模板方法模式解决了什么问题?
- 极大的提高了代码的复用性
- 模板方法已经定义了通用结构,模板不能确定的定义成抽象方法。
- 使用者只需要关心自己需要实现的功能即可。
接口的用法
接口是用来被类实现(implements)的,实现接口的类称为实现类。实现类可以理解成所谓的子类。
从上面可以看出,接口可以被类单实现,也可以被类多实现。
接口实现的注意事项
一个类实现接口,必须重写完全部接口的全部抽象方法,否则这个类需要定义成抽象类。
基本小结
- 类和类的关系:单继承。
- 类和接口的关系:多实现。
- 接口和接口的关系:多继承,一个接口可以同时继承多个接口。
接口多继承的作用
- 规范合并,整合多个接口为同一个接口,便于子类实现。
接口1
接口2
接口3
实现类
1、接口不能创建对象 2、一个类实现多个接口,多个接口中有同样的静态方法不冲突。 3、一个类继承了父类,同时又实现了接口,父类中和接口中有同名方法,默认用父类的。 4、一个类实现了多个接口,多个接口中存在同名的默认方法,不冲突,这个类重写该方法即可。 5、一个接口继承多个接口,是没有问题的,如果多个接口中存在规范冲突则不能多继承。
多态
方法调用:编译看左边,运行看右边。 变量调用:编译看左边,运行也看左边。(多态侧重行为多态)
父类
子类1
子类2
测试类
输出结果
什么是多态?
- 同类型的对象,执行同一个行为,会表现出不同的行为特征。
多态的常见形式
多态中成员访问特点
- 方法调用:编译看左边,运行看右边。
- 变量调用:编译看左边,运行也看左边。(多态侧重行为多态)
多态的前提
- 有继承/实现关系;有父类引用指向子类对象;有方法重写。
优势
-
在多态形式下,右边对象可以实现解耦合,便于扩展和维护。
-
定义方法的时候,使用父类型作为参数,该方法就可以接收这父类的一切子类对象,体现出多态的扩展性与便利。
多态下不能使用子类的独有功能
可用强制类型转换解决,下文介绍。
自动类型转换(从子到父):
- 子类对象赋值给父类类型的变量指向。
强制类型转换(从父到子)
- 此时必须进行强制类型转换:子类 对象变量 = (子类)父类类型的变量
- 作用:可以解决多态下的劣势,可以实现调用子类独有的功能。
多态下引用数据类型的类型转换
如果转型后的类型和对象真实类型不是同一种类型,那么在转换的时候就会出现ClassCastException
Java建议强转转换前使用 instanceof 判断当前对象的真实类型,再进行强制转换
- 变量名 instanceof 真实类型
- 判断关键字左边的变量指向的对象的真实类型,是否是右边的类型或者是其子类类型,是则返回true,反之返回false。
案例
需求:
- 使用面向对象编程模拟:设计一个电脑对象,可以安装2个USB设备
- 鼠标:被安装时可以完成接入、调用点击功能、拔出功能。
- 键盘:被安装时可以完成接入、调用打字功能、拔出功能。
分析:
- 定义一个USB的接口(申明USB设备的规范必须是:可以接入和拔出)。
- 提供2个USB实现类代表鼠标和键盘,让其实现USB接口,并分别定义独有功能。
- 创建电脑对象,创建2个USB实现类对象,分别安装到电脑中并触发功能的执行。
usb接口
鼠标类
键盘类
电脑类
测试类
输出结果
内部类
- 内部类就是定义在一个类里面的类,里面的类可以理解成(寄生),外部类可以理解成(宿主)。
内部类的使用场景、作用
- 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构可以选择使用内部类来设计。
- 内部类通常可以方便访问外部类的成员,包括私有的成员。
- 内部类提供了更好的封装性,内部类本身就可以用private protectecd等修饰,封装性可以做更多控制。
什么是静态内部类?
- 有static修饰,属于外部类本身。
- 它的特点和使用与普通类是完全一样的,类有的成分它都有,只是位置在别人里面而已。
静态内部类创建对象的格式
静态内部类的访问拓展
1、静态内部类中是否可以直接访问外部类的静态成员?
- 可以,外部类的静态成员只有一份可以被共享访问。
2、静态内部类中是否可以直接访问外部类的实例成员?
- 不可以的,外部类的实例成员必须用外部类对象访问。
静态内部类的使用场景、特点、访问总结
- 如果一个类中包含了一个完整的成分,如汽车类中的发动机类。
- 特点、使用与普通类是一样的,类有的成分它都有,只是位置在别人里面而已。
- 可以直接访问外部类的静态成员,不能直接访问外部类的实例成员。
- 注意:开发中实际上用的还是比较少。
什么是成员内部类?
- 无static修饰,属于外部类的对象。
- JDK16之前,成员内部类中不能定义静态成员,JDK 16开始也可以定义静态成员了。
成员内部类创建对象的格式
成员内部类的访问拓展
1、成员内部类中是否可以直接访问外部类的静态成员?
- 可以,外部类的静态成员只有一份可以被共享访问。
2、成员内部类的实例方法中是否可以直接访问外部类的实例成员?
- 可以,因为必须先有外部类对象,才能有成员内部类对象,所以可以直接访问外部类对象的实例成员
面试
局部内部类 (鸡肋语法,了解即可)
- 局部内部类放在方法、代码块、构造器等执行体中。
- 局部内部类的类文件名为: 外部类$N内部类.class。
- 可以定义抽象类,接口(jdk15 开始支持)
其实就是在方法里写一个类,这种做法我们开发是尽量避免使用的。
11.5.1、概述
匿名内部类
- 本质上是一个没有名字的局部内部类,定义在方法中、代码块中、等。
- 作用:方便创建子类对象,最终目的为了简化代码编写。
特点总结
- 匿名内部类是一个没有名字的内部类。
- 匿名内部类写出来就会产生一个匿名内部类的对象。
- 匿名内部类的对象类型相当于是当前new的那个的类型的子类类型。
代码演示
输出结果
11.5.2、匿名内部类常见使用形式
需求:某个学校需要让老师,学生,运动员一起参加游泳比赛
输出结果
总结
匿名内部类可以作为方法的实际参数进行传输。
简化写法(提前理解)
11.5.3、匿名内部类真实使用场景
代码演示