zoukankan      html  css  js  c++  java
  • Java中final修饰符深入研究

    一、开篇

    本博客来自:http://www.cnblogs.com/yuananyun/

    final修饰符是Java中比较简单常用的修饰符,同时也是一个被”误解“较多的修饰符。对很多Java程序员来说,他们大都只是草草看了一下各种书本上的介绍,然后背下来,什么时候想起
    来有这东西就用一下。对于何时使用final修饰符、使用final修饰符对程序有什么影响,这些其实他们并不知道,当然在这篇文章之前,我也是一知半解的。

    我们书本上对final的描述大概有三种用法:

      1. final可以修饰变量,被final修饰的变量被赋初始值之后,不能再对它重新赋值

      2. final可以修饰方法,被final修饰的方法不能被重写

      3. final可以修饰类,被final修饰的类不能被继承

    这里,我们将分成三个小节对final在这三种场景中的用法进行说明。

    二、final在变量中的用法

    (一)被final修饰的实例变量必须显示指定初始值,而且只能在如下三个地方指定初始值:

    1. 定义final实例变量时指定初始值
    2. 在非静态初始化块中为final实例变量指定初始值
    3. 在构造器中为final实例变量指定初始值

    其实这三种方式经过编译器处理后效果都一样,为什么呢?这是因为经过编译器处理后,这前面两种方式都会被抽取到构造其中进行赋初始值,这可能和我们平时的理解有点偏差,比如: final String var1=”我是一个兵”; 这么一条实例变量初始化语句,实际是分成两部完成的。第一步是对象创建的时候为变量var1分配内存,第二步是在构造器中为var1赋值”我是一个兵”,千万别以为对象是由构造器创建的,构造器仅仅完成对象的初始化工作而已。

    (二)对于final修饰的类变量(静态变量(static))而言,同样必须显式指定初始值,而且只能在以下两个地方指定初始值:

    1. 定义final类变量时指定初始值
    2. 在静态初始化块中为final类变量指定初始值

    需要指出的是,经过编译器的处理,这两种方式都会被抽取到静态初始化块中进行赋初始值。

    (三)final修饰局部变量的情形比较简单,因为java本来就要求局部变量必须显式地赋值,与普通的局部变量不同的是,final修饰的局部变量一旦被赋值则不能被改变了。

    (四)除此之外,final修饰符还能对编译期能确定值的变量执行”宏替换“,也就是我们常说的const常量。对于一个final变量,不管它是类变量、实例变量,还是局部变量,只要该变量的初始值在编译时就能确定下来,那么这个final变量不再是变量,而是相当于一个直接量。

    public static void main(String [] args)
        {
            final int a=5+2;
            final double b=1.2/3;
            final String str1="我是一个兵";
            final String str2="我是"+"一个兵";
            //str1==str2 为true,因为编译期就可以确定str1,str2的值,于是当作宏替换 :"我是一个兵"=="我是一个兵",当然是true
            final String str3="我是一个兵"+String.valueOf(99.0);
            final String str4="我是一个兵99.0";
            //str3==str4 为false,因为str3调用了String类的valueOf()函数,这在编译期是没法确定的,所以虽然str3和str4的字符串值相同,但不是同一个对象
            //这里同时也给出了一种判断是否使用Java字符串缓存的办法:如果编译期能够确定是相同内容的字符串的两个或多个变量,
    那么它们将使用同一个缓存的字符串对象。
        }

    注:对于宏变量只有在定义该变量的时候赋初始值才会有“宏变量”的效果,在非静态代码块、构造方法中为fianl实例变量指定初始值则不会。静态final类型的变量也一样。其实可以这么理解:当编译器遇到final修饰的变量定义时,如果发现它有指定初始值,那么就把这个变量当作初始值常量;如果没有的话,自然不能当作常量了。如:

    public class FinalInitTest {
    final String str1 ;
    final String str2 ;
    final String str3 = "java" ;
    {//非静态代码块中初始化s
    str1 = "java" ;
    }
    public FinalInitTest(){//构造方法中初始化
    str2 = "java" ;
    }
    public void display(){
    System.out.println(str1 + str1 == "javajava");
    System.out.println(str2 + str2 == "javajava");
    System.out.println(str3 + str3 == "javajava");
    }
    
    public static void main(String[] args){
    FinalInitTest fit = new FinalInitTest() ;
    fit.display() ;
    }
     }
    程序的运行结果为:
    false
    false
    true
    因为str1与str2在定义的时候并没有给初始化值,而是后面在构造函数中赋值的,所以没有“宏变换”的效果

     

    三、final在方法中的用法

    当final修饰某个方法时,用于限制该方法不能被子类重写。如果父类中某个方法使用了final修饰,那么这个方法将不可能被它的子类(不是子类的实例)访问到,因此这个方法也不可能被子类重写,从这个意义上来说,private和final同时修饰某个方法没有什么意义,但是java确实允许你这么干。如果你看到子类中定义了一个和父类中final修饰的同名的方法,那肯定不是重写,只是子类中定义了一个长得一样的方法而已,不信的话,你在子类该方式上加上@Override,将会报编译错误。

    四、final在类上的用法

    final修饰的类将不能被继承,和C#中的密封类一个道理。

    此外,程序需要在匿名内部类或者普通内部类中使用局部变量,则局部变量必须声明为final类型。否则会编译错误。为什么必须声明为final类型呢?这是因为对于普通变量而言,它的作用域是停留在该方法内,当方法执行结束,该局部变量也就随之消失,但内部类则可能产生隐式的“闭包”,闭包使得局部变量脱离它所在的方法继续存在。看下面的例子:

    public class ClosureTest {
    public static void main(String[] args) {
    final String str = "java" ;//定义局部变量
    
    new Thread(new Runnable(){
    public void run(){
    for(int i=0 ;i<100 ; i++){
    System.out.println(str + i);
    try {
    Thread.sleep(100) ;
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }).start() ;
    }
     }

    程序首先定义了一个局部变量str,当程序main方法执行完成后,str的生命周期就结束了,但是子线程还没有执行结束,而且子线程要用main中的局部变量str,这个时候就扩大了str的作用范围。这个时候如果str没有被修饰为final类型,而可以随便改变,则会引起极大的混乱,因此java编译器要求所有的内部类访问的局部变量必须使用final修饰符修饰。

    最后,本文深入分析了java中final修饰符的功能,看似简单的背后,其实蕴含很多重要的道理。学习一门技术不要仅限于表面的用法,高手往往都是站在了理解甚至重构的层次,路漫漫其修远兮,吾将上下而求索。

  • 相关阅读:
    Linux直接在通过终端打开图片文件
    【暑假】[实用数据结构]UVa11995 I Can Guess the Data Structure!
    【暑假】[实用数据结构]动态范围查询问题
    【暑假】[实用数据结构]范围最小值问题(RMQ)
    【暑假】[实用数据结构]动态连续和查询问题
    【暑假】[基本数据结构]基本的数据结构知识点总结梳理
    【暑假】[基本数据结构]根据in_order与post_order构树
    【暑假】[基本数据结构]根据BFS与DFS确定树
    【暑假】[网络流]网络流知识总结
    [HDOJ2546] 饭卡 (01背包)
  • 原文地址:https://www.cnblogs.com/yuananyun/p/3947587.html
Copyright © 2011-2022 走看看