final关键字
一、介绍
Java中的final关键字使用的地方很多,很多jdk的源码就包含这样的被final修饰的方法、类、或者变量,今天我来深入学习一下final关键字。
二、final关键字的使用
1、数据
对于基本数据类型,final会使其数值恒定不变
对于引用数据类型,一旦其被指向一个对象,final会使它无法改变它的指向(注意无法改变的这个引用对象的指向,但对象本身的数据是可以改的,Java并未提供一个无法被修改的对象),如:
1 class y{ 2 final int x = 1; 3 public void zz(){ 4 x=2;//错误 5 final Object p = new Object(); 6 p = new Object();//错误 7 } 8 }
在第四行和第六行中会出现错误,即被final修饰的数值和引用变量是无法再次赋值的
2、方法
使用final方法的原因有两个,第一个原因是把方法锁定,以防止任何继承类修改它的含义,这是出于设计的考虑,想要确保在继承中使方法行为保持不变,并且不会被覆盖。
过去使用final方法的第二个原因是效率,在早期实现中,如果一个方法被指明为final,就是同意编译器将针对该方法的所有调用都转为内嵌调用,现在已经不需要了。
只有在想要明令禁止覆盖才会将方法设为final,这似乎与private有点相似,因为类中所有的private方法都会被隐式的指定为final,所以private方法也是无法被覆盖的
3、类
当将某个类的整体定义为final时(通过final关键字置于它的定义之前),就表明了你拥有不打算继承该类,而且同样不允许别人这么做,换句话说,你对该类的设计永远不需要任何变动,或者出于安全的考虑,你不希望它有子类,由于类被定义为final,类所含的方法会被隐式的指定为final,如:
1 final class y{ 2 public void p(){ 3 4 } 5 } 6 class x extends y {//直接报错 7 public void x1(){ 8 System.out.println("this is x"); 9 } 10 11 @Override 12 public void p() { 13 super.p(); 14 } 15 }
在实际的设计中不要轻易的将类设为final,因为你觉得不会需要被继承的类可能只是暂时的
三、final的一些需要注意的点
1、被final修饰的引用变量指向的对象能修改吗?
看下面的例子:
1 final class y{ 2 int xx = 0; 3 public void p(){ 4 5 } 6 } 7 class x { 8 public void zz(){ 9 final y xxx = new y(); 10 xxx.xx = 2; 11 } 12 }
可以看到,我们可以通过final引用变量来修改对象的值,所以对象是可以修改的,但final引用变量的指向不能被修改
2、类的final变量和普通变量的区别
注意这里说的是类变量,当用final作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值了。
那么final变量和普通变量的区别在哪呢?看下面这个例子:
1 public class Test { 2 public static void main(String[] args) { 3 String a = "hello2"; 4 final String b = "hello"; 5 String d = "hello"; 6 String c = b + 2; 7 String e = d + 2; 8 System.out.println((a == c)); 9 System.out.println((a == e)); 10 } 11 }
输入结果:
true
false
为什么第一个比较结果为true,而第二个比较结果为fasle。这里面就是final变量和普通变量的区别了,当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。这种和C语言中的宏替换有点像。因此在上面的一段代码中,由于变量b被final修饰,因此会被当做编译器常量,所以在使用到b的地方会直接将变量b 替换为它的值。而对于变量d的访问却需要在运行时通过链接来进行。想必其中的区别大家应该明白了,不过要注意,只有在编译期间能确切知道final变量值的情况下,编译器才会进行这样的优化,比如下面的这段代码就不会进行优化:
1 public class Test { 2 public static void main(String[] args) { 3 String a = "hello2"; 4 final String b = getHello(); 5 String c = b + 2; 6 System.out.println((a == c)); 7 8 } 9 10 public static String getHello() { 11 return "hello"; 12 } 13 }
输出:
false
由于编译阶段无法确定final变量的值,所以就无法当成编译常量
3、static和final的对比
static作用于成员变量是说明这个变量只有一个副本,而final的任务则是保持它不变,下面例子就可以说明:
1 public class Test { 2 public static void main(String[] args) { 3 MyClass myClass1 = new MyClass(); 4 MyClass myClass2 = new MyClass(); 5 System.out.println(myClass1.i); 6 System.out.println(myClass1.j); 7 System.out.println(myClass2.i); 8 System.out.println(myClass2.j); 9 10 } 11 } 12 13 class MyClass { 14 public final double i = Math.random(); 15 public static double j = Math.random(); 16 }
这个例子中会发现j是始终不变的,而i则是会变化的,这就是static和final的区别
4、匿名内部类的外部局部变量只能是final
见此文:https://www.cnblogs.com/dolphin0520/p/3811445.html
5、final参数
java允许方法中的参数变量用final修饰,这意味着你无法在方法中更改参数引用所指向的变量,例如:
class x { public void zz(final Object z){ z= new Object();//报错 } }
这里有一个误区:“当你在方法中不需要改变作为参数的对象变量时,明确使用final进行声明,会防止你无意的修改而影响到调用方法外的变量”这句话是错的,事实上,无论你使不使用final,方法内的修改都不会影响到方法外的变量;
1、当传入基本数据类型时
1 final class y{ 2 public static void main(String[] args) { 3 x x1 = new x(); 4 int t = 1; 5 x1.zz(t); 6 System.out.println(t); 7 } 8 } 9 class x { 10 public void zz(final int x){ 11 x++;//报错 12 } 13 }
这个例子似乎在说明不能修改而保护了方法外的值,但其实有没有这个final,外面的值都不会受11行的影响,因为java参数传递采用的是值传递,对于基本类型的变量,相当于直接将变量进行了拷贝。所以即使没有final修饰的情况下,在方法内部改变了变量x的值也不会影响方法外的x
2、传入引用变量
上面说过final修饰引用变量只是保证引用变量的指向不会被改变,那参数用final修饰和不用final有什么区别吗?
这就是涉及到java中引用传参的机制,传入来的引用变量实际上也是一份拷贝,也就是说不是外部原来那个引用变量,传进来之后这个引用变量和外部的引用变量同时指向一个对象,而final修饰后不能改变这个拷贝的引用变量的指向,如:
1 public class Test { 2 public static void main(String[] args) { 3 MyClass myClass = new MyClass(); 4 StringBuffer buffer = new StringBuffer("hello"); 5 myClass.changeValue(buffer); 6 System.out.println(buffer.toString()); 7 } 8 } 9 10 class MyClass { 11 12 void changeValue(final StringBuffer buffer) { 13 buffer.append("world"); 14 } 15 }