`

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

 
阅读更多
我们首先来看一段代码:
String str=new String("abc");
    紧接着这段代码之后的往往是这个问题,那就是这行代码究竟创建了几个String对象呢?相信大家对这道题并不陌生,答案也是众所周知的,2个。接下来我们就从这道题展开,一起回顾一下与创建String对象相关的一些JAVA知识。
    我们可以把上面这行代码分成String str、=、"abc"和new String()四部分来看待。String str只是定义了一个名为str的String类型的变量,因此它并没有创建对象;=是对变量str进行初始化,将某个对象的引用(或者叫句柄)赋值给 它,显然也没有创建对象;现在只剩下new String("abc")了。那么,new String("abc")为什么又能被看成"abc"和new String()呢?我们来看一下被我们调用了的String的构造器:
 
public String(String original) {
        //other code ...
}
 
大家都知道,我们常用的创建一个类的实例(对象)的方法有以下两种:
  1. 使用new创建对象。
  2. 调用Class类的newInstance方法,利用反射机制创建对象。
 
我们正是使用new调用了String类的上面那个构造器方 法创建了一个对象,并将它的引用赋值给了str变量。同时我们注意到,被调用的构造器方法接受的参数也是一个String对象,这个对象正是"abc"。 由此我们又要引入另外一种创建String对象的方式的讨论——引号内包含文本。
 
这种方式是String特有的,并且它与new的方式存在很大区别。
 
String str="abc";
 
毫无疑问,这行代码创建了一个String对象。
 
String a="abc";
String b="abc";
 
那这里呢?答案还是一个。
 
String a="ab"+"cd";
 
再看看这里呢?答案仍是一个。有点奇怪吗?说到这里,我们就需要引入对字符串池相关知识的回顾了。
 
在JAVA虚拟机(JVM)中存在着一个字符串池,其中保存 着很多String对象,并且可以被共享使用,因此它提高了效率。由于String类是final的,它的值一经创建就不可改变,因此我们不用担心 String对象共享而带来程序的混乱。字符串池由String类维护,我们可以调用intern()方法来访问字符串池。
 
我们再回头看看String a="abc";,这行代码被执行的时候,JAVA虚拟机首先在字符串池中查找是否已经存在了值为"abc"的这么一个对象,它的判断依据是String 类equals(Object obj)方法的返回值。如果有,则不再创建新的对象,直接返回已存在对象的引用;如果没有,则先创建这个对象,然后把它加入到字符串池中,再将它的引用返 回。因此,我们不难理解前面三个例子中头两个例子为什么是这个答案了。
 
对于第三个例子:
 
String a="ab"+"cd";
 
由于常量的值在编译的时候就被确定了。在这里,"ab"和"cd"都是常量,因此变量a的值在编译时就可以确定。这行代码编译后的效果等同于:
 
String a="abcd";
 
因此这里只创建了一个对象"abcd",并且它被保存在字符串池里了。
现在问题又来了,是不是所有经过“+”连接后得到的字符串都会被添加到字符串池中呢?我们都知道“==”可以用来比较两个变量,它有以下两种情况:
  1. 如果比较的是两个基本类型(char,byte,short,int,long,float,double,boolean),则是判断它们的值是否相等。
  2. 如果表较的是两个对象变量,则是判断它们的引用是否指向同一个对象。
下面我们就用“==”来做几个测试。为了便于说明,我们把指向字符串池中已经存在的对象也视为该对象被加入了字符串池:
public class StringTest {
        public static void main(String[] args) {
               String a = "ab";// 创建了一个对象,并加入字符串池中
               System.out.println("String a = ""ab"";");
               String b = "cd";// 创建了一个对象,并加入字符串池中
               System.out.println("String b = ""cd"";");
               String c = "abcd";// 创建了一个对象,并加入字符串池中
               String d = "ab" + "cd";
               // 如果d和c指向了同一个对象,则说明d也被加入了字符串池
               if (d == c) {
                       System.out.println("""ab""+""cd"" 创建的对象 ""加入了"" 字符串池中");
               }
               // 如果d和c没有指向了同一个对象,则说明d没有被加入字符串池
               else {
                       System.out.println("""ab""+""cd"" 创建的对象 ""没加入"" 字符串池中");
               }
               String e = a + "cd";
               // 如果e和c指向了同一个对象,则说明e也被加入了字符串池
               if (e == c) {
                       System.out.println(" a +""cd"" 创建的对象 ""加入了"" 字符串池中");
               }
               // 如果e和c没有指向了同一个对象,则说明e没有被加入字符串池
               else {
                       System.out.println(" a +""cd"" 创建的对象 ""没加入"" 字符串池中");
               }
               String f = "ab" + b;
               // 如果f和c指向了同一个对象,则说明f也被加入了字符串池
               if (f == c) {
                       System.out.println("""ab""+ b   创建的对象 ""加入了"" 字符串池中");
               }
               // 如果f和c没有指向了同一个对象,则说明f没有被加入字符串池
               else {
                       System.out.println("""ab""+ b   创建的对象 ""没加入"" 字符串池中");
               }
               String g = a + b;
               // 如果g和c指向了同一个对象,则说明g也被加入了字符串池
               if (g == c) {
                       System.out.println(" a + b   创建的对象 ""加入了"" 字符串池中");
               }
               // 如果g和c没有指向了同一个对象,则说明g没有被加入字符串池
               else {
                       System.out.println(" a + b   创建的对象 ""没加入"" 字符串池中");
               }
        }
}
 
运行结果如下:
  1. String a = "ab";
  2. String b = "cd";
  3. "ab"+"cd" 创建的对象 "加入了" 字符串池中
  4. a  +"cd" 创建的对象 "没加入" 字符串池中
  5. "ab"+ b   创建的对象 "没加入" 字符串池中
  6. a  + b   创建的对象 "没加入" 字符串池中
 
从上面的结果中我们不难看出,只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中,对此我们不再赘述。
 
但是有一种情况需要引起我们的注意。请看下面的代码:
 
public class StringStaticTest {
        // 常量A
        public static final String A = "ab";
        // 常量B
        public static final String B = "cd";
        public static void main(String[] args) {
               // 将两个常量用+连接对s进行初始化
               String s = A + B;
               String t = "abcd";
               if (s == t) {
                       System.out.println("s等于t,它们是同一个对象");
               } else {
                       System.out.println("s不等于t,它们不是同一个对象");
               }
        }
}

这段代码的运行结果如下:
  • s等于t,它们是同一个对象
 
这又是为什么呢?原因是这样的,对于常量来讲,它的值是固定 的,因此在编译期就能被确定了,而变量的值只有到运行时才能被确定,因为这个变量可以被不同的方法调用,从而可能引起值的改变。在上面的例子中,A和B都 是常量,值是固定的,因此s的值也是固定的,它在类被编译时就已经确定了。也就是说:
 
String s=A+B;
 
等同于:
 
String s="ab"+"cd";
 
我对上面的例子稍加改变看看会出现什么情况:
 
public class StringStaticTest {
        // 常量A
        public static final String A;
        // 常量B
        public static final String B;
        static {
               A = "ab";
               B = "cd";
        }
        public static void main(String[] args) {
               // 将两个常量用+连接对s进行初始化
               String s = A + B;
               String t = "abcd";
               if (s == t) {
                       System.out.println("s等于t,它们是同一个对象");
               } else {
                       System.out.println("s不等于t,它们不是同一个对象");
               }
        }
}
 
它的运行结果是这样:
  • s不等于t,它们不是同一个对象
只是做了一点改动,结果就和刚刚的例子恰好相反。我们再来分 析一下。A和B虽然被定义为常量(只能被赋值一次),但是它们都没有马上被赋值。在运算出s的值之前,他们何时被赋值,以及被赋予什么样的值,都是个变 数。因此A和B在被赋值之前,性质类似于一个变量。那么s就不能在编译期被确定,而只能在运行时被创建了。
 
由于字符串池中对象的共享能够带来效率的提高,因此我们提倡大家用引号包含文本的方式来创建String对象,实际上这也是我们在编程中常采用的。
 
接下来我们再来看看intern()方法,它的定义如下:
public native String intern();
这是一个本地方法。在调用这个方法时,JAVA虚拟机首先检查字符串池中是否已经存在与该对象值相等对象存在,如果有则返回字符串池中对象的引用;如果没有,则先在字符串池中创建一个相同值的String对象,然后再将它的引用返回。
 
我们来看这段代码:
 
public class StringInternTest {
        public static void main(String[] args) {
               // 使用char数组来初始化a,避免在a被创建之前字符串池中已经存在了值为"abcd"的对象
               String a = new String(new char[] { 'a', 'b', 'c', 'd' });
               String b = a.intern();
               if (b == a) {
                       System.out.println("b被加入了字符串池中,没有新建对象");
               } else {
                       System.out.println("b没被加入字符串池中,新建了对象");
               }
        }
}
 
运行结果:
  1. b没被加入字符串池中,新建了对象
如果String类的intern()方法在没有找到相同值 的对象时,是把当前对象加入字符串池中,然后返回它的引用的话,那么b和a指向的就是同一个对象;否则b指向的对象就是JAVA虚拟机在字符串池中新建 的,只是它的值与a相同罢了。上面这段代码的运行结果恰恰印证了这一点。
 
最后我们再来说说String对象在JAVA虚拟机(JVM)中的存储,以及字符串池与堆(heap)和栈(stack)的关系。我们首先回顾一下堆和栈的区别:
  • 栈(stack):主要保存基本类型(或者叫内置类型)(char、byte、short、int、long、float、double、boolean)和对象的引用,数据可以共享,速度仅次于寄存器(register),快于堆。
  • 堆(heap):用于存储对象。
 
我们查看String类的源码就会发现,它有一个value属性,保存着String对象的值,类型是char[],这也正说明了字符串就是字符的序列。
 
当执行String a="abc";时,JAVA虚拟机会在栈中创建三个char型的值'a'、'b'和'c',然后在堆中创建一个String对象,它的值(value) 是刚才在栈中创建的三个char型值组成的数组{'a','b','c'},最后这个新创建的String对象会被添加到字符串池中。如果我们接着执行 String b=new String("abc");代码,由于"abc"已经被创建并保存于字符串池中,因此JAVA虚拟机只会在堆中新创建一个String对象,但是它的值 (value)是共享前一行代码执行时在栈中创建的三个char型值值'a'、'b'和'c'。
分享到:
评论

相关推荐

    new String(\"abc\")创建几个对象的解释

    java面试的疑惑剪辑 博文链接:https://z-jq1015.iteye.com/blog/248599

    java 面对对象编程.pdf.zip

    面向对象基础 面向对象和面向过程的区别 成员变量与局部变量的区别 创建一个对象用什么运算符?对象实体与对象引用有何不同? 对象的相等和引用相等的区别 类的构造方法的作用是什么?...这句话创建了几个

    浅析为什么a=”abc” 不等于 a=new String(“abc”)

    数字、字符串、布尔、null、undefined 属于原始资料类型,而Number、String、Boolean属于包装类型,通过new Number 创建的是包装类型的派生对象。所以两者是不等的。 直接赋值为基本类型之后的使用过程如下: 1、...

    java联系题

    //执行到这一行时,创建了几个对象? String s1 = "abc";//执行到这一行时,创建了几个对象? String s2 = new String("abc");//执行到这一行时,创建了几个对象? System.out.println(s == s1);//输出结果是什么...

    Java基础总结

    语句1在内存中创建了几个对象?4. String为什么是不可变的?jdk源码中的String如何定义的?为什么这么设计?4. 请描述一下static关键字和final关键字的用法。5. 接口和抽象类的区别是什么?6. 重载和重写的区别?7....

    C# for CSDN 乱七八糟的看不懂

    范围 string sbyte short int long string s = "hello"; sbyte val = 12; short val = 12; int val = 12; long val1 = 12; -128 到 127 -32,768 到 32,767 -2,147,483,648 2,147,483,647 -9,223,372,036,854,775,...

    黑马基础测试题

    String value = new String ("abc"); System.out.println(s == value); } } 6、 用控制台程序倒着输出九九乘法表;输出结果按下图所示: 1*9=9 2*9=18 3*9=27 4*9=36 5*9=45 6*9=54 7*9=63 8*9=72 9*9=81 ...

    最新JAVA编程题全集_50题及答案

    写一个函数,例如:给你的 a b c 则输出 abc acb bac bca cab cba import java.util.ArrayList; import java.util.List; public class NumTest { public static void main(String[] args) { String s="ABCD";...

    java面试宝典

    创建了几个String Object? 12 40、接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)? 12 41、Java 的接口和C++的虚类的相同和不同处。 12 42、一个“.java”源文件中...

    千方百计笔试题大全

    创建了几个String Object? 12 40、接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)? 12 41、Java 的接口和C++的虚类的相同和不同处。 12 42、一个“.java”源文件中...

    一个java正则表达式工具类源代码.zip(内含Regexp.java文件)

    在这是junit测试单元类我就不提交了,在main()方法中有几个小测试,有兴趣自己玩吧. 这个工具类目前主要有25种正规表达式(有些不常用,但那时才仔细深入的研究了一下正规,写上瘾了,就当时能想到的都写了): 1....

    Android面试

    1.String str = new String(“abc”) 创建了几个对象? Android 1:你是如何理解Android操作系统的。 2:是否熟悉framework层,如果熟悉,那就对framework做个简介。 3:是否熟悉多线程,如果熟悉,介绍下线程。 4:...

    Java反射的功能强大之处

    /** * new String("ABC") 创建了几个对象 * new String("A"+"BC") 创建了几个对象 */ //内存中创建了2个字符串对象 //利用反射API读取 s1和str的value属性 //开放不可见的访问权限

    最新Java面试宝典pdf版

    创建了几个String Object? 二者之间有什么区别? 23 34、String 和StringBuffer的区别 23 35、如何把一段逗号分割的字符串转换成一个数组? 24 36、数组有没有length()这个方法? String有没有length()这个方法? 24 ...

    Java面试宝典2010版

    创建了几个String Object? 二者之间有什么区别? 34、String 和StringBuffer的区别 35、如何把一段逗号分割的字符串转换成一个数组? 36、数组有没有length()这个方法? String有没有length()这个方法? 37、下面...

    Java面试笔试资料大全

    创建了几个String Object? 二者之间有什么区别? 23 34、String 和StringBuffer的区别 23 35、如何把一段逗号分割的字符串转换成一个数组? 24 36、数组有没有length()这个方法? String有没有length()这个方法? 24 ...

    JAVA面试宝典2010

    创建了几个String Object? 二者之间有什么区别? 23 34、String 和StringBuffer的区别 23 35、如何把一段逗号分割的字符串转换成一个数组? 24 36、数组有没有length()这个方法? String有没有length()这个方法? 24 ...

    java面试题大全(2012版)

    创建了几个String Object? 二者之间有什么区别? 23 34、String 和StringBuffer的区别 23 35、如何把一段逗号分割的字符串转换成一个数组? 24 36、数组有没有length()这个方法? String有没有length()这个方法? 24 ...

Global site tag (gtag.js) - Google Analytics