前言引入
final用于声明属性、方法和类,分别表示属性不可变、方法不可覆盖和类不可被继承
一、final关键字的基本用法
二、深入理解final关键字
三、final 域的内存语义(线程中)扩展
一、final关键字的基本用法
final属性
对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
举例代码
:被final修饰的引用变量不可变。这里的不可变有两种含义:
- 引用不可变
- 对象不可变
举例说明
public class Test {
public static void main(String[] args) {
final StringBuffer sb = new StringBuffer("Hello");
sb.append(" world");
System.out.println(sb);
}
}
运行结果:
举例代码:
- 从上面的例子可以看出,引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的。final关键字指的是引用不可变性,即它只能指向初始化时指向的那个对象,而不关心指向指向对象内容的变化。所以,被final修饰的变量必须初始化。
final变量初始化的几种方式
- 在定义的时候初始化 ;
- final变量可以在初始化块中初始化,但不可以在静态初始化块中初始化 ;
- 静态final成员变量可以在静态初始化块中初始化,但不可在初始化块中初始化;
- 在类的构造器中初始化,但静态final成员变量不可在构造函数中初始化
final 方法
下面这段话摘自《Java编程思想》第四版第143页:
“使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用(inline)。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。“
因此,如果只有在想明确禁止 该方法在子类中被覆盖的情况下才将方法设置为final的。
final类
当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。
- 在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。一般基本的类型为防止扩展类无意间破坏原来方法的实现类型都应该是final的,在JDK中String、StringBuffer等都是基本类型,所以String、StringBuffer等类是不能继承的。
二、深入理解final关键字
1.类的final变量和普通变量有什么区别?
当用final作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值了。
那么final变量和普通变量到底有何区别呢?下面请看一个例子:
public class Test {
public static void main(String[] args) {
String a = "hello2";
final String b = "hello";
String d = "hello";
String c = b + 2;
String e = d + 2;
System.out.println((a == c)); // true
System.out.println((a == e)); // false
}
}
大家可以先想一下这道题的输出结果。为什么第一个比较结果为true,而第二个比较结果为fasle。这里面就是final变量和普通变量的区别了,当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。这种和C语言中的宏替换有点像。
- 因此在上面的一段代码中,由于变量b被final修饰,因此会被当做编译器常量,所以在使用到b的地方会直接将变量b 替换为它的
值。而对于变量d的访问却需要在运行时通过链接来进行。想必其中的区别大家应该明白了,不过要注意,只有在编译期间能确切知道final变量值的情况下,编译器才会进行这样的优化,
比如下面的这段代码就不会进行优化:
public class Test {
public static void main(String[] args) {
String a = "hello2";
final String b = getHello();
String c = b + 2;
System.out.println((a == c)); // false
}
public static String getHello() {
return "hello";
}
}
三、final 域的内存语义(线程中)扩展
多线程的中的知识在这块补充一下。
** final 域的重排序规则**
对于final域,编译器和处理器要遵守两个重排序规则。
- 在构造函数内对一个final域的写入,与随后把这个被构造函数对象的引入用赋值给引用变量,这两个操作之间不能重排序;
- 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。
补充问题
-
被final修饰的类,能不能继承其它类呢?即public final class Son extends Father{}
可以
-
被final修饰的方法,能不能被子类继承?
可以
-
被final修饰的方法,能不能在本类中重载?
可以
问题:
final修饰的类,变量,方法有什么特点?ABC
A:被final修饰的类,表明该类是最终类,不能被继承
B:被final修饰的方法,表明该方法是最终方法,不能被重写
C:被final修饰的变量,表明该变量是常量,不能再次被赋值,可以初始值
D:缺省