深入理解Java-final关键字

简介: 本文详细解析了Java中`final`关键字的用法及其特性,涵盖修饰变量、方法和类的不同场景。修饰变量时,`final`确保值不可变,适用于常量定义;修饰方法时禁止子类重写,提升性能与安全性;修饰类时防止继承,保障类的完整性(如String类)。此外,文章探讨了不可变类的设计原则及多线程环境下的优势,并通过实例分析了`final`变量在类型转换中的特殊行为(如编译时常量优化)。最后,强调了合理使用`final`对代码可读性、性能优化及线程安全的重要意义。项目已开源至GitHub,欢迎关注获取更多技术干货!
## 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](https://github.com/Java-Edge/Java-Interview-Tutorial),**关注我,紧跟本系列专栏文章,咱们下篇再续!** - 🚀 魔都架构师 | 全网30W技术追随者 - 🔧 大厂分布式系统/数据中台实战专家 - 🏆 主导交易系统百万级流量调优 & 车联网平台架构 - 🧠 AIGC应用开发先行者 | 区块链落地实践者 - 🌍 以技术驱动创新,我们的征途是改变世界! - 👉 实战干货:[编程严选网](http://www.javaedge.cn/)
目录
相关文章
|
5月前
|
存储 缓存 安全
除了变量,final还能修饰哪些Java元素
在Java中,final关键字不仅可以修饰变量,还可以用于修饰类、方法和参数。修饰类时,该类不能被继承;修饰方法时,方法不能被重写;修饰参数时,参数在方法体内不能被修改。
65 3
|
18天前
|
存储 安全 Java
深入理解 Java 中的 instanceof 关键字
本文深入解析了 Java 中的 `instanceof` 关键字,探讨其在类型判断中的作用。作为二元操作符,`instanceof` 可用于检查对象是否为某类实例或实现特定接口,避免类型转换异常 (`ClassCastException`)。文章通过多态性下的类型判断、安全类型转换、接口实现检测及集合元素类型判定等实际应用场景,展示了 `instanceof` 的强大功能。掌握该关键字可提高代码健壮性,确保运行时类型安全。
25 0
|
2月前
|
缓存 安全 Java
Volatile关键字与Java原子性的迷宫之旅
通过合理使用 `volatile`和原子操作,可以在提升程序性能的同时,确保程序的正确性和线程安全性。希望本文能帮助您更好地理解和应用这些并发编程中的关键概念。
63 21
|
1月前
|
Java C语言
课时8:Java程序基本概念(标识符与关键字)
课时8介绍Java程序中的标识符与关键字。标识符由字母、数字、下划线和美元符号组成,不能以数字开头且不能使用Java保留字。建议使用有意义的命名,如student_name、age。关键字是特殊标记,如蓝色字体所示。未使用的关键字有goto、const;特殊单词null、true、false不算关键字。JDK1.4后新增assert,JDK1.5后新增enum。
|
3月前
|
Java 编译器 开发者
Java中的this关键字详解:深入理解与应用
本文深入解析了Java中`this`关键字的多种用法
526 9
|
4月前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
141 5
Java 并发编程——volatile 关键字解析
|
3月前
|
JSON Java 数据挖掘
利用 Java 代码获取淘宝关键字 API 接口
在数字化商业时代,精准把握市场动态与消费者需求是企业成功的关键。淘宝作为中国最大的电商平台之一,其海量数据中蕴含丰富的商业洞察。本文介绍如何通过Java代码高效、合规地获取淘宝关键字API接口数据,帮助商家优化产品布局、制定营销策略。主要内容包括: 1. **淘宝关键字API的价值**:洞察用户需求、优化产品标题与详情、制定营销策略。 2. **获取API接口的步骤**:注册账号、申请权限、搭建Java开发环境、编写调用代码、解析响应数据。 3. **注意事项**:遵守法律法规与平台规则,处理API调用限制。 通过这些步骤,商家可以在激烈的市场竞争中脱颖而出。
|
4月前
|
缓存 安全 Java
Java volatile关键字:你真的懂了吗?
`volatile` 是 Java 中的轻量级同步机制,主要用于保证多线程环境下共享变量的可见性和防止指令重排。它确保一个线程对 `volatile` 变量的修改能立即被其他线程看到,但不能保证原子性。典型应用场景包括状态标记、双重检查锁定和安全发布对象等。`volatile` 适用于布尔型、字节型等简单类型及引用类型,不适用于 `long` 和 `double` 类型。与 `synchronized` 不同,`volatile` 不提供互斥性,因此在需要互斥的场景下不能替代 `synchronized`。
2587 3
|
5月前
|
JavaScript 前端开发 Java
java中的this关键字
欢迎来到我的博客,我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。自学前端2年半,正向全栈进发。若我的文章对你有帮助,欢迎关注,持续更新中!🎉🎉🎉
101 9
|
5月前
|
设计模式 JavaScript 前端开发
java中的static关键字
欢迎来到瑞雨溪的博客,博主是一名热爱JavaScript和Vue的大一学生,致力于全栈开发。如果你从我的文章中受益,欢迎关注我,将持续分享更多优质内容。你的支持是我前进的动力!🎉🎉🎉
89 8
下一篇
oss创建bucket