java基础-复用类-final关键字的使用

java中final关键字的使用通常情况下是想表明"无法改变"的意思。它可以修饰数据,方法和类。

一.final修饰数据

final数据也称final变量,当final修饰变量时,意味着这个变量是不可以被更改的。而变量根据定义时的所处位置又可以分为类的成员变量和局部变量,根据变量类型来分又可以分为基本类型变量和引用类型变量。来一一测试final修饰这些变量时所产生的影响。

(1)final修饰基本类型的成员变量

此时,这个基本类型成员变量的值便不能被再次更改。

这里,定义了4个成员变量a,b,c,d;其中,,a,b,c是final修饰的,d则没有用final修饰。同时运用了4种初始化方式给他们进行初始化。从这里可以看出,final修饰的基本类型成员变量可以在定义时,构造器中以及显示实例中对它进行初始化动作。那么final修饰的基本类型变量能否实现惰性初始化呢?我们知道基本类型变量定义时如果没有对其进行初始化,那么在创建对象时编译器会自动完成变量的初始化动作,那么对于final修饰的基本类型变量是不是也是这样呢?试一下;

我们看到,在编译时就无法通过,具体报错信息如下:

说明在定义final修饰的基本类型变量时,在创建对象之前必须要给它进行初始化,无论是在定义处,构造器中还是显式实例内都行。至于惰性初始化,首先定义时没有初始化就报错了,那就不存在后续的惰性初始化了。

再来看看final类型的变量的值能否被修改:

编译器报错,看下具体错误原因:

这说明final修饰的基本类型变量的值一旦确定之后,便无法再次为其分配新值了,它的值是不可变的;

再来看上面的例子:

在定义时就初始化的变量和在构造器中初始化的有什么不同呢?

变量a是在定义时就初始化,给其赋值了一个字面量值,那么在编译期java编译器就已经知道了这个字面量值的存在,所以编译器就可以将这个字面量值代入任何可能用到这个变量a的计算式中,也就是说,可以在编译时执行计算式,这减轻了一些运行时的负担。

变量b是在构造器中初始化的,也就是说,只有当生成这个类的一个对象实例时,才会调用构造器对其进行初始化。变量b的值在编译期无法确定,只有在运行时才能确定。所以,变量a也可以称为"编译时常量"。

编译时常量的定义:

参考文章:https://blog.csdn.net/li_xunhuan/article/details/104040079

编译期常量,即 compile-time constant。其看似是一个静态,并不一定是由 static 修饰(static 一般只是用于强调只有一份),但强制要求使用 final 进行修饰。

编译期常量完整要求是:

  • declared final;被声明为 final(所有编译期常量都必须满足的条件);
  • primitive or String;基本类型或者字符串类型(满足其一即可);
  • initialized within declaration;声明时便已初始化(必要条件);
  • initialized with constant expression;使用常量表达式进行初始化(对于第三条的补充,说明初始化方式);

什么是常量表达式呢?这个可以参考 Oracle 的官方文档(15.28小节):

https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html

这里用浏览器的翻译功能翻译了一下,大致上能看懂。以上类型的表达式都称为常量表达式。第一条至第三条的解释为:

  • 基本类型以及 String 类型的字面量(new 出来的、变量引用的都不能算);
  • 基本类型以及 String 类型的强制类型转换;
  • 使用 + 等一元运算符进行加法运算/拼接运算得到的值;

那么在定义时便初始化的final修饰的基本类型变量是否都是编译时常量呢?看下面一个例子:

变量b是由Random类在运行时随机生成的一个数值来初始化的。而且,他的初始化表达式也并不是一个常量表达式。所以b并不是一个编译时变量。

接下来看一个更加典型的定义表达式:

VALUE_ONE肯定是一个编译时变量了。但是它却被三个关键字修饰,定义为public,表示它可以被用于包之外,定义为static,则强调只有一份,定义为final,说明它不能够被更改。所以,也可以称它为"常量" 。带有恒定初值的编译时常量被static修饰时便可以称之为常量,常量的命名规范是:常量名全部使用大写字母,并且单词与单词之间用下划线隔开,就像上面的VALUE_ONE一样。

 

(2)final修饰非基本类型的成员变量

看下面例子:

非基本类型的变量除了String类型以外,均不是编译时常量,被final修饰后,无法将变量的引用再次指向一个新的对象,但是可以修改对象里面的属性这种特性也适用于数组。因为数组也算是一种非基本类型变量

(3)final修饰局部变量

final修饰的局部基本类型变量和非基本类型变量与基本类型的成员变量和非基本类型的成员变量所需要遵循的规则没有什么不同。

二.final参数

java允许在方法的参数列表中以声明的方式将参数指定为final,当参数为基本类型时,可以读取参数,却无法修改参数,当参数为非基本类型时,这意味着你无法在方法中更改参数引用所指向的对象。如下面的例子:

2个报错位信息为:

验证了以上结论。注释掉报错的两行,看看输出结果:

 

二.final方法

使用final方法的原因是把方法锁定住,以防任何继承类修改它的含义。即final方法不能被重写。测试一下:

可以看到编译器报错:不能重写父类的f()方法,因为该方法是final的。

 

private关键字与final

类中所有private方法都隐式的指定为final的,由于无法取用private方法,所以也就无法覆盖它。可以对private方法添加final修饰词,但并不能给该方法增加额外的意义。但这一问题会造成混淆。因为如果你试图覆盖一个private方法(隐含是final的),似乎是奏效的,而且编译器也不会给出错误信息。例如:

解析:覆盖只有在某方法是基类接口的一部分时才会出现,即,必须能将一个对象向上转型为它的基类类型并调用相同的方法。如果某方法为private,它就不是基类接口的一部分,它仅仅是一些隐藏于类中的程序代码,只不过是具有与导出类中方法相同的名称已。但如果在导出类中以相同的名称生成一个public,protected或包访问权限的方法的话,该方法就不会产生在基类中出现的“仅具有相同名称”的情况。此时你并没有覆盖基类的方法,仅仅是生成了一个新的方法,由于private方法无法及时而有效的隐藏,所以除了把它看成是因为它所归属的类的组织结构的原因而存在外,其他任何事物都不需要考虑到它。

所以,在导出类OverridingPrivate2中的f()和g()方法并没有覆盖它的父类中的方法,这些方法仅仅是它的新方法。当把op2向上转型为op时,op指向的对象仍然是op2指向的对象OverridingPrivate2,所以OverridingPrivate2是调用不到它的父类的私有(private)方法的。所以会出现下面编译器报的错:

对于向上转型为WithFinals类型的op来说也是一样的。

那么如何避免这样的情况发生呢?我们可以在导出类的方法上加上@Override注解,看加上注解后有没有报错以及报的是什么错误:

编译报错说方法并没有覆盖它基类的方法,所以这个方法并没有覆盖基类的方法。

 

三.final类

当final修饰类时,表明这个类无法被继承。但要注意,final类中的域可以选择是或不是final,不论类是否被定义为final,相同的规则都适用于定义final类中的域。但是,final类不能被继承,所以final类中的所有方法都隐式指定为final的,因为无法覆盖他们,所以在final类中可以给方法添加final修饰词,但不会有任何意义。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值