您的位置:首页 > 移动开发 > Objective-C

分步理解String对象的数据类型

2007-11-10 16:17 239 查看
几天来上课学了不少东西,那天老师讲参数传递我突然想到了String对象,于是查了些资料,发现通过这个String对象可以把许多东西串起来理解,而且一步步逐渐深入。下面整理一下:
(应该先理解形参和实参的概念,学编程的都应该知道吧,这样理解下面的东西比较方便,但如果你真的不理解那就去查下吧。)

第一步:总结JAVA中的参数传递:
    JAVA传递所有参数都使用传值方式。
    1.对于原始数据类型,也就是int、 long、char之类的类型,是值传递,如果你在方法中修改了值,方法调用结束后,那个变量的值没有改变。
    2.对于对象类型,也就是Object的子类,当你向一个方法传递一个对象时(实际传递的是对象的引用),Java没有把对象放入堆栈,它只是拷贝对象的引用然后将这个引用的拷贝(也就是形参)放入堆栈,还是值传递。形参仍然指向原来的对象,如果你在方法中修改了这个形参指向的对象的成员值,那个修改是生效的,方法调用结束后,它的成员是新的值。但是如果在方法中形参被修改指向一个其它的对象,方法调用结束后,对原来对象的引用(实参)并没用指向新的对象

第二步:String对象作为参数的传递
    弄明白参数传递,有人会问String对象呢?String对象属于对象类型,如果对String参数指向的对象做出了修改,情况如何?
    首先当然知道String不属于8种基本数据类型,String是一个对象。因为对象的默认值是null,所以String的默认值也是null;但它又是一种特殊的对象,有其它对象没有的一些特性。
    当一个方法对传递给它的String参数做出了修改,那么原来该参数指向的String对象是否也就改变了呢? 答案是没有变,因为String是不可变的。String的实例一旦生成就不会再改变了,当你对它做出修改的时候,实际上是使对它的引用指向了另一个新的String,而不是将原来的String修改。所以按照值传递,方法中形参(也就是String引用)是原来实参的一个拷贝,被修改指向了新的String,但原来的实参并未并未指向新的String,更没有对原来的String对象做出修改。
    可以试试下面的程序:

public class Hello {
    public static void main(String[] args) {
        String s = "123";
        System.out.println(s);
        m1(s);
        System.out.println(s);

        int i = 123;
        System.out.println(i);
        m2(i);
        System.out.println(i);
    }

    private static void m1(String s) {
        s = "321";
    }
private static void m2(int i) {
        i = 321;
    }
}

第三步:String对象与常量池
    new String()和new String("")都是申明一个新的空字符串,是空串不是null。
    那么以下两种创建String对象的方式: 
    1.String str=new String ("abc");
    2.String str="abc";
    有区别么?
    先简单引入常量池这个简单的概念。常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量String对象的引用,但是jvm对两者的处理方式是不一样的。对于第一种,jvm会马上在heap中创建一个String对象,然后将该对象的引用返回给用户。对于第二种,jvm首先会在内部维护的常量池中通过String的 equels 方法查找是对象池中是否存放有该String对象,如果有,则返回已有的String对象给用户,而不会在heap中重新创建一个新的String对象;如果对象池中没有该String对象,jvm则在heap中创建新的String对象,将其引用返回给用户,同时将该引用添加至常量池中。注意:使用第一种方法创建对象时,jvm是不会主动把该对象放到常量池里面的,除非程序调用 String的intern方法看下面的例子:
     String s0="abc";
     String s1="abc";
     String s2="a" + "bc";
     System.out.println( s0==s1 ); 
     System.out.println( s0==s2 );
    结果为:
     true
     true
    首先,我们要知道Java会确保一个字符串常量只有一个拷贝。因为例子中的s0和s1中的"abc"都是字符串常量,它们在编译期就被确定了。当定义s1时,jvm发现常量池中已有"abc"对象了,因为"abc"equels "abc"  因此直接返回s0指向的对象给s1,也就是说s0和s1是指向同一个对象的引用。所以s0==s1为true;而"a"和"bc"也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中"abc"的一个引用所以我们得出    用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。
    看例2:
     String s0="abc";
     String s1=new String("abc");
     String s2="a" + new String("bc");
     System.out.println( s0==s1 );
     System.out.println( s0==s2 );
     System.out.println( s1==s2 );
    结果为:
     false
     false
     false 
    例2中s0还是常量池中"abc"的应用,s1因为无法在编译期确定,所以是运行时创建的新对象"abc"的引用,s2因为有后半部分new String("bc")所以也无法在编译期确定,所以也是一个新创建对象"abc"的应用;明白了这些也就知道为何得出此结果了。

关于String.intern():
    再补充介绍一点:存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用。 
    看例3就清楚了:
     String s0= “abc”;
     String s1=new String(”abc”);
     String s2=new String(“abc”);
     System.out.println( s0==s1 );
     System.out.println( “**********” );
     s1.intern();
     s2=s2.intern(); //把常量池中“abc”的引用赋给s2
     //执行完该语句后,s2原来指向的String对象已经成为垃圾对象了,随时会被GC收集。
     System.out.println( s0==s1);
     System.out.println( s0==s1.intern() ); 
     System.out.println( s0==s2 );
    结果为:
     false
     **********
     false //虽然执行了s1.intern(),但它的返回值没有赋给s1
     true //说明s1.intern()返回的是常量池中"abc"的引用
     true 

第四步:Java编译器对于String常量表达式的优化
        这个是别人的文章,内容比较多,下一篇我把他转载过来。着急的人可以直接输入题目去搜下,呵呵。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息