final关键字深入解析

news2024/12/30 3:52:53

final关键字特性

final关键字在java中使用非常广泛,可以申明成员变量、方法、类、本地变量。一旦将引用声明为final,将无法再改变这个引用。final关键字还能保证内存同步,本博客将会从final关键字的特性到从java内存层面保证同步讲解。这个内容在面试中也有可能会出现。

final使用

final变量

final变量有成员变量或者是本地变量(方法内的局部变量),在类成员中final经常和static一起使用,作为类常量使用。其中类常量必须在声明时初始化,final成员常量可以在构造函数初始化。

public class Main {public static final int i; //报错,必须初始化 因为常量在常量池中就存在了,调用时不需要类的初始化,所以必须在声明时初始化public static final int j;Main() {i = 2;j = 3;}
} 

就如上所说的,对于类常量,JVM会缓存在常量池中,在读取该变量时不会加载这个类。

 public class Main {public static final int i = 2;Main() {System.out.println("调用构造函数"); // 该方法不会调用}public static void main(String[] args) {System.out.println(Main.i);}
} 

final方法

final方法表示该方法不能被子类的方法重写,将方法声明为final,在编译的时候就已经静态绑定了,不需要在运行时动态绑定。final方法调用时使用的是invokespecial指令。

class PersonalLoan{public final String getName(){return"personal loan”;}
}
 
class CheapPersonalLoan extends PersonalLoan{@Overridepublic final String getName(){return"cheap personal loan";//编译错误,无法被重载}public String test() {return getName(); //可以调用,因为是public方法}
} 

final类

final类不能被继承,final类中的方法默认也会是final类型的,java中的String类和Integer类都是final类型的。

final class PersonalLoan{}
 
class CheapPersonalLoan extends PersonalLoan {//编译错误,无法被继承 
} 

final关键字的知识点

1.final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。final变量一旦被初始化后不能再次赋值。
2.本地变量必须在声明时赋值。 因为没有初始化的过程
3.在匿名类中所有变量都必须是final变量。
4.final方法不能被重写, final类不能被继承
5.接口中声明的所有变量本身是final的。类似于匿名类
6.final和abstract这两个关键字是反相关的,final类就不可能是abstract的。
7.final方法在编译阶段绑定,称为静态绑定(static binding)。
8.将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计,然后优化。

final方法的好处:

1.提高了性能,JVM在常量池中会缓存final变量
2.final变量在多线程中并发安全,无需额外的同步开销
3.final方法是静态编译的,提高了调用速度
4.final类创建的对象是只可读的,在多线程可以安全共享

从java内存模型中理解final关键字

java内存模型对final域遵守如下两个重拍序规则

1.初次读一个包含final域的对象的引用和随后初次写这个final域,不能重拍序。
2.在构造函数内对final域写入,随后将构造函数的引用赋值给一个引用变量,操作不能重排序。

以上两个规则就限制了final域的初始化必须在构造函数内,不能重拍序到构造函数之外,普通变量可以。

具体的操作是

1.java内存模型在final域写入和构造函数返回之前,插入一个StoreStore内存屏障,静止处理器将final域重拍序到构造函数之外。
2.java内存模型在初次读final域的对象和读对象内final域之间插入一个LoadLoad内存屏障。

new一个对象至少有以下3个步骤

1.在堆中申请一块内存空间
2.对象进行初始化
3.将内存空间的引用赋值给一个引用变量,可以理解为调用invokespecial指令

普通成员变量在初始化时可以重排序为1-3-2,即被重拍序到构造函数之外去了。 final变量在初始化必须为1-2-3。

读写final域重拍序规则

public class FinalExample {int i; final int j;static FinalExample obj;public void FinalExample () {i = 1; // 1j = 2; // 2}public static void writer () {//写线程A obj = new FinalExample ();// 3}public static void reader () { //读线程B执行if(obj != null) { //4int a = object.i; //5int b = object.j; //6}}
} 

我们可以用happens-before来分析可见性。结果是保证a读取到的值可能为0,或者1 而b读取的值一定为2。 首先,由final的重拍序规则决定3HB2,但是3和1不存在HB关系,原因在上面说过了。 因为线程B在线程A之后执行,所以3HB4。 那么2和4的HB关系怎么确定?? final的重拍序规则规定final的赋值必须在构造函数的return之前。所以2HB4。因为在一个线程内4HB6.所以可以得出结论2HB5。则b一定能得到j的最新值。而a就不一定了,因为没有HB关系,可以读到任意值。

HB判断可见性关系真是太方便了。可以参考我的另外一个博客http://medesqure.top/2018/08/25/happen-before/

可能发生的执行时序如下所示。

final对象是引用类型

如果final域是一个引用类型,比如引用的是一个int类型的数组。对于引用类型,写final域的重拍序规则增加了如下的约束

1.在构造函数内对一个final引用的对象的成员域的写入和随后在构造函数外将被构造对象的引用赋值给引用变量之间不能重拍序。 即先写int[]数组的内容,再将引用抛出去。

public class FinalReferenceExample {final int[] intArray; //final是引用类型static FinalReferenceExample obj;public FinalReferenceExample () {//构造函数在构造函数中不能被重排序 final类型在声明或者在构造函数中要赋值。intArray = new int[1];//1intArray[0] = 1; //2}public static void writerOne () {//写线程A执行obj = new FinalReferenceExample ();//3}public static void writerTwo () {//写线程B执行obj.intArray[0] = 2; //4}public static void reader () {//读线程C执行if (obj != null) {//5int temp1 = obj.intArray[0]; //6}}
} 

JMM保证了3和2之间的有序性。同样可以使用HB原则去分析,这里就不分析了。执行顺序如下所示。

final引用不能从构造函数“逸出”

JMM对final域的重拍序规则保证了能安全读取final域时已经在构造函数中被正确的初始化了。 但是如果在构造函数内将被构造函数的引用为其他线程可见,那么久存在对象引用在构造函数中逸出,final的可见性就不能保证。 其实理解起来很简单,就是在其他线程的角度去观察另一个线程的指令其实是重拍序的。

public class FinalReferenceEscapeExample {final int i;static FinalReferenceEscapeExample obj;public FinalReferenceEscapeExample () {i = 1; //1写final域obj = this;//2 this引用在此“逸出”因为obj不是final类型的,所以不用遵守可见性}public static void writer() {new FinalReferenceEscapeExample ();}public static void reader {if (obj != null) { //3int temp = obj.i; //4}}
} 

操作1的和操作2可能被重拍序。在其他线程观察时就会访问到未被初始化的变量i,可能的执行顺序如图所示。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/140598.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot项目从18.18M瘦身到0.18M

一、前言 SpringBoot部署起来虽然简单,如果服务器部署在公司内网,速度还行,但是如果部署在公网(阿里云等云服务器上),部署起来实在头疼:编译出来的 Jar 包很大,如果工程引入了许多开…

GAMES101作业6及课程总结(重点解决SAH扩展作业)

这次作业相对于作业5会麻烦一点点,而且框架相较于作业五的也麻烦了一点,当然作业的难点其实主要还是在扩展作业SAH那块。 目录课程总结与理解(光线追踪)框架梳理作业一:光线生成作业二:光线-三角形相交作业…

Neo4j图数据库 批量写入与查询

1 前言 1-1 简介 工作中需要对所有的实体数据进行存储构建实体知识图谱,为基于知识图谱的问答提供数据基础。选择使用Neo4j作为数据库进行存储。以下是关于Neo4j的简介。 1-2 任务背景 将处理好的实体数据(共计1100万)写入图数据库中,并且提供查询接口…

量子计算(二十):量子算法简介

文章目录 量子算法简介 一、概述 二、量子经典混合算法 量子算法简介 一、概述 量子算法是在现实的量子计算模型上运行的算法,最常用的模型是计算的量子电路模型。经典(或非量子)算法是一种有限的指令序列,或一步地解决问题的…

乐视--996、内卷、裁员环境下一朵“奇葩”

在2022.12.28日我们发表了一篇“为什么四天工作制才是企业良药,而非裁员”,大家认为四天工作制与我们的距离就像实现“一个小目标”一样,不太可能。这不他来了,乐视来了,他真的来了,“鸡毛真的上天了”。他来了他来了他…

SQL技巧:使用AVG()函数计算占比

计算方式对比 一般计算占比,比如转换率、留存率等,都是先分组求和再相除得到结果,但是在一定的条件下,可以直接使用AVG()求出百分比。 比如,要求统计报名转化率,报名转化率公式为转化率报名人数/浏览人数…

内核解读之内存管理(8)内存模型

文章目录基本的术语CONFIG_FLATMEM(平坦内存模型)稀疏的内存模型基本的术语 在介绍内存模型之前需要了解一些基本的知识。 1、什么是page frame? 在linux操作系统中,物理内存被分成一页页的page frame来管理,具体pa…

c++11 标准模板(STL)(std::deque)(八)

定义于头文件 <deque> std::deque 修改器 擦除元素 std::deque<T,Allocator>::erase iterator erase( iterator pos ); (1)(C11 前) iterator erase( const_iterator pos ); (C11 起) iterator erase( iterator first, iterator last ); (2)(C11 前) iterator …

即时编译助力人大金仓KES分析能力飞跃

随着数字化技术对各行各业的不断渗透&#xff0c;人大金仓在金融、能源、电信等行业逐步进入深水区&#xff0c;面临越来越多的核心类系统改造升级&#xff0c;这些系统不仅需要满足在线交易系统运行的高实时性要求&#xff0c;还需要保证高效分析能力以帮助客户进行业务决策。…

红米pro14笔记本系统故障怎么U盘重装系统?

红米pro14笔记本系统故障怎么U盘重装系统&#xff1f;今天和大家一起来分享如何使用U盘重装系统的方法分享。有用户的红米pro14笔记本系统出现了一些问题需要进行重新安装&#xff0c;那么今天我们就一起来分享看看怎么U盘重装系统的方法吧。 准备工作&#xff1a; 1、U盘一个&…

Java执行Linux命令死锁阻塞挂起,Runtime.getRuntime().exec阻塞卡死问题解决

1、前言&#xff1a; 最近在做一个需求需要调用linux下的ffmpeg来对处理视频&#xff0c;很简单的需求&#xff0c;我像往常一样写下如下的代码片段&#xff1a; Process process Runtime.getRuntime().exec(cmd); process.waitFor(); But当我运行代码时&#xff0c;发现代码执…

前端笔记 ---- document.execCommand 函数整理

1. 语法 使用语法 bool document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)返回值 一个 Boolean &#xff0c;如果是 false 则表示操作不被支持或未被启用。 备注&#xff1a; 在调用一个命令前&#xff0c;不要尝试使用返回值去校验浏览器的兼容性 2. 参…

基于Vue和SpringBoot的宾馆管理系统的设计和实现

作者主页&#xff1a;Designer 小郑 作者简介&#xff1a;Java全栈软件工程师一枚&#xff0c;来自浙江宁波&#xff0c;负责开发管理公司OA项目&#xff0c;专注软件前后端开发&#xff08;Vue、SpringBoot和微信小程序&#xff09;、系统定制、远程技术指导。CSDN学院、蓝桥云…

树形结构——红黑树

前言 在 JDK1.8 之后&#xff0c;HashMap 的底层是由数组、链表、红黑树来实现的&#xff0c;当数组长度到 64 的时候&#xff0c;或者链表长度到 8 的时候&#xff0c;会调用 treeifyBin 转换为红黑树实现。因为红黑树是小伙伴们面试的时候经常被考到的知识点&#xff0c;因此…

OSPF-MGRE实验

1.首先配ip [r6]int g 0/0/1 [r6-GigabitEthernet0/0/1]ip add 192.168.1.2 24 [r6-GigabitEthernet0/0/1]int g 0/0/0 [r6-GigabitEthernet0/0/0]ip add 192.168.2.2 24 [r6-GigabitEthernet0/0/0]int g 0/0/2 [r6-GigabitEthernet0/0/2]ip add 192.168.3.2 24 [r6-GigabitEt…

git pull 和git fetch

1.git fetch 用户一&#xff1a;本地初始化项目&#xff0c;创建文件&#xff0c;保存本地仓库&#xff0c;提交远程仓库 $ git init $ touch file.txt $ git add . $ git commit -m "创建了file.txt文件" [master (root-commit) 4dcee36] 创建了file.txt文件1 file …

简单又好用的财务分析工具有哪些?

什么样的财务分析工具才能算是简单又好用&#xff1f;是能够快速完成组合多变的财务指标运算分析&#xff1b;能够充分发挥企业经营健康晴雨表作用&#xff0c;反映企业财务健康状态&#xff1b;还是能够支持多维度动态分析、自助分析&#xff1b;或者是轻松合并账套&#xff0…

跨域与JSONP

1、同源策略 1.1、什么是同源 如果两个页面的协议&#xff0c;域名和端口都相同&#xff0c;则两个页面具有相同的源。 例如&#xff0c;下表给出了相对于 http://www.test.com/index.html 页面的同源检测&#xff1a; URL 是否同源 原因 http://www.test.com/other.html…

智慧图书馆中的“智慧”体现在哪些方面?

在信息时代背景下&#xff0c;各个领域都发生了巨大变革&#xff0c;图书馆也不例外&#xff0c;开始逐步向着现代化方向发展。传统图书馆存在较多的缺陷&#xff0c;已经无法满足人们的借阅需求&#xff0c;引进信息化技术&#xff0c;打造智慧图书馆是目前图书馆的必然发展趋…

Linux学习记录——오 vim基本知识

** Linux开发工具 ** Linux开发工具——vim vim最小集 vim是一个多模式编辑器&#xff0c;vi也一样&#xff0c;但vim兼容了vi的所有指令&#xff0c;还有一些独有的特性&#xff0c;本篇只针对vim展开。vim有各种模式&#xff0c;每个模式的用法都有差别&#xff0c;模式…