创建Java对象的五种方式以及JVM对字符串处理

看了原文章写的很不错,自己整理了一下,转载分享。原文地址:http://www.cnblogs.com/xiaoxuetu/archive/2013/03/28/2987239.html

一:获取Java对象的五种方式。
1通过关键字new调用构造器创建Java对象,eg :String str = new String ("hello");
2。 通过Class对象的 newInstance() 方法调用构造器创建Java对象,eg : Class.forName("com.mysql.jdbc.Driver").newInstance();  获取到Driver对象。
3 。通过Java的反序列化机制从IO流中恢复Java对象, Java对象要实现 Serializable 接口
public class ObjectIo {
    public static void main(String[] args) throws Exception {
        Person p = new Person("小明");
        FileOutputStream fos = new FileOutputStream("d:/objectIoTest.dat");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(p);
        oos.flush();
        oos.close();    //前面这几行都是为了下面几行通过Java的反序列化机制从IO流中恢复Java对象作准备
        
        //下面才是开始通过Java的反序列化机制从IO流中恢复Java对象
        FileInputStream fis = new FileInputStream("d:/objectIoTest.dat");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Person person = (Person) ois.readObject();
        System.out.println("这个人是 : " + person.name);
    }
}
4 通过Java对象提供的 clone()方法 复制一个新的Java对象 。首页该Java类要实现 Cloneable 接口
5 除上述四点之外,对于字符串以及基本类型的包装类(Byte, Short, Integer, Long, Character, Float, Double 和 Double),Java允许他们以直接量来创建Java对象,eg:Integer in = 5;
2.JVM对字符串变量的处理
在Java中,我们经常会用到字符串类型,关于字符串类型,有这么 三个类型:String , StringBuffer, StringBuilder ,那么为什么一个简单的字符串类型要分为这三种呢?JVM对他们的处理有是怎样的呢?
1)String,不可变的字符串
我们先来看一下最基本的笔试面试题: String javaStr = new String("小学徒的成长历程");这条语句创建了几个字符串对象?
答案是 两个 ,一个是“小学徒的成长历程”这个 直接量对应的字符串对象 ,一个是由new String()构造器返回的 字符串对象
那么究竟为什么是两个呢?为什么会有直接量对应的字符串对象呢?好啦,言归正传。其实这个就与JVM对字符串变量的处理有关了。
对于Java程序中的字符直接量(eg: String javaStr   =   "小学徒的成长历程 " ),JVM会使用一个 字符串池 来保存他们, 当第一次使用某个字符串直接量时,JVM会将它放入字符串池进行 缓存 。当程序再次需要使用该字符串时,无须重新创建一个新的字符串,而是直接引用变量执行字符串中已有的字符串。但是对于使用构造器进行初始化的字符串(eg : String javaStr = new String("小学徒的成长历程") ),因为 凡是通过构造器创建的对象都会进行内存分配 ,所以他就不会指向缓存池中已有的对象而指向新的对象,这样就会造成缓存池中存在多个值相同的字符串对象,浪费了资源。
看一张图片就明白了:


2。 下面我们再看一题经典的笔试面试题: String javaStr = "小学徒" + "的" + "成长历程";总共创建了多少个字符串对象?
答案是 一个, 因为 如果一个字符串连接表达式的值可以在 编译时确定下来 ,那么JVM会在编译时计算该字符串变量的值,并让他指向字符串池中对应的字符串。但如果程序使用了变量,或者调用了方法,那么就只能等到 运行时 才可 确定 该字符串连接式的值,也就无法在编译时确定字符串变量的值,因此无法确定该字符串变量的值,所以无法利用JVM的字符串池。
下面我们写一段代码验证一下吧:
public class Test{
    
    public static void main(String[] args) {
        //通过构造器进行初始化,如果是第一次,他同样会在缓存池中缓存该字符串
        //但是他依旧另外创建一个对象并指向该对象
        String newStr = new String("小学徒的成长历程");
        //javaStr的值是字符串直接量
        //所以,javaStr指向字符串缓存池中的"小学徒的成长历程"字符串
        String javaStr = "小学徒的成长历程";
        //由于缓存池中已经有了"小学徒的成长历程"字符串
        //所以,anotherStr也指向字符串缓存池中的"小学徒的成长历程"字符串
        String anotherStr = "小学徒的成长历程";
    
        System.out.println("javaStr == anotherStr : " + (javaStr == anotherStr));    //判断两个字符串是不是指向同一个对象
        System.out.println("newStr == anotherStr  : " + (newStr == anotherStr));
        System.out.println("newStr == javaStr     : " + (newStr == javaStr));
    } 
}
3。我们再用一题经典面试笔试题目来抛砖引玉吧,这样比较可以诱导大家的思考,同时增加大家的兴趣,不会太过闷,而且还能提醒大家在笔试面试的时候该注意什么地方,好啦,言归正传。
String name = "小学徒";  name = name + "的成长空间";两条语句总共创建了多少个字符串对象?
答案是 3个 ,一个是“小学徒”,一个是"的成长空间",这两个是存在与缓存池中的,还有一个是"小学徒的成长历程",这个是在 运行时期 确定的,不会缓存于 缓冲区
public class Test{
    public static void main(String[] args) {
        String name = "小学徒";    //定义一个字符串变量
        System.out.println(System.identityHashCode(name));    //输出该对象的hashCode值
        name = name + "的成长空间"; //拼接字符串变量
        System.out.println(System.identityHashCode(name));//输出该对象的hashCode值
    } 
打印出来的两个hashcCode值不一样,这就说明String是不可改变的。内存中的引用已经发生变化, 返回的是一个新的对象



或许你看了之后会说,没关系啊,这个java会自动进行垃圾回收,到时候回收就行了,到这里,我就得补充一下前面没有说到的问题了:
java为了节省内存,提高资源的复用,才引入了 字符串缓存池 的概念,而且,在缓存池中的字符串是不会被垃圾回收机制回收的,基本都是常驻内存,所以过多使用String类,可能会出现内存溢出
所以前面的代码中,对String对象进行操作后,其返回的是一个 新的对象 ,之前那个对象是没有改变的,改变的是name这个引用所指的对象,这时候的对象已经是新的对象,然而之前那个对象被废弃了,但是他存在缓存池,因此不会被垃圾回收机制回收, 所以这里会容易出现内存泄漏 ,所以如果 要操作字符串,尽量不用String而改为使用StringBuffer或者StringBuilder
2)StringBuilder和StringBuffer:可变的字符串
之所以说他们会改变的原因是:StringBuilder和StringBuffer在进行字符串操作的时候就不会去创建一个新出现的对象, 引用的都是同一个对象 ,减少了String带来的弊端。
public class Test{
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("小学徒");
        System.out.println(System.identityHashCode(sb));
        sb.append("的成长历程");
        System.out.println(System.identityHashCode(sb));
    }
}
打印的结果一样。 那么StringBuilder和StringBuffer这两个类有什么区别呢?
他们之间的 唯一区别 就在于StringBuffer是线程安全的,也就是说StringBuffer类里绝大部分方法都增加了 synchronized修饰符 ,这样就降低了该方法的执行效率,所以在没有多线程的环境下,推荐使用StringBuilder。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
String在JVM中的存储方式是通过一个char数组来存储字符串的字符数据。在JDK8及以前的版本中,String内部定义了一个final char\[\] value来存储字符串数据。而在JDK9中,String的存储结构发生了变化,改为了使用byte\[\]来存储字符串数据。\[2\] String对象JVM中的存储位置有两种情况: 1. 字符串常量池:在JDK中,双引号括起来的字符串常量,例如"abc"、"def",都是直接存储在方法区的字符串常量池中。这是因为字符串在实际开发中使用非常频繁,为了提高执行效率,将字符串放在字符串常量池中。 2. 堆内存:使用new关键字创建字符串对象会在堆内存中开辟空间。例如在示例代码中,使用new关键字创建字符串对象"c",会在堆内存中开辟空间存储字符串数据。\[3\] 需要注意的是,字符串是不可变的,即一旦创建就不能修改。这是因为String类被声明为final,不可被继承,并且String对象JVM中是不可变的。这意味着一旦字符串对象创建,它的值就不能被改变。\[3\] #### 引用[.reference_title] - *1* [【Java】String字符串JVM中的存储及其内存地址问题](https://blog.csdn.net/weixin_43390123/article/details/124376835)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [JVM上篇_13_StringTable_尚硅谷](https://blog.csdn.net/weixin_43811294/article/details/125462300)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [Java基础String字符串存储原理](https://blog.csdn.net/qq_46096136/article/details/126533585)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值