课时44:String类对象两种实例化方式比较
摘要:本次课程的主要讨论了两种处理模式在Java程序中的应用,直接赋值和构造方法实例化。此外,还讨论了字符串池的概念,指出在Java程序的底层,DOM提供了专门的字符串池,用于存储和查找字符串。
1. 直接赋值的对象化模式
2. 字符串池的概念
3. 构造方法实例化
现在已经清楚String类对象实例化过程之中,提供有我们的两种的处理模式。现在需要区分这两种处理模式到底该使用哪种会更好?我们从直观的角度上观察到,用直接赋值感觉会更好,那么除此之外,这两种模式还有哪些的差别所在?
01. 直接赋值的对象化模式
我们分析直接赋值的对象实例化模式。我们知道在我们的程序之中,只需要将一个字符串赋值给String类的对象,可以实现对象的实例化处理。现在假设只有如下一行代码:
public class StringDemo { public static void main(String args[]) { String str = "mldn" } }
这种情况下只是开辟出一块堆内存空间,此时的内存关系图如下。整个操作的程序代码之中,只会开辟有一块内存和一块栈内存空间。
除了这种内存模式之外,利用直接赋值实例化String的形式还可以实现同一个字符串对象数据的共享操作。现在观察String直接赋值时的数据共享,现在给出下面的代码:
public class StringDemo{ public static void main(string args[]){ String strA="mldn"String strB ="mldn" System.out.println(strA=strB); //地址判断 } }
这段代码描述的是地址判断,判断两个对象的地址相同,意味着这两个对象是同一个对象。现在来执行一下程序,结果如下图。
程序的判断结果返回了true,我们可以得出结论:这两个对象所指向的堆内存是同一个。这个时候的内存关系如图所示。
从整个程序代码而言,此时确实实现了数据共享。那为什么可以共享呢?
之所以现在会有这样的一个叫的特点,主要的原因是因为在java 程序的底层里面提供有一个专门的字符串池(字符串数组)。
既然是数组,数组的内容都是一个具体的数据。我们把程序的流程重新画在内存关系图上,当我们第一次把数据放的时候,数组有一个自己的维护过程,但是默认情况下,值全都是Null。
当我们第一次存一个字符串,里面有“mldn”,那么这个时候 StrA指向的是“mldn”所在的池。当后面再有一个 StrB 的时候,它首先会查找池,如果池中有数据,那么这个时候会引用池中的数据,如果池中没有数据,它会放置一个新的字符串。
02. 字符串池的概念
我们分析一下字符串池的概念。下面以如下代码为例:
Class StringDemo{ public static void main(string args[]){ String strA="mldn"; String strB="mldnjava"; String strC="mldn" System.out.println(strA=strB); //地址判断 } }
现在分析代码程序运行过程,首先String strA="mldn"
,首先会发现了池中没有数据,它要保存一个新的数据。
当运行第二句代码String strB="mldnjava"
时,池中仍然没有数据,那么既然池中没有数据,则这个程序的代码就要在池中再开一个空间存放"mldnjava"。
当我们在执行第三句String strC="mldn"
代码的时候,此时池中有数据,所以它不会开辟新的空间,它会指向池中的"mldn"。
所以说之所以能有这样的一个主要的特征,是因为在整个直接赋值的过程之中,对于字符串而言,可以实现池数据的自动保存,这样如果再有相同数据定义时,可以减少对象的产生以提升操作性能。这就是第一种字符串池的概念的理解。
03. 构造方法实例化
构造方法进行的对象实例化,可以说是我们进行对象定义时的一种常见做法。之后String类为了满足于设计的结构要求,也提供有构造方法识别化的做法。下面以如下代码举例:
Class StringDemo{ public static void main(string args[]){ String str = new string("mldn"); } }
那么此时,对于本程序而言。我们可以通过内存关系图进行观察。首先这个程序,按照刚才所说,第一点字符串是什么?一定要清楚,首先我们在整个过程中重点强调了字符串是一个匿名对象。我们认为在匿名对象只占据堆内存空间,那它应该会开辟堆内存。那么如果说现在要以匿名对象的形式要进行开辟,但是这个开辟的结果并不是我们要的,而是new。
我们最终结果所要的东西是关键字 new 所带来的对象。在这样的情况下,真正开辟的空间是关键字的 new ,而关键性的问题是两个的内容是一样的,STR 指向的是new。此时会产生两块内存空间,能够明显的发现有一块内存空间就将成为垃圾空间。
这就是关键字 new 所带来的使用特征,那么这样的话,我们可以发现,如果以现在的程序为例,直接赋值要比关键字 new 开辟更加节约空间。此时会开辟两块堆内存空间,而后我们只会使用一块,而另外一个由于字符串的常量所定义的匿名对象将成为我们的垃圾空间。但是如果现在更换一种形式,代码如下:
Class StringDemo{ public static void main(string args[]){ String strA = "mldn"; String strB = new string("mldn"); } }
按照上述代码,下面分析String内存分析。字符串池也是堆内存,当我们执行String strA = "mldn"的时候,他要开辟一个字符串常量保存在池之中,直接赋值的最大特征在于可以自动将对象保存到字符对象池之中,这是进行直接赋值操作的一个最主要的是特征。
当再运行String str
B
= new string("mldn")
代码时,这个字符串是否有对应的一块堆内存空间?这个对象是匿名对象,这个对象在池中一定会有,因为此时 STRA 指向着这个内容,那么此时会引用池中对象。STRB 在整个过程中开辟自己的堆内存空间,在堆内存中开辟新的字符串对象,而另外一个它是可以实现对象的重用。
除了上述特点之外,使用构造方法实例化String类对象时,不会自动出现入保存到字符串池的处理,我们观察构造方法实例化对象时的实操作。此时代码如下:
Class StringDemo{ public static void main(string args[]){ String strA = "mldn"; String strB = new string("mldn"); System.out.println(strA=strB); //地址判断 } }
上述代码要判断地址相同,如果地址相同,就意味着 STRB 所指向的空间已经入池了,但是事实上,我们经过刚才的分析并没有入池,现在编译运行代码查看结果。
这个操作是不会自动入池,它是属于一个自己专用的空间。可以发现构造方法实例化的对象是属于一种自己专用的内存空间,有些时候在String类里面也提供有帮助开发者实现手工入池的处理情况。这个方法是:public
String
intern()
,下面观察手工入池,代码如下:
Class StringDemo{ public static void main(string args[]){ String strA = "mldn"; String strB = new string("mldn").intern(); System.out.println(strA=strB); //地址判断 } }
此时编译运行代码查看结果如图所示。结果为ture,意味着这个对象的处理过程现在实现了入池,它也占了池中的引用,加入intern()之后,在使用构造方法定义的对象之后,由于使用intern()的方法,即便使用了构造出来的String类对象的内容,也可以实现入对象池的统一管理,但这种方法太麻烦。
若有以下面试题:请解释String类两种对象实例化方式的区别?
1. 直接赋值:只会产生一个实例化对象,可以自动保存到对象池之中,以实现字符串实例的重用。
2. 构造方法:会产生两个实例化对象,不会自动入池,无法实现对象重用。
但是可以利用intern()的方法进行手工入池处理。这个就是String类两种对象的使用方式,当然最终结果可以发现,以后不要用 new ,直接赋值是最稳妥的方式。