当前位置 博文首页 > 沉默王二:太过伤心,小王被这 10 道 Java 面试题虐哭了

    沉默王二:太过伤心,小王被这 10 道 Java 面试题虐哭了

    作者:[db:作者] 时间:2021-07-05 16:18

    有一天,小王告诉我,他去一家公司面试 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 的方法。

    第六题,当在 HashMap 中放入一个已经存在的 key 时,会发生什么?

    小王之所以没答对这道题,是因为他没有深入研究过 HashMap 的工作原理。

    Hash,一般译作“散列”,也有直接音译为“哈希”的,这玩意什么意思呢?就是把任意长度的数据通过一种算法映射到固定长度的域上(散列值)。

    再直观一点,就是对一串数据 wang 进行杂糅,输出另外一段固定长度的数据 er——作为数据 wang 的特征。我们通常用一串指纹来映射某一个人,别小瞧手指头那么大点的指纹,在你所处的范围内很难找出第二个和你相同的(人的散列算法也好厉害,有没有)。

    对于任意两个不同的数据块,其散列值相同的可能性极小,也就是说,对于一个给定的数据块,找到和它散列值相同的数据块极为困难。再者,对于一个数据块,哪怕只改动它的一个比特位,其散列值的改动也会非常的大——这正是 Hash 存在的价值!

    大家应该知道,HashMap 的底层数据结构是一个数组,通过 hash() 方法来确定下标。

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    

    当我们放入一个键值对的时候,会先调用 hash() 方法对 key 进行哈希算法,如果 key 是相同的,那么哈希后的结果也是相同的,意味着数组中的下标是相同的,新放入的值就会覆盖原来的值。

    第七题,下面这段代码将会打印出什么?

    public class Test {
        public static void main(String[] args) {
            char[] chars = new char[]{'\u0097'