您的位置:首页 > 编程语言 > Java开发

java 常量池详解

2014-07-13 23:23 225 查看
在class文件中,“常量池”是最复杂也最值得关注的内容。

  Java是一种动态连接的语言,常量池的作用非常重要,常量池中除了包含代码中所定义的各种基本类型(如int、long等等)和对象型(如String及数组)的常量值还,还包含一些以文本形式出现的符号引用,比如:

  类和接口的全限定名;

  字段的名称和描述符;

  方法和名称和描述符。

  在C语言中,如果一个程序要调用其它库中的函数,在连接时,该函数在库中的位置(即相对于库文件开头的偏移量)会被写在程序中,在运行时,直接去这个地址调用函数;

  而在Java语言中不是这样,一切都是动态的。编译时,如果发现对其它类方法的调用或者对其它类字段的引用的话,记录进class文件中的,只能是一个文本形式的符号引用,在连接过程中,虚拟机根据这个文本信息去查找对应的方法或字段。

  所以,与Java语言中的所谓“常量”不同,class文件中的“常量”内容很非富,这些常量集中在class中的一个区域存放,一个紧接着一个,这里就称为“常量池”。
转:http://hi.baidu.com/rickmeteor/blog/item/f0be11dff578ba1662279848.html
java中的常量池技术,是为了方便快捷地创建某些对象而出现的,当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),则在需要重复重复创建相等变量时节省了很多时间。常量池其实也就是一个内存空间,不同于使用new关键字创建的对象所在的堆空间。本文只从java使用者的角度来探讨java常量池技术,并不涉及常量池的原理及实现方法。个人认为,如果是真的专注java,就必须对这些细节方面有一定的了解。但知道它的原理和具体的实现方法则不是必须的。
1,常量池中对象和堆中的对象
public class Test{
Integer i1=new Integer(1);

   Integer i2=new Integer(1);
//i1,i2分别位于堆中不同的内存空间
  System.out.println(i1==i2);//输出false

   Integer i3=1;

   Integer i4=1;
//i3,i4指向常量池中同一个内存空间
  System.out.println(i3==i4);//输出true
//很显然,i1,i3位于不同的内存空间
System.out.println(i1==i3);//输出false
}
2,8种基本类型的包装类和对象池
java中基本类型的包装类的大部分都实现了常量池技术,这些类是Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127时才可使用对象池,也即对象不负责创建和管理大于127的这些类的对象。以下是一些对应的测试代码:
public class Test{
public static void main(String[] args){
  //5种整形的包装类Byte,Short,Integer,Long,Character的对象,
   //在值小于127时可以使用常量池
   Integer i1=127;
   Integer i2=127;
  System.out.println(i1==i2)//输出true
  //值大于127时,不会从常量池中取对象
   Integer i3=128;
   Integer i4=128;
  System.out.println(i3==i4)//输出false
  //Boolean类也实现了常量池技术
   Boolean bool1=true;
   Boolean bool2=true;
  System.out.println(bool1==bool2);//输出true
  //浮点类型的包装类没有实现常量池技术
   Double d1=1.0;
   Double d2=1.0;
   System.out.println(d1==d2)//输出false
  
}
}
3,String也实现了常量池技术
String类也是java中用得多的类,同样为了创建String对象的方便,也实现了常量池的技术,测试代码如下:
public class Test{
public static void main(String[] args){
//s1,s2分别位于堆中不同空间
String s1=new String("hello");
String s2=new String("hello");
System.out.println(s1==s2)//输出false
//s3,s4位于池中同一空间
String s3="hello";
String s4="hello";
System.out.println(s3==s4);//输出true
}
}
最后:
细节决定成败,写代码更是如此。
对Integer对象的补充:http://hi.baidu.com/fandywang_jlu/blog/item/c5590b4eae053cc3d1c86a13.html
Integer的封装吧:
   public static Integer valueOf(int i) {
        final int offset = 128;
        if (i >= -128 && i <= 127) {// must cache

           return IntegerCache.cache[i+ offset];
         }
         return new Integer(i);
       }
当你直接给一个Integer对象一个int值的时候,其实它调用了valueOf方法,然后你赋的这个值很特别,是128,那么没有进行cache方法,相当于new了两个新对象。所以问题中定义a、b的两句代码就类似于:
      Integer a = new Integer(128);
      Integer b =new Integer(128);
这个时候再问你,输出结果是什么?你就知道是false了。如果把这个数换成127,再执行:
      Integer a = 127;
      Integer b = 127;
      System.out.println(a== b);
结果就是:true
由上可知,我们进行对象比较时最好还是使用equals,便于按照自己的目的进行控制。
--------------------------------------------------补充-----------------------------------------------------------------------
   我们看一下IntegerCache这个类里面的内容:
   private static class IntegerCache {
      private IntegerCache() {
      }
      static final Integercache[] = newInteger[-(-128) + 127 + 1];
      static {
         for (int i = 0; i <cache.length; i++)
           cache[i]= new Integer(i - 128);
      }
   }
由于cache[]在IntegerCache类中是静态数组,也就是只需要初始化一次,即static{......}部分,所以,如果Integer对象初始化时是-128~127的范围,就不需要再重新定义申请空间,都是同一个对象---在IntegerCache.cache中,这样可以在一定程度上提高效率。
//////////////////////////////////////////////////////////对String方面的补充///////////////////////////////////////////////////////////
来自SUN官方文档:
3.10.5String Literals
A string literal consists of zero or more charactersenclosed in double quotes. Each character
may be represented by an escapesequence.

Astring literal is always of typeString
(§4.3.3. A string literal always refers to the same instance

(§4.3.1) of class String.

StringLiteral:
 " StringCharactersopt "
 
StringCharacters:
 StringCharacter
 StringCharacters StringCharacter
 
StringCharacter:
 InputCharacter but not " or \
 EscapeSequence
The escape sequences are described in§3.10.6.
Asspecified in§3.4,
neither of the characters CR andLF is ever considered to be anInputCharacter;
each is recognized as constituting aLineTerminator.

Itis a compile-time error for a line terminator to appear after the opening"
and before the closing matching". A long string literal can always bebroken up into shorter pieces and written as a (possibly parenthesized)expression using the string concatenation operator+

(§15.18.1).

Thefollowing are examples of string literals:
""    // the empty string
"\""    // a string containing " alone
"Thisis a string"  // a string containing16 characters
"Thisis a " +   // actually astring-valued constant expression,
 "two-line string" //  formed from two string literals
Because Unicode escapes are processed very early, it isnot correct to write"\u000a"
for a string literal containing a single linefeed (LF);the Unicode escape\u000a is transformed into an actual linefeed in translationstep 1(§3.3)and
the linefeed becomes a LineTerminator in step 2(§3.4),and so the string literal is not valid in
step 3. Instead, one should write"\n"

(§3.10.6).Similarly, it is not correct to write"\u000d" for a string literal containing a single carriage return (CR). Insteaduse"\r".

 
Each string literal is a reference(§4.3)to
an instance (§4.3.1,§12.5)of
class String (§4.3.3).String
objects have a constant value. String literals-or, moregenerally, strings that are the values of constant expressions(§15.28)-are"interned"
so as to share unique instances, using the methodString.intern.

Thus,the test program consisting of the compilation unit(§7.3):
packagetestPackage;
classTest {
 public static void main(String[] args) {
  String hello = "Hello", lo ="lo";
  System.out.print((hello == "Hello")+ " ");
  System.out.print((Other.hello == hello) +" ");
  System.out.print((other.Other.hello == hello)+ " ");
  System.out.print((hello ==("Hel"+"lo")) + " ");
  System.out.print((hello ==("Hel"+lo)) + " ");
  System.out.println(hello ==("Hel"+lo).intern());
 }
}
classOther { static String hello = "Hello"; }
and the compilation unit:
packageother;
publicclass Other { static String hello = "Hello"; }
produces the output:
truetrue true true false true
This example illustrates six points:
Literal strings within the same class(§8)
in the same package
(§7) represent references to the same String object(§4.3.1).

在同包同类下,引用自同一String对象.
Literal strings within different classes in the same package represent references to the same String object.
在同包不同类下,引用自同一String对象.
Literal strings within different classes in different packages likewise represent references to the same String object.
在不同包不同类下,依然引用自同一String对象.
Strings computed by constant expressions(§15.28)
are computed at compile time and then treated as if they were literals.

在编译成.class时能够识别为同一字符串的,自动优化成常量,所以也引用自同一String对象.
Strings computed at run time are newly created and therefore distinct.
在运行时创建的字符串具有独立的内存地址,所以不引用自同一String对象.
The result of explicitly interning a computed string is the same string as any pre-existing literal string with the same contents.
String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,
如果有则返回一个引用,没有则添加自己的字符串进进入常量池,
注意,只是字符串部分,
所以这时会存在2份拷贝,常量池的部分被String类私有持有并管理,自己的那份按对象生命周期继续使用.
API解释:
返回字符串对象的规范化表示形式。
一个初始时为空的字符串池,它由类String 私有地维护。
当调用 intern 方法时,如果池已经包含一个等于此String 对象的字符串(该对象由equals(Object)方法确定),则返回池中的字符串。否则,将此String
对象添加到池中,并且返回此String 对象的引用。

它遵循对于任何两个字符串s 和
t,当且仅当
s.equals(t) 为true 时,s.intern() == t.intern() 才为true。

所有字面值字符串和字符串赋值常量表达式都是内部的。字符串字面值在《Java
LanguageSpecification》的 §3.10.5 中已定义。
------------------------------------代码演示补充-------------------------------------
   String s0="java";
   String s1=newString("java");
   String s2=newString("java");
   s1.intern();
   s2=s2.intern(); //把常量池中“kvill”的引用赋给s2
   System.out.println(s0==s1);//false intern返回的引用没有引用变量接收~   s1.intern();等于废代码.
   System.out.println(s0==s1.intern() );//true
   System.out.println( s0==s2);//true
------------------------------------代码演示补充-------------------------------------
   String s1=newString("java");

   String s2=s1.intern();//s1 检查常量池 发现没有 就拷贝自己的字符串进去   

//s2 引用该字符串常量池的地址
   System.out.println(s2 ==s1);//false

   System.out.println( s2==s1.intern());//true

   System.out.println( s1==s1.intern());// false

最后引出equals()和==的故事
对于字符串比较,一定要切记使用equals,勿用==
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: