专栏首页带你撸出一手好代码深入理解final关键字

深入理解final关键字

通常我们对Java中final关键字的理解是“用final修饰的变量是不可变的”,如果尝试对final变量多次赋值,编译器将报错。似乎final的作用就是保证变量不可变,这没有错,但是如果我们在Java中灵活应用final的被修饰目标不可变的特性,往往能发掘出很多令人意想不到的效果,而非仅仅保证变量不可变这么粗浅而已。下面我们来说说final关键字的多重用法

用final修饰普通变量通常分为两种情况,修饰普通基本类变量和修饰引用类型变量,也就是对象类型变量。

修饰普通基本类型变量最能清楚直白的表现出final的作用,它能使变量的值无法改变,因为变量不能再次被赋值。

    final int myInt = 1;
    myInt = 2;

运行此代码,编译器会报错:Error:(20, 9) java: 无法为最终变量myInt分配值

但我们使用final修饰引用类型变量时,我们可以保证变量不能被再次赋值, 但我们无法保证对象值的改变。

   final StringBuilder sb 
       = new StringBuilder("Java");
    sb.append("Script");
    System.out.println(sb); 
    //resultJavaScript

如上代码所示, 虽然我们用final修饰变量,但仍旧无法阻止变量内在值的改变。 使用final能保证变量不能改变引用的目标,却不能保证变量所引用的目标本身的变化。因为对于基本类型,我们可以把变量看作是变量值的本身;而对于引用类型变量,变量和变量的值需要区分看待,它们只是以某种方式被关联起来了而已,事实上它们是不同的东西,所以final无法同时作用于两者身上。

Java不支持原生常量,在Java种也没有定义常量的const关键字。然而, 我们可以使用final关键字间接的实现常量。

public static final int CONST_ONE = 1;

public static final int CONST_TWO = 2;

常量是全局的、不可变的,因此我们同时使用static和final来修饰变量,就能达到定义常量的效果。 常量名通常全由大写字母组成。

final可以保证实例变量必须被初始化,这点特性能减少代码出错几率,如令所有Java程序员头疼的NPE

public class Main {
    private String name ;
    @Override
‍    public String toString() {
        return name;
    }
‍
    public static void main(String[] arg)throws Exception {
        Main main = new Main();
       System.out.println(main.toString().toLowerCase());
    }
}

以上代码因为没给name赋值,代码在运行起将报NPE异常。假如我们使用final修饰name变量,代码将无法通过编译,因为Java语法规定,final变量在使用前必须被初始化,因此我们必须在构造函数中初始化name变量,这样能百分百保证我们使用的name变量不会是null。

public class Main {
‍    private final String name;

    public Main(String name) {
        this.name = name;
    }
    
    @Override
    public String toString() {
        return name;
    }

    public static void main(String[] arg)throws Exception {
        Main main = new Main("Java");
           System.out.println(main.toString().toLowerCase());
        }
    }
}

final不仅可以修饰变量,还可以修饰方法和类。

如果我们用final修饰方法,假如方法所属的类被继承,方法将不能在子类中被重写。

class SuperClass{
    protected final String getName() {
        return “supper class”;
    }
    
    @Override
    public String toString() {
        return getName();
    }
}

classSubClass extends SuperClass{
   protected String getName() {
       return “sub class”;
   }
}  

以上代码无法通过编译,编译器报错

Error:(30,22) java: SubClass中的getName()无法覆盖SuperClass中的getName() 被覆盖的方法为final

因为SuperClass的getName方法被修饰为final,因此在子类中无法被重写。

通常,我们不希望方法在被继承时重写,可以用private修饰,因为这样方法的可见性被限制于方法所在的类中。但是,有时候我们需要公开方法,却又不想方法被重写,此时用final修饰方法就有用武之地了。

然而,这时又引出了另外一个问题,假如我们使用final修饰private方法,是否有实际意义。 事实上,在现代的jdk中,这么做是没有任何意义的,因为private无法被继承,自然也不存在继承时被修改的问题。 但是在早期的Java版本中,final修饰private方法的作用是告知编译器,这个方法在编译时需要内联处理。这个特性在现代jdk中已经被抛弃。

当用final修饰类时, 表示此类是密封的, 无法被继承。从Java源码中可知,我们最常用的String类便是一个final类。

在haskell、F#之类的函数时语言中,变量值默认就是不可变的,仿佛如Java变量默认就是final一样, 这种特性能极大的减少代码出错的几率。许多极难排查的代码错误,都是由于状态改变引起的,即变量值的改变引起的。 如果从源头杜绝, 就可以从根本上消灭所有这类错误,函数式语言也是基于此考虑才把变量不可变作为语言的默认特性,所以函数式编程是无状态的, 这是被证明优点多余缺点的一种特性。

此外, 变量的值一旦不可变,在多线程编程的环境下能保证线程安全,因为变量值不可变,也就不存在多个线程同时竞争资源的问题,代码自然是线程安全的。如String类, 就是以这种模式实现的, 当我们看到某个字符串被改变, 其实只是生成一个新的字符串而已,旧的字符串并没有被修改。当然,这样做会造成一定的性能问题, 两者间如何权衡,需要开发者根据实际情况考虑。

根据现代编程的指导原则, 在Java种定义的任何变量,默认都要加上final关键字, 这么做虽然反直觉,却有好处。退一万步说,至少能让代码的阅读者了解,变量是不可变的, 我们不用担心它会产生副作用。

本文分享自微信公众号 - 带你撸出一手好代码(gh_afab56b37671),作者:陈大侠

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-01-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • kotlin语言使用初体验(一)

    居说谷歌新认的干儿子kotlin极为受宠,隐隐有替代Java在 android平台老大位置的趋势。kotlin有谷歌撑腰,加上自己的底子也厚,再之与Java无缝...

    用户1608022
  • 正则表达式「^」符号的正确理解方式

    「^」这个符号在正则表达式的中的应用相信是所有程序员都掌握的, 因为它是正则表达式中最基础最常用的知识点。 它在正则表达式中表示两种不同的意义 01 表示匹配一...

    用户1608022
  • JavaScript对象的呼叫转移

    声明:此文以通俗易懂的模式讲解JavaScript语言中call、apply运行原理。 非业内人士或未成年人请点左上角按扭及时离开以避免走火入魔。 事实上类似于...

    用户1608022
  • Java Review (十二、面向对象----final 修饰符)

    对于 final 修饰的成员变量而言,一旦有了初始值,就不能被重新赋值,如果既没有在定义成员变量时指定初始值,也没有在初始化块、构造器中为成员变量指定初始值,那...

    三分恶
  • JDK1.9-final关键字

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

    cwl_java
  • 学习笔记-JAVA-final关键字考点

    final可以修饰成员变量,也可以修饰局部变量、形参。final变量一旦获得初始值之后,final的变量就不能被重新赋值。

    陈黎栋
  • 面试题11(谈谈final、finally、finalize的区别)

    考点:考察求职者对这3个java关键字的理解和区分 出现频率:★★★★ 【面试题解析】带有 final修饰符的类是不可派生的。在Java核心APⅠ中,有许多应用...

    Java学习
  • 浅析Java中的final关键字

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

    Spark学习技巧
  • 浅析Java中的final关键字

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

    Java团长
  • 面试题11(谈谈final、finally、finalize的区别)

    考点:考察求职者对这3个java关键字的理解和区分 出现频率:★★★★ 【面试题解析】带有 final修饰符的类是不可派生的。在Java核心APⅠ中,有许多应用...

    Java学习

扫码关注云+社区

领取腾讯云代金券