final关键字特性(中)

简介: final关键字特性

使用final修饰变量会让变量的值不能被改变吗;

见代码:

public class Final   
{   
    public static void main(String[] args)   
    {   
        Color.color[3] = "white";   
        for (String color : Color.color)   
            System.out.print(color+" ");   
    }   
}   
class Color   
{   
    public static final String[] color = { "red", "blue", "yellow", "black" };   
}  
执行结果: 
red blue yellow white 
看!,黑色变成了白色。 

在使用findbugs插件时,就会提示public static String[] color = { “red”, “blue”, “yellow”, “black” };这行代码不安全,但加上final修饰,这行代码仍然是不安全的,因为final没有做到保证变量的值不会被修改!

原因是:final关键字只能保证变量本身不能被赋与新值,而不能保证变量的内部结构不被修改。例如在main方法有如下代码Color.color = new String[]{""};就会报错了。

如何保证数组内部不被修改

那可能有的同学就会问了,加上final关键字不能保证数组不会被外部修改,那有什么方法能够保证呢?答案就是降低访问级别,把数组设为private。这样的话,就解决了数组在外部被修改的不安全性,但也产生了另一个问题,那就是这个数组要被外部使用的。 

解决这个问题见代码:

import java.util.AbstractList;   
import java.util.List;   
public class Final   
{   
    public static void main(String[] args)   
    {   
        for (String color : Color.color)   
            System.out.print(color + " ");   
        Color.color.set(3, "white");   
    }   
}   
class Color   
{   
    private static String[] _color = { "red", "blue", "yellow", "black" };   
    public static List<String> color = new AbstractList<String>()   
    {   
        @Override  
        public String get(int index)   
        {   
            return _color[index];   
        }   
        @Override  
        public String set(int index, String value)   
        {   
            throw new RuntimeException("为了代码安全,不能修改数组");   
        }   
        @Override  
        public int size()   
        {   
            return _color.length;   
        }   
    };  
}

这样就OK了,既保证了代码安全,又能让数组中的元素被访问了。

final方法的三条规则

规则1:final修饰的方法不可以被重写。

规则2:final修饰的方法仅仅是不能重写,但它完全可以被重载。

规则3:父类中private final方法,子类可以重新定义,这种情况不是重写。

代码示例

规则1代码
public class FinalMethodTest
{
  public final void test(){}
}
class Sub extends FinalMethodTest
{
  // 下面方法定义将出现编译错误,不能重写final方法
  public void test(){}
}
规则2代码
public class Finaloverload {
  //final 修饰的方法只是不能重写,完全可以重载
  public final void test(){}
  public final void test(String arg){}
}
规则3代码
public class PrivateFinalMethodTest
{
  private final void test(){}
}
class Sub extends PrivateFinalMethodTest
{
  // 下面方法定义将不会出现问题
  public void test(){}
}

final 和 jvm的关系

与前面介绍的锁和 volatile 相比较,对 final 域的读和写更像是普通的变量访问。对于 final 域,编译器和处理器要遵守两个重排序规则:


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


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


下面,我们通过一些示例性的代码来分别说明这两个规则:

public class FinalExample {
int i;                            // 普通变量
final int j;                      //final 变量
static FinalExample obj;
```java
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;                // 读普通域 
    int b = object.j;                // 读 final 域 
}
```
}

这里假设一个线程 A 执行 writer () 方法,随后另一个线程 B 执行 reader () 方法。下面我们通过这两个线程的交互来说明这两个规则。

写 final 域的重排序规则

写 final 域的重排序规则禁止把 final 域的写重排序到构造函数之外。这个规则的实现包含下面 2 个方面:


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


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


现在让我们分析 writer () 方法。writer () 方法只包含一行代码:finalExample = new FinalExample ()。这行代码包含两个步骤:


构造一个 FinalExample 类型的对象;


把这个对象的引用赋值给引用变量 obj。


假设线程 B 读对象引用与读对象的成员域之间没有重排序(马上会说明为什么需要这个假设),下图是一种可能的执行时序:


b68a4d88735b6009f1e960652a0a91aa.jpg

在上图中,写普通域的操作被编译器重排序到了构造函数之外,读线程 B 错误的读取了普通变量 i 初始化之前的值。而写 final 域的操作,被写 final 域的重排序规则“限定”在了构造函数之内,读线程 B 正确的读取了 final 变量初始化之后的值。


写 final 域的重排序规则可以确保:在对象引用为任意线程可见之前,对象的 final 域已经被正确初始化过了,而普通域不具有这个保障。以上图为例,在读线程 B“看到”对象引用 obj 时,很可能 obj 对象还没有构造完成(对普通域 i 的写操作被重排序到构造函数外,此时初始值 1 还没有写入普通域 i)。


目录
相关文章
|
2月前
|
存储 安全 Java
了解final关键字在Java并发编程领域的作用吗?
在Java并发编程中,`final`关键字不仅用于修饰变量、方法和类,还在多线程环境中确保对象状态的可见性和不变性。本文深入探讨了`final`关键字的作用,特别是其在final域重排序规则中的应用,以及如何防止对象的“部分创建”问题,确保线程安全。通过具体示例,文章详细解析了final域的写入和读取操作的重排序规则,以及这些规则在不同处理器上的实现差异。
了解final关键字在Java并发编程领域的作用吗?
|
2月前
|
存储 设计模式 编译器
【C++篇】C++类与对象深度解析(五):友元机制、内部类与匿名对象的高级应用
【C++篇】C++类与对象深度解析(五):友元机制、内部类与匿名对象的高级应用
30 2
|
2月前
|
存储 安全 Java
了解final关键字在Java并发编程领域的作用吗?
了解final关键字在Java并发编程领域的作用吗?
|
6月前
|
Java 数据安全/隐私保护
Java基础手册二(类和对象 对象创建和使用 面向对象封装性 构造方法与参数传递 this关键字 static关键字 继承 多态 方法覆盖 final关键字 访问控制权限修饰符)
Java基础手册二(类和对象 对象创建和使用 面向对象封装性 构造方法与参数传递 this关键字 static关键字 继承 多态 方法覆盖 final关键字 访问控制权限修饰符)
36 0
|
7月前
|
Java
Java面向对象高级【final关键字的使用】
Java面向对象高级【final关键字的使用】
|
C++
47 C++ - 继承中的静态成员特性
47 C++ - 继承中的静态成员特性
44 0
|
Java
Java面向对象中 final关键字的详解
Java面向对象中 final关键字的详解
61 0
|
安全 程序员
【面向对象语言三大特性之 “继承”】(一)
【面向对象语言三大特性之 “继承”】(一)
95 0
|
Java 编译器 C++
【面向对象语言三大特性之 “继承”】(二)
【面向对象语言三大特性之 “继承”】(二)
57 0
|
缓存 安全 Java
final关键字特性(上)
final关键字特性
101 0