知识点:

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修饰符:的更多相关文章

  1. Java final 修饰符知识点总结

    final从字面上理解含义为“最后的,最终的”.在Java中也同样表示出此种含义. final可以用来修饰变量(包括类属性.对象属性.局部变量和形参).方法(包括类方法和对象方法)和类. 1. fin ...

  2. Java中的final修饰符

    1.什么时候可以选择final修饰符 如果想让一个类不被其他类继承,不允许在有子类,这时候就要考虑用到final来修饰. 2.用final修饰的类 首先大家要明白,用final修饰的类是不能被继承的, ...

  3. 对于形式参数只能用final修饰符,其它任何修饰符都会引起编译器错误

    在Java中修饰符总共有一下几种: 1.访问控制修饰符    分别有:public private protected,缺省 2.其它修饰符      分别有:abstract,final,stati ...

  4. private static final 修饰符

    java修饰符分类修饰符字段修饰符方法修饰符根据功能同主要分下几种 1.权限访问修饰符 public,protected,default,private,四种级别修饰符都用来修饰类.方法和字段 包外 ...

  5. 类成员(static)和final修饰符

    在Java类里只能包含成员变量.方法.构造器.初始化块.内部类(包括接口.枚举)5种成员,类成员是用static来修饰的,其属于整个类. 当使用实例来访问类成员时,实际上依然是委托给该类来访问类成员, ...

  6. JAVA基础-栈与堆,static、final修饰符、内部类和Java内存分配

    Java栈与堆 堆:顺序随意 栈:后进先出(Last-in/First-Out). Java的堆是一个运行时数据区,类的对象从中分配空间.这些对象通过new.newarray.anewarray和mu ...

  7. as3 中 final 修饰符

    现在,在ActionScript 3.0的修饰符中,只有final修饰符没有介绍.之所有放在这里介绍,是因为final修饰符只与继承有关,指定一个方法不能被重写或一个类不能被继承. 一般来说,当用fi ...

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

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

  9. (五)final修饰符

    final修饰变量 final修饰符一般用于基本数据类型(int,float)或者不可变对象(String).这时候可以看作常量变量. 但是当final作用于可变数据类型时(数组,一般对象),仅仅表示 ...

随机推荐

  1. 无法打开物理文件 XXX.mdf"。操作系统错误 5:"5(拒绝访问。)"的解决办法

    附加数据库时报错: 无法打开物理文件 XXX.mdf".操作系统错误 5:"5(拒绝访问.)" 原因是数据库权限无法读取路径下的文件. 解决方案 一: 数据库使用wind ...

  2. Spring学习之AOP详解

    aop使用方式 @Aspect注解 wildcards通配符: * 匹配任意数量的字符 + 匹配指定类及其子类 .. 一般用于匹配任意数的子包或参数 operators运算符 && 与 ...

  3. 如何使用change命令改变密码的属性

    使用chage更改用户密码密码使用情况 -d //设置最近一次更改密码时间, 0下次登陆系统强制修改密码 -m //设置用户两次改变密码之间使用"最小天数" -M //设置用户两次 ...

  4. mysql 集群 数据同步

    mysql集群配置在网站负载均衡中是必不可少的: 首先说下我个人准备的负载均衡方式: 1.通过nginx方向代理来将服务器压力分散到各个服务器上: 2.每个服务器中代码逻辑一样: 3.通过使用redi ...

  5. VBA 禁止保存

    禁止保存 在workbook事件中 Private Sub Workbook_BeforeClose(Cancel As Boolean)    Me.Saved = TrueEnd Sub Priv ...

  6. EF时间模糊查询

    public List<Vote> SelectVoteByTime(string time) { return db.Vote.ToList().Where(x => x.V_Be ...

  7. mysql之explain

    ⊙ 使用EXPLAIN语法检查查询执行计划   ◎ 查看索引的使用情况   ◎ 查看行扫描情况   ⊙ 避免使用SELECT *   ◎ 这会导致表的全扫描   ◎ 网络带宽会被浪费   话说工欲善其 ...

  8. cacti客户端snmp设置

    1. ubuntu : apt-get install snmp snmpd vim /etc/default/snmpd  //将此配置文件中127.0.0.1 删掉. /etc/init.d/sn ...

  9. Social media users of the world unite!

    Social media users of the world unite!全世界社交媒体用户联合起来!If Plato were alive today, he might well regard ...

  10. QQ消息无限发送!源代码

    昨天我一个朋友发给我一个特别有趣的程序 可以无限发送QQ消息,非常有趣! 发送给朋友之后只要打开,便可自动发送消息. 点打开后 便可一直发送消息 用Edit plus 打开后  其源代码如下 是用VB ...