1 final
的使用
在Java
中,final
关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。
1.1 final
修饰变量
被final
修饰的变量表示常量,一旦赋值便无法更改。对于基本数据类型,是数值本身,对于引用数据类型是对象的地址。
1.2 final
修饰方法
在方法前面加上final
关键字,代表这个方法不可以被子类的方法重写。 如果一个方法的功能已经足够完整了,子类中不需要改变的话,可以声明此方法为final
。final
方法比非final
方法要快,因为在编译的时候已经静态绑定了,不需要在运行时再动态绑定。
1.3 final
修饰类
使用final
来修饰的类叫作final
类。final
类通常功能是完整的,它们不能被继承。Java
中有许多类是final
的,比如String
,Interger
以及其他包装类。
final
类中的成员变量可以根据需要设为final
,但是要注意final
类中的所有成员方法都会被隐式地指定为final
方法。
2 深入理解final
关键字
2.1 类的final
变量和普通变量的区别
当用final
作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者构造器中进行初始化赋值,而且final
变量一旦被初始化赋值之后,就不能更改了。
那么final
变量和普通变量到底有何区别呢?
public class Test {
public static void main(String[] args) {
String a = "hello2";
final String b = "hello";
String c = "hello";
String d = b + 2;
String e = c + 2;
System.out.println(a == d); // true
System.out.println(a == e); // false
}
}
为什么第一个比较结果为true
,而第二个比较结果为fasle
。这里面就是final
变量和普通变量的区别了,当final
变量是基本数据类型以及String
类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final
变量的地方,相当于直接访问的这个常量,不需要在运行时确定。因此在上面的一段代码中,由于变量b
被final
修饰,因此会被当做编译器常量,所以在使用到b
的地方会直接将变量b
替换为它的值。而对于变量d
的访问却需要在运行时通过链接来进行。 不过要注意,只有在编译期间能确切知道final
变量值的情况下,编译器才会进行这样的优化,比如下面的这段代码就不会进行优化:
public class Test {
public static void main(String[] args) {
String a = "hello2";
final String b = getHello();
String d = b + 2;
System.out.println(a == d); // false
}
public static String getHello() {
return "hello";
}
}
2.2 被final
修饰的引用变量指向的对象内容可变吗?
public class Test {
public static void main(String[] args) {
final TestClass testClass = new TestClass();
testClass.i = 1;
System.out.println(++testClass.i); // 2
}
}
class TestClass {
public int i = 0;
}
这段代码可以顺利编译通过并且有输出结果,输出结果为2
。这说明引用变量被final
修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的。
2.3 final
和static
static
作用于成员变量用来表示只保存一份副本,而final
的作用是用来保证变量不可变。 看下面这个例子:
public class Test {
public static void main(String[] args) {
TestClass testClass1 = new TestClass();
TestClass testClass2 = new TestClass();
System.out.println(testClass1.i); // 0.23917028365795312
System.out.println(testClass1.j); // 0.4634959316433682
System.out.println(testClass2.i); // 0.9060017513700431
System.out.println(testClass2.j); // 0.4634959316433682
}
}
class TestClass {
public final double i = Math.random();
public static double j = Math.random();
}
运行这段代码就会发现,每次打印的两个j
值都是一样的,而i的值却是不同的。从这里就可以知道final
和static
变量的区别了。
2.4 关于final
参数的问题
关于网上流传的“当你在方法中不需要改变作为参数的对象变量时,明确使用final
进行声明,会防止你无意的修改而影响到调用方法外的变量”这句话,这样说是不恰当的。因为无论参数是基本数据类型的变量还是引用类型的变量,使用final
声明都不会达到上面所说的效果。
对于基本数据类型:
上面这段代码好像让人觉得用final
修饰之后,就不能在方法中更改变量i的值了。但是,方法changeValue
和main
方法中的变量i
根本就不是一个变量,因为对于基本类型的变量,参数传递采用的是值传递,相当于直接将变量进行了拷贝。所以即使没有final
修饰的情况下,在方法内部改变了变量i
的值也不会影响方法外的i
。
对于引用数据类型:
public class JavaTest {
public static void main(String[] args) {
TestClass testClass = new TestClass();
StringBuffer buffer = new StringBuffer("hello");
testClass.changeValue(buffer);
System.out.println(buffer); // helloworld
}
}
class TestClass {
void changeValue(final StringBuffer buffer) {
buffer.append(" world");
}
}
运行这段代码就会发现输出结果为hello world
。用final
进行修饰并没有阻止在changeValue
中改变buffer
指向的对象的内容。假如把final
去掉了,然后在changeValue
中让buffer
指向其他对象,也不会影响到main
方法中的buffer
,对于引用变量,传递的是对象的地址,也就是说让实参和形参同时指向了同一个对象,因此让形参重新指向另一个对象对实参并没有任何影响。
参考
https://www.cnblogs.com/dolphin0520/p/3736238.html