热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【JDK源码分析】String的存储区与不可变专题

《ThinkinJava》中说:“关系操作符生成的是一个boolean结果,它们计算的是操作数的值之间的关系”。判断的是两个对象的内存地址是否一样&


《Think in Java》中说:
关系操作符生成的是一个boolean结果,它们计算的是操作数的值之间的关系”。

"=="判断的是两个对象的内存地址是否一样,适用于原始数据类型和枚举类型(它们的变量存储的是值本身,而引用类型变量存储的是引用);
equals是Object类的方法,Object对它的实现是比较内存地址,我们可以重写这个方法来自定义“相等”这个概念。
比如类库中的String、Date等类就对这个方法进行了重写。

综上,
对于枚举类型和原始数据类型的相等性比较,应该使用"==";
对于引用类型的相等性比较,应该使用equals方法。

// ... literals are interned by the compiler
// and thus refer to the same object
String s1 = "abcd";
String s2
= "abcd";
s1
== s2; // --> true // ... These two have the same value
// but they are not the same object
String s1 = new String("abcd");
String s2
= new String("abcd");
s1
== s2; // --> false

看上面一段代码,我们会发生疑惑:为什么通过字符串常量实例化的String类型对象是一样的,而通过new所创建String对象却不一样呢?
且看下面分解:

1. 数据存储区

String是一个比较特殊的类,除了new之外,还可以用字面常量来定义。为了弄清楚这二者间的区别,首先我们得明白JVM运行时数据存储区,这里有一张图对此有清晰的描述:

非共享数据存储区

非共享数据存储区是在线程启动时被创建的,包括:

  • 程序计数器(program counter register)控制线程的执行;
  • 栈(JVM Stack, Native Method Stack)存储方法调用与对象的引用等。

共享数据存储区

该存储区被所有线程所共享,可分为:

  • 堆(Heap)存储所有的Java对象,当执行new对象时,会在堆里自动进行内存分配。
  • 方法区(Method Area)存储常量池(run-time constant pool)、字段与方法的数据、方法与构造器的代码。
2. 两种实例化

实例化String对象:

public class StringLiterals {public static void main(String[] args) {String one = "Test";String two = "Test";String three = "T" + "e" + "s" + "t";String four = new String("Test");}
}

javap -c StringLiterals反编译生成字节码,我们选取感兴趣的部分如下:

public static void main(java.lang.String[]); Code: 0: ldc #2 // String Test 2: astore_1 3: ldc #2 // String Test 5: astore_2 6: ldc #2 // String Test 8: astore_3 9: new #3 // class java/lang/String 12: dup 13: ldc #2 // String Test 15: invokespecial #4 // Method java/lang/String."": (Ljava/lang/String;)V 18: astore 4 20: return }

ldc #2表示从常量池中取#2的常量入栈,astore_1表示将引用存在本地变量1中。
因此,我们可以看出:
对象onetwothree均指向常量池中的字面常量"Test";
对象four是在堆中new的新对象;

如下图所示:

总结如下:

  • 当用字面常量实例化时,String对象存储在常量池;
  • 当用new实例化时,String对象存储在堆中;

操作符==比较的是对象的引用,当其指向的对象不同时,则为false。因此,开篇中的代码会出现通过new所创建String对象不一样。

3. 不可变String

不可变性

所谓不可变性(immutability)指类不可以通过常用的API被修改。为了更好地理解不可变性,我们先来看《Thinking in Java》中的一段代码:

//: operators/Assignment.java
// Assignment with objects is a bit tricky.
import static net.mindview.util.Print.*;class Tank {int level;
}
public class Assignment {public static void main(String[] args) {Tank t1 = new Tank();Tank t2 = new Tank();t1.level = 9;t2.level = 47;print("1: t1.level: " + t1.level +", t2.level: " + t2.level);t1 = t2;print("2: t1.level: " + t1.level +", t2.level: " + t2.level);t1.level = 27;print("3: t1.level: " + t1.level +", t2.level: " + t2.level);}
}
/* Output:
1: t1.level: 9, t2.level: 47
2: t1.level: 47, t2.level: 47
3: t1.level: 27, t2.level: 27
*///:~

上述代码中,在赋值操作t1 = t2;之后,t1、t2包含的是相同的引用,指向同一个对象。
因此对t1对象的修改,直接影响了t2对象的字段改变。显然,Tank类是可变的。

也许,有人会说s = s.concat("ef");不是修改了对象s么?而事实上,我们去看concat的实现,会发现return new String(buf, true);返回的是新String对象。
只是s1的引用改变了,如下图所示:

String源码

JDK7的String类:

public final class Stringimplements java.io.Serializable, Comparable, 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
}

 

String类被声明为final,不可以被继承,所有的方法隐式地指定为final,因为无法被覆盖。字段char value[]表示String类所对应的字符串,被声明为private final;即初始化后不能被修改。

常用的new实例化对象String s1 = new String("abcd");的构造器:

public String(String original) {this.value = original.value;this.hash = original.hash;
}

只需将value与hash的字段值进行传递即可。

4. 反射

String的value字段是final的,可不可以通过过某种方式修改呢?答案是反射。在stackoverflow上有这样修改value字段的代码:

String s1 = "Hello World";
String s2
= "Hello World";
String s3
= s1.substring(6);
System.out.println(s1);
// Hello World
System.out.println(s2); // Hello World
System.out.println(s3); // World

Field field
= String.class.getDeclaredField("value");
field.setAccessible(
true);
char[] value = (char[])field.get(s1);
value[
6] = 'J';
value[
7] = 'a';
value[
8] = 'v';
value[
9] = 'a';
value[
10] = '!'; System.out.println(s1); // Hello Java!
System.out.println(s2); // Hello Java!
System.out.println(s3); // World

 

这时,有人会诧异:
为什么对象s2的值也会被修改,而对象s3的值却不会呢?根据前面的介绍,s1与s2指向同一个对象;所以当s1被修改后,s2也会对应地被修改。
至于s3对象为什么不会?我们来看看substring()的实现:

public String substring(int beginIndex) {if (beginIndex <0) {throw new StringIndexOutOfBoundsException(beginIndex);}int subLen &#61; value.length - beginIndex;if (subLen <0) {throw new StringIndexOutOfBoundsException(subLen);}return (beginIndex &#61;&#61; 0) ? this : new String(value, beginIndex, subLen);
}

 

当beginIndex不为0时&#xff0c;返回的是new的String对象&#xff1b;当beginIndex为0时&#xff0c;返回的是原对象本身。
如果将上述代码String s3 &#61; s1.substring(6);改为String s3 &#61; s1.substring(0);&#xff0c;那么对象s3也会被修改了。

如果仔细看java.lang.String.java&#xff0c;我们会发现&#xff1a;
当需要改变字符串内容时&#xff0c;String类的方法返回的是新String对象&#xff1b;
如果没有改变&#xff0c;String类的方法则返回原对象引用。
这节省了存储空间与额外的开销。

5. 参考资料

[1] Programcreek, JVM Run-Time Data Areas.
[2] Corey McGlone, Looking "Under the Hood" with javap.
[3] Programcreek, Diagram to show Java String’s Immutability.
[4] Stackoverflow, Is a Java string really immutable?
[5] Programcreek, Why String is immutable in Java ?

http://www.cnblogs.com/en-heng/p/5121870.html

java.lang.reflect.Field对象的方法&#xff1a;
get

public Object get(Object obj)throws IllegalArgumentException,IllegalAccessException

Returns the value of the field represented by this Field, on the specified object. The value is automatically wrapped in an object if it has a primitive type.

The underlying field&#39;s value is obtained as follows:

If the underlying field is a static field, the obj argument is ignored; it may be null.

Otherwise, the underlying field is an instance field. If the specified obj argument is null, the method throws a NullPointerException. If the specified object is not an instance of the class or interface declaring the underlying field, the method throws an IllegalArgumentException.

If this Field object enforces Java language access control, and the underlying field is inaccessible, the method throws an IllegalAccessException. If the underlying field is static, the class that declared the field is initialized if it has not already been initialized.

Otherwise, the value is retrieved from the underlying instance or static field. If the field has a primitive type, the value is wrapped in an object before being returned, otherwise it is returned as is.

If the field is hidden in the type of obj, the field&#39;s value is obtained according to the preceding rules.

 

 

Parameters:
obj - object from which the represented field&#39;s value is to be extracted
Returns:
the value of the represented field in object obj; primitive values are wrapped in an appropriate object before being returned
Throws:
IllegalAccessException - if the underlying field is inaccessible.
IllegalArgumentException - if the specified object is not an instance of the class or interface declaring the underlying field (or a subclass or implementor thereof).
NullPointerException - if the specified object is null and the field is an instance field.
ExceptionInInitializerError - if the initialization provoked by this method fails.

 

转:https://www.cnblogs.com/softidea/p/5122532.html



推荐阅读
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • This article discusses the efficiency of using char str[] and char *str and whether there is any reason to prefer one over the other. It explains the difference between the two and provides an example to illustrate their usage. ... [详细]
  • 初识java关于JDK、JRE、JVM 了解一下 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • 本文详细介绍了Android中的坐标系以及与View相关的方法。首先介绍了Android坐标系和视图坐标系的概念,并通过图示进行了解释。接着提到了View的大小可以超过手机屏幕,并且只有在手机屏幕内才能看到。最后,作者表示将在后续文章中继续探讨与View相关的内容。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 本文介绍了一个题目的解法,通过二分答案来解决问题,但困难在于如何进行检查。文章提供了一种逃逸方式,通过移动最慢的宿管来锁门时跑到更居中的位置,从而使所有合格的寝室都居中。文章还提到可以分开判断两边的情况,并使用前缀和的方式来求出在任意时刻能够到达宿管即将锁门的寝室的人数。最后,文章提到可以改成O(n)的直接枚举来解决问题。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • PriorityQueue源码分析
     publicbooleanhasNext(){returncursor&amp;amp;lt;size||(forgetMeNot!null&amp;amp;am ... [详细]
  • 开发笔记:快速排序和堆排序
    本文由编程笔记#小编为大家整理,主要介绍了快速排序和堆排序相关的知识,希望对你有一定的参考价值。快速排序思想:在partition中,首先以最右边的值作为划分值x,分别维护小于 ... [详细]
  • 尾部|柜台_Java并发线程池篇附场景分析
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java并发-线程池篇-附场景分析相关的知识,希望对你有一定的参考价值。作者:汤圆个人博客 ... [详细]
author-avatar
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有