final在Java中是一个保留的关键字,可以声明成员变量、方法、类以及本地变量。

一旦你将引用声明作final,你将不能改变这个引用了,编译器会检查代码,如果你试图将变量再次初始化的话,编译器会报编译错误。

一、final变量

  final成员变量表示常量,只能被赋值一次,赋值后值不再改变(final要求地址值不能改变)

  当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。本质上是一回事,因为引用的值是一个地址,final要求值,即地址的值不发生变化。

  final修饰一个成员变量(属性),必须要显示初始化。这里有两种初始化方式,一种是在变量声明的时候初始化;第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值。

二、final方法

使用final方法的原因有两个。

第一个原因是把方法锁定,以防任何继承类修改它的含义,不能被重写;

第二个原因是效率,final方法比非final方法要快,因为在编译的时候已经静态绑定了,不需要在运行时再动态绑定。

(注:类的private方法会隐式地被指定为final方法)

三、final类

当用final修饰一个类时,表明这个类不能被继承。

final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。

在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。

四、final使用总结

final关键字的好处:

(1)final关键字提高了性能。JVM和Java应用都会缓存final变量。

(2)final变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。

(3)使用final关键字,JVM会对方法、变量及类进行优化。

关于final的重要知识点

  1. final关键字可以用于成员变量、本地变量、方法以及类。
  2. final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。
  3. 你不能够对final变量再次赋值。
  4. 本地变量必须在声明时赋值。
  5. 在匿名类中所有变量都必须是final变量。
  6. final方法不能被重写。
  7. final类不能被继承。
  8. final关键字不同于finally关键字,后者用于异常处理。
  9. final关键字容易与finalize()方法搞混,后者是在Object类中定义的方法,是在垃圾回收之前被JVM调用的方法。
  10. 接口中声明的所有变量本身是final的。
  11. final和abstract这两个关键字是反相关的,final类就不可能是abstract的。
  12. final方法在编译阶段绑定,称为静态绑定(static binding)。
  13. 没有在声明时初始化final变量的称为空白final变量(blank final variable),它们必须在构造器中初始化,或者调用this()初始化。不这么做的话,编译器会报错“final变量(变量名)需要进行初始化”。
  14. 将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计,然后优化。
  15. 按照Java代码惯例,final变量就是常量,而且通常常量名要大写。
  16. 对于集合对象声明为final指的是引用不能被更改,但是你可以向其中增加,删除或者改变内容。

五、final原理

最好先理解java内存模型 Java并发(二):Java内存模型

对于final域,编译器和处理器要遵守两个重排序规则:

1.在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。

  (先写入final变量,后调用该对象引用)

  原因:编译器会在final域的写之后,插入一个StoreStore屏障

2.初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。

  (先读对象的引用,后读final变量)

  编译器会在读final域操作的前面插入一个LoadLoad屏障

示例1:

public class FinalExample {int i; // 普通变量final int j; // final 变量static FinalExample obj;public void FinalExample() { // 构造函数i = 1; // 写普通域j = 2; // 写 final 域
    }public static void writer() { // 写线程 A 执行obj = new FinalExample();}public static void reader() { // 读线程 B 执行FinalExample object = obj; // 读对象引用int a = object.i; // 读普通域         a=1或者a=0或者直接报错i没有初始化int b = object.j; // 读 final域      b=2
    }
}

第一种情况:写普通域的操作被编译器重排序到了构造函数之外

而写 final 域的操作,被写 final 域的重排序规则“限定”在了构造函数之内,读线程 B 正确的读取了 final 变量初始化之后的值。

写 final 域的重排序规则可以确保:在对象引用为任意线程可见之前,对象的 final 域已经被正确初始化过了,而普通域不具有这个保障。

第二种情况:读对象的普通域的操作被处理器重排序到读对象引用之前

而读 final 域的重排序规则会把读对象 final 域的操作“限定”在读对象引用之后,此时该 final 域已经被 A 线程初始化过了,这是一个正确的读取操作。

读 final 域的重排序规则可以确保:在读一个对象的 final 域之前,一定会先读包含这个 final 域的对象的引用。

示例2:如果 final 域是引用类型

对于引用类型,写 final 域的重排序规则对编译器和处理器增加了如下约束:

在构造函数内对一个 final 引用的对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。

public class FinalReferenceExample {final int[] intArray; // final 是引用类型static FinalReferenceExample obj;public FinalReferenceExample() { // 构造函数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  temp1=1或者temp1=2,不可能等于0
        }}
}

假设首先线程 A 执行 writerOne() 方法,执行完后线程 B 执行 writerTwo() 方法,执行完后线程 C 执行 reader () 方法。

 

在上图中,1 是对 final 域的写入,2 是对这个 final 域引用的对象的成员域的写入,3 是把被构造的对象的引用赋值给某个引用变量。这里除了前面提到的 1 不能和 3 重排序外,2 和 3 也不能重排序。

JMM 可以确保读线程 C 至少能看到写线程 A 在构造函数中对 final 引用对象的成员域的写入。即 C 至少能看到数组下标 0 的值为 1。而写线程 B 对数组元素的写入,读线程 C 可能看的到,也可能看不到。JMM 不保证线程 B 的写入对读线程 C 可见,因为写线程 B 和读线程 C 之间存在数据竞争,此时的执行结果不可预知。

参考资料 / 相关推荐

深入理解 Java 内存模型(六)——final

深入理解Java中的final关键字

浅谈Java中的final关键字

Java并发(二):Java内存模型

转载于:https://www.cnblogs.com/hexinwei1/p/10025840.html

Java并发(十九):final实现原理相关推荐

  1. Java并发机制的底层实现原理

    Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令.本章我们将 ...

  2. 《Java并发编程的艺术》一一第2章Java并发机制的底层实现原理

    第2章Java并发机制的底层实现原理 2.1 volatile的应用 Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行, ...

  3. 《Java并发编程的艺术》:第2章 Java并发机制的底层实现原理

    前言 Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节 码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和 CPU的指令. ...

  4. java第十九次学习笔记

    java第十九次学习笔记 异常 java第十九次学习笔记 前言 一.try catch 二.多个异常如何处理 前言 斯人若彩虹遇上方知有 一.try catch package Demo01;impo ...

  5. Java并发编程—定时器Timer底层原理

    原文作者:妮蔻 原文地址:Java并发编程笔记之Timer源码分析 目录 一.timer问题复现 二.Timer 实现原理分析 timer在JDK里面,是很早的一个API了.具有延时的,并具有周期性的 ...

  6. java并发编程——九 AbstractQueuedSynchronizer AQS详解

    文章目录 AbstractQueuedSynchronizer概述 AbstractQueuedSynchronizer的使用 AQS实现分析 同步队列 独占锁的获取与释放 独占式超时获取 共享式锁的 ...

  7. java并发机制的底层实现原理(volatile,synchronized,原子操作)

    目录 volatile的应用 volatile的定义与实现原理 volatile的使用优化 synchronized的实现原理与应用 Java对象头 锁的升级与对比 偏向锁 轻量级锁 锁的优缺点对比 ...

  8. 吃透这JAVA并发十二核心,面试官都得对你刮目相看

    1.HashMap 面试第一题必问的 HashMap,挺考验Javaer的基础功底的,别问为啥放在这,因为重要!HashMap具有如下特性: HashMap 的存取是没有顺序的. KV 均允许为 NU ...

  9. JAVA并发十二连招

    1.HashMap 面试第一题必问的 HashMap,挺考验Javaer的基础功底的,别问为啥放在这,因为重要!HashMap具有如下特性: HashMap 的存取是没有顺序的. KV 均允许为 NU ...

  10. Java并发:明白Synchronized实现原理,锁什么?

    最近看到synchronized的知识点,做些简单记录. 一.Synchronized的基本使用 Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法.Synchr ...

最新文章

  1. Java的Redis连接池代码性能不错
  2. ”device not found“错误原因及解决方法
  3. 知乎网解决HTML5 Placeholder的方案
  4. Channel延续篇
  5. mysql索引与约束有什么关系_MySQL 约束与索引
  6. java 程序中打开文件和文件夹
  7. 主动断开socket链接_TCP连接与断开详解(socket通信)
  8. ggforce|绘制区域轮廓-区域放大-寻找你的“onepiece”
  9. 换脸App爆红引发人脸盗刷担忧?看到支付宝的回应放心了...
  10. oracle数据库查表_oracle数据库之多表查询二
  11. LeetCode——remove-duplicates-from-sorted-list
  12. 2022MathorCup高校数模挑战赛D题思路
  13. 免费分享佳能ir c3320 c3330 c3325彩色复印机中文维修手册
  14. (完美解决)Word标题的编号出现一条黑色竖线
  15. 标签系列三:spring 中property解释以及property标签里面的属性
  16. MySQL【部署 04】8.0.25离线部署(下载+安装+配置)Failed dependencies 问题处理及8.0配置参数说明
  17. python3识别图中的文字_Python3调用百度AI识别图片中的文字功能示例【测试可用】...
  18. Guarded Blocks 保护块
  19. java开发微信公众号(SpringMVC)2-消息管理功能
  20. 小米3的卡槽,卡住了

热门文章

  1. a标签跳转后关闭当前页面_微信小程序2020-day-2 导航项目(跳转三种形态)
  2. 2016没有自带公式编辑器_如何在Visio中插入数学公式
  3. 牛客练习赛44 A 小y的序列 (模拟,细节)
  4. .call() 与 .apply() 的用法及区别
  5. SVG 图像入门教程
  6. es6学习笔记(一)
  7. 极光IM初始化SDK出错
  8. 阿里笔试-二叉树由前序遍历和中序遍历推导后序遍历
  9. 学习面试题Day08
  10. numpy 函数一:linspace