Java中的关键字之final详解

 更新时间:2024年01月02日 09:04:33   作者:luffylv  
这篇文章主要介绍了Java中的关键字之final详解,final关键字算是个高频的java基础问题了,面试官可能会问说说final,final修饰的抽象类能够被继承吗等等,下面汇总关于final关键字的知识点,需要的朋友可以参考下

一、final关键字概述

final有最终的、不可变更的意思,它可用于修饰类、变量和方法。用于表示它修饰的类、方法和变量不可改变。

总结起来是如下三点:

  • final修饰变量——表示该变量一旦获得了初始值就不可被改变,即不能够被重新赋值。final即可以修饰成员变量(包括类变量和实例变量),也可以修饰局部变量形参。
  • final修饰方法——final修饰的方法不可被重写。
  • final修饰类——final修饰的类不可以被继承。

二、final修饰变量

1、final修饰成员变量

Java语法规定:final修饰的成员变量必须由程序员显示地指定初始值。

final修饰的类变量、实例变量能指定初始值的地方如下:

  • 类变量:必须在静态初始化块中指定初始值或声明该类变量时指定初始值,而且只能在这两个地方的其中之一指定。
  • 实例变量:必须在普通初始化块、声明该实例变量或构造器中指定初始值,而且只能在三个地方的其中之一指定。

若成员变量的值未在上面任何一处指定初始值,那么这些成员变量的值将一直是系统默认分配的0、'\u0000'、false或null,这些成员变量也就完全失去了存在的意义。

final修饰成员变量的例子如下:

public class FinalVariable {
    // 定义成员变量时指定默认值
    final int a = 2;
    final String str;
    final int b;
    final static float c;
    // 在普通初始化块中,给没有指定默认值的实例变量指定初始值
    {
        str = "final";
        // 声明实例变量a时已经指定了默认值,因此不能为a重新赋值
        // a = 66;
    }
    // 在静态初始化块中,给没有指定默认值的类变量指定初始值
    static {
        c = 6.6f;
    }
    // 在构造器中,可对没有指定默认值,有没有在普通初始化中
    // 指定初始值的实例变量指定初始值
    public FinalVariable() {
        b = 8;
        // 已在普通初始化块中为str指定了初始值,因此在构造器中不能对str重新赋值
        // str = "change";
    }
    public void change() {
        // 不能再普通方法中为final修饰的成员变量指定初始值,也不能重赋值
        // c = 8.8f;
    }
}

2、final修饰局部变量

系统不会对局部变量进行初始化,局部变量必须被显示初始化。因此使用final修饰局部变量时,即可以在定义时指定默认值,也可以不指定默认值。如果在定义时没有指定默认值,则可以在后面代码中对该final变量赋初始值,但只能一次,不能重复赋值;如果在定义时已经指定默认值,则后面代码中不能再对该变量赋值。

final修饰局部变量的例子如下:

public class FinalLocalVariable {
    // 不能对final修饰的形参赋值
    public void test(final int a) {
        // 因为形参在调用该方法时,已经由系统根据传入的参数来完成初始化,
        // 因此使用final修饰的形参不能被赋值。
        // a = 6;
    }
    public static void main(String[] args) {
        // 定义final局部变量时指定默认值,则str变量无法重新赋值
        final String str = "final";
        // 定义final局部变量时没有指定默认值,则该局部变量可被赋值一次
        final int b;
        b = 8;
    }
}

3、final修饰基本类型变量和引用类型变量的区别

当使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。但对于引用类型变量而言,它保存的仅仅是一个引用,final只保证这个引用变量所引用的地址不会改变,即一直引用同一个对象,但可以改变引用类型变量所引用的对象的内容。

下面以final修饰数组和Person对象举例:

import lombok.Data;
import java.util.Arrays;
@Data
class Person {
    private int age;
    public Person(int age) {
        this.age = age;
    }
}
public class FinalReference {
    public static void main(String[] args) {
        // final修饰数组变量,iArr是一个引用变量
        final int[] iArr = {5, 6, 12, 9};
        System.out.println(Arrays.toString(iArr));
        // 对数组元素进行排序
        Arrays.sort(iArr);
        System.out.println(Arrays.toString(iArr));
        // 对数组元素赋值
        iArr[2] = -8;
        System.out.println(Arrays.toString(iArr));
        // 不能对iArr重新赋值
        // iArr = null;
        // final修饰Person变量,p是一个引用变量
        final Person p = new Person(45);
        // 改变Person对象的age实例变量
        p.setAge(23);
        System.out.println(p.getAge());
        // 不能对p重新赋值
        // p = null;
    }
}

4、final变量的“宏替换”

final修饰符的一个重要用途就是定义“宏变量”。对一个final变量来说,不管它是类变量、实例变量还是局部变量,只要该变量满足三个条件,这个final变量就不再是一个变量,而是相当于一个直接量。

  1. 使用final修饰符修饰;
  2. 在定义该final变量时指定了初始值(该初始值可以是直接量,也可以是基本的算数表达式或字符串连接运算,不可以是普通变量或调用方法);
  3. 该初始值可以在编译时就被确定下来。

满足以上三个条件的final变量本质上就是一个“宏变量”,编译器会把程序中所有用到该变量的地方直接替换成该变量的值。

下面看下final变量作为“宏变量”的例子:

上面代码a是final修饰的局部变量,并且在定义时指定了初始值5,对于程序来说,变量a其实根本不存在,实际上编译器在编译阶段,将System.out.println(a);转换为System.out.println(5);。

final变量的b和str1在定义分别通过基本的算数表达式和字符串连接运算指定了初始值。因此b和str1是“宏变量”。由于str1是“宏变量”,它将被直接替换成“final宏变量888”,str1将指向的是字符串常量池中的“final宏变量888”。因此第一个判断为true。而str2需要调用String类的方法,因此编译器无法在编译时确定str2的值,str2不会被当成“宏变量”处理。因此第二个判断为false。

s2变量引用的字符串可以在编译时就确定下来,因此时直接引用常量池中已有的“final宏变量”。因此第三个判断相等。对于s3而言,它的值由st1和st2进行连接运算后得到。由于st1、st2只是两个普通变量,编译期不会执行“宏替换”,因此编译器无法在编译时确定s3的值,也就无法让s3指向字符串常量池中缓存的“final宏变量”,所以第4个判断为false。而s4是由st3和st4进行连接运算后得到,st3和st4是两个宏变量,因此可以执行“宏替换”,编译器可以编译阶段确定s4的值,使其指向字符串常量池中缓存的“final宏变量”,所以第5个判断为true。实际上s2和s4是等价的。

三、final修饰方法

 final修饰的方法不可被重写,如果出于某些原因,不希望子类重写父类某个方法,则可以使用final修饰该方法。

Java提供的Object类里就有一个final方法:getClass(),因为Java不希望任何类重写这个方法,所以使用final把这个方法密封起来。但对于该类提供的toStirng()和equals()方法,都允许子类重写,因此没有使用final修饰他们。

四、final修饰类

final修饰的类不可以有子类,例如java.lang.Math类就是一个final类,他不可以有子类。

当子类继承父类时,将可以访问到父类内部数据,并可以通过重写父类方法来改变父类方法的实现细节,这可能导致一些不安全的因素。为了保证某个类不可被继承,则可以使用final修饰这个类。

到此这篇关于Java中的关键字之final详解的文章就介绍到这了,更多相关final关键字内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Idea中指定xml文件失效的解决过程

    Idea中指定xml文件失效的解决过程

    最近在开发的过程中遇到了一个奇怪的问题,下面这篇文章主要给大家介绍了关于Idea中指定xml文件失效的解决过程,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2023-01-01
  • JAVA设计模式---单例模式你知道吗

    JAVA设计模式---单例模式你知道吗

    这篇文章主要给大家介绍了关于Java单例模式,文中通过示例代码介绍的非常详细,对大家学习或者使用Java具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2021-09-09
  • Idea中Springboot热部署无效问题解决

    Idea中Springboot热部署无效问题解决

    这篇文章主要介绍了Idea中Springboot热部署无效问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • Java方法的返回值及注意事项小结

    Java方法的返回值及注意事项小结

    这篇文章主要介绍了Java方法的返回值及注意事项,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-04-04
  • SpringCloud开发课程查询功能

    SpringCloud开发课程查询功能

    这篇文章主要介绍了SpringCloud开发课程查询功能,本文通过图文实例相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • 浅谈Java代理(jdk静态代理、动态代理和cglib动态代理)

    浅谈Java代理(jdk静态代理、动态代理和cglib动态代理)

    下面小编就为大家带来一篇浅谈Java代理(jdk静态代理、动态代理和cglib动态代理)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-01-01
  • 基于UncategorizedSQLException异常处理方案

    基于UncategorizedSQLException异常处理方案

    这篇文章主要介绍了基于UncategorizedSQLException异常处理方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • Java数据结构与算法学习之双向链表

    Java数据结构与算法学习之双向链表

    双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。本文将为大家详细介绍双向链表的特点与使用,需要的可以参考一下
    2021-12-12
  • SpringBoot 如何添加容器启动的初始化逻辑的操作方法

    SpringBoot 如何添加容器启动的初始化逻辑的操作方法

    这篇文章主要介绍了SpringBoot 如何添加容器启动的初始化逻辑,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • SpringBoot集成Hadoop实现文件的上传和下载功能

    SpringBoot集成Hadoop实现文件的上传和下载功能

    Spring Hadoop简化了Apache Hadoop,提供了一个统一的配置模型以及简单易用的API来使用HDFS、MapReduce、Pig以及Hive,这篇文章主要介绍了SpringBoot集成Hadoop实现文件的上传和下载,需要的朋友可以参考下
    2024-07-07

最新评论