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修饰成员变量赋初始值归纳如下:

类变量:必须在静态初始化块中赋初始值,或在声明该类变量时赋初始值,而且只能在这两个地方其中一个进行

实例变量:必须在普通初始化块、声明该实例变量时、构造器中赋初始值,而且只能在这三个地方其中的一个进行

实例变量不能在静态初始化块中赋初始值,原因参上

类变量不能在普通初始化块、构造器中赋初始值,原因参上

代码示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

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成员变量赋初始值,则不能在初始化之前就访问该成员变量,否则会报错:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

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修饰的形参,不能被赋值、修改,因为形参是方法在调用的时候,根据传入的参数来完成初始化,所以不能再次赋值

代码示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

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只是保证这个引用地址不被改变,而引用指向的对象完全可以改变

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

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、该初始值可以在编译时就确定下来

1

2

3

4

5

6

7

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、编译器会把程序中所有用到该变量的地方,直接替换成该变量的值

宏变量 的另一种情况:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

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 的常量池

宏替换:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

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的值就能确定下来

修改后代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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 实例变量而言,只有在定义该变量时赋初始值,才有 宏变量 的效果

上面代码在做修改,体会宏变量的本质:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

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 修饰的方法,编译会报错,如下代码就会编译报错:

1

2

3

4

5

6

7

8

9

10

11

class BaseClass{

    public final void test(){

         

    }

}

 

public class FinalMethodTest extends BaseClass{

    public void test(){

         

    }

}

编译时效果:

注意:

1、对于一个private方法,只在当前类中可见,子类中无法访问,所以子类也就无法重写该方法,这时如果子类中定义一个与父类private方法相同方法名、相同形参列表、相同返回类型的方法,这不算重写,只是子类自己新定义了一个方法而已

2、所以,即使使用 final 修饰了一个 private 的方法,其子类一样可以定义一个一样的方法,这不是重写,仅仅只是子类自己新定义的方法而已,如下代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

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修饰的方法仅仅是不能被重写,但可以被重载,如下代码是合法的:

1

2

3

4

5

6

7

8

9

10

11

12

13

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 修饰这个类

下面代码将会编译报错:

1

2

3

4

5

6

7

final class BaseClass{

    //-do sth

}

 

public class FinalOverrideClass extends BaseClass{

    //-do sth

}

 编译结果:

转载https://www.cnblogs.com/baby-zhude/p/8067538.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值