A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 xiaoyi_2018 于 2018-4-8 19:16 编辑

通过代码及注释来详细解说String的几种实例化在内存中的差别与static final修饰符说明,建议复制代码块在eclipse查看。文件一:
[Java] 纯文本查看 复制代码
package com.it.test01;

public class Test01 {
    public static void main(String[] args) {
        String str1 = "abcd"; // 直接赋值
        String str2 = new String("1234"); // 使用标准的new调用构造方法

        // 使用直接赋值后,只要是以后声明的字符串内容相同,则不会再开辟新的内存空间。
        String str3 = "abcd";
        System.out.println(str1.equals(str3));
        System.out.println(str1 == str3);

        /*
         * String直接赋值,用的是同一个内存空间,称为共享设计
         * 其值保持在字符串对象池中,即方法区的常量池 
         * 疑问:1.说明这个字符串也是随着类的加载而加载? 
         * 如果在方法中呢,随着方法的加载加载到常量池? -----可以说是运行期时,在类加载到方法区时,检索了所有的字面值常量入常量池
         * 是在编译期就已经在字符池生成? 不是。在编译期,方法区还没加载类,常量池还是空的。运行期才进行类的加载,常量值常量入常量池
         * 可是并不是静态变量呀?--静态常量也是运行期,随着类的加载而加载
         * ---在运行期,随着执行到String str2 = new String("1234");这行代码时,才在常量池中实例化一份,堆里复制一份,共有两份
         * String a = "123";运行期开始时,随着类的加载,jvm会自动检索入常量池
         * JVM会先到常量池里查找,
         * 如果有的话返回常量池里的这个实例的引用,
         * 否则的话创建一个新实例并置入常量池里。
         * 
         * 
         */
        System.out.println("---------------------------");
        //使用new关键字,不管如何都会开辟新的空间
        String str4=new String("abcd");


        System.out.println(str4.equals(str1));
        System.out.println(str4 == str1);

        System.out.println("----------------------------");

        String str5=new String("abcd");
        System.out.println(str4.equals(str5));
        System.out.println(str4 == str5);

        /*
         * new创建字符串时首先查看池中是否有相同的字符串,
         * 如果有,则把常量池中的地址给放堆中存储,然后把堆中的地址返回
         * 如果池中没有,会在池中创建一份常量
         * 
         */
        String str6="ab"+"cd";
        System.out.println(str6.equals(str1));
        System.out.println(str6==str1);

        System.out.println(str6.equals(str4));
        System.out.println(str6==str4);

        System.out.println("-----------------------------");
        String str7="ab";
        String str8="cd";
        String str9=str7+str8;

        System.out.println(str9.equals(str1));
        System.out.println(str9==str1);



        /*
         * String实例化的时机
         * 1.直接赋值的字符串是常量,在运行期,跟静态变量一样,随着类的加载而加载,jvm会检索其中的字面值常量值,常量存储到常量池
         * 2.使用new实例化创建的对象存储在堆(heap)中,是运行期执行到这行代码新创建的
         * 3.使用只包含常量的字符串连接符如"a"+"b"创建的也是常量,运行期初就会确定,存储在String Pool中
         * 4.使用包含变量的字符串连接符如"a"+s1创建的对象是运行期运行到这段代码时才创建的,存储在堆(heap)中,然后复制给常量池
         * 
         * 注意:
         * 第3句话,运行期加载类时就会确定合并后的字符串,存储在jvm的字符串池中。
         * 这里是在字符串池中只生成一个最终的对象,而没有"a","b"
         * 
         * 也有说是编译期就把字面值常量就存储在常量池的,我觉得不对
         */

        /*
         * 字符串池的优缺点
         * 优点:
         *  避免了相同内容字符串的重复创建,节省了内存,
         *  省去了再次创建相同字符串的时间,提升了性能
         * 缺点:
         *  牺牲了JVM在常量池中遍历对象所需要的时间,不过这个时间成本相对较低
         *  
         */

        /*
         * static final修饰的字符串
         * private static final String = "abc" ;
         * 
         * 1.使用直接赋值,字符串会在运行初期生成在字符串池中
         * 2.final修饰的变量即为常量,只能被赋值一次。
         * 3.static修饰符能修饰属性、方法和内部类,表示静态的。
         *   类中的静态变量和静态方法通过类名.调用,不需要创建一个类的对象来访问该类的静态成员
         *   所以static修饰的变量也被称为类变量
         *   优先于对象而存在
         *   随着类的加载而加载
         *   当然也可以通过对象.调用
         *   
         *   static修饰的成员属于类,类的成员都存储在堆内存中
         *   一个类中,一个static变量只会有一个存储空间
         *   即使有多个类实例,这些类实例中的这个static变量会共享同一个内存空间
         *   
         *   static修饰的String,会在堆内存中复制一份常量池中的值。
         *   所以调用static final String变量,实际是调用堆内存的地址,不会遍历字符串池的对象,节省了遍历时间
         * 
         */



    }
}

文件二:
[Java] 纯文本查看 复制代码
package com.it.test01;
/*
 * 字符串对象可以存放在两个地方,字符串池(pool)和堆,
 * 类加载时确定如何给一个引用变量赋值
 */
public class Test02 {
    public static final String A = "ab";
    public static final String B = "cd";

    public static final String C;
    public static final String D;
    static {
        C = "ab";
        D = "cd";
    }

    public static void main(String[] args) {
        /*
         * 从pool中寻找字符串"abcd"并返回池中的地址给t
         * pool中如果没有就在池中新建并返回地址给t
         */
        String t = "abcd";// 指向池
        /*
         * 运行期将在堆上新建字符串对象,并返回堆的地址给str
         * 这个对象不会加入到pool
         */
        String str = new String("abc");

        String s1 = "ab";// 指向池
        String s2 = "cd";// 指向池

        /*
         * s1和s2都是变量
         * 将在堆上创建s1和s2,(即便s1和s2指向的对象在池中已经存在,也会将值拷贝到对象创建新对象)
         *然后在堆上另创建一个对象存储合并后的值,并返回该对象在堆中的地方给s
         *
         */
        String s = s1 + s2;// 指向堆
        System.out.println(s == t);// false

        String ss = "ab" + s2;// 指向堆
        System.out.println(ss == t);// false

        /*
         * 运行初期得出合并后的"abcd"加入到pool中,并返回池中的地址给sss
         */
        String sss = "ab" + "cd";// 指向池
        System.out.println(sss == t);// true

        /*
         * A和B都是常量,常量只能被赋值一次
         * A,B在声明的地方就赋值,那么这个值在运行期类加载时就是确定的,后面无法更改
         * A,B运行初期随着类的加载就已经确定,存储在常量池中
         * A+B的值在运行期也确定,也存储在常量池,并返回在池中的地址给ssss
         * 
         */
        String ssss = A + B;// 指向池
        System.out.println(ssss == t);// true

        /*
         * C和D是实例常量,在构造器中赋值
         * 或者是类常量,在静态块中赋值
         * C+D无法确定C和D来自池,于是在堆上进行
         */
        System.out.println((C + D) == t);// false
    }

}

1 个回复

倒序浏览
加油
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马