final

final 可以修饰变量、方法和类,表示所修饰的内容一旦赋值之后就不会再被改变。例如String类就是一个final类型的类。

1.具体使用场景

1.1 变量

1.1.1 成员变量

每个类中的成员变量可以分为类变量(static修饰的变量)以及实例变量。针对这两种类型的变量赋初值的时机是不同的。

类变量(两个赋初值时机):

  • 在声明变量的时候直接赋初值
  • 在静态代码块中给类变量赋初值

实例变量(三个赋初值时机):

  • 可以在声明变量的时候给实例变量赋初值
  • 在非静态初始化块中赋初值
  • 在构造器中赋初值

初始化报错情况:

  • final在变量未初始化的时候,系统不会进行隐式的初始化,则会出现报错情况
  • final修饰的变量一经赋值,就不可再更改,否则报错。
  • 实例变量不可以在静态初始化块中赋初值,否则报错。
  • 实例方法不能为 final 类型变量赋值,否则报错

总结:

类变量:必须要在静态初始化块中指定初始值或者声明该类变量时指定初始值,而且只能在这两个地方之一进行指定;

实例变量:必要要在非静态初始化块声明该实例变量或者在构造器中指定初始值,而且只能在这三个地方进行指定。

1.1.2 局部变量

final局部变量由程序员进行显式初始化,如果final局部变量已经进行了初始化则后面就不能再次进行更改,如果final变量未进行初始化,可以进行赋值,当且仅有一次赋值,一旦赋值之后再次赋值就会出错。

方法形参:

  • 调用方法,给参数列表传入实参时,完成初始化

方法局部变量:

  • 在使用前 进行初始化

初始化报错情况:

  • 变量当且仅有一次赋值,若赋值后进行修改则会报错。
  • 没有对局部变量进行初始化操作就引用,则会报错。

1.1.3 基本数据类型

同上成员变量/局部变量。

1.1.4 引用数据类型

重新初始化赋值操作,会报错。因为引用地址发生了变化。

对引用类型中的属性进行修改,可以,因为指向地址没有发生变化。

final修饰基本数据类型变量时,不能对基本数据类型变量重新赋值,因此基本数据类型变量不能被改变。而对于引用类型变量而言,它仅仅保存的是一个引用,final只保证这个引用类型变量所引用的地址不会发生改变,即一直引用这个对象,但这个对象属性是可以改变的

1.2 宏变量

宏变量是指,可以执行宏替换的变量。也就是说,编译器会将程序中用到该变量的地方全部替换成该变量的值。

利用final变量的不可更改性,在满足一下三个条件时,该变量就会成为一个“宏变量”,即是一个常量:

  • 使用final修饰符修饰
  • 在定义该final变量时就指定了初始值
  • 该初始值在编译时就能够唯一指定

宏变量已经不再是变量范畴,而是直接量。因为使用宏变量的地方,编译器会直接替换成对应的直接量。

1.3 方法

重写:

当父类的方法被 final 修饰的时候,子类不能重写父类的方法。如 Object 类中的 getClass() 方法就是 final 的,不可重写。但是 hashCode() 不是被 final 所修饰的,所以可以重写。

也就是说,final修饰的方法不能够被子类所重写

重载:

final 修饰的方法可以被重载。

1.4 类

当一个类被final修饰时,表名该类是不能被子类继承的。是最终类。

另,final 不允许和 abstract 同时修饰一个方法或类。因为 final 不允许被重写和继承,而 abstract 是抽象的,没有实现,所以必须被子类继承重写。

2. 不可变类

2.1 概念

不可变类:不变类的意思是创建该类的实例后,该实例的实例变量是不可改变的。

可变类:创建该对象后,该对象的实例变量是可变的。

2.2 设计规则

不可变类有以下几个设计规则:

  • 无法扩展。是最终类(将类声明为 final
  • 所有成员都是 private final
  • 不提供对成员的改变方法,如 set 方法
  • 通过构造器初始化成员,若构造器传入引用数据类型,需要进行深拷贝(复制该类的新实例而不是引用),确保类的不可变。
  • 在公开修改类状态的方法时,必须始终返回该类的新实例。
  • 必要时重写 hashCodeequals 方法,保证两个 equals 方法判断为相等的对象,其 hashCode 也应该相等

2.3 举例

比如,java中八个基本类型的包装类,和 String 类都属于不可变类。举例看看String的实现:

/** The value is used for character storage. */
private final char value[];

可以看出Stringvalue就是final修饰的,上述其他几条性质也是吻合的。

2.4 优点

优点:

  • 高效率

    • 拷贝对象内容不用复制本身,而是复制地址,复制地址只需要很小的内存空间,具有非常高的效率。
    • 保证了 hashCode 的唯一性,因此可以放心的进行缓存而不必每次重新计算新的哈希码。可以提高在 HashMap 等以不可变类实例为 key 的容器的性能
  • 线程安全。避免值被其他进程修改的情况,同时省去了同步加锁等过程。

3. 多线程中的final

3.1 抛出问题

java内存模型中,为了能让编译器和处理器底层发挥他们的最大优势,所以对其的约束很少。那么处理器和编译器为了性能优化就会对指令序列有编译器和处理器的重排序操作。那么问题来了,在多线程情况下, final 会进行怎样的重排序?会导致线程安全的问题吗?

3.2 final 域的重排序规则

3.2.1 final 域为基本类型

示例代码:

public class FinalDemo {
private int a; //普通域
private final int b; //final域
private static FinalDemo finalDemo; public FinalDemo() {
a = 1; // 1. 写普通域
b = 2; // 2. 写final域
} public static void writer() {
finalDemo = new FinalDemo();
} public static void reader() {
FinalDemo demo = finalDemo; // 3.读对象引用
int a = demo.a; //4.读普通域
int b = demo.b; //5.读final域
}
}

假设线程A在执行writer()方法,线程B执行reader()方法。

3.2.1.1 写操作

final域的重排序规则禁止对final域的写重排序到构造函数之外,这个规则的实现主要包含了两个方面:

  • JMM禁止编译器把final域的写重排序到构造函数之外;

  • 编译器会在final域写之后,构造函数return之前,插入一个storestore屏障。这个屏障可以禁止处理器把final域的写重排序到构造函数之外。

那么对于writer方法,就存在的一种可能执行时序图:

因此,写final域的重排序规则可以确保在对象引用为任意线程可见之前,对象的 final 域已经被正确的初始化过了,而普通域就不具有这个保障。(也就是说,final 可以保证正在创建中的对象不能被其他线程访问到。)

但是这其实是有个前提条件的:在构造函数中,不能让这个被构造的对象被其他线程可见。也就是说该对象不能再构造函数中"溢出".

如下代码:

public class FinalReferenceEscapeDemo {
private final int a;
private FinalReferenceEscapeDemo referenceDemo; public FinalReferenceEscapeDemo() {
a = 1; //1
referenceDemo = this; //2
} public void writer() {
new FinalReferenceEscapeDemo();
} public void reader() {
if (referenceDemo != null) { //3
int temp = referenceDemo.a; //4
}
}
}

假设一个线程A执行writer方法线程B执行reader方法。

因为构造函数中操作1和2之间没有数据依赖性,1和2可以重排序,先执行了2,这个时候引用对象referenceDemo是个没有完全初始化的对象,而当线程B去读取该对象时就会出错。尽管依然满足了final域写重排序规则:在引用对象对所有线程可见时,其final域已经完全初始化成功。但是,引用对象“this”逸出,该代码依然存在线程安全的问题。

3.2.1.2 读操作

final域重排序规则为:在一个线程中,初次读对象引用和初次读该对象包含的final域,JMM会禁止这两个操作的重排序。也就是说,确保在读一个对象的 final 域之前,一定会先读包含这个 final 域的对象的引用。(注意,这个规则仅仅是针对处理器),处理器会在读final域操作的前面插入一个LoadLoad屏障。实际上,读对象的引用和读该对象的final域存在间接依赖性,一般处理器不会重排序这两个操作。

3.2.2 final 域为引用类型

3.2.2.1 写操作

针对引用数据类型,在之前基本类型的规则基础删,增加了这样的约束:在构造函数内对一个final修饰的对象的成员域的写入,与随后在构造函数之外把这个被构造的对象的引用赋给一个引用变量,这两个操作是不能被重排序的。这句话是比较拗口的,下面结合实例来看。

public class FinalReferenceDemo {
final int[] arrays;
private FinalReferenceDemo finalReferenceDemo; public FinalReferenceDemo() {
arrays = new int[1]; //1
arrays[0] = 1; //2
} public void writerOne() {
finalReferenceDemo = new FinalReferenceDemo(); //3
} public void writerTwo() {
arrays[0] = 2; //4
} public void reader() {
if (finalReferenceDemo != null) { //5
int temp = finalReferenceDemo.arrays[0]; //6
}
}
}

线程线程A执行wirterOne方法,执行完后线程B执行writerTwo方法,然后线程C执行reader方法。下图就以这种执行时序出现的一种情况来讨论。

3.2.2.2 读操作

JMM可以确保线程C至少能看到写线程A对final引用的对象的成员域的写入,即能看下arrays[0] = 1,而写线程B对数组元素的写入可能看到可能看不到。JMM不保证线程B的写入对线程C可见,线程B和线程C之间存在数据竞争,此时的结果是不可预知的。如果可见的,可使用锁或者volatile

4.扩展

4.1 final、finally、finalize的区别

final:用于声明属性,方法和类,表示属性不可变性,方法不可覆盖,类不可继承。

finally:是异常处理语句结构的一部分,表示总是会执行。

finalize:是Object类中的一个方法,在垃圾收集器执行的时候,会去调用被回收对象的此方法,供垃圾收集时其他资源的回收。比如关闭文件等。

[Java] 详细解说final关键字的更多相关文章

  1. JAVA面向对象-----final关键字

    JAVA面向对象-–final关键字 1:定义静态方法求圆的面积 2:定义静态方法求圆的周长 3:发现方法中有重复的代码,就是PI,圆周率. 1:如果需要提高计算精度,就需要修改每个方法中圆周率. 4 ...

  2. 聊聊Java的final关键字

    Java的final关键字在日常工作中经常会用到,比如定义常量的时候.如果是C++程序员出身的话,可能会类比C++语言中的define或者const关键字,但其实它们在语义上差距还是挺大的. 在Jav ...

  3. java之final关键字

    final关键字(可以读不可以写.只读) 1.final的变量的值不能够被改变 ①.final的成员变量 ②.final的局部变量(形参) //意思是“实参”一旦传进我的方法里面,就不允许改变 2.f ...

  4. Java的final关键字详解

    Java中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什么是final关键字?将变量,方法和类声明为final代表了什么?使用final的好处是什么?最后也有一些使 ...

  5. java中final 关键字的作用

    final 关键字的作用 java中的final关键字可以用来声明成员变量.本地变量.类.方法,并且经常和static一起使用声明常量. final关键字的含义: final在Java中是一个保留的关 ...

  6. Java基础 -- final关键字

    在java的关键字中,static和final是两个我们必须掌握的关键字.不同于其他关键字,他们都有多种用法,而且在一定环境下使用,可以提高程序的运行性能,优化程序的结构.下面我们来了解一下final ...

  7. Java中final关键字修饰变量、方法、类的含义是什么

    Java中的关键字final修饰变量.方法.类分别表示什么含义? 先看一个简单的介绍 修饰对象 解释说明 备注 类 无子类,不可以被继承,更不可能被重写. final类中的方法默认是final的 方法 ...

  8. java浅析final关键字

    谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法. ...

  9. 关于Java中final关键字的详细介绍

    Java中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什么是final关键字?将变量,方法和类声明为final代表了什么?使用final的好处是什么?最后也有一些使 ...

  10. java基础---->final关键字的使用

    这里介绍一些java基础关于final的使用,文字说明部分摘自java语言规范.心甘情愿这四个字,透着一股卑微,但也有藏不住的勇敢. Final关键字的说明 一.关于final变量规范说明 .A fi ...

随机推荐

  1. freeswitch两个DTMF转换接口的区别

    概述 freeswitch支持三种模式的DTMF传输方式,分别时inband.INFO.2833. 在传统的PSTN网络中,所有的DTMF码都是inband模式,所以VOIP网络和PSTN网络对接中, ...

  2. 腾讯视频客户端 MP4 下载

    腾讯视频直接使用客户端下载视频,得到的是 QLV 文件,这种加密视频文件只能通过腾讯视频客户端播放.最新版的腾讯客户端下载的 QLV 文件,使用各种转码软件都不能正常转码.从服务器下载的 TS 文件一 ...

  3. 深入理解java线程池 一

    本文为博主原创,未经允许不得转载: 在多线程和高并发场景中,需要创建大量的线程来进行业务处理,我们通常创建线程有两种方法,一种是通过继承Thread类,另一种是实现Runnable的接口,但是我们创建 ...

  4. 01-module/分频器/激励写法

    1.module module有出入接口,输出接口 module有时钟和复位 // input clock; rest_n; // n表示低电平复位 //output o_data; module m ...

  5. JAVA - 文件切分存储 - FileInputStream 流形式

    package stream; import java.io.*; /** * @author*/ public class TestStream { public static void main( ...

  6. Oracle Rac 的简单学习

    Oracle Rac 的简单学习 Oracle RAC的概念 Oracle RAC (Real Application Clusters) 是 Oracle 数据库管理系统的一个功能, 它允许将数据库 ...

  7. 【转帖】Java Full GC (Ergonomics) 的排查

    文章目录 1. Full GC (Ergonomics) 1.1 Java 进程一直进行 Full GC 1.2 Full GC 的原因 1.3 检查堆占用 2. 代码检查 3. 解决方式 1. Fu ...

  8. 我们开源了一个 Ant Design 的单元测试工具库

    我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品.我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值. 本文作者:佳岚 欢迎大家点一个小小的 Star ant-design ...

  9. 记一次JSF异步调用引起的接口可用率降低

    前言 本文记录了由于JSF异步调用超时引起的接口可用率降低问题的排查过程,主要介绍了排查思路和JSF异步调用的流程,希望可以帮助大家了解JSF的异步调用原理以及提供一些问题排查思路.本文分析的JSF源 ...

  10. iframe父页面传递参数给子页面

    父页面通过ifarame传递参数 有些时候,我们需要在嵌套页面. 我们就需要使用iframe了. 通过iframe传递参数给子页面. 需求描述,当我们点击按钮的时候. 传递参数给子页面.子页面接受后展 ...