浅析Java中的final关键字

浅析Java中的final关键字

浅析Java中的final关键字

  谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字。另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法。下面是本文的目录大纲:

  一.final关键字的基本用法

  二.深入理解final关键字

  若有不正之处,请多多谅解并欢迎指正。

  请尊重作者劳动成果,转载请标明原文链接:

  http://www.cnblogs.com/dolphin0520/p/3736238.html

一.final关键字的基本用法

  在Java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。下面就从这三个方面来了解一下final关键字的基本用法。

  1.修饰类

  当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。

  在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。

  2.修饰方法

  下面这段话摘自《Java编程思想》第四版第143页:

  “使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。“

  因此,如果只有在想明确禁止 该方法在子类中被覆盖的情况下才将方法设置为final的。

  注:类的private方法会隐式地被指定为final方法。

  3.修饰变量

  修饰变量是final用得最多的地方,也是本文接下来要重点阐述的内容。首先了解一下final变量的基本语法:

  对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。

  举个例子:

  

  上面的一段代码中,对变量i和obj的重新赋值都报错了。

二.深入理解final关键字

  在了解了final关键字的基本用法之后,这一节我们来看一下final关键字容易混淆的地方。

1.类的final变量和普通变量有什么区别?

  当用final作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值了。

  那么final变量和普通变量到底有何区别呢?下面请看一个例子:

1
2
3
4
5
6
7
8
9
10
11
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变量值的情况下,编译器才会进行这样的优化,比如下面的这段代码就不会进行优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
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修饰的引用变量一旦初始化赋值之后就不能再指向其他的对象,那么该引用变量指向的对象的内容可变吗?看下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
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的作用是用来保证变量不可变。看下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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。

  再看下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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编程思想》

 

作者: 海子
         
本博客中未标明转载的文章归作者 海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
分类:  Java基础
标签:  Java
27
1
(请您对文章做出评价)
« 上一篇: Java内部类详解
» 下一篇: 浅谈Java中的深拷贝和浅拷贝(转载)
posted @  2014-07-09 15:34  海 子 阅读( 87630) 评论( 23编辑  收藏

  
#1楼   2014-07-09 17:31  Antineutrino   
不要误会其本意,使用final修饰方法参数的目的是防止修改这个参数的值,同时也是一种声明和约定,强调这个参数是不可变的
  
#2楼   2014-07-09 19:54  沧海一滴   
@ Antineutrino
引用 不要误会其本意,使用final修饰方法参数的目的是防止修改这个参数的值,同时也是一种声明和约定,强调这个参数是不可变的

?
  
#3楼   2014-07-09 23:05  CamelOnTheWay   
@ Antineutrino
引用 不要误会其本意,使用final修饰方法参数的目的是防止修改这个参数的值,同时也是一种声明和约定,强调这个参数是不可变的

同感!
  
#4楼   2014-07-14 08:39  muxiao   
final 变量的修饰方法有三种,除了你说的哪两种之外,还有一种:如果是static final变量,可以在static代码块中初始化;如果没有static修饰符的话,可以在一般代码块中修饰。
  
#5楼   2015-04-07 17:15  chows_cnblogs   
受益匪浅 谢谢博主
  
#6楼   2015-06-12 09:54  Captain_Lucky   
刚好想了解final修饰引用变量,赞一个!
  
#7楼   2015-07-19 15:53  huangzh198   
在看Calendar类,
Calendar类的YEAR,HOUR,MINUTE,SECOND是Final的,但为什么我运行以下代码,每次的秒数会变化呢?

1
2
3
4
5
6
7
public  class  Main{
     public  static  void  main(String[] args){
         Calendar canlendar =  new  GregorianCalendar();
         System.out.println( "minute:"  + canlendar.get(Calendar.MINUTE));
         System.out.println( "second:"  + canlendar.get(Calendar.SECOND));
     }
}


而且Calendar类中有可以改变这些数据域的方法


1
2
The calendar fields can be changed using three methods: set(), add(), and roll().
set(f, value) changes calendar field f to value. In addition, it sets an internal member variable to indicate that calendar field f has been changed. Although calendar field f is changed immediately, the calendar's time value in milliseconds is not recomputed until the next call to get(), getTime(), getTimeInMillis(), add(), or roll() is made. Thus, multiple calls to set()  do  not trigger multiple, unnecessary computations. As a result of changing a calendar field using set(), other calendar fields may also change, depending on the calendar field, the calendar field value, and the calendar system. In addition, get(f) will not necessarily  return  value set by the call to the set method after the calendar fields have been recomputed. The specifics are determined by the concrete calendar  class .
  
#8楼   2015-08-03 10:59  oct   
final修饰其实是防止其被重新初始化 也就是被重新new 一个 或者被 = 赋值而已。
  
#9楼   2015-09-11 21:54  鸿鹄鸿鹄   
很不错啊
  
#10楼   2015-10-05 22:09  Java开发学习者   
最后一个例子,既然形参的改变不会影响原参,输出为什么会发生变化呢
  
#11楼   2015-10-22 00:24  朩頭亼の儛   
@ huangzh198
Calendar 改变数据域并非改变了 YEAR,HOUR 等的值,而是改变了 Calendar 类里的一个叫 fields 的成员,它里面存的才是对应的 YEAR,HOUR 数值。

1
2
3
4
5
6
7
8
/**
  * The calendar field values for the currently set time for this calendar.
  * This is an array of <code>FIELD_COUNT</code> integers, with index values
  * <code>ERA</code> through <code>DST_OFFSET</code>.
  * @serial
  */
@SuppressWarnings ( "ProtectedField" )
protected  int            fields[];
  
#12楼   2015-10-22 00:27  朩頭亼の儛   
@ Java开发学习者
这个可以参考博主写的 

2.被final修饰的引用变量指向的对象内容可变吗?

部分。
  
#13楼   2015-10-23 10:41  正俊   
为什么StringBuffer可以用可以修改,而其他的数据类型修改的不成功呢?Stringbuffer是什么原理,请指教?
  
#14楼   2015-10-25 19:56  huangzh198   
@ 朩頭亼の儛
好的,我看到了。明白了为什么这些YEAR、HOUR要大写了,因为他们的指向的确是不变的,变的只是fields[]里的数值
  
#15楼   2015-11-23 18:18  Java乔   
@ muxiao
这个应该属于静态的范畴了,跟final没多大关系了
  
#16楼   2015-11-23 18:20  Java乔   
@ Java开发学习者
因为字符串是引用数据类型,方法中改变了所指向地址中的字符串,所以结果改变
  
#17楼   2015-11-23 18:25  Java乔   
类的private方法会隐式地被指定为final方法。
这句话不是很明白,有什么依据吗?私有化是不会被继承的,而final是可以继承的。为什么私有会隐式的指定为final呢?
  
#18楼   2015-11-23 19:22  xiaojunjor   
“当你在方法中不需要改变作为参数的对象变量时,明确使用final进行声明,会防止你无意的修改而影响到调用方法外的变量”
这句话个人感觉没问题,参数如果是基本数据类型,那用final修饰就无意义了,这句话里也说了是对象变量时。
然后你举的例子中是用append方法修改了buffer,这个是属于changeValue方法本身的意愿,所以自然会修改buffer。

但是如果你在changeValue方法中这么定义:
buffer = new StringBuffer("HolleWorld");
这样是会报错的,所以说防止了对buffer指向的无意修改,但是却保留了对buffer对象本身的修改权。
  
#19楼   2015-11-25 14:55  天狼星主   
对于传值的说明,以下的代码可以说明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public  class  TestMain {
 
     /**
      *
      * @param args
      */
     public  static  void  main(String[] args) {
         MyClass myClass =  new  MyClass();
         int  i =  0 ;
         myClass.setI(i);
         i++;
         System.out.println(i);
         myClass.outI();
     }
 
}
 
class  MyClass {
     private  int  i;
 
     public  void  setI( final  int  i) {
         this .i = i;
     }
 
     public  void  outI() {
         System.out.println( this .i);
     }
}
“让形参重新指向另一个对象对实参并没有任何影响”最好一句话貌似是不对。你最后一个例子只是拿 StringBuffer 来举例,这是个特殊的类。你换个其他类就知道了。
  
#21楼   2016-03-10 14:35  uurun   
文章写的不错 学习了
看了后面的评论 很多对这部分知识都理解的不深
对比c++ 当final修饰值类型时就相当于c++中的const 例如const int i = 0;i是常量无法改变其值。 而当final修饰引用类型时相当于c++中的int* const p = (int*)malloc(sizeof(int)); p指向的值*p可以被修改,而指针p指向的地址不能改变。

在java方法中创建一个引用类型 例如 Double d = new Double(1.0); 其实同时在栈内存和堆内存都占用了内存。new Double(1.0)实在堆内存申请的空间,而等号赋值d却是是栈内存中,d保存的是堆内存的地址,其实是一个值类型。那么终结博主文章可以这样理解 final是修饰在堆内存中的分配值类型不可变。
  
#22楼   2016-03-31 15:31  fairy1674   
学些了啊,还好
  
#23楼   2016-05-18 11:10  havenenjoy   
个人觉得楼主最后一个例子,好像有点问题。java里面怎么会只有值传递呢?
基本数据类型是值传递,对象操作是引用传递。最后一例加不加final,不影响结果,都会打印helloworld。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值