final 是Java 中重要关键字之一,可以应用于类、方法以及变量上。
1. final变量
一旦将变量声明为 final,你就不能改变这个引用了,编译器会检查代码,如果试图将变量再次初始化的话,编译器会报编译错误。来看代码例子
public class Test {
public static void main(String[] args) {
String a = "world2"; //普通变量
final String b = "world"; //定义final变量,在声明阶段进行初始化
String d = "world";
String c = b + 2;
String e = d + 2;
System.out.println("c is:" +c);
System.out.println("e is:" +e);
// System.out.println("modify b value:" +(b++)); //编译会报错,不能修改final变量
System.out.println((a == c)); //true
System.out.println((a == e)); //false
}
}
结果:
c is:world2
e is:world2
true
false
除了值不能更改之外,普通变量与final修饰的变量的另一个区别是内存申请的区别
普通变量,在执行拼接后赋予另一个变量,会在内存区域重新开辟一块儿进行存储。
对于final,则能够直接进行改变而不会进行重新开辟内存空间,相当于常量了。
具体来说 final string 在编译的时候,值已经确定,b可替换成"world",所以c就是 "world"+"2",这个形式的初始化会被编译器优化成"world"存在字符串常量区,由于b已经被初始化为"world",这个字符串已经存在于常量区,所以直接返回其引用,a==c。
e的值在编译时不确定,不能编译优化,所以e=d+"2",jvm会在堆中生成一个值为"world2"的对象,返回引用给e,所以a不等于e。
关于string的内存分配在本系列《第7章 字符串String类(随笔)》有详细介绍
final变量的初始化
final修饰的变量只能通过初始化程序或赋值语句初始化一次。如果final修饰的变量在声明期间未初始化,则必须在声明它的类的构造函数中对其进行初始化。这样的变量也称为空白最终变量。任何在构造函数之外设置空白最终变量的尝试都将导致编译错误。
同样,如果一个 静态的 final 变量在声明时没有初始化,那么它必须在声明它的类的静态初始化块中初始化。这样的变量称为空白静态最终变量。任何在静态初始化器之外设置空白静态最终变量的尝试都将导致编译错误。
来看代码例子:
class Point
{
public final int x = 1; // final变量,直接赋值阶段进行初始化
public final int y; // 空白最终变量
public static final int z; // 空白静态最终变量
// 构造函数
public Point() {
y = 2; //在构造函数中进行实例化
}
// 静态初始化块
static {
z = 3; //在static代码块中进行实例化
}
}
class Main
{
public static void main(String[] args)
{
Point pt = new Point();
System.out.println("(" + pt.x + "," + pt.y + "," + pt.z + ")");
}
}
结果:
(1,2,3)
2. final方法
首先,final方法不能被重写。
将方法定义为final类型可以防止任何子类修改该类的定义与实现方式,同时定义为final的方法执行效率要高于非final方法。
来看代码例子
class A {
public final void show() {
System.out.println("A.show()");
}
}
class B extends A {
// 编译错误:Cannot override the final method from A
public void show() {
System.out.println("B.show()");
}
}
public class Main {
public static void main(String[] args) {
A a = new A();
a.show(); // 输出 "A.show()"
B b = new B();
b.show(); // 编译错误:Cannot override the final method from A
}
}
如果一个方法被声明为 final,那么子类就不能重写这个方法,从而保证了父类的行为不会被修改。
举例:
class Animal {
final public void eat() {
System.out.println("Animal is eating");
}
}
class Dog extends Animal {
// public void eat() {
// System.out.println("Dog is eating"); //不能重写final方法,否则会在编译阶段报错
// }
}
class Main {
public static void main(String[] args) {
Dog myDog = new Dog();
myDog.eat();
}
}
输出:
Animal is eating
这在某些情况下是非常有用的,例如在 Java 中,Object 类中的 notify() 和 wait() 方法就是 final 方法,这样可以确保所有子类都遵循相同的实现。
来看 wait() 方法代码:
/**
* Causes the current thread to wait until it is awakened, typically
* by being <em>notified</em> or <em>interrupted</em>.
* <p>
* In all respects, this method behaves as if {@code wait(0L, 0)}
* had been called. See the specification of the {@link #wait(long, int)} method
* for details.
*
* @throws IllegalMonitorStateException if the current thread is not
* the owner of the object's monitor
* @throws InterruptedException if any thread interrupted the current thread before or
* while the current thread was waiting. The <em>interrupted status</em> of the
* current thread is cleared when this exception is thrown.
* @see #notify()
* @see #notifyAll()
* @see #wait(long)
* @see #wait(long, int)
*/
public final void wait() throws InterruptedException {
wait(0L);
}
3. final类
final类不能被继承。
这在一些情况下是非常有用的。例如,假设我们有一个类 Animal,它有一个方法 eat(),现在我们想要创建一个子类 Dog,它继承自 Animal 类。如果我们希望 Dog 类不能继承 Animal 类,我们可以将 Animal 类声明为 final 类。这样,Dog 类就不能继承 Animal 类了。
来看代码例子:
final class Animal {
public void eat() {
System.out.println("Animal is eating");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("Dog is eating");
}
}
class Main {
public static void main(String[] args) {
Dog myDog = new Dog();
myDog.eat();
}
}
结果会报错:
java: 无法从最终Animal进行继承