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 - 魔法字符串
''' #capitalize() ---首字母转换为大写--- s="sslssd" v=s.capitalize(); print(v) ''' ''' #center(20, ...
- Linux入门进阶第一天——vim文本编辑器
一.VI / VIM概述 [更新]:VIM资料参见:http://www.runoob.com/linux/linux-vim.html 是什么? 是一个文本编辑器. Vim是从 vi 发展出来的一个 ...
- flex stacked column graph
Flex: Stacked column chart – programmatically in actionscript By bishopondevelopment I was looking f ...
- echarts 去掉上面的小图标
在option里找到toolbox,删除对应的代码即可: toolbox: { y : -30, show : true, feature : { mark : '辅助线开关', markUndo : ...
- win10 64位redis的安装和测试
步骤记录: 1.官网没有redis64位的版本,在git开源项目上找到64位的可用版本 https://www.cnblogs.com/tommy-huang/p/6093813.html 这里有下载 ...
- 覆盖Django mysql model中save方法时碰到的一个数据库更新延迟问题
最近有一个需求,通过django的admin后台,可以人工配置5张表的数据,这些数据进行一些业务规则处理后会统一成一份数据缓存在一个cache之中供服务端业务访问,因而任何一张表的数据更新(增.删.改 ...
- RHCSA-day3
10.配置LDAP客户端 在classroom.example.com上已经部署了一台LDAP认证服务器,按以下要求将你的系统加入到该LDAP服务中,并使用Kerberos认证用户密码: 该LDAP认 ...
- Qt-QML-Canvas写个小小的闹钟
先看下演示效果 大致过程 先绘制仪表盘,圆圈和刻度 剩下再绘制三个指针 最后在绘制上面的电子时钟 下面写源代码 import QtQuick 2.0 Rectangle { id:root ancho ...
- HDU-2844:Coins(多重背包+二进制优化)
链接:HDU-2844:Coins 题意:给你n个种类的钱和对应的数量,同统计一下从1到m能够凑成的钱有多少个. 题解:C[i] = 1 + 2 + 4 + ··· + 2^k + a (0 < ...
- hbase和ZooKeeper集群安装配置
一:ZooKeeper集群安装配置 1:解压zookeeper-3.3.2.tar.gz并重命名为zookeeper. 2:进入~/zookeeper/conf目录: 拷贝zoo_sample.cfg ...