String s=new String("abc")创建了几个对象

深入解析Java中String对象的创建方式

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的代码;

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

关于内存区域,参考:Java的内存机制

 

源文档 <http://www.cnblogs.com/ydpvictor/archive/2012/09/09/2677260.html>

<think>我们被要求回答:使用 String s = new String("abc"); 语句创建几个对象? 根据引用[2]和引用[4]的解释,这个语句可能创建1个或2个对象,具体取决于字符串常量池中是否已经存在"abc"。 具体过程: 1. 首先,当执行到new String("abc")时,因为这里使用了字符串字面量"abc",所以JVM会先去字符串常量池中查找是否存在内容为"abc"的字符串对象。 2. 如果常量池中已经存在"abc",那么就不会在常量池中创建新的"abc"对象,而是直接使用已有的。然后,在堆内存中创建一个新的String对象(通过new关键字),这个新对象的内容指向常量池中的"abc"(或者说是复制了常量池中字符串的内容,取决于JVM实现,但通常都是指向同一个字符数组)。所以这种情况下,创建1对象(堆中的String对象)。 3. 如果常量池中不存在"abc",那么首先在常量池中创建一个内容为"abc"的字符串对象,然后在堆内存中再创建一个新的String对象(通过new关键字),这个堆中的对象指向常量池中的"abc"。所以这种情况下,创建了2个对象(一个在常量池,一个在堆中)。 因此,答案是:取决于字符串常量池中是否已经存在"abc"字符串对象。如果不存在,则创建2个;如果存在,则创建1个。 另外,引用[1]和[3]也提到了这个问题,但核心解释与上述一致。 注意:在Java 7之前,字符串常量池位于方法区(永久代),而Java 7及之后,字符串常量池被移到了堆中。但无论在哪里,上述逻辑都是类似的:即常量池中如果没有,则先在常量池创建,然后在堆中创建新的String对象。 所以,我们通常说:在大多数情况下,由于程序可能之前已经使用了"abc",所以常量池中很可能已经存在,那么只创建一个对象(堆中的)。但如果是第一次出现,则创建两个。 但是,在面试中,这个问题通常的答案是:创建了两个对象(一个在常量池,一个在堆中)。这是因为,字符串字面量"abc"本身就会导致在常量池中创建(如果不存在的话),而new操作会在堆中创建一个新的String对象。所以,严格来说,这个语句执行时,会确保有两个对象:一个在常量池中(如果之前没有),一个在堆中。 然而,如果之前已经使用过字面量"abc",那么常量池中已经存在,所以此时只创建一个堆对象。所以,我们不能绝对地说一定是两个,但可以认为在字符串常量池没有"abc"的情况下,会创建两个。 因此,我们回答如下: 一般情况下,如果字符串常量池中不存在"abc",则创建两个对象;如果存在,则只创建一个对象(堆中的String对象)。 为了更严谨,我们还可以补充:在类加载时,字符串字面量可能已经被放入常量池(例如,在同一个类中,如果之前已经使用了"abc",那么常量池中就已经存在了)。所以,具体要看执行该语句时,常量池中是否已有"abc"。 根据引用[5]的测试代码,我们可以看出,字符串常量池的复用机制。 因此,我们总结: 使用 String s = new String("abc"); 创建对象数量可能是1个或2个。 但是,注意:这个语句本身(不考虑之前代码)在第一次执行时,会创建两个对象:因为字面量"abc"会被放入常量池(如果常量池没有),然后new会在堆中创建一个新的String对象。所以,在同一个类中,如果之前没有使用过"abc",那么第一次执行该语句时,会创建两个对象。 所以,在回答时,我们可以说:通常,在第一次遇到这个语句时,会创建两个对象;如果之前已经使用过字面量"abc",那么只会创建一个对象。 但是,由于类加载过程中可能已经将一些字符串放入常量池,所以具体还要看上下文。 然而,在面试中,我们常常回答:创建了两个对象(一个在字符串常量池,一个在堆中)。这是基于字面量"abc"会创建对象(如果常量池没有)和new创建对象这两个动作。 因此,我们给出如下回答: 该语句会创建1个或2个对象。具体来说: - 如果字符串常量池中不存在"abc",则创建2个:一个在字符串常量池中(由字面量"abc"创建),一个在堆中(由new创建)。 - 如果字符串常量池中已存在"abc",则只创建1个:在堆中创建新的String对象。 引用[4]也明确指出:从逻辑上讲,这句话确实创建了两个字符串对象(前提是常量池中原来没有)。 所以,我们按照引用[4]的结论:在逻辑上,这句话创建了两个对象(即使常量池中已有,但new操作必然在堆中创建一个,而字面量"abc"在常量池中创建对象是在类加载时或者之前执行的字面量赋值时完成的,但这里我们关注的是该语句执行时,它会触发两个对象创建吗?) 实际上,字面量"abc"的处理是在类加载的解析阶段或者执行该语句时(具体时机取决于JVM实现)完成的。当类加载时,如果常量池中没有,就会创建。所以,在第一次执行该语句之前,类加载时已经将"abc"放入常量池?不一定,因为类加载的解析阶段可能是惰性的。所以,执行该语句时,会检查常量池中是否有"abc",没有则创建。 因此,执行该语句时,必然会导致常量池中有一个"abc"对象(如果没有的话),并且堆中有一个新的String对象。 所以,我们可以说:该语句执行时,会创建1到2个对象,但通常在没有事先准备的情况下,会创建2个对象。 但是,为了准确,我们按照引用[2]和[4]的描述。 因此,回答如下: 使用`String s = new String("abc");`语句创建对象的数量取决于字符串常量池中是否已经存在内容为"abc"的字符串对象: - 如果常量池中不存在"abc":则首先在字符串常量池中创建一个"abc"字符串对象,然后在堆内存中创建一个新的String对象(其内容为"abc")。此时共创建2个对象。 - 如果常量池中已存在"abc":则只在堆内存中创建一个新的String对象(其内容为"abc")。此时共创建1对象。 注意:字符串常量池在Java 7之后位于堆中,因此整个过程中创建对象都在堆中(但常量池中的对象new出来的对象是两个独立的对象)。 另外,引用[3]中提到:s1和s2分别创建几个对象?以及S1==S2?(答案是false,因为堆中的对象和常量池中的对象不是同一个,即使内容相同,地址也不同,而==比较的是地址)。 因此,我们还可以补充:使用`new String("abc")`创建对象字符串常量池中的"abc"对象不是同一个对象(即使内容相同),因为它们在内存中的位置不同。 最后,根据引用[5]的测试代码,我们可以看到通过intern方法可以将堆中字符串对象的引用指向常量池中的对象(如果常量池中已有,则返回常量池的引用;如果没有,则会将堆中的字符串对象放入常量池并返回)。 所以,为了验证,我们可以写一个简单的程序: 情况1:常量池中没有"abc" String s1 = new String("abc"); // 第一次出现"abc",所以常量池中创建,堆中创建,共2个对象 情况2:常量池中已有"abc" String str = "abc"; // 这一行会使得常量池中创建"abc"(如果之前没有) String s2 = new String("abc"); // 此时常量池中已有,所以只在堆中创建一个对象 因此,我们可以通过代码控制。 但是,在实际编程中,我们通常避免使用`new String(String)`的方式,因为这样会创建不必要的对象,而直接使用字面量(如`String s = "abc";`)可以复用常量池中的对象,节省内存。 相关问题: 1. 如何验证使用`String s = new String("abc")`创建对象数量? 2. 字符串常量池在Java内存模型的哪个区域? 3. 使用`intern()`方法有什么作用?它会影响创建对象的数量吗? 4. 为什么说在Java字符串是不可变的? 5. 如何避免创建不必要的字符串对象? 我们按照要求生成相关问题。</think>### 使用 `String s = new String("abc")` 创建对象数量分析 该语句创建对象数量**取决于字符串常量池中是否已存在内容为 `"abc"` 的对象**,具体分为两种情况: 1. **字符串常量池中不存在 `"abc"` 时(首次出现)** - **创建 2 个对象**: - 在**字符串常量池**中创建字面量 `"abc"` 的对象(`String` 实例)。 - 在**堆内存**中通过 `new` 关键字创建新的 `String` 对象(内容指向常量池中的 `"abc"`)。 - **原因**:字面量 `"abc"` 会触发常量池对象创建,而 `new` 会独立生成堆对象。 2. **字符串常量池中已存在 `"abc"` 时(非首次出现)** - **创建 1对象**: - 仅在**堆内存**中通过 `new` 创建新的 `String` 对象,复用常量池中已有的 `"abc"`。 - **原因**:字面量 `"abc"` 已存在于常量池,无需重复创建。 #### 关键验证:对象地址比较 ```java String s1 = new String("abc"); String s2 = "abc"; // 直接引用常量池对象 System.out.println(s1 == s2); // 输出 false(堆对象 vs 常量池对象) ``` - **`s1 == s2` 为 `false`**:证明 `s1`(堆对象)与 `s2`(常量池对象)是两个独立实例[^1][^4]。 #### 内存结构示意图 ``` 字符串常量池 (Heap) 堆内存 (Heap) +--------------+ +-----------------+ | "abc" |<------| new String("abc") [s1] | (唯一字面量) | | (独立实例) | +--------------+ +-----------------+ ``` ### 总结 - **最小创建 1对象**(常量池已有 `"abc"` 时)。 - **最大创建 2 个对象**(常量池无 `"abc"` 时)。 - **根本原因**: - 字面量 `"abc"` 会隐式检查/创建常量池对象[^2][^4]。 - `new` 关键字强制在堆中生成新实例[^3][^5]。 > ⚠️ 实际开发中,应优先使用 `String s = "abc"`(直接复用常量池对象),避免 `new String("abc")` 造成不必要的堆内存开销。 --- ### 相关问题 1. **如何验证字符串常量池中是否存在某个字符串对象?** 2. **`String.intern()` 方法的作用是什么?它如何影响对象创建数量?** 3. **Java 7 之后字符串常量池的位置从永久代移到了堆内存,这种变化有什么实际影响?** 4. **为什么 `String s1 = "a" + "b";` 只创建 1对象,而 `String s2 = new String("a") + new String("b");` 会创建多个对象?** 5. **在哪些场景下使用 `new String(String)` 是合理的?** [^1]: 引用自 `String s=new String(“abc”)一共创建几个对象?`,说明 `new` 创建的堆对象与常量池对象地址不同。 [^2]: 引用自 `String a = new String(“abc“); 创建几个对象?`,详细解释了常量池检查与对象创建逻辑。 [^3]: 引用自 `String s = new String(“ abc “)面试题之到底创建几个对象?`,分析了字面量和 `new` 的交互机制。 [^4]: 引用自 `String s1 = new String(“abc“);这句话创建几个字符串对象?`,明确逻辑上可能创建两个对象。 [^5]: 引用自 `String s = new String() 并输出发生了什么`,通过测试代码验证对象创建场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值