内部类

内部类

什么是内部类

将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。可以把内部类理解成寄生,外部类理解成宿主。

什么时候使用内部类

一个事务内部还有一个独立的事物,内部的事物脱离外部的事物无法独立使用

  1. 人里面有一颗心脏
  2. 汽车内部有一个发动机
  3. 为了实现更好的封装性

内部类的分类

按定义的位置来分

成员内部类

​ 类定义在成员位置。类中方法外称为成员位置,无static修饰的内部类

特点

  • 无static修饰的内部类,属于外部类对象的
  • 宿主:外部类对象

内部类的使用格式

1
外部类.内部类  //访问内部类的类型都是用	外部类.内部类

获取成员内部类对象的方式

  • 方式1:外部直接创建成员内部类的对象

    1
    外部类.内部类 变量 = new 外部类().new 内部类();
  • 方式2:在外部类中定义一个方法提供内部类的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
方式一:
public class Test {
public static void main(String[] args) {
// 宿主:外部类对象。
// Outer out = new Outer();
// 创建内部类对象。
Outer.Inner oi = new Outer().new Inner();
oi.method();
}
}

class Outer {
// 成员内部类,属于外部类对象的。
// 拓展:成员内部类不能定义静态成员。
public class Inner{
// 这里面的东西与类是完全一样的。
public void method(){
System.out.println("内部类中的方法被调用了");
}
}
}


方式二:
public class Outer {
String name;
private class Inner{
static int a = 10;
}
public Inner getInstance(){
return new Inner();
}
}

public class Test {
public static void main(String[] args) {
Outer o = new Outer();
System.out.println(o.getInstance());


}
}

成员内部类的细节

编写成员内部类的注意点:

  1. 成员内部类可以被一些修饰符修饰,比如:private、默认、protectd、public、static等
  2. 在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量
  3. 创建内部类对象时,对象中有一个隐含的Outer.this记录外部类对象的地址值

详解:

  1. 内部类被private修饰,外界无法直接获取内部类的对象,只能通过在外部类中定义一个方法提供内部类的对象获取。
  2. 被其他权限修饰符修饰的内部类一般使用直接使用 外部类.内部类 变量 = new 外部类().new 内部类();
  3. 内部类被static修饰是成员内部类中的特殊情况,也叫做静态内部类
  4. 内部类如果想要访问外部类的成员变量,外部类的变量必须使用Final修饰,JDK8以前必须手动写final,JDK1.8之后不需要手写,JDK默认加上

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test {
public static void main(String[] args) {
Outer.inner oi = new Outer().new inner();
oi.method();
}
}

class Outer { // 外部类
private int a = 30;

// 在成员位置定义一个类
class inner {
private int a = 20;

public void method() {
int a = 10;
System.out.println(???); // 10 答案:a
System.out.println(???); // 20 答案:this.a
System.out.println(???); // 30 答案:Outer.this.a
}
}
}

成员内部类内存图

内部类内存图

执行步骤:

  1. 方法区加载Test.class,JVM自动加载main方法到栈内存

  2. 创建一个Outer.Inner oi变量

    • 这里会先加载Outer.class的所有成员变量、成员方法到方法区

    • 在加载Outer.class的成员内部类Outer&Inner.class的所有成员变量和方法

  3. new Outer().new Inner(),在堆内存中创建对应的内存空间

    • 先创建一个外部类对象,初始化成员变量int a 赋值10,对象地址为001

    • 在创建一个内部类对象,初始化成员变量int a赋值20,还存在一个Outer this指向外部类对象的地址值,内部类对象的地址值为002

  4. 将内部类对象的地址值赋值给变量oi,oi执行show方法

  5. 将show方法加载到栈内存中,show方法的调用者为内部对象

    • 先打印方法中的局部变量a为30

    • 在打印this.a,就是内部类中的成员变量a为20

    • Outer.this.a,就会从内部类中找到Outer.this,发现这个执行外部类对象的地址,找到外部类对象,并打印该外部类对象的成员变量a,值为10

  6. 执行完方法,出栈、堆内存地址不被引用,到时候被垃圾回收机制回收。

静态内部类

​ 类定义在了成员位置。类中方法外称为成员位置,有static修饰的内部类

静态内部类特点

  • 静态类内部类是一种特殊的成员内部类
  • 有static修饰,属于外部类本身的。
  • 总结:静态内部类与其他类的用法完全一样。只是访问的时候需要加上外部类.内部类
  • 静态内部类可以直接访问外部类的静态成员
  • 静态内部类不可以直接访问外部类的非静态成员,如果需要访问,要创建外部类对象
  • 静态内部类中没有隐含的Outer.this

内部类使用格式

1
外部类.内部类

静态内部类对象的创建格式

1
外部类.内部类 变量  = new 外部类.内部类构造器;

调用方法的格式

  • 调用非静态方法的格式:先创建对象,用对象调用
  • 调用静态方法的格式:外部类.内部类.方法名();

代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 外部类:Outer01
class Outer01{
private static Stirng sc_name="小米";

//内部类:Inner01
public static class Inner01{
//这里的东西与类是完全一样的
private String name;

public Inner01(String name){
this,name = name;
}
public void showName(){
System.out.println(this.name);//张三
//静态内部类可以直接访问外部类的静态成员
System.out.println(sc_name);//小米
}
}
}

public class InnerClassDemo01{
public static void main(String[] args){
//创建静态内部类对象
Outer01.Inner01 in = new Outer01.Inner01("张三");
in.showName();
}
}

局部内部类

定义在方法中的类

定义格式

1
2
3
4
5
6
7
8
9
10
class 外部类名{
数据类型 变量名;

修饰符 返回值类型 方法名(参数列表){
class 内部类{
//成员变量
//成员方法
}
}
}

匿名内部类

​ 没有名字的内部类,可以在方法中,也可以在类中方法外。

概述

​ 匿名内部类是内部类的简化写法。它是一个隐含了名字的内部类。

格式

1
2
3
4
5
6
7
8
new 类名或者接口名(){
重写方法;
}

1.匿名内部类中{}就表示一个没有名字的类
2.这个没有名字的类要想实现接口或者继承类,直接把类名或者接口卸载{}前
3.创建对象,在java中通过new关键字创建对象,所以在类名或者接口前加上一个new
4.从语法上来讲,这个整体其实是匿名内部类对象

匿名内部类包含了:

  • 继承或者实现关系
  • 方法重写
  • 创建对象(从语法上来讲,这个整体其实是匿名内部类对象)

为什么使用匿名内部类

实际上,如果我们希望定义一个只用使用一次的类,就可以考虑使用匿名内部类。匿名内部类的本质作用就是为了简化代码

之前我们使用接口时,似乎做了以下几步操作:

  1. 定义子类
  2. 重写接口中的方法
  3. 创建子类对象
  4. 调用重写后的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Swim{
public abstract void swimming();
}

//1.定义接口的实现类
class Student implements Swim{
//2.重写抽象方法
@Override
public void swimming(){
System.out.println("狗刨式")
}
}

public class Test{
public static void main(String[] args){
//3.创建实现类对象
Student s= new Student();
//4.调用方法
s.swiming();
}
}

我们的目的,最终只是为了调用方法,那么能不能简化?匿名内部类就是做这样的快捷方式

匿名内部类前提和格式

匿名内部类必须继承一个父类或者实现一个父接口

1
2
3
4
5
6
7
new 父类名或者接口名(){
// 方法重写
@Override
public void method() {
// 执行语句
}
};

使用方式

以上述代码为例,匿名内部类使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
interface Swim{
public abstract void swimming();
}

public class Demo{
public static void main(Stringp[] args){
//使用匿名内部类
new Swim(){
@Override
public void swimming(){
System.out.println("自由泳");
}
}.swimming();

// 接口 变量 = new 实现类(); // 多态,走子类的重写方法
Swim s2 = new Swim() {
@Override
public void swimming() {
System.out.println("蛙泳...");
}
};

s2.swimming();
s2.swimming();
}
}

匿名内部类的特点

  1. 定义一个没有名字的内部类
  2. 这个类实现了父类,或者父类接口
  3. 匿名内部类会创建这个没有名字的类对象

匿名内部类使用场景

通常在方法的形式参数是接口或者抽象类时,也可以将匿名内部类作为参数传递。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
interface Swim {
public abstract void swimming();
}

public class Demo07 {
public static void main(String[] args) {
// 普通方式传入对象
// 创建实现类对象
Student s = new Student();
goSwimming(s);

// 匿名内部类使用场景:作为方法参数传递
Swim s3 = new Swim() {
@Override
public void swimming() {
System.out.println("蝶泳...");
}
};
// 传入匿名内部类
goSwimming(s3);

// 完美方案: 一步到位
goSwimming(new Swim() {
public void swimming() {
System.out.println("大学生, 蛙泳...");
}
});

goSwimming(new Swim() {
public void swimming() {
System.out.println("小学生, 自由泳...");
}
});
}

// 定义一个方法,模拟请一些人去游泳
public static void goSwimming(Swim s) {
s.swimming();
}
}

案例

在控制台输出”HelloWorld” ,在补齐代码处补齐

分析:

  1. Outer.method() 通过类直接调用的话,可以确定method是一个静态方法
  2. 方法返回什么类型才能继续调用show方法? 所以这里应该返回Inter接口对象再去调用方法show方法
  3. 通过一个匿名内部类是创建一个匿名内部类对象,在匿名内部类中去实现Inter接口并重写show方法
  4. 最后在通过这个对象调用重写的show方法,在重写的show方法中打印HelloWorld
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface Inter {
void show();
}
class Outer {
//补齐代码
public static Inter method{
return new Inter() {
@Override
public void show() {
System.out.println("HelloWorld");
}
};
}
}
public class OuterDemo {
public static void main(String[] args) {
Outer.method().show();
}
}

内部类
http://example.com/2023/01/29/Java基础/内部类/internal-class/
作者
zhuixun
发布于
2023年1月29日
许可协议