Q:char和String的区别。
A:
1、本质区别:
- char 是基本数据类型,与byte,int,double,long,boolean,float,short相似。
- String是一个类。
2、 深入了解具体区别
- 把String当作是字符串和字符串类型都是不准确的。String其实相当与一个装char类型数据的容器类类型,实例化之后,它就是一个容器,用于盛放char类型的数据。
String p = new String("123456"); //该行代码的意思是实例了一个字符串容器p,里面装了“123456”
- String类的一些补充知识: 1)String类是通过char数组来保存字符串的。 2)String类是final类,即String类不能被继承,其成员方法也都为final方法。 3)String对象一旦被创建了就是固定不变的,无法被修改。任何change操作都会生成一个新的String对象。
- 字符串常量池(Java中会使用常量池来存放数据以达到提高性能和减少内存开销的目的)字符串常量池顾名思义就是存放字符串的常量池。当我们创建字符串常量的时候,JVM会先检查字符串常量池里有没有相同的数据,有就返回常量池中该数据的实例引用;如果该常量不在字符串常量池里,JVM就会实例化该字符串并将其存放到常量池中。由于String的不可变性,字符串常量池里绝对不会出现两个完全相同的字符串。
- 字面值方式赋值和使用new运算符赋值的区别:
//字面值方式赋值 public void test1{ String a = "123"; String b = "123"; System.out.println(a==b); } //运行结果为:true
使用字面值方式赋值时,JVM会先去字符串常量池里查找是否有“123”这个对象,如果不存在就创建这个对象,如果存在就直接调用字符串常量池里的实例引用。应用到上面的例子就是:给a赋值的时候,字符串常量池里没有“123”这个对象,那么JVM就会在字符串常量池里创建“123”这个对象,然后将“123”这个对象的引用地址返回给字符串a,这样:变量a就会指向字符串常量池中的“123”。当给b赋值的时候,JVM发现字符串常量池中有“123”这个对象,因此把“123”的引用地址返回给变量b,这样:变量b就会指向字符串常量池中的“123”。由于变量a和变量b都指向同一个对象“123”,所以输出true。
//使用new运算符赋值 public void test2{ String c = new String('456'); String d = new String('456'); System.out.println(c==d); } //运行结果为false
使用new运算符赋值时,JVM同样会先检查字符串常量池里是否有“456”这个对象,如果不存在就创建这个对象,如果存在就直接调用字符串常量池里的实例引用。但是由于new运算符是new出来一个新的对象,对象的不同会导致变量引用地址不同,即c==d的结果为false。应用到上面的例子就是:用new运算符给c赋值时,由于字符串常量池里没有“456”这个对象,那么JVM会先在字符串常量池里创建一个“456”对象,然后再在堆里创建一个“456”对象,将堆中的这个“456”对象返回给变量c引用,这样:变量c就指向了堆中创建的这个“456”对象。当用new运算符给d赋值时,由于字符串常量池中已经有“456”这个对象了,故只需要在堆中再创建一个“456”对象,并将这个对象返回给d引用,这样:变量d就指向了堆里的另一个“456”对象。由于变量c和变量d指向不同的对象,所以输出false。
注意:使用String时不一定会创建一个新的对象,字符串常量池里不允许有两个相同的对象,当使用字面值赋值的方法时,如果字符串常量池中已经有该对象里但在堆里面,每new一次都会产生一个新的对象,与这个对象的内容是否相同无关,因此堆中可能会出现很多个内容相同的对象,但实际上它们都是不同的对象。
-
String的intern()方法:扩充常量池的一个方法。当一个String实例str调用intern()方法时,java查找常量池中是否有相同unicode的字符串常量,如果有,则返回其引用,如果没有,则在常量池中增加一个unicode等于str的字符串并返回它的引用。 注意:只有 a.equals(b) 为 true 时,a.intern() == b.intern() 才为 true。
public void test3(){ String s0 = "kvill"; String s1 = new String("jackson"); String s2 = new String("jackson"); System.out.println( s0 == s1 ); //false s1.intern(); //虽然执行了s1.intern(),但它的返回值没有赋给s1 s2 = s2.intern(); //把常量池中"jackson"的引用赋给s2 System.out.println( s0 == s1); //flase System.out.println( s0 == s1.intern() ); //true//说明s1.intern()返回的是常量池中"jackson"的引用 System.out.println( s0 == s2 ); //true }
-
equals()方法与“==”: (1)“==”用在数值类型的变量时,是比较两个变量的储存的值是否相同;用在引用类型的变量时,是比较两个变量的引用地址是否相同。 (2)equals()方法是用于比较两个变量的引用地址是否相同。(equals()方法不能用在数值类型变量的比较上) 注意:Object类中equals()方法只能用于比较引用地址,但实际上在Object类的许多子类中都会对equals()方法进行重写。String类中就对equals()方法进行重写,使其只用于比较字符串对象所存储的字符串是否相等,并不是比较其引用地址。同理,Double,Date,Integer等也对equals()方法进行重写,使其用于比较两个变量的内容是否相同。
//“==”与equals()方法: public void test4(){ String s1="hello"; String s2="hello"; String s3=new String("hello"); System.out.println( s1 == s2); //true,表示s1和s2指向同一对象,它们都指向常量池中的"hello"对象 System.out.println( s1 == s3); //flase,表示s1和s3的地址不同,即它们分别指向的是不同的对象,s1指向常量池中的地址,s3指向堆中的地址 System.out.println( s1.equals(s3)); //true,表示s1和s3所指向对象的内容相同 }
-
String类中“+”运算符的具体实现: (1)Java编译时,会尽可能地把左起的字符串常量先连起来(直到遇到变量就停止),形成新的字符串常量参与后续连接。 (2)接下来的字符串连接是从左向右依次进行,对于不同的字符串,首先以最左边的字符串为参数创建StringBuilder对象,然后依次对右边进行append操作,最后将StringBuilder对象通过toString()方法转换成String对象。
//String类中“+”运算符的具体实现: public void test5(){ String a = "aa"; String b = "bb"; String c = "xx" + "yy " + a + "zz" + "mm" + b; System.out.println(c); }
实现方法: (1)先将“xx”和“yy”连接起来成为一个新的字符串常量,遇到变量a之后自动停止连接。 (2)接着,以“xxyy”为参数创建StringBuilder对象,然后依次对右边进行append操作,直到所有字符串都被连起来,最后使用toString()方法将StringBuilder对象转换为String对象。 注意:每做一次 + 就产生个StringBuilder对象,然后append后就扔掉。这样会导致连接字符串的过程中会有很多创建和销毁对象的操作,造成运行缓慢。如果我们直接采用 StringBuilder 对象进行 append 的话,我们可以节省 N - 1 次创建和销毁对象的时间。所以对于在循环中要进行字符串连接的应用,一般都是用StringBuffer或StringBulider对象来进行append操作。
-
String类中的final修饰词的理解: final只对引用的"值"(即内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象的变化,final是不负责的。
//String类中的final修饰词: final StringBuffer a = new StringBuffer("111"); final StringBuffer b = new StringBuffer("222"); a=b;//此句编译不通过 不能改变final变量的引用路径 final StringBuffer a = new StringBuffer("111"); a.append("222");//编译通过 可以改变final变量的具体内容
-
补充:关于String str = new String("abc")创建了多少个对象? 这段代码在执行的过程中,new只调用了一次,也就是说只创建了一个对象。而这道题目让人混淆的地方就是这里,这段代码在运行期间确实只创建了一个对象,即在堆上创建了"abc"对象。而为什么大家都在说是2个对象呢,这里面要澄清一个概念,该段代码执行过程和类的加载过程是有区别的。在类加载的过程中,确实在运行时常量池中创建了一个"abc"对象,而在代码执行过程中确实只创建了一个String对象。
这个问题如果换成 String str = new String("abc")涉及到几个String对象?合理的解释是2个。而如果换成这段代码执行过程中创建了多少个对象?合理解释是1个。