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)。


目录
相关文章
|
Android开发 数据格式 XML
Android学习之保存用户登录信息
版权声明:本文为博主原创文章,转载请注明出处http://blog.csdn.net/u013132758。 https://blog.csdn.net/u013132758/article/details/50206887 ...
1914 0
|
Ubuntu
ubuntu 换源 阿里源
ubuntu 换源 阿里源
1703 0
|
机器学习/深度学习 API 异构计算
7.1.3.2、使用飞桨实现基于LSTM的情感分析模型的网络定义
该文章详细介绍了如何使用飞桨框架实现基于LSTM的情感分析模型,包括网络定义、模型训练、评估和预测的完整流程,并提供了相应的代码实现。
|
存储 缓存
ETag的值是如何在HTTP响应中传递给客户端的
ETag的值是如何在HTTP响应中传递给客户端的
|
分布式计算 DataWorks 关系型数据库
DataWorks操作报错合集之出现了传参后提示有字段没映射上,但字段连线都已经正常连接的情况,该如何处理
DataWorks是阿里云提供的一站式大数据开发与治理平台,支持数据集成、数据开发、数据服务、数据质量管理、数据安全管理等全流程数据处理。在使用DataWorks过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
|
存储 安全 索引
Pandas 2.2 中文官方教程和指南(二十四)(3)
Pandas 2.2 中文官方教程和指南(二十四)
254 0
|
NoSQL Redis 数据库
Redis的GUI工具——Another-Redis-Desktop-Manager连接远程数据库Redis
Redis的GUI工具——Another-Redis-Desktop-Manager连接远程数据库Redis
6220 0
|
Linux
如何防止僵尸进程?
如何防止僵尸进程?
465 0
|
小程序 API
微信小程序开发之数据埋点统计
微信小程序开发之数据埋点统计
763 0
微信小程序开发之数据埋点统计