假设有以下代码:
/**
*
*/
package com.gsoft.geloin;
import junit.framework.Assert;
import junit.framework.TestCase;
import org.junit.Test;
/**
* @author Geloin
*
*/
public class MyTest extends TestCase {
@Test
public void test() throws Exception {
Base b = new Base();
Assert.assertEquals(b.count, 2);
Assert.assertEquals(b.display(), 2); // (1)
Derived d = new Derived();
Assert.assertEquals(d.count, 20);
Assert.assertEquals(d.display(), 20); // (2)
Base bd = new Derived();
Assert.assertEquals(bd.count, 2);
Assert.assertEquals(bd.display(), 20); // (3)
Base bd2 = d;
Assert.assertEquals(bd2.count, 2);
Assert.assertEquals(bd2.display(), 20); // (4)
}
}
class Base {
int count = 2;
public int display() {
System.out.println(this.count);
return this.count;
}
}
class Derived extends Base {
int count = 20;
public int display() {
System.out.println(this.count);
return this.count;
}
}
显然大部分人对(1)和(2)处的代码没有异议,而通过阅读 《对象与内存控制》一文,则可参(3)和(4)的代码进行解释:对于实例变量而言,其值为编译时的对象对应的实例变量的值,而对于方法而言,其行为取决于运行时对象。
假设有以下代码:
/**
* @author Geloin
* @date 2013-10-24 上午10:31:26
*/
package com.gsoft.geloin;
/**
* @author Geloin
* @date 2013-10-24 上午10:31:26
*
*/
public class FinalInstanceVaribaleTest {
final int var1 = 3;
final int var2;
final int var3;
{
var2 = 2;
}
public FinalInstanceVaribaleTest() {
var3 = 1;
}
public static void main(String[] args) {
FinalInstanceVaribaleTest fiv = new FinalInstanceVaribaleTest();
System.out.println(fiv.var1);
System.out.println(fiv.var2);
System.out.println(fiv.var3);
}
}
打开cmd,进入FinalInstanceVaribaleTest所在的目录,然后分别执行以下代码:
javac FinalInstanceVaribaleTest.java
javap -c FinalInstanceVaribaleTest.java
显示界面如下所示:
Compiled from "FinalInstanceVaribaleTest.java"
public class com.gsoft.geloin.FinalInstanceVaribaleTest extends java.lang.Object{
final int var1;
final int var2;
final int var3;
public com.gsoft.geloin.FinalInstanceVaribaleTest();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_3
6: putfield #2; //Field var1:I
9: aload_0
10: iconst_2
11: putfield #3; //Field var2:I
14: aload_0
15: iconst_1
16: putfield #4; //Field var3:I
19: return
public static void main(java.lang.String[]);
Code:
0: new #5; //class com/gsoft/geloin/FinalInstanceVaribaleTest
3: dup
4: invokespecial #6; //Method "<init>":()V
7: astore_1
8: getstatic #7; //Field java/lang/System.out:Ljava/io/PrintStream;
11: aload_1
12: invokevirtual #8; //Method java/lang/Object.getClass:()Ljava/lang/Class;
15: pop
16: iconst_3
17: invokevirtual #9; //Method java/io/PrintStream.println:(I)V
20: getstatic #7; //Field java/lang/System.out:Ljava/io/PrintStream;
23: aload_1
24: getfield #3; //Field var2:I
27: invokevirtual #9; //Method java/io/PrintStream.println:(I)V
30: getstatic #7; //Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_1
34: getfield #4; //Field var3:I
37: invokevirtual #9; //Method java/io/PrintStream.println:(I)V
40: return
}
javap是java的反编译工具,具体使用方法请教度娘。
被final修饰的实例变量必须显式指定初始值,而且只能在如下三个位置指定初始值:
(1) 定义final实例变量时指定初始值;
(2) 在非静态初始化块中为final实例变量指定初始值;
(3) 在构造器中为final实例变量指定初始值。
由反编译工具的结果可知:经过编译器处理,final实例变量本质上只能在构造器中被赋初始值。由此可见,final实例变量不能被再次赋值(调用它时,总是通过构造子调用的)。
那如果是类变量,也就是在final前面再加上static呢?且看如下代码:
/**
* @author Geloin
* @date 2013-10-24 上午10:31:26
*/
package com.gsoft.geloin;
/**
* @author Geloin
* @date 2013-10-24 上午10:31:26
*
*/
public class FinalInstanceVaribaleTest {
static final int var1 = 3;
static final int var2;
static {
var2 = 2;
}
public static void main(String[] args) {
System.out.println(FinalInstanceVaribaleTest.var1);
System.out.println(FinalInstanceVaribaleTest.var2);
}
}
反编译之,如下所示:
Compiled from "FinalInstanceVaribaleTest.java"
public class com.gsoft.geloin.FinalInstanceVaribaleTest extends java.lang.Object{
static final int var1;
static final int var2;
public com.gsoft.geloin.FinalInstanceVaribaleTest();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3: getstatic #3; //Field var1:I
6: invokevirtual #4; //Method java/io/PrintStream.println:(I)V
9: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
12: getstatic #5; //Field var2:I
15: invokevirtual #4; //Method java/io/PrintStream.println:(I)V
18: return
static {};
Code:
0: ldc #6; //String abc
2: invokevirtual #7; //Method java/lang/String.length:()I
5: putstatic #3; //Field var1:I
8: iconst_2
9: putstatic #5; //Field var2:I
12: return
}
对于final类变更(加上static)而言,同样必须指定初始值,而且final类变量只能在2个地方指定初始值:
(1) 定义final类变量时指定初始值;
(2) 在静态初始化块中为final类变量指定初始值;
由以上反编译后的代码可知,final类变量实际上、本质上是在static块中完成初始化的。
再来推翻一个我们的旧识,且看以下代码:
/**
* @author Geloin
* @date 2013-10-24 上午10:31:26
*/
package com.gsoft.geloin;
/**
* @author Geloin
* @date 2013-10-24 上午10:31:26
*
*/
public class FinalInstanceVaribaleTest {
public static void main(String[] args) {
String s1 = "abcd";
String s2 = "ab" + "cd";
System.out.println(s1 == s2); // (1)
String s3 = "ab";
String s4 = "cd";
String s5 = s3 + s4;
System.out.println(s5 == s1); // (2)
final String s6 = "ab";
final String s7 = "cd";
String s8 = s6 + s7;
System.out.println(s8 == s1); // (3)
}
}
教科书上说,字符串间不能相等。于是我们结合上文对final的描述,会得出以下结果:(1)、(2)、(3)分别输出false、false和true。然而事实呢?且运行代码试试?
我们再次反编译之,得到以下结果:
Compiled from "FinalInstanceVaribaleTest.java"
public class com.gsoft.geloin.FinalInstanceVaribaleTest extends java.lang.Object{
public com.gsoft.geloin.FinalInstanceVaribaleTest();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2; //String abcd // (1)
2: astore_1
3: ldc #2; //String abcd // (2)
5: astore_2
6: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
9: aload_1
10: aload_2
11: if_acmpne 18
14: iconst_1
15: goto 19
18: iconst_0
19: invokevirtual #4; //Method java/io/PrintStream.println:(Z)V
22: ldc #5; //String ab // (3)
24: astore_3
25: ldc #6; //String cd // (4)
27: astore 4
29: new #7; //class java/lang/StringBuilder
32: dup
33: invokespecial #8; //Method java/lang/StringBuilder."<init>":()V
36: aload_3
37: invokevirtual #9; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
40: aload 4
42: invokevirtual #9; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
45: invokevirtual #10; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
48: astore 5 // (5)
50: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
53: aload 5
55: aload_1
56: if_acmpne 63
59: iconst_1 // (6)
60: goto 64
63: iconst_0 // (7)
64: invokevirtual #4; //Method java/io/PrintStream.println:(Z)V
67: ldc #2; //String abcd
69: astore 8
71: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
74: aload 8
76: aload_1
77: if_acmpne 84
80: iconst_1
81: goto 85
84: iconst_0
85: invokevirtual #4; //Method java/io/PrintStream.println:(Z)V
88: return
}
相当不好分析,没关系,我们用System.out.println将以上代码分解开来,即如上述代码中粗体所示。
如上述代码中粗体所示:
(1) s1和s2分别被直接映射成一个字符串abcd(第(1)和(2)),也就是说,它们同时指向内存中的abcd,所以s1==s2应该是成立的;
(2) s3和s4分别指向内存中的ab和cd(第(3)和(4)),而s5则指向了内存中StringBuilder对应的内存(第(5)),而不是字符串abcd,所以s1==s5不成立;
(3) s6和s7由于被final修改,直接被定义成了常量(第(6)和(7)),而s8为两个常量相加,指向了内存中的abcd,所以s1==s8成立。
其实,对于一个final变量,不管它是类变量,实例变量还是局部变量,只要定义该变量时使用了final修饰符,并在定义该变量时指定了初始值,而且该初始值可以在编译时就被确定下来,那么这个final变量本质上已经不再是变量,而是相当于一个直接量。
对于final实例变量而言,只有在定义该变量时指定初始值才会有“宏变量”的效果,在非静态初始化、构造器中为final实际变量指定初始值,则不会有宏变量的效果。
如下代码所示:
/**
* @author Geloin
* @date 2013-10-24 上午10:31:26
*/
package com.gsoft.geloin;
/**
* @author Geloin
* @date 2013-10-24 上午10:31:26
*
*/
public class FinalInstanceVaribaleTest {
final String var1 = "Java";
final String var2;
final String var3;
{
var2 = "Java";
}
public FinalInstanceVaribaleTest() {
var3 = "Java";
}
public void display() {
System.out.println(var1 + var1 == "JavaJava");
System.out.println(var2 + var2 == "JavaJava");
System.out.println(var3 + var3 == "JavaJava");
}
public static void main(String[] args) {
FinalInstanceVaribaleTest fiv = new FinalInstanceVaribaleTest();
fiv.display();
}
}
反编译后如下所示:
Compiled from "FinalInstanceVaribaleTest.java"
public class com.gsoft.geloin.FinalInstanceVaribaleTest extends java.lang.Object{
final java.lang.String var1;
final java.lang.String var2;
final java.lang.String var3;
public com.gsoft.geloin.FinalInstanceVaribaleTest();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #2; //String Java
7: putfield #3; //Field var1:Ljava/lang/String;
10: aload_0
11: ldc #2; //String Java
13: putfield #4; //Field var2:Ljava/lang/String;
16: aload_0
17: ldc #2; //String Java
19: putfield #5; //Field var3:Ljava/lang/String;
22: return
public void display();
Code:
0: getstatic #6; //Field java/lang/System.out:Ljava/io/PrintStream;
3: iconst_1 // (1)
4: invokevirtual #7; //Method java/io/PrintStream.println:(Z)V
7: getstatic #6; //Field java/lang/System.out:Ljava/io/PrintStream;
10: new #8; //class java/lang/StringBuilder
13: dup
14: invokespecial #9; //Method java/lang/StringBuilder."<init>":()V
17: aload_0
18: getfield #4; //Field var2:Ljava/lang/String;
21: invokevirtual #10; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: aload_0
25: getfield #4; //Field var2:Ljava/lang/String;
28: invokevirtual #10; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: invokevirtual #11; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: ldc #12; //String JavaJava
36: if_acmpne 43
39: iconst_1
40: goto 44
43: iconst_0
44: invokevirtual #7; //Method java/io/PrintStream.println:(Z)V
47: getstatic #6; //Field java/lang/System.out:Ljava/io/PrintStream;
50: new #8; //class java/lang/StringBuilder
53: dup
54: invokespecial #9; //Method java/lang/StringBuilder."<init>":()V
57: aload_0
58: getfield #5; //Field var3:Ljava/lang/String;
61: invokevirtual #10; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
64: aload_0
65: getfield #5; //Field var3:Ljava/lang/String;
68: invokevirtual #10; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
71: invokevirtual #11; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
74: ldc #12; //String JavaJava
76: if_acmpne 83
79: iconst_1
80: goto 84
83: iconst_0
84: invokevirtual #7; //Method java/io/PrintStream.println:(Z)V
87: return
public static void main(java.lang.String[]);
Code:
0: new #13; //class com/gsoft/geloin/FinalInstanceVaribaleTest
3: dup
4: invokespecial #14; //Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #15; //Method display:()V
12: return
}
且看(1)所示,在获取var1时,直接获取常量,而获取var2和var3时,则作为变量获取。