final 关键字
- final 关键字
- final 字段
- final 函数列表中的参数
- final 方法
- final 类
final 关键字
Java中里面有final这个关键字,这个关键字总体上是用来表达” 不能被改变“ 这个意思的。我们使用这个关键字表达不能被改变,有两种使用场景,有三个使用位置。
使用场景
- 设计上就不允许改变的
- 为了效率不允许被改变
使用位置
- 字段
- 方法
- 类
我们将从使用位置着手,帮助大家了解final关键字的使用。
final 字段
很多时候,我们需要表达常量这个概念。我们都知道Java是面向对象的编程语言,在Java中一切都是对象Object;但是除此之外,Java还有8中基本数据类型。
所以final修饰的字段要么是8中基本数据类型,要么是Object。修饰基本类型的时候,代表的是修饰变量的值不能变化;修饰Object引用的时候,代表的是这个变量代表的引用不能改变(引用不能改变,但是引用指向的对象是可以改变的)
那既然是常量,不能被改变。那么我们怎么赋予其初始值呢?
- 直接初始化 : 可以在声明的时候直接给final变量赋值。这是最简单的方式,适用于你知道变量的确切值并且它不会改变的情况。
public class Example {public final int MAX_SIZE = 100;
}
- 构造器初始化 : 如果你需要根据不同的构造条件来设置final变量的值,那么可以在构造器中初始化它们。每个构造器都必须对所有的final实例变量进行初始化,否则编译器会报错。
public class Example {private final int maxSize;public Example(int size) {this.maxSize = size; // 必须在所有构造函数中初始化}
}
静态final变量 初始化也有两种
- 直接初始化
public class Example {public static final int MAX_SIZE = 100;
}
- 静态块初始化
public class Example {public static final int[] VALUES;static {// 更复杂的初始化逻辑VALUES = new int[3];VALUES[0] = 1;VALUES[1] = 2;VALUES[2] = 3;}
}
fianl修饰变量 - 代码案例
public class Final {private static final int VALUE_ONE = 1;private final int VALUE_TWO = 2;public static final int VALUE_THREE = 3;private final int int4 = Random.class.newInstance().nextInt(20);private Value v1 = new Value("1");private final Value v2 = new Value("2");private static final Value v3 = new Value("3");private final int[] array = {1, 2, 3, 4, 5};public Final() throws InstantiationException, IllegalAccessException {}public static void main(String[] args) throws InstantiationException, IllegalAccessException {Final f = new Final();f.v2.value = "tes";f.v1 = new Value("new");//f.v2 = new Value("test2");for (int i = 0; i < f.array.length; i++) {f.array[i]++;}for (int i = 0; i < f.array.length; i++) {System.out.print(f.array[i]);}}
}class Value {String value;public Value(String value) {this.value = value;}
}
final 函数列表中的参数
Java允许你用final 修饰参数列表中的形参,这个意味着,在这个函数中不能修改这个形参。如果这个形参代表的是基本参数类型,意味着函数体中不能修改参数值;如果修饰的是引用,代表我们不能修改引用。
public class Test8 {public static void main(String[] args) {System.out.println(add(1, 2));System.out.println(add(new Tmp1(1), new Tmp1(2)));}public static int add(final int a, final int b) {//a++;return a + b;}public static int add(final Tmp1 a, final Tmp1 b) {//a = new Tmp1("5");a.a = 5;b.a = 10;return a.a + b.a;}
}class Tmp1 {int a;public Tmp1(int a) {this.a = a;}
}
final 方法
使用final修饰方法主要有两个方面的考虑
- 当一个方法被声明为final时,它不能被子类重写或覆盖,确保了父类的特定行为不会被改变,从而维护了父类的行为一致性。这对于那些你希望保持不变的核心功能特别有用。
- 编译器知道final方法不会被覆盖,因此可以在某些情况下进行内联优化。内联意味着直接将方法体插入到调用点,减少方法调用的开销,提升执行效率。
** final 和 private**
final关键字跟private关键字也有关系。任何类中的私有方法跟final差不多。因为我们没有获取私有方法的权限,这也就意味着,我们不能改变父类中的私有方法的实现。
private final 代码案例展示
public class Test9 {public static void main(String[] args) {OverRiddingBaseClass1 overRiddingBaseClass1 = new OverRiddingBaseClass1();overRiddingBaseClass1.f();overRiddingBaseClass1.f1();// BaseClass baseClass = overRiddingBaseClass1;
// baseClass.f();
// baseClass.f1();
// OverRiddingBaseClass overRiddingBaseClass = overRiddingBaseClass1;
// overRiddingBaseClass.f();
// overRiddingBaseClass.f1();}
}class BaseClass {private final void f() {System.out.println("BaseClass.f()");}private void f1() {System.out.println("BaseClass.f1()");}
}class OverRiddingBaseClass extends BaseClass {private final void f() {System.out.println("OverRiddingBaseClass.f()");}private void f1() {System.out.println("OverRiddingBaseClass.f1()");}
}class OverRiddingBaseClass1 extends OverRiddingBaseClass {public final void f() {System.out.println("OverRiddingBaseClass1.f()");}public void f1() {System.out.println("OverRiddingBaseClass1.f1()");}
}
Overriding 只会发生在父类非私有的方法中,上面代码案例中我们在最下面的子类把f()改成自己的实现,实际上这个方法是public,父类的是private;子类中的方法只是同名,不能算是父类的Overriding。
final 类
在Java中,使用final关键字修饰类(class)意味着该类不能被继承。这也就是说,任何试图创建此类子类的尝试都将导致编译错误。final类是不可扩展的,它提供了一些重要的好处,同时也带来了一定的限制。
- 防止继承:
- 定义:当一个类被声明为final时,它不能作为其他类的父类。
- 好处:确保了该类的行为和状态不会被改变或扩展,从而维护了类的完整性和一致性。
public final class FinalClass {// 类成员和方法
}
- 提高安全性
- 防止恶意扩展:final类可以防止其他开发人员通过继承来修改类的行为,特别是在库或框架中公开的API中,这对于保护敏感逻辑或数据非常重要。
- 避免意外破坏:减少了因继承带来的潜在错误风险,例如子类可能无意间改变了父类的关键行为。
- 促进编译器优化
- 性能提升:由于final类的方法不能被重写,编译器可以在某些情况下对这些方法进行内联调用等优化操作,从而提高程序执行效率。
- 静态绑定:对于final类的方法调用,编译器可以直接确定要调用的方法版本,无需在运行时动态查找最合适的版本。
- 表达设计意图
- 清晰的设计语义:通过将类声明为final,开发者明确表达了不允许对该类进行任何扩展的设计意图。这有助于其他开发人员理解代码库中的不可变规则和限制条件。
- 文档化:final关键字起到了一种自我文档的作用,告诉其他程序员哪些类是固定的,哪些是可以自由扩展的。
- 支持不可变对象
- 构建不可变类:为了实现不可变性,通常会将所有方法都设为final,并且让类本身也成为final。这样可以确保一旦对象创建后其状态不会发生变化,有助于构建线程安全的对象,因为在多线程环境中,不可变对象天生就是线程安全的。
- 简化调试和维护
- 减少复杂度:当类不能被继承时,追踪其行为变得更为简单,因为你不需要考虑不同子类中可能存在的各种重写版本。
- 降低维护成本:由于final类的行为是固定的,所以在进行代码审查或维护时,可以更放心地依赖这些类的一致性表现,减少了需要考虑的变量和可能性。
- 限制
- 灵活性降低:final类不能被继承,这意味着如果以后需要扩展类的功能,必须通过组合或其他设计模式来实现,而不是直接继承。
- 代码复用受限:因为不能继承,所以不能利用继承机制来复用final类中的代码。
final修饰class使用场景
- 核心类库:如Java标准库中的String、Integer等包装类都是final的,以保证它们的安全性和一致性。
- 工具类:当类主要包含静态方法,并且不打算被实例化时,可以将其声明为final,如Math类。
- 安全敏感类:涉及敏感信息处理或系统资源管理的类应该考虑设为final,以防止未经授权的修改。
- 不可变类:为了实现线程安全和不可变性,类及其成员应尽可能设为final。