Java程序员面试秘笈
上QQ阅读APP看书,第一时间看更新

第3章 运算符和类型转换

Java运算符起源于C和C++,但是它有自己独特的运算方式和行为,并增加了独有的运算符。这一点求职者必须充分重视。

求职者在掌握Java运算符和赋值部分内容时,需要特别注意下面的几个知识点。

• 运算符/和%的用法。

• ++i、i++、--i和i—的不同。

• instanceof运算符的使用。

• 位逻辑和移位运算符的使用。

• 三目运算符的运算方式。

• 运算符的优先级和相互结合性。

• 数值类型转换如何处理。

面试题1111%13的运算结果

What is the value of 111%13?(111%13的值是什么?)

(a)3。

(b)5。

(c)7。

(d)9。

考点:这是一个非常简单的面试题,主要考察求职者对于Java基本运算符的理解和掌握。

出现频率:★★★★

面试题解析】算术运算符用在Java数学表达式中,其用法和功能与在代数学中一样,Java定义了下列算术运算符,如表3.1所示。

表3.1 算术运算符及其含义

算术运算符的运算数必须是数字类型。算术运算符不能用在布尔类型上,但是可以用在char类型上,因为在Java中,实质上char类型是int类型的一个子集。

1.基本算术运算符

基本算术运算符加、减、乘、除(+、-、*、/)可以对所有的数字类型操作。减运算也用作表示单个操作数的负号。记住对整数进行“/”运算时,所有的余数都会被舍去。下面这个简单例子示范了算术运算符,也说明了浮点型除法和整型除法之间的差别。

        // 基本算术运算符
        class BasicMath {
        public static void main(String args[]) {
        // 整数
            System.out.println("Integer Arithmetic");
            int a = 1 + 1;
            int b = a * 3;
            int c = b / 4;
            int d=c-a;
            int e = -d;
            System.out.println("a="+a);
            System.out.println("b="+b);
            System.out.println("c="+c);
            System.out.println("d="+d);
            System.out.println("e="+e);
            //浮点数
            System.out.println("\nFloating Point Arithmetic");
            double da=1+1;
            double db=da*3;
            double dc=db/4;
            double dd=dc-a;
            double de=-dd;
            System.out.println("da="+da);
            System.out.println("db="+db);
            System.out.println("dc="+dc);
            System.out.println("dd="+dd);
            System.out.println("de="+de);
        }
        }

当求职者运行这个程序,会看到如下输出:

        Integer Arithmetic
        a=2
        b=6
        c=1
        d=-1
        e=1
        Floating Point Arithmetic
        da=2.0
        db=6.0
        dc=1.5
        dd=-0.5
        de=0.5

2.模运算符

模运算符(%),其运算结果是整数除法的余数。值得注意的是,%能像整数类型一样被用于浮点类型,这不同于C/C++,在C/C++中,%仅仅能用于整数类型。下面的示例程序说明了模运算符%的用法。

        //%运算符
        class Modulus {
        public static void main(String args[]) {
              int x=42;
              double y=42.25;
              System.out.println("x mod 10 ="+x%10);
              System.out.println("y mod 10="+y%10);
        }
        }

当运行这个程序,会看到如下输出:

        x mod 10=2
        y mod 10=2.25

本面试题是求111除以13后的余数,即7。

参考答案:(c)。

面试题2 选择正确的递增运算结果

What is the value of y after execution zhe flowing statements?(执行完下列表达式后结果是什么?)

        x=4;
        y=5;
        y=x++;

(a)4

(b)5

(c)6

(d)7

考点:考察求职者对于递增运算符的理解程度。

出现频率:★★★★

面试题解析】“++”和“--”是Java的递增和递减运算符。它们具有一些特殊的性能,使用起来非常有趣,下面将对二者做详细讨论。

递增运算符对其运算数加1,递减运算符对其运算数减1。

        x=x+1;

运用递增运算符可重写为:

        x++;

同样,语句:

        x=x-1;

运用递减运算符可重写为:

        x--;

在前面的例子中,递增或递减运算符采用前缀(prefix)或后缀(postfix)格式都是相同的。但是,当递增或递减运算符作为一个较大的表达式的一部分,前缀或后缀就会有重要的不同。如果递增或递减运算符放在其运算数前面,Java就会先执行相应的递增或递减操作,重新获取该运算数的值,并将其用于表达式的其他部分。如果运算符放在其运算数后面,Java就会先获得该操作数的值,再执行递增或递减运算。例如:

        x=42;
        y=++x;

在这个例子中,y将被赋值为43,因为在将x的值赋给y以前,要先执行递增运算。这样,语句行“y=++x;”和下面两句是等价的:

        x=x+1;
        y=x;

但是,当递增运算符作为后缀时,如下:

        x=42;
        y=x++;

在执行递增运算以前,已将x的值赋给了y,因此y的值还是42。当然,在这两个例子中,x都被赋值为43。在本例中,语句行“y=x++;”与下面两个语句等价:

        y=x;
        x=x+1;

下面的程序说明了递增运算符的使用:

        //递增运算符
        Class IncDec{
        public static void main(String args[]) {
              int a =1;
              int b=2;
              int c;
              int d;
              c=++b;
              d=a++;
              c++;
              System.out.println("a="+a);
              System.out.println("b="+b);
              System.out.println("c="+c);
              System.out.println("d="+d);
            }
        }

该程序的输出如下:

        a=2
        b=3
        c=4
        d=1

参考答案:(b)。

面试题3 8|9&10^11的运算结果

What is the value of 8|9&10^11?(8|9&10^11的结果是多少?)

(a)8

(b)9

(c)10

(d)11

考点:考察求职者对Java位运算的理解和掌握。

出现频率:★★★

面试题解析】Java定义的位运算(bitwise operators)直接对整数类型的位进行操作,这些整数类型包括long、int、short、cha和byte。位运算符及其作用如表3.2所示。

表3.2 位运算符及其结果

既然位运算符在整数范围内对位操作,因此必须理解两点:Java是如何存储整数值、如何表示负数。因此,在继续讨论之前,简短概述一下这两个话题。

所有的整数类型以二进制数位来表示。例如,byte型值42的二进制代码是00101010,其中每个位置代表2次方,在最右边的位以20开始,向左下一个位置将是21或2,然后是23,24,25等,依此类推。42的二进制代码中位置1、3、5的值为1(从右边以0开始数),这样42是21+23+25的和,也即2+8+32。

所有的整数类型(除了char类型之外)都是有符号的整数,这意味着它们既能表示正数,又能表示负数。Java使用2 的补码来表示负数,也就是将其对应的正数的二进制代码取反(即将1变成0,将0变成1),然后对取反后的结果加1。例如,将42的二进制代码的各位取反,由00101010取反得到11010101,再加1,得到11010110,即-42的二进制代码。要对一个负数解码,首先对其所有的位取反,然后加1。例如-42,取反后为00101001,然后加1,这样就得到了00101010,即42。

如果考虑到零的交叉问题,求职者就容易理解Java(以及其他绝大多数语言)使用2的补码的原因。假定byte类型的零值用00000000代表,它的补码是仅仅将每一位取反,即生成11111111,代表负零。但问题是负零在整数数学中是无效的。为了解决负零的问题,在使用2的补码代表负数的值时,对其值加1。负零11111111加1后为100000000,但位1太靠左,而不适合返回到byte类型的值,因此人们规定,-0和0的表示方法一样,-1的补码为11111111。尽管我们在这个例子使用了byte类型的值,但基本的原则也同样适用于其他Java的整数类型。

Java使用2的补码来存储负数,并且因为Java中的所有整数都是有符号的,所以应用位运算符能够容易地达到意想不到的结果。例如,Java用高位来代表负数,为避免意外,请记住不管高位的顺序如何,它决定一个整数的符号。

1.位逻辑运算符

位逻辑运算符有“与”(AND)、“或”(OR)、“异或”(XOR)、“非”(NOT),分别用“&”、“|”、“^”、“~”表示。每位逻辑运算的结果如表3.3所示。位运算符应用于每个运算数内的每个单独的位。

表3.3 位逻辑运算符的结果

位逻辑运算符示例如下:

        //位逻辑运算符
        class BitLogic {
        public static void main(String args[]) {
        String binary[] = {
        "0000","0001","0010","0011","0100","0101","0110","0111",
        "1000","1001","1010","1011","1100","1101","1110","1111"
        };
        int a=3;//0+2+1即0011
        int b=6;//4+2+0即0110
        int c=a|b;
        int d=a&b;
        int e=a^b;
        int f=(~a&b)|(a&~b);
        int g=~a&0x0f;
        System.out.println("a="+binary[a]);
        System.out.println("b="+binary[b]);
        System.out.println("a|b="+binary[c]);
        System.out.println("a&b="+binary[d]);
        System.out.println("a^b="+binary[e]);
        System.out.println("~a&b|a&~b="+binary[f]);
        System.out.println("~a="+binary[g]);
        }
        }

在本例中,变量ab对应位的组合代表了二进制数所有的4种组合模式:0-0、0-1、1-0、和1-1。“|”运算符和“&”运算符分别对变量ab的各个对应位运算得到了变量c和变量d的值。对变量ef的赋值说明了“^”运算符的功能。字符串数组binary代表了0~15对应的二进制的值。在本例中,数组各元素的排列顺序显示了变量对应值的二进制代码。数组之所以这样构造,是因为变量的值 n 对应的二进制代码可以被正确地存储在数组对应元素binary[n]中。例如变量 a 的值为3,则它的二进制代码对应地存储在数组元素binary[3]中。~a的值与数字0x0f(对应二进制为00001111)进行按位与运算的目的是减小~a的值,保证变量g的结果小于16。因此该程序的运行结果可以用数组binary对应的元素来表示。该程序的输出如下:

        a=0011
        b=0110
        a|b=0111
        a&b=0010
        a^b=0101
        ~a&b|a&~b=0101
        ~a=1100

2.左移运算符

左移运算符<<使指定值的所有位都左移规定的位数。它的通用格式如下所示:

        value<<num

这里,num指定value移动的位数。左移运算符<<使指定值的所有位都左移num位,每左移一个位,高阶位都被移出(并且丢弃),并用0填充右边位。这意味着当左移的运算数是int类型时,每移动1位,它的第31位就要被移出并且丢弃;当左移的运算数是long类型时,每移动1位,它的第63位就要被移出并且丢弃。

在对byte和short类型的值进行移位运算时,求职者必须小心。因为Java在对表达式求值时,将自动把byte和short类型扩大为int型,而且表达式的值也是int型。对byte和short类型的值进行移位运算的结果是int型,如果左移不超过31位,原来byte和short对应各位的值也不会丢弃。但是,如果对一个负的byte或者short类型的值进行移位运算,被扩大为int型后,它的符号也被扩展。这样,整数值结果的高位就会被1填充。因此,为了得到正确的结果,必须舍弃得到结果的高位。这样做最简单的办法是将结果转换为byte型。示例代码如下:

        //左移位运算符
        class ByteShift {
        public static void main(String args[]) {
        byte a =64,b;
        int i;
        i=a<<2;
        b=(byte)(a<<2);
        System.out.println("Original value of a:" +a);
        System.out.println("i and b:"+i+" "+b);
        }
        }

该程序产生的输出如下所示:

        Original value of a: 64
        i and b: 256 0

变量 a 在赋值表达式中被扩大为int型,64(01000000)左移两次后生成值256(100000000),被赋给变量i。然而,经过左移后变量b中唯一的1被移出,低位全部成了0,因此b的值也变成了0。

既然每次左移都可以使原来的操作数翻倍,程序员们经常使用这个办法来进行快速的2的乘法。但是要小心,如果将1移进高阶位(31或63位),那么该值将变为负值。示例代码如下:

        class MultByTwo {
        public static void main(String args[]) {
        int i;
        int num = 0xFFFFFFE;
        for(i=0; i<4; i++) {
        num = num<<1;
        System.out.println(num);
        }
        }
        }

该程序的输出如下所示:

        536870908
        1073741816
        2147483632
        -32

初值经过仔细选择,以便在左移4 位后产生-32。正如运行结果所示,当1 被移进31位时,数字被解释为负值。

3.右移运算符

右移运算符>>使指定值的所有位都右移规定的次数。它的通用格式如下所示:

        value>>num

这里,num指定要移位值value移动的位数。也就是,右移运算符>>使指定值的所有位都右移num位。

将值32右移2次,将结果8赋给变量a,代码如下:

        Int a=32;
        a=a>>2;//a值为8

当值中的某些位被“移出”时,这些位的值将被丢弃。例如,35右移2次,它的2个低位将被移出丢弃,将结果8赋给变量a,示例代码如下:

        int a=35;
        a=a>>2;//a值仍然为8

用二进制表示该过程可以更清楚地看到程序的运行过程。

        00100011 35
        >> 2
        00001000 8

将值每右移一次,就相当于将该值除以2 并且舍弃了余数。可以利用这个特点将一个整数进行快速的2的除法。当然,一定要确保不会将该数原有的任何一位移出。

右移时,被移走的最高位(最左边的位)由原来最高位的数字补充。例如,如果要移走的值为负数,每一次右移都在左边补1;如果要移走的值为正数,每一次右移都在左边补0。这叫做符号位扩展(保留符号位),在进行右移操作时用来保持负数的符号。例如,-8>>1是-4,用二进制表示如下:

        11111000-8
        >>1
        11111100-4

要注意一个有趣的问题,由于符号位扩展(保留符号位)每次都会在高位补1,因此-1右移的结果总是-1。但有时会不希望在右移时保留符号,下面的例子将一个byte型的值转换为用十六进制表示,右移后的值与0x0f进行按位与运算,可以舍弃任何的符号位扩展,以便得到的值可以作为定义数组的下标,从而得到对应数组元素代表的十六进制字符。

示例代码如下:

        class HexByte {
        static public void main(String args[]) {
        char hex[]={
        '0','1','2','3','4','5','6','7',
        '8','9','a','b','c','d','e','f''
        };
        byte b=(byte)0xf1;
        System.out.println("b=0x"+hex[(b>>4)&0x0f]+hex[b&0x0f]);
        }
        }

该程序的输出如下:

        b=0xf1

4.无符号右移

正如上面描述的,每一次右移,>>运算符总是自动地用它的先前最高位的内容补移位后的最高位。这样做保留了原值的符号,但有时这样的操作并不是我们想要的。例如,如果进行移位操作的运算数不是数字值,就不希望进行符号位扩展(保留符号位),在处理像素值或图形时,这种情况相当普遍。在这种情况下,不管运算数的初值是什么,希望移位后总是在高位(最左边)补0无符号移动(unsigned shift)。这时可以使用Java的无符号右移运算符>>>,它总是在左边补0。下面的程序段说明了无符号右移运算符>>>。在示例中,变量a被赋值为-1,用二进制表示32位全是1。这个值被无符号右移24位,当然忽略了符号位扩展,左边总是补0。最后将得到的值255赋给变量a。示例代码如下:

        int a=-1;
        a=a>>>24;

下面用二进制形式进一步说明该操作。

int型-1的二进制代码为11111111111111111111111111111111。

无符号右移24位:>>>24。

得到int型255的二进制代码00000000000000000000000011111111。由于无符号右移运算符>>>只是对32位和64位的值有意义,所以并不像想象中那样有用。在表达式中,过小的值总是被自动扩大为int型。这意味着符号位扩展和移动总是发生在32 位,而不是8位或16位。这样,对第7位以0开始的byte型的值进行无符号移动是不可能的,因为在实际移动运算时,是对扩大后的32位值进行操作。示例代码如下:

        class ByteUShift {
        static public void main(String args[]) {
        char hex[]={
        '0','1','2','3','4','5','6','7',
        '8','9','a','b','c','d','e','f'
        };
        byte b=(byte)0xf1;
        byte c=(byte)(b>>4);
        byte d=(byte)(b>>>4);
        byte e=(byte)((b&0xff)>>4);
        System.out.println("b=0x"+hex[(b>>4)&0x0f]+hex[b&0x0f]);
        System.out.println("b>>4=0x"+hex[(c>>4)&0x0f]+hex[c&0x0f]);
        System.out.println("b>>>4=0x"+hex[(d>>4)&0x0f]+hex[d&0x0f]);
        System.out.println("(b&0xff)>>4=0x"+hex[(e>>4)&0x0f]+hex[e&0x0f]);
        }
        }

该程序的输出显示了无符号右移运算符>>>对byte型值的处理,实际上不是对byte型值直接操作,而是将其扩大到int型后再处理。在示例中变量b被赋为任意的负byte型值,对变量b右移4位后转换为byte型,将得到的值赋给变量c,因为有符号位扩展,所以该值为0xff。对变量b进行无符号右移4位操作后转换为byte型,将得到的值赋给变量d,期望值是0x0f,但实际上是0xff,因为在移动之前变量b就被扩展为int型,已经有符号扩展位。最后一个表达式将变量b的值通过按位与运算将其变为8位,然后右移4位,将得到的值赋给变量e,这次得到了预想的结果0x0f。由于对变量d(它的值已经是0xff)进行按位与运算后的符号位的状态已经明了,所以对变量d再没有进行无符号右移运算。示例的运行结果如下:

        B=0xf1
        b>>4=0xff
        b>>>4=0xff
        (b&0xff)>>4=0x0f

5.位运算符赋值

所有的二进制位运算符都有一种将赋值与位运算组合在一起的简写形式。下面两个语句都是将变量a右移4位后赋给a,代码如下:

        a=a>>4;
        a>>=4;

同样,下面两个语句都是将表达式aORb运算后的结果赋给a,代码如下:

        a=a|b;
        a|=b;

下面的程序定义了几个int型变量,然后运用位赋值简写的形式将运算后的值赋给相应的变量,代码如下:

        class OpBitEquals {
        public static void main(String args[]) {
        int a=1;
        int b=2;
        int c=3;
        a|=4;
        b>>=1;
        c<<=1;
        a^=c;
        System.out.println("a="+a);
        System.out.println("b="+b);
        System.out.println("c="+c);
        }
        }

该程序的输出如下所示:

        a=3
        b=1
        c=6

面试题解析】该面试题可以解析成 (8|((9&10)^11))的表达顺序。

参考答案:(d)。

面试题4 选择表达式的输出结果

What is the output displayed by the following program?(下列程序输出结果是什么?)

        public static void main(String args[]){
        int n=7;
        n<<=3;
        n=n&n+1|n+2^n+3;
        n>>=2;
        System.out.println(n);
        }

(a)0

(b)-1

(c)14

(d)64

考点:考察求职者对Java位运算的理解和掌握。

出现频率:★★★

面试题解析】第1个赋值语句使得n的值为7,7的二进制形式为00000111。

第2个赋值语句可以写成n=n<<3,n左移3位的结果是00111000。

第3个赋值语句的运算顺序是(n&n)+(1|n)+(2^n)+3,运算结果是57(十进制),其二进制位00111001。

第4个赋值语句使得n的值右移2位,由于n的符号为0,因此,在右移时左边填充位是0,即00001110,转换为十进制就是14。

参考答案:(c)。

面试题5 选择“abcd” instanceof Object的返回结果

What is the value returned by "abcd" instanceof Object?("abcd" instanceof Object返回结果是什么?)

(a)"abcd"

(b)true

(c)false

(d)String

考点:考察求职者对于instanceof的掌握,这也是求职者容易忽略的内容。

出现频率:★★★★

面试题解析】instanceof是Java的一个二元操作符,和=、>、<是同一类的比较运算符。由于它是由字母组成的,所以也是Java的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据。示例代码如下:

        String s="I AM an Object!";
        boolean isObject=s instanceof Object;

示例声明了一个String对象引用,指向一个String对象,然后用instancof来测试它所指向的对象是否是Object类的一个实例,显然,这是真的,所以返回true,也就是isObject的值为true。

instanceof有一些用处。一个处理账单的系统中有3个类,如下所示:

        public class Bill {//省略细节}
        public class PhoneBill extends Bill{//省略细节}
        public class GasBill extends Bill{//省略细节}

在处理程序里有一个方法,接受一个Bill类型的对象,用来计算金额。假设电话和燃气账单计算方法不同,而传入的Bill对象可能是两种中的任何一种,所以要用instanceof来判断,相关代码如下:

        public double calculate(Bill bill) {
        if (bill instanceof PhoneBill) {
        //计算电话账单
        }
        if (bill instanceof GasBill) {
        //计算燃气账单
        }
        ...
        }

这样就可以用一个方法处理两种子类。

然而,这种做法通常被认为是没有利用好面向对象中的多态性。其实实现上面的功能使用方法重载完全可以实现,这是面向对象编程应有的做法,避免回到结构化的编程模式。只要提供两个名字和返回值都相同,接受参数类型不同的方法就可以了,如下代码所示:

        public double calculate(PhoneBill bill) {
        //计算电话账单
        }
        public double calculate(GasBill bill) {
        //计算燃气账单
        }

所以,在绝大多数情况下使用instanceof并不是我们推荐的做法,应当利用好多态性。

instanceof的运算结果只能是boolean类型,即只能是true或者false。本试题中,由于“abcd”是一个字符串,即String对象,那么自然也是Object的一个实例,所以运算结果应该是true。

参考答案:(b)。

面试题6 考察运算符的优先级

请选择下面代码运行后打印的结果。

        public static void main(String[] args) {
                int x=5;
                int y=3;
                x=x+(x++)+(++x)+y;
                System.out.print(x);
              }

(a)20

(b)21

(c)18

(d)19

考点:考察求职者对Java运算符优先级的掌握。

出现频率:★★★★★

面试题解析】Java语言从左向右计算表达式的值。当一个表达式中有多个运算符的时候,运算的次序由表3.4所示的优先级顺序决定。

表3.4 Java运算符的优先级

记住上面列出的Java运算符优先级就很容易给出正确的答案,本试题的计算次序可以解析如下:

(5+1)+5+(5+1)+3=20

参考答案:(a)。

面试题7 9+8%7+6的运算结果

What is the value of 9+8%7+6?(9+8%7+6结果是多少?)

(a)17

(b)16

(c)13

(d)14

考点:考察求职者对Java运算符优先级的掌握。

出现频率:★★★★

面试题解析】求职者需要熟悉Java的运算符优先级才能知道计算过程,按照前面讲述的优先级表,可以得出如下计算表达:

(9+(8%7)+6)=16

参考答案:(b)。

面试题8 选择正确的运算结果

下面代码运行输出结果是什么?

        public static void main(String[] args) {
                int x=0;
                boolean b1,b2,b3,b4;
                b1=b2=b3=b4=true;
                x=(b1|b2&b3^b4)?x++:--x;
                System.out.println(x);
              }

(a)-1

(b)0

(c)1

(d)2

考点:考察求职者对Java运算符优先级的掌握。

出现频率:★★★★

面试题解析】在赋值语句“x=(b1|b2&b3^b4)?x++:--x;”中,存在三元运算符。“b1|b2&b3^b4”的结果是true,所以应该返回x++的值,根据后缀运算符的特性,可以知道结果为0。“b1|b2&b3^b4”的计算请参考Java运算符的优先级列表。

参考答案:(b)。

面试题9 请给出equals()和==的区别

考点:考察求职者对Java布尔运算符==和equals()方法的区别。

出现频率:★★★★

面试题解析】该面试题涉及Java的等值运算符==。在Java语法中,等值运算符有两个:==和!=。在等值运算符使用中,可以分为原始数据比较和引用对象比较两种情况。

1.原始数据比较

相等运算符(==)和不等运算符(!=)的两端可以是原始数据类型,包括boolean类型的值。在软件开发中,需要注意的是浮点数值的比较,看似相等,但是实际上可能是不相等的。

例如,比较是否相等,这是一个非常简单的数学问题,但是在编程中会出现问题。示例代码如下:

        public static void main(String[] args) {
                  double a=1.0-2.0/3.0;
                  double b=1.0/3.0;
                  System.out.println("the value of a="+a);
                  System.out.println("the value of b="+b);
                  System.out.println(a==b);
              }

运行该示例代码,显示输出结果如下:

        the value of a=0.33333333333333337
        the value of b=0.3333333333333333
        false

因为计算机表示浮点数字有精度限制,有些看似相等的浮点表达式实际上是不相等的,这一点尤其在做财务或者计算相关软件项目时需要特别注意。

2.引用对象比较

在比较对象的时候,常常用到“==”和“equals(Object)”。示例代码如下:

        public class Example1
        {
            public static void main(String[] args)
            {
                String s1=new String("abc");
                String s2=new String("abc");
                // s1=s2;
                System.out.println("用 == 比较结果");
                System.out.println(s1==s2); //false
            }
        }

既然两个String对象内容同为“abc”,为什么会输出false呢?那是由于“==”比较的是两个对象的引用,并不是它们的内容。怎样才能比较内容是否相等呢?去掉“s1=s2”一句的注释符,结果就不同了,因为它们的引用相同了。

由于equals()方法为Object类中定义的方法,所以按照默认方式定义过的类均是其子类。也就是说Object类为所有类的父类或超类,Object中的equals()方法的定义形式为:

        public boolean equals(Object obj)

返回类型为boolean,即true或者是false,与“==”返回类型一样。Object类中定义的equals()方法在没有覆盖(或称改写、重写)的情况下,与“==”一样是比较对象的引用。示例代码如下:

        public class Example4
        {
            public static void main(String[] args)
            {
                Example4 e=new Example4();
                Example4 e4=new Example4();
                System.out.println("用equals(Object) 比较结果");
                System.out.println(e.equals(e4));   //结果为false
                System.out.println("用 == 比较结果");
                System.out.println(e==e4);         //结果为false
            }
        }

equals()方法与“==”相比的特殊之处就在于它可以覆盖,所以可以通过覆盖让equals()比较数据内容。当然JDK中也有覆盖过equals()方法的类,例如java.lang.String,就覆盖了从Object继承来的equals()方法,用以比较字符串内容是否相同。示例代码如下:

        public class Example1
          {
              public static void main(String[] args)
              {
                  String s1=new String("abc");
                  String s2=new String("abc");
                  System.out.println("用 == 比较结果");
                  System.out.println(s1==s2);//false
                  System.out.println("用equals(Object) 比较结果");
                  System.out.println(s1.equals(s2));//true
              }
        }

示例中用equals()比较的结果为true,用“==”比较结果为false。String.equals()方法直接比较了两个字符串的内容,如果相同则返回true,否则返回false。可以尝试把其中一个"abc"改成"abcde",看看结果有何变化。

参考答案:

equals()方法是Object类定义的一个方法,用来比较两个对象引用是否相等,这一点和“==”表达式是一样的。

所不同的是,equals()方法可以在子类或者特定类中重写,例如String类就重写了该方法,使用String类的equals()方法可以比较两个String实例的内容是否相等。

面试题10 判断一系列表达式运算后的最终结果

What is the output displayed by the following program?(下列程序代码输出结果是什么?)

        public class Question {
            public static void main(String[] args) {
                  String s1="ab";
                  String s2="abcd";
                  String s3="cd";
                  String s4=s1+s3;
                  s1=s4;
                  System.out.println("s1"+((s1==s2)?"==":"!=")+"s2");
            }
        }

(a)s1==s2

(b)s1!=s2

(c)s1

(d)s1=”abcd”

考点:主要考察求职者对==的理解。

出现频率:★★★★

面试题解析】该面试题主要考察求职者对“==”的理解,由于字符串s1和字符串s2的内容相同,但是是不同的对象,即代表着不同的对象引用,所以s1==s2的值应该是false,那么运行结果是s1!=s2。

参考答案:(b)。

面试题11 赋值运算符选择题

下面代码运行后输出哪个值?

        public static void main(String[] args) {
                int x=10;
                int y=8;
                  x+=y;
                  System.out.println(x);
            }

(a)10

(b)8

(c)18

(d)28

考点:考察求职者对Java语言中的赋值运算符的掌握情况。

出现频率:★★★

面试题解析】Java提供了一套赋值运算符。赋值符号“=”就是赋值运算符,作用是将一个数据赋给一个变量。例如a=3就是一个赋值运算,将3赋给变量a。当然也可以将一个表达式赋给一个变量。值得求职者注意的是复合赋值运算符,即在赋值符号“=”前加入其他运算符。常见的复合赋值运算符如下:

• x+=y等价于x=x+y;

• x-=y等价于x=x-y;

• x*=y等价于x=x*y;

• x/=y等价于x=x/y;

• x%=y等价于x=x%y;

• x&=y等价于x=x&y;

• x|=y等价于x=x|y;

• x^=y等价于x=x^y;

• x<<=y等价于x=x<<y;

• x>>=y等价于x=x>>y。

注意:在赋值运算中,除了基本的赋值符号“=”和“+=”外,所有的其他运算符的运算对象都必须是Java的基本数据类型。“+=”运算符可以用于字符串对象。

在为变量赋予对象引用时,需要理解如下赋值代码:

        public static void main(String[] args) {
                //定义一个原始类型变量和一个对象的引用变量 状态1
                int i=1;
                Object o=new Object();
                //定义一个j和p,并赋值 状态2
                  int j=i;
                  Object p=o;
                  //j增加1,为p重写赋值 状态3
                  j++;
                  p=new Object();
            }

注释1F的代码段中,定义了一个i整型变量和一个o对象引用变量,可以用图3.1表示。注意在图3.1中,变量o是一个Object实例对象的引用,而不是存储了对象本身。整型变量和对象赋值后,状态2如图3.2所示。执行最后一段代码,状态3如图3.3所示。

图3.1 状态1代码段的示意图

图3.2 状态2代码段的赋值示意图

图3.3 状态3代码段的赋值示意图

从图3.1到图3.3的赋值变化很好地展示了Java语言如何为变量进行赋值。对于对象引用的赋值,不是将对象本身赋给变量,而是将对象的引用赋给了变量。

参考答案:(c)。

面试题12 参数传递选择题

请从下列代码运行结果中选择正确的一项。

        public class ParameterPass {
          public static void main(String[] args) {
              int i = 0;
                addTwo(i++);
                System.out.println(i);
            }
            static void addTwo(int i) {
                i += 2;
            }
        }

(a)0

(b)1

(c)2

(d)3

考点:考察求职者对Java参数传递的理解和掌握。

出现频率:★★★★

面试题解析】对于原始数据类型,也就是int、long、char之类的类型,是传值的,开发者在方法中修改了参数值,方法调用结束后,那个变量的值并没有发生改变。也就是说,在参数传递的过程中,该变量的值在方法调用期间会复制给形参。因为形参是方法的局部变量,所以对形参做出的任何修改在调用结束后都不会影响该变量的值。

如果传递的是指向对象的引用,那么就会传递引用值。这意味着,在方法调用期间,实参和形参都是该引用值所代表对象的别名。所以,在参数传递之前,必须由实参表达式算出一个对象的引用,否则会出现错误。示例代码如下:

        public class Canshu {
          public static void main(String args[]) {
                // 原始数据类型int变量
                int val;
                // StringBuffer类型变量
                StringBuffer sb;
                // 字符串变量
                // 为变量赋值
                String str = "ads";
                val = 10;
                sb = new StringBuffer("pears");
                // 打印原始变量值
                System.out.println("val is " + val);
                System.out.println("str is " + str);
                System.out.println("sb is " + sb);
                System.out.println("");
                // 调用方法
                System.out.println("calling modify");
                modify(val, str, sb);
                // 打印出方法调用后变量的值
                System.out.println("returned from modify");
                System.out.println("");
                System.out.println("val is " + val);
                  System.out.println("str is " + str);
                  System.out.println("sb is " + sb);
          }
        public static void modify(int a, String r1, StringBuffer r2) {
                // 方法调用内部的参数值
                System.out.println("in modify...");
                a = 0;
                System.out.println();
                r1 ="ddd";
                r2.append(" taste good");
                r2 = new StringBuffer("test");
                // 打印方法调用内部的变量值
                System.out.println("a is " + a);
                System.out.println("r1 is " + r1);
                System.out.println("r2 is " + r2);
          }
        }

该代码运行后的结果如下:

        val is 10
        str is ads
        sb is pears

        calling modify
        in modify...

        a is 0
        r1 is ddd
        r2 is test
        returned from modify

        val is 10
        str is ads
        sb is pears taste good

示例代码中,定义了原始数据类型变量val和两个对象引用变量strsb。在方法调用过程中,变量val将值传递给了形参,执行modify()方法后,val并没有发生变化。

但是对于对象引用变量sb,传递的是一个对象的引用值(也可以称为地址),在方法调用期间,实参和形参都是指向一个对象的引用,所以在方法调用过程中,对该对象的引用会导致该对象的变化,这种变化在方法调用后也是存在的。

那么String类型的变量str又是怎么回事呢?String并不是一个原始数据类型的变量,为什么在方法调用后没有发生变化?答案是String是特殊的对象,它不能被改写。

String是一个支持非可变性的类,这种类的特点是状态固定(不存在任何修改对象的方法),在该对象的生存周期内,它的值是永远不变的(它是线程安全的)。

实际上,在方法代码中,如下所示:

        public static void modify(int a, String r1, StringBuffer r2) {
                // 方法调用内部的参数值
                System.out.println("in modify...");
                a = 0;
                System.out.println();
                r1 = "ddd";
                r2.append(" taste good");
                r2 = new StringBuffer("test");
                // 打印方法调用内部的变量值
                System.out.println("a is " + a);
                System.out.println("r1 is " + r1);
                System.out.println("r2 is " + r2);
          }

代码中的“r1="ddd"”等价于“r1=new String("ddd")”,也就是说重新生成了一个新的对象。这一点需要求职者深刻掌握。

参考答案:(b)。

面试题13 选择正确结果

运行下面的代码,正确的结果是哪一个?

        public class ReferenceTest {
          public static void main(String[] args) {
                int a = 1;
                ReferenceTest rt = new ReferenceTest();
                System.out.println(a);
                rt.modify(a);
                System.out.println(a);
          }
          void modify(int number) {
                number = number + 1;
          }
        }

(a)1 2

(b)1 0

(c)1 1

(d)不能编译

考点:考察求职者对Java参数传递的理解和掌握。

出现频率:★★★★

面试题解析】第1个打印执行前,a的值为1,所以打印的第1个数字应该是1。由于modify()方法的参数传递是值传递,即方法执行完毕后,被传入的参数值不会发生改变,所以第2个打印数字也是应该为1。

参考答案:(c)。

面试题14 补全代码

在下面程序标明“插入内容”的位置处插入哪条语句会导致编译错误?

        public class ParameterUse {
          public static void main(String[] args) {
                int a = 0;
                final int b = 1;
                int[] c = { 2 };
                final int[] d = { 3 };
                useArgs(a, b, c, d);
          }
            static void useArgs(final int a, int b, final int[] c, int[] d) {
                // 插入内容
          }
        }

请选择两个正确的答案:

(a)a++

(b)b++

(c)b=a

(d)c[0]++

(e)d[0]++

(f)c=d

考点:考察求职者对于J a v a的final变量的参数传递理解。

出现频率:★★★★

面试题解析】声明形参的时候,可以在方法调用的参数调用之前加上关键字final。Final修辞的参数也可以叫做空白最终变量,也可以当作常量。即在对其赋值之前(例如方法调用之前),该变量为空(为初始化),赋值之后,在该变量的生存周期内再也不能改变变量的值。

final变量只可以被赋值一次。在方法调用时,final形式参数被赋予实际参数的值,在方法内部,不能对final参数进行重新赋值或者是修改,所以a++和c=d会导致编译错误。

参考答案:(a)、(f)。

面试题15 选择正确的类型转换

Which of the following are legal lines of code?(下列哪些代码是合法的?)

(a)int w=(int)888.8

(b)byte x=(byte)1000L

(c)long y=(byte)100

(d)byte z=(byte)100L

考点:考察用户对Java语言类型转换的理解和掌握。

出现频率:★★★★★

面试题解析】在Java中,整型、实型、字符型被视为简单数据类型,这些类型由低级到高级分别为:(byte,short,char) int long float double。

简单数据类型之间的转换又可以分为:

• 低级到高级的自动类型转换;

• 高级到低级的强制类型转换;

• 包装类过渡类型能够转换。

1.自动类型转换

低级变量可以直接转换为高级变量,下面的语句可以在Java中直接通过,代码如下:

        byte b;
        int i=b;
        long l=b;
        float f=b;
        double d=b;

如果低级类型为char型,向高级类型(整型)转换时,会转换为对应ASCII码值,代码如下:

        char c='c';
        int i=c;
        System.out.println("output:"+i);

输出:

        output:99;

对于byte、short、char 3种类型而言,它们是平级的,因此不能相互自动转换,可以使用下述的强制类型转换,代码如下:

        short i=99;
        char c=(char)i;
        System.out.println("output:"+c);

输出:

        output:c

2.强制类型转换

将高级变量转换为低级变量时,情况会复杂一些,可以使用强制类型转换。示例代码如下:

        int i=99;
        byte b=(byte)i;
        char c=(char)i;
        float f=(float)i;

3.包装类过渡类型转换

Java的包装类将基本数据类型与对象类型在一定程度上统一起来,并提供以下两个方面的功能。

• 将基本类型封装后当作对象进行操作。

• 为基本数据类型提供各种转换功能。

Java共有6个包装类,分别是Boolean、Character、Integer、Long、Float和Double。从字面上就可以看出它们分别对应于boolean、char、int、long、float和double类型。而String和Date本身就是类,也就不存在包装类的概念。

在进行简单数据类型之间的转换(自动转换或强制转换)时,可以利用包装类进行过渡。

一般情况下,首先声明一个变量,然后生成一个对应的包装类,就可以利用包装类的各种方法进行类型转换了。例如,当希望把float型转换为double型时,示例代码如下:

        float f1=100.00f;
        Float F1=new float(f1);
        Double d1=F1.doubleValue();//F1.doubleValue()为Float类的返回double值型的方法

当希望把double型转换为int型时,代码如下所示:

        double d1=100.00;
        Double D1=new Double(d1);
        int i1=D1.intValue();

当希望把int型转换为double型时,自动转换如下:

        int i1=200;
        double d1=i1;

简单类型的变量转换为相应的包装类,可以利用包装类的构造函数,如Boolean(boolean value)、Character(char value)、Integer(int value)、Long(long value)、Float(float value)、Double(double value)。而在各个包装类中,总有形为×××Value()的方法,来得到其对应的简单类型数据。利用这种方法,也可以实现不同数值型变量间的转换。例如,对于一个双精度实型类,intValue()可以得到其对应的整型变量,而doubleValue()可以得到其对应的双精度实型变量。

一个浮点类型数据通过强制类型转换可以转换为int类型,这时小数位会被截去,所以(a)是正确的。一个长整型数值可以被转换为byte类型,如果长整型数大于127,转换为byte类型就会变为-128,所以(b)和(d)是正确的。由于长整型数的范围覆盖byte类型数范围,所以(c)也是正确的。

参考答案:(a)、(b)、(c)、(d)。

面试题16 表达式5.4+"3.2"的结果

What is the result of the expression 5.4+“3.2”?(表达式5.4+“3.2”的结果是什么?)

(a)the double value of 8.6

(b)the String "8.6"

(c)the long value 8

(d)the String "5.43.2"

考点:考察求职者对于String类型变量的相关转换和使用。

出现频率:★★★★

面试题解析】String是Java程序员使用非常频繁的一个对象类型,特别是在开发Web应用项目的时候,因为采用B/S结构的应用,客户端提交的form中的值都是String类型值,这就需要程序员将其转换为所需要的类型来处理。随着各种框架和组件的使用,此类的转换可以实现自动化,但是,String类型转换仍然是Java程序员面试的重点考察之一。

通过查阅类库中各个类提供的成员方法可以看到,几乎从java.lang.Object类派生的所有类提供了toString()方法,即将该类转换为字符串。Characrer、Integer、Float、Double、Boolean、Short等类的toString()方法用于将字符、整数、浮点数、双精度数、逻辑数、短整型等类转换为字符串,示例代码如下:

        int i1=10;
        float f1=3.14f;
        double d1=3.1415926;
        Integer I1=new Integer(i1);//生成Integer类
        Float F1=new Float(f1); //生成Float类
        Double D1=new Double(d1); //生成Double类
        //分别调用包装类的toString()方法转换为字符串
        String si1=I1.toString();
        String sf1=F1.toString();
        String sd1=D1.toString();
        Sysytem.out.println("si1"+si1);
        Sysytem.out.println("sf1"+sf1);
        Sysytem.out.println("sd1"+sd1);

下面给出Java开发中常见的类型转换函数:

        string->byte
        Byte static byte parseByte(String s)

        byte->string
        Byte static String toString(byte b)

        char->string
        Character static String to String (char c)

        string->Short
        Short static Short parseShort(String s)

        Short->String
        Short static String toString(Short s)

        String->Integer
        Integer static int parseInt(String s)

        Integer->String
        Integer static String tostring(int i)

        String->Long
        Long static long parseLong(String s)

        Long->String
        Long static String toString(Long i)

        String->Float
        Float static float parseFloat(String s)

        Float->String
        Float static String toString(float f)

        String->Double
        Double static double parseDouble(String s)

        Double->String
        Double static String toString(Double d)

该面试题中,当+符号两侧的运算数类型不一致时,就会发生类型转换。当一个运算数为原始数据类型,另外一个为字符串时,则基本类型的运算数要转换为等价的字符串。所以表达式5.4+“3.2”在+运算前会变成“5.4”+“3.2”。

参考答案:(d)。

面试题17 日期选择题

请选择下面代码运行后的一个正确结果。

        import java.util.Date;

        public class DateTest {
          public static void main(String[] args) {
                Date mydate = new Date(0);
                System.out.println(mydate);
          }
        }

(a)0

(b)Thu Jan 01 08:00:00 CST 1970

(c)Thu Jan 01 08:00:00 CST 1900

(d)Thu Jan 01 08:00:00 CST 2000

考点:考察求职者对于Java日期类型和日期处理相关知识的理解。

出现频率:★★★★

面试题解析】Java的日期相关处理是软件项目开发中的基本问题。掌握日期的处理,也是对求职者的一个基本要求。求职者必须深刻领悟Java中关于日期的类和相应的转换。

Java统计从1970年1月1日起的毫秒的数量来表示日期。也就是说,例如,1970年1月2日,是在1月1日后的86,400,000毫秒。同样的,1969年12月31日是在1970年1月1日前86,400,000毫秒。Java的Date类使用long类型记录这些毫秒值,因为long是有符号整数,所以日期可以在1970年1月1日之前,也可以在这之后。long类型表示的最大正值和最大负值可以轻松地表示290,000,000年的时间,这应该能够满足大多数人的时间要求。

1.Date类

Date类可以在java.util包中找到,它创建一个表示创建时刻的对象,用一个long类型的值表示一个指定的时刻。它的构造函数是Date(),getTime()方法返回Date对象的long值。在下面的程序中,使用Date()构造函数创建一个表示程序运行时刻的对象,并且利用getTime()方法找到这个日期代表的毫秒数量,示例代码如下:

        import java.util.*;
        public class Now {
            public static void main(String[] args) {
                Date now = new Date();
                long nowLong = now.getTime();
                System.out.println("Value is " + nowLong);
            }
        }

当运行这个程序后,打印出972,568,255,150。这个数字在一个合理的范围:相对1970年1月1日,时间不到31年,是合理的。计算机是用这个毫秒值表示时间,但人们可不习惯说“我将在996,321,998,34见到你”。幸运的是,Java提供了一个转换Date对象到字符串的途径。

2.DateFormat类

DateFormat类的一个目标是建立一个人们能够识别的字符串。然而,因为语言文化的差别,不是所有的人都希望看到格式严格相同的日期。法国人更喜欢看到“25 decembre 2000”,美国人则习惯看到“December 25,2000”所以一个DateFormat的实例被创建以后,这个对象包含了日期的显示格式的信息。如果使用用户电脑区域设置默认的格式,可以像下面那样,创建DateFormat对象,使用getDateInstance()方法,示例代码如下:

        DateFormat df = DateFormat.getDateInstance();

DateFormat类可以在java.text包中找到。可以使用format()方法转换Date对象为一个字符串。示例程序如下:

        import java.util.*;
        import java.text.*;
        public class NowString {
            public static void main(String[] args) {
                Date now = new Date();
                DateFormat df = DateFormat.getDateInstance();
                String s = df.format(now);
                System.out.println("Today is " + s);
            }
        }

注意:能够使用getDateInstance()方法改变DateFormat实例的语种。但是,在上面的例子中,是通过改变Windows的控制面板的区域设置做到的。不同地方的区域设置不同,结果就不同,这样有好处,也有不足,Java程序员应该了解这些。Java程序员可以只写一行代码就显示日期,而且世界不同地区的电脑运行同样的程序会显示不同的日期格式。

在程序中混合输出文本和日期,如果文本是英文,就不希望日期格式是德文或是西班牙文。如果程序员依靠日期格式编程,日期格式将根据运行程序所在电脑的区域设置不同而不同。

通过parse()方法,DateFormat能够以一个字符串创立一个Date对象。这个方法能抛出ParseException异常,所以必须使用适当的异常处理技术。下面的示例程序通过字符串创建Date对象,示例代码如下:

        import java.util.*;
        import java.text.*;
        public class ParseExample {
            public static void main(String[] args) {
                String ds = "November 1, 2000";
                DateFormat df = DateFormat.getDateInstance();
              try {
                Date d = df.parse(ds);
                }
              catch(ParseException e) {
                System.out.println("Unable to parse " + ds);
                }
            }
        }

3.GregorianCalendar类

创建一个代表任意日期的途径是使用GregorianCalendar类的构造函数,它包含在java.util包中,形式如下:

        GregorianCalendar(int year, int month, int date)

注意:月份的表示,一月是0,二月是1,依此类推,12 月是11。因为大多数人习惯于使用单词而不是使用数字来表示月份,所以父类Calendar使用常量JANUARY、FEBRUARY等来表示月份。例如,创建Wilbur和Orville制造第1 架动力飞机的日期(December 17, 1903),可以使用:

        GregorianCalendar firstFlight = new GregorianCalendar(1903, Calendar.DECEMBER, 17);

可见使用常量让程序更加易懂。但是,也应该学习怎样阅读下面的短格式。下面的例子同样表示December 17,1903。

        GregorianCalendar firstFlight = new GregorianCalendar(1903, 11, 17);

需要将GregorianCalendar对象转换到Date。要做到这一点,可以使用getTime()方法,本方法从其父类Calendar继承而来。GetTime()方法返回GregorianCalendar相应的Date对象。下面的实例创建一个GregorianCalendar对象,转换到Date对象,得到和输出相应的字符串这样一个过程,代码如下:

        import java.util.*;
        import java.text.*;
        public class Flight {
            public static void main(String[] args) {
                GregorianCalendar firstFlight = new GregorianCalendar(1903, Calendar.DECEMBER, 17);
                Date d = firstFlight.getTime();
                DateFormat df = DateFormat.getDateInstance();
                String s = df.format(d);
                System.out.println("First flight was " + s);
            }
        }

有时候创建一个代表当前时刻的GregorianCalendar类的实例是很有用的。可以简单地使用没有参数的GregorianCalendar构造函数,代码如下:

        GregorianCalendar thisday = new GregorianCalendar();

使用GregorianCalendar对象输出今天日期的示例代码如下:

        import java.util.*;
        import java.text.*;
        class Today {
            public static void main(String[] args) {
                GregorianCalendar thisday = new GregorianCalendar();
                Date d = thisday.getTime();
                DateFormat df = DateFormat.getDateInstance();
                String s = df.format(d);
                System.out.println("Today is " + s);
            }
        }

注意:Date()构造函数和GregorianCalendar()构造函数很类似,都创建一个代表今天的日期对象。

4.日期处理

GregorianCalendar类提供处理日期的方法。一个有用的方法是add(),使用add()方法,能够增加年、月数、天数到日期对象中。要使用add()方法,必须提供要增加的字段和要增加的数量。一些有用的字段是DATE、MONTH、YEAR、和WEEK_OF_YEAR。下面的程序使用add()方法计算未来80天的日期。在Jules的《环球80天》中,80是一个重要的数字,使用这个程序可以计算电影中主角从出发的那一天(1872年10月2日)起80天后的日期。

        import java.util.*;
        import java.text.*;
        public class World {
            public static void main(String[] args) {
                GregorianCalendar worldTour = new GregorianCalendar(1872, Calendar.OCTOBER, 2);
                worldTour.add(GregorianCalendar.DATE, 80);
                Date d = worldTour.getTime();
                DateFormat df = DateFormat.getDateInstance();
                String s = df.format(d);
                System.out.println("80 day trip will end " + s);
            }
        }

回到之前的面试题,初始化的Date(0),该日期实际指的是1970年1月1日。

参考答案:(b)。