本帖最后由 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
}
}
|