Java中final修饰符深入研究
一、开篇
final修饰符是Java中比较简单常用的修饰符,同时也是一个被”误解“较多的修饰符。对很多Java程序员来说,他们大都只是草草看了一下各种书本上的介绍,然后背下来,什么时候想起
来有这东西就用一下。对于何时使用final修饰符、使用final修饰符对程序有什么影响,这些其实他们并不知道,当然在这篇文章之前,我也是一知半解的。我们书本上对final的描述大概有三种用法:
final可以修饰变量,被final修饰的变量被赋初始值之后,不能再对它重新赋值
final可以修饰方法,被final修饰的方法不能被重写
final可以修饰类,被final修饰的类不能被继承
这里,我们将分成三个小节对final在这三种场景中的用法进行说明。
二、final在变量中的用法
(一)被final修饰的实例变量必须显示指定初始值,而且只能在如下三个地方指定初始值:
- 定义final实例变量时指定初始值
- 在非静态初始化块中为final实例变量指定初始值
- 在构造器中为final实例变量指定初始值
其实这三种方式经过编译器处理后效果都一样,为什么呢?这是因为经过编译器处理后,这前面两种方式都会被抽取到构造其中进行赋初始值,这可能和我们平时的理解有点偏差,比如: final String var1=”我是一个兵”; 这么一条实例变量初始化语句,实际是分成两部完成的。第一步是对象创建的时候为变量var1分配内存,第二步是在构造器中为var1赋值”我是一个兵”,千万别以为对象是由构造器创建的,构造器仅仅完成对象的初始化工作而已。
(二)对于final修饰的类变量(静态变量(static))而言,同样必须显式指定初始值,而且只能在以下两个地方指定初始值:
- 定义final类变量时指定初始值
- 在静态初始化块中为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修饰符深入研究的更多相关文章
- java中final修饰符的使用
1.final修饰符的用法: final可以修饰变量,被final修饰的变量被赋初始值之后,不能对它重新赋值. final可以修饰方法,被final修饰的方法不能被重写. final可以修饰类,被fi ...
- JAVA中final修饰符小结
一.final关键字可以用来修饰类.方法.变量.各有不同. A.修饰类(class). 1.该类不能被继承. 2.类中的方法不会被覆盖,因此默认都是final的. 3.用 ...
- Java中的 修饰符
java中的修饰符分为类修饰符,字段修饰符,方法修饰符. 根据功能的不同,主要分为以下几种. 1.权限访问修饰符 访问权限的控制常被称为具体实现的隐藏 把数据和方法包进类中,以及具体实现的隐藏,常共 ...
- Java中各种修饰符与访问修饰符
Java中各种修饰符与访问修饰符 类: 访问修饰符 修饰符 class 类名称 extends 父类名称 implement 接口名称 (访问修饰符与修饰符的位置可以互换) 访问修饰符 名称 说明 备 ...
- Java中final修饰的数据
目录 Java中final修饰的数据 有初始值的final域 final+基本数据类型 final+引用数据类型 final与static final 空白final域 final修饰的参数 基本数据 ...
- Java中访问修饰符public、private、protecte、default
Java中访问修饰符public.private.protecte.default的意义讲解:public: Java语言中访问限制最宽的修饰符,一般称之为“公共的”.被其修饰的类.属性以及方法不 仅 ...
- java中的修饰符和基本数据类型
1.java中的修饰符 java中的修饰符主要是用来对类资源进行一个权限控制,上面表格表现的很清晰,无需多言. 2.java中的基本数据类型 java中的数据类型分为引用类型和基本类型.基本数据类型有 ...
- 对于Java中权限修饰符的理解
老是把Java中权限修饰符给忘记,写一个博客加深印象吧 权限分为四个作用域:当前类,同一个包,其他包的子类,其他包的类. 首先要知道包的概念,Java中一个包是指一个package下的所有文件. pr ...
- Java中的修饰符
-----------------------------------------------01----------------------------------------------- 类,方 ...
随机推荐
- python 消息队列-rabbitMQ 和 redis介绍使用
1.rabbitMQ 与ptyhon 进程queue 区别.进程queue 主要用户Python父子进程之间或者统一进程不同子进程.rabbit可以用户不同语言之前的相互交流,socket可以实现同样 ...
- 网站sql注入漏洞修复方案之metinfo 6.1.0系列
近日,我们SINE安全对metinfo进行网站安全检测发现,metinfo米拓建站系统存在高危的sql注入漏洞,攻击者可以利用该漏洞对网站的代码进行sql注入攻击,伪造恶意的sql非法语句,对网站的数 ...
- MongoDB分片介绍
本文简单介绍MongoDB的分片功能,对分片进行了概述,具体的功能详解,后续文章会陆续推出 分片是把数据分配到多个服务器上的一种方式,MongoDB使用分片实现大数据部署以及高吞吐操作. 大数据以及高 ...
- dtree的自定义select动作
项目中用到了dtree,别问我为什么用这么古老的插件,因为简单啊orz,文件树的条目不多,detree加载卡顿的问题也不用解决,开森. 在使用过程中在选择节点后需要自定义一些onclick的动作,本来 ...
- 关于makefile中自动产生依赖的理解
本博文是在学习了<GNU Make中文手册>后记录下来的自己的关于自动产生makefile依赖的语句的理解,向大家分享. <GNU make中文手册>中的相关章节见一下链接: ...
- python-对于mysql数据库的操作
python操作mysql数据库 问题:DDL,DCL,DML的区别? 语言与框架:jdbc/odbc操作数据库 java(ibatis/hibernate/jpa)操作数据库 客户端工具:navic ...
- Shrio第一天——入门与基本概述
一.Shiro是什么 Apache Shiro是Java的一个安全框架.(希罗:/笑哭) Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境. sh ...
- 20155233 《Java程序设计》 第十一周课堂练习总结
20155233 <Java程序设计> 第十一周课堂练习总结 测试题目 1.修改教材P74 一行代码 NineNineTable.java, 让执行结果是个三角形: 提交在IDEA或命令行 ...
- 20155233 2016-2017-2 《Java程序设计》第1周学习总结
20155233 2016-2017-2 <Java程序设计>第1周学习总结 教材学习内容总结 本周主要学习教材的第一章.第二章,了解了Java的发展历程,最基本的Java环境的搭建,以及 ...
- MYSQL--慢查询,卡死等处理
命令: show processlist; 如果是root帐号,你能看到所有用户的当前连接.如果是其它普通帐号,只能看到自己占用的连接. show processlist;只列出前100条,如果想全列 ...