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);

其中str1是在常量池中,str2在堆中,str1+str2由于在编译阶段不能确定其值,因而是在运行阶段创建在堆上(如果是"123" + "456",则"123456"也在常量池中),然而intern函数将"123456"放入常量池中,并返回给str3,同样str4指向常量池中已经有的"123456",而str5,str6都是在堆上的,因而结果为true, false.

问题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

public static final double

MAX_VALUE

1.7976931348623157E308

public static final double

MIN_VALUE

4.9E-324

public static final double

NaN

0d/0d

public static final double

NEGATIVE_INFINITY

-1d/0d

public static final double

POSITIVE_INFINITY

1d/0d

public static final int

SIZE

64

 

java.lang.Float

public static final float

MAX_VALUE

3.4028234663852886E38f

public static final float

MIN_VALUE

1.401298464324817E-45f

public static final float

NaN

0f/0f

public static final float

NEGATIVE_INFINITY

-1f/0f

public static final float

POSITIVE_INFINITY

1f/0f

public static final int

SIZE

32

 

java.lang.Integer

public static final int

MAX_VALUE

2147483647

public static final int

MIN_VALUE

-2147483648

public static final int

SIZE

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
posted @ 2010-02-27 15:47  刘超觉先  阅读(1555)  评论(1编辑  收藏  举报