51、类 ExampleA 继承 Exception,类 ExampleB 继承 ExampleA。
有如下代码片断:输出是什么?
try {
throw new ExampleB("b")
} catch(ExampleA e){
System.out.println("ExampleA");
} catch(Exception e){
System.out.println("Exception");
}
输出:ExampleA。(根据里氏代换原则[能使用父类型的地方一定能使用子类型], 抓取 ExampleA 类型异常的 catch 块能够抓住 try 块中抛出的 ExampleB 类型的 异常)
class Annoyance extends Exception {
}
class Sneeze extends Annoyance {
}
class Human {
public static void main(String[] args) throws Exception {
try {
try {
throw new Sneeze();
} catch (Annoyance a) {
System.out.println("Caught Annoyance");
throw a;
}
} catch (Sneeze s) {
System.out.println("Caught Sneeze");
return;
} finally {
System.out.println("Hello World!");
}
}
}
52、List、Set、Map 是否继承自 Collection 接口?
List、Set 是,Map 不是。Map 是键值对映射容器,与 List 和 Set 有明显的区别, 而 Set 存储的零散的元素且不允许有重复元素(数学中的集合也是如此),List 是线性结构的容器,适用于按数值索引访问元素的情形。
53、阐述 ArrayList、Vector、LinkedList 的存储性能和特性
ArrayList 和 Vector 都是使用数组方式存储数据,此数组元素数大于实际存储的 数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉 及数组元素移动等内存操作,所以索引数据快而插入数据慢,Vector 中的方法由 于添加了 synchronized 修饰,因此 Vector 是线程安全的容器,但性能上较 ArrayList 差,因此已经是 Java 中的遗留容器。LinkedList 使用双向链表实现存 储(将内存中零散的内存单元通过附加的引用关联起来,形成一个可以按序号索 引的线性结构,这种链式存储方式与数组的连续存储方式相比,内存的利用率更 高),按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本 项的前后项即可,所以插入速度较快。Vector 属于遗留容器(Java 早期的版本中 提供的容器,除此之外,Hashtable、Dictionary、BitSet、Stack、Properties 都是遗留容器),已经不推荐使用,但是由于 ArrayList 和 LinkedListed 都是非 线程安全的,如果遇到多个线程操作同一个容器的场景,则可以通过工具类 Collections 中的 synchronizedList 方法将其转换成线程安全的容器后再使用(这 是对装潢模式的应用,将已有对象传入另一个类的构造器中创建新的对象来增强 实现)。
补充:遗留容器中的 Properties 类和 Stack 类在设计上有严重的问题,Properties 是一个键和值都是字符串的特殊的键值对映射,在设计上应该是关联一个 Hashtable 并将其两个泛型参数设置为 String 类型,但是 Java API 中的 Properties 直接继承了 Hashtable,这很明显是对继承的滥用。这里复用代码的方式应该是 Has-A 关系而不是 Is-A 关系,另一方面容器都属于工具类,继承工具 类本身就是一个错误的做法,使用工具类最好的方式是 Has-A 关系(关联)或 Use-A 关系(依赖)。同理,Stack 类继承 Vector 也是不正确的。Sun 公司的工 程师们也会犯这种低级错误,让人唏嘘不已。
54、Collection 和 Collections 的区别?
Collection 是一个接口,它是 Set、List 等容器的父接口;Collections 是个一个 工具类,提供了一系列的静态方法来辅助容器操作,这些方法包括对容器的搜索、 排序、线程安全化等等。
55、List、Map、Set 三个接口存取元素时,各有什么特点?
List 以特定索引来存取元素,可以有重复元素。Set 不能存放重复元素(用对象的 equals()方法来区分元素是否重复)。Map 保存键值对(key-value pair)映射, 映射关系可以是一对一或多对一。Set 和 Map 容器都有基于哈希存储和排序树的 两种实现版本,基于哈希存储的版本理论存取时间复杂度为 O(1),而基于排序树 版本的实现在插入或删除元素时会按照元素或元素的键(key)构成排序树从而达 到排序和去重的效果。
56、TreeMap 和 TreeSet 在排序时如何比较元素? Collections 工具类中的 sort()方法如何比较元素?
TreeSet 要求存放的对象所属的类必须实现 Comparable 接口,该接口提供了比 较元素的 compareTo()方法,当插入元素时会回调该方法比较元素的大小。 TreeMap 要求存放的键值对映射的键必须实现 Comparable 接口从而根据键对元 素进行排序。Collections 工具类的 sort 方法有两种重载的形式,第一种要求传入 的待排序容器中存放的对象比较实现 Comparable 接口以实现元素的比较;第二 种不强制性的要求容器中的元素必须可比较,但是要求传入第二个参数,参数是 Comparator 接口的子类型(需要重写 compare 方法实现元素的比较),相当于 一个临时定义的排序规则,其实就是通过接口注入比较元素大小的算法,也是对 回调模式的应用(Java 中对函数式编程的支持)。
57、Thread 类的 sleep()方法和对象的 wait()方法都可以让线 程暂停执行,它们有什么区别?
sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程 暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保 持,因此休眠时间结束后会自动恢复(线程回到就绪状态,请参考第 66 题中的线 程状态转换图)。wait()是 Object 类的方法,调用对象的 wait()方法导致当前线 程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用 对象的 notify()方法(或 notifyAll()方法)时才能唤醒等待池中的线程进入等锁池 (lock pool),如果线程重新获得对象的锁就可以进入就绪状态。
补充:可能不少人对什么是进程,什么是线程还比较模糊,对于为什么需要多线 程编程也不是特别理解。简单的说:进程是具有一定独立功能的程序关于某个数 据集合上的一次运行活动,是操作系统进行资源分配和调度的一个独立单位;线 程是进程的一个实体,是 CPU 调度和分派的基本单位,是比进程更小的能独立运 行的基本单位。线程的划分尺度小于进程,这使得多线程程序的并发性高;进程 在执行时通常拥有独立的内存单元,而线程之间可以共享内存。使用多线程的编 程通常能够带来更好的性能和用户体验,但是多线程的程序对于其他程序是不友 好的,因为它可能占用了更多的 CPU 资源。当然,也不是线程越多,程序的性能 就越好,因为线程之间的调度和切换也会浪费 CPU 时间。时下很时髦的 Node.js 就采用了单线程异步 I/O 的工作模式。
58、线程的 sleep()方法和 yield()方法有什么区别?
① sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的 线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的 机会; ② 线程执行 sleep()方法后转入阻塞(blocked)状态,而执行 yield()方法后转 入就绪(ready)状态; ③ sleep()方法声明抛出 InterruptedException,而 yield()方法没有声明任何异 常; ④ sleep()方法比 yield()方法(跟操作系统 CPU 调度相关)具有更好的可移植性。
59、当一个线程进入一个对象的 synchronized 方法 A 之后, 其它线程是否可进入此对象的 synchronized 方法 B?
不能。其它线程只能访问该对象的非同步方法,同步方法则不能进入。因为非静 态方法上的 synchronized 修饰符要求执行方法时要获得对象的锁,如果已经进入 A 方法说明对象锁已经被取走,那么试图进入 B 方法的线程就只能在等锁池(注 意不是等待池哦)中等待对象的锁。
60、请说出与线程同步以及线程调度相关的方法。
wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用 此方法要处理 InterruptedException 异常;
notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并 不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且 与优先级无关;
notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给 所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;