final关键字
1、final的基本用法
在java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。
- 修饰类
- 当用final修饰一个类时,表明这个类不能被继承。
- final类的所有成员方法都会被隐式地指定为final方法
- 修饰方法
- 把方法锁定,以防任何继承类修改它的含义。因此想明确禁止该方法在子类中被覆盖的情况下才将方法设置为final。
- final修饰方法中的参数,则该参数在函数中无法改变,但是这个参数与原来的变量也不时同一个变量,因为Java参数传递采用的是值传递。
- 修饰变量
- final成员变量表示常量,只能被赋值一次,赋值后不再改变。
- 修饰一个基本数据类型
- 修饰一个引用类型:不能改变引用的地址,但是可以改变地址指向的内容。
- 修饰一个成员变量,必须要显示初始化
- 在变量声明的时候初始化
- 在声明变量时不赋值,在构造函数中赋值。
- 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” ;
利用JVM指令集可知:
- ldc指令: String型常量值先从常量池中推送至栈顶,使得需要赋值的变量值为该字符串在常量区的地址。(我这里理解的是str指向了常量区,而不是常量值复制给str这个变量)
- astore_1 指令:将栈顶引用型数值存入第二个本地变量(局部变量表)
- return指令:从当前方法返回void。
c、对于 String str = new String(“str”) ;
- new指令:在java堆上为 String 创建一个对象并分配内存空间,并将其地址压入操作数栈顶。
- dup指令:复制栈顶数值并将复制值压入栈顶,也就是说此时操作数栈上有连续相同的两个对象地址。
- ldc指令:将String型常量从常量池中推送至栈顶,把常量区地址复制给栈顶地址指向的对象。
- invokespecial指令:调用实例初始化方法 < init > : () v,注意这个方法是一个实例方法,所以需要从操作数栈顶弹出一个String对象的引用,也就是说这一步会弹出一个之前入栈的对象地址。 私有方法。
- astore_1 指令:将栈顶引用型数值(对象在堆里的地址)存入到局部变量表。
- 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关键字!
如有问题,欢迎指正交流。