小鲁班今年计算机专业大四了,在学校可学了不少软件开发的东西,也自学了一些 JAVA 的后台框架,踌躇满志,一心想着找个好单位实习。当投递了无数份简历后,终于收到了一个公司发来的面试通知,小鲁班欣喜若狂。
到了人家单位后,前台小姐姐给了小鲁班一份笔试题目,要求在一个小时内完成,小鲁班双手接过题目后,粗略的看了一下题目,心里暗喜,嘻嘻这个还不简单。一顿操作猛如虎,做完了感觉也没什么错误。就交卷了,等待片刻后,小姐姐亲切的说需要一周内等通知哦。于是呢,小鲁班就回去耐心的等待了。可是半个月都快过去了,什么消息都没有,小鲁班就纳闷了,明明我做的挺好的呀,为什么连面试的机会都不给我。
小鲁班于是找到了他表哥鲁班大师,正准备吐槽这件事,并把一些当时面试的题目重现了一些,并把自己对题目的理解也说了遍,鲁班大师一看他填的答案就开始嘲讽他,前 5 道题目关于 String 类的判断题可真是完全避开了正确答案呀,而且后边的题目也是大部分都是错了,人家当然不给你机会呀。
小鲁班你可要虚心学习了,就拿下边最简单的一题来说,你怎么连 == 对于非基本数据类型是比较引用而不是比较值的都不知道呀
String str1 = new String("AA");
String str2 = new String("AA");
System.out.println(str1 == str2);
这里的正确答案是false
鲁班大师:感觉你的 JAVA 基础不咋地呀,你说说你在学校学习你所掌握的关于 String 类的知识点,你表哥今天有空帮你恶补一波吧。
小鲁班垂头丧气的说到:
String 类有如下这些特点
-
String 类是 final 类,也即意味着 String 类不能被继承,并且它的成员方法都默认为 final 方法。
-
String 类其实是通过 char 数组来保存字符串的。
-
String 对象一旦被创建就是固定不变的了,对 String 对象的任何改变都不影响到原对象,相关的任何 change 操作都会生成新的对象。
鲁班大师:嗯,不错嘛,那有没有深入一点的理解呢,比如关于字符串常量池
小鲁班:这个我~~ 忘记了!
鲁班大师:没关系,那你得认真听讲了
小鲁班:emmm
字符串常量池
-
我们知道字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多。JVM 为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。每当我们创建字符串常量时,JVM 会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于 String 字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串。
-
字符串池的出现避免了相同内容的字符串的创建,节省了内存,省去了创建相同字符串的时间,同时提升了性能;另一方面,字符串池的缺点就是牺牲了 JVM 在常量池中遍历对象所需要的时间,不过其时间成本相比而言比较低。
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 的是新的地址,而不是在常量池的地址。
image鲁班大师又问了:我看你还挺懵的,你知道 == 和 equals 吗
小鲁班:这个我知道。
-
对于 ==,如果作用于基本数据类型的变量(byte,short,char,int,long,float,double,boolean ),****则直接比较其存储的 "值" 是否相等;如果作用于引用类型的变量(String),则比较的是所指向的对象的地址(即是否指向同一个对象)。
-
对于 equals 方法,注意:equals 方法不能作用于基本数据类型的变量。如果没有对 equals 方法进行重写,则比较的是引用类型的变量所指向的对象的地址;而 String 类对 equals 方法进行了重写,用来比较指向的字符串对象所存储的字符串是否相等。其他的一些类诸如 Double,Date,Integer 等,都对 equals 方法进行了重写用来比较指向的对象所存储的内容是否相等。
鲁班大师:嗯,答的不错,但是要应付一些面试题,你还要知道这些。
-
单独使用 "" 引号创建的字符串都是常量, 编译期就已经确定存储到 String Pool 中;
-
使用 new String("") 创建的对象会存储到 heap 中, 是运行期新创建的;
-
使用只包含常量的字符串连接符如 "aa" + "aa" 创建的也是常量, 编译期就能确定, 已经确定存储到 String Pool 中;
-
使用包含变量 (引用) 的字符串连接符如 "aa" + s1 创建的对象是运行期才创建的, 存储在 heap 中;
-
但是如果 s1 是被 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 的 intern() 方法在运行过程中将字符串添加到 String Pool 中。
当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),
那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。
String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2); // false
String s3 = s1.intern();
String s4 = s1.intern();
System.out.println(s3 == s4); // true
鲁班大师:优秀呀,小鲁班!不过呢我们既然都研究了 String,那么关于 StringBuffer 和 StringBuilder 也得知道,给你布置个作业,把他们 3 者的区别写一下自己的见解,发到我的邮箱,今天就到此为止了,表哥得去开黑了。
小鲁班:谢谢表哥,我一定好好整理的!小鲁班回到家后,立马奋笔疾书。。。
send to 鲁班大师 @qq.com
String、StringBuffer、StringBuilder 的区别?
-
可变与不可变:String 是不可变字符串对象,StringBuilder 和 StringBuffer 是可变字符串对象(其内部的字符数组长度可变)。
-
是否多线程安全:String 中的对象是不可变的,也就可以理解为常量,显然线程安全。StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,只是 StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是非线程安全的。
-
String、StringBuilder、StringBuffer 三者的执行效率如下:
-
StringBuilder > StringBuffer > String 当然这个是相对的,不一定在所有情况下都是这样。比如 String str = "hello"+ "world" 的效率就比 StringBuilder st = new StringBuilder().append("hello").append("world") 要高。因此,这三个类是各有利弊,应当根据不同的情况来进行选择使用:
-
当字符串相加操作或者改动较少的情况下,建议使用 String str="hello" 这种形式;
-
当字符串相加操作较多的情况下,建议使用 StringBuilder,如果采用了多线程,则使用 StringBuffer。
好累呀,因为虚心的小鲁班除了整理了这些之外,同时自己继续学习封装类的比较,因为也被其他笔试题坑惨了呀!
-
两个基本类型的只能用 ==
-
基本型和封装型用 ==,封装型将会自动拆箱变为基本型后再进行比较
-
用 == 来比较两个封装类的话,比较的是地址。(其中 - 127 到 127 之间的 Integer 地址相同,超出这个范围则不同)
-
至少有一个封装型的建议使用. equals。用 == 对基本型和封装性比较必须保证封装型不为 null。如果为 null 则不能转化为基本型就会报错。
-
两个封装型进行 equals() 比较,首先 equals() 会比较类型,如果类型相同,则继续比较值,如果值也相同,返回 true
-
封装类型调用 equals(), 但是参数是基本类型,这时候,先会进行自动装箱,基本型转换为其封装类型, 若类型不同返回 false, 若装箱后类型相同,则比较值,如果值相同,则返回 true
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<short=char<int<long<float<double,同级之间相互转换也需要强制转换。
//对于未声明数据类型的整形,其默认类型为int型。
//在浮点类型(float/double)中,对于未声明数据类型的浮点型,默认为double型。
System.out.println(b.equals(128.0));//false
- new Integer(123) 每次都会新建一个对象;
- Integer.valueOf(123) 会使用缓存池 ( -128~127) 中的对象,多次调用会取得同一个对象的引用,如果在缓存池没有,则 NEW
写完这些后小鲁班,小鲁班关了台灯,合上了电脑,休息一下准备明天的面试~
小鲁班面试回来了,遇到比较坑的一道题
X a=.....;
X b=a
System.out.print(a==b); 要结果是 false,X 是一个给定类型
答案:
float a =0f/0;
float b=a;
System.out.println(a==b);
原理:NaN 不等于任何浮点数值,包括它自身在内;即它与任何数比较均返回 false,但是可以用 Float.compare() 来比较 NaN
延伸:infinity 无穷大 float a=3.0f/0; float b=a+1;
无穷大加上一个数还是无穷大。 System.out.println(a==b);
Java知己 每日福利关注公众号:「Java知己」,发送「1024」,免费领取 30 本经典编程书籍。与 10 万程序员一起进步。每天更新Java知识哦,期待你的到来!
网友评论