一、java中final关键字的基本用法
根据程序上下文环境,Java关键字final有“这是无法改变的”或者“终态的”含义,它可以用来修饰类、方法和变量(成员变量和局部变量)。你可能出于两种理解而需要阻止改变:设计或效率。
1.修饰类
当用final修饰一个类时,表明这个类不能被继承,没有子类。final类中的成员变量可以根据需要设置成final,但是成员方法默认都是final的。
public final class Fu {
public final String str = "mark";
}
//这里继承Fu类编译器会报错
class Zi extends Fu{
}
在设计类时候,如果这个类不需要有子类,类的实现细节不允许改变,并且确信这个类不会载被扩展,那么就设计为final类。一般可以将工具类设计为final类。
2.修饰方法
如果一个类不允许其子类覆盖某个方法,则可以把这个方法声明为final方法。 使用final方法有如下两点原因:
-
把方法锁定,方式任何继承类修改它的含义。
-
高效。在早期的java实现版本中,编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。在最近的Java版本中,不需要使用final方法进行这些优化了。
因此,如果只有在想明确禁止该方法在子类中被覆盖的情况下才将方法设置为final的。
在这里需要注意的一点是:重写的前提是子类可以继承父类的此方法,如果父类的final方法同时访问控制权限为private,则会导致子类中不能直接继承到此方法,此时可以在子类中定义相同的方法名和参数列表,不在是final与重写的矛盾,而是直接在子类中定义了一个新的方法。
注:类的private方法会隐式地被指定为final方法。
3.修饰变量
- 当final修饰的成员变量表示常量,一旦被赋值之后就不能在改变。
- 当final修饰一个引用类型时,则在其初始化之后便不能指向其他对象了,但是该引用所指向的对象的内容是可以发生变化的。本质上其实是一样的,因为引用的地址是一个值,final要求地址的值不能改变。
- final修饰一个成员变量(属性)时,必须要显示的进行初始化。这里初始化有两种方式:
1.在变量声明的时候进行初始化。
2.在声明变量的时候不赋初始值(这种变量成为final空白),但是要在这个属性所在的类的所有构造方法里面对这个变量赋初始值。
- 当函数的参数类型声明为final时,说明该参数是只读型的。即你可以读取使用该参数,但是无法改变该参数的值。
二、深入理解final关键字
1. 类的final变量与普通变量的区别
当final作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者类的构造方法里面进行初始化赋值,而且final变量一旦被赋值之后就不能再被改变了。
public class FinalTest {
public static void main(String[] args) {
String a = "helloworld";
final String b = "hello";
String d = "hello";
String c = b + "world";
String e = d + "world";
System.out.println((a == c));
System.out.println((a == e));
}
}
输出结果:
true
false
当final修饰的变量时基本数据类型以及String类型时,如果在编译期间能够知道它的确切值,则编译器会把它当做编译期常量来使用,不需要再运行时来确定。由于b被final修饰,因此会被当做编译器常量,所以在使用到b的地方会直接将b替换为它的值。然而对于变量d的访问却需要在运行时动态确定。
注:只有在编译期间能确切知道final变量值的情况下,编译器才会进行这样的优化。
eg:
public class FinalTest {
public static void main(String[] args) {
String a = "helloworld";
final String b = getHello();
String c = b + "world";
System.out.println((a == c));
}
private static String getHello() {
return "hello";
}
}
输出结果:
false
不要以为某些数据是final就可以在编译期知道其值,通过变量b我们就知道了,在这里是使用getHello()方法对其进行初始化,他要在运行期才能知道其值。
2. 被final修饰的引用变量指向的对象内容是否可变
eg:
public class FinalTest {
public static void main(String[] args) {
final Student stu = new Student();
System.out.println(stu.name);
stu.name = "mark";
System.out.println(stu.name);
}
}
class Student{
public String name = "james";
}
输出结果:
james
mark
引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的
3. 被final修饰的方法参数问题
被final修饰的参数代表了该参数是不可以被改变的。
public class FinalTest {
public static void main(String[] args) {
int count = 10;
change(count);
System.out.println(count);
}
private static void change(final int count) {
// count被final修饰,不能改变
// count = 20;
System.out.println(count);
}
}
1、上面这段代码change方法中的参数count用final修饰之后,就不能在方法中更改变量i的值了。
2、需要注意的是:java中参数传递采用的是值传递,对于基本类型的变量,传递的是实参的一份拷贝。所以即使没有final修饰形参,在方法内部修改了count变量的值也不会改变main方法里面count的值。
3、值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
4、引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
public class FinalTest {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("hello");
change(sb);
System.out.println(sb.toString());
}
private static void change(final StringBuffer str) {
str.append("world");
}
}
输出结果:
helloworld
用final进行修饰虽不能再让sb指向其他对象,但对于sb指向的对象的内容是可以改变的
如果去掉final,再来看看:
public class FinalTest {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("hello");
change(sb);
System.out.println(sb.toString());
}
private static void change(StringBuffer str) {
str = new StringBuffer("world");
System.out.println(str);
}
}
输出结果:
world
hello
去掉final之后,在change方法中让str指向了其他的对象,不会对main方法里面的buffer造成影响。原因在于java采用的是值传递,对于引用变量,传递的是引用的值,也就是说让实参和形参同时指向了同一个对象,因此让形参重新指向另一个对象对实参并没有任何影响。