@ghimi
2018-09-15T02:18:00.000000Z
字数 3948
阅读 1294
Java
谈到 final
关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到 final
关键字.另外, Java 中的String
类就是一个final
类,那么今天我们就来了解 final
这个关键字的用法.下面是本文的目录大纲:
1. final
关键字的基本用法
2. 深入理解final
关键字
在 Java
中, final
关键字可以用来修饰类,方法和变量(包括成员变量和局部变量).下面就从三个方面来了解一下final
关键字的基本用法.
当用final
修饰一个类时,表名这个类不能被继承.也就是是说,如果一个类你永远不会让他被继承,就可以用final
进行修饰.final
类中的成员变量可以根据需要设为final
,但是要注意final
类中的所有成员方法都会被隐式的指定为final
方法.
final class People{
public People(){
}
}
class Man extends People{//检查性异常:类型 Man 不能成为终态类 People 的子类
}
在使用 final 修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者处于安全的考虑,尽量不要将类设计为 final 类.
下面这段话摘自第四版第143页:
使用final方法的原因有两个.第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率.在早期的Java版本中,会将final方法转为内嵌调用.但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升.在最近的Java版本中,不需要使用final方法进行这些优化了.
因此,如果只有在想明确禁止该方法在子类中被覆盖的情况下才将方法设置为final的.
注:类的private方法会隐式地被指定为final方法
修饰变量是 final 用的最多的地方,也是本文接下来要重点阐述的内容.首先了解一下 final 变量的基本语法:
对于一个 final 变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象.
举个例子:
class Man{
private final int i = 0;
public Man(){
i = 1;//检查性异常
final Object obj = new Object();
obj = new Object(); //检查性异常
}
}
上面的一段代码中,对变量i和obj的重新赋值都报错了.
在了解了 final 关键字的基本用法之后,这一节我们来看一下 final 关键字容易混淆的地方.
1. 类的final 变量和普通变量有什么区别?
当用 final 作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证使用之前被初始化赋值即可) 必须在定义时或者构造器中进行初始化赋值,而且 final 变量一旦别初始化赋值之后,就不能再被赋值了.
那么 final 变量和普通变量到底有什么区别呢? 下面请看一个例子:
public class Test {
public static void main(String[] args){
Stirng a = "hello2";
final String b = "hello";
String d = "hello";
String c = b + 2;
String e = d + 2;
System.out.println((a == c));
System.out.println((a == e));
}
}
true
false
大家可以先想一下这道题的输出结果.为什么第一个比较结果为 true ,而第二个比较结果为 false .这里面就是 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));
}
public static String getHello(){
return "hello";
}
}
这段代码的输出结果为 false
2. 被 final 修饰的引用变量指向的对象内容可变吗?
在上面提到被 final 修饰的引用变量一旦初始化赋值之后就不能再指向其他的对象,那么该引用变量指向的对象内容可变吗?看下面的例子:
public class Test {
public static void main(String[] args){
final MyClass myClass = new MyClass();
System.out.prinln(++myClass.i)
}
}
class MyClass {
public int i = 0;
}
这段代码可以顺利编译通过并且有输出结果,输出结果为1.这说明引用变量被 final 修饰之后,虽然不能再指向其他对象,但是它指向的对象内容是可变的.
3. final 和 static
很多时候会很容易吧 static 和 final 关键字混淆,static 作用于成员变量用来表示值保存一个副本,而 fianl 的作用是用来保证变量不可变.看下面这个例子:
public class Test {
public stataic void main(Stirng[] args){
MyClass myClass1 = new MyClass();
MyClass myClass2 = new MyClass();
System.out.println(myClass1.i);
System.out.prinltn(myClass1.j);
System.out.println(myClass2.i);
System.out.println(myClass2.j);
}
}
运行这段代码就会发现,每次打印的两个 j 值都是一样的,而 i 的值却是不同的.从这里就可以知道 final 和 static 变量的区别了.
4. 匿名内部类中使用的外部变量为什么只能是 final 变量?
除了匿名内部类内部,方法和作用于的内部类内部使用的外部变量也必须是 final 的。原因如下:
内部类会自动拷贝外部变量的引用,为了避免:
a. 外部方法修改引用,而导致内部类得到的引用值不一致
b. 内部类修改引用,而导致外部方法的参数值在修改前和修改后不一致。于是就用 final 来让该引用不可改变。
5. 关于 final 参数的问题
关于网上流传的"当你在方法中不需要改变作为参数的对象变量时,明确使用 final 进行声明,会防止你无意的修改而影响调用方法外的变量"这句话,我个人的理解这样说是不恰当的.因为无论参数是基本数据类型的变量还是引用类型的变量,使用 final 声明都不会达到上面所说的效果.
如下例:
public class Test {
public stataic void main(String[] args){
MyClass myClass = new MyClass();
int i = 0;
myClass.changeValue(i);
}
}
class MyClass{
void changeValue(final int i){
i++;//编译检查异常:不能对终态局部变量 i 赋值,它必须为空白,并且不适用复合赋值.
}
}
上面这段代码好像让人觉得用 final 修饰之后,就不能再方法汇总更改变量 i 的值了.殊不知,方法 changeValue 和 main 方法中的变量根本就不是一个变量,因为 java 参数传递采用的是值传递,对于基本类型的变量,相当于直接将变量进行了拷贝.所以即使没有 final 修饰的情况下,在方法内部改变了变量 i 的值也不会影响方法外的 i.
再看下面这段代码 :
public class Test{
public static void main(String[] args){
MyClass myClass = new MyClass();
StringBuilder buffer = new StringBuilder("hello");
myClass.changeValue(buffer);
System.out.println(buffer.toString());
}
}
class MyClass {
void changeValue(final StringBuilder buffer){
buffer.append("world");
}
}
运行这段代码就会发现输出结果为 helloworld.很显然,用 final 进行修饰并没有阻止在 changeValue 中改变 buffer 指向的对象的内容.有人说假如把 final 去掉了,玩意在 changeValue 中让 buffer 指向了其他对象怎么办.有这种想法的朋友可以自己手动写代码试一下这样的结果是什么,如果把 final 去掉了,然后在 changeValue 中让 buffer 指向了其他对象,也不会影响到 main 方法中的 buffer 原因在于 java 采用的是值传递,对于引用变量,传递的是引用的值,也就是说让实参和形参同时指向了同一个对象,因此让形参重新指向另一个对象对实参并没有任何影响.
所以关于网上流传的 final 参数的说法,我个人不是很赞同.