首页 > 编程语言 > 详细

Java基础认知--final关键字

时间:2020-01-10 00:29:36      阅读:84      评论:0      收藏:0      [点我收藏+]

这篇主题为Java的final关键字。

    解释一个事物必须先问一个问题,那就是他的存在有何用处,弄清楚这个问题,你基本就能有个清楚的认识了。

final关键字所能修饰的角色:1.类  2. 方法 3.域

  • 修饰类

修饰类时,此类就是final类,final类不能被继承。这样做主要为了设计,和安全考虑。比如String类就是Final类。如果你想设计一个最终实用类,如String类,此类封装了字符串数据,提供了操控字符串的很多方法,多到你可以想什么操作基本都能找到,而且你不想留给别人定制其他操作的空间,此类中方法的行为固定不会改变,也为安全考虑,况且也没有必要扩展。

  • 修饰方法

1. final修饰方法时,此方法将不能被重写,如果你不希望方法被扩展或改变行为,或者说没必要去改变此方法,可以设置为final。个人认为设置final方法主要是避免代码的繁重,滥用。

2. 写这篇文章之前看了很多的博客,很多博客说final方法JVM可以通过内连(如果方法体较短,相当于将代码逻辑直接放到调用方法处,省去了方法寻找调用过程)的方式优化,提高运行效率。我对此有些怀疑。有些博客还进行了实验,所以我们一起来看一下这个验证例程:

public class Test1 {
    
    public static void getJava() {
        String str1 = "Java";
        String str2 = "final";
        for(int i =0;i < 10000;i++) {
            str1 += str2;
        }
    }
    public static final void getJavaFinal() {
        String str1 = "Java";
        String str2 = "final";
        for(int i =0;i < 10000;i++) {
            str1 += str2;
        }
    }
    public static void main(String[] args) {
                //调用非final方法
        long start1 = System.currentTimeMillis();
        getJava();
        System.out.println(System.currentTimeMillis() - start1);

                //直接调用代码体
        long start2 = System.currentTimeMillis();
        String str1 = "Java";
        String str2 = "final";
        for(int i =0;i < 10000;i++) {
            str1 += str2;
        }
        System.out.println(System.currentTimeMillis() - start2);
                //调用final方法
        long start3 = System.currentTimeMillis();
        getJavaFinal();
        System.out.println(System.currentTimeMillis() - start3);    
    }
}

作者说他运行了5次,我们也来运行5次看看结果:

方法 1次 2次 3次 4次 5次 AVG

非final方法

155

151 153 155 149 152.6
代码体 98 95 101 96 97 97.4
final方法 103 109 107 106 110 107

 

从图表不难看出,效率排序为直接运行代码体>final方法>非final方法,但是总感觉哪里不对,我在初高中时不知是化学课还是生物课,被灌输了一种实验方法即控制变量法,即保持其他条件不变,改变你要验证的变量。上面代码看上去已经应用了此方法,但是他忽略了一个因素,就是代码位置。所以我们变换一下代码位置,来重新运行一下:

public class Test2 {
    public static void getJava() {
        String str1 = "Java";
        String str2 = "final";
        for(int i =0;i < 10000;i++) {
            str1 += str2;
        }
    }
    public static final void getJavaFinal() {
        String str1 = "Java";
        String str2 = "final";
        for(int i =0;i < 10000;i++) {
            str1 += str2;
        }
    }
    
    public static void main(String[] args) {

                //直接调用代码体
        long start2 = System.currentTimeMillis();
        String str1 = "Java";
        String str2 = "final";
        for(int i =0;i < 10000;i++) {
            str1 += str2;
        }
        System.out.println(System.currentTimeMillis() - start2);
              //调用非final方法
        long start1 = System.currentTimeMillis();
        getJava();
        System.out.println(System.currentTimeMillis() - start1);
              //调用final方法
        long start3 = System.currentTimeMillis();
        getJavaFinal();
        System.out.println(System.currentTimeMillis() - start3);    
    }
}
方法 1次 2次 3次 4次 5次 AVG

代码体

155

153 152 159 155 154.8
非final方法 98 98 99 100 98 98.6
final方法 113 106 106 105 107 107.4

惊不惊喜,意不意外,这次非final方法成效率最高的了,看来跟调用位置有关,所以该如何验证final方法是否会效率更高呢,在同样的位置调用不同方法可验证,所以我试着运行了下面的两个例子:

public class Test1 {
    
    public static void getJava() {
        String str1 = "Java";
        String str2 = "final";
        for(int i =0;i < 10000;i++) {
            str1 += str2;
        }
    }
    public static void main(String[] args) {
               //调用非final方法
               long start1 = System.currentTimeMillis();
               getJava();
               System.out.println(System.currentTimeMillis() - start1);    
    }
}
public class Test2 {
    
    public static final void getJava() {
        String str1 = "Java";
        String str2 = "final";
        for(int i =0;i < 10000;i++) {
            str1 += str2;
        }
    }
    public static void main(String[] args) {
               //调用final方法
               long start1 = System.currentTimeMillis();
               getJava();
               System.out.println(System.currentTimeMillis() - start1);    
    }
}

技术分享图片

 Test1和Test2轮流执行,我一共手动循环执行了6次,从上面图表可以看出,统计上来看,final方法平均时间比非final方法还多几毫秒,非final方法执行时间比final方法短的共出现2次,final方法执行时间比非final方法短的共出现3次,差不多对半,就跟抛硬币一样,如果执行大量的重复实验,概率应该一样。综上验证在执行效率上来说final方法并不比非final高,从另一个角度也可以验证,大家可以用javap命令获得字节码,除了ACC_Flag外并未有其他区别,JVM应该也不会区别对待。还有另一个原因就是也许现在编译器不管有没有final,都会对以上方法优化。

使用final修饰方法,还有一个功能就是关闭动态绑定,这样方法调用就省去了动态连接过程,理论上效率会提高,但是实际上不会有什么改观。引用自《Java编程思想》。

所以综上final并不会提高运行效率,所以大家在使用final时候,要出于设计考虑而不是效率考虑。

  • 修饰变量

我觉得final在修饰变量上能发挥出更大的作用,下面我们就来讲讲final修饰成员所产生的影响:

1. 修饰基本类型时,变量值不可变

2. 修饰引用类型时,变量与引用地址绑定,不可赋值其他引用

举例如下:

class T {
    final int i = 9;
    final T1 t = new T1();
    void change() {
       // i = i + 1;  the final field T.i can not be assigned
       // t = new T();  the final field T.t can not be assigned
        t.name = "b";
    }
    class T1{
        String name = "a";
    }

我用的eclipse,改变i的值和t的值编译都不会通过,但是虽然 t 的引用值不能变,但是t引用所指的对象的内容却可以改变。

除了上面的作用,final变量还带来了其他影响,当final修饰实现了常量池技术的字符串时,编译器会对其进行优化,请看如下例程:

public class F {
    
    String a = "abc";
    String b = "a" + "bc";
    String c = "a";
    String d = c + "bc";
    final String e = "a";
    String g = e + "bc";
    
    public static void main(String[] args) {
        F f = new F();
        System.out.println(f.a == f.b);
        System.out.println(f.a == f.d);
        System.out.println(f.a == f.g);
        
    }
}

以上运行结果是什么大家可以先想一下自己的答案,运行结果为true,false,true. 为什么呢?我们来看一下编译后的字节码就很清楚了(只摘了关键部分):

技术分享图片

 ldc 指令是将常量池数据压入操作数栈,putfield指令是设置对象字段,aload_0指令是将栈桢中局部变量表中索引为0的引用类型值压入操作数栈。

    从上面可以看出成员变量a,b都指向常量池中的“abc”,而d是有StringBuilder类型的对象转化来的,相当于d是一个新对象的引用,d跟a是不一样的。

但是当给变量e加上final时,e与“bc”的连接操作直接被优化成字符串常量“abc”的加载,即g与a指向同一个值。

以上可以看出,使用final修饰值为字符串常量的变量时,在对此变量在连接操作时,相当于变量与值等同,即+e就等同于+"a"。

注意:此种效果只使用于变量直接指向字符串常量,如果这种形式 final String e = new String("e")将不会有此效果。

   

欢迎各位留言提出疑问,讨论交流!

Java基础认知--final关键字

原文:https://www.cnblogs.com/heisenburg/p/12150506.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!