Java面试知识点总结
2016-12-15
转自:Java面试知识点总结
2. 谈一谈”==“与”equals()"的区别。
3. Java中的四种引用及其应用场景是什么?
4. object中定义了哪些方法?
5. hashCode的作用是什么?
6. ArrayList, LinkedList, Vector的区别是什么?
7. String, StringBuilder, StringBuffer的区别是什么?
8. Map, Set, List, Queue、Stack的特点及用法。
9. HashMap和HashTable的区别
10. HashMap的实现原理
11. ConcurrentHashMap的实现原理
12. TreeMap, LinkedHashMap, HashMap的区别是什么?
13. Collection与Collections的区别是什么?
14. 对于“try-catch-finally”,若try语句块中包含“return”语句,finally语句块会执行吗?
15. Java中的异常层次结构
16. Java面向对象的三个特征与含义
17. Override, Overload的含义与区别
18. 接口与抽象类的区别
19. 静态内部类与非静态内部类的区别
20. Java中多态的实现原理
21. 简述Java中创建新线程的两种方法
22. 简述Java中进行线程同步的方法
23. 简述Java中具有哪几种粒度的锁
24. 给出“生产者-消费者”问题的一种解决方案
25. ThreadLocal的设计理念与作用
26. concurrent包的整体架构
27. ArrayBlockingQueue, CountDownLatch类的作用
28. wait(),sleep() 的区别
29. 线程池的用法与优势
30. for-each与常规for循环的效率对比
31. 简述Java IO与NIO的区别
32. 反射的作用与原理
33. Java中的泛型机制
34. Java 7与Java 8的新特性
35. 常见设计模式
36. JNI的基本用法
37. 动态代理的定义、应用场景及原理
38. 注解的基本概念与使用
本篇文章会对面试中常遇到的Java技术点进行全面深入的总结,帮助我们在面试中更加得心应手,不参加面试的同学也能够借此机会梳理一下自己的知识体系,进行查漏补缺(阅读本文需要有一定的Java基础;若您初涉Java,可以通过这些问题建立起对Java初步的印象,待有了一定基础后再后过头来看收获会更大)。本文的问题列表来自于http://www.nowcoder.com/discuss/3043,在此感谢原作者的无私分享:)
1. Java中的原始数据类型都有哪些,它们的大小及对应的封装类是什么?
(1)boolean
boolean数据类型非true即false。这个数据类型表示1 bit的信息,但是它的大小并没有精确定义。
《Java虚拟机规范》中如是说:“虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位”。这样我们可以得出boolean类型单独使用是4个字节,在数组中又是1个字节。那虚拟机为什么要用int来代替boolean呢?为什么不用byte或short,这样不是更节省内存空间吗?实际上,使用int的原因是,对于当下32位的CPU来说,一次进行32位的数据交换更加高效。
综上,我们可以知道:官方文档对boolean类型没有给出精确的定义,《Java虚拟机规范》给出了“单独时使用4个字节,boolean数组时1个字节”的定义,具体还要看虚拟机实现是否按照规范来,所以1个字节、4个字节都是有可能的。这其实是一种时空权衡。
boolean类型的封装类是Boolean。
(2)byte——1 byte——Byte
(3)short——2 bytes——Short
(4)int——4 bytes——Integer
(5)long——8 bytes——Long
(6)float——4 bytes——Float
(7)double——8 bytes——Double
(8)char——2 bytes——Character
2. 谈一谈”==“与”equals()"的区别。
- ==操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同
- equals方法是用于比较两个独立对象的内容是否相同,就好比去比较两个人的长相是否相同,它比较的两个对象是独立的。
- 如果一个类没有自己定义equals方法,那么它将继承Object类的equals方法,Object类的equals方法的实现代码如下(和==一样):
boolean equals(Object o){ return this==o;}
3. Java中的四种引用及其应用场景是什么?
-
强引用: 通常我们使用new操作符创建一个对象时所返回的引用即为强引用
-
软引用: 若一个对象只能通过软引用到达,那么这个对象在内存不足时会被回收,可用于图片缓存中,内存不足时系统会自动回收不再使用的Bitmap
-
弱引用: 若一个对象只能通过弱引用到达,那么它就会被回收(即使内存充足),同样可用于图片缓存中,这时候只要Bitmap不再使用就会被回收
-
虚引用: 虚引用是Java中最“弱”的引用,通过它甚至无法获取被引用的对象,它存在的唯一作用就是当它指向的对象回收时,它本身会被加入到引用队列中,这样我们可以知道它指向的对象何时被销毁。
4. object中定义了哪些方法?
clone(), equals(), hashCode(), toString(), notify(), notifyAll(), wait(), finalize(), getClass()
5. hashCode的作用是什么?
请参见散列表的基本原理与实现
6. ArrayList, LinkedList, Vector的区别是什么?
-
ArrayList: 内部采用数组存储元素,支持高效随机访问,支持动态调整大小
-
LinkedList: 内部采用链表来存储元素,支持快速插入/删除元素,但不支持高效地随机访问
-
Vector: 可以看作线程安全版的ArrayList
7. String, StringBuilder, StringBuffer的区别是什么?
-
String: 不可变的字符序列,若要向其中添加新字符需要创建一个新的String对象
-
StringBuilder: 可变字符序列,支持向其中添加新字符(无需创建新对象)
-
StringBuffer: 可以看作线程安全版的StringBuilder
8. Map, Set, List, Queue、Stack的特点及用法。
-
Map<K, V>: Java中存储键值对的数据类型都实现了这个接口,表示“映射表”。支持的两个核心操作是get(Object key)以及put(K key, V value),分别用来获取键对应的值以及向映射表中插入键值对。
-
Set<E>: 实现了这个接口的集合类型中不允许存在重复的元素,代表数学意义上的“集合”。它所支持的核心操作有add(E e), remove(Object o), contains(Object o),分别用于添加元素,删除元素以及判断给定元素是否存在于集中。
-
List<E>: Java中集合框架中的列表类型都实现了这个接口,表示一种有序序列。支持get(int index), add(E e)等操作。
-
Queue<E>: Java集合框架中的队列接口,代表了“先进先出”队列。支持add(E element), remove()等操作。
-
Stack<E>: Java集合框架中表示堆栈的数据类型,堆栈是一种“后进先出”的数据结构。支持push(E item), pop()等操作。
更详细的说明请参考官方文档,对相关数据结构不太熟悉的同学可以参考《算法导论》或其他相关书籍。
9. HashMap和HashTable的区别
-
HashTable是线程安全的,而HashMap不是
-
HashMap中允许存在null键和null值,而HashTable中不允许
更加详细的分析请参考深入解析HashMap、HashTable
10. HashMap的实现原理
简单的说,HashMap的底层实现是“基于拉链法的散列表”。详细分析请参考深入解析HashMap、HashTable
11. ConcurrentHashMap的实现原理
ConcurrentHashMap是支持并发读写的HashMap,它的特点是读取数据时无需加锁,写数据时可以保证加锁粒度尽可能的小。由于其内部采用“分段存储”,只需对要进行写操作的数据所在的“段”进行加锁。关于ConcurrentHashMap底层实现的详细分析请参考Java并发编程:并发容器之ConcurrentHashMap
12. TreeMap, LinkedHashMap, HashMap的区别是什么?
-
HashMap的底层实现是散列表,因此它内部存储的元素是无序的;
-
TreeMap的底层实现是红黑树,所以它内部的元素的有序的。排序的依据是自然序或者是创建TreeMap时所提供的比较器(Comparator)对象。
-
LinkedHashMap能够记住插入元素的顺序。
更加详细的说明请参考HashMap,LinkedMap,TreeMap的区别
13. Collection与Collections的区别是什么?
Collection<E>是Java集合框架中的基本接口;Collections是Java集合框架提供的一个工具类,其中包含了大量用于操作或返回集合的静态方法。
对Java集合框架还不太熟悉的小伙伴请参考Java核心技术点之集合框架
14. 对于“try-catch-finally”,若try语句块中包含“return”语句,finally语句块会执行吗?
答案是会执行。只有两种情况finally块中的语句不会被执行:
-
调用了System.exit()方法;
-
JVM“崩溃”了。
15. Java中的异常层次结构
Java中的异常层次结构如下图所示:
橙色: unchecked; 蓝色: checked
Throwable对象可以分为两组。一组是unchecked异常,异常处理机制往往不用于这组异常,包括:
- Error类通常是指Java的内部错误以及如资源耗尽的错误。当Error(及其衍生类)发生时,我们不能在编程层面上解决Error,所以应该直接退出程序。
- Exception类有特殊的一个衍生类RuntimeException。RuntimeException(及其衍生类)是Java程序自身造成的,也就是说,由于程序员在编程时犯错。RuntimeException完全可以通过修正Java程序避免。比如将一个类型的对象转换成没有继承关系的另一个类型,即ClassCastException。这类异常应该并且可以避免。
剩下的是checked异常。这些类是由编程与环境互动造成程序在运行时出错。比如读取文件时,由于文件本身有错误,发生IOException。再比如网络服务器临时更改URL指向,造成MalformedURLException。文件系统和网络服务器是在Java环境之外的,并不是程序员所能控制的。如果程序员可以预期异常,可以利用异常处理机制来制定应对预案。比如文件出问题时,提醒系统管理员。再比如在网络服务器出现问题时,提醒用户,并等待网络服务器恢复。异常处理机制主要是用于处理这样的异常。
16. Java面向对象的三个特征与含义
三大特征:封装、继承、多态。详细介绍请戳Java面向对象三大特性
17. Override, Overload的含义与区别
-
Override表示“重写”,是子类对父类中同一方法的重新定义
-
Overload表示“重载”,也就是定义一个与已定义方法名称相同但签名不同的新方法
18. 接口与抽象类的区别
接口是一种约定,实现接口的类要遵循这个约定;抽象类本质上是一个类,使用抽象类的代价要比接口大。接口与抽象类的对比如下:
-
抽象类中可以包含属性,方法(包含抽象方法与有着具体实现的方法),常量;接口只能包含常量和方法声明。
-
抽象类中的方法和成员变量可以定义可见性(比如public、private等);而接口中的方法只能为public(缺省为public)。
-
一个子类只能有一个父类(具体类或抽象类);而一个接口可以继承一个多个接口,一个类也可以实现多个接口。
-
子类中实现父类中的抽象方法时,可见性可以大于等于父类中的;而接口实现类中的接口 方法的可见性只能与接口中相同(public)。
19. 静态内部类与非静态内部类的区别
静态内部类不会持有外围类的引用,而非静态内部类会隐式持有外围类的一个引用。
欲进一步了解内部类,请戳Java核心技术点之内部类
20. Java中多态的实现原理
所谓多态,指的就是父类引用指向子类对象,调用方法时会调用子类的实现而不是父类的实现。多态的实现的关键在于“动态绑定”。详细介绍请戳Java动态绑定的内部实现机制
21. 简述Java中创建新线程的两种方法
-
继承Thread类(假设子类为MyThread),并重写run()方法,然后new一个MyThread对象并对其调用start()即可启动新线程。
-
实现Runnable接口(假设实现类为MyRunnable),而后将MyRunnable对象作为参数传入Thread构造器,在得到的Thread对象上调用start()方法即可。
22. 简述Java中进行线程同步的方法
临界区:指不能或不希望被多个线程同时访问的程序片段。
Java中的每个对象都有一个内部锁,这个内部锁也被称为监视器(monitor);每个类内部也有一个锁,用于控制多个线程对其静态成员的并发访问。
ReentrantLock类能够给临界区“上锁”
Lock myLock = new ReentrantLock(); public void add(long value) { myLock.lock(); try { this.count = this.count + value; } finally { myLock.unlock(); } }
当一个线程执行完“myLock.lock()”时,它就获得了一个锁对象,这就相当于它给临界区上了锁,其他线程都无法进来。
ReentrantLock锁是可重入的,这意味着线程可以重复获得已经持有的锁,每个锁对象内部都持有一个计数,每当线程获取依次锁对象,这个计数就加1,释放一次就减1。只有当计数值变为0时,才意味着这个线程释放了锁对象,这时其他线程才可以来获取。
synchronized关键字,若一个实例方法用synchronized关键字修饰,那么这个对象的内部锁会“保护”此方法,我们称此方法为同步方法。
public synchronized void add(int value) { ... }
等价于:
public void add(int value) { this.innerLock.lock(); try { ... } finally { this.innerLock.unlock(); } }
内部锁对象只有一个相关条件。wait方法添加一个线程到这个条件的等待集中;notifyAll / notify方法会唤醒等待集中的线程。
public synchronized void add(int value) { while (this.count <= 5) { wait(); } this.count += value; notifyAll(); }
上面代码中,当count<=5是,通过wait使当前线程停止并释放对象内部锁
synchronized方法可以获得对象的内部锁(前提是还未被其他线程获取),获得对象内部锁的另一种方法就是通过同步阻塞:
public class Counter { private Object lock = new Object(); synchronized (lock) { //临界区 } ... }
Thread类
- sleep方法,这是一个静态方法,作用是让当前线程进入休眠状态(但线程不会释放已获取的锁)。
- join方法,这是一个实例方法,在当前线程中对一个线程对象调用join方法会导致当前线程停止运行,等那个线程运行完毕后再接着运行当前线程。也就是说,把当前线程还没执行的部分“接到”另一个线程后面去,另一个线程运行完毕后,当前线程再接着运行。实际上,join方法内部调用了Object类的实例方法wait。java多线程系列_join方法的使用(5)
- interrupt方法,这是一个实例方法。通过使用这个方法,能够终止那些通过调用可中断方法进入阻塞状态的线程。常见的可中断方法有sleep、wait、join,这些方法的内部实现会时不时的检查当前线程的中断状态,若为true会立刻抛出一个InterruptedException异常
public class MutliThreadDemo { public static void main(String[] args) { MutliThread1 thr1 = new MutliThread1("Thread1"); thr1.start(); while(!thr1.interrupted()) thr1.interrupt(); } } public class MutliThread1 extends Thread { private int ticket=3; public MutliThread1 (String name){ super(name); } @Override public void run() { while(ticket>0){ System.out.println(ticket--+" is saled by "+Thread.currentThread().getName()); try { Thread.sleep(500); } catch (InterruptedException e) { System.out.println("Interruption: "+e.toString()); } } } }
结果如下:
3 is saled by Thread1 Interruption: java.lang.InterruptedException: sleep interrupted 2 is saled by Thread1 Interruption: java.lang.InterruptedException: sleep interrupted 1 is saled by Thread1 Interruption: java.lang.InterruptedException: sleep interrupted
更加详细的介绍请戳Java核心技术点之多线程
23. 简述Java中具有哪几种粒度的锁
Java中可以对类、对象、方法或是代码块上锁。更加详细的介绍请戳Java核心技术点之多线程
24. 给出“生产者-消费者”问题的一种解决方案
使用阻塞队列:
public class BlockingQueueTest {
private int size = 20;
private ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(size);
public static void main(String[] args) {
BlockingQueueTest test = new BlockingQueueTest();
Producer producer = test.new Producer();
Consumer consumer = test.new Consumer();
producer.start();
consumer.start();
}
class Consumer extends Thread{
@Override
public void run() {
while(true){
try {
//从阻塞队列中取出一个元素
queue.take();
System.out.println("队列剩余" + queue.size() + "个元素");
} catch (InterruptedException e) {
}
}
}
}
class Producer extends Thread{
@Override
public void run() {
while (true) {
try {
//向阻塞队列中插入一个元素
queue.put(1);
System.out.println("队列剩余空间:" + (size - queue.size()));
} catch (InterruptedException e) {
}
}
}
}
}
25. ThreadLocal的设计理念与作用
ThreadLocal的作用是提供线程内的局部变量,在多线程环境下访问时能保证各个线程内的ThreadLocal变量各自独立。也就是说,每个线程的ThreadLocal变量是自己专用的,其他线程是访问不到的。ThreadLocal最常用于以下这个场景:多线程环境下存在对非线程安全对象的并发访问,而且该对象不需要在线程间共享,但是我们不想加锁,这时候可以使用ThreadLocal来使得每个线程都持有一个该对象的副本。
关于ThreadLocal的实现原理分析请戳深入剖析ThreadLocal
26. concurrent包的整体架构
27. ArrayBlockingQueue, CountDownLatch类的作用
-
CountDownLatch: 允许线程集等待直到计数器为0。适用场景: 当一个或多个线程需要等待指定数目的事件发生后再继续执行。
-
ArrayBlockingQueue: 一个基于数组实现的阻塞队列,它在构造时需要指定容量。当试图向满队列中添加元素或者从空队列中移除元素时,当前线程会被阻塞。通过阻塞队列,我们可以按以下模式来工作:工作者线程可以周期性的将中间结果放入阻塞队列中,其它线程可以取出中间结果并进行进一步操作。若工作者线程的执行比较慢(还没来得及向队列中插入元素),其他从队列中取元素的线程会等待它(试图从空队列中取元素从而阻塞);若工作者线程执行较快(试图向满队列中插入元素),则它会等待其它线程取出元素再继续执行。
28. wait(),sleep() 的区别
-
wait(): Object类中定义的实例方法。在指定对象上调用wait方法会让当前线程进入等待状态(前提是当前线程持有该对象的monitor),此时当前线程会释放相应对象的monitor,这样一来其它线程便有机会获取这个对象的monitor了。当其它线程获取了这个对象的monitor并进行了所需操作时,便可以调用notify方法唤醒之前进入等待状态的线程。
-
sleep(): Thread类中的静态方法,作用是让当前线程进入休眠状态,以便让其他线程有机会执行。进入休眠状态的线程不会释放它所持有的锁。
29. 线程池的用法与优势
-
优势: 实现对线程的复用,避免了反复创建及销毁线程的开销;使用线程池统一管理线程可以减少并发线程的数目,而线程数过多往往会在线程上下文切换上以及线程同步上浪费过多时间。
-
用法: 我们可以调用ThreadPoolExecutor的某个构造方法来自己创建一个线程池。但通常情况下我们可以使用Executors类提供给我们的静态工厂方法来更方便的创建一个线程池对象。创建了线程池对象后,我们就可以调用submit方法提交任务到线程池中去执行了;线程池使用完毕后我们要记得调用shutdown方法来关闭它。
关于线程池的详细介绍以及实现原理分析请戳深入理解Java之线程池、Java四种线程池的使用
30. for-each与常规for循环的效率对比
关于这个问题我们直接看《Effective Java》给我们做的解答:
for-each能够让代码更加清晰,并且减少了出错的机会。下面的惯用代码适用于集合与数组类型:
for (Element e : elements) { doSomething(e); }
使用for-each循环与常规的for循环相比,并不存在性能损失,即使对数组进行迭代也是如此。实际上,在有些场合下它还能带来微小的性能提升,因为它只计算一次数组索引的上限。
31. 简述Java IO与NIO的区别
-
Java IO是面向流的,这意味着我们需要每次从流中读取一个或多个字节,直到读取完所有字节;NIO是面向缓冲的,也就是说会把数据读取到一个缓冲区中,然后对缓冲区中的数据进行相应处理。
-
Java IO是阻塞IO,而NIO是非阻塞IO。
-
Java NIO中存在一个称为选择器(selector)的东西,它允许你把多个通道(channel)注册到一个选择器上,然后使用一个线程来监视这些通道:若这些通道里有某个准备好可以开始进行读或写操作了,则开始对相应的通道进行读写。而在等待某通道变为可读/写期间,请求对通道进行读写操作的线程可以去干别的事情。
更进一步的说明请戳Java NIO与IO
32. 反射的作用与原理
反射的作用概括地说是运行时获取类的各种定义信息,比如定义了哪些属性与方法。原理是通过类的class对象来获取它的各种信息。
详细介绍请参见Java核心技术点之反射
33. Java中的泛型机制
关于泛型机制的详细介绍请直接戳Java核心技术点之泛型
34. Java 7与Java 8的新特性
这里有两篇总结的非常好的:Java 7的新特性 Java 8的新特性
35. 常见设计模式
所谓“设计模式”,不过是面向对象编程中一些常用的软件设计手法,并且经过实践的检验,这些设计手法在各自的场景下能解决一些需求,因此它们就成为了如今广为流传的”设计模式“。也就是说,正式因为在某些场景下产生了一些棘手的问题,才催生了相应的设计模式。明确了这一点,我们在学习某种设计模式时要充分理解它产生的背景以及它所解决的主要矛盾是什么。
常用的设计模式可以分为以下三大类:
-
创建型模式: 包括工厂模式(又可进一步分为简单工厂模式、工厂方法模式、抽象工厂模式)、建造者模式、单例模式。
-
结构型模式: 包括适配器模式、桥接模式、装饰模式、外观模式、享元模式、代理模式。
-
行为型模式: 包括命令模式、中介者模式、观察者模式、状态模式、策略模式。
关于每个模式具体的介绍请参考图说设计模式
36. JNI的基本用法
关于JNI,这里有篇好文:Android中的JNI
37. 动态代理的定义、应用场景及原理
关于动态代理,请直接参见Java核心技术点之动态代理
38. 注解的基本概念与使用
-
注解可以看作是“增强版的注释”,它可以向编译器、虚拟机说明一些事情。
-
注解是描述Java代码的代码,它能够被编译器解析,注解处理工具在运行时也能够解析注解。注解本身是“被动”的信息,只有主动解析它才有意义。
-
除了向编译器/虚拟机传递信息,我们也可以使用注解来生成一些“模板化”的代码。
关于注解更进一步的分析请参考Java核心技术点之注解
这些就够了吗?当然不够。上面列出了面试中关于Java的常见问题,同时大多也是Java技术体系的核心技术点,通过这些问题而引发出的一系列问题正是为我们指出了完善自身知识体系的一条道路,我们要做的是顺着这条道路坚持走下去:)