zoukankan      html  css  js  c++  java
  • java中String类型变量的赋值问题

    第一节 String类型的方法参数

    运行下面这段代码,其结果是什么?

    package com.test;
    
    public class Example {
        
        String str = new String("good");
        char[] ch = { 'a', 'b', 'c' };
    
        public static void main(String[] args) {
            Example ex = new Example();
            ex.change(ex.str, ex.ch);
            System.out.println(ex.str);
            System.out.println(ex.ch);
        }
    
        public void change(String str, char ch[]) {
            str = "test ok";
            ch[0] = 'g';
        }
        
    }

    结果如下:

    good
    gbc

    解说:java 中String是 immutable的,也就是不可变,一旦初始化,引用指向的内容是不可变的(注意:是内容不可变)。

      也就是说,假设代码中有String str = “aa”;str=“bb”;,则第二条语句不是改变“aa”原来所在存储地址中的内容,而是另外开辟了一个空间用来存储“bb”;同时由于str原来指向的“aa”现在已经不可达,jvm会通过GC自动回收。
     
      在方法调用时,String类型和数组属于引用传递,在上述代码中,str作为参数传进change(String str, char ch[]) 方法,方法参数str指向了类中str指向的字符串,但str= "test ok"; 语句使得方法参数str指向了新分配的地址,该地址存储“test ok”,而原来的str仍然指向“good”。对于数组而言,在change方法中,方法参数ch指向了类中ch指向的数组,ch[0] = 'g';语句改变了类中ch指向的数组的内容

    我们再来看下面这段代码,它的运行结果是什么?

    package com.test;
    
    public class Example {
        
        String str = new String("good");
        char[] ch = { 'a', 'b', 'c' };
    
        public static void main(String[] args) {
            Example ex = new Example();
            ex.change(ex.str, ex.ch);
            System.out.println(ex.str);
            System.out.println(ex.ch);
        }
    
        public void change(String str, char ch[]) {
            str = str.toUpperCase();
            ch = new char[]{ 'm', 'n' };
        }
        
    }

    结果如下:

    good
    abc

    结合前面的解释进行理解,这个结果是不是在意料之中?!

    根据JDK中java.lang.String的源码进行分析,从中可以得出String类型的对象不可变的原因,大致上有如下两个:

      1、java.lang.String类型在实现时,其内部成员变量全部使用final来修饰,保证成员变量的引用值只能通过构造函数来修改;

      2、java.lang.String类型在实现时,在外部可能修改其内部存储值的函数实现中,返回时一律构造新的String对象或者新的byte数组或者char数组;

    仅凭第1点还不能保证其不可变特性:假如通过String类型的toCharArray方法可以直接访问String类型内部定义的char数组,那么即便String类型内部的char数组使用了final来修饰,也仅仅保证这个成员变量的引用不可变,而无法保证引用指向的内存区域不可变。

    第2点保证了外部不可能修改java.lang.String类型对象的内部属性,从而保证String对象是不可变的。


    第二节 String类型变量的赋值

    2.1 String变量赋值方式:s2=new String(s1)

    下面这段代码的运行结果是什么

    package com.soft;
    
    public class ExecutorsDemo {
        
        public static void main(String[] args) {
            String s1="abc"+"def";
            String s2=new String(s1);
            if(s1.equals(s2))
                System.out.println("equals succeeded");
            if(s1==s2)
                System.out.println("==succeeded");
        }
    }

    结果:

    equals succeeded

    解说:上述代码中,s1与s2指向不同的对象,但是两个对象的内容却是一样的,故“s1==s2”为假,s1.equals(s2)为真。

    此处我们来细说一下"=="与equals的作用:

      (1)"=="操作符的作用

        A、用于基本数据类型的比较

        B、判断引用是否指向堆内存的同一块地址

      (2)equals的作用

        用于判断两个变量是否是对同一个对象的引用,即堆中的内容是否相同,返回值为布尔类型

    2.2 String变量赋值方式:s2 = s1

    package com.soft;
    
    public class ExecutorsDemo {
        
        public static void main(String[] args) {
            String s1 = new String("java");
            String s2 = s1;
    
            System.out.println(s1==s2);
            System.out.println(s1.equals(s2));
        }
    }

     结果:

    true
    true

    解说:如果理解了前面那个例子的运行情况,那么这个就是一目了然的事情,此处s1与s2指向同一个对象,"=="操作符的作用之一就是判断引用是否指向堆内存的同一块地址,equals的作用是判断两个变量是否是对同一个对象的引用(即堆中的内容是否相同),故此处均输出“true”


    第三节 将字符数组或字符串数组转换为字符串

    此处再补充两个应用场景

    一、将字符数组转换为字符串

    下面代码中的两种方式均可直接将字符数组转换为字符串,不需要遍历拼接

    package com.test;
    
    public class Main {
        
        public Main() {
        }
    
        public static void main(String[] args) {
            char[] data = {'a', 'b', 'c'};
    //      String str = new String(data);
            String str = String.valueOf(data);
            System.out.println(str);
        }
        
    }

    此处可以看一下其他作者的文章以深入理解:【Java】数组不能通过toString方法转为字符串  http://www.cnblogs.com/ningvsban/p/3955483.html

    二、将字符串数组转换为字符串

    下面的代码是我们常用的方式,循环拼接

    package com.test;
    
    public class Main {
        
        public Main() {
        }
    
        public static void main(String[] args) {
            String[] ary = {"abc", "123", "45"};
            String s = "";
            for(String temp : ary) {
                s=s.concat(temp);//和下面的一行二选一即可
    //          s += temp;
            }
            System.out.println(s);
        }
        
    }

    上述代码段不需要过多解释了


    第四节 StringBuffer和StringBuilder

    提到String,就不得不提一下JDK中另外两个常用来表示字符串的类,StringBuffer和StringBuilder。在编写java代码的过程中有时要频繁地对字符串进行拼接,如果直接用“+”拼接的话会建立很多的String型对象,严重的话会对服务器资源和性能造成不小的影响;而使用StringBuilder和StringBuffer能解决以上问题。根据注释,StringBuffer可谓老资格了,从JDK1.0时即伴随Java征战世界,而StringBuilder直到JDK1.5时才出现。面试时,StringBuffer和StringBuilder的区别也是常问的话题,StringBuffer是线程安全的,而StringBuilder不是线程安全的。

    一、StringBuffer和StringBuilder的共同点:

    1、用来完成字符串拼接操作;

    2、都是可变对象,对象内的字符缓存会随着拼接操作而动态扩展;

    3、构造时传入内部缓存大小时,可以降低缓存扩展的次数,明显提升字符串拼接操作的效率;

    二、StringBuffer和StringBuilder的区别:

    1、StringBuilder的方法都是线程不安全的,从另外一个角度讲,StringBuilder类型的对象在做字符串拼接操作时,由于少了线程同步的操作,执行效率上有很大提升;

    2、StringBuffer的方法都加上了synchronized关键字,因而在一定的场景下,StringBuffer类型的对象都是线程安全的,但在执行效率上,由于多了线程同步的操作,因而会有少许的损失;

    在大多数场景下,字符串拼接操作都是不需要考虑多线程环境下对结果的影响的,因而使用StringBuilder类型可以提升代码的执行效率。

    在多个线程的代码中共享同一个StringBuffer类型的对象时,需要关注synchronized关键字对最终结果的影响。由于StringBuffer类的实现中,仅仅对每个方法使用了synchronized修饰,这只能保证在多线程场景下,访问StringBuffer对象的同一个方法时可以保证最终结果的一致性,假如一个线程访问A方法,另外一个线程方法B方法,则由于加锁对象的不同,可能会出现不一致的现象,这是需要程序员特别要注意的地方。类似的,可以参考Vector的实现和应用场景。

    针对上面的将字符串数组转换为字符串,可以借助上面提到的StringBuilder(当然StringBuffer也可以),代码如下:

    package com.test;
    
    public class Main {
        
        public Main() {
        }
    
        public static void main(String[] args) {
            String[] ary = {"abc", "123", "45"};
            StringBuilder sb = new StringBuilder();
            for(int i = 0; i < ary.length; i++){
                sb. append(ary[i]);
            }
            String newStr = sb.toString();
            System.out.println(newStr);
        }
        
    }

    参考资料

    这里有两篇文章,值得一读:

    (1)三分钟理解Java中字符串(String)的存储和赋值原理 http://blog.csdn.net/zhuiwenwen/article/details/12351565

    (2)Java之内存分析和String对象 http://www.cnblogs.com/devinzhang/archive/2012/01/25/2329463.html

  • 相关阅读:
    数码摄影入门之十 数码相片后期处理
    Easy CHM 2.10
    LeapFTP 3.0.0.43 汉化版(附带LeapFTP 3.0注册码)
    使用“淘宝助理”的常见错误
    “互联网浏览器”控件与webBrowser控件的区别
    易语言源代码毁来者来了!!
    易语言正则表达式的多行匹配替换
    Explorer.exe鲜为人知的参数
    原始套接字概述
    网络技术数据封装
  • 原文地址:https://www.cnblogs.com/studyLog-share/p/5307913.html
Copyright © 2011-2022 走看看