文章目录
- 一、 概述
- (1) 介绍
- 1. 什么是内部类
- 2. 为什么要声明内部类
- 3. 内部类使用举例
- 4. 内部类的分类
- (2)举例
- (3)重点知识
- 1. 对成员内部类的理解
- 2. 创建成员内部类的实例
- 2.1 静态成员内部类
- 2.2 非静态成员内部类
- 3. 在成员内部类中调用外部类的结构
- 3.1 非静态成员内部类的方法里面调用属性
- 3.2 非静态成员内部类调用方法
- 3.3 字节码文件
- 4. 局部内部类的使用
- 4.1 介绍
- 4.2 开发中
- 4.3 字节码文件
- 4.4 再举例
- 二、 成员内部类
- (1)概述
- (2) 创建成员内部类对象
- (3)举例
- 三、 局部内部类
- (1)非匿名局部内部类
- (2) 匿名内部类
- 四、练习
- (1)练习1
- (2)练习2
- 1. 接口
- 2. 抽象类
- 3. 代码
一、 概述
面向对象三条主线:
①类及类的内部成员(属性、方法、构造器;代码块、内部类)
②封装、继承、多态
③关键字
平时开发的时候,自己定义内部类的机会比较少,一般都是源码会使用,我们要知道它怎么用的即可。
(1) 介绍
1. 什么是内部类
将一个类A定义在另一个类B里面,里面的那个类A就称为内部类(InnerClass)
,类B则称为外部类(OuterClass)
。
2. 为什么要声明内部类
具体来说,当一个事物A的内部,还有一个部分需要一个完整的结构B(用一个变量不足以刻画它,里面可能还有更丰富的属性、方法)进行描述,而这个内部的完整的结构B又只为外部事物A提供服务,不在其他地方单独使用,那么整个内部的完整结构B最好使用内部类。
平常我们自己设计的时候,可能会将类A与类B设计为并列结构,但是源码会更加规范,它发现类B只在类A里面使用,那就不妨将类B声明在类A里面,表明只在A里面用,那么这个类B就是内部类。
总的来说,遵循高内聚、低耦合
(该隐藏的隐藏、该暴露的暴露)的面向对象开发原则。
3. 内部类使用举例
<1> Thread
类内部声明了State类(状态),表示线程的生命周期。
<2> HashMap
类中声明了Node类,表示封装的key和value。
4. 内部类的分类
根据内部类声明的位置(如同变量
的分类),我们可以分为:
-
成员内部类:直接声明在外部类的里面。(方法、构造器的外面)
- 使用static修饰的:静态的成员内部类(跟外部类的类相关,随着类的加载而加载)
- 不使用static修饰的:非静态的成员内部类(跟外部类的对象相关,随着对象的创建而加载)
-
局部内部类:声明在方法内、构造器内、代码块内的内部类
- 匿名的局部内部类
- 非匿名的局部内部类
(2)举例
class Person{ //外部类
//静态成员内部类
static class Dog{ //只供Person使用,并不是属于Person的一部分
}
//非静态成员内部类
class Bird{
}
//方法
public void method(){
//局部内部类
class InnerClass1{
}
}
//构造器
public Person(){
//局部内部类
class InnerClass2{ //可以与方法method()中的InnerClass1重名
}
}
//代码块
{
//局部内部类
class InnerClass3{
}
}
}
(3)重点知识
内部类这节要讲的知识:
- 成员内部类的理解。
- 如何创建成员内部类的实例。
- 如何在成员内部类中调用外部类的结构。
- 局部内部类的基本使用。
1. 对成员内部类的理解
关于成员内部类
的理解:
-
从类的角度看:
- 内部可以声明属性、方法、构造器、代码块、内部类等结构。
- 此内部类可以声明父类,可以实现接口(单继承,多实现)。
- 可以使用
final
修饰(不能有子类)。 - 可以使用
abstract
修饰(不能实例化)。
-
从外部类的成员的角度看:
- 在内部可以调用外部类的结构。比如:属性、方法等
- 除了使用
public
、缺省权限
修饰之外,还可以使用private
、protected
修饰。(以前说的类都是外部类,可以使用public、默认权限之外,不能使用private)而成员可以使用四种权限来修饰。 - 可以使用
static
修饰。(外部类用static修饰没有意义)
2. 创建成员内部类的实例
2.1 静态成员内部类
创建Person的静态的成员内部类Dog。
public class OuterClassTest {
public static void main(String[] args) {
//1.创建Person的静态的成员内部类的实例
}
}
class Person{ //外部类
//静态成员内部类
static class Dog{ //只供Person使用,并不是属于Person的一部分
}
}
Dog是静态的,随着类的加载而加载,要创建Dog的对象,不需要创建Person的对象。
若直接new一个Dog,又不合适,因为Dog也没有对外暴露。会报错,如下:
当然要说清楚是谁里面的Dog呀,所以需要这样:new Person.Dog();
那前面声明呢?直接是Dog吗?不行哦,看下面:
同样需要说明是谁里面的Dog,这样:Person.Dog dog=new Person.Dog();
此时Dog里面的方法,就可以调用啦。
比如:
🌱代码
public class OuterClassTest {
public static void main(String[] args) {
//1.创建Person的静态的成员内部类的实例
Person.Dog dog=new Person.Dog();
dog.eat();
}
}
class Person{ //外部类
//1.静态成员内部类
static class Dog{ //只供Person使用,并不是属于Person的一部分
public void eat(){
System.out.println("狗吃骨头");
}
}
}
🍺输出结果
2.2 非静态成员内部类
创建Person的非静态的成员内部类的实例。
非静态的结构都需要依赖外部类的对象,所以要想创建Bird类的对象,就需要先创建外部类Person的对象。
若是像上面那种写法是不行的(因为Bird不是静态的),如下:
需要先创建Person的对象:Person p1=new Person();
接下来的写法很容易写错,这样来写:p1.new Bird();
左边的声明还是和上一个例子一样:Person.Bird bird=p1.new Bird();
注意这样的写法是错误的:
应该是p1这个对象去new。
此时Bird里面的方法,就可以调用啦。
比如:
🌱代码
public class OuterClassTest {
public static void main(String[] args) {
//2.创建Person的非静态的成员内部类的实例
//Person.Bird bird=new Person.Bird(); //报错
Person p1=new Person(); //new p1.Bird(); 报错
Person.Bird bird=p1.new Bird();
bird.eat();
}
}
class Person{ //外部类
//2.非静态成员内部类
class Bird{
public void eat(){
System.out.println("鸟吃虫子");
}
}
}
🍺输出结果
3. 在成员内部类中调用外部类的结构
这个其实没什么特别的,只是有的时候会出现重名的属性、方法。
这里就以内部类Bird
为例。
3.1 非静态成员内部类的方法里面调用属性
比如在Person里面定义了一个String型的name,还有int型的age。
Bird类里面也定义一个name,还有方法show()。如下:
public class OuterClassTest {
public static void main(String[] args) {
//2.创建Person的非静态的成员内部类的实例
Person.Bird bird=p1.new Bird();
bird.eat();
}
}
class Person{ //外部类
//属性
String name="Piter";
int age=1;
//非静态成员内部类
class Bird{
String name="啄木鸟";
public void eat(){
System.out.println("鸟吃虫子");
}
public void show(){
System.out.println("age= "+age);
}
}
}
可以发现,在Bird类里面可以直接调用外部Person类里面的属性age
。说明可以直接来写。
那name
的调用呢?这里当然就近是自己类里面的啦。
若是此时show()方法有一个参数String name,那么此时输出语句的name就是形参的name了,如下:
那现在想调用Bird类里面的name
咋办?
前面说过,用this
去区分形参和环境变量,如下:
这个this指的是Bird还是Person?看this写在哪个方法里面的,谁调用show(),谁就是this。
此时show()是Bird里面的方法,所以this指的是Bird。
那如何去调用Person里面的name呢?
Person类里面的name属性可以这样来写:Person.this.name;
接下来用bird调用show()方法,如下:
🌱代码
package yuyi01;
public class OuterClassTest {
public static void main(String[] args) {
//2.创建Person的非静态的成员内部类的实例
Person.Bird bird=p1.new Bird();
//bird.eat();
bird.show("黄鹂");
}
}
class Person{ //外部类
//属性
String name="Piter";
int age=1;
//非静态成员内部类
class Bird{
String name="啄木鸟";
public void eat(){
System.out.println("鸟吃虫子");
}
public void show(String name){
System.out.println("age= "+age);
System.out.println("name= "+name);
System.out.println("BirdName= "+this.name); //Bird类里面的name
System.out.println("PersonName= "+Person.this.name); //Person类里面的name
}
}
}
🍺输出结果
☕强调
这里再次强调一下show()
方法里面的这些调用,如下:
public void show(String name){
System.out.println("age= "+age); //直接输出的是外部类的age,与内部类的属性没有冲突,若要补全的话,应该是 Person.this.age
System.out.println("name= "+name); //这里的name是形参,传入的是什么输出的就是什么
System.out.println("BirdName= "+this.name); //Bird类里面的name
System.out.println("PersonName= "+Person.this.name); //外部Person类里面的name
}
3.2 非静态成员内部类调用方法
内部类里面能不能调用外部类的方法呢?
比如现在在Person类里面写了一个方法eat(),与内部类Bird中的eat()方法重名,如下:
class Person{ //外部类
//属性
String name="Piter";
int age=1;
//静态成员内部类
static class Dog{ //只供Person使用,并不是属于Person的一部分
public void eat(){
System.out.println("狗吃骨头");
}
}
//非静态成员内部类
class Bird{
String name="啄木鸟";
public void eat(){
System.out.println("鸟吃虫子");
}
//...
}
//举例方法
public void eat(){
System.out.println("人吃有营养的食物");
}
//...
}
现在在内部类Bird中写一个show1()方法,调用eat()方法,很显然,这里调用的是自己的eat()方法。
如下:
这里明显省略了this.
。
若此时想调用外部类的eat()方法,就需要这样写:Person.this.eat();
然后在测试类里面测试:bird.show1();
🌱代码
package yuyi01;
public class OuterClassTest {
public static void main(String[] args) {
//2.创建Person的非静态的成员内部类的实例
Person.Bird bird=p1.new Bird();
bird.show1();
}
}
class Person{ //外部类
//属性
String name="Piter";
int age=1;
//静态成员内部类
static class Dog{ //只供Person使用,并不是属于Person的一部分
public void eat(){
System.out.println("狗吃骨头");
}
}
//非静态成员内部类
class Bird{
String name="啄木鸟";
public void eat(){
System.out.println("鸟吃虫子");
}
public void show1(){
eat(); //Bird中的eat()方法, 即: this.eat();
this.eat();
Person.this.eat(); //外部类Person中的eat()方法
}
}
//举例方法
public void eat(){
System.out.println("人吃有营养的食物");
}
}
🍺输出结果
☕强调
这里再次强调一下show1()
方法里面的这些调用,如下:
public void show1(){
eat(); //Bird中的eat()方法, 即: this.eat();
this.eat();
Person.this.eat(); //外部类Person中的eat()方法
}
内部类有能力调用外部类的结构,静态成员内部类不能调用非静态的结构,非静态成员内部类调用外部静态、非静态都可以。
3.3 字节码文件
package yuyi01;
/**
* ClassName: OuterClassTest
* Package: yuyi01
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/29 0029 16:15
*/
public class OuterClassTest {
public static void main(String[] args) {
//1.创建Person的静态的成员内部类的实例
Person.Dog dog=new Person.Dog();
dog.eat();
//2.创建Person的非静态的成员内部类的实例
//Person.Bird bird=new Person.Bird(); //报错
Person p1=new Person(); //new p1.Bird(); 报错
Person.Bird bird=p1.new Bird();
bird.eat();
bird.show("黄鹂");
bird.show1();
}
}
class Person{ //外部类
//属性
String name="Piter";
int age=1;
//静态成员内部类
static class Dog{ //只供Person使用,并不是属于Person的一部分
public void eat(){
System.out.println("狗吃骨头");
}
}
//非静态成员内部类
class Bird{
String name="啄木鸟";
public void eat(){
System.out.println("鸟吃虫子");
}
public void show1(){
eat(); //Bird中的eat()方法, 即: this.eat();
this.eat();
Person.this.eat(); //外部类Person中的eat()方法
}
public void show(String name){
System.out.println("age= "+age); //直接输出的是外部类的age,与内部类的属性没有冲突,若要补全的话,应该是 Person.this.age
System.out.println("name= "+name); //这里的name是形参,传入的是什么输出的就是什么
System.out.println("BirdName= "+this.name); //Bird类里面的name
System.out.println("PersonName= "+Person.this.name); //外部Person类里面的name
}
}
//举例方法
public void eat(){
System.out.println("人吃有营养的食物");
}
//方法
public void method(){
//局部内部类
class InnerClass1{
}
}
//构造器
public Person(){
//局部内部类
class InnerClass2{ //可以与方法method()中的InnerClass1重名
}
}
//代码块
{
//局部内部类
class InnerClass3{
}
}
}
编译完之后会生成字节码文件,接口和类都会对应字节码文件。
☕分析
①Person类有对应的字节码文件Person.class
。
OuterClassTest类也有对应的字节码文件,如下:
②Bird类与Dog类是Person类的内部类,对应字节码文件有一个$
符:Person$Bird.class
和Person$Dog.class
(静态与非静态没有区分)。
③类InnerClass1与类InnerClass2与InnerClass3都是局部内部类。
它们前面有1、2、3,因为局部内部类是要放在相应的方法、构造器、代码块里面的,是可以同名的,前面加个标号以示区分。
在文件夹里面也不能出现同名文件啊,通过1、2、3来区分同名的局部内部类。
大家就熟悉一下,如何表达内部类的字节码文件,通过外部类加一个$符来表达。
4. 局部内部类的使用
4.1 介绍
局部内部类:定义在方法、构造器、代码块中。更多是在方法里面。
比如,A就是局部内部类:
public class OuterClassTest1 {
//说明:局部内部类的使用
public void method1(){
//局部内部类
class A{
//可以声明属性、方法等
}
}
}
4.2 开发中
其实,在开发当中,上面的写法不常见。
一般都是这样,
比如在调用此方法的时候,需要返回一个实例,这个实例是Comparable
类型的,如下:
//开发中的场景
public Comparable getInstance(){
}
这个Comparable
类型其实是一个接口:
现在需要返回一个接口的实例,接口要想造实例,要先提供一个实现类,这个实现类就写到内部了。
<1> 方式1-提供接口的实现类的匿名对象
public class OuterClassTest1 {
//开发中的场景
public Comparable getInstance(){
//提供实现Comparable接口的类
//方式1:提供接口的实现类的匿名对象
class MyComparable implements Comparable{
//重写Comparable里面的抽象方法
@Override
public int compareTo(Object o) {
return 0;
}
}
return new MyComparable();
}
}
这里就相当于提供了Comparable的实现类MyComparable,这个类写到方法里面了,就是局部内部类
。
当我们调用getInstance()
方法的时候,就返回了Comparable一个实现类的对象。
<2> 方式2-提供接口的匿名实现类的匿名对象
刚才接口Comparable的实现类MyComparable是有名的,而造的对象没有名:return new MyComparable();
,所以刚才的方式1叫“提供接口的实现类的匿名对象”。
现在让实现类也匿名。这样写呢:
接口Comparable没有构造器啊,怎么造对象?其实这里是用Comparable充当一下而已,毕竟实现类匿名了。
形式上用接口来充当一下。
小括号后边用一个大括号,体现它是一个实现类,然后把接口里面的抽象方法重写一下即可。
如下:
public class OuterClassTest1 {
//开发中的场景
public Comparable getInstance(){
//提供实现Comparable接口的类
//方式 2:提供接口的匿名实现类的匿名对象
return new Comparable(){
@Override
public int compareTo(Object o) {
return 0;
}
};
}
}
现在我们写的就是“接口的匿名实现类的匿名对象”。实现类虽然匿名了,但还是有的,所以也是内部类。
就是在创建匿名实现类的对象时候,利用了接口的多态,左边是接口类型,右边因为是又利用接口类型来new,但其实new 接口(){}里面的所有就是匿名实现类。
通俗一点就是你造了一个没有名字的类,并且这个类实现了某接口的抽象方法,那么这个没有名字的类就是那个接口的实现类,而没有名字的实现类就叫匿名实现类。
<3> 方式3-提供接口的匿名实现类的对象
可能刚才的方式2大家有点接受不了,那就来看一下有名对象。
如下:
public class OuterClassTest1 {
//开发中的场景
public Comparable getInstance(){
//提供实现Comparable接口的类
//方式 3:提供接口的匿名实现类的对象
Comparable c=new Comparable() {
@Override
public int compareTo(Object o) {
return 0;
}
};
return c;
}
}
<4> 方式4-提供接口的实现类的对象
再来看一下最基础的方式吧。
如下:
public class OuterClassTest1 {
//开发中的场景
public Comparable getInstance(){
//方式 4:提供接口的实现类的对象
class MyComparable implements Comparable{
//重写Comparable里面的抽象方法
@Override
public int compareTo(Object o) {
return 0;
}
}
MyComparable m=new MyComparable();
return m;
}
}
4.3 字节码文件
package yuyi01;
/**
* ClassName: OuterClass
* Package: yuyi01
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/30 0030 17:28
*/
public class OuterClassTest1 {
//说明:局部内部类的使用
public void method1(){
//局部内部类
class A{
//可以声明属性、方法等
}
}
//开发中的场景
public Comparable getInstance(){
//提供实现Comparable接口的类
//方式 1:提供接口的实现类的匿名对象
/*class MyComparable implements Comparable{
//重写Comparable里面的抽象方法
@Override
public int compareTo(Object o) {
return 0;
}
}
return new MyComparable();*/
//方式 2:提供接口的匿名实现类的匿名对象
/* return new Comparable(){
@Override
public int compareTo(Object o) {
return 0;
}
};*/
//方式 3:提供接口的匿名实现类的对象
/*Comparable c=new Comparable() {
@Override
public int compareTo(Object o) {
return 0;
}
};
return c;*/
//方式 4:提供接口的实现类的对象
class MyComparable implements Comparable{
//重写Comparable里面的抽象方法
@Override
public int compareTo(Object o) {
return 0;
}
}
MyComparable m=new MyComparable();
return m;
}
}
对应的字节码文件:
若是匿名实现类的匿名对象,字节码文件是这样的:
从这个角度来看,方式2也有内部类,只不过这个内部类是一个方法里面匿名的类。
4.4 再举例
之前在接口那一节说过这样一个例子:
【USBTest.java】
package yuyi01;
/**
* ClassName: USBTest
* Package: yuyi01
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/11/26 0026 15:59
*/
public class USBTest {
public static void main(String[] args) {
//造一个电脑
Computer computer=new Computer(); //computer类调用抽象接口的抽象方法,具体类实现抽象接口(重写方法)
//场景一:创建接口实现类(Printer)的对象(printer)
//造一个打印机
Printer printer=new Printer();
//在电脑上连打印机
computer.transferData(printer); //transferData()方法里面声明的是USB类型,实际赋值的是Printer类型。即:USB usb=new Printer();
System.out.println(); //换行
//场景二:创建接口实现类的匿名对象
computer.transferData(new Camera());
System.out.println(); //换行
//场景三: 创建接口匿名实现类的对象
USB usb1=new USB(){
public void start(){
System.out.println("U盘开始工作");
}
public void stop(){
System.out.println("U盘结束工作");
}
};
computer.transferData(usb1);
System.out.println(); //换行
//场景四: 创建接口匿名实现类的匿名对象
computer.transferData(new USB(){
public void start(){
System.out.println("扫描仪开始工作");
}
public void stop(){
System.out.println("扫描仪结束工作");
}
});
}
}
//电脑
class Computer{
public void transferData(USB usb){ //多态:USB usb=new Printer();
System.out.println("设备连接成功...");
usb.start();
System.out.println("数据传输细节操作...");
usb.stop();
}
}
//打印机
class Printer implements USB{ //打印机实现USB接口
@Override
public void start() {
System.out.println("打印机开始工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
//照相机
class Camera implements USB{
@Override
public void start() {
System.out.println("照相机开始工作");
}
@Override
public void stop() {
System.out.println("照相机结束工作");
}
}
//USB接口
interface USB{
//声明常量,比如USB的长、宽、高...
//方法
public abstract void start();
void stop();
}
这里的场景三就相当于一个局部内部类:(在main方法里面的匿名实现类)
public class USBTest {
public static void main(String[] args) {
//造一个电脑
Computer computer=new Computer(); //computer类调用抽象接口的抽象方法,具体类实现抽象接口(重写方法)
//场景三: 创建接口匿名实现类的对象
USB usb1=new USB(){
public void start(){
System.out.println("U盘开始工作");
}
public void stop(){
System.out.println("U盘结束工作");
}
};
computer.transferData(usb1);
}
}
//电脑
class Computer{
public void transferData(USB usb){ //多态:USB usb=new Printer();
System.out.println("设备连接成功...");
usb.start();
System.out.println("数据传输细节操作...");
usb.stop();
}
}
//USB接口
interface USB{
//声明常量,比如USB的长、宽、高...
//方法
public abstract void start();
void stop();
}
场景一与场景二都不算是内部类,Printer和Camera都在外部定义了,属于外部类。
场景三和场景四都是在main方法里面提供的类,只不过匿名而已。
看一下字节码文件:
字节码文件可以不关注,代码会写就行。
二、 成员内部类
(1)概述
如果成员内部类中不使用外部类的非静态成员,那么通常将内部类声明为静态内部类
,否则声明为非静态内部类
。
语法格式:
[修饰符] class 外部类{
[其他修饰符] [static] class 内部类{
}
}
成员内部类的使用特征,概括来讲有如下两种角色:
- 成员内部类作为
类的成员的角色
:- 和外部类不同,Inner class还可以声明为private或protected;
- 可以调用外部类的结构。(注意:在静态内部类中不能使用外部类的非静态成员)
- Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量;
- 成员内部类作为
类的角色
:- 可以在内部定义属性、方法、构造器等结构
- 可以继承自己的想要继承的父类,实现自己想要实现的父接口们,和外部类的父类和父接口无关
- 可以声明为abstract类 ,因此可以被其它的内部类继承
- 可以声明为final的,表示不能被继承
- 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
注意点:
- 外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式
- 成员内部类可以直接使用外部类的所有成员,包括私有的数据
- 当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的
(2) 创建成员内部类对象
- 实例化静态内部类
外部类名.静态内部类名 变量 = 外部类名.静态内部类名();
变量.非静态方法();
- 实例化非静态内部类
外部类名 变量1 = new 外部类();
外部类名.非静态内部类名 变量2 = 变量1.new 非静态内部类名();
变量2.非静态方法();
(3)举例
public class TestMemberInnerClass {
public static void main(String[] args) {
//创建静态内部类实例,并调用方法
Outer.StaticInner inner = new Outer.StaticInner();
inner.inFun();
//调用静态内部类静态方法
Outer.StaticInner.inMethod();
System.out.println("*****************************");
//创建非静态内部类实例(方式1),并调用方法
Outer outer = new Outer();
Outer.NoStaticInner inner1 = outer.new NoStaticInner();
inner1.inFun();
//创建非静态内部类实例(方式2)
Outer.NoStaticInner inner2 = outer.getNoStaticInner();
inner1.inFun();
}
}
class Outer{
private static String a = "外部类的静态a";
private static String b = "外部类的静态b";
private String c = "外部类对象的非静态c";
private String d = "外部类对象的非静态d";
static class StaticInner{
private static String a ="静态内部类的静态a";
private String c = "静态内部类对象的非静态c";
public static void inMethod(){
System.out.println("Inner.a = " + a);
System.out.println("Outer.a = " + Outer.a);
System.out.println("b = " + b);
}
public void inFun(){
System.out.println("Inner.inFun");
System.out.println("Outer.a = " + Outer.a);
System.out.println("Inner.a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
// System.out.println("d = " + d);//不能访问外部类的非静态成员
}
}
class NoStaticInner{
private String a = "非静态内部类对象的非静态a";
private String c = "非静态内部类对象的非静态c";
public void inFun(){
System.out.println("NoStaticInner.inFun");
System.out.println("Outer.a = " + Outer.a);
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("Outer.c = " + Outer.this.c);
System.out.println("c = " + c);
System.out.println("d = " + d);
}
}
public NoStaticInner getNoStaticInner(){
return new NoStaticInner();
}
}
三、 局部内部类
(1)非匿名局部内部类
语法格式:
[修饰符] class 外部类{
[修饰符] 返回值类型 方法名(形参列表){
[final/abstract] class 内部类{
}
}
}
- 编译后有自己的独立的字节码文件,只不过在内部类名前面冠以外部类名、$符号、编号。
- 这里有编号是因为同一个外部类中,不同的方法中存在相同名称的局部内部类
- 和成员内部类不同的是,它前面不能有权限修饰符等
- 局部内部类如同局部变量一样,有作用域
- 局部内部类中是否能访问外部类的非静态的成员,取决于所在的方法
举例:
/**
* ClassName: TestLocalInner
* @Author 尚硅谷-宋红康
* @Create 17:19
* @Version 1.0
*/
public class TestLocalInner {
public static void main(String[] args) {
Outer.outMethod();
System.out.println("-------------------");
Outer out = new Outer();
out.outTest();
System.out.println("-------------------");
Runner runner = Outer.getRunner();
runner.run();
}
}
class Outer{
public static void outMethod(){
System.out.println("Outer.outMethod");
final String c = "局部变量c";
class Inner{
public void inMethod(){
System.out.println("Inner.inMethod");
System.out.println(c);
}
}
Inner in = new Inner();
in.inMethod();
}
public void outTest(){
class Inner{
public void inMethod1(){
System.out.println("Inner.inMethod1");
}
}
Inner in = new Inner();
in.inMethod1();
}
public static Runner getRunner(){
class LocalRunner implements Runner{
@Override
public void run() {
System.out.println("LocalRunner.run");
}
}
return new LocalRunner();
}
}
interface Runner{
void run();
}
(2) 匿名内部类
因为考虑到这个子类或实现类是一次性的,那么我们“费尽心机”的给它取名字,就显得多余。那么我们完全可以使用匿名内部类的方式来实现,避免给类命名的问题。
new 父类([实参列表]){
重写方法...
}
new 父接口(){
重写方法...
}
举例1:使用匿名内部类的对象直接调用方法:
interface A{
void a();
}
public class Test{
public static void main(String[] args){
new A(){
@Override
public void a() {
System.out.println("aaaa");
}
}.a();
}
}
举例2:通过父类或父接口的变量多态引用匿名内部类的对象
interface A{
void a();
}
public class Test{
public static void main(String[] args){
A obj = new A(){
@Override
public void a() {
System.out.println("aaaa");
}
};
obj.a();
}
}
举例3:匿名内部类的对象作为实参
interface A{
void method();
}
public class Test{
public static void test(A a){
a.method();
}
public static void main(String[] args){
test(new A(){
@Override
public void method() {
System.out.println("aaaa");
}
});
}
}
四、练习
(1)练习1
🌋题目描述
编写一个匿名内部类,它继承Object,并在匿名内部类中,声明一个方法public void test()打印雨翼轻尘。
请编写代码调用这个方法。
🍹分析
若是写成外部类,应该是这样来写,大家都能看得懂:
public class ObjectTest {
public static void main(String[] args) {
SubObject sub1 = new SubObject(); //创建SubObject的实例
sub1.test();
}
}
class SubObject extends Object{ //继承于Object可写可不写,本身就继承于Object
public void test(){
System.out.println("雨翼轻尘");
}
}
输出结果:
回归到一开始说“面向对象”时候讲过的事儿:
以后写代码全是这个套路。
现在需要的是匿名,通常只调用一次。
那么直接在main方法里面做即可。
//提供有一个继承于Object的匿名子类的对象
new Object(); //这样写表示new的是一个Object
但是现在new的不是Object,那么就加上一个大括号。
在大括号里面写上刚才的方法即可,如下:
//提供有一个继承于Object的匿名子类的对象
new Object(){
public void test(){
System.out.println("雨翼轻尘");
}
};
这就相当于把这个匿名子类的对象也顺带造好了。这个对象有名字,写前面即可。
注意因为子类没有名字,所以只能用多态的方式赋给父类Object。如下:
//提供有一个继承于Object的匿名子类的对象
Object obj=new Object(){
public void test(){
System.out.println("雨翼轻尘");
}
};
现在创建的可不是Object对象,Object对象里面也不可能有test()方法啊,既然里面声明了test()方法了,那就只能理解为Object的子类。
现在就提供了有一个继承于Object的匿名子类的对象obj
。
接下来拿着obj去调用test()方法,如下:
//提供有一个继承于Object的匿名子类的对象
Object obj=new Object(){
public void test(){
System.out.println("雨翼轻尘");
}
};
obj.test();
可以看到现在是报错的:
为啥不让调用呢?
因为此时声明的是Object,里面根本就没有test()方法。
这里相当于是多态,必须要转成子类的类型才能调test()。但是现在子类没有名字啊,那对象也别起名字了,没啥用,直接调用test,
如下:
//提供有一个继承于Object的匿名子类的匿名对象
new Object(){
public void test(){
System.out.println("雨翼轻尘");
}
}.test();
这种写法之前遇到过,比如:new Object().toString();
,直接new了一个Object对象,然后直接调toString()方法。
现在这个题目的情况是,我们造了一个Object子类的对象,用这个对象去调用test()方法。
子类?我们在Object()后面多增加了一个大括号,里面增加了一个方法。写完之后这个对象就已经有了这个方法,既然有,那么就可以直接去调用这个方法。
//new Object().toString();
new Object(){
//再增加一个方法test()
public void test(){
System.out.println("雨翼轻尘");
}
}.test();
🌱代码
【ObjectTest.java】
package yuyi01;
/**
* ClassName: ObjectTest
* Package: yuyi01
* Description:
* 编写一个匿名内部类,它继承Object,并在匿名内部类中,声明一个方法public void test()打印雨翼轻尘。
* @Author 雨翼轻尘
* @Create 2023/12/1 0001 13:27
*/
public class ObjectTest {
public static void main(String[] args) {
//提供有一个继承于Object的匿名子类的匿名对象
new Object(){
public void test(){
System.out.println("雨翼轻尘");
}
}.test();
}
}
🍺输出结果
(2)练习2
这里为了大家理解,再举个例子。
1. 接口
先看一段代码:
package yuyi01;
/**
* ClassName: OuterClass2
* Package: yuyi01
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/12/1 0001 14:04
*/
public class OuterClassTest2 {
public static void main(String[] args) {
SubA a=new SubA();
a.method(); //调用的是重写的方法
}
}
interface A{ //接口A
public void method(); //抽象方法
}
class SubA implements A{
@Override
public void method() {
System.out.println("Sub");
}
}
很简单,之前说接口的时候,已经很明白了。
输出结果:
【举例1 :提供接口匿名实现类的对象】
此时我们new一个A()。
但是接口不能new,所以在后面加一个大括号,在里面将抽象方法重写,如下:
//举例1:提供接口匿名实现类的对象
A a1=new A(){
public void method(){
System.out.println("匿名实现类重写的方法method()");
}
};
理解为:提供了接口A的匿名实现类的对象
然后通过a1来调用method()
方法,如下:
//举例1
A a1=new A(){
public void method(){
System.out.println("匿名实现类重写的方法method()");
}
};
a1.method();
输出结果:
举例1中类没有名,对象有名,叫a1
【举例2:提供接口匿名实现类的匿名对象】
//举例2:提供接口匿名实现类的匿名对象
new A(){
public void method(){
System.out.println("匿名实现类重写的方法method2()");
}
}.method();
这里就跟Object那个例子类似了。
输出结果:
举例2中直接拿对象调了方法,对象没有名字。
2. 抽象类
比如现在有一个抽象类B,如下:
abstract class B{ //抽象类B
//抽象类里面可以有抽象方法,也可以没有
public abstract void method2(); //抽象类
}
【标准写法】
package yuyi01;
public class OuterClassTest2 {
public static void main(String[] args) {
//举例1
SubB s1=new SubB();
s1.method2();
}
}
abstract class B{ //抽象类B
//抽象类里面可以有抽象方法,也可以没有
public abstract void method2(); //抽象类
}
class SubB extends B{
//重写B中的抽象方法
@Override
public void method2() {
System.out.println("SubB");
}
}
【举例2】
现在提供继承于抽象类的匿名子类的对象,那就new一个B,
B b=new B();
但是B是抽象类啊,提供子类的同时把抽象方法也重写一下。
抽象类虽然有构造器,但是有抽象方法,所以在后面加一个大括号,在里面将抽象方法重写一下,或者可以叫“实现”,一般都将抽象方法的重写叫实现。
B b=new B(){
public void method2(){
System.out.println("继承于抽象类的子类调用的方法");
}
};
b.method2();
输出结果:
这个子类没有名字,但是对象有名字,叫b。
这里可以证明一下它是一个子类,Object中有一个方法叫getClass(),获取对象所属的类,如下:
System.out.println(b.getClass());
可以看到,b所属的类与B没有关系,因为这个类没有名字,写在main方法里面的,所以是类OuterClassTest2里面的内部类,由于又是方法(main方法)里面的且没有名字,最后就有一个号码。
还可以获取当前类的父类System.out.println(b.getClass().getSuperclass());
,即B,如下:
方法里面定义的内部类,它的父类当然是父类B啦。
【举例3】
直接new一个B,然后直接调用method2()
,显然现在不靠谱,因为抽象类不能造对象。
所以在小括号后面要将方法重写一下:
new B(){
public void method2(){
System.out.println("继承于抽象类的子类调用的方法2");
}
}.method2();
输出结果:
接下来写一个普通的类C,然后写一个方法method3()
,如下:
class C{
public void method3(){
System.out.println("C");
}
}
调用一下:
【标准写法】
public class OuterClassTest2 {
public static void main(String[] args) {
//举例1
C c=new C();
c.method3();
}
}
class C{
public void method3(){
System.out.println("C");
}
}
输出结果:
【举例2】
将上面那种写法换一个方式:
//举例2:提供一个继承于C的匿名子类的对象
C c1=new C(){};
c1.method3();
因为C里面没有抽象方法,所以没有提示我们要去重写,这里只不过打印的还是C而已。
跟上面一样,这里可以看一下当前对象所属的类:
再看一下当前类的父类:
这里加了大括号与没加大括号差别很大,若是没有加大括号,那么getClass()结果就是C,getSuperclass()结果就是Object,如下:
【举例3】
还可以将method3()
方法重写,这样对象调用的就是重写的方法了,如下:
//举例3
C c2=new C(){
public void method3(){
System.out.println("SubC");
}
};
c2.method3();
输出结果:
3. 代码
这里将练习2的代码放在这里,供大家学习使用。
🌱代码
package yuyi01;
/**
* ClassName: OuterClass2
* Package: yuyi01
* Description:
*
* @Author 雨翼轻尘
* @Create 2023/12/1 0001 14:04
*/
public class OuterClassTest2 {
public static void main(String[] args) {
SubA a=new SubA();
a.method(); //调用的是重写的方法
//举例1:提供接口匿名实现类的对象
A a1=new A(){
public void method(){
System.out.println("匿名实现类重写的方法method()");
}
};
a1.method();
//举例2:提供接口匿名实现类的匿名对象
new A(){
public void method(){
System.out.println("匿名实现类重写的方法method2()");
}
}.method();
//举例1
SubB s1=new SubB();
s1.method2();
//举例2:提供继承于抽象类的匿名子类的对象
B b=new B(){
public void method2(){
System.out.println("继承于抽象类的子类调用的方法");
}
};
b.method2();
System.out.println(b.getClass());
System.out.println(b.getClass().getSuperclass());
//举例3
new B(){
public void method2(){
System.out.println("继承于抽象类的子类调用的方法2");
}
}.method2();
//举例1
C c=new C();
c.method3();
//举例2
C c1=new C(){};
//C c1=new C();
c1.method3();
System.out.println(c1.getClass());
System.out.println(c1.getClass().getSuperclass());
//举例3
C c2=new C(){
public void method3(){
System.out.println("SubC");
}
};
c2.method3();
}
}
interface A{ //接口A
public void method(); //抽象方法
}
class SubA implements A{
@Override
public void method() {
System.out.println("Sub");
}
}
abstract class B{ //抽象类B
//抽象类里面可以有抽象方法,也可以没有
public abstract void method2(); //抽象类
}
class SubB extends B{
//重写B中的抽象方法
@Override
public void method2() {
System.out.println("SubB");
}
}
class C{
public void method3(){
System.out.println("C");
}
}
🍺输出结果