
7.4 两种实例化方式比较

清楚了String类的比较操作之后,下面就需要解决一个最为重要的问题。对于String类的对象存在两种实例化的操作方式,那么这两种方式有什么区别,在开发中应该使用哪一种方式更好呢?
1.分析直接赋值的对象实例化模式
在程序中只需将一个字符串赋值给String类的对象就可以实现对象的实例化处理,如以下范例所示。
范例:直接赋值实例化对象

此时在内存中会开辟一块堆内存,内存空间中将保存有"mldn"字符串数据,并且栈内存将直接引用此堆内存空间,如图7-4所示。

图7-4 String类直接赋值实例化
通过图7-4可以发现,通过直接赋值的方式为String类对象实例化会开辟一块堆内存空间,而且对同一字符串的多次直接赋值还可以实现对堆内存空间的重用,即采用直接赋值的方式进行String类对象实例化,在内容相同的情况下不会开辟新的堆内存空间,而会直接指向已有的堆内存空间。
范例:观察直接赋值时的堆内存自动引用

通过本程序的执行可以发现,由于使用了直接赋值实例化操作方式,而且内容相同,所以即使没有直接发生对象的引用操作,最终两个String对象(strA、strB)也都自动指向了同一块堆内存空间。但是如果在直接赋值时内容与之前不一样,则会自动开辟新的堆内存空间(String strd="yootk";),本程序的内存关系如图7-5所示。

图7-5 String对象自动引用
提示:关于字符串对象池。
实际上,在JVM的底层存在有一个对象池(String只是对象池中保存的一种类型,此外还有多种其他类型),当代码中使用了直接赋值的方式定义了一个String类对象时,会将此字符串对象所使用的匿名对象入池保存,如果后续还有其他String类对象也采用了直接赋值的方式,并且设置了同样内容,那么将不会开辟新的堆内存空间,而是使用已有的对象进行引用的分配,从而继续使用。
范例:通过代码分析字符串对象池操作

本程序采用直接赋值的方式声明了3个String类对象,实质上,这些对象都保存在字符串对象池(本质上是保存在了一个动态对象数组)中,对于此时的程序,就可以得出图7-6所示的内存关系。
对象池本质为共享设计模式的一种应用,关于共享设计模式的简单解释:好比在家中准备的工具箱一样,如果有一天需要用到螺丝刀,发现家里没有,那么肯定要去买一把新的,但是用完之后不可以丢掉,会将其放到工具箱中以备下次需要时继续使用,工具箱中所保存的工具将为家庭中的每一个成员服务。

图7-6 字符串对象池操作分析

图7-6 字符串对象池操作分析(续)
2.分析构造方法实例化
如果要明确地调用String类中的构造方法进行String类对象的实例化操作,那么一定要使用关键字new,而每当使用关键字new就表示要开辟新的堆内存空间,而这块堆内存空间的内容就是传入构造方法中的字符串数据,现在使用以下代码进行该操作的内存分析。
范例:构造方法实例化对象

本程序使用一个字符串常量作为str对象的内容,并利用构造方法实例化了一个新的String类对象,本程序语句执行后的内存结构如图7-7所示。

图7-7 构造方法实例化String类对象
因为每一个字符串都是一个String类的匿名对象,所以首先在堆内存中开辟一块空间保存字符串"mldn",而后又使用关键字new,开辟另一块堆内存空间,而真正使用的是用关键字new开辟的堆内存。而之前定义的字符串常量开辟的堆内存空间将不会被任何的栈内存所指向,成为垃圾空间,并等待被GC回收。所以,使用构造方法的方式开辟的字符串对象,实际上会开辟两块空间,其中有一块空间将成为垃圾。
除了内存的浪费之外,如果使用了构造方法实例化String类对象,由于关键字new永远表示开辟新的堆内存空间,所以其内容不会保存在对象池中。
范例:构造方法实例化String类对象不自动入池保存


本程序首先利用构造方法开辟了一个新的String类对象,由于此时不会自动保存到对象池中,所以在使用直接赋值的方式声明String类对象后将开辟新的堆内存空间。因为两个堆内存的地址不同,所以最终的地址判断结果为false。
如果现在希望开辟的新内存数据也可以进入对象池保存,那么可以采用String类定义的一个手动入池的操作。
手动保存到对象池:public String intern()。
范例:String类对象手动入池

本程序由于使用了String类的intern()方法,所以会将指定的字符串对象保存在对象池中,随后如果使用直接赋值的方式将会自动引用已有的堆内存空间,所以地址判断的结果为true。
提示:两种String类对象实例化区别。
直接赋值(String str="字符串";):只会开辟一块堆内存空间,并且会自动保存在对象池中以供下次重复使用。
构造方法(String str=new String("字符串")):会开辟两块堆内存空间,其中有一块空间将成为垃圾,并且不会自动入池,但是用户可以使用intern()方法手动入池。
在读者进行实际项目开发过程中,请尽量使用直接赋值的方式为String类对象实例化。