目录
String类的理解和创建对象
两种创建String对象的区别
String习题
第一题
第二题
第三题
第四题
第五题
第六题
第七题
第八题
第九题
1)String对象用于保存字符串,也就是一组字符序列
2)字符串常量对象是用双引号括起的字符序列。例如:“你好”、"12.97"、"boy"等
3)字符串的字符使用Unicode字符编码,一个字符(不区分字母还是汉字)占两个字节。
4)String类较常用构造器(其它看手册):
代码演示:
细节:
String 是final 类,不能被其他的类继承
String 有属性 private final char value[]; 用于存放字符串内容
一定要注意:value 是一个final类型, 不可以修改():指的的是value不能指向新的地址,但是单个字符内容是可以变化
package idea.chapter13.string_;
public class String01 {
public static void main(String[] args) {
//1.String 对象用于保存字符串,也就是一组字符序列
//2. "jack" 字符串常量, 双引号括起的字符序列
//3. 字符串的字符使用Unicode字符编码,一个字符(不区分字母还是汉字)占两个字节
//4. String 类有很多构造器,构造器的重载
// 常用的构造器有
//String s1 = new String();
//String s2 = new String(String original);
//String s3 = new String(char[] a);
//String s4 = new String(char[] a,int startIndex,int count)
//String s5 = new String(byte[] b)
//5. String 类实现了接口 Serializable[String 可以串行化:可以在网络传输]
// 接口 Comparable [String 对象可以比较大小]
//6. String 是final 类,不能被其他的类继承
//7. String 有属性 private final char value[]; 用于存放字符串内容
//8. 一定要注意:value 是一个final类型, 不可以修改():指的的是value不能指向新的地址,但是单个字符内容是可以变化
//举例
//一个final修饰的char数组
final char[] value = {'a', 'b', 'c'};
//例外一个char数组
char[] v2 = {'t', 'o', 'm'};
value[0] = 'H';//修改final修饰的char数组中的内容,不会报错
//注意,如果让原数组,指向一个新数组,那么是不可以的,下面的这句话就会报错,但是如果去掉final,就不会报错
//value = v2; 不可以修改 指定是不可以修改value地址,可以修改内容
String name = "jack";
name = "tom";
}
}
方式一:直接赋值Strings="jack";
方式二:调用构造器 String s2=new String("jack");
·1.方式一:先从常量池查看是否有"jack"数据空间,如果有,直接指向;如果 没有则重新创建,然后指向。s最终指向的是常量池的空间地址
2.方式二:先在堆中创建空间,里面维护了value属性,指向常量池的jack空间。 如果常量池没有"jack",重新创建,如果有,直接通过value指向。最终指向 的是堆中的空间地址。
思路分析: 1.在执行String a = "abc";这句话的时候,因为是直接赋值,所以会先去常量池中查看是否有abc的数据空间,在查找完之后发现没有,就会在常量池中创建,并让a指向这个空间 2.在执行String b = "abc";这句话的时候,因为是直接赋值,所以会先去常量池中查看是否有abc的数据空间存在,在查找我之后,发现存在,就不会创建空间,而是让b直接指向 所以在执行a.equals(b)的时候,因为String类重写了equals方法,判断的是值是否相等,所以这里比较的是内容,因此返回true 看看String类的equals方法的源码 public boolean equals(Object anObject) { if (this == anObject) {//判断传入参数的类型是否和当前的类型是否相同 return true; } if (anObject instanceof String) {//判断传入参数的运行类型是不是String类型或者是String类的子类型 String anotherString = (String)anObject;//如果是String类型或者是String类的子类型,那么就向下转型 int n = value.length; if (n == anotherString.value.length) {//如果长度相同 char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) {//然后一个一个的比较字符 if (v1[i] != v2[i])//如果发现两个数组中,有一个字符不相同那么就返回false return false; i++; } return true;//如果两个字符串的所有字符都相等,则返回true } } return false;//如果比较的不是字符串(也就是传入的数据类型,不是String类型或者不是String类型的子类型),则直接返回false } 在执行a==b的时候,因为String是引用类型判断的是地址是否相同,因为a和b指向的都是常量池中的同一块空间,所以返回true
package idea.chapter13.string_;
public class StringExercise01 {
public static void main(String[] args) {
/*
结论
·1.方式一:先从常量池查看是否有"jack"数据空间,如果有,直接指向;如果
没有则重新创建,然后指向。s最终指向的是常量池的空间地址
2.方式二:先在堆中创建空间,里面维护了value属性,指向常量池的jack空间。
如果常量池没有"jack",重新创建,如果有,直接通过value指向。最终指向
的是堆中的空间地址。
*/
String a = "abc";
String b = "abc";
System.out.println(a.equals(b));
System.out.println(a == b);
/*
思路分析:
1.在执行String a = "abc";这句话的时候,因为是直接赋值,所以会先去常量池中查看是否有abc的数据空间,在查找完之后发现没有,就会在常量池中创建,并让a指向这个空间
2.在执行String b = "abc";这句话的时候,因为是直接赋值,所以会先去常量池中查看是否有abc的数据空间存在,在查找我之后,发现存在,就不会创建空间,而是让b直接指向
所以在执行a.equals(b)的时候,因为String类重写了equals方法,判断的是值是否相等,所以这里比较的是内容,因此返回true
看看String类的equals方法的源码
public boolean equals(Object anObject) {
if (this == anObject) {//判断传入参数的类型是否和当前的类型是否相同
return true;
}
if (anObject instanceof String) {//判断传入参数的运行类型是不是String类型或者是String类的子类型
String anotherString = (String)anObject;//如果是String类型或者是String类的子类型,那么就向下转型
int n = value.length;
if (n == anotherString.value.length) {//如果长度相同
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {//然后一个一个的比较字符
if (v1[i] != v2[i])//如果发现两个数组中,有一个字符不相同那么就返回false
return false;
i++;
}
return true;//如果两个字符串的所有字符都相等,则返回true
}
}
return false;//如果比较的不是字符串(也就是传入的数据类型,不是String类型或者不是String类型的子类型),则直接返回false
}
在执行a==b的时候,因为String是引用类型判断的是地址是否相同,因为a和b指向的都是常量池中的同一块空间,所以返回true
*/
//在执行String a = "abc";这句话时,会先在常量池中查找是否有abc这个数据空间,如果有直接指向,但是这里没有,所以会重新创建
//当指向到String b = "abc";这句话时,也会去常量池查找,因为在上面String a = "abc";这句话已经在常量池里创建好了空间
//因此String b = "abc";不会去创建新的空间而且直接指向,a所创建的空间
}
}
思路分析:
1.当执行到String a = "jack";的时候,因为是直接赋值的方式,所以会先去常量池中查找是否有jack这个数据空间,查询完发现不存在就会创建一个空间,并让a指向他 2.当执行到 String b = new String("jack");的时候,因为是创建对象的方式,所以会现在堆中开辟一个空间,里面有一个value属性,然后去常量池中查找,是否有Jack这个空间 查完,发现已经存在Jack这个数据空间,所以直接人value直接指向,并不是b直接指向,而且是b指向堆中的value属性,然后value属性在指向常量池中的数据空间 所以在执行a.equals(b);的时候,因为String重写了equals方法判断的是值是否相同,所以会返回true 在执行a==b 的时候,因为String是引用类型,因此==在判断的是地址是否相同,因为a指向的是常量池中的地址,而b指向的是堆中的value属性,所以会返回true 在执行a==b.intern();的时候,我们首先要知道b.intern()返回的就是常量池中的地址,而a也是指向的是常量池中的地址,所以两个会返回true 在执行b==b.intern();的时候,我们首先要知道b.intern()返回的就是常量池中的地址,而b指向的是堆中的value属性,两个指向的地方都不同所以返回false
细节:
当调用 intern 方法时,如果池已经包含一个等于此String对象的字符串(用equals(Object)方法确定)则返回池中的字符串。 否则,将此String对象添加到池中,并返回此String对象的引用
(1)b.intern()方法最终返回的是常量池的地址(对象).
package idea.chapter13.string_;
public class StringExercise03 {
public static void main(String[] args) {
/*
结论:
1.方式一:先从常量池查看是否有"jack"数据空间,如果有,直接指向;如果
没有则重新创建,然后指向。s最终指向的是常量池的空间地址
2.方式二:先在堆中创建空间,里面维护了value属性,指向常量池的jack空间。
如果常量池没有"jack",重新创建,如果有,直接通过value指向。最终指向
的是堆中的空间地址。
*/
String a = "jack"; //a 指向 常量池的 “jack”
String b = new String("jack");//b 指向堆中对象
System.out.println(a.equals(b));//T 因为string重写了equals方法所以判断的是两值是否相同所以返回true
System.out.println(a == b);//F a指向的是常量池中的地址 b指向的是堆中的地址 所以返回false
System.out.println(a == b.intern());//T b.intern()方法最终返回的是常量池的地址(对象).所以和a指向的也是常量池中的地址所以两个相同返回true
System.out.println(b == b.intern()); //F b.intern()返回的是常量池的地址 b指向的是堆中的地址 所以返回false
/*
思路分析:
1.当执行到String a = "jack";的时候,因为是直接赋值的方式,所以会先去常量池中查找是否有jack这个数据空间,查询完发现不存在就会创建一个空间,并让a指向他
2.当执行到 String b = new String("jack");的时候,因为是创建对象的方式,所以会现在堆中开辟一个空间,里面有一个value属性,然后去常量池中查找,是否有Jack这个空间
查完,发现已经存在Jack这个数据空间,所以直接人value直接指向,并不是b直接指向,而且是b指向堆中的value属性,然后value属性在指向常量池中的数据空间
所以在执行a.equals(b);的时候,因为String重写了equals方法判断的是值是否相同,所以会返回true
在执行a==b 的时候,因为String是引用类型,因此==在判断的是地址是否相同,因为a指向的是常量池中的地址,而b指向的是堆中的value属性,所以会返回true
在执行a==b.intern();的时候,我们首先要知道b.intern()返回的就是常量池中的地址,而a也是指向的是常量池中的地址,所以两个会返回true
在执行b==b.intern();的时候,我们首先要知道b.intern()返回的就是常量池中的地址,而b指向的是堆中的value属性,两个指向的地方都不同所以返回false
*/
/*
知识点:
当调用 intern 方法时,如果池已经包含一个等于此String对象的字符串(用equals(Object)方法确定)则返回池中的字符串。
否则,将此String对象添加到池中,并返回此String对象的引用
解读;(1)b.intern()方法最终返回的是常量池的地址(对象).
*/
}
}
思路分析:
1.String s1= "jack";因为是直接赋值,所以指向的是常量池中的地址 2.String s2= "java";因为是直接赋值,所以指向的是常量池中的地址 4.String s4= "java";因为是直接赋值,所以指向的是常量池中的地址 3.String s3 = new String("java");因为并不是直接赋值,而且是在堆中开辟一个空间,里面有一个value属性,value属性指向常量池中的空间 在执行s2 == s3 的时候,因为s2指向的是常量池中地址,而s3指向的是堆中的地址,所以返回false 在执行s2 == s4 的时候,因为s2指向的是常量池中地址,而s4指向的也是常量池中的地址,并且两个指向的是同一块地方,所以返回true 在执行s2.equals(s3)的时候,因为String重写了equals方法,所以判断的是值是否相等,所以返回true 在执行s1 == s2 的时候,因为s1指向的是常量池中的Jack空间,而s2指向的是常量池中Java空间,所以返回false
package idea.chapter13.string_;
public class StringExercise04 {
public static void main(String[] args) {
String s1 = "Jack"; //指向常量池”jack”
String s2 = "java"; //指向常量池”java”
String s4 = "java";//指向常量池”java”
String s3 = new String("java");//指向堆中对象
System.out.println(s2 == s3); // F S2指向的是常量池中的对象 S3指向的是堆中的对象因此返回一个false
System.out.println(s2 == s4); //T S2指向的常量池中的对象 S4指向的也是常量池中的对象 因此返回一个true
System.out.println(s2.equals(s3));//T 因为String重写了equals方法所以判断的是是否相等所以返回一个true
System.out.println(s1 == s2); //F S1指向的是常量池中的Jack S2指向的是常量池中的Java对象 所以返回一个false
/*
思路分析:
1.String s1= "jack";因为是直接赋值,所以指向的是常量池中的地址
2.String s2= "java";因为是直接赋值,所以指向的是常量池中的地址
4.String s4= "java";因为是直接赋值,所以指向的是常量池中的地址
3.String s3 = new String("java");因为并不是直接赋值,而且是在堆中开辟一个空间,里面有一个value属性,value属性指向常量池中的空间
在执行s2 == s3 的时候,因为s2指向的是常量池中地址,而s3指向的是堆中的地址,所以返回false
在执行s2 == s4 的时候,因为s2指向的是常量池中地址,而s4指向的也是常量池中的地址,并且两个指向的是同一块地方,所以返回true
在执行s2.equals(s3)的时候,因为String重写了equals方法,所以判断的是值是否相等,所以返回true
在执行s1 == s2 的时候,因为s1指向的是常量池中的Jack空间,而s2指向的是常量池中Java空间,所以返回false
*/
}
}
思路分析:
1.在执行System.out.println(p1.name.equals(p2.name));这句话的时候,因为String已经重写了equals方法,所以判断的是值是否相同,所以返回true 2.在执行System.out.println(p1.name == p2.name);这句话的时候,因为p1.name指向是常量池中的地址 p2.name指向的也是常量池中的地址 因为p1和p2的值是相同的所以指向的都是同一块地方所以返回一个true 3.在执行System.out.println(p1.name == "jack");这句话的时候//"jack"是一个常量,其实本质就是常量池中的一个地址所以 p1.name指向的是常量池中的地址 因此返回的是true 4.在执行System.out.println(s1 == s2);这句话的时候//s1和s2分别指向的是不同的堆空间因此返回的是false
package idea.chapter13.string_;
public class StringExercise05 {
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "jack";
Person p2 = new Person();
p2.name = "jack";
System.out.println(p1.name.equals(p2.name));//判断的是值是否相同所以返回一个true
System.out.println(p1.name == p2.name);//p1.name指向是常量池中的地址 p2.name指向的也是常量池中的地址 因为p1和p2的值是相同的所以指向的都是同一块地方所以返回一个true
System.out.println(p1.name == "jack");//"jack"是一个常量,其实本质就是常量池中的一个地址所以 p1.name指向的是常量池中的地址 因此返回的是true
String s1 = new String("bcde");
String s2 = new String("bcde");
System.out.println(s1 == s2);//s1和s2分别指向的是不同的堆空间因此返回的是false
/*
思路分析:
1.在执行System.out.println(p1.name.equals(p2.name));这句话的时候,因为String已经重写了equals方法,所以判断的是值是否相同,所以返回true
2.在执行System.out.println(p1.name == p2.name);这句话的时候,因为p1.name指向是常量池中的地址 p2.name指向的也是常量池中的地址 因为p1和p2的值是相同的所以指向的都是同一块地方所以返回一个true
3.在执行System.out.println(p1.name == "jack");这句话的时候//"jack"是一个常量,其实本质就是常量池中的一个地址所以 p1.name指向的是常量池中的地址 因此返回的是true
4.在执行System.out.println(s1 == s2);这句话的时候//s1和s2分别指向的是不同的堆空间因此返回的是false
*/
}
}
class Person {
public String name;
}
思路分析:
package idea.chapter13.string_;
public class StringExercise06 {
public static void main(String[] args) {
//1.String是一个final类,代表不可变的字符序列
//2.字符串是不可变的。一个字符串对象一旦被分配,其内容是不可变的
//以下语句创建了几个对象?2个对象,画出内存布局图
String s1 = "hello";//会指向常量池中的一个空间
s1 = "haha";
//因为字符串是final类的所以在指向到s1="haha";这句话时,并不是把原来的hello替换成haha而且先在常量池里寻找有没有haha如果有直接指向如果没有就在常量池中创建一个新的对象
//然后让s1指向haha开辟的空间
}
}
思路分析:
package idea.chapter13.string_;
public class StringExercise07 {
public static void main(String[] args) {
String a = "hello" + "abc";
//问创建了几个对象,之创建了一个对象
//创建了一个对象,在底层编译器不会在常量池分别创建两对象 在底层做了优化等价于String a="helloabc";
//1.编译器不傻,做一个优化,判断创建的常量池对象,是否有引用指向
//2.String a ="hello"+"abc";=》 String a = "helloabc";
}
}
思路分析:
1.在执行String s1 = "jack";这句话的时候,指向的是常量池中的Jack 2.在执行String s2 = "Java";这句话的时候,指向的是常量池中的java 3.在执行String s5 = "jackjava";这句话的时候,指向的是常量池中的jackjava 4.在执行String s6 = (s1 + s2).intern();这句话的时候,因为intern()方法,返回的是常量池中的地址 所以在执行s5==s6的时候,因为s5指向的时候常量池中的地址,而且s6指向的也是常量池中的地址,因为intern()方法,返回的就是常量池中的地址,所以返回的是true 在执行System.out.println(s5.equals(s6));这句话的时候,因为String已经重写equals()方法,比较的是内容是否相同,很显然他们的内容都是相同的 ,所以返回true
package idea.chapter13.string_;
public class StringExercise09 {
public static void main(String[] args) {
String s1 = "jack"; //s1 指向池中的 “jack”
String s2 = "java"; // s2 指向池中的 “java”
String s5 = "jackjava"; //s5 指向池中的 “jackjava”
String s6 = (s1 + s2).intern();//s6返回的时常量池的地址 因为s5已经创建了jackjava因此s6不会在创建新的对象,指向的和s5是常量池中的同一块地方
System.out.println(s5 == s6); //T
System.out.println(s5.equals(s6));//T
/*
思路分析:
1.在执行String s1 = "jack";这句话的时候,指向的是常量池中的Jack
2.在执行String s2 = "Java";这句话的时候,指向的是常量池中的java
3.在执行String s5 = "jackjava";这句话的时候,指向的是常量池中的jackjava
4.在执行String s6 = (s1 + s2).intern();这句话的时候,因为intern()方法,返回的是常量池中的地址
所以在执行s5==s6的时候,因为s5指向的时候常量池中的地址,而且s6指向的也是常量池中的地址,因为intern()方法,返回的就是常量池中的地址,所以返回的是true
在执行System.out.println(s5.equals(s6));这句话的时候,因为String已经重写equals()方法,比较的是内容是否相同,很显然他们的内容都是相同的 ,所以返回true
*/
}
}
思路分析:
//1. 先 创建一个 StringBuilder sb = StringBuilder() //也就是new了一个StringBuilder 看源码 /* public StringBuilder() { super(16); } */ //2. 执行 sb.append("hello"); 调用StringBuilder的append方法先把a这个字符串追加 看源码 /* @Override public StringBuilder append(String str) { super.append(str); return this; } */ //3. sb.append("abc"); 调用StringBuilder的append方法把b这个字符串追加 看源码 /* @Override public StringBuilder append(String str) { super.append(str); return this; } */ //4. String c= sb.toString() 最后调用toString 方法最后的是new也就是c指向了堆中的value空间 value空间指向常量池中的helloabc /* @Override public String toString() { // Create a copy, don't share the array return new String(value, 0, count); } */ //最后其实是 c 指向堆中的对象(String) value[] -> 池中 "helloabc" //小结:底层是StringBuilder sb =new StringBuilder(); sb.append(a);sb.append(b); sb是在堆中,并且append是在原来字符串的基础上追加的 // 重要规则,String c1=“ab"+"cd”;常量相加,看的是池。String c1=a+b;变量相加,是在堆中
小结:底层是StringBuilder sb =new StringBuilder(); sb.append(a);sb.append(b); sb是在堆中,并且append是在原来字符串的基础上追加的
// 重要规则,String c1=“ab"+"cd”;常量相加,看的是池。String c1=a+b;变量相加,是在堆中
package idea.chapter13.string_;
public class StringExercise08 {
public static void main(String[] args) {
String a = "hello"; //创建 a对象
String b = "abc";//创建 b对象
//解读
//1. 先 创建一个 StringBuilder sb = StringBuilder()
//也就是new了一个StringBuilder 看源码
/*
public StringBuilder() {
super(16);
}
*/
//2. 执行 sb.append("hello"); 调用StringBuilder的append方法先把a这个字符串追加 看源码
/*
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
*/
//3. sb.append("abc"); 调用StringBuilder的append方法把b这个字符串追加 看源码
/*
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
*/
//4. String c= sb.toString() 最后调用toString 方法最后的是new也就是c指向了堆中的value空间 value空间指向常量池中的helloabc
/*
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
*/
//最后其实是 c 指向堆中的对象(String) value[] -> 池中 "helloabc"
//小结:底层是StringBuilder sb =new StringBuilder(); sb.append(a);sb.append(b); sb是在堆中,并且append是在原来字符串的基础上追加的
// 重要规则,String c1=“ab"+"cd”;常量相加,看的是池。String c1=a+b;变量相加,是在堆中
String c = a + b;
String d = "helloabc";
System.out.println(c == d);//真还是假? 是false
String e = "hello" + "abc";//直接看池, e指向常量池
System.out.println(d == e);//真还是假? 是true
}
}
思路分析: 1.String str = new String("Jack");在执行这句话的时候,会在堆中开辟一个空间,里面有一个value属性,指向常量池中的jack final char[] ch = {'j', 'a', 'v', 'a'};在指向这句话的时候 会有一个ch指向数组, 到此在堆中就开辟了两个对象,一个是指向value属性,一个是指向数组 2.然后创建了一个Test1对象,此时在栈区中就一个对象,指向堆区 3.ex.change(ex.str, ex.ch);我们在调用方法的时候,我们知道每调用一个方法,会开辟一个新的对应的栈区,因为ex.str是一个对象,因此刚刚传入的时候 在change方法中的str和我们堆中指向的空间是一样的,指向的是堆中的value属性,但是在str = "java"执行到这句话的时候,就会断开原来的指向,而是指向的常量池中新的地址 然后又将ch[0]='h';又将数组中的第一个元素的值进行了修改 4.在方法结束后,对应生成的栈区也会销毁,此时在通过ex.str去访问堆中的属性的时候,访问到的就是jack ex.ch 访问到的就是 hava //所以最后的结果时 hsp and hava
package idea.chapter13.string_;
public class StringExercise10 {
public static void main(String[] args) {
}
}
/*
思路分析:
1.String str = new String("Jack");在执行这句话的时候,会在堆中开辟一个空间,里面有一个value属性,指向常量池中的jack
final char[] ch = {'j', 'a', 'v', 'a'};在指向这句话的时候 会有一个ch指向数组,
到此在堆中就开辟了两个对象,一个是指向value属性,一个是指向数组
2.然后创建了一个Test1对象,此时在栈区中就一个对象,指向堆区
3.ex.change(ex.str, ex.ch);我们在调用方法的时候,我们知道每调用一个方法,会开辟一个新的对应的栈区,因为ex.str是一个对象,因此刚刚传入的时候
在change方法中的str和我们堆中指向的空间是一样的,指向的是堆中的value属性,但是在str = "java"执行到这句话的时候,就会断开原来的指向,而是指向的常量池中新的地址
然后又将ch[0]='h';又将数组中的第一个元素的值进行了修改
4.在方法结束后,对应生成的栈区也会销毁,此时在通过ex.str去访问堆中的属性的时候,访问到的就是jack ex.ch 访问到的就是 hava
//所以最后的结果时 hsp and hava
*/
class Test1 {
String str = new String("Jack");
//在执行String str = new String("hsp");这句话时会先在堆中创建空间,里面维护了value属性,指向常量池的hsp空间
final char[] ch = {'j', 'a', 'v', 'a'};
//在执行final char[] ch = {'j', 'a', 'v', 'a'};这句话时会在堆中创建一个ch对象对象数组
public void change(String str, char ch[]) {
str = "java";//在执行到str = "java";这句话时会先去常量池查找有无Java如果没有则在常量池创建一个,这是chang中的str指向的就是Java,而不在指向原来堆中的value属性会断开原来的连接指向的是在常量池中新开辟的Java
ch[0] = 'h';//在执行到ch[0] = 'h';这句话时会把ch数组的第一个元素修改成h
}
public static void main(String[] args) {
Test1 ex = new Test1();//ex会指向堆中开辟的空间
ex.change(ex.str, ex.ch);//每调用一个方法会开辟一个新的栈 在传给change方法时ex.str指向的也是堆中value属性中保存的 ex.ch指向的也是堆中的数组
System.out.print(ex.str + " and ");//当方法调用结束后,之前开辟的栈区就会消失 因此在ex.str时指向的还是最初的value
System.out.println(ex.ch);//因为数组的元素被修改了所以,输出的内容时hava
//所以最后的结果时 jack and hava
}
}