> 文章列表 > String类的详解(1)

String类的详解(1)

String类的详解(1)

目录

String类的理解和创建对象

两种创建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";}
}

两种创建String对象的区别

方式一:直接赋值Strings="jack";

方式二:调用构造器 String s2=new String("jack");

·1.方式一:先从常量池查看是否有"jack"数据空间,如果有,直接指向;如果 没有则重新创建,然后指向。s最终指向的是常量池的空间地址

2.方式二:先在堆中创建空间,里面维护了value属性,指向常量池的jack空间。 如果常量池没有"jack",重新创建,如果有,直接通过value指向。最终指向 的是堆中的空间地址。

String习题

第一题

 思路分析: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])//如果发现两个数组中,有一个字符不相同那么就返回falsereturn 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])//如果发现两个数组中,有一个字符不相同那么就返回falsereturn 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方法所以判断的是两值是否相同所以返回trueSystem.out.println(a == b);//F   a指向的是常量池中的地址   b指向的是堆中的地址  所以返回falseSystem.out.println(a == b.intern());//T  b.intern()方法最终返回的是常量池的地址(对象).所以和a指向的也是常量池中的地址所以两个相同返回trueSystem.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指向的是堆中的对象因此返回一个falseSystem.out.println(s2 == s4);  //T   S2指向的常量池中的对象    S4指向的也是常量池中的对象  因此返回一个trueSystem.out.println(s2.equals(s3));//T   因为String重写了equals方法所以判断的是是否相等所以返回一个trueSystem.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));//判断的是值是否相同所以返回一个trueSystem.out.println(p1.name == p2.name);//p1.name指向是常量池中的地址   p2.name指向的也是常量池中的地址  因为p1和p2的值是相同的所以指向的都是同一块地方所以返回一个trueSystem.out.println(p1.name == "jack");//"jack"是一个常量,其实本质就是常量池中的一个地址所以 p1.name指向的是常量池中的地址 因此返回的是trueString 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方法,所以判断的是值是否相同,所以返回true2.在执行System.out.println(p1.name == p2.name);这句话的时候,因为p1.name指向是常量池中的地址   p2.name指向的也是常量池中的地址  因为p1和p2的值是相同的所以指向的都是同一块地方所以返回一个true3.在执行System.out.println(p1.name == "jack");这句话的时候//"jack"是一个常量,其实本质就是常量池中的一个地址所以 p1.name指向的是常量池中的地址 因此返回的是true4.在执行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); //TSystem.out.println(s5.equals(s6));//T/*思路分析:1.在执行String s1 = "jack";这句话的时候,指向的是常量池中的Jack2.在执行String s2 = "Java";这句话的时候,指向的是常量池中的java3.在执行String s5 = "jackjava";这句话的时候,指向的是常量池中的jackjava4.在执行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这个字符串追加   看源码
/*
@Overridepublic StringBuilder append(String str) {super.append(str);return this;}*///3. sb.append("abc");          调用StringBuilder的append方法把b这个字符串追加   看源码
/*@Overridepublic StringBuilder append(String str) {super.append(str);return this;}*///4. String c= sb.toString()   最后调用toString 方法最后的是new也就是c指向了堆中的value空间  value空间指向常量池中的helloabc
/*@Overridepublic String toString() {// Create a copy, don't share the arrayreturn 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这个字符串追加   看源码/*@Overridepublic StringBuilder append(String str) {super.append(str);return this;}*///3. sb.append("abc");          调用StringBuilder的append方法把b这个字符串追加   看源码/*@Overridepublic StringBuilder append(String str) {super.append(str);return this;}*///4. String c= sb.toString()   最后调用toString 方法最后的是new也就是c指向了堆中的value空间  value空间指向常量池中的helloabc/*@Overridepublic String toString() {// Create a copy, don't share the arrayreturn 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);//真还是假? 是falseString e = "hello" + "abc";//直接看池, e指向常量池System.out.println(d == e);//真还是假? 是true}
}

第九题

思路分析:
1.String str = new String("Jack");在执行这句话的时候,会在堆中开辟一个空间,里面有一个value属性,指向常量池中的jackfinal 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属性,指向常量池中的jackfinal 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属性会断开原来的连接指向的是在常量池中新开辟的Javach[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时指向的还是最初的valueSystem.out.println(ex.ch);//因为数组的元素被修改了所以,输出的内容时hava//所以最后的结果时   jack and hava}
}