1 修饰属性或变量
无论属性是基本类型、引用类型,都使变量里存放的"值"不可变。
常和static关键字协作,作为常量:
- 基本类型,变量放的是实实在在的值,如1,"abc"
- 引用类型,变量放的是个地址,所以final修饰引用类型变量指里面的地址不能变,即它只能指向初始时指向的那个对象,不关心指向的对象内容的变化
所以修饰的变量必须初始化:
java
public static final String LOAN = "loan";
LOAN = new String("loan") //invalid compilation error
- 定义时
- 初始化块中,但不可在静态初始化块中,静态的final实例变量才可以在静态初始化块中
- 构造方法中,但静态final实例变量不可以在其中
final变量只读!
2 修饰方法
该方法可被继承,但不许被任何子类重写。
调用final方法时,直接将方法主体插入到调用处,而非进行方法调用,这样能提高程序效率(内联机制)。
如认为一个方法功能够完整,子类中不需要改变,可声明为final。final方法比非final方法快,因为在编译时候已静态绑定,无需在运行时再动态绑定。
java
class PersonalLoan{
public final String getName(){
return "personal loan";
}
}
class CheapPersonalLoan extends PersonalLoan{
@Override
public final String getName(){
return "cheap personal loan"; //compilation error: overridden method is final
}
}
3 修饰类
使用final来修饰的类叫作final类。
final类通常功能是完整的,不能被继承。Java中有许多类是final,如String、Interger及其他包装类。类不可被继承,但这并非表示final类的实例变量也不可变,除非给实例变量也增加final修饰。
java
final class PersonalLoan{
}
class CheapPersonalLoan extends PersonalLoan{ //compilation error: cannot inherit from final class
}
一个类不可同时被abstract和final修饰。
4 final关键字的好处
- 提高性能:JVM和Java应用都会缓存final变量
- final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销
- 使用final关键字,JVM会对方法、变量及类进行优化
5 不可变类
创建不可变类要使用final关键字。不可变类是指它的对象一旦被创建了就不能被更改了。String是不可变类的代表。不可变类有很多好处,譬如它们的对象是只读的,可以在多线程环境下安全的共享,不用额外的同步开销等等。
6 其他重要知识点
- 🈲对final变量再赋值
- 本地变量须在声明时赋值
- 在匿名类中,所有变量都须final
- 接口中声明的所有变量本身是final
- final和abstract这两个关键字反相关,final类不能abstract
- final方法在编译阶段绑定,称为静态绑定(static binding)
- 没在声明时初始化final变量的称为空白final变量(blank final variable),须在构造器中初始化,或调用this()初始化。不这么做的话,编译器会报错"final变量(变量名)需要进行初始化"
final变量就是常量,常量名通常大写:
java
private final int COUNT = 10;
对于集合对象声明为final指的是引用不能被更改,但是你可以向其中增加,删除或者改变内容。譬如:
java
private final List Loans = new ArrayList();
list.add("home loan"); //valid
list.add("personal loan"); //valid
loans = new Vector(); //not valid
7 可安全设为 final 的字段
此项检查会报告那些可安全地声明为 final
的字段。所有 final
字段都有一个值,并且这个值在初始化后不会改变,可使代码更易理解和推断。
范围与限制
为了避免过于耗时的分析,此检查仅在以下情况下报告字段: 字段具有 private
修饰符,或字段定义在局部类或匿名类中。 一个字段可以被设为 final
,如满足以下条件:
- 字段是static的。字段在其声明或一个
static
初始化块中被精确地初始化一次 - 字段是非static的。字段在其声明、一个实例初始化块或类的每一个构造函数 中被精确地初始化一次
且字段在其他任何地方都未被修改。
示例:
java
public class Person {
private String name;
Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
修复后:
java
public class Person {
private final String name;
Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
使用 "Annotations" (注解) 按钮来修改注解列表。这些注解会被认为隐含了对字段的写入操作(这会阻止字段被标记为 final
)。
8 有趣现象
java
byte b1 = 1;
byte b2 = 3;
// 当程序执行到这一行的时候会出错
// 因b1、b2可自动转换成int型变量,运算时JVM对它进行转换,结果导致把一个int赋值给byte
byte b3 = b1 + b2; // Error: Type mismatch: cannot convert from int to byte
final byte b1=1;
final byte b2=3;
// 不会出错,看了上面的解释就知道原因
byte b3=b1+b2;
8.1 现象一:byte b3 = b1 + b2;
报错
Java 的算术运算类型提升 (Integer Promotion):对小于 int
的整型(即 byte
, short
, char
)进行算术运算(如 +
, -
, *
, /
),它们的操作数会先被自动提升(promote)为 int
类型,然后执行运算。
因此,b1 + b2
表达式实际按 (int)b1 + (int)b2
来执行的。两个 int
类型相加,其结果必然是 int
类型。
表达式 b1 + b2
的结果是 int
类型(值为 4),而变量 b3
被声明为 byte
类型。将一个 int
类型的值赋给 byte
类型的变量属于窄化原始类型转换 (Narrowing Primitive Conversion)。
Java 编译器不允许这种可能导致精度丢失的隐式窄化转换。因为 int
的范围(-2^31 到 2^31-1)远大于 byte
的范围(-128 到 127),直接赋值可能会丢失信息。所以编译器会报错,提示类型不匹配。
如果确实需要将结果赋给 byte
,必须进行强制类型转换:
java
byte b3 = (byte)(b1 + b2); // 显式强制转换,编译通过
8.2 final byte b3 = b1 + b2不报错
当 byte
变量被 final
修饰且在声明时用常量(字面量)初始化时,b1
和 b2
成为编译时常量 (Compile-time constants)。
常量折叠 (Constant Folding): Java 编译器会对涉及编译时常量的表达式进行常量折叠 优化。即编译器在编译阶段就直接计算出 b1 + b2
的结果。它看到 1 + 3
,直接计算得到常量 4
。
编译时检查常量值: 此时,赋值语句 byte b3 = b1 + b2;
在编译期间实际上被看作是 byte b3 = 4;
。Java 编译器特殊规则:如果一个 int
类型的常量值 (字面量或编译时常量表达式的结果)在 byte
类型的表示范围内(-128 到 127),那么编译器允许将这个 int
常量隐式地赋值 给 byte
变量。
赋值通过: 因为 4
在 byte
的范围 [-128, 127] 之内,所以编译器认为这个赋值是安全的,不会导致信息丢失,因此编译通过。
本文已收录在Github,关注我,紧跟本系列专栏文章,咱们下篇再续!
- 🚀 魔都架构师 | 全网30W技术追随者
- 🔧 大厂分布式系统/数据中台实战专家
- 🏆 主导交易系统百万级流量调优 & 车联网平台架构
- 🧠 AIGC应用开发先行者 | 区块链落地实践者
- 🌍 以技术驱动创新,我们的征途是改变世界!
- 👉 实战干货:编程严选网
本文由博客一文多发平台 OpenWrite 发布!