Java面试中总喜欢出几个关于String对象的题,比如:
1.String s = new String("abc");创建了几个String对象。
2.String s1 = "abc";
String s2 = "a";
String s3 = s2 + "bc";
String s4 = "a" + "bc";
String s5 = s3.intern();
请问s1==s3是true还是false,s1==s4是false还是true。s1==s5呢?
首相我们知道,字符串是常量;它们的值在创建之后不能更改。因为 String 对象是不可变的,所以可以共享(String类自己维护了一个池,把程序中的String对象放到那个池里面,于是我们代码中只要用到值相同的String,就是同一个String对象,节省了空间);但是,并不是任何时候任何String对象都在这个字符串池中。
首先解释下什么是字符串字面常数(String Literals),字面常数(Literals)就是你写在源代码里面的值,比如说int i = 6; 6就是一个整数形字面常数。String s = "abc"; “abc”就是一个字符串字面常数。Java中,所有的字符串字面常数都放在上文提到的字符串池里面,是可以共享的,就是说,String s1 = "abc"; String s2 = "abc"; s1,s2都引用的同一个字符串对象,而且这个对象在字符串池里面,因此s1==s2。另外,字符串字面常数是什么时候实例化并放到字符串池里面去的呢?答案是Load Class的时候
在程序运行时通过连接(+)计算出来的字符串对象,是新创建的,他们不是字面常数,就算他们值相同,他们也不在字符串池里面,他们在堆内存空间里,因此引用的对象各不相同。
因此:第一题答案是创建了两个对象,一个是字符串字面常数,在字符串池中。一个是new出来的字符串对象,在堆中。第二题:因为s2是一个变量,所以s3是运行时才能计算出来的字符串,是new的,在堆中不在字符串池中。s4是通过常量表达式计算出来的,他等同于字符串字面常数,在字符串池中。所以,s1!=s3,s1==s4。再看s5,s5是s3放到字符串池里面返回的对像,所以s1==s5。这里新手要注意的是,s3.intern()方法,是返回字符串在池中的引用,并不会改变s3这个变量的引用,就是s3还是指向堆中的那个"abc",并没有因调用了intern()方法而改变,实际上也不可能改变。
那么什么是常量表达式呢?
记住一个原则就好了,那就是编译时能确定的,就算,运行时才能确定的,就不算。以下为例子
String s = 1 + "23";//算,符合第二条Casts to primitive types and casts to type String
String s = (2 > 1) + "" ;//算,意味着s=="true",且这个“true”已经放到字符串池里面去了。
String s = (o instanceof Object) + "";//不算,instanceof这个操作符决定了不算。s=="true",但这个"true"对象在堆中。
留意下面的情况。
final String s2 = "a";
String s3 = s2 + "bc";//算
注意现在的s2+"bc"也算一个常量表达式,理由是那个列表里面的最后两条,s2是一个常量变量(constant variables),问题又来了,什么是常量变量?规范里也说了,被final修饰,并且通过常量表达式初始化的变量,就是常量变量。变量s2被final修饰,他通过常量表达式"a"初始化,所以s2是一个常量变量,所以s3引用的"abc",也在字符串池里面。
再举个反例:
final String s2 = getA();//s2不是常量变量,但是s2引用的"a"其实还是在常量池中,这两点不矛盾
public String getA(){return "a"}
String s3 = s2 + "bc";//此时s3不算常量表达式,因为s2不是常量变量
这是时候的s2,就不是常量变量了哦,因为getA()不是一个常量表达式。
3.常用的两个方法:
lastindexof("/"); / 找到所要找的字符的位置的下标
substring(2); / 获取下标是2的以后(从左向右)的字符值