我是靠谱客的博主 傲娇金毛,最近开发中收集的这篇文章主要介绍【Java SE基础专题 二】String类型详解,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

本篇文章具体聊聊Java中关于String类型的数据问题,String为什么是不可变的。Java中的变量和基本类型的值存放于栈内存,而new出来的对象本身存放于堆内存,指向对象的引用还是存放在栈内存。例如如下的代码:

int  i=1;
String s =  new  String( "Hello World" );

变量i和s以及1存放在栈内存,而s指向的对象Hello World存放于堆内存

栈内存的常量数据共享

栈内存的一个特点是数据共享,这样设计是为了减小内存消耗,前面定义了i=1,i和1都在栈内存内,

  • 如果再定义一个j=1,此时将j放入栈内存,然后查找栈内存中是否有1,如果有则j指向1。
  • 如果给j赋值2,则在栈内存中查找是否有2,如果没有就在栈内存中放一个2,然后j指向2。
  • 如果j++,这时指向的变量并不会改变,而是在栈内寻找新的常量(比原来的常量大1),如果栈内存有则指向它,如果没有就在栈内存中加入此常量并将j指向它

也就是如果该常量也在栈内存中,就将变量指向该常量,如果没有就在该栈内存增加一个常量,并将变量指向这个增加的常量这种基本类型之间比较大小和我们逻辑上判断大小是一致的。如定义i和j是都赋值1,则i==j结果为true==用于判断两个变量指向的地址是否一样。i==j就是判断i指向的1和j指向的1是同一个吗?当然是了

同样的对于直接赋值的字符串常量(如String s=“Hello World”;中的Hello World)也是存放在栈内存中,所以也满足条件

String s="Hello World"
String w="Hello World"
s==w//为true

如果定义和,s==w吗?肯定是true,因为他们指向的是同一个Hello World,栈中的Hello World

堆内存非数据共享

堆内存没有数据共享的特点,例如通过new的方式定义两个字符串:

String s =  new  String( "Hello World" );//变量s在栈内存内,Hello World 这个String对象在堆内存内
String w = new  String( "Hello World" );//变量w存放在栈内存,w指向这个新的String对象
s==w//为false

堆内存中不同对象(指同一类型的不同对象)的比较如果用==则结果肯定都是false,s和w指向堆内存中不同的String对象。如何判断两个String对象相等呢?用equals方法。

综合分析

知道了上边的知识综合分析以下下边的问题

public class StringDemo{
  private static final String MESSAGE="taobao";
  public static void main(String [] args) {
    String a ="tao"+"bao";
    String b="tao";
    String c="bao";
    System.out.println(a==MESSAGE);  //true
    System.out.println((b+c)==MESSAGE); //false
  }
}

MESSAGE 成员变量及其指向的字符串常量肯定都是在栈内存里的

  • 变量 a 运算完也是指向一个字符串taobao 这涉及到编译器优化问题。对于字符串常量的相加,在编译时直接将字符串合并,而不是等到运行时再合并。也就是说String a = “tao” + “bao” ;和String a = “taobao” ;编译出的字节码是一样的。所以等到运行时,根据上面说的栈内存是数据共享原则,a和MESSAGE指向的是同一个字符串
  • 对于后面的(b+c),(b+c)只能等到运行时才能判定是什么字符串,编译器不会优化,运行时b+c计算出来的taobao和栈内存里已经有的taobao不是一个,b+c计算出来的"taobao"应该是放在堆内存中的String对象

那么为什么b+c计算出来的在堆内存呢?Java对String的相加是通过StringBuffer实现的,先构造一个StringBuffer里面存放”tao”,然后调用append()方法追加”bao”,然后将值为”taobao”的StringBuffer转化成String对象。StringBuffer对象在堆内存中,那转换成的String对象理所应当的也是在堆内存中。

intern改进

下面改造一下这个语句

 System. out .println( (b+c).intern()== MESSAGE );//true

结果是true, intern() 方法会先检查 String 池 ( 或者说成栈内存 ) 中是否存在相同的字符串常量,如果有就返回。所以 intern()返回的就是MESSAGE指向的"taobao"。

final改进

再把变量b和c的定义改一下

final  String b =  "tao" ;
final  String c =  "bao" ;
System. out .println( (b+c)== MESSAGE );//true

现在b和c不可能再次赋值了,所以编译器将b+c编译成了taobao。因此,这时的结果是true。因为被当成常量处理了,在字符串相加中,只要有一个是非final类型的变量,编译器就不会优化,因为这样的变量可能发生改变,所以编译器不可能将这样的变量替换成常量。例如将变量b的final去掉,结果又变成了false。这也就意味着会用到StringBuffer对象,计算的结果在堆内存中。

堆中String的intern改进

如果对指向堆内存中的对象的String变量调用intern()会怎么样呢?(b+c).intern(),b+c的结果就是在堆内存中。对于指向栈内存中字符串常量的变量调用intern()返回的还是它自己,没有多大意义。它会根据堆内存中对象的值,去查找常量池中是否有相同的字符串,如果有就将变量指向这个常量池中的变量。

String a = "tao"+"bao";
String b = new String("taobao");
System.out.println(a==MESSAGE); //true
System.out.println(b==MESSAGE);  //false
b = b.intern();
System.out.println(b==MESSAGE); //true
System. out .println(a==a.intern());  //true

综合分析2

这里的str1指的是方法区中的字符串常量池中的“hello”,编译时期就知道的; String str2 = "he" + new String("llo");这里的str2必须在运行时才知道str2是什么,所以它是指向的是堆里定义的字符串“hello”,所以这两个引用是不一样的。

String str1="hello";
String str2="he"+ new String("llo");
System.out.println(str1==str2);

基于此有如下判断:

  • 编译器没那么智能,它不知道"he" + new String("llo")的内容是什么,所以才不敢贸然把"hello"这个对象的引用赋给str2. 如果语句改为:"he"+"llo"这样就是true了
  • 如果用str1.equal(str2),那么返回的是true;因为String类重写了equals()方法

new String(“llo”)实际上创建了2个String对象,一个是使用“llo”通过双引号创建的(在字符串常量池)字面量在常量池中,另一个是通过new创建的(在堆里)。只不过他们的创建的时期不同,一个是编译期,一个是运行期。

对于 String s = "a"+"b"+"c";语句中,“a”,"b", "c"都是常量,编译时就直接存储他们的字面值,而不是他们的引用,在编译时就直接将它们连接的结果提取出来变成"abc"了,也就是不存在符号引用。

String的不可变性

简单的来说:String 类中使⽤ final 关键字修饰字符数组来保存字符串, private final char value[] ,所以 String 对象是不可变的,⽽ StringBuilder 与 StringBuffer 都继承⾃ AbstractStringBuilder 类,在AbstractStringBuilder 中也是使⽤字符数组保存字符串 char[]value 但是没有⽤ final 关键字修饰,所以这两种对象都是可变的

最后

以上就是傲娇金毛为你收集整理的【Java SE基础专题 二】String类型详解的全部内容,希望文章能够帮你解决【Java SE基础专题 二】String类型详解所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(41)

评论列表共有 0 条评论

立即
投稿
返回
顶部