final关键字介绍
许多程序设计语言都有自己的办法告诉编译器某个数据是“常数”。常数主要应用于下述两个方面:
(1) 编译期常数,它永远不会改变
(2) 在运行期初始化的一个值,我们不希望它发生变化
对于编译期的常数,编译器(程序)可将常数值“封装”到需要的计算过程里。也就是说,计算可在编译期间提前执行,从而节省运行时的一些开销。在Java中,这些形式的常数必须属于基本数据类型(Primitives),而且要用final关键字进行表达。在对这样的一个常数进行定义的时候,必须给出一个值。
无论static还是final字段,都只能存储一个数据,而且不得改变。
若随同对象句柄使用final,而不是基本数据类型,它的含义就稍微让人有点儿迷糊了。对于基本数据类型,final会将值变成一个常数;但对于对象句柄,final会将句柄变成一个常数。进行声明时,必须将句柄初始化到一个具体的对象。而且永远不能将句柄变成指向另一个对象。然而,对象本身是可以修改的。Java对此未提供任何手段,可将一个对象直接变成一个常数(但是,我们可自己编写一个类,使其中的对象具有“常数”效果)。这一限制也适用于数组,它也属于对象。
下面是演示final字段用法的一个例子:
// The effect of final on fields class Value {
int i = 1;
} public class FinalData {
// Can be compile-time constants
final int i1 = 9;
static final int I2 = 99;
// Typical public constant:
public static final int I3 = 39;
// Cannot be compile-time constants:
final int i4 = (int)(Math.random()*20);
static final int i5 = (int)(Math.random()*20); Value v1 = new Value();
final Value v2 = new Value();
static final Value v3 = new Value();
//! final Value v4; // Pre-Java 1.1 Error:
// no initializer
// Arrays:
final int[] a = { 1, 2, 3, 4, 5, 6 }; public void print(String id) {
System.out.println(
id + ": " + "i4 = " + i4 +
", i5 = " + i5);
}
public static void main(String[] args) {
FinalData fd1 = new FinalData();
//! fd1.i1++; // Error: can't change value
fd1.v2.i++; // Object isn't constant!
fd1.v1 = new Value(); // OK -- not final
for(int i = 0; i < fd1.a.length; i++)
fd1.a[i]++; // Object isn't constant!
//! fd1.v2 = new Value(); // Error: Can't
//! fd1.v3 = new Value(); // change handle
//! fd1.a = new int[3]; fd1.print("fd1");
System.out.println("Creating new FinalData");
FinalData fd2 = new FinalData();
fd1.print("fd1");
fd2.print("fd2");
}
}
由于i1和I2都是具有final属性的基本数据类型,并含有编译期的值,所以它们除了能作为编译期的常数使用外,在任何导入方式中也不会出现任何不同。I3是我们体验此类常数定义时更典型的一种方式:public表示它们可在包外使用;Static强调它们只有一个;而final表明它是一个常数。注意对于含有固定初始化值(即编译期常数)的fianl static基本数据类型,它们的名字根据规则要全部采用大写。也要注意i5在编译期间是未知的,所以它没有大写。
不能由于某样东西的属性是final,就认定它的值能在编译时期知道。i4和i5向大家证明了这一点。它们在运行期间使用随机生成的数字。例子的这一部分也向大家揭示出将final值设为static和非static之间的差异(文末对此结合实例作出了更加详细的解答)。只有当值在运行期间初始化的前提下,这种差异才会揭示出来。因为编译期间的值被编译器认为是相同的。这种差异可从输出结果中看出:
fd1: i4 = 15, i5 = 9
Creating new FinalData
fd1: i4 = 15, i5 = 9
fd2: i4 = 10, i5 = 9
注意对于fd1和fd2来说,i4的值是唯一的,但i5的值不会由于创建了另一个FinalData对象而发生改变。那是因为它的属性是static,而且在载入时初始化,而非每创建一个对象时初始化。
从v1到v4的变量向我们揭示出final句柄的含义。正如大家在main()中看到的那样,并不能认为由于v2属于final,所以就不能再改变它的值。然而,我们确实不能再将v2绑定到一个新对象,因为它的属性是final。这便是final对于一个句柄的确切含义。我们会发现同样的含义亦适用于数组,后者只不过是另一种类型的句柄而已。将句柄变成final看起来似乎不如将基本数据类型变成final那么有用。
2. 空白final
Java允许我们创建“空白final”,它们属于一些特殊的字段。尽管被声明成final,但却未得到一个初始值。无论在哪种情况下,空白final都必须在实际使用前得到正确的初始化。而且编译器会主动保证这一规定得以贯彻。然而,对于final关键字的各种应用,空白final具有最大的灵活性。举个例子来说,位于类内部的一个final字段现在对每个对象都可以有所不同,同时依然保持其“不变”的本质。下面列出一个例子:
// "Blank" final data members class Poppet { } public class BlankFinal {
final int i = 0; // Initialized final
final int j; // Blank final
final Poppet p; // Blank final handle
// Blank finals MUST be initialized
// in the constructor:
BlankFinal() {
j = 1; // Initialize blank final
p = new Poppet();
}
BlankFinal(int x) {
j = x; // Initialize blank final
p = new Poppet();
}
public static void main(String[] args) {
BlankFinal bf = new BlankFinal();
}
}
现在强行要求我们对final进行赋值处理——要么在定义字段时使用一个表达 式,要么在每个构建器中。这样就可以确保final字段在使用前获得正确的初始化。
3. final自变量
Java允许我们将自变量设成final属性,方法是在自变量列表中对它们进行适当的声明。这意味着在一个方法的内部,我们不能改变自变量句柄指向的东西。如下所示:
// Using "final" with method arguments class Gizmo {
public void spin() {}
} public class FinalArguments {
void with(final Gizmo g) {
//! g = new Gizmo(); // Illegal -- g is final
g.spin();
}
void without(Gizmo g) {
g = new Gizmo(); // OK -- g not final
g.spin();
}
// void f(final int i) { i++; } // Can't change
// You can only read from a final primitive:
int g(final int i) { return i + 1; }
public static void main(String[] args) {
FinalArguments bf = new FinalArguments();
bf.without(null);
bf.with(null);
}
}
注意此时仍然能为final自变量分配一个null(空)句柄,同时编译器不会捕获它。这与我们对非final自变量采取的操作是一样的。
方法f()和g()向我们展示出基本类型的变量为final时会发生什么情况:我们只能读取自变量,不可改变它。
static final 与 final 与 static 的区别
我们先看个小例子,代码如下:
public final class EmployeeModel {
private final String firstName;
private final String lastName;
//按上面方式定义,运行正常
//进行如下改动
//private static final String firstName;
//private static final String lastName;
//结果出错,显示“空白终态字段 firstName 可能尚未初始化”
//再进行相应改动
//private static String firstName;
//private static String lastName;
//结果运行正常
public EmployeeModel(String fn,String ln)
{
firstName=fn;
lastName=ln;
} public String getFirstName(){
return firstName;
} public String getLastName(){
return lastName;
}
}
对于以上3中情况结合上知识作出以下解释说明:
第一种情况:
java 中final定义的成员变量并不一定必须在定义的时候初始化,final本身的含义是一旦赋值以后不可更改变量的值,final定义的变量只要在实际的类的对象在使用前得到初始化即可
故可在构造方法中进行初始化(2.空白final中的实例亦可说明该问题)
第二种情况:
进行如下修改后便不会出现问题:
private static final String firstName;
private static final String lastName;
static{
firstName = "";
lastName = "";
}
原因是:static是独立于对象实例的,static的final变量,要么在静态代码块里面进行初始化,要么在定义的时候进行初始化。(JAVA没有实现静态的构造方法)
而非static的final变量,要么在构造函数进行初始化,要么在实例代码块进行初始化。
因此final如果没有在定义时初始化,则一定要在构造器里面初始化,该种情况将其声明成static后,它不能在构造器里面初始化,转而要用静态代码块才能初始化。(静态方法只能初始化静态变量)
第三种情况:
定义为static类型变量后,若未进行初始化,JAVA将自己对其进行默认的初始化,如int类型默认为0.
final关键字介绍的更多相关文章
- Swift - final关键字的介绍,以及使用场景
final关键字在大多数的编程语言中都存在,表示不允许对其修饰的内容进行继承或者重新操作.Swift中,final关键字可以在class.func和var前修饰. 通常大家都认为使用final可以更好 ...
- 关于Java中final关键字的详细介绍
Java中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什么是final关键字?将变量,方法和类声明为final代表了什么?使用final的好处是什么?最后也有一些使 ...
- java final 关键字醍醐灌顶
醍醐灌顶: final 关键字,它可以修饰数据 .方法.类. 可能有些同学傻傻分不清出,这里可以快速弄懂final; final 实例域: 可以将实例域定义为final,构建对象时必须初始化这样的域, ...
- java基础4:深入理解final关键字
本文主要介绍了final关键字的使用方法及原理 具体代码在我的GitHub中可以找到 https://github.com/h2pl/MyTech 文章首发于我的个人博客: https://h2pl. ...
- Java——final关键字
前言 Java中的关键字final的含义通常为"这是无法改变的".下面将介绍final用于修饰数据.方法和类的这三种情况. final数据 许多编程语言都有某种方法,来向告诉编译器 ...
- static 和 final 关键字 对实例变量赋初始值的影响
static 和 final 关键字 对实例变量赋初始值的影响 最近一直在看<深入理解Java虚拟机>,在看完了对象内存分配.Class文件格式之后,想深扒一下实例变量是如何被赋上初始值的 ...
- Java中final关键字修饰变量、方法、类的含义是什么
Java中的关键字final修饰变量.方法.类分别表示什么含义? 先看一个简单的介绍 修饰对象 解释说明 备注 类 无子类,不可以被继承,更不可能被重写. final类中的方法默认是final的 方法 ...
- 【Java基本功】一文读懂final关键字的用法
本文主要介绍了final关键字的基本使用方法及原理 final关键字可以修饰类.方法和引用. 修饰类,该类不能被继承.并且这个类的对象在堆中分配内存后地址不可变. 修饰方法,方法不能被子类重写. 修饰 ...
- 聊聊Java的final关键字
Java的final关键字在日常工作中经常会用到,比如定义常量的时候.如果是C++程序员出身的话,可能会类比C++语言中的define或者const关键字,但其实它们在语义上差距还是挺大的. 在Jav ...
随机推荐
- 洛谷P1550 [USACO08OCT]打井Watering Hole
P1550 [USACO08OCT]打井Watering Hole 题目背景 John的农场缺水了!!! 题目描述 Farmer John has decided to bring water to ...
- loj#6041. 「雅礼集训 2017 Day7」事情的相似度(后缀自动机+启发式合并)
题面 传送门 题解 为什么成天有人想搞些大新闻 这里写的是\(yyb\)巨巨说的启发式合并的做法(虽然\(LCT\)的做法不知道比它快到哪里去了--) 建出\(SAM\),那么两个前缀的最长公共后缀就 ...
- SpringMVC中web.xml的配置(直接coppy使用就行)
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://w ...
- 搭建git服务器(临时服务器,命令行形式,针对2到5人左右,轻量)
服务端配置 ############################################################################################## ...
- C++_类和动态内存分配1—动态内存和类
静态类成员 num_strings成员声明为静态存储类.静态类成员有一个特点:无论创建了多少对象,程序都只创建一个静态类变量副本.也就是说,类的所有对象共享一个静态成员.num_strings成员可以 ...
- C++_标准模板库STL概念介绍4-算法
STL包含很多处理容器的非成员函数: sort() copy() find() random_shuffle() set_union() set_intersection() set_differen ...
- Linux系统下Qt环境搭建
http://www.linuxidc.com/Linux/2013-07/87576.htm 主要是需要提前安装一些动态库,否则编译的时候会出现 /usr/bin/ld: cannot find - ...
- POJ_2492 A Bug's Life 【并查集】
一.题面 POJ2492 二.分析 并查集判断类别的题目感觉套路都差不多. 还是先判断在不在一个集合里,在一个集合里才能判断是否同类. 若不在一个集合里则需要将这两个点联系起来. 关于联系起来后关系的 ...
- gym101964G Matrix Queries seerc2018g题 数学归纳法+线段树(递归)
题目传送门 题目大意: 给出2^k大小的白色矩形,q次操作,每次将一行或者一列颜色反转,问总体矩阵的价值,矩阵的价值定义是,如果整个矩阵颜色相同,价值为1,否则就把这个矩阵切成四份,价值为四个小矩阵的 ...
- POJ - 1061 扩展gcd
题意:求\((n-m)t+Lk=x-y\)的解\(t\) #include<iostream> #include<algorithm> #include<cstdio&g ...