关于final关键字的两个面试题

一、介绍

final关键字常用,但真的了解它吗。我做一下记录,仅供参考

  • 简述final的作用

  • 为什么局部内部类和匿名内部类只能访问局部的final变量

二、简述final的作用

final是一个修饰符,表示最终的,不可被修改

  • 修饰类:表示类不可被继承,所以抽象类不能使用final关键字

  • 修饰方法:表示方法不能被重写,但是可以进行重载

  • 修饰变量:表示赋值后就不能修改该变量的值

修饰变量时,有几种情况

  • 修饰成员变量时

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class Demo {

    // 声明时直接赋值
    final int a = 1;

    // 在代码块中进行赋值
    final int b;
    {
    b = 2;
    }

    // 在构造函数中进行赋值
    final int c;
    public Demo(int c) {
    this.c = c;
    }
    }
  • 修饰局部变量时

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Demo0 {

    public static void main(String[] args) {
    final int a = 1;// 一旦赋值无法改变
    final int b;// 在声明时,可以不赋值
    b = 2;// 在使用前一定要进行赋值
    System.out.println(a + b);
    }
    }
  • 修饰静态变量时

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Demo {

    // 直接赋值
    static final int a = 1;

    // 在静态代码块中赋值
    static final int b;
    static {
    b = 2;
    }

    }
  • 修饰基本数据类型和引用类型时

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class Demo {

    public static void main(String[] args) {
    final int a = 1;
    a = 2;// 不可修改

    final Person person = new Person("半月", 18);
    person.setName("半月无霜");// 可以进行修改,final声明的只是person这个局部变量
    person = new Person("半月无霜", 22);// 不允许修改
    }
    }

    @Data
    @AllArgsConstructor
    class Person {
    String name;
    int age;
    }

三、局部内部类和匿名内部类

为什么局部内部类和匿名内部类只能访问局部的final变量

这的确是一个好问题,我们一起来看看

先简单写出局部内部类和匿名内部类,发现他们使用外部的变量时,外部变量都要用final修饰,不然idea直接爆红了。

image-20220305184215565

image-20220305190654868

Variable used in lambda expression should be final or effectively final

简单翻译一下:在lambda表达式中使用变量应该是final或者有效的final

还有局部内部类,也有这样的问题

Variable ‘number1’ is accessed from within inner class, needs to be final or effectively final

在内部类中访问number1,需要final或者有效的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
package com.banmoon.algorithm.order;

public class Demo {

public static void main(String[] args) {
Demo demo = new Demo();
demo.test1(10);
demo.test2(10);
}

public void test1(int number1){
int number2 = 1;
number1 = number1 + number2;
number2 = 10;
final int finalNumber = number1;
final int finalNumber1 = number2;
new Thread(() -> {
System.out.println(finalNumber);
System.out.println(finalNumber1);
}).start();
}

public void test2(int number1){
int number2 = 1;
number1 = number1 + number2;
number2 = 10;
int finalNumber = number1;
int finalNumber1 = number2;
class InnerClass {
public void print(){
System.out.println(finalNumber);
System.out.println(finalNumber1);
}
}
}
}

为什么不能直接使用呢,原因也很简单。

因为,在进行编译java文件时,上面提到了内部类会生成独立的class文件

那么问题随之而来,匿名内部类和局部内部类想要使用外部的变量该怎么办,编译时候它是怎么做的?它直接将外部的变量复制了一份在内部类中。

好的那么,问题又来了。外部类中有一份,内部类中也有一份,数据不一致该怎么办。干脆一刀切算了,直接使用final关键字吧。

像上面示例的27和28行,虽然它没有final修饰,但也可以编译通过,这两个变量已经不会再做更改了。不过在使用内部类的时候,还是得小心,最好还是加上final

四、最后想说的话

我是半月,祝你幸福!!!