Java基础5-一文了解final关键字的特性、使用方法,以及实现原理(一)

简介: Java基础5-一文了解final关键字的特性、使用方法,以及实现原理(一)

final关键字在java中使用非常广泛,可以申明成员变量、方法、类、本地变量。一旦将引用声明为final,将无法再改变这个引用。final关键字还能保证内存同步,本博客将会从final关键字的特性到从java内存层面保证同步讲解。这个内容在面试中也有可能会出现。

final使用

final变量

final变量有成员变量或者是本地变量(方法内的局部变量),在类成员中final经常和static一起使用,作为类常量使用。其中类常量必须在声明时初始化,final成员常量可以在构造函数初始化。

public class Main {
    public static final int i; //报错,必须初始化 因为常量在常量池中就存在了,调用时不需要类的初始化,所以必须在声明时初始化
    public static final int j;
    Main() {
        i = 2;
        j = 3;
    }
}
复制代码

就如上所说的,对于类常量,JVM会缓存在常量池中,在读取该变量时不会加载这个类。

public class Main {
    public static final int i = 2;
    Main() {
        System.out.println("调用构造函数"); // 该方法不会调用
    }
    public static void main(String[] args) {
        System.out.println(Main.i);
    }
}
复制代码

final修饰基本数据类型变量和引用

@Test
public void final修饰基本类型变量和引用() {
    final int a = 1;
    final int[] b = {1};
    final int[] c = {1};
//  b = c;报错
    b[0] = 1;
    final String aa = "a";
    final Fi f = new Fi();
    //aa = "b";报错
    // f = null;//报错
    f.a = 1;
}复制代码

final方法表示该方法不能被子类的方法重写,将方法声明为final,在编译的时候就已经静态绑定了,不需要在运行时动态绑定。final方法调用时使用的是invokespecial指令。

class PersonalLoan{
    public final String getName(){
        return"personal loan”;
    }
}

class CheapPersonalLoan extends PersonalLoan{
    @Override
    public final String getName(){
        return"cheap personal loan";//编译错误,无法被重载
    }

    public String test() {
        return getName(); //可以调用,因为是public方法
    }
}
复制代码

final类

final类不能被继承,final类中的方法默认也会是final类型的,java中的String类和Integer类都是final类型的。

class Si{
    //一般情况下final修饰的变量一定要被初始化。
    //只有下面这种情况例外,要求该变量必须在构造方法中被初始化。
    //并且不能有空参数的构造方法。
    //这样就可以让每个实例都有一个不同的变量,并且这个变量在每个实例中只会被初始化一次
    //于是这个变量在单个实例里就是常量了。
    final int s ;
    Si(int s) {
        this.s = s;
    }
}
class Bi {
    final int a = 1;
    final void go() {
        //final修饰方法无法被继承
    }
}
class Ci extends Bi {
    final int a = 1;
//        void go() {
//            //final修饰方法无法被继承
//        }
}
final char[]a = {'a'};
final int[]b = {1};复制代码
final class PersonalLoan{}

class CheapPersonalLoan extends PersonalLoan {  //编译错误,无法被继承 
}
复制代码
@Test
public void final修饰类() {
    //引用没有被final修饰,所以是可变的。
    //final只修饰了Fi类型,即Fi实例化的对象在堆中内存地址是不可变的。
    //虽然内存地址不可变,但是可以对内部的数据做改变。
    Fi f = new Fi();
    f.a = 1;
    System.out.println(f);
    f.a = 2;
    System.out.println(f);
    //改变实例中的值并不改变内存地址。

    Fi ff = f;
    //让引用指向新的Fi对象,原来的f对象由新的引用ff持有。
    //引用的指向改变也不会改变原来对象的地址
    f = new Fi();
    System.out.println(f);
    System.out.println(ff);
}复制代码

final关键字的知识点

  1. final成员变量必须在声明的时候初始化或者在构造器中初始化,否则就会报编译错误。final变量一旦被初始化后不能再次赋值。
  2. 本地变量必须在声明时赋值。 因为没有初始化的过程
  3. 在匿名类中所有变量都必须是final变量。
  4. final方法不能被重写, final类不能被继承
  5. 接口中声明的所有变量本身是final的。类似于匿名类
  6. final和abstract这两个关键字是反相关的,final类就不可能是abstract的。
  7. final方法在编译阶段绑定,称为静态绑定(static binding)。
  8. 将类、方法、变量声明为final能够提高性能,这样JVM就有机会进行估计,然后优化。

final方法的好处:

  1. 提高了性能,JVM在常量池中会缓存final变量
  2. final变量在多线程中并发安全,无需额外的同步开销
  3. final方法是静态编译的,提高了调用速度
  4. final类创建的对象是只可读的,在多线程可以安全共享
    5.

final关键字的最佳实践

final的用法

1、final 对于常量来说,意味着值不能改变,例如 final int i=100。这个i的值永远都是100。但是对于变量来说又不一样,只是标识这个引用不可被改变,例如 final File f=new File("c:\test.txt");

那么这个f一定是不能被改变的,如果f本身有方法修改其中的成员变量,例如是否可读,是允许修改的。有个形象的比喻:一个女子定义了一个final的老公,这个老公的职业和收入都是允许改变的,只是这个女人不会换老公而已。

关于空白final

final修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量。

 另外,final变量定义的时候,可以先声明,而不给初值,这中变量也称为final空白,无论什么情况,编译器都确保空白final在使用之前必须被初始化。 但是,final空白在final关键字final的使用上提供了更大的灵活性,为此,一个类中的final数据成员就可以实现依对象而有所不同,却有保持其恒定不变的特征。

public class FinalTest { 
final int p; 
final int q=3; 
FinalTest(){ 
p=1; 
} 
FinalTest(int i){ 
p=i;//可以赋值,相当于直接定义p 
q=i;//不能为一个final变量赋值 
} 
} 复制代码

final内存分配

刚提到了内嵌机制,现在详细展开。要知道调用一个函数除了函数本身的执行时间之外,还需要额外的时间去寻找这个函数(类内部有一个函数签名和函数地址的映射表)。所以减少函数调用次数就等于降低了性能消耗。

final修饰的函数会被编译器优化,优化的结果是减少了函数调用的次数。如何实现的,举个例子给你看:

public class Test{ 
final void func(){System.out.println("g");}; 
public void main(String[] args){ 
for(int j=0;j<1000;j++)   
func(); 
}} 
经过编译器优化之后,这个类变成了相当于这样写: 
public class Test{ 
final void func(){System.out.println("g");}; 
public void main(String[] args){ 
for(int j=0;j<1000;j++)  
{System.out.println("g");} 
}} 
复制代码

看出来区别了吧?编译器直接将func的函数体内嵌到了调用函数的地方,这样的结果是节省了1000次函数调用,当然编译器处理成字节码,只是我们可以想象成这样,看个明白。

不过,当函数体太长的话,用final可能适得其反,因为经过编译器内嵌之后代码长度大大增加,于是就增加了jvm解释字节码的时间。

在使用final修饰方法的时候,编译器会将被final修饰过的方法插入到调用者代码处,提高运行速度和效率,但被final修饰的方法体不能过大,编译器可能会放弃内联,但究竟多大的方法会放弃,我还没有做测试来计算过。

下面这些内容是通过两个疑问来继续阐述的

使用final修饰方法会提高速度和效率吗

见下面的测试代码,我会执行五次:

public class Test   
{   
    public static void getJava()   
    {   
        String str1 = "Java ";   
        String str2 = "final ";   
        for (int i = 0; i < 10000; i++)   
        {   
            str1 += str2;   
        }   
    }   
    public static final void getJava_Final()   
    {   
        String str1 = "Java ";   
        String str2 = "final ";   
        for (int i = 0; i < 10000; i++)   
        {   
            str1 += str2;   
        }   
    }   
    public static void main(String[] args)   
    {   
        long start = System.currentTimeMillis();   
        getJava();   
        System.out.println("调用不带final修饰的方法执行时间为:" + (System.currentTimeMillis() - start) + "毫秒时间");   
        start = System.currentTimeMillis();   
        String str1 = "Java ";   
        String str2 = "final ";   
        for (int i = 0; i < 10000; i++)   
        {   
            str1 += str2;   
        }   
        System.out.println("正常的执行时间为:" + (System.currentTimeMillis() - start) + "毫秒时间");   
        start = System.currentTimeMillis();   
        getJava_Final();   
        System.out.println("调用final修饰的方法执行时间为:" + (System.currentTimeMillis() - start) + "毫秒时间");   
    }   
}  
复制代码
结果为: 
第一次: 
调用不带final修饰的方法执行时间为:1732毫秒时间 
正常的执行时间为:1498毫秒时间 
调用final修饰的方法执行时间为:1593毫秒时间 
第二次: 
调用不带final修饰的方法执行时间为:1217毫秒时间 
正常的执行时间为:1031毫秒时间 
调用final修饰的方法执行时间为:1124毫秒时间 
第三次: 
调用不带final修饰的方法执行时间为:1154毫秒时间 
正常的执行时间为:1140毫秒时间 
调用final修饰的方法执行时间为:1202毫秒时间 
第四次: 
调用不带final修饰的方法执行时间为:1139毫秒时间 
正常的执行时间为:999毫秒时间 
调用final修饰的方法执行时间为:1092毫秒时间 
第五次: 
调用不带final修饰的方法执行时间为:1186毫秒时间 
正常的执行时间为:1030毫秒时间 
调用final修饰的方法执行时间为:1109毫秒时间 

由以上运行结果不难看出,执行最快的是“正常的执行”即代码直接编写,而使用final修饰的方法,不像有些书上或者文章上所说的那样,速度与效率与“正常的执行”无异,而是位于第二位,最差的是调用不加final修饰的方法。 
复制代码

由以上运行结果不难看出,执行最快的是“正常的执行”即代码直接编写,而使用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[]{""};就会报错了。

Java基础5-一文了解final关键字的特性、使用方法,以及实现原理(二):https://developer.aliyun.com/article/1535640

目录
打赏
0
0
0
0
93
分享
相关文章
Java HashMap详解及实现原理
Java HashMap是Java集合框架中常用的Map接口实现,基于哈希表结构,允许null键和值,提供高效的存取操作。它通过哈希函数将键映射到数组索引,并使用链表或红黑树解决哈希冲突。HashMap非线程安全,多线程环境下需注意并发问题,常用解决方案包括ConcurrentHashMap和Collections.synchronizedMap()。此外,合理设置初始化容量和加载因子、重写hashCode()和equals()方法有助于提高性能和避免哈希冲突。
63 17
Java HashMap详解及实现原理
|
1月前
|
Volatile关键字与Java原子性的迷宫之旅
通过合理使用 `volatile`和原子操作,可以在提升程序性能的同时,确保程序的正确性和线程安全性。希望本文能帮助您更好地理解和应用这些并发编程中的关键概念。
42 21
|
20天前
|
Java静态代码块深度剖析:机制、特性与最佳实践
在Java中,静态代码块(或称静态初始化块)是指类中定义的一个或多个`static { ... }`结构。其主要功能在于初始化类级别的数据,例如静态变量的初始化或执行仅需运行一次的初始化逻辑。
35 4
课时8:Java程序基本概念(标识符与关键字)
课时8介绍Java程序中的标识符与关键字。标识符由字母、数字、下划线和美元符号组成,不能以数字开头且不能使用Java保留字。建议使用有意义的命名,如student_name、age。关键字是特殊标记,如蓝色字体所示。未使用的关键字有goto、const;特殊单词null、true、false不算关键字。JDK1.4后新增assert,JDK1.5后新增enum。
Java中的this关键字详解:深入理解与应用
本文深入解析了Java中`this`关键字的多种用法
252 9
利用 Java 代码获取淘宝关键字 API 接口
在数字化商业时代,精准把握市场动态与消费者需求是企业成功的关键。淘宝作为中国最大的电商平台之一,其海量数据中蕴含丰富的商业洞察。本文介绍如何通过Java代码高效、合规地获取淘宝关键字API接口数据,帮助商家优化产品布局、制定营销策略。主要内容包括: 1. **淘宝关键字API的价值**:洞察用户需求、优化产品标题与详情、制定营销策略。 2. **获取API接口的步骤**:注册账号、申请权限、搭建Java开发环境、编写调用代码、解析响应数据。 3. **注意事项**:遵守法律法规与平台规则,处理API调用限制。 通过这些步骤,商家可以在激烈的市场竞争中脱颖而出。
【零基础学Java】—final关键字与四种用法(二十九)
【零基础学Java】—final关键字与四种用法(二十九)
|
10月前
|
Java中final关键字(看这篇就够了)
Java中final关键字(看这篇就够了)
112 0
在Java中,关于final、static关键字与方法的重写和继承【易错点】
在Java中,关于final、static关键字与方法的重写和继承【易错点】
66 5
|
8月前
|
Java面向对象 ( 多态 | final关键字 | 接口 )
Java面向对象 ( 多态 | final关键字 | 接口 )