>>分享Java编程技术,对《Java面向对象编程》等书籍提供技术支持 书籍支持  卫琴直播  品书摘要  在线测试  资源下载  联系我们
发表一个新主题 开启一个新投票 回复文章 您是本文章第 21388 个阅读者 刷新本主题
 * 贴子主题:  面试官刁难:Java字符串可以引用传递吗? 回复文章 点赞(0)  收藏  
作者:Jacky    发表时间:2020-03-30 14:13:58     消息  查看  搜索  好友  邮件  复制  引用

              

面试官刁难:Java字符串可以引用传递吗?

   老读者都知道了,六年前,我从苏州回到洛阳,抱着一幅“海归”的心态,投了不少简历,也“约谈”了不少面试官,但仅有两三个令我感到满意。其中有一位叫老马,至今还活在我的手机通讯录里。他当时扔了一个面试题把我砸懵了:“王二,Java 字符串可以引用传递吗?”

     当时二十三岁,正值青春年华,从事 Java 编程已有 N 年经验(N < 4),自认为所有的面试题都能对答如流,结果没想到啊,被“刁难”了——原来洛阳这块互联网的荒漠也有技术专家啊。现在回想起来,脸上不自觉地泛起了羞愧的红晕:主要是自己当时太菜了。不管怎么说,是时候写篇文章剖析一下字符串是否可以引用传递了。

     对于绝大多数的初级程序员或者说不重视“内功”的老鸟来说,往往停留在“知其然不知其所以然”的层面上——会用,略知一二,但要求他把问题说清楚的时候,就只能挠挠头双手一摊一张问号脸了。

     好了,让我们来步入正题。先来看一段有趣但令人困惑的代码片段吧。    

  public  static  void  main (String [ ] args )  {
    String x  =  new  String ( "沉默王二" ) ;
     change (x ) ;
    System .out . println (x ) ;
}

public  static  void  change (String x )  {
    x  =  "沉默王三" ;
}

  从代码的字面逻辑来看,程序应该输出“沉默王三”,但事与愿违,程序输出的结果却是“沉默王二”。 change() 方法做的是无用功,因为 String 是值传递而不是引用传递。引用传递可以在被调用的方法中对实参进行修改,但值传递却不可以。为什么呢?

     x 存储的是一个引用,该引用指向内存中的“沉默王二”字符串对象。当我们把 x 作为参数传递给  change() 方法时,x 仍然指向的是内存中“沉默王二”字符串,就像下面这幅图表达的意思一样。

     那么问题来了。正因为 Java 是值传递,x 的值是“沉默王二”的引用。那么当  change() 方法被调用的时候,x 不是刚好指向了内存中新创建的字符串对象“沉默王三”了吗?就像下面这幅图表达的意思那样。

     哦,看起来是一个很完美的解释,对吧?但这样的解释存在一些问题。

     当字符串“沉默王二”被创建的时候,Java 会在内存中申请一小段空间,用来存储这个字符串对象。然后呢,把对象的引用指向了变量 x,也就是说,变量 x 实际上存储的是对象的引用(对象在内存中存储的地址)。

     我相信大家对上面这一点(对象和对象引用)已经完全理解了。

     关键的点来了。当变量 x 作为参数(实参)传递给  change() 方法时,实际上传递的是 x 的一个拷贝(形参)。在  change() 方法中,形参 x 起先引用的也是“沉默王二”这个对象,当执行  x = "沉默王三" 的时候,会在内存中创建新的字符串“沉默王三”,然后形参 x 不再引用“沉默王二”这个对象了,改为引用“沉默王三”这个对象了。但实参 x 呢?并没有发生任何的改变!就像下面这幅图一样。

     假如我们真的需要改变字符串呢?那就不能使用 String 类了,最好使用 StringBuilder,来撸一串代码吧。    

  public  static  void  main (String [ ] args )  {
    StringBuilder x  =  new  StringBuilder ( "沉默王二" ) ;
     change (x ) ;
    System .out . println (x ) ;
}

public  static  void  change (StringBuilder x )  {
    x . delete ( 3 , 4 ) . append ( "三" ) ;
}

  上述代码会输出“沉默王三”,但假如我们使用 new 关键字重新对形参 x 进行赋值,就无济于事。    

  public  static  void  main (String [ ] args )  {
    StringBuilder x  =  new  StringBuilder ( "沉默王二" ) ;
     change (x ) ;
    System .out . println (x ) ;
}

public  static  void  change (StringBuilder x )  {
    x  =  new  StringBuilder ( "沉默王三" ) ;
}

  程序输出的结果仍然是“沉默王二”,原因其实和 String 一样, change() 方法在内存中创建了新的字符串“沉默王三”,然后形参 x 不再引用“沉默王二”这个对象,改为引用“沉默王三”这个对象了。但实参 x 并没有任何改变。

     看到这,有些读者可能更疑惑了。 x = new StringBuilder("沉默王三") 不可以改变实参,而  x.delete(3,4).append("三") 却可以,为什么?为什么?为什么?为什么呢?

     不要着急,我们来分析一下  delete() 方法的源码。    

  public AbstractStringBuilder  delete ( int start ,  int end )  {
     int len  = end  - start ;
     if  (len  >  0 )  {
        System . arraycopy (value , start +len , value , start , count -end ) ;
        count  -= len ;
     }
     return  this ;
}

  其中 value 是一个字符数组,用来存储字符序列;count 用来表示字符序列中实际有效的字符数量。

     当  count -= len 执行之前,value 的字符内容为“沉默王二”,count 为 4。我是怎么知道的呢?通过 IDEA 的 debug 视图,截图为证。

     当  count -= len 执行之后,value 的字符内容仍然为“沉默王二”,但 count 变成了 3。

     当鼠标停留在 this 上时,此时的字符内容为“沉默王”,也就意味着 x 当前的字符内容为“沉默王”。同样的,当我们在  append() 方法上进行 debug 的时候,也可以观察到字符串发生变化的细节。

     当  append() 方法执行结束后,此时形参 x 的字符内容为“沉默王三”。

     当  change() 方法执行完后,此时实参 x 的字符内容为“沉默王三”。

     通过上面的源码分析,大家应该会发现另外一个事实:x 对象始终是“StringBuilder@512”,这意味着什么呢?一图胜千言,画个图大家一看就明白了。

     由于形参 x 和实参 x 引用的都是同一个对象,那么  change() 方法执行结束后,实参 x 的字符内容自然也就发生了变化。

     综上所述:Java 字符串不是引用传递而是值传递;更进一步的说,Java 只有值传递,没有引用传递。

    
  遥想公瑾当年,小乔初嫁了,雄姿英发。

羽扇纶巾,谈笑间,樯橹灰飞烟灭。

故国神游,多情应笑我,早生华发。
哎,后悔啊,早年我要是能把这道面试题吃透的话,也不用被老马刁难了。另外,我想要告诉大家的是,作为程序员,我们千万不要轻视这些基础的知识点。因为基础的知识点是各种上层技术共同的基础,只有彻底地掌握了这些基础知识点,才能更好地理解程序的运行原理,做出更优化的产品。

          好了,各位读者朋友们,以上就是本文的全部内容了。 能看到这里的都是最优秀的程序员,升职加薪就是你了?。如果觉得不过瘾,还想看到更多,可以 star 二哥的 GitHub【itwanger.github.io】,本文已收录。

     原创不易,如果觉得有点用的话,请不要吝啬你手中 点赞的权力;如果想要第一时间看到二哥更新的文章,请扫描下方的二维码,关注沉默王二公众号。我们下篇文章见!

     点击在新窗口中浏览原图
CTRL+鼠标滚轮放大或缩小

                                                                                                                
----------------------------
原文链接:https://blog.csdn.net/qing_gee/article/details/103814019

程序猿的技术大观园:www.javathinker.net



[这个贴子最后由 sunweiqin 在 2020-03-31 10:37:22 重新编辑]
  Java面向对象编程-->Java注解
  JavaWeb开发-->使用Session(Ⅱ)
  JSP与Hibernate开发-->使用JPA和注解
  Java网络编程-->基于UDP的数据报和套接字
  精通Spring-->Vue简介
  Vue3开发-->计算属性和数据监听
  NIO底层原理
  Java泛型中的通配符 T,E,K,V,? 你了解吗
  Java 语言中十大“坑爹”功能!
  面试问我,创建多少个线程合适?我该怎么说
  超详细的Java运算符修炼手册(优秀程序员不得不知道的运算技...
  面试必考-static、final和单例模式
  5个非常有挑战性的Java面试题
  Java入门实用代码:获取所有线程
  Java入门实用代码:获取远程文件大小
  Java入门实用代码:删除链表中的元素
  Java入门实用代码:获取链表(LinkedList)的第一个和最后一...
  Java入门实用代码:修改文件最后的修改日期
  通过Java读取Excel数据
  判断一个字符是否是汉字
  【Java 并发笔记】CountDownLatch 相关整理
  更多...
 IPIP: 已设置保密
楼主      
该用户目前不在线 patebeng2 
  
威望: 0
级别: 新手上路
魅力: 105
经验: 105
现金: 1008
发文章数: 1
注册时间: 0001-01-01
 消息  查看  搜索  好友  邮件  复制  引用


离心泵
离心水泵
螺杆泵
全焊接球阀
焊接球阀
多级泵
磁力泵
磁力泵
螺杆泵

上海螺杆泵
上海离心泵
直埋全焊接球阀
埋地全焊接球阀
离心泵

球阀厂
球阀
Fully Welded Ball Valve161
Welded Ball Valve182
Welded Ball Valve in china54
Fully Welded Ball Valve in china53
发文章时间 2020-12-07 09:13:57
 IPIP: 已设置保密 1 楼     
该用户目前不在线 kericnnoe 
威望: 未知
级别: 未知
魅力: 未知
经验: 未知
现金: 未知
发文章数: 未知
注册时间: 未知
 复制  引用


在短短幾年中就有無數家線上百家樂系統商崛起,好比眾人皆知的DG百家樂、MW、SA、歐博線上,真人百家樂線上系統商也曾被眾多玩家評比已超越實體賭場

歐博線上系統商也曾被眾多玩家評比已超越實體賭場
发文章时间 2023-03-07 00:26:47
 IPIP: 已设置保密 2 楼     
1页 2条记录 当前第1
发表一个新主题 开启一个新投票 回复文章


中文版权所有: JavaThinker技术网站 Copyright 2016-2026 沪ICP备16029593号-2
荟萃Java程序员智慧的结晶,分享交流Java前沿技术。  联系我们
如有技术文章涉及侵权,请与本站管理员联系。