final
在 Java 中,final 是一个关键字,用于表示 “不可变的” 或 “最终的”。它可以修饰变量、方法和类,在不同场景下有不同的含义和用法。
final 修饰变量
当一个变量被 final 修饰时,该变量应该被叫做常量,它只能被赋值一次且在被使用之前必须被赋值。赋值之后,其值不能再被修改。这适用于基本类型和引用类型。
常量名的定义规范:单个单词组成时所有字母大写;多个单词组成时每个单词之间用下划线连接,如:XXX_YYY_ZZZ
基本类型变量
对于基本类型(如 int,double 等),final 使得该变量成为一个常量,其数值不可改变。
const 是 Java 保留的关犍字,但目前并没有使用。
final 修饰的常量可以放在 switch 语句的 case 后。
程序示例:
public static void main(String[] args) {
final int a = 20;
a = 10; // 报错: cannot assign a value to final variable 'a'
}
程序示例:
public static void main(String[] args) {
final int a; // 不使用 a 时不对 a 进行赋值是不会报错的
System.out.println(a); // 报错: Variable 'a' might not have been initialized
}
引用类型变量
对于引用类型,final 保证的是引用指向的对象地址不可变,即不能指向另一个对象,但对象内部的状态(成员变量)仍然可以改变。
final 引用只能保证引用本身不变,不保证对象内容不变。要实现真正的不可变对象,还需要类本身设计为不可变(如所有成员变量都是 final,且不提供修改内部状态的方法)。
程序示例:
public class Test {
public static void main(String[] args) {
final StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 允许,修改的是对象内容
System.out.println(sb); // 输出 "Hello World"
sb = new StringBuilder(); // 编译错误!不能重新赋值
}
}
程序示例:
JavaBean 类 Student 类:
public class Student {
public int age;
public String name;
public Student() {
}
public Student(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试类:
public class Test {
public static void main(String[] args) {
final Student S = new Student(23, "zhangSan"); // 用 final 修饰一个引用数据类型
S.setAge(24); // 对象内部的值可以改变
S.setName("liSi"); // 对象内部的值可以改变
System.out.println(S.getAge() + ", " + S.getName()); // 24, liSi
S = new Student(); // 报错: cannot assign a value to final variable 'S'
}
}
程序示例:
public class Test {
public static void main(String[] args) {
final int[] ARR = {1, 2, 3, 4}; // 用 final 修饰一个引用数据类型
ARR[0] = 20; // 对象内部的值可以改变
ARR[1] = 10; // 对象内部的值可以改变
for (int i = 0; i < ARR.length; i++) {
System.out.print(ARR[i] + " "); // 20 10 3 4
}
ARR = new int[3]; // 报错: cannot assign a value to final variable 'ARR'
}
}
程序示例:
public class Test {
public static void main(String[] args) {
final StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 允许,修改的是对象内容
System.out.println(sb); // 输出 "Hello World"
sb = new StringBuilder(); // 编译错误!不能重新赋值
}
}
成员变量
实例变量:可以在声明时、构造代码块中或构造方法中赋值,且必须保证在对象创建完成前被赋值。
程序示例:
public class Student {
final String name; // 空白 final 变量
public Student(String name) {
this.name = name; // 在构造方法中赋值
}
}
静态变量:可以在声明时或静态代码块中赋值。
程序示例:
public class Constants {
static final double PI = 3.14159; // 声明时赋值
static final long ID;
static {
ID = 1001L; // 静态代码块中赋值
}
}
局部变量
局部变量在使用前必须赋值,一旦赋值后不能再修改。
程序示例:
public class Test {
public void method() {
final int localVar = 10;
// localVar = 20; // 编译错误
}
}
参数
方法参数也可以声明为 final,表示在方法内部不能修改参数的值。
程序示例:
public class Test {
public void print(final int value) {
// value = 100; // 编译错误
System.out.println(value);
}
}
类常量
在 Java 中,类常量是指被 static final 关键字共同修饰的变量。它属于类级别(而不是对象级别),一旦初始化后其值就不能再被改变。类常量通常用于定义程序中通用的、不会变化的固定值。
类常量通过类名直接访问,无需创建对象。
类常量存储在方法区(或从 Java 8 开始,存储在堆中的 Class 对象中),在类加载时完成初始化。
所有实例共享同一份内存,不会因为创建多个对象而重复分配。
接口中定义的变量默认就是 public static final 的,因此接口中的变量也是类常量(但通常不推荐在接口中定义常量,而应使用枚举或专门的常量类)。
常量类:通常将相关的类常量放在一个专门的常量类中,便于集中管理。
程序示例:
public final class AppConstants {
private AppConstants() {} // 私有构造,防止实例化
public static final int TIMEOUT = 5000;
public static final String BASE_URL = "https://api.example.com";
}
类常量的定义
public class MathConstants {
// 类常量:属于类,值不可变
public static final double PI = 3.141592653589793;
public static final int MAX_COUNT = 100;
}
static:表示该变量属于类,所有实例共享一份内存,可以通过 类名.变量名 直接访问。
final:表示该变量只能被赋值一次,之后不可更改。
Math 类中的类常量:
类常量的分类
根据初始化时机和值的确定性,类常量可分为两类:
(1) 编译时常量
由基本类型或 String 类型定义,并且值在编译时就能确定。
编译器会直接将常量的值 “内联” 到使用它的代码中,不会产生对常量的引用。
示例:
public static final int AGE = 30;
public static final String NAME = "Alice";
(2) 运行时常量
值在运行时才能确定,例如通过方法调用、对象创建等。
即使使用 static final 修饰,它的值也是在类加载时初始化的,但不会发生编译期内联。
示例:
public static final long CURRENT_TIME = System.currentTimeMillis();
public static final String RUNTIME_CONFIG = loadConfig();
引用类型类常量
如果类常量的类型是引用类型(如数组、集合、自定义对象),final 仅保证引用不能指向另一个对象,但对象内部的状态仍然可以修改。
因此,定义引用类型的类常量时,如果希望集合本身也不可变,通常配合 Collections.unmodifiableXxx() 等方法包装。
程序示例:
public class Test {
public static final List<String> NAMES = new ArrayList<>();
public static void main(String[] args) {
// 允许修改列表内容
NAMES.add("Tom"); // 合法
NAMES.remove(0); // 合法
// 不允许重新赋值
// NAMES = new ArrayList<>(); // 编译错误
}
}
为什么字符串的值不可变:
字符串用数组 value 用来存储字符串的内容,数组 value 存储的是字节,因为 value 是用 final 修饰的,所以 value 保存的地址值不能发生改变。由于有 private 修饰符,所以外界无法获取到 value 的值,也就是获取不到这个数组的地址,并且 String.java 没有提供 value 的 getter() 和 setter() 方法,所以在外界不能获取到 value 记录的地址值。这里是用 final 和 private 两个关键字结合起来使用,使得字符串不能发生改变。
final 修饰方法
用 final 修饰方法就表示这个方法是最终的方法,不能被子类重写。
设计目的:防止子类修改父类中关键方法的实现,确保行为一致性和安全性(例如模板方法模式中的固定步骤)。
程序示例:
class Parent {
public final void show() {
System.out.println("This is final method.");
}
}
class Child extends Parent {
// 编译错误!不能重写 final 方法
// public void show() { }
}
final 修饰类
用 final 修饰一个类就表示这个类是最终的类,不能被继承,也就是不能有子类。
程序示例:
final class Utility {
// 工具类,不希望被扩展
}
// 编译错误!不能继承 final 类
// class SubUtility extends Utility { }
常见例子:Java 核心类库中的 String、Integer 等包装类都是 final 的,主要是为了安全性和不可变性,避免被子类修改行为。
浙公网安备 33010602011771号