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----------------------------------------------- 类,方 ...
随机推荐
- PTA(BasicLevel)-1008数组元素循环右移问题
一 .问题描述 原题描述 将长度为n的整形数组A进行右移m位操作, [A0 A1 A2 A3 ... Am...An-1]变为[An-m...An-1 A0 A1 A2 A3 ...An-m ...
- 5.18-笨办法学python-习题17(文件拷贝)
from sys import argv from os.path import exists #又import了一个命令exists,这个命令将文件名字符串作为参数,如果文件存在返回TRUE,否则返 ...
- OAuth(开放授权)
HTTP Basic Auth HTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和password,简言之,Basic Auth是配合RESTful API 使 ...
- 学号20155311 2016-2017-2《Java程序设计》课程总结
学号20155311 2016-2017-2<Java程序设计>课程总结 (按顺序)每周作业链接汇总 预备作业1:(http://www.cnblogs.com/gaoziyun11/p/ ...
- 20155320 《Java程序设计》实验三 敏捷开发与XP实践
20155320 <Java程序设计>实验三 敏捷开发与XP实践 实验内容 XP基础 XP核心实践 相关工具 (一)研究一下Code菜单 具体内容: 在IDEA中使用工具(Code-> ...
- 【AHOI2013】差异
题面 题解 $ \because \sum_{1 \leq i < j \leq n} i + j = \frac{n(n-1)(n+1)}2 $ 所以只需求$\sum lcp(i,j)$即可. ...
- 国外10个ASP.Net C#下的开源CMS
国外10个ASP.Net C#下的开源CMS https://blog.csdn.net/peng_hai_lin/article/details/8612895 1.Ludico Ludico是 ...
- 开源工具 | 手游自动化框架GAutomator,新增iOS系统和UE4引擎支
WeTest 导读 GAutomator是腾讯WeTest推出的手游自动化测试框架,已用于腾讯多个手游项目组的自动化测试. 1.GAutomator诞生背后 研究过手游自动化测试的同学都知道,虽然市场 ...
- CentOS7.2安装mysql-5.7.19多实例
安装多实例之前首先需要先安装mysql,这里就不介绍如何安装mysql了,参考前面的博客:https://www.cnblogs.com/hei-ma/p/9505509.html 安装多实例之前需要 ...
- AWS探索及创建一个aws EC2实例
一.AWS登陆 1.百度搜索aws,或者浏览器输入:http://aws.amazon.com 2.输入账户及密码登陆(注册流程按照提示走即可) 二.创建EC2实例(相当于阿里云的ecs) 1.找到E ...