final修饰符:
知识点:
1、final关键字用于修饰类、变量和方法
2、有点类似C#里的 sealed 关键字,用于表示它修饰的方法、变量和类不可以再被改变
3、final修饰变量时,表示该变量一旦获取了初始值,就不可以再被改变
4、final可以修饰成员变量(包括类变量、实例变量),也可修饰局部变量、形参
5、有的书上说final修饰的变量不能被赋值,这是错误的,严格的说,fanal修饰的变量,一旦被赋值就不可再次赋值改变
6、因为final修饰的变量赋值后不可再被改变,所以final修饰的成员变量和局部变量有一些不同
一、final修饰成员变量:
1、成员变量是随类的初始化(类变量)或类对象实例的初始化(实例变量)而初始化的
2、当类初始化时,系统会为类变量分配内存,并赋初始值;当类对象初始化时,系统会为实例变量分配内存,并赋初始值
3、当执行类静态初始化块时,系统可以为类变量赋初始值,当执行类普通初始化块、构造器时,系统可以为实例变量赋初始值
4、因此,成员变量可以在定义变量时赋初始值,也可以在静态初始化快、普通初始化块、构造器内给赋初始值
5、对于final修饰的成员变量而言,一旦有了初始值,就不可再次赋值
6、如果既没有在定义成员变量时赋初始值,也没有在初始化块、构造器中赋初始值,那么这些成员变量就失去了意义,因为不能在普通方法中为final修饰的成员变量赋值
7、因此Java规定,final修饰的成员变量必须由程序员显示的赋初始值
final修饰成员变量赋初始值归纳如下:
类变量:必须在静态初始化块中赋初始值,或在声明该类变量时赋初始值,而且只能在这两个地方其中一个进行
实例变量:必须在普通初始化块、声明该实例变量时、构造器中赋初始值,而且只能在这三个地方其中的一个进行
实例变量不能在静态初始化块中赋初始值,原因参上
类变量不能在普通初始化块、构造器中赋初始值,原因参上
代码示例:
public class FinalVariableTest{ //-定义实例变量时赋初始值,合法
public final int a=6; //-定义实例变量时,先不赋初始值,放在普通初始化块或构造器中赋初始值,合法
public final String str;
public final int c; //-定义类变量时,先不赋初始值,放到静态初始化块中赋初始值,合法
public final static double d; //-定义实例变量,先不赋初始值,但是后面的普通初始化块、构造器中都没有赋初始值,不合法
//public final char ch; static{
//-静态初始化块内,给类变量赋初始值,合法
d=5.5;
} {
//-普通初始化块内,给未付初始值的实例变量赋初始值,合法
str="hello"; //-普通初始化块内,给已经赋初始值的实例变量赋值,不合法
//a=9;
} public FinalVariableTest(){ //-构造器内,对已经在普通初始化快内赋值的实例变量再次赋值,不合法
//str="java"; //-构造器内,给未赋初始值的实例变量赋初始值,合法
c=5;
} public void changFinal(){ //-普通方法内,给final修饰符修饰过的成员变量赋值,违法
//d=1.2;
//ch='A';
} public static void main(String[] args){
FinalVariableTest fin=new FinalVariableTest();
System.out.println(fin.a);
System.out.println(fin.c);
System.out.println(fin.d);
System.out.println(fin.str);
}
}
运行结果:
如果要在初始化块或构造器中给final成员变量赋初始值,则不能在初始化之前就访问该成员变量,否则会报错:
public class FinalErrorTest{ //-定义一个final修饰的实例变量,未付初始值,合法
//-而且系统不会为final修饰的变量赋初始值
public final int age; {
//-age实例变量未赋初始值,直接输出不合法,会报错
//-如果把修饰符final去掉,则下面输出就合法了
//System.out.println(age); //-在普通初始化块中,给未赋初始值的、final修饰的实例变量赋初始值,合法
age=3; //-已经赋初始值,输出合法
System.out.println(age);
} public FinalErrorTest(){ } public static void main(String[] args){
FinalErrorTest fin=new FinalErrorTest();
}
}
二、final修饰局部变量:
1、系统不会为局部变量进行初始化
2、局部变量必须由程序员显式初始化
3、final修饰的局部变量,可以在声明时赋初始值,也可以在后面赋值,但只能赋值一次。
4、final修饰的形参,不能被赋值、修改,因为形参是方法在调用的时候,根据传入的参数来完成初始化,所以不能再次赋值
代码示例:
public class FinalLocalVariableTest{
public void test(final int a){
//-final修饰的形参,不能被赋值、修改
//-因为形参是方法在调用的时候,根据传入的参数来完成初始化,所以不能再次赋值,下面代码编译会报错
//a=5;
} public static void main(String[] args){
final String str="hello";
//-final 修饰的 str 变量声明时已经赋值,不能再次被赋值,下面代码编译会报错
//str="java"; final double b;
b=3.6; //-final 修饰的 b 变量已经赋值,不能再次被赋值,下面代码编译会报错
//b=6.5;
System.out.println(str);
}
}
运行结果:
final修饰基本类型变量,和引用类型变量的区别:
1、final修饰基本类型变量时,不能对基本类型变量重新赋值,因为基本类型变量不能被改变
2、对于引用类型变量而言,保存的仅仅是一个引用地址,final只是保证这个引用地址不被改变,而引用指向的对象完全可以改变
import java.util.Arrays;
class Penson{
private int age; public Penson(){ } public Penson(int age){
this.age=age;
} public void setAge(int age){
this.age=age;
} public int getAge(){
return this.age;
}
} public class FinalReferenceVariable{
public static void main(String[] args){
//-final 修饰的 array 引用变量是一个数组
final int[] array={5,69,8,6}; //-输出数组
System.out.println(Arrays.toString(array)); //-给数组重新排序,没改变array引用的地址,还是指向这个数组对象,所以合法
Arrays.sort(array);
System.out.println(Arrays.toString(array)); //-改变数组内的某个值,没改变array引用的地址,还是指向这个数组对象,所以合法
array[2]=-8;
System.out.println(Arrays.toString(array)); //-改变了array引用变量的地址,所以非法,编译报错
//array=null; //-final修饰的p对象,是一个Penson实例对象
final Penson p=new Penson(23); //-修改了p对象中的age变量,但是p对象引用没改变,还是指向p对象,所以合法
p.setAge(30);
System.out.println(p.getAge()); //-修改了p对象的引用地址,所以非法,编译报错
//p=null;
}
}
运行结果:
总结:final 修饰的引用变量不能被重新赋值,但是引用变量所指向的对象内容完全可以改变
可执行宏替换的 final 变量:
1、final修饰的变量,不管是类变量、实例变量、局部变量,只要满足3个条件,该变量就不再是一个变量,而是一个直接量:
.1、使用final 修饰符修饰
.2、定义该 final 变量时使用了初始值
.3、该初始值可以在编译时就确定下来
public class FinalLocalVariable{
public static void main(String[] args){
//-定义一个final局部变量 并赋初始值
final int a=3;
System.out.println(a);
}
}
运行结果:
如上代码所示:对于这个程序来说,变量 a 根本不存在,当执行 System.out.println(a); 代码时,实际转换为执行:System.out.println(5);
由此产生了一个名字:宏变量:
1、final 修饰符的一个重要用途,就是定义 宏变量
2、当定义final变量的时候给定了初始值,并且该初始值在编译的时候就能确定下来,该变量实质上就是一个 宏变量
3、编译器会把程序中所有用到该变量的地方,直接替换成该变量的值
宏变量 的另一种情况:
public class FinalReplaceVariable{
public static void main(String[] args){
//-定义了 4个 final 局部变量,都是 宏变量
final int a=3+5;
final double b=1.5/3;
final String str1="张三"+"李四";
final String str2="张三"+"李四"+99.0; //-final 修饰的 局部变量 str3 因为调用了方法,无法在编译的时候 把值确定下来 所以不是宏变量
final String str3=str1+ String.valueOf(99.0); System.out.println(str1=="张三李四");
System.out.println(str2=="张三李四99.0");
System.out.println(str3=="张三李四99.0"); }
}
如上代码所示:
1、final变量赋值,如果被赋值的表达式只是基本的算术表达式,或基本的字符串连接运算,没有访问普通变量、调用方法,JVM在编译时依然可以把值确定下来,该变量依然可以当做 宏变量
2、当做宏变量的变量,在参与运算时,将直接用后面的值来代替 宏变量,关于这一点,原理其实就是 java 的常量池
宏替换:
public class FinalStringJoin{
public static void main(String[] args){ String a="张三李四";
String b="张三"+"李四"; String c="张三";
String d="李四"; //-将字符串直接量 c、d 进行连接运算
//-e变量的值,无法在编译时就确定下来,所以变量e无法指向常量池中缓存的 字符串直接量
String e=c+d; //-true
System.out.println(a==b);
//-false
System.out.println(a==e);
}
}
运行结果:
如上代码所示:
1、在编译时值就能确定下来的 变量,可以直接指向常量池中缓存的直接量,指向常量池缓存中同一个直接量的变量,程序可以执行 宏替换, 是相等的
2、在编译阶段值无法确定下来的变量,不可以共享常量池中缓存的直接量,程序不能执行 宏替换, 不能判断相等
3、让上面代码中 a与e相等,只需要把 c和d定义为final变量,使他们称为 宏变量,这样在编译阶段,系统将会执行 宏替换,把c和d都替换成常量池缓存中对应值,所以在编译阶段e的值就能确定下来
修改后代码如下:
public class FinalStringJoinEquals{
public static void main(String[] args){ //-定义两个普通字符串直接量,编译时值就能确定下来
String a="张三李四";
String b="张三"+"李四"; //-定义两个final 宏变量,在用到 这两个宏变量的地方,可以直接执行 宏替换
final String c="张三";
final String d="李四"; //-e变量的值,在编译时就能确定下来,因为c和d是宏变量,编译时就可以执行 宏替换
String e=c+d; //-true
System.out.println(a==b);
//-true
System.out.println(a==e);
}
}
运行结果:
总结:
1、对于实例变量而言,在声明时、普通初始化块中、构造器中,这三个地方赋初始值是一样的
2、对于final 实例变量而言,只有在定义该变量时赋初始值,才有 宏变量 的效果
上面代码在做修改,体会宏变量的本质:
public class FinalStringJoinEquals{
public static void main(String[] args){ //-定义两个普通字符串直接量,编译时值就能确定下来
String a="张三李四";
String b="张三"+"李四"; //-定义两个final 局部变量,未付初始值,合法
final String c;
final String d; //-给两个final局部变量 赋初始值,合法
c="张三";
d="李四"; //-e变量的值,在编译时不能确定下来,因为c和d不再是宏变量,编译时不可以执行 宏替换
String e=c+d; //-true
System.out.println(a==b);
//-false
System.out.println(a==e);
}
}
运行效果:
称为宏变量的条件总结:
1、必须 final 修饰符修饰
2、必须在声明时就赋初始值
3、必须在程序编译时,变量值就能确定下来
以上三个条件少一个,该变量就不是 宏变量 ,在后面的执行中,就不会执行宏替换
三、final修饰方法:
1、final修饰的方法不可被重写
2、如果不希望子类重写父类的某个方法,可以用 final 修饰该方法
3、Object类中的 getClass() 方法,就是 final 修饰的,不可被重写,toString()、equals()方法不是 final 修饰的,因此允许其子类 重写这两个方法
4、子类重写父类中 final 修饰的方法,编译会报错,如下代码就会编译报错:
class BaseClass{
public final void test(){ }
} public class FinalMethodTest extends BaseClass{
public void test(){ }
}
编译时效果:
注意:
1、对于一个private方法,只在当前类中可见,子类中无法访问,所以子类也就无法重写该方法,这时如果子类中定义一个与父类private方法相同方法名、相同形参列表、相同返回类型的方法,这不算重写,只是子类自己新定义了一个方法而已
2、所以,即使使用 final 修饰了一个 private 的方法,其子类一样可以定义一个一样的方法,这不是重写,仅仅只是子类自己新定义的方法而已,如下代码:
class BaseClass{
//-本类私有的,子类不可见,更不可重写
private final void test(){
System.out.println("BaseClass类的 test() 方法 ");
}
} public class PrivateFinalMethodTest extends BaseClass{ //-不会报错,不是重写,只是本类自己定义的方法而已
public void test(){
System.out.println("PrivateFinalMethodTest类的 test() 方法,不是重写! ");
} public static void main(String[] args){
new PrivateFinalMethodTest().test();
}
}
运行结果:
5、final修饰的方法仅仅是不能被重写,但可以被重载,如下代码是合法的:
public class FinalOverloadMethod{
public final void test(){ }
//-final修饰的方法,只是不能被重写,完全可以被重载
public final void test(String str){ } public static void main(String[] args){
//-do sth
}
}
四、final 修饰类:
1、final 修饰的类不可以有子类,java.lang.Math类就是 final 修饰的类,它不可以有子类
2、如果想保证某个类不被继承,可以用 final 修饰这个类
下面代码将会编译报错:
final class BaseClass{
//-do sth
} public class FinalOverrideClass extends BaseClass{
//-do sth
}
编译结果:
final修饰符:的更多相关文章
- Java final 修饰符知识点总结
final从字面上理解含义为“最后的,最终的”.在Java中也同样表示出此种含义. final可以用来修饰变量(包括类属性.对象属性.局部变量和形参).方法(包括类方法和对象方法)和类. 1. fin ...
- Java中的final修饰符
1.什么时候可以选择final修饰符 如果想让一个类不被其他类继承,不允许在有子类,这时候就要考虑用到final来修饰. 2.用final修饰的类 首先大家要明白,用final修饰的类是不能被继承的, ...
- 对于形式参数只能用final修饰符,其它任何修饰符都会引起编译器错误
在Java中修饰符总共有一下几种: 1.访问控制修饰符 分别有:public private protected,缺省 2.其它修饰符 分别有:abstract,final,stati ...
- private static final 修饰符
java修饰符分类修饰符字段修饰符方法修饰符根据功能同主要分下几种 1.权限访问修饰符 public,protected,default,private,四种级别修饰符都用来修饰类.方法和字段 包外 ...
- 类成员(static)和final修饰符
在Java类里只能包含成员变量.方法.构造器.初始化块.内部类(包括接口.枚举)5种成员,类成员是用static来修饰的,其属于整个类. 当使用实例来访问类成员时,实际上依然是委托给该类来访问类成员, ...
- JAVA基础-栈与堆,static、final修饰符、内部类和Java内存分配
Java栈与堆 堆:顺序随意 栈:后进先出(Last-in/First-Out). Java的堆是一个运行时数据区,类的对象从中分配空间.这些对象通过new.newarray.anewarray和mu ...
- as3 中 final 修饰符
现在,在ActionScript 3.0的修饰符中,只有final修饰符没有介绍.之所有放在这里介绍,是因为final修饰符只与继承有关,指定一个方法不能被重写或一个类不能被继承. 一般来说,当用fi ...
- Java中final修饰符深入研究
一.开篇 本博客来自:http://www.cnblogs.com/yuananyun/ final修饰符是Java中比较简单常用的修饰符,同时也是一个被"误解"较多的修饰符.对很 ...
- (五)final修饰符
final修饰变量 final修饰符一般用于基本数据类型(int,float)或者不可变对象(String).这时候可以看作常量变量. 但是当final作用于可变数据类型时(数组,一般对象),仅仅表示 ...
随机推荐
- Solr --- Group查询与Facet区别
简介 facet的查询结果主要是分组信息:有什么分组,每个分组包括多少记录:但是分组中有哪些数据是不可知道的,只有进一步搜索. group则类似于关系数据库的group by,可以用于一个或者几个字段 ...
- MongoDB集群与LBS应用系列(二)--与Hadoop集成
长期以来,我每开个系列,只有兴趣写一篇,很难持之与恒.为了克服这个长久以来的性格弱点,以及梳理工作半年的积累.最近一个月会写两篇关于Mongo在地理大数据方面的实践和应用,一篇关于推荐系统的初期准备过 ...
- relocation error: /usr/lib64/libc.so.6: symbol _dl_starting_up, version GLIBC_PRIVATE not defined in file ld-linux-x86-64.so.2 with link time reference 问题解决
在建立一个错误的软连接到ld-linux-x86-64.so.2时,悲剧就这么发生了.此时大部分命令都不能使用,SSH当然也不能登录了.这个时候一定不要退出终端. 有人说那就把软连接复原吧,可是ln也 ...
- laravel5.4安装的报错
laravel5.4安装的报错 [InvalidArgumentException] Could not find package laravle/installer at any version f ...
- SpringMVC 之@RequestBody 接收Json数组对象
1. 摘要 程序流程: 前台使用ajax技术,传递json字符串到后台: 后台使用Spring MVC注解@RequestBody 接受前台传递的json字符串,并返回新的json字符串到前台: 前台 ...
- shiro 框架
惊天给大家总结一点shiro框架的小知识 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理.使用Shiro的易于理解的API,您可以快速.轻松地获得任何应 ...
- vue基础——组件基础
一.基本示例 这里有一个Vue组件的示例: // 定义一个名为 button-counter 的新组件 main.js Vue.component('button-counter', { data: ...
- js脚本代码调试小技巧
以前写js代码调试代码查看数据是否正确的时候不知道F12(开发者工具),都是alert(xxx)或者console.log(xxx), 现在知道还可以用document.write或者try...ca ...
- HIBERNATE知识复习记录4-HQL和QBC
Hibernate中共提供了三种检索方式:HQL(Hibernate Query Language).QBC.QBE(Query By Example). HQL 是Hibernate Query L ...
- 关于str.split(",")中间 什么时候该加\\转义
java 分割符,对于某些符号的分割符进行切割的时候需要加转义字符,我贴上例子 1. package test1; public class split { public static void ma ...