引言

  本文将讲解String的几个性质。

一、String的不可变性

  对于初学者来说,很容易误认为String对象是可以改变的,特别是+链接时,对象似乎真的改变了。然而,String对象一经创建就不可以修改。接下来,我们一步步 分析String是怎么维护其不可改变的性质

1. 手段一:final类 和 final的私有成员

我们先看一下String的部分源码:

public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {/** The value is used for character storage. */private final char value[];/** Cache the hash code for the string */private int hash; // Default to 0/** use serialVersionUID from JDK 1.0.2 for interoperability */private static final long serialVersionUID = -6849794470754667710L;}

  我们可以发现 String是一个final类,且3个成员都是私有的,这就意味着String是不能被继承的,这就防止出现:程序员通过继承重写String类的方法的手段来使得String类是“可变的”的情况。

  从源码发现,每个String对象维护着一个char数组 —— 私有成员value。数组value 是String的底层数组,用于存储字符串的内容,而且是 private final ,但是数组是引用类型,所以只能限制引用不改变而已,也就是说数组元素的值是可以改变的,而且String 有一个可以传入数组的构造方法,那么我们可不可以通过修改外部char数组元素的方式来“修改”String 的内容呢?

我们来做一个实验,如下:

public static void main(String[] args) {char[] arr = new char[]{'a','b','c','d'};       String str = new String(arr);       arr[3]='e';     System.out.println("str= "+str);System.out.println("arr[]= "+Arrays.toString(arr));}

运行结果

str= abcd
arr[]= [a, b, c, e]

  结果与我们所想不一样。字符串str使用数组arr来构造一个对象,当数组arr修改其元素值后,字符串str并没有跟着改变。那就看一下这个构造方法是怎么处理的:

public String(char value[]) {this.value = Arrays.copyOf(value, value.length);}

  原来 String在使用外部char数组构造对象时,是重新复制了一份外部char数组,从而不会让外部char数组的改变影响到String对象。

2. 手段二:改变即创建对象的方法

  从上面的分析我们知道,我们是无法从外部修改String对象的,那么可不可能使用String提供的方法,因为有不少方法看起来是可以改变String对象的,如replace()replaceAll()substring()等。我们以substring()为例,看一下源码:

public String substring(int beginIndex, int endIndex) {//........return ((beginIndex == 0) && (endIndex == value.length)) ? this: new String(value, beginIndex, subLen);}

从源码可以看出,如果不是切割整个字符串的话,就会新建一个对象。也就是说,只要与原字符串不相等,就会新建一个String对象

扩展

基本类型的包装类跟String很相似的,都是final类,都是不可改变的对象,以及维护着一个存储内容的private final成员。如 Integer类:

public final class Integer extends Number implements Comparable<Integer> {private final int value;
}

二、String的+操作 与 字符串常量池

我们先来看一个例子:

public class MyTest {public static void main(String[] args) {String s = "Love You";      String s2 = "Love"+" You";String s3 = s2 + "";String s4 = new String("Love You");System.out.println("s == s2 "+(s==s2));System.out.println("s == s3 "+(s==s3));System.out.println("s == s4 "+(s==s4));}
}

运行结果:

s == s2  true
s == s3  false
s == s4  false

  是不是对运行结果感觉很不解。别急,我们来慢慢理清楚。首先,我们要知道编译器有个优点:在编译期间会尽可能地优化代码,所以能由编译器完成的计算,就不会等到运行时计算,如常量表达式的计算就是在编译期间完成的。所以,s2 的结果其实在编译期间就已经计算出来了,与 s 的值是一样,所以两者相等,即都属于字面常量,在类加载时创建并维护在字符串常量池中。但 s3 的表达式中含有变量 s2 ,只能是运行时才能执行计算,也就是说,在运行时才计算结果,在堆中创建对象,自然与 s 不相等。而 s4 使用new直接在堆中创建对象,更不可能相等。

  那在运行期间,是如何完成String的+号链接操作的呢,要知道String对象可是不可改变的对象。我们使用jad命令 jad MyTest.class 反编译上面例子的calss文件回java代码,来看看究竟是怎么实现的:

public class MyTest
{public MyTest(){}public static void main(String args[]){String s = "Love You";String s2 = "Love You";//已经得到计算结果String s3 = (new StringBuilder(String.valueOf(s2))).toString();String s4 = new String("Love You");System.out.println((new StringBuilder("s == s2 ")).append(s == s2).toString());System.out.println((new StringBuilder("s == s3 ")).append(s == s3).toString());System.out.println((new StringBuilder("s == s4 ")).append(s == s4).toString());}
}

  可以看出,编译器将 + 号处理成了StringBuilder.append()方法。也就是说,在运行期间,链接字符串的计算都是通过 创建StringBuilder对象,调用append()方法来完成的,而且是每一个链接字符串的表达式都要创建一个 StringBuilder对象。因此对于循环中反复执行字符串链接时,应该考虑直接使用StringBuilder来代替 + 链接,避免重复创建StringBuilder的性能开销。

字符串常量池

常量池可以参考我上一篇文章,此处不会深入,只讲解与String相关的部分。

  字符串常量池的内容大部分来源于编译得到的字符串字面常量。在运行期间同样也会增加,

String intern():

返回字符串对象的规范化表示形式。
一个初始为空的字符串池,它由类 String 私有地维护。
当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用。
它遵循以下规则:对于任意两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。

另外一点值得注意的是,虽然String.intern()的返回值永远等于字符串常量。但这并不代表在系统的每时每刻,相同的字符串的intern()返回都会是一样的(虽然在95%以上的情况下,都是相同的)。因为存在这么一种可能:在一次intern()调用之后,该字符串在某一个时刻被回收,之后,再进行一次intern()调用,那么字面量相同的字符串重新被加入常量池,但是引用位置已经不同。

三、String 的hashcode()方法

  String也是遵守equals的标准的,也就是 s.equals(s1)为true,则s.hashCode()==s1.hashCode()也为true。此处并不关注eqauls方法,而是讲解 hashCode()方法,String.hashCode()有点意思,而且在面试中也可能被问到。先来看一下代码:

public int hashCode() {int h = hash;if (h == 0 && value.length > 0) {char val[] = value;for (int i = 0; i < value.length; i++) {h = 31 * h + val[i];}hash = h;}return h;}

为什么要选31作为乘数呢?

从网上的资料来看,一般有如下两个原因:

  • 31是一个不大不小的质数,是作为 hashCode 乘子的优选质数之一。另外一些相近的质数,比如37、41、43等等,也都是不错的选择。那么为啥偏偏选中了31呢?请看第二个原因。

  • 31可以被 JVM 优化,31 * i = (i << 5) - i。

作者:jinggod
出处:http://www.cnblogs.com/jinggod/p/8425182.html

java基础(五) String性质深入解析相关推荐

  1. java string hash变量_java基础(六)-----String性质深入解析

    本文将讲解String的几个性质. 一.String的不可变性 对于初学者来说,很容易误认为String对象是可以改变的,特别是+链接时,对象似乎真的改变了.然而,String对象一经创建就不可以修改 ...

  2. Java基础之String深入解析

    Java基础之String深入解析 以下是本文目录大纲 生成地方和地址指向,intern()方法 String可否被继承? 1. 生成地方和地址指向,intern()方法 先看代码 String s1 ...

  3. 靠java_基础不牢靠,何以争朝夕?Java基础面试82道详细解析!(一)

    基础不牢靠,何以争朝夕?Java基础面试82道详细解析!(一) 发布时间:2020-03-31 12:08:31 来源:51CTO 阅读:229 作者:wx5d9ed7c8443c3 题目的基本顺序是 ...

  4. Java基础:String类

    相关阅读 Java基础:String类 Java字符串格式化 Java基础:正则表达式 1. 概述 字符串是由多个字符组成的一串数据(字符序列),字符串可以看成是字符数组. 在实际开发中,字符串的操作 ...

  5. Java基础之String,StringBuilder,StringBuffer三者的区别

    Java基础之String,StringBuilder,StringBuffer三者的区别 目录 运行速度方面 线程安全方面 小结 1. 运行速度方面 运行速度,在这方面运行速度快慢为:StringB ...

  6. bytes数组转string指定编码_好程序员Java学习路线分享Java基础之string

    好程序员Java学习路线分享Java基础之string 好程序员Java培训 先来讲一下字符串 字串符分类: 不可变字符串:----String.字符串本身不能发生改变,与指向字符串的引用无关. St ...

  7. java基础-字符串String

    今天要介绍的知识点为String,也叫字符串.这是个应用的比较广泛并且充满神奇色彩的知识点.为什么说神奇呢?因为它涉及了好些个看起来高深莫测的概念.并且,这个知识点其实已经属于java中引用类型的知识 ...

  8. 面渣逆袭:Java基础五十三问,快来看看有没有你不会的

    这节我们回到梦开始的地方--Java基础,其实过了萌新阶段,面试问基础就问的不多,但是保不齐突然问一下.想一下,总不能张口高并发.闭口分布式,结果什么是面向对象,说不清,那多少有点魔幻.所以赶紧来看看 ...

  9. [JAVA基础类库] String类 ○ StringBuffer类 ○ StringBuilder类

    引言 字符串就是一连串的字符序列,Java提供了String.StringBuffer和StringBuilder三个类来封装对字符串,并提供了系列方法来操作字符串对象. String类是不可变类的: ...

最新文章

  1. 【数学建模】层次分析法AHP(评价与决策)
  2. 使用Sidecar支持异构平台的微服务
  3. WebSocket服务器TransactionID_SiteDetailMap的析构工作
  4. 入坑机器学习?送你一篇麻省博士的学习心得
  5. pytorch学习入门 (二) Variable(变量)
  6. SD卡启动盘制作软件
  7. oracle视图不能创建,ORACLEsoctt不能创建视图
  8. 众筹网站系统源码+手机端
  9. java 注册成功跳转,写了个注册页面填了注册信息后点注册按钮居然不跳转,为什么?...
  10. python网站有中文界面吗_手把手教你用python开发界面程序
  11. linux远程无密码登录,linux ssh无密码登录,远程执行脚本文件
  12. python能做什么毕业设计-毕业设计涉及到python?看我用Python优雅的写论文!
  13. matlab 纹理映射
  14. 2015 Objective-C 新特性
  15. 从vim转向Emacs _ emacser.com文章收集
  16. 计算机文言,中国最具有概括性的文言文文字?本人学计算机编程的
  17. 在校生如何做一个可以让简历有亮点的java项目?
  18. opencv自带实例下载
  19. PHP 控制器怎么调接口,mvc控制器的访问与参数解析和API接口获取数据
  20. 方便的电商历史价格查询网站asd-price,只需三个字母

热门文章

  1. 一条语句执行跨越若干个数据库
  2. 为什么我的vc6.0建立工程时显示不能访问类查文件类信息,类查看信息不可用
  3. 堆排序-Python
  4. 1365. 有多少小于当前数字的数字(哈希表)
  5. buu 凯撒?替换?呵呵!
  6. javascript中的运算符号
  7. MMU关闭时Cache的缓存策略是怎样的
  8. 01-Secure Payload Dispatcher (SPD)
  9. linux kernel中的wait_for_completion和complete总结
  10. 2022-01-04