一、String类的使用:
- String:字符串,使用一对“”引起来表示
- String声明为final的,不可以被继承
- 字符串的字符使用Unicode进行编码,一个字符(不区分字母还是汉字)占两个字节
- String实现了Serializable接口:表示字符串是支持序列化的,说明可以在网络上传输。
- 实现了Comparable接口:表示String可以比较大小
- String类内部定义了final char[] value用于存储字符串数据
- 一定要注意:value是一个final类型,不可以修改:即value不能指向新的地址,但是单个字符内容是可以变化的。
- String代表不可变的字符序列,简称:不可变性
体现:
1.当对字符串重新赋值时,需要重新指定内存区域赋值,不能使用原有的value进行赋值。
2.当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
3.当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值。
- 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中
- 字符串常量池中是不会存储相同内容的字符串的
案例代码如下:
package www.demo1; import org.junit.Test; public class StringTest { @Test public void test1(){ final char[] value = {'a', 'b', 'c'}; char[] v2 = {'t', '0', 'm'}; value[0] = 'H'; // value=v2; //不可以修改value地址 //"abc" 字符串常量,双引号括起的字符序列 String s1="abc"; //字面量的定义方式 String s2="abc"; s1="hello"; System.out.println(s1); System.out.println(s2); System.out.println("***********************"); //比较s1,s2的地址值,结果为true System.out.println(s1==s2); String s3="abc"; s3+="def"; System.out.println(s3); System.out.println("***********************"); String s4="abc"; String s5=s4.replace("a","m"); System.out.println(s4); System.out.println(s5); } }
二、String实例化的方式:
方式一:通过字面量定义的方式
方式二:通过new + 构造器() 的方式
代码理解:
//通过字面量定义的方式:此时数据abc声明在方法区中的字符串常量池中 String s1="abc"; String s2="abc"; //通过new+构造器的方式: 此时s3,s4保存的地址值,是数据在堆空间中开辟以后对应的地址值 String s3 = new String("abc"); String s4 = new String("abc"); System.out.println(s1==s2);//true System.out.println(s1==s3);//false System.out.println(s1==s4);//false System.out.println(s3==s4);//false System.out.println("**************************"); Person p1 = new Person("Tom",14); Person p2 = new Person("Tom",14); System.out.println(p1.name.equals(p2.name));//true,因为比较的是实体内容(String重写了equals()方法) System.out.println(p1==p2);//false new的两个对象地址值是不一样的 System.out.println(p1.name==p2.name); //true p1.name="Lay"; System.out.println(p2.name); //结果为Tom
结论:
常量与常量的拼接结果在常量池,且常量池中不会存在相同内容的常量
只要其中有一个结果是变量,结果就在堆中
如果拼接的结果调用intern()方法,返回值就在常量池中
加上代码便于理解:
String s1 = "Java"; String s2 = "Android"; String ss ="JavaAndroid"; String s3 = "Java"+"Android"; String s4 = s1 + "Android"; String s5 = "Java" + s2; String s6 = s1 + s2; System.out.println(ss==s3);//true System.out.println(ss==s4); //false System.out.println(ss==s5);//false System.out.println(ss==s6);//false System.out.println(s4==s5);//false String s7=s4.intern(); System.out.println(ss==s7);//true
额外补充**************************
创建String对象的两种方式:
String的内存布局
String的相关测试
测试题1:
public class StringExercise01 { public static void main(String[] args) { String a = "abc"; String b = "abc"; //String重写了equals方法 ,比较的是具体的值,所以为true System.out.println(a.equals(b)); //true //a 首先看常量池有没有abc //b 首先看常量池有没有abc 有,就将b直接指向abc //所以地址值是一样的 System.out.println(a == b); //true } }
测试题2:
public class StringExercise02 { public static void main(String[] args) { String a = "ly"; String b = new String("ly"); System.out.println(a.equals(b)); //true System.out.println(a == b); //false System.out.println(a == b.intern()); //true System.out.println(b == b.intern()); //false //当调用intern()方法时,如果池已经包含一个等于此String对象的字符串(用equals(Object)方法确定) //则返回池中的字符串.否则,将此String对象添加到池中,并返回此String对象的引用 //解读:b.intern() 方法最终返回的是常量池的地址(对象) } }
测试题3:
public class StringExercise03 { public static void main(String[] args) { String s1 = "ly"; //s1 指向常量池的ly String s2 = "java"; //s2 指向常量池的java String s4 = "java"; //s4 指向常量池的java String s3 = new String("java"); //指向堆中的对象 System.out.println(s2 == s3); //false System.out.println(s2 == s4); //true System.out.println(s2.equals(s3)); //true System.out.println(s1 == s2); //false } }
测试题4:
public class StringExercise04 { public static void main(String[] args) { Person p1 = new Person(); p1.name = "lyedu"; Person p2 = new Person(); p2.name = "lyedu"; System.out.println(p1.name.equals(p2.name)); //比较的内容 true System.out.println(p1.name == p2.name); //true //"lyedu"返回的地址:就是在常量池中的地址 //p1.name 指向的也是常量池中的"lyedu" 对应的地址 System.out.println(p1.name == "lyedu"); //true String s1 = new String("bcde"); String s2 = new String("bcde"); System.out.println(s1 == s2); //false } } class Person { public String name; }
对应的内存布局图如下:
测试题5:
//以下语句创建了几个对象,并画出内存布局图
String s1="hello";
s1="haha";
//创建了两个对象
内存布局图
String类相关的面试题
面试题1:
//创建了几个对象? 只有1个对象
String a = "hello" + "abc";//等价于==>String a = "helloabc";
System.out.println(a);
面试题2:
一共有3个对象,内存布局图如下:
具体代码和讲解如下:
public class StringExercise05 { public static void main(String[] args) { String a = "hello"; //创建a对象 String b = "abc"; //创建b对象 //解读 //1.先创建一个StringBuilder sb = new StringBuilder(); //2.执行 sb.append("hello"); //3.执行sb.append("abc"); //4.String c = sb.toString(); //最后其实是 c 指向堆中对象的(String) value[]->池中 "helloabc" String c = a + b; String d = "helloabc"; System.out.println(c == d); //false String e = "hello" + "abc"; System.out.println(d == e);//true } }
该题小结:
底层是StringBuilder sb = new StringBuilder();
sb.append(a); sb.append(b); sb是在堆中,并且append是在原来字符串的基础上追加的。
重要规则:
String c1=“ab” + “cd”; 常量相加,看的是池。
String c1 = a + b; 变量相加,是在堆中。
面试题3:
public class StringExercise06 { public static void main(String[] args) { String s1 = "lyedu";//指向常量池中的"lyedu" String s2 = "java";//指向常量池中的"java" String s5 = "lyedujava"; //指向常量池中的"lyedujava" String s6 = (s1 + s2).intern(); //指向池中的 "lyedujava" System.out.println(s5 == s6); //true System.out.println(s5.equals(s6)); //true } }
面试题4:
public class StringExercise07 { public static void main(String[] args) { Test1 ex = new Test1(); ex.change(ex.str, ex.ch); System.out.println(ex.str + " and "); System.out.println(ex.ch); } } class Test1 { String str = new String("ly"); final char[] ch = {'j', 'a', 'v', 'a'}; public void change(String str, char ch[]) { str = "java"; ch[0] = 'h'; } }
输出结果为:
ly and
hava
以上是String字符串的讲解,有任何问题都可以在评论区指出!