有一天,小李告诉我,他去一家公司面试 Java 岗,结果被面试官虐哭了。整整 10 道 Java 面试题,小李一道也没答正确。
他沮丧地给我说,“哥,说点我的情况,你愿意听吗?我和一个女孩相处,女孩大我两岁,我非科班。本来打算国庆换一家薪水高点的,好确认关系。我经验不多,技术一般般,之前在一家外包公司,有一个甲方内推,我就鲁莽地把外包的工作辞了,结果没想到面试被虐了,我担心女朋友会不会因为我没有工作和我分手。”
听他这么一说,确实挺虐心的。后来我就安慰他,要他端正心态,先把这些面试题整明白,然后继续找工作,不要想太多。
借这个机会,我就把小王遇到的这 10 道面试题分享出来,希望能对其他小伙伴一些帮助。
第一题,下面这串代码打印的结果是什么
public class Test { public static void main(String[] args) { System.out.println(Math.min(Double.MIN_VALUE, 0.0d)); } }
小李之所以没答对这道题,是因为他觉得 Double.MIN_VALUE 和 Integer.MIN_VALUE 一样,是个负数,应该小于 0.0d。
但事实上,Double. MIN_VALUE 和 Double. MAX_VALUE 一样,都是正数,Double. MIN_VALUE 的值是 2^(-1074),直接打印 Double. MIN_VALUE 的话,输出结果为 4.9E-324。
因此这道题的正确答案是输出 0.0。
第二题,在 try 块或者 catch 语句中执行 return 语句或者 System.exit() 会发生什么,finally 语句还会执行吗?
小李之所以没答对这道题,是因为在他的刻板印象中,finally 语句是无论如何都会执行的。
但事实上,在 try 块或者 catch 语句中执行 return 语句时,finally 语句会执行;在 try 块或者 catch 语句中执行 System.exit() 时,finally 语句不会执行。
public class Test1 { public static void main(String[] args) { returnTryExec(); returnCatchExec(); exitTryExec(); exitCatchExec(); } public static int returnTryExec() { try { return 0; } catch (Exception e) { } finally { System.out.println("finally returnTryExec"); return -1; } } public static int returnCatchExec() { try { } catch (Exception e) { return 0; } finally { System.out.println("finally returnCatchExec"); return -1; } } public static void exitTryExec() { try { System.exit(0); } catch (Exception e) { } finally { System.out.println("finally exitTryExec"); } } public static void exitCatchExec() { try { } catch (Exception e) { System.exit(0); } finally { System.out.println("finally exitCatchExec"); } } }
程序执行结果如下所示:
finally returnTryExec finally returnCatchExec
第三题,私有方法或者静态方法能被重写(override)吗?
小李之所以没答对这道题,是因为他不确定私有方法或者静态方法与重写之间的关系。
重写的两个方法名相同,方法参数的个数也相同;不过一个方法在父类中,另外一个在子类中。
class LaoWang{ public void write() { System.out.println("老王写了一本《基督山伯爵》"); } } class XiaoWang extends LaoWang { @Override public void write() { System.out.println("小王写了一本《茶花女》"); } } public class OverridingTest { public static void main(String[] args) { LaoWang wang = new XiaoWang(); wang.write(); } }
父类 LaoWang 有一个 write() 方法(无参),方法体是写一本《基督山伯爵》;子类 XiaoWang 重写了父类的 write() 方法(无参),但方法体是写一本《茶花女》。
在 main 方法中,我们声明了一个类型为 LaoWang 的变量 wang。在编译期间,编译器会检查 LaoWang 类是否包含了 write() 方法,发现 LaoWang 类有,于是编译通过。在运行期间,new 了一个 XiaoWang 对象,并将其赋值给 wang,此时 Java 虚拟机知道 wang 引用的是 XiaoWang 对象,所以调用的是子类 XiaoWang 中的 write() 方法而不是父类 LaoWang 中的 write() 方法,因此输出结果为“小李写了一本《茶花女》”。
而私有方法对子类是不可见的,它仅在当前声明的类中可见,private 关键字满足了封装的最高级别要求。另外,Java 中的私有方法是通过编译期的静态绑定的方式绑定的,不依赖于特定引用变量所持有的对象类型。
方法重写适用于动态绑定,因此私有方法无法被重写。
class LaoWang{ public LaoWang() { write(); read(); } public void write() { System.out.println("老王写了一本《基督山伯爵》"); } private void read() { System.out.println("老王在读《哈姆雷特》"); } } class XiaoWang extends LaoWang { @Override public void write() { System.out.println("小王写了一本《茶花女》"); } private void read() { System.out.println("小王在读《威尼斯商人》"); } } public class PrivateOrrideTest { public static void main(String[] args) { LaoWang wang = new XiaoWang(); } }
程序输出结果如下所示:
小王写了一本《茶花女》 老王在读《哈姆雷特》
在父类的构造方法中,分别调用了 write() 和 read() 方法,write()方法是 public 的,可以被重写,因此执行了子类的 write() 方法,read() 方法是私有的,无法被重写,因此执行的仍然是父类的 read() 方法。
和私有方法类似,静态方法在编译期也是通过静态绑定的方式绑定的,不依赖于特定引用变量所持有的对象类型。方法重写适用于动态绑定,因此静态方法无法被重写。
public class StaticOrrideTest { public static void main(String[] args) { Laozi zi = new Xiaozi(); zi.write(); } } class Laozi{ public static void write() { System.out.println("老子写了一本《基督山伯爵》"); } } class Xiaozi extends Laozi { public static void write() { System.out.println("小子写了一本《茶花女》"); } }
程序输出结果如下所示:
老子写了一本《基督山伯爵》
引用变量 zi 的类型为 Laozi,所以 zi.write() 执行的是父类中的 write() 方法。
静态方法也叫类方法,直接通过类名就可以调用,通过对象调用的时候,IDE 会发出警告。
第四题,1.0/0.0 得到的结果是什么?会抛出异常吗,还是会出现编译错误?
小李之所以没答对这道题,是因为他没有深入研究过 double 类型和 int 类型的除法运算。
数字在 Java 中可以分为两种,一种是整形,一种是浮点型。不太清楚的小伙伴先去研究一下数据类型。
当浮点数除以 0 的时候,结果为 Infinity 或者 NaN。
System.out.println(1.0 / 0.0); // Infinity System.out.println(0.0 / 0.0); // NaN
Infinity 的中文意思是无穷大,NaN 的中文意思是这不是一个数字(Not a Number)。
当整数除以 0 的时候(10 / 0),会抛出异常:
Exception in thread "main" java.lang.ArithmeticException: / by zero at com.itwanger.eleven.ArithmeticOperator.main(ArithmeticOperator.java:32)
通常,我们在进行整数的除法运算时,需要先判断除数是否为 0,以免程序抛出异常。
第五题,Java 支持多重继承吗?
小李之所以没答对这道题,是因为他知道,通过接口可以达到多重继承的目的。
来定义两个接口,Fly 会飞,Run 会跑。
public interface Fly { void fly(); } public interface Run { void run(); }
然后让一个类同时实现这两个接口。
public class Pig implements Fly,Run{ @Override public void fly() { System.out.println("会飞的猪"); } @Override public void run() { System.out.println("会跑的猪"); } }
但说到多重继承,讨论的关键字是 extends,而非 implements。
Java 只支持单一继承,是因为涉及到菱形问题。如果有两个类共同继承一个有特定方法的父类,那么该方法可能会被两个子类重写。然后,如果你决定同时继承这两个子类,那么在你调用该重写方法时,编译器不能识别你要调用哪个子类的方法。
类 C 同时继承了类 A 和类 B,类 C 的对象在调用类 A 和类 B 中重写的方法时,就不知道该调用类 A 的方法,还是类 B 的方法。
总结:
小李之所以没答对这道题,是因为他觉得这道题太简单了,结果说反了,大意了啊,好好说了小李遇到的十大java面试题不是他的基础不牢而是他缺少这方面的经验,我们结合他给我们的反馈,年后是跳槽的高峰期,有打算的小伙伴要提前准备了,希望大家都能够顺利面上心仪的岗位。