final的几种用法

“谈谈final、finally、finalize有什么不同?”这是个非常经典的问题。finally是Java保证重点代码一定要被执行的一种机制。finalize是基础类java.lang.Object的一个方法,它的设计目的是保证对象在被垃圾收集前完成特定资源的回收。今天我们重点谈谈final,多年的面试经验告诉我,很多人其实失分就是在final上,而非另外两者。

final可以用来修饰类、方法、变量,分别有不同的意义。final修饰的class代表不可被继承扩展;final修改的方法表示不可被重写(override);final修饰的变量是不可被修改的。如果你能够给出上面的回答,至少及格了。但是这样的回答隐藏掉了很多信息,这里真正有变化的是当final修饰变量的时候。

首先以上叙述还不够准确,精确地说“final修饰的变量一旦被赋值将不可被修改”,请看如下代码:

public class MyClass {
  public MyClass(int foo) {
    this.foo = foo;
  }
  private final foo;
}

在这段代码中类MyClass的成员变量foo被修饰为final,但并没有对其赋值,而是在构造函数中为其指定的取值。当然你不能就此认为“final修饰的变量是不可被修改的”说法错误,但至少没有表达出这层意思。其实Java语言这样设计是有道理的,利用这一特性可以达到这样的目的:当我们发明一个类的时候,有时会发现有一些需要外部传入的参数,我们希望一旦被确定后,在对象被销毁之前不希望其被改变。遇到这种情况,我们就可以将这些参数定义为final的成员变量,然后外界只能够通过构造函数的参数指定。

final是否可以用来修饰函数参数或者内部变量?答案是肯定的。事实上这种行为是被推荐的,因为这样可以确保变量不会被意外修改。而且被final修饰过的变量与参数还可以被传递给匿名类,例如:

public void sort(List<String> list, final boolean asc) {
  Collections.sort(list, new Comparator<String>() {
    @Override
    public int compare(String o1, String o2) {
      return asc ? o1.compareTo(o2) : o2.compareTo(o1);
    }
  });
}

Java在实现内部类时,实际会拷贝一份,不是去直接使用局部变量,final在这里可以防止出现数据一致性的问题。

外界有很多文章或者数据中还会介绍,final也许会有性能的好处。比如,利用final可能有助于JVM将方法进行内联,可以改善编译器进行条件编译的能力等等。坦白说,很多类似的结论都是基于假设得出的,比如现在高性能JVM(如HotSpot)判断内联未必依赖final的提示,要相信JVM还是非常智能的。类似的,final变量对性能的影响,大部分情况下,并没有考虑的必要。不要指望这样的小技巧带来所谓的性能好处。相反,使用final更重要的理由,应该是为提高代码可读性,防止变量被意外修改

最后还要强调,final虽然会带来不可变的效果,但并不意味着任何情况下都是如此。比如final修饰java.util.List类型的变量,并不会防止列表内容被修改。实际的效果是防止变量指向的List对象不会被再次创建。而如果你需要的是一个不允许修改的List,则可以使用Collections.unmodifiableList()得到。

<think>我们正在讨论final变量的初始化方式。根据引用内容,final变量根据其类型(成员变量或类变量)和上下文有不同的初始化要求。 回顾引用内容: 引用[1]:final修饰的全局变量必须在构造函数执行完成之前初始化。可以通过显式赋值或构造函数初始化。 引用[2]:展示了在构造函数中初始化final静态成员变量,但实际上这是错误的,因为静态变量不能在构造函数中初始化(引用[3]和[4]解释了原因)。 引用[3]和[4]:详细说明了类变量(静态变量)和成员变量的初始化方式: - 成员变量(实例变量)初始化方式: 1. 声明时直接赋值 2. 在构造代码块中赋值 3. 在每个构造函数中赋值(必须保证每个构造函数都对其赋值) - 类变量(静态变量)初始化方式: 1. 声明时直接赋值 2. 在静态代码块中赋值 引用[5]:提供了代码示例,其中静态final变量k在静态代码块中初始化,实例final变量j在构造函数中初始化,m在构造代码块中初始化。 因此,总结如下: 一、final成员变量(实例变量)的初始化方式: 1. 声明时直接初始化:如 `private final int i = 3;` 2. 在构造代码块中初始化:如 private final int k; { k = 4; } 3. 在每个构造函数中初始化:确保每个构造函数(包括重载的)都对该final变量赋值。注意:如果在构造代码块中已经初始化,则不能在构造函数中再次赋值(因为只能赋值一次)。 二、final类变量(静态变量)的初始化方式: 1. 声明时直接初始化:如 `private final static int p = 3;` 2. 在静态代码块中初始化:如 private final static int q; static { q = 3; } 注意:静态final变量不能通过构造函数初始化,因为构造函数在创建对象时调用,而静态变量属于类,在类加载时就必须初始化完成。如果尝试在构造函数中初始化静态final变量,会导致每个对象创建时都尝试赋值一次,这是不允许的(因为final变量只能赋值一次)[^3][^4]。 另外,对于局部final变量,可以在声明时初始化,也可以在后面使用之前初始化(但只能初始化一次)。 下面用代码示例说明: ```java public class FinalInitializationExample { // 静态final变量初始化 private static final int STATIC_FINAL1 = 10; // 方式1:声明时初始化 private static final int STATIC_FINAL2; static { STATIC_FINAL2 = 20; // 方式2:静态代码块中初始化 } // 实例final变量初始化 private final int instanceFinal1 = 30; // 方式1:声明时初始化 private final int instanceFinal2; private final int instanceFinal3; { // 构造代码块 instanceFinal2 = 40; // 方式2:构造代码块中初始化 } public FinalInitializationExample() { // 在构造函数中初始化 instanceFinal3 = 50; // 方式3:构造函数中初始化 } public FinalInitializationExample(int value) { // 另一个构造函数也必须初始化所有未初始化的final实例变量 instanceFinal3 = value; // 注意:instanceFinal2已经在构造代码块中初始化,这里不能再赋值 } } ``` 注意事项: 1. final变量一旦初始化后就不能重新赋值。 2. 对于实例变量,必须在对象创建完成(构造函数结束)之前初始化。如果通过构造函数初始化,则每个重载的构造函数都必须确保对该变量进行赋值(或者通过构造代码块已经赋值,那么构造函数中就不需要再赋值,且不能再次赋值)。 3. 对于静态变量,必须在类加载完成(即类初始化阶段)完成初始化,且只能初始化一次。 因此,根据final变量的不同类型,选择合适的初始化方式。 §§相关问题§§ 1. 为什么final静态变量不能在构造函数中初始化? 2. 在构造代码块和构造函数中同时初始化同一个final变量会导致什么问题? 3. final局部变量有哪些初始化规则? 4. Java中final关键字还可以修饰什么?各有什么作用? </think>### `final`变量的初始化方式详解 在Java中,`final`变量根据其类型(成员变量或类变量)有不同的初始化规则,核心原则是:**必须在首次使用前完成初始化,且只能赋值一次**。以下是详细分类: --- #### 一、类变量(Static Final) 类变量属于类级别,必须在**类加载阶段**完成初始化: 1. **声明时直接初始化** ```java public static final int MAX_VALUE = 100; // ✅ 直接赋值 ``` 2. **静态代码块初始化** ```java public static final int CONFIG_VALUE; static { CONFIG_VALUE = loadConfig(); // ✅ 静态块赋值 } ``` **禁止在构造函数中初始化**: ```java public static final int ERROR_VALUE; public MyClass() { ERROR_VALUE = 10; // ❌ 编译错误![^2][^3] } ``` *原因*:构造函数在每次实例化时调用,违反`final`单次赋值原则[^4]。 --- #### 二、成员变量(Instance Final) 成员变量属于对象级别,必须在**对象构造完成前**初始化: 1. **声明时直接初始化** ```java private final String id = UUID.randomUUID().toString(); // ✅ ``` 2. **构造代码块初始化** ```java private final Date createTime; { createTime = new Date(); // ✅ 构造块赋值 } ``` 3. **构造函数初始化** ```java private final String name; public MyClass(String name) { this.name = name; // ✅ 构造函数赋值 } ``` *关键要求*:**所有重载构造函数都必须初始化**未初始化的`final`成员[^1]: ```java private final int count; public MyClass() { count = 0; } // ✅ public MyClass(int n) { count = n; } // ✅ // 若缺少初始化则编译失败 ``` --- #### 三、局部变量(Local Final) 局部变量在方法/代码块中使用: ```java void calculate() { final double PI; // 声明 PI = 3.14159; // ✅ 后续初始化 final int MAX = 100; // ✅ 声明时初始化 // PI = 3.14; // ❌ 禁止二次赋值 } ``` *规则*:只需在**首次使用前**初始化即可[^5]。 --- ### 初始化冲突禁止场景 | 错误类型 | 错误示例 | 原因 | |--------------------------|----------------------------------|--------------------------| | 构造函数未初始化 | `public MyClass() {}` | 未保证所有路径初始化[^1] | | 静态变量在构造函数赋值 | `static final int x;`<br>`构造函数{x=1;}` | 多次赋值风险[^3] | | 构造块+构造函数重复赋值 | `{id=1;}`<br>`构造函数{id=2;}` | 违反单次赋值原则[^4] | > ⚠️ **关键区别**: > - 类变量(`static final`):初始化与类绑定(类加载时) > - 成员变量(`final`):初始化与对象绑定(构造函数完成前) ```mermaid graph TD A[final变量] --> B[类变量 static final] A --> C[成员变量 final] A --> D[局部变量] B --> B1[初始化方式1: 声明赋值] B --> B2[初始化方式2: 静态代码块] C --> C1[初始化方式1: 声明赋值] C --> C2[初始化方式2: 构造代码块] C --> C3[初始化方式3: 构造函数] D --> D1[首次使用前赋值] ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值