final
关键字可以修饰成员变量,方法,以及本地变量。如果将引用声明为final
l类型,那么该引用就不会再被改变,被final
修饰之后,就是一个不可变的东西。
final
变量
被final
修饰的变量(成员变量和局部变量)都称作为final
变量,其存储在常量池当中。final
变量经常和static
关键字一起使用,作为常量。
用final
修饰的变量,只能进行一次赋值操作,并且整个生命周期中不可改变。
不过在针对基本类型和引用类型时,final
关键字的效果有细微差别:
class Value {
int v;
public Value(int v) {
this.v = v;
}
}
public class TestFinal {
final int f1 = 1;
// value1 = 4; //无法再为value1赋值
final int f2;
public TestFinal() {
f2 = 2;
}
public static void main(String[] args) {
final int value1 = 1;
final double value2;
value2 = 2.0;
final Value value3 = new Value(1);
value3.v = 4;
}
}
value3是一个引用变量,final
修饰的引用变量,限定了引用变量的值不可变,即不能把value3
再次引用一个Value
对象,但是引用对象的值是可以改变的。
另一方面,因为final
修饰的数据的值不可改变,所以必须在使用前就已经对其赋值。有两种方法:
- 声明时直接赋值
- 构造方法中赋值
最后要注意: 同时使用static
和final
修饰的成员在内存中只占据一段不可改变的存储空间。
final
方法参数
很多时候,我们把变量作为参数传递时,会要求传过去的值不会改变。于是就会用final
来修饰了。即在参数前面添加final
关键字即可,它表示整个方法中,不改变参数的值。
public void finalFunc(final int i,final Value value){
//i=5; //error,不能改变i的值
//value=new Value(); //error,不能改变该引用的值
value.v=111; //可以改变引用对象的值
}
final
方法
final
修饰的方法,代表该方法不可被子类重写。若你认为一个方法的功能已经足够完整了,子类中不需要改变的话,可以声明此方法为final
。final
方法比非final
方法要快,因为在编译的时候已经静态绑定了,不需要在运行时再动态绑定。关于private
和final
关键字还有一点联系就是类中所有的private
方法都隐式地指定为是final的,由于无法在类外使用private
方法,所以也就无法覆盖它。下面是final方法的例子:
class PersonalLoan {
public final String getName() {
return "personal loan";
}
}
class CheapPersonalLoan extends PersonalLoan {
@Override
public final String getName() {
return "cheap personal loan"; // compilation error: overridden method is final
}
}
final
类
使用final
来修饰的类叫作final
类。final
类通常功能是完整的,它们不能被继承.java
中有许多类是final
的,譬如String, Interger
以及其他包装类。下面是final类的实例:
final class PersonalLoan{
}
class CheapPersonalLoan extends PersonalLoan{ //compilation error: cannot inherit from final class
final
关键字优点
- 提高了性能,
JVM
和java
应用都会缓存final
变量 final
变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。- 使用
final
关键字,JVM
会对方法、变量及类进行优化。
创建不可变类要使用final
关键字。不可变类是指它的对象一旦被创建就不能被更改。String
是不可变类的代表。不可变类有很多好处,譬如它们的对象是只读的,可以在多线程环境下安全的共享,不用额外的同步开销
几个易混点
1.类的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
}
}
得出的结果就是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";
}
}
2. 被final
修饰的引用变量指向的对象内容可变吗
public class Test {
public static void main(String[] args) {
final MyClass myClass = new MyClass();
System.out.println(++myClass.i); //输出为 1
}
}
class MyClass {
public int i = 0;
}
说明引用变量被final
修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的。
3.final
和static
很多时候会容易把static
和final
关键字混淆,static
作用于成员变量用来表示只保存一份副本,而final
的作用是用来保证变量不可变。
public class Test {
public static void main(String[] args) {
MyClass myClass1 = new MyClass();
MyClass myClass2 = new MyClass();
System.out.println(myClass1.i);
System.out.println(myClass1.j);
System.out.println(myClass2.i);
System.out.println(myClass2.j);
}
}
class MyClass {
public final double i = Math.random();
public static double j = Math.random();
}
运行这段代码就会发现,每次打印的两个j
值都是一样的,而i
的值却是不同的。从这里就可以知道final
和static
变量的区别了。