对象与内存控制(续)

        假设有以下代码:

/**
 * 
 */
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时,则作为变量获取。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值