zoukankan      html  css  js  c++  java
  • Java对象创建方式及JVM对字符串处理

    1.Java程序中创建对象的5种常见方式  

    在讲Jvm对字符串的处理之前,我们先来讲一下,在Java中,最常见的5种创建对象的方式:

    1)通过关键字new调用构造器创建Java对象,eg :String str = new String("hello");

    2)通过Class对象的newInstance()方法调用构造器创建Java对象,eg : Class.forName("com.mysql.jdbc.Driver").newInstance();   

    3)通过Java的反序列化机制从IO流中恢复Java对象,eg :

    复制代码
     1 package test;
     2 
     3 import java.io.Serializable;
     4 
     5 public class Person implements Serializable {
     6 
     7     static final long serialVersionUID = 1L;
     8 
     9     String name; // 姓名
    10 
    11     public Person() {}
    12     
    13     public Person(String name) {
    14         super();
    15         this.name = name;
    16     }
    17 }
    复制代码
    复制代码
     1 package test;
     2 
     3 import java.io.FileInputStream;
     4 import java.io.FileOutputStream;
     5 import java.io.ObjectInputStream;
     6 import java.io.ObjectOutputStream;
     7 
     8 public class ObjectIo {
     9     public static void main(String[] args) throws Exception {
    10         Person p = new Person("小明");
    11         FileOutputStream fos = new FileOutputStream("d:/objectIoTest.dat");
    12         ObjectOutputStream oos = new ObjectOutputStream(fos);
    13         oos.writeObject(p);
    14         oos.flush();
    15         oos.close();    //前面这几行都是为了下面几行通过Java的反序列化机制从IO流中恢复Java对象作准备
    16         
    17         //下面才是开始通过Java的反序列化机制从IO流中恢复Java对象
    18         FileInputStream fis = new FileInputStream("d:/objectIoTest.dat");
    19         ObjectInputStream ois = new ObjectInputStream(fis);
    20         Person person = (Person) ois.readObject();
    21         System.out.println("这个人是 : " + person.name);
    22     }
    23 }
    复制代码

    运行结果:

    4)通过Java对象提供的clone()方法复制一个新的Java对象,eg :

    复制代码
     1 package test;
     2 
     3 /**
     4  * 必须实现Cloneable接口,并且重写clone()方法
     5  * @ClassName: Base 
     6  * @author 小学徒
     7  * @date 2013-3-28
     8  */
     9 public class Base implements Cloneable{
    10     int i = 20;
    11     
    12     @Override
    13     protected Object clone() throws CloneNotSupportedException {
    14         return super.clone();
    15     }
    16 }
    复制代码
    复制代码
    1 package test;
    2 
    3 public class CloneTest {
    4     public static void main(String[] args) throws Exception {
    5         Base b = new Base();
    6         Base c = (Base) b.clone();
    7         System.out.println("b和c是同一个对象? " + (c == b));
    8     }
    9 }    
    复制代码

    运行结果 :

    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("小学徒的成长历程")),因为凡是通过构造器创建的对象都会进行内存分配,所以他就不会指向缓存池中已有的对象而指向新的对象,这样就会造成缓存池中存在多个值相同的字符串对象,浪费了资源。

    复制代码
     1 public class Test{
     2     
     3     public static void main(String[] args) {
     4         //通过构造器进行初始化,如果是第一次,他同样会在缓存池中缓存该字符串
     5         //但是他依旧另外创建一个对象并指向该对象
     6         String newStr = new String("小学徒的成长历程");
     7         //javaStr的值是字符串直接量
     8         //所以,javaStr指向字符串缓存池中的"小学徒的成长历程"字符串
     9         String javaStr = "小学徒的成长历程";
    10         //由于缓存池中已经有了"小学徒的成长历程"字符串
    11         //所以,anotherStr也指向字符串缓存池中的"小学徒的成长历程"字符串
    12         String anotherStr = "小学徒的成长历程";
    13     
    14         System.out.println("javaStr == anotherStr : " + (javaStr == anotherStr));    //判断两个字符串是不是指向同一个对象
    15         System.out.println("newStr == anotherStr  : " + (newStr == anotherStr));
    16         System.out.println("newStr == javaStr     : " + (newStr == javaStr));
    17     } 
    18 }
    复制代码

    运行结果:

    上面的测试代码块执行后,他在内存中的分配情况是这样的:

    下面我们再看一题经典的笔试面试题:String javaStr = "小学徒" + "的" + "成长历程";总共创建了多少个字符串对象?

    答案是一个,因为如果一个字符串连接表达式的值可以在编译时确定下来,那么JVM会在编译时计算该字符串变量的值,并让他指向字符串池中对应的字符串。但如果程序使用了变量,或者调用了方法,那么就只能等到运行时才可确定该字符串连接式的值,也就无法在编译时确定字符串变量的值,因此无法确定该字符串变量的值,所以无法利用JVM的字符串池。

    下面我们写一段代码验证一下吧:

    复制代码
     1 public class Test{
     2     
     3     public static void main(String[] args) {
     4         String anotherStr = "小学徒的成长历程";
     5         
     6         //虽然javaStr的值不是直接量,但是因为javaStr的值可以在编译时确定
     7         //所以javaStr也会直接引用字符串池中对应的字符串
     8         String javaStr = "小学徒" + "的" + "成长历程";
     9         
    10         String a = "的";
    11         
    12         //使用了变量,只能等到运行时才可确定该字符串连接式的值
    13         //也就无法在编译时确定字符串变量的值,因此无法确定该字符串变量的值,所以无法利用JVM的字符串池
    14         String contactStr = "小学徒" + a + "成长历程";
    15         
    16         //调用了方法只能等到运行时才可确定该字符串连接式的值
    17         //也就无法在编译时确定字符串变量的值,因此无法确定该字符串变量的值,所以无法利用JVM的字符串池
    18         String methodStr =  "小学徒的成长历程" + a.length();
    19         
    20         //判断各个字符串是否相等
    21         System.out.println("javaStr == anotherStr : " + (javaStr == anotherStr));
    22         System.out.println("contactStr == javaStr : " + (contactStr == javaStr));
    23         System.out.println(" methodStr == javaStr : " + (methodStr == javaStr));
    24         
    25     
    26     } 
    27 }
    复制代码

    运行结果:

    ③呵呵,我们再用一题经典面试笔试题目来抛砖引玉吧,这样比较可以诱导大家的思考,同时增加大家的兴趣,不会太过闷,而且还能提醒大家在笔试面试的时候该注意什么地方,好啦,言归正传。String name = "小学徒";  name = name + "的成长空间";两条语句总共创建了多少个字符串对象?

    答案是两个,因为当一个String对象创建完成后,该String类里包含的字符串序列就被固定下来了,以后永远都不能改变。(如果目前不懂这句的话,没关系,看下补充你就理解的了)

    复制代码
    1 public class Test{
    2     
    3     public static void main(String[] args) {
    4         String name = "小学徒";    //定义一个字符串变量
    5         System.out.println(System.identityHashCode(name));    //输出该对象的hashCode值
    6         name = name + "的成长空间"; //拼接字符串变量
    7         System.out.println(System.identityHashCode(name));//输出该对象的hashCode值
    8     } 
    9 }
    复制代码

    运行结果:

    我们可以看到两个的值是不一样的,所以此处说明String是典型的不可变类,上述代码之后代码中的内存分配情况是

    或许你看了之后会说,没关系啊,这个java会自动进行垃圾回收,到时候回收就行了,到这里,我就得补充一下前面没有说到的问题了:

    java为了节省内存,提高资源的复用,才引入了字符串缓存池的概念,而且,在缓存池中的字符串是不会被垃圾回收机制回收的,基本都是常驻内存,所以过多使用String类,可能会出现内存溢出

    所以前面的代码中,对String对象进行操作后,其返回的是一个新的对象,之前那个对象是没有改变的,改变的是name这个引用所指的对象,这时候的对象已经是新的对象,然而之前那个对象被废弃了,但是他存在缓存池,因此不会被垃圾回收机制回收,所以这里会容易出现内存泄漏,所以如果要操作字符串,尽量不用String而改为使用StringBuffer或者StringBuilder。

    2)StringBuilder和StringBuffer:可变的字符串

    之所以说他们会改变的原因是:StringBuilder和StringBuffer在进行字符串操作的时候就不会去创建一个新出现的对象,引用的都是同一个对象,减少了String带来的弊端。

    复制代码
    1 public class Test{
    2     
    3     public static void main(String[] args) {
    4         StringBuilder sb = new StringBuilder("小学徒");
    5         System.out.println(System.identityHashCode(sb));
    6         sb.append("的成长历程");
    7         System.out.println(System.identityHashCode(sb));
    8     }
    9 }
    复制代码

    运行结果:

    那么StringBuilder和StringBuffer这两个类有什么区别呢?

    他们之间的唯一区别就在于StringBuffer是线程安全的,也就是说StringBuffer类里绝大部分方法都增加了synchronized修饰符,这样就降低了该方法的执行效率,所以在没有多线程的环境下,推荐使用StringBuilder。

     StringBuffer的源代码:

    StringBuilder的源代码:

     

     

    转载请注明出处:http://www.cnblogs.com/xiaoxuetu/  ,谢谢合作

  • 相关阅读:
    希腊字母写法
    The ASP.NET MVC request processing line
    lambda aggregation
    UVA 10763 Foreign Exchange
    UVA 10624 Super Number
    UVA 10041 Vito's Family
    UVA 10340 All in All
    UVA 10026 Shoemaker's Problem
    HDU 3683 Gomoku
    UVA 11210 Chinese Mahjong
  • 原文地址:https://www.cnblogs.com/daichangya/p/12959652.html
Copyright © 2011-2022 走看看