浅析Java中的final关键字

1.修饰类
  当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。
   在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。


修饰方法
用final关键字修饰方法,它表示该方法不能被覆盖。(但可以继承之). 关于private和final关键字还有一点联系,这就是类中所有的private方法都隐式地指定为是final的,由于无法在类外使用private方法,所以也就无法覆盖它。

3.修饰变量
对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
在java中,用final关键字修饰的变量,只能进行一次赋值操作,并且在生存期内不可以改变它的值。更重要的是,final会告诉编译器,这个数据是不会修改的,那么编译器就可能会在编译时期就对该数据进行替换甚至执行计算,这样可以对我们的程序起到一点优化。不过在针对基本类型和引用类型时,final关键字的效果存在细微差别。我们来看下面的例子:
1 class Value { 2 int v; 3 public Value( int v) { 4 this .v = v; 5 } 6 } 7 8 public class FinalTest { 9 10 final int f1 = 1; 11 final int f2; 12 public FinalTest() { 13 f2 = 2; 14 } 15 16 public static void main(String[] args) { 17 final int value1 = 1; 18 // value1 = 4; 19 final double value2; 20 value2 = 2.0; 21 final Value value3 = new Value(1); 22 value3.v = 4; 23 } 24 }
上面的例子中,我们先来看一下main方法中的几个final修饰的数据, 在给value1赋初始值之后,我们无法再对value1的值进行修改,final关键字起到了常量的作用。从value2我们可以看到,final修饰的变量可以不在声明时赋值,即可以先声明,后赋值。value3时一个引用变量,这里我们可以看到final修饰引用变量时,只是限定了引用变量的引用不可改变,即不能将value3再次引用另一个Value对象,但是引用的对象的值是可以改变的 ,从内存模型中我们看的更加清晰:
上图中,final修饰的值用粗线条的边框表示它的值是不可改变的,我们知道引用变量的值实际上是它所引用的对象的地址,也就是说该地址的值是不可改变的,从而说明了为什么引用变量不可以改变引用对象。而实际引用的对象实际上是不受final关键字的影响的,所以它的值是可以改变的。
另一方面,我们看到了用final修饰成员变量时的细微差别,因为final修饰的数据的值是不可改变的,所以我们必须确保在使用前就已经对成员变量赋值了。因此对于final修饰的成员变量,我们有且只有两个地方可以给它赋值,一个是声明该成员时赋值,另一个是在构造方法中赋值,在这两个地方我们必须给它们赋初始值。
最后我们需要注意的一点是,同时使用static和final修饰的成员在内存中只占据一段不能改变的存储空间。

2.修饰方法参数
前面我们可以看到,如果变量是我们自己创建的,那么使用final修饰表示我们只会给它赋值一次且不会改变变量的值。那么如果变量是作为参数传入的,我们怎么保证它的值不会改变呢?这就用到了final的第二种用法,即在我们编写方法时,可以在参数前面添加final关键字,它表示在整个方法中,我们不会(实际上是不能)改变参数的值:
public class FinalTest { /* ... */ public void finalFunc( final int i, final Value value) { // i = 5; 不能改变i的值 // v = new Value(); 不能改变v的值 value.v = 5; // 可以改变引用对象的值 }}

Q)何为未被初始化的 final 变量?
Ans)在声明final修饰的变量的时候没有初始化的变量,也称为 blank final 变量。
考虑这样一个场景,一个对象的某个字段,只有在该对象被实例化的时候被赋值一次,以后该字段的值不会被改变。这个时候blank final 变量就派上用场了。
1 class Student{ 2 int id; 3 String name; 4 final String PAN_CARD_NUMBER; 5 ... 6 }
Student中的PAN_CARD_NUMBER 字段只有在学生实例被创建的时候赋值一次,以后不会改变。
 
Q)何为final参数?
Ans)被final修饰的参数,该参数的值不能改变。栗子:
复制代码
1 class Bike11{ 2 int cube( final int n){ 3 n=n+2; //can't be changed as n is final 4 n*n*n; 5 } 6 public static void main(String args[]){ 7 Bike11 b= new Bike11(); 8 b.cube(5); 9 } 10 }
复制代码
上述代码中,cube() 方法的参数n被final修饰,其内部对n试图修改将产生编译错误。
 
Q)构造函数能声明为final吗?
Ans)不能,因为构造函数从来不会被继承。


二.深入理解final关键字
  在了解了final关键字的基本用法之后,这一节我们来看一下final关键字容易混淆的地方。
1.类的final变量和普通变量有什么区别?
  当用final作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值了。
  那么final变量和普通变量到底有何区别呢?下面请看一个例子:
public   class  Test {
     public   static   void  main(String[] args)  {
        String a =  "hello2"
         final  String b =  "hello" ;
        String d =  "hello" ;
        String c = b +  2
        String e = d +  2 ;
        System.out.println((a == c));
        System.out.println((a == e));
    }
}
  
true false
  大家可以先想一下这道题的输出结果。为什么第一个比较结果为true,而第二个比较结果为fasle。这里面就是final变量和普通变量的区别了, 当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。 这种和C语言中的宏替换有点像。因此在上面的一段代码中,由于变量b被final修饰,因此会被当做编译器常量,所以在使用到b的地方会直接将变量b 替换为它的  值。而对于变量d的访问却需要在运行时通过链接来进行。想必其中的区别大家应该明白了,不过要注意, 只有在编译期间能确切知道final变量值的情况下,编译器才会进行这样的优化 ,比如下面的这段代码就不会进行优化:
public   class  Test {
     public   static   void  main(String[] args)  {
        String a =  "hello2"
         final String b = getHello();
        String c = b +  2
        System.out.println((a == c));
 
    }
     
     public   static  String getHello() {
         return   "hello" ;
    }
}
  这段代码的输出结果为false。
2.被final修饰的引用变量指向的对象内容可变吗?
  在上面提到被final修饰的引用变量一旦初始化赋值之后就不能再指向其他的对象,那么该引用变量指向的对象的内容可变吗?看下面这个例子:
public   class  Test {
     public   static   void  main(String[] args)  {
         final  MyClass myClass =  new  MyClass();
        System.out.println(++myClass.i);
 
    }
}
 
class  MyClass {
     public   int  i =  0 ;
}
  这段代码可以顺利编译通过并且有输出结果,输出结果为1。这说明 引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的。
3.final和static
  很多时候会容易把static和final关键字混淆, static作用于成员变量用来表示只保存一份副本,而final的作用是用来保证变量不可变 。看下面这个例子:
public   class  Test {
     public   static   void  main(String[] args)  {
        MyClass myClass1 =  new  MyClass();
        MyClass myClass2 =  new  MyClass();
        System.out.println(myClass1.i);
        System.out.println(myClass1.j);
        System.out.println(myClass2.i);
        System.out.println(myClass2.j);
 
    }
}
 
class  MyClass {
     public   final   double  i = Math.random();
     public   static   double  j = Math.random();
}
  运行这段代码就会发现, 每次打印的两个j值都是一样的,而i的值却是不同的 。从这里就可以知道final和static变量的区别了。
4.匿名内部类中使用的外部局部变量为什么只能是final变量?
  这个问题请参见上一篇博文中 《Java内部类详解》 中的解释,在此处不再赘述。
5.关于final参数的问题
  关于网上流传的”当你在方法中不需要改变作为参数的对象变量时,明确使用final进行声明,会防止你无意的修改而影响到调用方法外的变量“这句话,我个人理解这样说是不恰当的。
  因为无论参数是基本数据类型的变量还是引用类型的变量,使用final声明都不会达到上面所说的效果。
  看这个例子就清楚了:
  上面这段代码好像让人觉得用final修饰之后,就不能在方法中更改变量i的值了。殊不知,方法changeValue和main方法中的变量i根本就不是一个变量, 因为java参数传递采用的是值传递,对于基本类型的变量,相当于直接将变量进行了拷贝。所以即使没有final修饰的情况下,在方法内部改变了变量i的值也不会影响方法外的i。
  再看下面这段代码:
public   class  Test {
     public   static   void  main(String[] args)  {
        MyClass myClass =  new  MyClass();
        StringBuffer buffer =  new  StringBuffer( "hello" );
        myClass.changeValue(buffer);
        System.out.println(buffer.toString());
    }
}
 
class  MyClass {
     
     void  changeValue( final  StringBuffer buffer) {
        buffer.append( "world" );
    }
}
  运行这段代码就会发现输出结果为 helloworld。很显然, 用final进行修饰并没有阻止在changeValue中改变buffer指向的对象的内容 。有人说假如把final去掉了,万一在changeValue中让buffer指向了其他对象怎么办。有这种想法的朋友可以自己动手写代码试一下这样的结果是什么,如果把final去掉了,然后在changeValue中让buffer指向了其他对象,也不会影响到main方法中的buffer,原因在于java采用的是值传递,对于引用变量,传递的是引用的值,也就是说让实参和形参同时指向了同一个对象,因此让形参重新指向另一个对象对实参并没有任何影响。
   所以关于网上流传的final参数的说法,我个人不是很赞同。
参考资料:
  《Java编程思想》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值