关于final修饰符你不知道的事

来源:互联网 发布:中国文化产业现状知乎 编辑:程序博客网 时间:2024/05/19 23:58

你可能知道:

final可以修饰变量,并在赋予初值后,不可以再改变

final可以修饰方法,该方法不能被重写

final可以修饰类,该类不能派生子类


但你是否知道以下关于final的其他重要知识?

1. final修饰实例变量:

final修饰的实例变量必须显式地指定初始值,而且该初始值只能在以下3个位置指定:

(1)定义时直接指定

比如:final int a=5;

(2)在非静态初始化块中指定

比如:class Test{

final int a;

{

a=5;

}

   }

(3)在类的构造器中指定

比如:class Test{

final int a;

public Test(){

a=5;

}

   }

实际上以上3种情况并没有太大的区别,看过http://blog.csdn.net/xuejingfu1/article/details/51480687java初始化实质这篇博文的同学会知道,在系统编译时,(1)和(2)中的代码实质上都会被提取到构造器中来执行初始化,只是其执行顺序和其在源代码中的顺序一致。


2.final变量与普通实例变量的区别:

在1.中已经说明final变量必须显式的指定初始值,而对普通实例变量来说,指定初始值并不是必须的,因为系统会为其指定默认的初始值。

比如:int a; String str;定义这两个普通变量不指定初始值的情况下,默认会有:a=0;str=null;

为了加深记忆,以下贴出一个小例子:

public class FinalTest {

//定义时赋初值
final int a1=1;
final int a2;
final int a3;

//非静态初始化块中赋初值
{
a2=2;
}

//构造器中赋初值
public FinalTest() {
a3=3;
}

public static void main(String[] args) {
FinalTest ft=new FinalTest();

System.out.println(ft.a1);
System.out.println(ft.a2);
System.out.println(ft.a3);
}

}

输出结果:

----------------------------

1

2

3

----------------------------

再次强调,经过编译器的处理,以上3种方式都会被抽取到构造器中赋初值。如果你依然不服,可以借助javap工具来分析编译器的编译过程。

以下是作者的分析截图:


3.final修饰变量

final修饰类变量时同样也必须指定初始值,可以在以下两个位置指定

(1)定义时直接指定

比如 static final int a1=1;

(2)在静态初始化块中指定

比如:class Test{

static final int a;

static{

a=5;

}

   }

这里也贴出一个小程序:

public class FinalTest2 {

//定义时赋初值
static final int a1=1;
static final int a2;

//静态初始化块中赋初值
static {
a2=2;
}

public static void main(String[] args) {

System.out.println(FinalTest2.a1);
System.out.println(FinalTest2.a2);
}

}

输出结果:

------------------------------------

1

2

------------------------------------

这里需要说明的是,以上两种方式,在经过编译器的处理后都会抽取到静态初始化块中赋予初值。



4.何种情况下final变量执行“宏替换

对于一个final变量,无论是类变量、实例变量,还是局部变量,只要在定义时指定了初值,并且该初值在编译时能够确定下来的话。那么这个final变量实质上就是一个直接量,又叫常量,宏变量。

看一个例子:

public class FinalTest3 {

final String s1="loveyou";
final String s2;
final String s3;
final String s4;
final String s5;

{
s2="love";
s3="you";
}

public FinalTest3() {
s4=s2+s3;
s5="love"+"you";
}

public static void main(String[] args) {
FinalTest3 ft=new FinalTest3();
System.out.println(ft.s1==ft.s4);
System.out.println(ft.s1==ft.s5);
}


}

输出结果:

-----------------------------

false

true

-----------------------------

因为s4变量的值在编译时并不能确定下来,所以s4不会当成“宏变量”来处理。

知识拓展:

java会缓存所有曾经用过的字符串直接量。例如执行final String s1="loveyou";后,系统的字符串池中会缓存一个字符串“loveyou”,此时执行s5="love"+"you";系统将会让s5指向字符串池中的“loveyou”,因此s1==s5返回true;


5. 局部内部类中访问的局部变量必须是final修饰的

首先要明确,所谓局部内部类就是一个方法体内的内部类。当然,匿名内部类肯定属于局部内部类

看一下例子:

import java.util.Arrays;

interface IntArrayGenerator{
//接口里定义的方法实际上都是抽象方法,用于封装处理行为
int generate();
}
public class TestInner {

public int[] getAarray(IntArrayGenerator generator,int length){
int[] result=new int[length];
for (int i = 0; i < result.length; i++) {
result[i]=generator.generate();
}
return result;

}

public static void main(String[] args) {

TestInner ti=new TestInner();
final int seed=5;  //注释1
int[] result=ti.getAarray(new IntArrayGenerator() {

@Override
public int generate() {
return (int) Math.round(Math.random()*seed);
}
}, 6);

System.out.println(Arrays.toString(result));
}


}

在注释1那行,如果seed变量没有用final修饰,会报错“Cannot refer to a non-final variable seed inside an inner class defined in a different method”,意思是不能在局部内部类里引用一个非final的变量。

那么,为什么java要规定局部内部类中访问的局部变量必须是final修饰的呢?

对于普通局部变量而言,它的作用域就是停留在该方法体内,当方法执行结束,该局部变量也随之消失;但内部类则可能产生隐式的“闭包Closure”,闭包将使得局部变量脱离他所在的方法继续存在。

public class TestClosure {

public static void main(String[] args) {
final String str="out";
new Thread(new Runnable() {

@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(str+" "+i);
}
try {
Thread.sleep(100);

} catch (Exception e) {
e.printStackTrace();
}
}
}).start();// 执行到此,main方法生命周期结束
}

}

程序正常输出,一般来讲,这里main方法结束,局部变量str的作用域也会结束,但只要线程里的run()方法没有执行完,匿名内部类的实例的生命周期就没有结束。将一直访问str局部变量的值,这就是内部类会扩大局部变量作用域的实例。想想看,如果这里str不用final修饰,这里仅仅是读str的值,如果是改变str的值呢,这将引起多大的混乱。

有关作用域方面的知识请点击http://blog.csdn.net/xuejingfu1/article/details/51513791

郑重声明:以上内容源于李刚老师的《突破程序员基本功的16课》以及加上作者自己的理解,总结以分享。


1 0
原创粉丝点击