有以下代码片段:java
try{ throw new ExampleB("b") }catch(ExampleA e){ System.out.println("ExampleA"); }catch(Exception e){ System.out.println("Exception"); }
请问执行此段代码的输出是什么?mysql
答:输出: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!"); } } }
上面这段代码输出是什么?应该是Caught Annoyance, Caught Sneeze, Hello World!面试
答:List、Set 是,Map 不是。Map是键值对映射容器,与List和Set有明显的区别,而Set存储的零散的元素且不容许有重复元素(数学中的集合也是如此),List是线性结构的容器,适用于按数值索引访问元素的情形。算法
答:ArrayList 和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增长和插入元素,它们都容许直接按序号索引元素,可是插入元素要涉及数组元素移动等内存操做,因此索引数据快而插入数据慢,Vector因为使用了synchronized 方法(线程安全),一般性能上较ArrayList 差,而LinkedList 使用双向链表实现存储(将内存中零散的内存单元经过附加的引用关联起来,造成一个能够按序号索引的线性结构,这种链式存储方式与数组的连续存储方式相比,其实对内存的利用率更高),按序号索引数据须要进行前向或后向遍历,可是插入数据时只须要记录本项的先后项便可,因此插入速度较快。Vector属于遗留容器(早期的JDK中使用的容器,除此以外Hashtable、Dictionary、BitSet、Stack、Properties都是遗留容器),如今已经不推荐使用,可是因为ArrayList和LinkedListed都是非线程安全的,若是须要多个线程操做同一个容器,那么能够经过工具类Collections中的synchronizedList方法将其转换成线程安全的容器后再使用(这实际上是装潢模式最好的例子,将已有对象传入另外一个类的构造器中建立新的对象来增长新功能)。sql
补充:遗留容器中的Properties类和Stack类在设计上有严重的问题,Properties是一个键和值都是字符串的特殊的键值对映射,在设计上应该是关联一个Hashtable并将其两个泛型参数设置为String类型,可是Java API中的Properties直接继承了Hashtable,这很明显是对继承的滥用。这里复用代码的方式应该是HAS-A关系而不是IS-A关系,另外一方面容器都属于工具类,继承工具类自己就是一个错误的作法,使用工具类最好的方式是HAS-A关系(关联)或USE-A关系(依赖)。同理,Stack类继承Vector也是不正确的。数据库
答:Collection 是一个接口,它是Set、List等容器的父接口;Collections 是个一个工具类,提供了一系列的静态方法来辅助容器操做,这些方法包括对容器的搜索、排序、线程安全化等等。编程
答:List以特定索引来存取元素,可有重复元素。Set不能存放重复元素(用对象的equals()方法来区分元素是否重复)。Map保存键值对(key-value pair)映射,映射关系能够是一对一或多对一。Set和Map容器都有基于哈希存储和排序树的两种实现版本,基于哈希存储的版本理论存取时间复杂度为O(1),而基于排序树版本的实如今插入或删除元素时会按照元素或元素的键(key)构成排序树从而达到排序和去重的效果。设计模式
答:TreeSet要求存放的对象所属的类必须实现Comparable接口,该接口提供了比较元素的compareTo()方法,当插入元素时会回调该方法比较元素的大小。TreeMap要求存放的键值对映射的键必须实现Comparable接口从而根据键对元素进行排序。Collections工具类的sort方法有两种重载的形式,第一种要求传入的待排序容器中存放的对象比较实现Comparable接口以实现元素的比较;第二种不强制性的要求容器中的元素必须可比较,可是要求传入第二个参数,参数是Comparator接口的子类型(须要重写compare方法实现元素的比较),至关于一个临时定义的排序规则,其实就是是经过接口注入比较元素大小的算法,也是对回调模式的应用。数组
public class Student implements Comparable<Student> { private String name; // 姓名 private int age; // 年龄 public Student(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } @Override public int compareTo(Student o) { return this.age - o.age; // 比较年龄(年龄的升序) } }
import java.util.Set; import java.util.TreeSet; class Test01 { public static void main(String[] args) { Set<Student> set = new TreeSet<>(); // Java 7的钻石语法(构造器后面的尖括号中不须要写类型) set.add(new Student("Hao LUO", 33)); set.add(new Student("XJ WANG", 32)); set.add(new Student("Bruce LEE", 60)); set.add(new Student("Bob YANG", 22)); for(Student stu : set) { System.out.println(stu); } // 输出结果: // Student [name=Bob YANG, age=22] // Student [name=XJ WANG, age=32] // Student [name=Hao LUO, age=33] // Student [name=Bruce LEE, age=60] } }
public class Student { private String name; // 姓名 private int age; // 年龄 public Student(String name, int age) { this.name = name; this.age = age; } /** * 获取学生姓名 */ public String getName() { return name; } /** * 获取学生年龄 */ public int getAge() { return age; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } }
import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; class Test02 { public static void main(String[] args) { List<Student> list = new ArrayList<>(); // Java 7的钻石语法(构造器后面的尖括号中不须要写类型) list.add(new Student("Hao LUO", 33)); list.add(new Student("XJ WANG", 32)); list.add(new Student("Bruce LEE", 60)); list.add(new Student("Bob YANG", 22)); // 经过sort方法的第二个参数传入一个Comparator接口对象 // 至关因而传入一个比较对象大小的算法到sort方法中 // 因为Java中没有函数指针、仿函数、委托这样的概念 // 所以要将一个算法传入一个方法中惟一的选择就是经过接口回调 Collections.sort(list, new Comparator<Student> () { @Override public int compare(Student o1, Student o2) { return o1.getName().compareTo(o2.getName()); // 比较学生姓名 } }); for(Student stu : list) { System.out.println(stu); } // 输出结果: // Student [name=Bob YANG, age=22] // Student [name=Bruce LEE, age=60] // Student [name=Hao LUO, age=33] // Student [name=XJ WANG, age=32] } }
答:sleep()方法是线程类(Thread)的静态方法,致使此线程暂停执行指定时间,将执行机会给其余线程,可是监控状态依然保持,到时后会自动恢复(线程回到就绪(ready)状态),由于调用sleep不会释放对象锁。wait()是Object 类的方法,对此对象调用wait()方法致使本线程放弃对象锁(线程暂停执行),进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备得到对象锁进入就绪状态。
补充:这里彷佛漏掉了一个做为先决条件的问题,就是什么是进程,什么是线程?为何须要多线程编程?答案以下所示:
进程是具备必定独立功能的程序关于某个数据集合上的一次运行活动,是操做系统进行资源分配和调度的一个独立单位;线程是进程的一个实体,是CPU调度和分派的基本单位,是比进程更小的能独立运行的基本单位。线程的划分尺度小于进程,这使得多线程程序的并发性高;进程在执行时一般拥有独立的内存单元,而线程之间能够共享内存。使用多线程的编程一般可以带来更好的性能和用户体验,可是多线程的程序对于其余程序是不友好的,由于它占用了更多的CPU资源。
答:
① sleep()方法给其余线程运行机会时不考虑线程的优先级,所以会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
② 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;
③ sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
④ sleep()方法比yield()方法(跟操做系统相关)具备更好的可移植性。
答:不能。其它线程只能访问该对象的非同步方法,同步方法则不能进入。
答:
下面的例子演示了100个线程同时向一个银行帐户中存入1元钱,在没有使用同步机制和使用同步机制状况下的执行状况。
public class Account { private double balance; // 帐户余额 /** * 存款 * @param money 存入金额 */ public void deposit(double money) { double newBalance = balance + money; try { Thread.sleep(10); // 模拟此业务须要一段处理时间 } catch(InterruptedException ex) { ex.printStackTrace(); } balance = newBalance; } /** * 得到帐户余额 */ public double getBalance() { return balance; } }
public class AddMoneyThread implements Runnable { private Account account; // 存入帐户 private double money; // 存入金额 public AddMoneyThread(Account account, double money) { this.account = account; this.money = money; } @Override public void run() { account.deposit(money); } }
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test01 { public static void main(String[] args) { Account account = new Account(); ExecutorService service = Executors.newFixedThreadPool(100); for(int i = 1; i <= 100; i++) { service.execute(new AddMoneyThread(account, 1)); } service.shutdown(); while(!service.isTerminated()) {} System.out.println("帐户余额: " + account.getBalance()); } }
在没有同步的状况下,执行结果一般是显示帐户余额在10元如下,出现这种情况的缘由是,当一个线程A试图存入1元的时候,另一个线程B也可以进入存款的方法中,线程B读取到的帐户余额仍然是线程A存入1元钱以前的帐户余额,所以也是在原来的余额0上面作了加1元的操做,同理线程C也会作相似的事情,因此最后100个线程执行结束时,原本指望帐户余额为100元,但实际获得的一般在10元如下。解决这个问题的办法就是同步,当一个线程对银行帐户存钱时,须要将此帐户锁定,待其操做完成后才容许其余的线程进行操做,代码有以下几种调整方案:
1. 在银行帐户的存款(deposit)方法上同步(synchronized)关键字
public class Account { private double balance; // 帐户余额 /** * 存款 * @param money 存入金额 */ public synchronized void deposit(double money) { double newBalance = balance + money; try { Thread.sleep(10); // 模拟此业务须要一段处理时间 } catch(InterruptedException ex) { ex.printStackTrace(); } balance = newBalance; } /** * 得到帐户余额 */ public double getBalance() { return balance; } }
2. 在线程调用存款方法时对银行帐户进行同步
public class AddMoneyThread implements Runnable { private Account account; // 存入帐户 private double money; // 存入金额 public AddMoneyThread(Account account, double money) { this.account = account; this.money = money; } @Override public void run() { synchronized (account) { account.deposit(money); } } }
3.经过JDK 1.5显示的锁机制,为每一个银行帐户建立一个锁对象,在存款操做进行加锁和解锁的操
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Account { private Lock accountLock = new ReentrantLock(); private double balance; // 帐户余额 public void deposit(double money) { accountLock.lock(); try { double newBalance = balance + money; try { Thread.sleep(10); // 模拟此业务须要一段处理时间 } catch (InterruptedException ex) { ex.printStackTrace(); } balance = newBalance; } finally { accountLock.unlock(); } } /** * 得到帐户余额 */ public double getBalance() { return balance; } }
答:Java 5之前实现多线程有两种实现方法:一种是继承Thread类;另外一种是实现Runnable接口。两种方式都要经过重写run()方法来定义线程的行为,推荐使用后者,由于Java中的继承是单继承,一个类有一个父类,若是继承了Thread类就没法再继承其余类了,显然使用Runnable接口更为灵活。
补充:Java 5之后建立线程还有第三种方式:实现Callable接口,该接口中的call方法能够在线程执行结束时产生一个返回值,代码以下所示:
import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; class MyTask implements Callable<Integer> { private int upperBounds; public MyTask(int upperBounds) { this.upperBounds = upperBounds; } @Override public Integer call() throws Exception { int sum = 0; for(int i = 1; i <= upperBounds; i++) { sum += i; } return sum; } } public class Test { public static void main(String[] args) throws Exception { List<Future<Integer>> list = new ArrayList<>();//callable的返回值要用future来接收 ExecutorService service = Executors.newFixedThreadPool(10); for(int i = 0; i < 10; i++) { //采用service注册的方式将callable实现类注册到future里面,静候结果便可。 list.add(service.submit(new MyTask((int) (Math.random() * 100)))); } int sum = 0; for(Future<Integer> future : list) { while(!future.isDone()) ; sum += future.get(); } System.out.println(sum); } }
答:synchronized关键字能够将对象或者方法标记为同步,以实现对对象和方法的互斥访问,能够用synchronized(对象) { … }定义同步代码块,或者在声明方法时将synchronized做为方法的修饰符。在第60题的例子中已经展现了synchronized关键字的用法。
答:若是系统中存在临界资源(资源数量少于竞争资源的线程数量的资源),例如正在写的数据之后可能被另外一个线程读到,或者正在读的数据可能已经被另外一个线程写过了,那么这些数据就必须进行同步存取(数据库操做中的悲观锁就是最好的例子)。当应用程序在对象上调用了一个须要花费很长时间来执行的方法,而且不但愿让程序等待方法的返回时,就应该使用异步编程,在不少状况下采用异步途径每每更有效率。事实上,所谓的同步就是指阻塞式操做,而异步就是非阻塞式操做。
答:启动一个线程是调用start()方法,使线程所表明的虚拟处理机处于可运行状态,这意味着它能够由JVM 调度并执行,这并不意味着线程就会当即运行。run()方法是线程启动后要进行回调(callback)的方法。
答:在面向对象编程中,建立和销毁对象是很费时间的,由于建立一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每个对象,以便可以在对象销毁后进行垃圾回收。因此提升服务程序效率的一个手段就是尽量减小建立和销毁对象的次数,特别是一些很耗资源的对象建立和销毁,这就是"池化资源"技术产生的缘由。线程池顾名思义就是事先建立若干个可执行的线程放入一个池(容器)中,须要的时候从池中获取线程不用自行建立,使用完毕不须要销毁线程而是放回池中,从而减小建立和销毁线程对象的开销。
Java 5+中的Executor接口定义一个执行线程的工具。它的子类型即线程池接口是ExecutorService。要配置一个线程池是比较复杂的,尤为是对于线程池的原理不是很清楚的状况下,所以在工具类Executors面提供了一些静态工厂方法,生成一些经常使用的线程池,以下所示:
第60题的例子中有经过Executors工具类建立线程池并使用线程池执行线程的代码。若是但愿在服务器上使用线程池,强烈建议使用newFixedThreadPool方法来建立线程池,这样能得到更好的性能。
6六、线程的基本状态以及状态之间的关系?
除去起始(new)状态和结束(finished)状态,线程有三种状态,分别是:就绪(ready)、运行(running)和阻塞(blocked)。其中就绪状态表明线程具有了运行的全部条件,只等待CPU调度(万事俱备,只欠东风);处于运行状态的线程可能由于CPU调度(时间片用完了)的缘由回到就绪状态,也有可能由于调用了线程的yield方法回到就绪状态,此时线程不会释放它占有的资源的锁,坐等CPU以继续执行;运行状态的线程可能由于I/O中断、线程休眠、调用了对象的wait方法而进入阻塞状态(有的地方也称之为等待状态);而进入阻塞状态的线程会由于休眠结束、调用了对象的notify方法或notifyAll方法或其余线程执行结束而进入就绪状态。注意:调用wait方法会让线程进入等待池中等待被唤醒,notify方法或notifyAll方法会让等待锁中的线程从等待池进入等锁池,在没有获得对象的锁以前,线程仍然没法得到CPU的调度和执行。
答:Lock是Java 5之后引入的新的API,和关键字synchronized相比主要相同点:Lock 能完成synchronized所实现的全部功能;主要不一样点:Lock 有比synchronized 更精确的线程语义和更好的性能。synchronized 会自动释放锁,而Lock 必定要求程序员手工释放,而且必须在finally 块中释放(这是释放外部资源的最好的地方)。
6八、Java中如何实现序列化,有什么意义?
答:序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。能够对流化后的对象进行读写操做,也可将流化后的对象传输于网络之间。序列化是为了解决对象流读写操做时可能引起的问题(若是不进行序列化可能会存在数据乱序的问题)。
要实现序列化,须要让一个类实现Serializable接口,该接口是一个标识性接口,标注该类对象是可被序列化的,而后使用一个输出流来构造一个对象输出流并经过writeObject(Object obj)方法就能够将实现对象写出(即保存其状态);若是须要反序列化则能够用一个输入流创建对象输入流,而后经过readObject方法从流中读取对象。序列化除了可以实现对象的持久化以外,还可以用于对象的深度克隆(参见Java面试题集1-29题)
答:字节流,字符流。字节流继承于InputStream、OutputStream,字符流继承于Reader、Writer。在java.io 包中还有许多其余的流,主要是为了提升性能和使用方便。
补充:关于Java的IO须要注意的有两点:一是两种对称性(输入和输出的对称性,字节和字符的对称性);二是两种设计模式(适配器模式和装潢模式)。另外Java中的流不一样于C#的是它只有一个维度一个方向。
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class MyUtil { private MyUtil() { throw new AssertionError(); } public static void fileCopy(String source, String target) throws IOException { try (InputStream in = new FileInputStream(source)) { try (OutputStream out = new FileOutputStream(target)) { byte[] buffer = new byte[4096]; int bytesToRead; while((bytesToRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesToRead); } } } } public static void fileCopyNIO(String source, String target) throws IOException { try (FileInputStream in = new FileInputStream(source)) { try (FileOutputStream out = new FileOutputStream(target)) { FileChannel inChannel = in.getChannel(); FileChannel outChannel = out.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(4096); while(inChannel.read(buffer) != -1) { buffer.flip(); outChannel.write(buffer); buffer.clear(); } } } } }