第六题,当在 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'};
String str = new String(chars);
byte[] bytes = str.getBytes();
System.out.println(Arrays.toString(bytes));
}
}
小李之所以没答对这道题,是因为他没有深入研究过字符编码方面的一些知识。
在这段程序中,我们通过一个字符数组创建了一个字符串对象,然后调用 String 类的 getByte()
方法得到字节数组并将其打印到控制台。
这道面试题考察的核心并不是最终的打印结果(结果是不确定的),而是字符编码。通常情况下,我们在调用 getBytes()
方法时,要指定编码,比如说 str.getBytes(StandardCharsets.UTF_8)
。
当我们没有指定编码的时候,JDK 会调用平台默认的字符编码,而不同的操作系统,编码不尽相同的,bytes 的结果也就会不同。
当使用 UTF_8 时,结果为 -62, -105
,当使用 GB2312 时,结果为 63
。
第八题,当方法在父类中抛出 NullPointerException
时,是否可以使用抛出 RuntimeException
的方法来重写它?
小李之所以没答对这道题,是因为他被重写(overriding)和重载(overloading)的概念搞混了。
方法重写和方法重载时,方法名可以完全相同,但根本的不同在于方法重写时发生在运行时,方法重载时发生在编译时。
另外,方法重写和方法重载时的规则也不尽相同。在 Java 中,不能重写 private、static 和 final 方法,但可以重载它们。
我们来重点看一下方法重写时的规则:
1)方法签名必须相同,包括返回类型、参数的数量、参数的类型和参数的顺序。
2)重写后的方法不能抛出比父类中更高级别的异常。举例来说,如果父类中的方法抛出的是 IOException,那么子类中重写的方法不能抛出 Exception,可以是 IOException 的子类或者不抛出任何异常。这条规则只适用于可检查的异常。
可检查(checked)异常必须在源代码中显式地进行捕获处理,不检查(unchecked)异常就是所谓的运行时异常,比如说 NullPointerException、ArrayIndexOutOfBoundsException 之类的,不会在编译器强制要求。
3)重写后的方法访问权限不能比父类中的方法低,比如说父类中的方法是 public,重写后的方法就不能是 protected。
public class ExceptionDemo {
public static void main(String[] args) {
Super s = new Child();
s.write();
}
}
class Super{
public void write() throws NullPointerException { }
}
class Child extends Super {
@Override
public void write() throws RuntimeException { }
}
RuntimeException 和 NullPointerException 属于不检查异常,所以本题的答案是可以的。如果是可检查异常的话,IDE 就会发出警告。
第九题,下面这段代码使用了 compareTo()
方法,有问题吗?
class Employee implements Comparable {
private int id;
@Override
public int compareTo(Object o) {
Employee emp = (Employee) o;
return this.id - emp.id;
}
}
小李之所以没答对这道题,是因为他想当然地认为 id 的都是正整数。
当我们需要按照一定的规则进行排序的时候,通常要实现 Comparable 接口,并实现 compareTo 方法,规则如下:
1)如果当前对象小于另外一个对象,则 compareTo 方法必须返回负数;如果当前对象大于另外一个对象,则必须返回正数;如果两个对象相等,则返回零。
2)通常来说,compareTo 方法必须和 equals 方法一致,如果两个对象通过 equals 方法判断的结果为 true,那么 compareTo 必须返回零。
不过,JDK 中有一个反例,就是 BigDecimal。
BigDecimal bd1 = new BigDecimal("2.0");
BigDecimal bd2 = new BigDecimal("2.00");
System.out.println("equals: " + bd1.equals(bd2));
System.out.println("compareTo: " + bd1.compareTo(bd2));
输出结果如下所示:
equals: false
compareTo: 0
这是因为 JDK 认为 2.0 和 2.00 的精度不一样,所以不能 equals,但值确实是相等的。
3)不能使用减法来比较整数值,因为减法的结果可能溢出。应该使用 Integer.compareTo()
来进行比较。如果你想通过减法操作来提高性能,必须得确保两个操作数是正整数,或者确保两者相差的值小于 Integer.MAX_VALUE。
public class CompareDemo {
public static void main(String[] args) {
List<Employee> list = new ArrayList<>();
list.add(new Employee(1));
list.add(new Employee(Integer.MIN_VALUE));
list.add(new Employee(Integer.MAX_VALUE));
Collections.sort(list);
System.out.println(list);
}
}
class Employee implements Comparable {
private int id;
public Employee(int id) {
this.id = id;
}
@Override
public int compareTo(Object o) {
Employee emp = (Employee) o;
return this.id - emp.id;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
'}';
}
}
程序的输出结果如下所示:
[Employee{id=1}, Employee{id=2147483647}, Employee{id=-2147483648}]
排序就乱了。因为 Integer.MIN_VALUE - 1
变成了正数 2147483647
。
第十题,StringBuffer 和 StringBuilder 之间有什么区别?
小李之所以没答对这道题,是因为他觉得这道题太简单了,结果说反了,大意了啊。
StringBuilder 是 JDK 1.5 之后引入的,它和 StringBuffer 最大的区别就在于它的一系列方法都是非同步的。
总结:
好了,以上就是小李在百度面试遇到的 10 道虐心的面试题,本来最后一道是送分题,结果大意说反了,让小李更加懊恼。年后是跳槽的高峰期,有打算的小伙伴要提前准备了,希望大家都能够顺利面上心仪的岗位
共同学习,写下你的评论
评论加载中...
作者其他优质文章