一、开篇

本博客来自:http://www.cnblogs.com/yuananyun/

final修饰符是Java中比较简单常用的修饰符,同时也是一个被”误解“较多的修饰符。对很多Java程序员来说,他们大都只是草草看了一下各种书本上的介绍,然后背下来,什么时候想起
来有这东西就用一下。对于何时使用final修饰符、使用final修饰符对程序有什么影响,这些其实他们并不知道,当然在这篇文章之前,我也是一知半解的。

我们书本上对final的描述大概有三种用法:

    1. final可以修饰变量,被final修饰的变量被赋初始值之后,不能再对它重新赋值

    2. final可以修饰方法,被final修饰的方法不能被重写

    3. final可以修饰类,被final修饰的类不能被继承

这里,我们将分成三个小节对final在这三种场景中的用法进行说明。

二、final在变量中的用法

(一)被final修饰的实例变量必须显示指定初始值,而且只能在如下三个地方指定初始值:

  1. 定义final实例变量时指定初始值
  2. 在非静态初始化块中为final实例变量指定初始值
  3. 在构造器中为final实例变量指定初始值

其实这三种方式经过编译器处理后效果都一样,为什么呢?这是因为经过编译器处理后,这前面两种方式都会被抽取到构造其中进行赋初始值,这可能和我们平时的理解有点偏差,比如: final String var1=”我是一个兵”; 这么一条实例变量初始化语句,实际是分成两部完成的。第一步是对象创建的时候为变量var1分配内存,第二步是在构造器中为var1赋值”我是一个兵”,千万别以为对象是由构造器创建的,构造器仅仅完成对象的初始化工作而已。

(二)对于final修饰的类变量(静态变量(static))而言,同样必须显式指定初始值,而且只能在以下两个地方指定初始值:

  1. 定义final类变量时指定初始值
  2. 在静态初始化块中为final类变量指定初始值

需要指出的是,经过编译器的处理,这两种方式都会被抽取到静态初始化块中进行赋初始值。

(三)final修饰局部变量的情形比较简单,因为java本来就要求局部变量必须显式地赋值,与普通的局部变量不同的是,final修饰的局部变量一旦被赋值则不能被改变了。

(四)除此之外,final修饰符还能对编译期能确定值的变量执行”宏替换“,也就是我们常说的const常量。对于一个final变量,不管它是类变量、实例变量,还是局部变量,只要该变量的初始值在编译时就能确定下来,那么这个final变量不再是变量,而是相当于一个直接量。

public static void main(String [] args)
{
final int a=5+2;
final double b=1.2/3;
final String str1="我是一个兵";
final String str2="我是"+"一个兵";
//str1==str2 为true,因为编译期就可以确定str1,str2的值,于是当作宏替换 :"我是一个兵"=="我是一个兵",当然是true
final String str3="我是一个兵"+String.valueOf(99.0);
final String str4="我是一个兵99.0";
//str3==str4 为false,因为str3调用了String类的valueOf()函数,这在编译期是没法确定的,所以虽然str3和str4的字符串值相同,但不是同一个对象
//这里同时也给出了一种判断是否使用Java字符串缓存的办法:如果编译期能够确定是相同内容的字符串的两个或多个变量,
那么它们将使用同一个缓存的字符串对象。
}

注:对于宏变量只有在定义该变量的时候赋初始值才会有“宏变量”的效果,在非静态代码块、构造方法中为fianl实例变量指定初始值则不会。静态final类型的变量也一样。其实可以这么理解:当编译器遇到final修饰的变量定义时,如果发现它有指定初始值,那么就把这个变量当作初始值常量;如果没有的话,自然不能当作常量了。如:

public class FinalInitTest {
final String str1 ;
final String str2 ;
final String str3 = "java" ;
{//非静态代码块中初始化s
str1 = "java" ;
}
public FinalInitTest(){//构造方法中初始化
str2 = "java" ;
}
public void display(){
System.out.println(str1 + str1 == "javajava");
System.out.println(str2 + str2 == "javajava");
System.out.println(str3 + str3 == "javajava");
} public static void main(String[] args){
FinalInitTest fit = new FinalInitTest() ;
fit.display() ;
}
}
程序的运行结果为:
false
false
true
因为str1与str2在定义的时候并没有给初始化值,而是后面在构造函数中赋值的,所以没有“宏变换”的效果

 

三、final在方法中的用法

当final修饰某个方法时,用于限制该方法不能被子类重写。如果父类中某个方法使用了final修饰,那么这个方法将不可能被它的子类(不是子类的实例)访问到,因此这个方法也不可能被子类重写,从这个意义上来说,private和final同时修饰某个方法没有什么意义,但是java确实允许你这么干。如果你看到子类中定义了一个和父类中final修饰的同名的方法,那肯定不是重写,只是子类中定义了一个长得一样的方法而已,不信的话,你在子类该方式上加上@Override,将会报编译错误。

四、final在类上的用法

final修饰的类将不能被继承,和C#中的密封类一个道理。

此外,程序需要在匿名内部类或者普通内部类中使用局部变量,则局部变量必须声明为final类型。否则会编译错误。为什么必须声明为final类型呢?这是因为对于普通变量而言,它的作用域是停留在该方法内,当方法执行结束,该局部变量也就随之消失,但内部类则可能产生隐式的“闭包”,闭包使得局部变量脱离它所在的方法继续存在。看下面的例子:

public class ClosureTest {
public static void main(String[] args) {
final String str = "java" ;//定义局部变量 new Thread(new Runnable(){
public void run(){
for(int i=0 ;i<100 ; i++){
System.out.println(str + i);
try {
Thread.sleep(100) ;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start() ;
}
}

程序首先定义了一个局部变量str,当程序main方法执行完成后,str的生命周期就结束了,但是子线程还没有执行结束,而且子线程要用main中的局部变量str,这个时候就扩大了str的作用范围。这个时候如果str没有被修饰为final类型,而可以随便改变,则会引起极大的混乱,因此java编译器要求所有的内部类访问的局部变量必须使用final修饰符修饰。

最后,本文深入分析了java中final修饰符的功能,看似简单的背后,其实蕴含很多重要的道理。学习一门技术不要仅限于表面的用法,高手往往都是站在了理解甚至重构的层次,路漫漫其修远兮,吾将上下而求索。

Java中final修饰符深入研究的更多相关文章

  1. java中final修饰符的使用

    1.final修饰符的用法: final可以修饰变量,被final修饰的变量被赋初始值之后,不能对它重新赋值. final可以修饰方法,被final修饰的方法不能被重写. final可以修饰类,被fi ...

  2. JAVA中final修饰符小结

    一.final关键字可以用来修饰类.方法.变量.各有不同. A.修饰类(class).      1.该类不能被继承.      2.类中的方法不会被覆盖,因此默认都是final的.      3.用 ...

  3. Java中的 修饰符

    java中的修饰符分为类修饰符,字段修饰符,方法修饰符. 根据功能的不同,主要分为以下几种. 1.权限访问修饰符  访问权限的控制常被称为具体实现的隐藏 把数据和方法包进类中,以及具体实现的隐藏,常共 ...

  4. Java中各种修饰符与访问修饰符

    Java中各种修饰符与访问修饰符 类: 访问修饰符 修饰符 class 类名称 extends 父类名称 implement 接口名称 (访问修饰符与修饰符的位置可以互换) 访问修饰符 名称 说明 备 ...

  5. Java中final修饰的数据

    目录 Java中final修饰的数据 有初始值的final域 final+基本数据类型 final+引用数据类型 final与static final 空白final域 final修饰的参数 基本数据 ...

  6. Java中访问修饰符public、private、protecte、default

    Java中访问修饰符public.private.protecte.default的意义讲解:public: Java语言中访问限制最宽的修饰符,一般称之为“公共的”.被其修饰的类.属性以及方法不 仅 ...

  7. java中的修饰符和基本数据类型

    1.java中的修饰符 java中的修饰符主要是用来对类资源进行一个权限控制,上面表格表现的很清晰,无需多言. 2.java中的基本数据类型 java中的数据类型分为引用类型和基本类型.基本数据类型有 ...

  8. 对于Java中权限修饰符的理解

    老是把Java中权限修饰符给忘记,写一个博客加深印象吧 权限分为四个作用域:当前类,同一个包,其他包的子类,其他包的类. 首先要知道包的概念,Java中一个包是指一个package下的所有文件. pr ...

  9. Java中的修饰符

    -----------------------------------------------01----------------------------------------------- 类,方 ...

随机推荐

  1. 嵌入式C语言自我修养 06:U-boot镜像自拷贝分析:section属性

    6.1 GNU C 的扩展关键字:attribute GNU C 增加一个 __atttribute__ 关键字用来声明一个函数.变量或类型的特殊属性.声明这个特殊属性有什么用呢?主要用途就是指导编译 ...

  2. 第一节 如何用Go实现单链表

    一.概念介绍 下面这副图是我们单链表运煤车队. 每节运煤车就是单链表里的元素,每节车厢里的煤炭就是元素中保存的数据.前后车通过锁链相连,作为单链表运煤车,从1号车厢开始,每节车厢都知道后面拉着哪一节车 ...

  3. 3x3开窗中值滤波器的FPGA硬件实现

    数字逻辑课程的自由设计中,我编写了一个3x3开窗的中值滤波器,处理一副128*128像素值的图像,并且最终可以在FPGA上板实现. 中值滤波的本质就是对于一个n*n的窗口,将其内部的值进行排序,取中位 ...

  4. 20155239 实验四 Android程序设计

    20155239 实验四 Android程序设计 目录 第24章:初识Android 任务一: 完成Hello World, 要求修改res目录中的内容,Hello World后要显示自己的学号 学习 ...

  5. 20155310 《JAVA程序设计》实验二(JAVA面向对象程序设计)实验报告

    20155310 <JAVA程序设计>实验二(JAVA面向对象程序设计)实验报告 实验内容 •初步掌握单元测试和TDD •理解并掌握面向对象三要素:封装.继承.多态 •初步掌握UML建模 ...

  6. debug 调试原理理解

    引言: 昨天,看了一篇文章,很受启发,记得之前听别的人远程调试过代码,觉得很神奇,在自己程序里打断点,连接远程服务器,开启调试后可以调用远程方法来看数据的输入和输出,不需要查找问题,重新部署,测试问题 ...

  7. 前后端分离之JWT用户认证zf

    在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当我们透过帐号密码验证一个使用者时,当下一个request请求时它就把刚刚的资料忘了.于是我 ...

  8. 【LG3234】[HNOI2014]抄卡组

    题面 题解 分三种情况: 若所有串都没有通配符,直接哈希比较即可. 若所有串都有通配符, 把无通配符的前缀 和 无通配符的后缀哈希后比较即可. 中间部分由于通配符的存在,一定可以使所有串匹配. 若部分 ...

  9. 新技能get,使用PHPStorm的deployment工具

    1. 工具栏 Tools - Deployment - Configuration 2. 添加一个服务端的配置信息 type 类型可以选择:FTP.local等. 填完信息别忘了点"Test ...

  10. SSM-CRUD实战

    前端最容易出现缓存问题,所以以后每次都必须完全在idea加载完后,再在浏览器端多 执行 ctrl+F5 索要最新copy 这样就能拿到最新的改动了,就不会出现各种代码没问题但是功能就是实现不了的问题 ...