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 }

 

posted @ 2021-11-28 20:12  空心小木头  阅读(85)  评论(0编辑  收藏  举报