本文出自:鲁班大师
小鲁班今年计算机专业大四了,在学校可学了不少软件开发的东西,也自学了一些JAVA的后台框架,踌躇满志,一心想着找个好单位实习。当投递了无数份简历后,终于收到了一个公司发来的面试通知,小鲁班欣喜若狂。
到了人家单位后,前台小姐姐给了小鲁班一份笔试题目,要求在一个小时内完成,小鲁班双手接过题目后,粗略的看了一下题目,心里暗喜,嘻嘻这个还不简单。一顿操作猛如虎,做完了感觉也没什么错误。就交卷了,等待片刻后,小姐姐亲切的说需要一周内等通知哦。于是呢,小鲁班就回去耐心的等待了。可是半个月都快过去了,什么消息都没有,小鲁班就纳闷了,明明我做的挺好的呀,为什么连面试的机会都不给我。
小鲁班于是找到了他表哥鲁班大师,正准备吐槽这件事,并把一些当时面试的题目重现了一些,并把自己对题目的理解也说了遍,鲁班大师一看他填的答案就开始嘲讽他,前5道题目关于String类的判断题可真是完全避开了正确答案呀,而且后边的题目也是大部分都是错了,人家当然不给你机会呀。
小鲁班你可要虚心学习了,就拿下边最简单的一题来说,你怎么连==对于非基本数据类型是比较引用而不是比较值的都不知道呀
String str1 = new String("AA");
String str2 = new String("AA");
System.out.println(str1 == str2);
这里的正确答案是false
鲁班大师:感觉你的JAVA基础不咋地呀,你说说你在学校学习你所掌握的关于String类的知识点,你表哥今天有空帮你恶补一波吧。
小鲁班垂头丧气的说到:
鲁班大师:嗯,不错嘛,那有没有深入一点的理解呢,比如关于字符串常量池
小鲁班:这个我~~忘记了!
鲁班大师:没关系,那你得认真听讲了
小鲁班:emmm
String a = "AA";
String b = "AA";
String c = new String("AA");
a
、b
和堆中创建的AA
都是指向JVM字符串常量池中的"AA"对象,他们指向同一个对象。
new关键字一定会产生一个对象AA,同时这个对象是存储在堆中。所以String c=new String("AA")
这一句应该产生了两个对象
:保存在方法区中字符串常量池的AA
和保存堆中AA
。但是在Java中根本就不存在两个完全一模一样的字符串对象。故堆中的AA应该是引用字符串常量池中AA
。所以c、堆AA、池AA的关系应该是:c—>堆AA—>池AA。
虽然a、b、c是不同的引用,但是从String的内部结构我们是可以理解上面的。String c = new String("AA");
虽然c的内容是创建在堆中,但是他的内部value还是指向JVM常量池的AA的value,它构造AA时所用的参数依然是AA字符串常量。所以a==b是ture
,因为内存地址是一样的 a==c是false
,因为c的内存地址指向是在堆中new的是新的地址,而不是在常量池的地址。
鲁班大师又问了:我看你还挺懵的,你知道==和equals吗
小鲁班:这个我知道。
基本数据类型
的变量(byte,short,char,int,long,float,double,boolean ),则直接比较其存储的"值"是否相等;如果作用于引用类型的变量(String),则比较的是所指向的对象的地址(即是否指向同一个对象)。引用类型
的变量所指向的对象的地址
;而String类对equals方法进行了重写,用来比较指向的字符串对象所存储的字符串是否相等。其他的一些类诸如Double,Date,Integer等,都对equals方法进行了重写用来比较指向的对象所存储的内容是否相等。鲁班大师:嗯,答的不错,但是要应付一些面试题,你还要知道这些。
" "
引号创建的字符串都是常量,编译期
就已经确定存储到String Pool中;new String(" ")
创建的对象会存储到heap中,是运行期
新创建的;常量
,编译期就能确定,已经确定存储到String Pool中;包含变量(引用)
的字符串连接符如"aa" + s1创建的对象是运行期
才创建的,存储在heap中;final修饰
的话,则s1是属于常量。结果存在String Pool,但是 final修饰的是一个方法返回的值也是在编译器确定。好了,这些你都知道了,那你把刚那份题目在做一次看看
String str1 = "aaa";
String str2 = "aaa";
System.out.println(str1 == str2);// true 因为String有常量池
String str3 = new String("aaa");
String str4 = new String("aaa");
System.out.println(str3 == str4);// false 可以看出用new的方式是生成不同的对象,比较堆上的
String s0="helloworld";
String s1="helloworld";
String s2="hello"+"world";
System.out.println(s0==s1); //true 可以看出s0跟s1是指向同一个对象
System.out.println(s0==s2); //true 可以看出s0跟s2是指向同一个对象
String st0="helloworld";
String st1=new String("helloworld");
String st2="hello" + new String("world");
System.out.println( st0==st1 ); //false 用new String() 创建的字符串不是常量,不能在编译期就确定
System.out.println( st0==st2 ); //false st2地址存在堆中,不可能相同
System.out.println( st1==st2 ); //false
String stri1="abc";
String stri2="def";
String stri3=stri1+stri2;
System.out.println(stri3=="abcdef"); //false 变量相+是在的堆内存中创建
String strin0 = "a1";
String strin1 = "a" + 1; //这种不是变量,是常量
System.out.println((strin0 == strin1)); //result = true
String strin2 = "atrue";
String strin3= "a" + "true";
System.out.println((strin2 == strin3)); //result = true
String strin4 = "a3.4";
String strin5 = "a" + 3.4;
System.out.println((strin4 == strin5)); //result = true
String string0 = "ab";
String string1 = "b";
String string2 = "a" + string1;
System.out.println((string0 == string2)); //result = false 在字符串的"+"连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的
String test="javalanguagespecification";
String test2="java";
String test3="language";
String test4="specification";
System.out.println(test == "java" + "language" + "specification"); //true 字符串字面量拼接操作是在Java编译器编译期间就执行了
System.out.println(test == test2 + test3 + test4); //false 字符串引用的"+"运算是在Java运行期间执行的
String ss0 = "ab";
final String ss1 = "b";
String ss2 = "a" + ss1;
System.out.println((ss0 == ss2)); //result = true 对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。所以此时的"a" + s1和"a" + "b"效果是一样的
String ss10 = "ab";
final String ss11 = getS1();
String ss12 = "a" + ss11;
System.out.println((ss10 == ss12)); //result = false 这里面虽然将s1用final修饰了,但是由于其赋值是通过方法调用返回的,那么它的值只能在运行期间确定
public static String getS1(){
return "b";
}
鲁班大师:优秀呀,小鲁班!不过呢我们既然都研究了String,那么关于StringBuffer和StringBuilder也得知道,给你布置个作业,把他们3者的区别写一下自己的见解,发到我的邮箱,今天就到此为止了,表哥得去开黑了。
小鲁班:谢谢表哥,我一定好好整理的!小鲁班回到家后,立马奋笔疾书。。。
send to 鲁班大师@qq.com
可变与不可变
:String是不可变字符串对象,StringBuilder和StringBuffer是可变字符串对象(其内部的字符数组长度可变)。是否多线程安全
:String中的对象是不可变的,也就可以理解为常量,显然线程安全。StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,只是StringBuffer
中的方法大都采用了synchronized
关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是非线程安全的。好累呀,因为虚心的小鲁班除了整理了这些之外,同时自己继续学习封装类的比较,因为也被其他笔试题坑惨了呀!
==
,封装型将会自动拆箱
变为基本型后再进行比较比较的是地址
。(其中-127到127之间的Integer地址相同,超出这个范围则不同
)建议使用.equals
。用==对基本型和封装性比较必须保证封装型不为null
。如果为null则不能转化为基本型就会报错。 int a=128;
int a2=127;
Integer b=128;
Integer b2=127;
Integer c=128;
Integer c2=127;
Integer d=new Integer(a);
Integer d2=new Integer(a);
Integer b2=57;
Integer c2=57;
System.out.println(a==b);//true
System.out.println(b==c);//false
System.out.println(b2==c2);//3true
System.out.println(a==d);//true
System.out.println(b==d);//false
System.out.println(d==d2);//false
//由强类型向弱类型转换需要强制转换,而由弱类型向强类型转换则系统自动转换。
//double 类型相比int类型是属于强类型,则由double类型的数据向int类型数据转换就需要强制转换,反之则自动转换。数据类型的强弱关系如下:
//byte
//对于未声明数据类型的整形,其默认类型为int型。
//在浮点类型(float/double)中,对于未声明数据类型的浮点型,默认为double型。
System.out.println(b.equals(128.0));//false
写完这些后小鲁班,小鲁班关了台灯,合上了电脑,休息一下准备明天的面试~