Java面试题每日五题(2010/02/26)
问题1.
public static void append(String str){
str += " Append!";
}
public static void append(StringBuffer sBuffer){
sBuffer.append(" Append!");
}
public void test(){
String str = "Nothing";
append(str);
System.out.println(str);
StringBuffer sBuffer = new StringBuffer("Nothing");
append(sBuffer)
System.out.println(sBuffer);
}
执行test后显示什么?
答案:
Nothing
Nothing Append
这个问题首先要看String的一个重要性质:不可修改性(immutable),操作符+则是隐式创建一个新的String对象(如果此对象不是编译阶段就能够确定的常量,在第二题的解答中将详细叙述).
如题目中str += " Append!"语句,str引用最初指向的String对象和追加"Append!"后的对象是不同的对象。
然后要看Java的函数调用的过程,虚拟机为每一个调用的Java方法建立一个新的栈帧,栈帧包括为方法的局部变量所预留的空间,该方法的操作数栈等等,将新的栈压入Java栈中,当处理实例方法的时候,虚拟机从所调用的方法栈内的操作数栈中弹出objectref和args,把objectref作为局部变量0放入新的栈中,将所有的args依次作为局部变量1,2,…,当处理类方法的时候,虚拟机从所调用的方法栈内的操作数栈中弹出args,放入新的栈帧中作为局部变量0,1,2,…,最后虚拟机把新的栈帧作为当前栈帧,将程序计数器指向新方法的第一条指令。
如题目,当test()函数中的str传进append函数后,是放入函数的栈帧中作为局部变量了,因此append函数中的str和test()函数中的str是两个变量,虽然他们最初指向同一个String对象。然而在append函数中,作为局部变量的str并没有也不可能改变它指向的String对象的值,而是创建了另外一个String对象,并且局部变量str指向的新创建的对象,然而这并没有改变test()中的str变量的指向,它仍然指向原来的String对象。
对于很多字符串连接时,应当使用StringBuffer类,使用这个类的对象来进行字符串连接时就不会有多余的中间对象生成,从而优化了效率。
append函数中修改的是指向的StringBuffer对象的本身,由于test()中的sBuffer也是指向这个对象,因而对对象的修改,也能够看得到。
问题2.
"abcdefg".toLowerCase() == "abcdefg"是true还是false
答案:
true
这里首先要注意==和equal()的区别:
对于==来讲,对于基本类型和对于对象来说是不同的,对于基本类型,如果两者的值是相同的,则相同。但是对于浮点数来讲,有以下需要注意的:
- 如果两者之间有一个是NaN,则为false。NaN不等于任何类型,即便是它自己,所以NaN != NaN为true。
- 对于零来说,正零和负零时相等的,-0.0 == 0.0
- 正无穷和负无穷仅仅等于它们自己。
对于对象来讲,==相等当且仅当两者都是null(当然是相同的类型)或者指向同一个对象。
而Object类提供了方法equals(),此方法本来也是比较引用是否指向同一个对象的,
public boolean equals(Object obj) {
return (this == obj);
}
然而对于有的类来说,可能有自己逻辑上的相等的概念,而不仅仅是引用的对象是否相同,比如说Integer,当两个数值确实一样,但是是两个对象的时候,我们也希望有一种办法来判定两者在逻辑上是相等的,所以会改写Object的equals函数,
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
有时候我们创建自己的类,也需要进行逻辑比较,比如一个员工类,比较的时候逻辑上比较其员工号即可,因而我们也要改写equals函数,但要注意一下几点:
- 首先使用==检查是否指向同一个对象,是则返回true
- 然后使用instanceof检查参数是否是你要比较的类型,如果不是则返回false
- 然后将参数转换为正确的类型,来比较关键的字段
- 然后检查equals方法的对称性(x.equals(y) 则 y.equals(x)),传递性(x.equals(y), y.equals(z)则x.equals(z)),一致性(x.equals(y)在值没有改变的情况下始终相等)
- 最后别忘了改写hashCode函数,使得你的类在基于散列值的集合类(HashMap, HashSet, Hashtable)中能够正常运作。
- 不要把equals声明中的Object对象替换为你的类型(equals(Employee e)),因为这样你是重载了equals函数而非重写了equals函数。
下面是一个例子:
public class Employee {
String id;
String name;
double salary;
public boolean equals(Object o){
if(this == o)
return true;
if(o instanceof Employee){
Employee other = (Employee)o;
return id.equals(other.getId());
}
return false;
}
public int hashCode() {
return id.hashCode();
}
public String getId(){
return id;
}
}
如下实验可以证实:
Integer int1= new Integer(1);
Integer int2= new Integer(1);
Integer int3=int1; //int3和int1引用同一个对象
System.out.println("int1==int2 is " + (int1==int2));
System.out.println("int1==int3 is " + (int1==int3));
System.out.println("int1 equal int2 is " + (int1.equals(int2)));
System.out.println("int1 equal int3 is " + (int1.equals(int3)));
打印结果如下:
int1==int2 is false
int1==int3 is true
int1 equal int2 is true
int1 equal int3 is true
那如题目中,两个"abcdefg"是同一个对象吗?这就要提到String的一个性质:String常量池的概念。
以""创建的字符串在编译阶段就放在.class的Constant Pool的CONSTANT_String_info结构中。虚拟机中有以下几个区域:方法区,堆,Java栈,程序计数器,本地方法栈。ClassLoader会把Constant Pool加载到方法区中,当""创建的字符串的时候,会到String常量串池中去查找,并返回其地址赋给对象变量。
以new创建的字符串则是运行时在堆中构造的。
所以题目中的两个"abcdefg"是指向同一个地址的,以下可以证明:
String str1 = "abcdefg";
String str2 = "abcdefg";
String str3 = new String("abcdefg");
System.out.println("str1 == str2 is " + (str1 == str2));
System.out.println("str1 equal str2 is " + (str1.equals(str2)));
System.out.println("str1 == str3 is " + (str1 == str3));
System.out.println("str1 equal str3 is " + (str1.equals(str3)));
结果为:
str1 == str2 is true
str1 equal str2 is true
str1 == str3 is false
str1 equal str3 is true
然而本题目中"abcdefg".toLowerCase()按照String的不可改变性,不是应该生成另外一个对象吗?这就要看toLowerCase的实现了:
如果没有任何字符需要改变,return this;
如果做了改变则,return new String(0, count+resultOffset, result);
由此可知"ABCDEF".toLowerCase() == "abcdef"就是false了。
这里还需要提一下的是String的intern函数:它是在常量池中查找String,如果有就返回,没有就添加到常量池。
String str1 = "123";
String str2 = new String("456");
String str3 = (str1 + str2).intern();
String str4 = (str1 + str2).intern();
String str5 = str1 + str2;
String str6 = str1 + str2;
System.out.println(str3 == str4);
System.out.println(str5 == str6);
问题3.
true && false || true 是 true 还是 false
答案:
true
这里主要考虑的是Short-circuiting 短路,如以下实例:
static boolean test1(int val) {
System.out.println("test1(" + val + ")");
System.out.println("result: " + (val < 1));
return val < 1;
}
static boolean test2(int val) {
System.out.println("test2(" + val + ")");
System.out.println("result: " + (val < 2));
return val < 2;
}
static boolean test3(int val) {
System.out.println("test3(" + val + ")");
System.out.println("result: " + (val < 3));
return val < 3;
}
boolean b = test1(0) && test2(2) && test3(2);
System.out.println("expression is " + b);
结果为:
test1(0)
result: true
test2(2)
result: false
expression is false
在此程序中,test3(2)没有执行,被短路了。
问题4.
int i = 1 / 0;
int j = 1 % 0;
double k = 1.0 / 0;
i,j,k三条语句执行后分别显示什么
答案:
语句一
Exception in thread "main" java.lang.ArithmeticException: / by zero
语句二
Exception in thread "main" java.lang.ArithmeticException: / by zero
语句三
k is Infinity
当查看JDK的文档的时候,我们可以清楚的看到,double和float是允许除以0的,而int是不允许除以0的。
double l = 1.0 % 0;
输出:l is NaN
java.lang.Double | ||
1.7976931348623157E308 | ||
4.9E-324 | ||
0d/0d | ||
-1d/0d | ||
1d/0d | ||
64 |
java.lang.Float | ||
3.4028234663852886E38f | ||
1.401298464324817E-45f | ||
0f/0f | ||
-1f/0f | ||
1f/0f | ||
32 |
java.lang.Integer | ||
2147483647 | ||
-2147483648 | ||
32 |
问题5.
List list1 = null;
List list2 = new ArrayList();
System.out.println(list1 instanceof ArrayList);
System.out.println(list1 instanceof List);
System.out.println(list1 instanceof Object);
System.out.println(list2 instanceof ArrayList);
System.out.println(list2 instanceof List);
System.out.println(list2 instanceof Object);
分别是什么
回答:
false
false
false
true
true
true
a instanceof B是在运行时检查类型的,当且仅当a不是null,并且a可以Cast成B类型的对象的时候为true。
如果有以下程序:
class Point { int x, y; }
class Element { int atomicNumber; }
Point p = new Point();
Element e = new Element();
System.out.println(e instanceof Point);
是输出false吗?不是,是compile error,也即在编译阶段就可以知道e无论如何都不可能称为Point类型的对象(二者没有继承关系),则编译不过。
注意instanceof 和Class equivalence的不同之处
- instanceOf可以看到继承关系,Class equivalence不能
- 也即 儿子对象 instanceOf 父亲 为true,儿子.class == 父亲.class为false