final是Java中的一个重要关键字,本篇将从final修饰数据、方法和类三个角度做出总结。

Java中final修饰的数据

final修饰的数据代表着:永远不变。意思是,一旦你用final修饰一块数据,你之后就只能看看它,你想修改它,没门。

我们不希望改变的数据有下面两种情况:

  • 永不改变的编译时常量
//编译时知道其值
private final int valueOne = 9;
  • 在运行时(不是编译时)被初始化的值,且之后不希望它改变
//在编译时不能知道其值
private final int i4 = rand.nextInt(20);

设置成常量有啥好处呢?

很简单,让编译器觉得简单,就是最大的好处。比如把PI设置成final,且给定值为3.14,编译器自然会觉得这个东西不会再被修改了,是足够权威的。那么,编译器就会在运行之前(编译时)就把这3.14代入所有的PI中计算,这样在真正运行的时候,速度方面当然会快一点。

有初始值的final域

声明为final且当场就给定初始值的域

 private final int valueOne = 9;

final+基本数据类型

final修饰的基本数据类型变量存储的数值永恒不变。

/*基本类型变量*/
//带有编译时数值的final基本类型
private final int valueOne = 9;
private static final int VALUE_TWO = 99;
public static final int VALUE_THREE = 39;
//!false:fd1.valueOne++;
//!false:fd1.VALUE_TWO++;
//!false:fd1.VALUE_THREE++;

康康上面醒目的三句false语句,很好地印证了我们之前说的:数值不让改!!!

需要注意的是,按照惯例,下面是定义常量的典型方式

//典型常量的定义方式
public static final int VALUE_THREE = 39;
  • public修饰符使其可被用于包之外。
  • static使数据只有一份。
  • final表示其无法被更改
  • 名称全为大写英文字母,以下划线隔开

final+引用数据类型

我们之前说过,基本类型存数值,引用类型存地址值。那么既然final+基本数据类型不让改数值,聪明的我们稍微一联想就明白,final+引用数据类型就是不让你改变量存储实际对象的地址值啦。(也就是不能再让它指向新的对象,很专一)

private Value v1 = new Value(1);
private final Value v2 = new Value(22);
private static final Value V_3 = new Value(333);
//引用变量并不是常量,存储地址可以改变
fd1.v1 = new Value(10); //v2是引用变量,final修饰之后表示地址不能改变,但是实际对象的值是可以改变的
fd1.v2.i++;
//!false:fd1.v2 = new Value(3); //V_3与v2类似,是静态和非静态的区别,下面会说明
fd1.V_3.i++;
//!false:fd1.V_3 = new Value(10);
}

通过例子,确实也证明上面所说,一个以final修饰的引用数据类型变量,无法再指向一个新的对象,因为它所存储的地址值已经无法被更改,但是并不影响它指向的实际对象。就拿一个比较典型的引用类型来举例,我们知道数组就是一种典型的引用类型,数组的引用变量存储的是数组再堆中的地址,堆中存放的就是数组每个索引的数值。

/*引用变量之数组*/
private final int[] a = {1,2,3,4,5,6};

引用变量a被指定为final,所以它里面的地址值不能再改,也就无法再让它指向一个新的数组。

//!false:fd1.a = new int[]{2,3,4,5,6,7};
for (int i = 0; i < fd1.a.length; i++) {
fd1.a[i]++;

但是,它指向的数组里的每个元素却可以改动,因为数组中的元素并没有任何的限定。

final与static final

private final int i4 = rand.nextInt(20);
static final int INT_5 = rand.nextInt(20);
System.out.println(fd1);//fd1: i4 = 15,INT_518

FinalData fd2 = new FinalData("fd2");
System.out.println(fd2);//fd2: i4 = 13,INT_518
FinalData fd3 = new FinalData("fd3");
System.out.println(fd3);//fd3: i4 = 1,INT_5 = 18
  • 上面示例分别创建了三个不同的对象,对其final 和final static 进行测试。
  • 需要明确的是,两者都以final修饰,都不能被改变。
  • 三个对象的i4值,没有用static修饰,不相同且不能改变。
  • 而INT_5的值因为被static修饰,在类加载时已经被初始化,不随对象改变而改变。

空白final域

声明为final却没有给定初始值的域

private final String id;//空白final

如果只有上面的这句,编译器会报错,因为它没有初始化。

Variable 'id' might not have been initialized

所以,若定义了空白final域,一定记得在构造器中给它赋值!(必须在域的定义处或者每个构造器中以表达式对final进行赋值,因为系统不会为final域默认初始化)

//在构造器中为空白final域赋初值
public FinalData(){
id = "空白final默认id";
}
public FinalData(String id){
this.id = id;
}

不要试图在初始化之前访问域,不然会报错。

final让域可以根据对象的不同而不同,增加灵活性的同时,又保留不被改变的特性。

final修饰的参数

基本数据类型的参数

类似地,就是传入的参数不让改,只让读,这一点很好理解。

public int finalParamTest(final int i){
//!false:i++;
//不让改,只让读
return i+1;
}

但是,我又新增了许多测试,分别定义四种不同的参数传入该方法,发现传入param0和param1编译会报错。(非常疑惑,这部分书上没提,查了许多资料也没有理解清楚,希望大牛可以评论区指点迷津

/*检测传入参数*/
int param0 = 5;
final int param1 = 10;
static final int PARAM_2 = 15;
static int param3 = 20;
//!false:System.out.println(fd1.finalParamTest(param0));
//!false:System.out.println(fd1.finalParamTest(param1));
//non-static field'param1' cannot be referenced from a static context
System.out.println(fd1.finalParamTest(PARAM_2));
System.out.println(fd1.finalParamTest(param3));
/*为什么形参列表里的参数用final修饰,但是用final修饰的param1无法传进去,
一定要static修饰?*/

引用数据类型的参数

public void finalReferenceTest(final FinalData fd){
//!false:fd = new FinalData();
//不能再指向新的对象,存储地址不准变
fd.param0++;
}

还是类似,不可以让这个引用类型的参数再指向新的对象,但是可以改变其实际指向对象的值。

最后的最后,下面的代码是根据《Thinking in Java》中的示例,结合自己的思想,将各个板块融合而成的超级无敌测试代码,冲冲冲!

package com.my.pac16;

import java.util.Arrays;
import java.util.Random; /**
* @auther Summerday
*/ class Value{
int i;//package access
public Value(int i){
this.i =i;
} }
/*final域在使用前必须被初始化:定义时,构造器中*/
public class FinalData {
/*检测传入参数*/
int param0 = 5;
final int param1 = 10;
static final int PARAM_2 = 15;
static int param3 = 20;
private static Random rand = new Random(47);
private final String id;//空白final
public FinalData(){
id = "空白final默认id";
}
public FinalData(String id){
this.id = id;
}
//带有编译时数值的final基本类型
private final int valueOne = 9;
private static final int VALUE_TWO = 99;
//典型常量的定义方式
public static final int VALUE_THREE = 39;
//在编译是不能知道其值
private final int i4 = rand.nextInt(20);
static final int INT_5 = rand.nextInt(20);
private Value v1 = new Value(1);
private final Value v2 = new Value(22);
private static final Value V_3 = new Value(333); private final int[] a = {1,2,3,4,5,6};
@Override
public String toString(){
return id+": "+"i4 = "+i4+",INT_5 = "+INT_5;
}
public int finalParamTest(final int i){
//!false:i++;
//不让改,只让读
return i+1;
}
public void finalReferenceTest(final FinalData fd){
//!false:fd = new FinalData();
//不能再指向新的对象,存储地址不准变
fd.param0++; } public static void main(String[] args) {
FinalData fd1 = new FinalData("fd1");
/*基本类型变量*/
//!false:fd1.valueOne++;
//!false:fd1.VALUE_TWO++;
//!false:fd1.VALUE_THREE++;
/*引用变量*/
fd1.v1 = new Value(10);
fd1.v2.i++
//!false:fd1.v2 = new Value(3);
System.out.println("fd1.v2.i = [" + fd1.v2.i + "]"); //!false:fd1.V_3 = new Value(10);
fd1.V_3.i++;
System.out.println("fd1.V_3.i = [" + fd1.V_3.i + "]");
/*引用变量之数组*/
System.out.println("before:fd1.a[] = " + Arrays.toString(fd1.a)); /*数组引用变量a是final修饰,
但是不代表它指向的数据值是final,
而是a存储的地址值不能改变
*/
//!false:fd1.a = new int[]{2,3,4,5,6,7};
for (int i = 0; i < fd1.a.length; i++) {
fd1.a[i]++;
}
System.out.println("after :fd1.a[] = " + Arrays.toString(fd1.a));
/*final 与static final*/
//下面示例分别创建了三个不同的对象,对其final 和final static 进行测试
/*可以发现,三个对象的i4值是随机生成且不能改变的,且不相同,
而INT_5的值不随对象改变而改变,因为被static修饰,在类加载时已经被初始化*/
System.out.println(fd1);//fd1: i4 = 15,INT_518 FinalData fd2 = new FinalData("fd2");
System.out.println(fd2);//fd2: i4 = 13,INT_518
FinalData fd3 = new FinalData("fd3");
System.out.println(fd3);//fd3: i4 = 1,INT_5 = 18 //!false:System.out.println(fd1.finalParamTest(param0));
//!false:System.out.println(fd1.finalParamTest(param1));
//non-static field'param1' cannot be referenced from a static context
System.out.println(fd1.finalParamTest(PARAM_2));
System.out.println(fd1.finalParamTest(param3));
/*为什么形参列表里的参数用final修饰,但是用final修饰的param1无法传进去,
一定要static修饰?*/ System.out.println("fd1.param0 = "+fd1.param0);
fd1.finalReferenceTest(fd1);
System.out.println("fd1.param0 = "+fd1.param0);
} }

Java中final修饰的方法

  • final修饰的方法不能被重写,但是可以被重载。
  • private被认为是final,再加一个final也没啥额外意义。
  • 需要注意的是:子类中定义了一个与父类privateprivate final修饰的方法时,其实是重新定义了一个方法,并不是方法重写。(因为final修饰的方法无法被重写,可用@Override注解加以验证。
package com.my.pac16;

/**
* @auther Summerday
*/
public class FinalMethod {
public final int age = 10;
public final void test() {
System.out.println("test()");
} private final void test2() {
System.out.println("test2()");
} } final class Subclass extends FinalMethod {
//不能重写:public final void test()
//可以重载
public final void test(int a) {
System.out.println("test()");
} //!false:@Override
public final void test2() {
System.out.println("is not override test2()");
} public static void main(String[] args) {
Subclass sb = new Subclass();
sb.test(2);
sb.test();
sb.test2();//并不是重写
System.out.println(sb.age);//final修饰的域可以被继承
}
}

Java中final修饰的类

  • final修饰的类无法被继承

  • Java中的String类就是典型的例子。

    public final class String extends Object

  • 一个类被final修饰,里面的成员自然也就相当于用final修饰。

文章如有理解错误或叙述不到位,欢迎大家在评论区加以指正。

参考书籍:《Thinking in Java》

Java中final修饰的数据的更多相关文章

  1. Java中final修饰符深入研究

    一.开篇 本博客来自:http://www.cnblogs.com/yuananyun/ final修饰符是Java中比较简单常用的修饰符,同时也是一个被"误解"较多的修饰符.对很 ...

  2. java中final修饰符的使用

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

  3. Java中Final修饰一个变量时,是引用不能变还是引用的对象不能变

    Java中,使用Final修饰一个变量,是引用不能变,还是引用对象不能变? 是引用对象的地址不能变,引用变量所指的对象的内容可以改变. final变量永远指向这个对象,是一个常量指针,而不是指向常量的 ...

  4. Java中final修饰参数的作用

    在方法参数前面加final关键字就是为了防止数据在方法体中被修改. 主要分两种情况:第一,用final修饰基本数据类型:第二,用final修饰引用类型. 第一种情况,修饰基本类型(非引用类型).这时参 ...

  5. Java中final修饰的方法是否可以被重写

    这是一次阿里面试里被问到的题目,在我的印象中,final修饰的方法是不能被子类重写的.如果在子类中重写final修饰的方法,在编译阶段就会提示Error.但是回答的时候还是有点心虚的,因为final变 ...

  6. JAVA中final修饰符小结

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

  7. Java反射机制可以动态修改实例中final修饰的成员变量吗?

    问题:Java反射机制可以动态修改实例中final修饰的成员变量吗? 回答是分两种情况的. 1. 当final修饰的成员变量在定义的时候就初始化了值,那么java反射机制就已经不能动态修改它的值了. ...

  8. Java中的 修饰符

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

  9. Java中final的用法总结

    1.         修饰基础数据成员的final 这是final的主要用途,其含义相当于C/C++的const,即该成员被修饰为常量,意味着不可修改.如java.lang.Math类中的PI和E是f ...

随机推荐

  1. nyoj 62-笨小熊(以对应数组中的ASC位 + 1)

    62-笨小熊 内存限制:64MB 时间限制:2000ms Special Judge: No accepted:15 submit:43 题目描述: 笨小熊的词汇量很小,所以每次做英语选择题的时候都很 ...

  2. 搭建Nginx正向代理服务

    需求背景: 前段时间公司因为业务需求需要部署一个正向代理,需要内网服务通过正向代理访问到外网移动端厂商域名通道等效果,之前一直用nginx做四层或者七层的反向代理,正向代理还是第一次配置,配置的过程也 ...

  3. Cygwin安装教程

    cygwin是一个在windows平台上运行的unix模拟环境,是cygnus solutions公司开发的自由软件. 它对于学习unix/linux操作环境,或者从unix到windows的应用程序 ...

  4. Spring中常见的设计模式——工厂模式

    一.简单工厂模式 简单工厂模式(Simple Factory Pattern)由一个工厂对象决定创建哪一种产品类的实例,简单工厂模式适用于工厂类负责创建对象较少的情况,且客户端只需要传入工厂类的参数, ...

  5. webapi跨域使用session

    在之前的项目中,我们设置跨域都是直接在web.config中设置的. 这样是可以实现跨域访问的.因为我们这边一般情况下一个webapi会有多个网站.小程序.微信公众号等访问,所以这样设置是没有问题的. ...

  6. Git及Github

    目录 Git及Github的使用 Git的基本介绍 Git命令行操作 1.设置签名 2.创建本地库 3.仓库初始化 4.状态查看 5.添加文件 6.提交文件 7.历史记录 8.前进后退 9.删除文件 ...

  7. 【NHOI2018】找素数

    [题目描述] 素数又称质数,是指一个大于 1 的正整数,如果除了 1 和它本身以外,不能再被其它的数整除,例如:2.3.5.97 等都是素数.2 是最小的素数. 现在,给你 n 个数字,请你从中选取一 ...

  8. linux 进程简介

    进程相关知识简介 进程定义: 一个运行中的程序即一个process task struct: 内核存储进程信息的固定格式称为task struct,task struct记录了例如该进程内存下一跳位置 ...

  9. 开源WPF控件库MaterialDesignInXAML推荐

    今天介绍一个开源的C# WPF开源控件库,非常漂亮,重点是开源哦 WPF做桌面开发是很有优势的,除了微软自带的控件外,还有很多第三方的控件库,比如收费的Dev Express For WPF.Tele ...

  10. Kafka原理详解

    Kafka是最初由Linkedin公司开发,是一个分布式.支持分区的(partition).多副本的(replica),基于zookeeper协调的分布式消息系统,它的最大的特性就是可以实时的处理大量 ...