跳至正文
-
关注
IT极限技术分享汇

ITmax

IT极限技术分享汇

ITmax

  • 首页
  • 商店
  • 我的帐户
  • 结账
  • 购物车
  • 首页
  • 商店
  • 我的帐户
  • 结账
  • 购物车
关

搜索

技术分享

浅析内部类,理解Java中的this

作者 mumupudding
2026年7月7日 2 分钟阅读
0

问题背景

之前看代码的时候不懂为什么要使用var that = this;来保留this关键字,本文深入了解一下。
部分代码如下:

@Override
public Pair<Long, Long> updateInventoryAndCostPriceForPurchase(List<InboundOrderDto.SaveRequest.StockTransactionRequest> items) {
    // 保存this引用
    var that = this;

    // 创建用于更新库存的基本对象
    Iterable<UpdateInventoryItem> updateInventoryItems = items.stream()
            .map(item -> new UpdateInventoryItem() {
                @Override
                public UUID skuUuid() {
                    return item.getSku().getUuid();
                }

                @Override
                public Long stockUnitRatio() {
                    Long sellingUnitId = uuidCacheService.uuidToId(item.getSellingUnitUuid(), sellingUnitRepository).orElseThrow(EntityNotFoundException::new);
                    SellingUnit sellingUnit = that.sellingUnitRepository.findById(sellingUnitId).orElseThrow(EntityNotFoundException::new);

                    if (!sellingUnit.getProductSkuId().equals(uuidCacheService.uuidToId(item.getSku().getUuid(), productSkuRepository).orElseThrow(EntityNotFoundException::new))) {
                        throw new IllegalStateException();
                    }
                    return sellingUnit.getRatio();
                }
            }).collect(Collectors.toList());

}

之前认为this关键字永远指向当前类。比如:

public class UserController {
     private UserService userService; 

    public void page() {
      this.userService.page();
    }
}

这里的this指向的是UserController。但是在内部类中并不是这样。
我们首先了解一下什么是内部类。

内部类

菜鸟教程中关于内部类是这样介绍的:

在 Java 中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。

这里简单介绍一下成员内部类和匿名内部类

成员内部类

成员内部类是最普通的内部类,定义为位于另一个类的内部。

class Outer {
    private String name;
    Outer(String name) {
        this.name = name;
    }
    class Inner {
        void hello() {
            System.out.println("Hello, " + Outer.this.name);
        }
    }
}

Outer是一个普通类,Inner是成员内部类。成员内部类是依附外部类而存在的,如果想要创建成员内部类Inner的对象,必须存在一个外部类的对象。

public class Main {
  public static void main(String[] args) {
    Outer outer = new Outer("张三");
    // 必须通过Outer对象来创建
    Outer.Inner inner = outer.new Inner();
    inner.hello();
  }
}

为什么Inner能访问Outer的private成员
为什么实例化Inner需要依赖于Outer对象

成员内部类并不是完全独立的对象,每一个Inner对象,都默认绑定了一个Outer对象

Outer outer = new Outer("张三");
Outer.Inner inner = outer.new Inner();

实际上更像

Inner inner = new Inner(outer);

Inner类中除了有一个this指向自己,还隐含地持有一个Outer实例,可以用Outer.this访问外部类对象。

查看Inner的反编译代码:

class Outer$Inner {
    Outer$Inner(final Outer this$0) {
        this.this$0 = this$0;
    }

    void hello() {
        System.out.println("Hello, " + this.this$0.name);
    }
}

可以看到,编译器为Inner类生成了一个this$0字段,用来保存对应的Ouer实例。因此,

Outer.this.name

本质上会被编译成

this$0.name

所以在内部类中直接访问

System.out.println(name);

等价于

System.out.println(this$0.name)

也就是说,内部类之所以能够直接访问外部类成员,本质上是因为它持有一个外部类对象的引用。

匿名内部类

匿名内部类不需要在Outer calss中明确定义,而是在方法内部定义。

class Outer {
    Outer(String name) {
        this.name = name;
    }

    void asyncHello() {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello, " + Outer.this.name);
            }
        };

        new Thread(r).start();
    }
}

观察asyncHello()方法,我们在方法内部实例化了一个Runnable。
Runnable本身是接口,接口是不可以被实例化的。所以这里是定义了一个实现Runnable接口的匿名类,new Runnable() {}实例化了实现Runnable接口的匿名内部类对象,然后将该对象转型为Runnable。

示例代码等价于

class Xxx implements Runnable {
    @Override
    public void run() {
        System.out.println("Hello, " + Outer.this.name);
    }
}
Runnable r = new Xxx();

匿名内部类没有类名,所以这里用Xxx。

发现实际上被实例化的对象类型,是某个匿名类,并不是Runnable。

编译器在编译过程中,Outer类被编译为Outer.class,而匿名类被编译为Outer$1.class。如果有多个匿名类,Java编译器会将每个匿名类依次命名为Outer$1、Outer$2、Outer$3……

本质上类似于

Outer$1 obj = new Outer$1();
Runnable r = obj;

因为

Outer$1 implements Runnable

所以

Outer$1 -> Runnable

是一个向上转型,子类转为父类。

回归问题,为什么要var that = this

简化问题代码,以此代码为例:

class OuterService {
    private OuterRepository outerRepository;

    void test() {

        var that = this;

        Runnable r = new Runnable() {
            @Override
            public void run() {
                that.outerRepository.findAll();
                System.out.println(this);
                System.out.println(that);
            }
        };
    }
}

我们已经知道,普通方法中this指向的是OuterService对象,但是进入匿名内部类后:

new Runnable() {
    @Override
    public void run() {
    }
}

已经产生了一个新的类:

OuterService$1

因此,此时的this不再指向OuterService,而是指向OuterService$1,即匿名内部类对象本身。
所以在匿名内部类中使用this无法访问外部类成员,才需要

var that = this;

提前把外部类的this保存下来,后面再去访问that.outerRepository(),实际上是在访问外部的OuterService对象

除了保留this关键字,还可以这种方式访问外部对象:

OuterService.this.outerRepository()

使用var that = this并不是“必须”的,而是一种

  • 历史写法
  • 避免长类名
  • 避免泛型嵌套时可读性差

的习惯。

Lambda 表达式为什么不需要保留this

Lambda 表达式是java8的新特性,提供了一种更为简洁的语法,尤其适用于函数式接口。相比于传统的匿名内部类,Lambda 表达式使代码更紧凑。

Lambda 表达式,可以简单理解为就是 Java 用来“简化匿名内部类”的语法。

Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("hello");
    }
};

使用 Lambda表达式

Runnable r = () -> {
    System.out.println("hello");
};

效果完全一样。
因为Runnable只有一个抽象方法 void run(); 所以java推断 () -> {} 就是run()的实现。
因此

() -> {
    System.out.println("hello");
}

本事上是在

快速实现一个接口

所以 Lambda 可以看作是对匿名内部类的简化写法
Lambda表达式虽然与匿名内部类的“功能相近”,但是底层机制并不完全一样。

匿名内部类编译后会创建一个新的类,所以this关键字指向的是匿名内部类。

Lambda表达式 () -> {} 不会创建新的this作用域。Lambda内部的this直接继承外层。

Lambda表达式更接近于把一段函数当作参数传递,它只是”捕获到外层上下文”。

内部类的作用

再来看一下内部类的几个典型场景的特点

内部类可以直接访问外部类状态。

比如上文提到的

class Outer {
    private String name;
    Outer(String name) {
        this.name = name;
    }
    class Inner {
        void hello() {
            System.out.println("Hello, " + Outer.this.name);
        }
    }
}

这里

System.out.println("Hello, " + Outer.this.name);

实际上是访问外部类的Outer的成员。

如果没有外部类:

class UserPrinter {
    private UserService userService;

    UserPrinter(UserService userService) {
        this.userService = userService;
    }
}

需要通过手动传递对象引用。

内部类适合“只服务于当前类”的逻辑

class UserService {
    private class UserValidator {
        boolean valid(String name) {
            return name != null && !name.isBlank();
        }
    }
}

UserValidator 只给 UserService 使用。
外部不需要知道它的存在。因此内部类可以增强封装性。

匿名内部类适合“一次性实现”

比如:

void asyncHello() {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello, " + Outer.this.name);
            }
        };

        new Thread(r).start();
    }

并不需要单独写一个类实现Runnable接口。代码会更紧凑。

内部列可以“模拟多继承”

Java 类只能单继承:

class A extends B

不能

class A extends B, C

但是内部类可以继承不同的类:

class Outer extends A {
    class Inner extends B {
    }
}

虽然并不是真正的多继承,但是在某些设计中可以实现类似的效果。这种写法在现代开发中已经比较少见了。

总结

在普通类中 this 指向当前对象,匿名内部类中会生成新的类 Outer$1,因此匿名内部类中的this实际指向的是Outer$1,而不是外部类对象。如果需要访问外部类实例,可以通过OuterClass.this或var that = this;提前保留外部类引用。

参考文章:
https://liaoxuefeng.com/books/java/oop/basic/inner-class/inde…
https://www.runoob.com/w3cnote/java-inner-class-intro.html

https://segmentfault.com/a/1190000047830557

作者

mumupudding

关注我
其他文章
上一个

DeepSeek正式官宣摇人,夯!

下一个

为什么 chatgpt 的 ios app 最多只能开 plus 不能开 pro 吗?

暂无评论!成为第一个。

发表回复 取消回复

您的邮箱地址不会被公开。 必填项已用 * 标注

Copyright 2026 — IT极限技术分享汇. 备案号:浙ICP备17006560号-2