使用cmd反汇编学会final关键字

1、final的基本用法

在java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。

  • 修饰类
    • 当用final修饰一个类时,表明这个类不能被继承。
    • final类的所有成员方法都会被隐式地指定为final方法
  • 修饰方法
    • 把方法锁定,以防任何继承类修改它的含义。因此想明确禁止该方法在子类中被覆盖的情况下才将方法设置为final。
    • final修饰方法中的参数,则该参数在函数中无法改变,但是这个参数与原来的变量也不时同一个变量,因为Java参数传递采用的是值传递。
  • 修饰变量
    • final成员变量表示常量,只能被赋值一次,赋值后不再改变。
      • 修饰一个基本数据类型
      • 修饰一个引用类型:不能改变引用的地址,但是可以改变地址指向的内容。
      • 修饰一个成员变量,必须要显示初始化
        • 在变量声明的时候初始化
        • 在声明变量时不赋值,在构造函数中赋值。

2、final变量与普通变量的区别

  • final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说,在用到final变量的地方,相当于直接访问这个常量,不需要在运行时确定(与C语言中的宏定义相似)。
  • 只有在编译期间能确切知道final变量值的情况下,编译器才会进行这样的优化(而不是在运行期间才知道)。

3、一道常见的面试题目

下面请看这个代码片段,并回答问题,可以帮助你理解:

String a = "Hello2" ;
String b = "Hello" ;
final String c = "Hello" ;
String d = b + "2" ;
String e = c + "2" ;
/*
问题:
1、判断(a==e) 为true还是false ? 答案 true
2、判断(a==d) 为true还是false ? 答案 false
3、判断("Hello2".equals(d))为true还是false ? 答案 true
4、判断("Hello2".equals(e))为true还是false ? 答案 true
*/

为什么a == e 答案是true,而a == d 答案却是false
首先需要掌握几个其他知识:

  • 我们要先了解java语言从代码到运行的过程
    • .java源代码文件 —— .class字节码文件 —— JVM 虚拟机运行
  • 使用cmd命令操作符和JVM指令集来看自己的Java文件经历了什么
    • 命令: java javac javap
  • java对基本数据类型和引用类型存储方式的不同
    • 对基本数据类型而言,是变量存储在栈中,直接从常量区把值压入到栈。
    • 对引用类型而言,是先在堆中创建一个对象,在栈中创建一个指针变量,指向java堆中的对象,然后再把常量区的值复制给这个对象。

a、先从一个更简单的例子入手

String str = new String(str");
String str1 = "str" ;
System.out.println( str1 == str ) ; //false

下面是我的探究过程
在cmd命令框中使用 javap -c命令来反汇编来看具体进行了哪些过程

b、对于 String str = “str” ;

cmd
利用JVM指令集可知:

  1. ldc指令: String型常量值先从常量池中推送至栈顶,使得需要赋值的变量值为该字符串在常量区的地址。(我这里理解的是str指向了常量区,而不是常量值复制给str这个变量)
  2. astore_1 指令:将栈顶引用型数值存入第二个本地变量(局部变量表)
  3. return指令:从当前方法返回void。

c、对于 String str = new String(“str”) ;

在这里插入图片描述

  1. new指令:在java堆上为 String 创建一个对象并分配内存空间,并将其地址压入操作数栈顶。
  2. dup指令:复制栈顶数值并将复制值压入栈顶,也就是说此时操作数栈上有连续相同的两个对象地址。
  3. ldc指令:将String型常量从常量池中推送至栈顶,把常量区地址复制给栈顶地址指向的对象。
  4. invokespecial指令:调用实例初始化方法 < init > : () v,注意这个方法是一个实例方法,所以需要从操作数栈顶弹出一个String对象的引用,也就是说这一步会弹出一个之前入栈的对象地址。 私有方法。
  5. astore_1 指令:将栈顶引用型数值(对象在堆里的地址)存入到局部变量表。
  6. return指令:从当前方法返回void 结束方法

对比一下就会发现,进行的操作完全不一样。直接把常量值赋给str,则变量str就是指向常量区的地址,而new出来的,会先在java堆里创建一个对象,str为指向该对象的地址,显而易见,这两个地址一定不一样。

再看这道面试题

由于final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。
所以 String e = c + “2” ; 这个语句等效于 String e = “Hello2” ; e 就是指向常量"Hello2"的地址,而 String d = b + “2” ; b 不能直接看做常量,所以编译期间并不能知道它的确切值,所以d就不是指向常量"Hello2"的地址(应该会在java堆中创建一个对象,可以自行在cmd上进行试验)。
所以
(a == e) 为true还是false ? 答案 true
(a == d) 为true还是false ? 答案 false
那为什么
(“Hello2”.equals(d))为true还是false ? 答案 true
(“Hello2”.equals(e))为true还是false ? 答案 true
这是与equals函数的机制有关,可自行去看源码。

这就是final关键字!
如有问题,欢迎指正交流。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.Toser

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值