导读
final关键字在Java中有多种用途,可以用于修饰类、方法和变量。本文从常规使用场景、常见的误解、底层原理、jdk源码案例,常见使用场景等几方面全面剖析关键字的各个方面内容。帮助大家理解并使用该关键字,并在面试中吊打八股文面试官。
用于修饰类
当一个类被声明为final时,表示这个类不能被继承。也就是说,没有其他类可以继承这个final类。
**代码示例:**
final class FinalClass {
// 类体
}
// 下面的代码会编译错误,因为FinalClass是final的,不能被继承
class SubClass extends FinalClass {
// 编译错误
}
如果你希望一个类能够被扩展,那么不应该将其声明为final。- 如果一个类中包含需要被子类重写的方法,那么这个类也不能被声明为final。
用于修饰方法
当一个方法被声明为final时,表示这个方法不能被子类重写。
**代码示例:**
class ParentClass {
final void finalMethod() { System.out.println("This is a final method.");
}}
class ChildClass extends ParentClass {
// 下面的代码会编译错误,因为finalMethod是final的,不能被重写 void finalMethod() { // 编译错误
System.out.println("Trying to override final method."); }}
```- 如果你希望子类能够提供特定于自己的实现,那么不应该将方法声明为final。- 在设计类的继承结构时,过度使用final方法可能会限制类的灵活性。
用于修饰变量
当一个变量被声明为final时,表示这个变量的值不能被改变。对于基本数据类型,这意味着其值不能被修改;
对于引用类型,这意味着引用本身不能指向其他对象,但对象的内容是可以改变的(除非对象本身也是final的)。
代码示例:
// 基本数据类型
final int number = 10;
//
number = 20;
// 编译错误,不能修改final变量的值
// 引用类型
final StringBuilder sb = new StringBuilder("Hello");
sb.append(", World!"); // 这是允许的,因为StringBuilder对象本身不是final的//
sb = new StringBuilder("New"); // 编译错误,不能修改final变量的引用```-
如果你需要在程序运行过程中改变变量的值,那么不应该将其声明为final。
- 对于引用类型的final变量,如果对象的状态也需要保持不变,那么对象本身也应该是不可变的(例如,使用final修饰对象的所有字段,并且不提供修改这些字段的方法)。
使用的总结
final关键字在Java中用于确保类、方法和变量的不可变性。
尽管final关键字的使用可以带来安全和性能上的好处,但在实际使用中,很容易出现误解和错误。接下来我们详细说明一下final关键字的常见误解。
final使用中可能产生误解的知识点
当 final 修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;
如果 final 修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以变化的。
无法不初始化的 final 变量:
final 成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。这是因为 final 变量的值在初始化后不能再被修改,编译器需要确保这一点。
final 方法的重载与重写:
final 方法可以被重载,但不能被重写。这意味着你可以在同一个类中存在多个同名但参数列表不同的 final 方法。
final 类中的 final 方法:
final 类中的所有成员方法都会被隐式地指定为 final 方法,这确保了这些方法的行为不会被子类改变。
final底层如何实现
基本数据类型:
对于基本数据类型, final 确保其值在初始化后不能被改变。编译器和处理器会遵守内存屏障规则,确保 final 变量的值在多线程环境下保持一致性。
引用类型:
对于引用类型, final 确保引用本身不能被改变,但对象的内容是可以改变的。这意味着 final 变量可以指向同一个对象,但该对象的状态可以变化。
方法锁定:
final 方法不能被子类重写,这通过在编译时进行静态绑定来实现,避免了运行时的动态绑定开销。这使得 final 方法在多线程环境下更加稳定和安全。
内联优化:
JVM的JIT编译器可能会对 final 方法进行内联优化,将方法体的直接执行插入到调用点,从而减少方法调用的开销。
类继承限制:
final 类不能被继承,这通过在编译时进行检查来实现,确保没有类可以继承 final 类。
内存模型与重排序规则:
编译器和处理器要遵守两个重排序规则,确保在对象引用为任意线程可见之前,对象的 final 域已经被正确初始化过了。包括禁止对 final 域的写重排序到构造函数之外,以及在初次读对象引用和读 final 域之间禁止重排序。
JDK源码使用案例
1. java.lang.String类
- 在JDK源码中,`String`类被声明为`final`。 - 作用: - 保证字符串的不可变性。因为字符串在Java中经常被用于存储关键的、不可更改的数据,如文件路径、网络地址等。如果`String`类可以被继承并修改其行为,可能会导致很多难以排查的错误。
有人说,String字符串不可变是因为成员数组value是常量,有人说是因为String被final修饰且提供的方法都不修改原字符串,大家如何看呢?
2.java.util.ArrayList类
- 例如`ArrayList`中的`get(int index)`方法。 - 作用: - 防止子类重写这个方法改变其获取元素的逻辑。`ArrayList`的设计者希望这个基本的获取元素操作按照既定的方式(通过索引直接访问数组元素)进行,这样可以保证在不同版本的JDK中以及不同的使用场景下,`ArrayList`的基本操作的一致性和稳定性。
3. java.lang.System类中的out和err变量
- 在`System`类中,`public static final PrintStream out = ...`和`public static final PrintStream err = ...`。 - 作用: - 这些变量被声明为`final`,表示它们的引用一旦被初始化就不能再指向其他对象。这样可以确保标准输出流和错误输出流在整个程序运行期间都是固定的,不会出现意外的改变,保证了程序输出。
4.java.lang.Math类中的常量
如public static final double PI = 3.14159265358979323846; - 作用: - 这些常量被声明为`final`,防止它们在程序运行过程中被修改。因为数学常量的值是固定的,如果不加以限制,可能会导致计算结果的错误。
final类的一些使用场景
不可变对象: final 类可以用于创建不可变对象,确保对象的状态在初始化后不能被修改。例如,Java中的 String 类就是不可变的,因为它的所有属性都是 final 的。
单例模式:在单例模式中, final 类可以确保只有一个实例对象被创建,并且这个实例对象需要在整个应用程序中保持唯一性。例如,Java中的 Singleton 类可以使用 final 关键字实现不可变单例模式。
工具类: final 类可以用于创建工具类,这些类提供了通用的功能,通常不需要被修改或扩展。例如,Java中的 Collections 类就是不可变的,它提供了一系列静态方法来操作集合。
代理模式:在代理模式中, final 方法可以用于确保代理对象的行为不被修改,从而保证行为的稳定性。例如,代理类中的关键方法可以声明为 final ,防止子类覆盖。
结语
以上内容就是关于final关联字使用我所能想到的相关内容,如有遗漏或错误,欢迎留言指正。
(^▽^)
本文暂时没有评论,来添加一个吧(●'◡'●)