记录一下编译器怎么老提示我改代码
前言
起因是每次在子线程调用局部变量时编译器爆红,要求参数改为final数组类型,今天心血来潮特地研究一下为什么必须得用final修饰。
一个常见的问题
当我们在子线程调用局部变量时编译器就会提醒我们 变量 'test' 在内部类中被访问,需要被声明为 final 或者是事实上的不可变。 当我们添加了final时 将'test'转换为最终的单元素数组
严谨的编译器
编译器就像一个非常谨慎的管家,总是想要确保你的代码不会闯入奇怪的麻烦。它有点像一个小摄影师,总是让你的代码在镜头前保持最佳状态。当你告诉它要将变量声明为 final 时,它感觉你是在为变量贴上“不可改变”的标签。但是,有时候你可能真的需要在代码中对变量做一些小改动,这时编译器就会变得像一个幽默的小丑,对你说:“哎呀, final 可不是那么容易使用的,得来点儿小花招。” 所以,你加了 final 后,编译器就告诉你,想使用 final 变量,得来点儿“小花招”,比如用数组。这就像你告诉编译器你想做一些小修饰,编译器回应:“哦,你是想要点特色吗?来试试用数组吧,这样我就会觉得你很时尚。”
背后的原因让人暖心
首先我们知道final(保护数据的一致性),这里的一致性指对引用变量的一致性,对基本类型来说就是值的一致性。
为什么需要用final保护数据的一致性呢?
使用 final 修饰变量可以保护数据的一致性,因为它确保在多线程环境中不会出现竞态条件或不一致的状态。在多线程编程中,多个线程同时访问和修改共享变量可能会导致数据不一致的问题,因为线程之间的操作顺序是不确定的。通过使用 final 修饰变量,可以在以下几个方面保护数据的一致性:
- 可见性保证: 当一个变量被声明为 final 时,它的值对所有线程都是可见的。这意味着在一个线程中对 final 变量的修改对其他线程立即可见,防止了由于缓存不一致性而引发的问题。
- 避免竞态条件: 竞态条件指的是多个线程之间在执行操作的时序上的不确定性,可能导致意外的结果。通过将变量声明为 final,可以避免多个线程同时对变量进行修改,从而消除了竞态条件的风险。
- 线程安全性:final 变量是不可修改的,因此在多线程环境下不会发生意外的修改操作。这有助于确保数据的线程安全性,避免了需要使用额外的同步机制来保护变量的情况。
为什么在使用 final 后要使用数组?
当一个变量被声明为 final 后,它的值就不能再被修改。但在一些情况下,我们可能需要在不修改变量本身的前提下,改变其所持有的值。这时,可以使用数组来解决这个问题。 使用数组的主要原因是,数组是引用类型,而 final 关键字只保证引用本身不会被改变,但并不限制引用所指向的对象的内容。因此,我们可以通过将变量声明为指向数组的 final 引用,然后在数组中改变元素的值,达到在 final 变量的限制下修改值的目的。 这种方式相当于“绕过”了 final 关键字的限制,因为数组的引用本身是 final 的,但数组中的元素可以被修改。这在一些需要在不改变变量本身的前提下,改变其持有的值的场景中很有用。 举例来说,如果你需要在一个匿名内部类中修改一个被声明为 final 的变量,可以将该变量包装在一个数组中,并将数组引用声明为 final。然后,在内部类中,你可以修改数组中的元素,从而实现修改值的目的,同时遵循 final 变量的限制。
总结
在之前的Java版本中,当局部变量被匿名内部类引用时,编译器要求该变量为 final 或隐式的 final。这是因为匿名内部类(子线程也算一种匿名内部类)可能会在外部方法执行完毕后继续存在,而局部变量的生命周期通常在方法执行完毕后结束。这可能导致匿名内部类访问无效的变量。 为了解决这个问题,编译器要求变量为 final,这样在匿名内部类中会创建一个拷贝作为成员变量,确保变量的有效性。这样做的好处是,即使局部变量已经消失,匿名内部类仍然可以使用这个不变的成员变量。 然而,在JDK 8之后,编译器对内部类访问外部方法的局部变量进行了改进。不再需要显式声明为 final,因为编译器会自动添加 final 修饰。这使得代码更加简洁,减少了程序员的工作量。