final关键字的基本用法
在java中,final关键字可以用来修饰类、方法、变量(包括成员变量和局部变量)。下面我们从这三个方面了解一下final的用法
1、修饰类
final修饰一个类时,表示该类不能继承。final类中的成员变量可以根据需要设为final,但是final类中的所有成员方法都会被隐式地指定为final方法。
注意:在使用final修饰类的时候,一定要谨慎选择,除非这个类以后不会用来继承或者出于安全考虑,尽量不要将类设计为final类。
2、修饰方法
”使用final修饰方法的主要原因是把方法锁定,防止任何继承类修改它的含义”。因此,只有在想明确禁止该方法在子类中被覆盖的情况下才将方法设置为final的。
注:“类的private方法会隐式的被指定为final方法”。
3、修饰变量
修饰变量是final用的最广的地方,也是要重点理解的地方。先了解一下final变量的基本语法:
对于一个final变量,如果是基本数据类型的变量,则其数值一但被初始化之后便不可以修改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
class Man{
private final int i = 0;
public Man(){
i = 1;//报错
final Object obj = new Object();
obj = new Object();//报错
}
}
深入探讨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
}
}
代码分析:
大家都知道String是一个final类,final类的特点就是共享数据,==是地址的比较,而final类的String相同的字符串,指向的是同一个地址!String类中用“”中的字面值创建对象时,都会先在串池空间中查找,如果有就返回字符串地址,没有在串池里创建一个对象。如果是new在堆空间创建String类对象,则不会有这个过程(String做字符串连接也是一样,每连接一次相当于创建一个对象)。
那么我在看一下输出结果,为什么第一个是true,第二个是false。这里面就是final变量和普通变量的区别了,当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。由于变量b被final修饰,因此,会被当做编译器常量,所以在使用到b的地方会直接将变量b替换为它的值。而对于变量d的访问却需要在运行时通过链接来进行。
不过要注意,只有在编译期间能确切知道final变量值的情况下,编译器才会进行这样的优化,比如下面的这段代码就不会进行优化:
java代码:
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;
}
代码分析:
输出结果为1。这说明引用变量被final修饰之后,虽然不能在指向其他对象,但是它指向的对象内容是可变的。
3、final与static区别?
static作用于成员变量(即静态变量)在内存中只有一个拷贝(为了节约内存),jVM只会为静态变量分配一次内存,在加载过程中完成静态变量的内存分配,可用类名直接访问。而final只是保证变量不可改变,不可改变的是引用的指向,具体的引用指向的值是可以改变的。
public class test00 {
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的值却是不同的。
4、为什么局部内部类和匿名内部类只能访问局部final变量?
public class Test {
public static void main(String[] args) {
}
public void test(final int b) {
final int a = 10;
new Thread(){
public void run() {
System.out.println(a);
System.out.println(b);
};
}.start();
}
}
public class Test {
public static void main(String[] args) {
}
public void test(final int a) {
new Thread(){
public void run() {
System.out.println(a);
}
}.start();
}
}
我们都知道经过编译后会得到两个.class文件。然后,我们分析过程,当test方法执行完毕后,变量a的生命周期就结束了,而此时Thread对象的生命周期有可能还没有结束,那么在Thread的run方法中继续访问a是不可能的了,但是为了实现这种效果,java采用了“复制”的手段来解决。在编译期间编译器会默认复制一个本地局部变量。如果这个变量的值在编译期间可以确定,则编译器默认会在匿名内部类(局部内部类)的常量池中添加一个内容相等的字面量或者直接将相应的字节码嵌入到执行字节码中。所以,匿名内部类中使用的变量是另一个局部变量,只不过值和方法中的局部变量相等。如果局部变量的值在编译期间无法确定,则通过构造器传参的方式对拷贝进行初始化赋值。所以为了避免数据值的不一致性,必须限定变量为final类型。