Stringstr=new String("abc") 这行代码究竟创建了几个对象?

本文探讨了Java中String对象的不同创建方式,重点分析了使用new关键字与直接使用引号创建的区别,以及这些操作如何影响字符串池。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Stringstr=new String("abc");   紧接着这段代码之后的往往是这个问题,那就是这行代码究竟创建了几个String对象呢?

相信大家对这道题并不陌生,答案也是众所周知的,2个。

接下来我们就从这道题展开,一起回顾一下与创建String对象相关的一些JAVA知识。  

我们可以把上面这行代码分成Stringstr、=、"abc"和new String()四部分来看待。Stringstr只是定义了一个名为str的String类型的变量,因此它并没有创建对象;=是对变量str进行初始化,将某个对象的引用(或者叫句柄)赋值给它,显然也没有创建对象;现在只剩下newString("abc")了。那么,newString("abc")为什么又能被看成"abc"和new String()呢?

我们来看一下被我们调用了的String的构造器:  

public String(String original) {  //other code ...  }  大家都知道,我们常用的创建一个类的实例(对象)的方法有以下两种:

一、使用new创建对象。 

二、调用Class类的newInstance方法,利用反射机制创建对象。

我们正是使用new调用了String类的上面那个构造器方法创建了一个对象,并将它的引用赋值给了str变量。同时我们注意到,被调用的构造器方法接受的参数也是一个String对象,这个对象正是"abc"。由此我们又要引入另外一种创建String对象的方式的讨论——引号内包含文本。

这种方式是String特有的,并且它与new的方式存在很大区别。  

Stringstr="abc";  

毫无疑问,这行代码创建了一个String对象。  

Stringa="abc";  Stringb="abc";   那这里呢?

答案还是一个。  

Stringa="ab"+"cd";   再看看这里呢?

答案是三个。

说到这里,我们就需要引入对字符串池相关知识的回顾了。  

在JAVA虚拟机(JVM)中存在着一个字符串池,其中保存着很多String对象,并且可以被共享使用,因此它提高了效率。由于String类是final的,它的值一经创建就不可改变,因此我们不用担心String对象共享而带来程序的混乱。字符串池由String类维护,我们可以调用intern()方法来访问字符串池。  

我们再回头看看Stringa="abc";,这行代码被执行的时候,JAVA虚拟机首先在字符串池中查找是否已经存在了值为"abc"的这么一个对象,它的判断依据是String类equals(Object obj)方法的返回值。如果有,则不再创建新的对象,直接返回已存在对象的引用;如果没有,则先创建这个对象,然后把它加入到字符串池中,再将它的引用返回。因此,我们不难理解前面三个例子中头两个例子为什么是这个答案了。

只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中,对此我们不再赘述。因此我们提倡大家用引号包含文本的方式来创建String对象以提高效率,实际上这也是我们在编程中常采用的。

栈(stack):主要保存基本类型(或者叫内置类型)(char、byte、short、int、long、float、double、boolean)和对象的引用,数据可以共享,速度仅次于寄存器(register),快于堆。 

堆(heap):用于存储对象

堆区:主要存放Java程序运行时创建的所有引用类型(new()生成的对象)都放在其中;

栈区:主要存放Java程序运行时所需的局部变量、方法的参数、对象的引用以及中间运算结果等数据;

代码区:主要存放Java的代码;

数据区:主要存放静态变量及全局变量

<think>我们正在处理关于Java字符串intern方法、字符串常量池以及newString对象和intern方法之间的行为关系的问题。用户提供了多个引用,我们需要整合这些信息来解答。核心问题:理解intern方法的行为及其与字符串常量池的关系,特别是涉及newString和字符串拼接的情况。根据引用,我们可以总结以下几点:1.字符串常量池(internpool)位于方法区中,用于存储字符串字面量(如直接赋值:Strings="hello";)。当使用字面量方式创建字符串时,会检查常量池中是否存在,存在则返回引用,不存在则创建后放入池中再返回。2.使用newString("hello")会创建两个对象(如果常量池中没有"hello"):一个在堆中(new出来的对象),一个在字符串常量池中(字面量"hello")。但如果常量池中已有,则只创建堆中的对象。3.intern()方法:当调用一个字符串对象的intern方法时,会检查常量池中是否有内容相同的字符串。如果有,则返回池中的引用;如果没有,则将该字符串对象添加到池中(在JDK7及以上版本中,常量池位于堆中,所以intern方法会将堆中该对象的引用存到常量池,并返回该引用),然后返回这个引用。特别注意不同JDK版本的行为差异:-JDK6及之前:字符串常量池位于方法区(永久代),使用intern方法时,如果常量池没有,会复制一份字符串对象到常量池,然后返回常量池中的引用。-JDK7及之后:字符串常量池被移到堆中,intern方法在处理时,如果常量池没有,则不会复制对象,而是将堆中该对象的引用存储到常量池,并返回这个引用。用户特别提到了一些代码示例,我们需要解释这些行为。用户要求解释的例子(类似):Strings=newString("1");s.intern();Strings2="1";System.out.println(s==s2);//输出false以及:Strings3=newString("1")+newString("1");s3.intern();Strings4="11";System.out.println(s3==s4);//JDK7/8输出true解释:第一个例子:Strings=newString("1");这行代码创建两个对象(如果常量池中原来没有"1"):1)字符串字面量"1"会被放入常量池(在类加载时可能已经存在,如果之前没有的话)。2)在堆中创建一个新的String对象,其内容为"1"。注意:即使常量池中已有"1",new也会在堆中创建一个新的对象。s.intern();检查常量池,因为常量池中已经有"1"(由字面量创建,或者在类加载时已存在,或者在newString("1")时创建了),所以intern方法返回常量池中的引用,但是我们没有接收这个返回值,所以s仍然指向堆中的对象Strings2="1";s2指向的是常量池中的"1"的引用。因此,s(堆中对象)和s2(常量池中对象)是两个不同的对象,所以s==s2为false。第二个例子(字符串拼接):Strings3=newString("1")+newString("1");这里通过拼接创建了一个新的字符串"11"。注意:在拼接时,并不会将"11"自动放入常量池。在JDK7及以后,常量池中并没有"11"这个字符串(因为拼接是运行时才确定的,编译器不会处理)。s3.intern();此时,因为常量池中没有"11",所以intern方法会将s3(堆中的这个"11"字符串对象)的引用存放到常量池中(JDK7及以上),然后返回这个引用(但我们没有接收返回值,不过常量池中已经保存了s3的引用)。Strings4="11";此时,常量池中已经有了"11"(存放的是s3的引用),所以s4指向的就是s3所指向的对象。因此,s3和s4指向的是同一个对象,所以s3==s4为true。但是需要注意的是,在JDK6中,第二个例子的行为不同:intern方法在JDK6中,如果常量池没有,则会在永久代(方法区)的常量池中创建一个新的字符串对象(内容为"11"),然后返回这个新对象的引用。而s3指向的是堆中的对象,所以s3==s4为false。现在,我们根据用户问题,解释一下newString与intern和==的关系。用户问题:newString+intern==?我们可以这样回答:newString创建新的对象(在堆中),而字符串字面量(如"abc")会使用常量池中的对象。intern方法可以手动将字符串对象添加到常量池(如果常量池没有该字符串),并返回常量池中的引用(可能是已有的,也可能是新加入的)。当我们比较两个字符串引用(使用==)时,比较的是引用的地址是否相同。如果两个引用指向同一个对象,则相等;否则不相等。因此,使用new创建的字符串对象,在没有调用intern方法时,与常量池中的字符串对象(如果有相同的字面量)是不同的对象,所以==比较会为false。而如果调用了intern方法,并且常量池中原本没有这个字符串,那么在JDK7及以上版本,调用intern方法后,常量池中存储的是该堆对象的引用,之后通过字面量获取的字符串引用就是该堆对象的引用,因此通过==比较会相等。总结:使用newString创建对象位于堆中,而直接使用字面量创建对象可能位于常量池(或堆中,通过常量池引用)。intern方法可以使得堆中的字符串对象与常量池建立联系。我们还需要注意字符串拼接的情况(如引用3、5):在编译期能够确定的字符串常量拼接(如final修饰的字符串变量)会被编译器优化为常量,并在类加载时放入常量池。而运行时拼接(如使用+运算符拼接非final的字符串变量)会使用StringBuilder,在堆上生成新的字符串对象,并不会放入常量池,除非调用intern()方法。最后,我们按照要求生成相关问题。</think> ### Java字符串的intern()方法与常量池行为详解 #### 字符串常量池基础 字符串常量池是JVM在方法区(JDK7前)或堆内存(JDK7+)中维护的特殊存储区域,用于缓存字符串字面量: 1. 直接赋值(`String s = "hello"`)时,JVM先在常量池查找: - 若存在则返回池中对象的引用 - 若不存在则在池中创建对象后返回引用 $$ \text{直接赋值} \Rightarrow \begin{cases} \text{命中缓存} & \text{返回池中引用} \\ \text{未命中} & \text{创建对象入池} \end{cases} $$ 2. `new String()` 会强制在堆中创建对象(即使内容相同): ```java String s1 = "Hello"; // 常量池对象 String s2 = new String("Hello"); // 堆中新对象 System.out.println(s1 == s2); // false(不同地址)[^2] ``` #### intern()方法行为 `intern()`方法将字符串对象与常量池关联: ```java String s3 = new String("Hello").intern(); // 手动入池 String s4 = "Hello"; System.out.println(s3 == s4); // true(相同引用)[^1] ``` - 调用`intern()`时: - **JDK6-** - 检查常量池是否存在等价字符串 - 不存在:复制堆中对象到常量池(返回池中引用) - 存在:直接返回池中引用 - **JDK7+** - 不存在:在池中记录堆中对象的引用(不复制) - 存在:返回池中引用 #### 关键场景分析 1. **字面量提前创建常量** ```java String s = new String("1"); // 常量池已有"1" s.intern(); // 无效果(池中已存在) String s2 = "1"; System.out.println(s == s2); // false(堆对象 vs 池对象)[^5] ``` 2. **运行时字符串拼接** ```java String s3 = new String("1") + new String("1"); // 堆对象"11",常量池尚无"11" s3.intern(); // JDK7+:将s3引用存入常量池 String s4 = "11"; // 指向s3的堆对象 System.out.println(s3 == s4); // JDK7+:true [^5] ``` - JDK6结果不同:`intern()`会复制对象到独立于堆的常量池 3. **编译期拼接优化** ```java final String a = "we"; // final触发编译期优化 final String b = "chat"; String c = a + b + "handsome"; // 编译期合并为"wechathandsome" // 等价于直接赋值,常量池直接查找[^3] ``` #### 总结与最佳实践 | 操作 | JDK6行为 | JDK7+行为 | 引用地址一致性 | |----------------------|----------------------|----------------------|--------------| | `new String("str")` | 创建堆+池对象 | 创建堆+池对象 | ❌ | | `intern()`未命中常量池 | 池中创建对象副本 | 池中存储堆对象引用 | ✅(JDK7+) | | 字面量拼接(编译期) | 等同直接赋值 | 等同直接赋值 | ✅ | | `+`拼接(运行期) | 新建堆对象 | 新建堆对象 | ❌(需intern) | > **最佳实践**:优先使用字面量赋值避免重复对象;在需要引用比对或内存优化时使用`intern()`,但要注意JDK版本差异[^4][^5]。 ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值