原文链接:https://dzone.com/articles/var-work-in-progress
作者:Anghel Leonard
译者:沈歌
如果你需要准备面试,可以看一下这篇博客中20个为Java开发人员准备的面试投行的问题。
大量的Java开发人员面试例如巴克莱银行(Barclays)、瑞士信贷集团(Credit Suisse)、花旗银行(Citibank)这样的投行的Java开发岗位,但是大多数人都不知道会被问什么问题。
这篇文章中,我将分享一些对于3年经验以上的程序员会被问的最多的问题。
对于两年及两年以下Java开发经历的人,投行一般不会通过社招招聘,一般只有可能在毕业时候通过校招进去。
实际面试的时候并不保证一定会被问到这些问题,而且实际上,大概率问不到,但是通过这篇文章你能够知道大概会被问什么类型的问题。而且你准备的越充分,面试的时候表现的会越好。
另外,如果这20个问题你觉得不够的话,可以看两篇文章: 和 。
话不多说,进入正题。接下来我们开始看我从朋友和大学同学那里收集到的他们面试投行遇到的问题。
Java程序员投行面试问题
问题1: 多线程环境下使用HashMap
有什么问题,什么时候使用get()
方法会进入死循环?
答:没什么问题,会不会出问题取决于你怎么用。例如,如果你在一个线程内,所有线程只是读取数据,那么没什么问题。例如Map
HashMap
做了改动,例如:增加、更新或者移除任何的键值对。因为put()
操作会引起re-sizing,有可能导致死循环,所以应该使用或者,后面这个更好一些。 问题2:如果你不重写hashCode()
方法,会有什么后果吗?
答:这是一个好问题。根据我得理解,一个差的hashcode方法会导致, 然后导致往hashMap
中添加一个对象的时候耗时增加。
从开始, key碰撞比之前Java版本的Key碰撞对性能影响要小一些,在大于某一个阈值后,会取代,链表最坏情况下O(n)
的性能问题会减少到二叉树的O(logN)
。
问题3:Java里面所有的不变的属性需要设置为final吗?
答:没有必要,你可以实现相同的功能通过以下操作:设为非final的private 变量,且只有在构造函数中才能修改。不设set方法,如果是一个可变对象,不要泄露任何指向这个对象的引用。
只能确保这个变量不会被赋予一个不同的引用,但是你仍然可以改变引用变量的属性值。
这是面试官想要听到的一个点。如果你想要知道更多Java中引用变量的知识,推荐加入Udemy的课程
问题4:String的substring()的实现原理
答:substring取原来string的一部分创建一个新的对象。这个问题主要想问的是开发者是否熟悉substring可能导致的风险。
直到Java1.7, substring 拥有原来的字符数组的引用,这意味着即使是五字符这么小的字符串,也可能会导致一个1GB字符数组无法被垃圾回收因为有一个强引用。
这个问题在Java1.7中已经被修复,原来的字符数组不会被引用,但是会导致创建substring耗时会有点长,以前时间复杂度是O(1)
, Java 7之后时间复杂度是O(n)
。
问题5:写一个单例模式的临界区代码()
答: 这个问题实际上是想让候选人写一个。
记得使用
确保单例这是使用双重校验锁写的线程安全的单例代码:
public class Singleton { private static volatile Singleton _instance; /** * Double checked locking code on Singleton * @return Singelton instance */ public static Singleton getInstance() { if (_instance == null) { synchronized (Singleton.class) { if (_instance == null) { _instance = new Singleton(); } } } return _instance; }}
与此同时,最好能够知道典型的设计模式,比如单例模式、工厂模式、装饰模式等,如果你对这个感兴趣, 这个不错。
问题6:Java中如何处理写存储过程或者读存储过程时遇到的错误?
答: 这是Java面试中最难的问题之一。我的回答是一个存储过程应该在操作错误时返回错误码,但是如果存储过程本身出问题,捕获SQLException
是唯一选择。
中对于Java的异常和捕获有很多好的建议,值得一读。
问题7:Executor.submit()
和Executer.execute()
有什么区别?
答 这个面试问题来自于我的这篇文章。随着对于Java开发人员的并发技能要求的增加,
这个题目越来越受欢迎。答案是,前者返回一个Future
对象,可以用于找到工作线程的运行结果。
在异常处理上也不一样,在任务抛出异常时,如果是通过execute()
提交的,会抛出无需捕获的异常(如果你没有特殊处理,会打印错误栈道System.err)。如果是通过submit()
提交的,任何异常,无论是不是checked exception,都是返回的一部分,Future.get将把异常包在ExecutionExeption
中,向上层抛出。
如果你想学习任何Future, Callable,异步计算和提高并发编程技巧,建议你学习这个课程.
这是一个基于独立编写的 的高级课程。这个课程绝对对得起你付出的钱和时间。并发编程很难而且有很多技巧,书和课程结合起来一起学是不错的方式。
问题8: 工厂模式和抽象工厂模式有什么区别?()
答:抽象工场模式提供一个多层级的抽象。考虑不同的工厂继承自同一个抽象工厂,代表基于工厂的不同对象结构的创建,例如,AutomobileFactory,UserFactory, RoleFactory
等都继承自AbstractFactory
。每一个独立的工厂代表那种类型物体的创造器。
如果你想要学习更多关于抽象工厂设计模式,我建议你看 这个课程,提供了优秀的真实案例帮你更好的理解设计模式。
这里是一个工厂模式和抽象工厂模式的UML图:
如果你想要更多选择,也可以看这个课程:
问题9: 什么是单例?整个方法使用synchronized
和只有临界区使用synchronized
哪个好?()
答:Java中的单例是指在整个Java应用中一个类只有一个实例。例如, java.lang.Runtime
是一个单例类。
创建一个单例在Java 4之前非常难,但是自从Java 5 引入了, 它变得非常容易了。
你可以看我的这篇文章,这里使用了枚举和双重校验锁的方式,这个题主要就是想问这个。
问题10: 你能基于Java 4,Java5里面的HashMap如何迭代取值?
这个问题有点棘手,但是一般是使用while
或者for
循环。Java里面迭代Map有四种方式。一种是使用keySet()
,迭代每一个key的时候使用get()
方法去取value,但是有点慢。第二种方法是使用entrySet()
。然后使用for each
循环或者Iterator.hashNext()
方法来迭代取值即可。 (keySet, entrySet和foreach, Iterator进行组合,所以是4种。)
这个方法比较好,因为在每次迭代时,key
和 value
都已经取出来了,你不需要调用get()
方法去取value,使用get()
方法当你从一个桶里面的大的链表中取数据,时间复杂度是O(n)。
你可以在我的博客 中查看细节和示例代码。
问题11:什么时候重写 hashCode()
和 equals()
方法?()
答:当你需要的时候,尤其是你想要通过业务逻辑校验两个对象是否相等,而不是通过两个对象是否执行同一地址。例如两个员工对象在emp_id
相等的时候相等,即使它们是通过不同的代码创建出来的两个不同对象。
另外,如果你使用一个对象作为HashMap
的key,你必须这两个方法。
作为java equals-hashcode约束的一部分,你当你重写equals的时候,你必须重写hashcode. 否则你不能再Set,Map这样的类里面使用,因为他们一来于equals()
方法来保证逻辑正确性。
你也可以看我的这篇文章看理解重写这两个方法可能导致的问题:
问题12:在重写hashCode()
方法的时候你遇到哪些问题?
如果你不重写equals方法,equals和hashcode中的约束不会生效。根据该约束,两个对象通过equals()
相等,一定有相同的hashcode。
在这种情况下,另一个对象可能返回一个不同的hashCode
并存储在该位置,将破坏的不可变,因为它不支持重复的key。
当你使用put()
方法添加对象时,它迭代之前在map中那个桶位置的所有的Map.Entry
对象,并且更新到新值。如果Map已经包含了那个Key,如果hashCode没有重写,这个机制不会起作用。
如果你想要学习更多关于Java集合(Map, Set)中equals()
和hashCode()
方法的作用,建议你看一下这个课程.
问题13:synchroize getInstance()
方法的临界区和synchronize 整个getInstance()
方法哪个好?()
答: 答案是只synchronize 临界区。因为如果你锁了整个方法,每次调用这个方法,都必须等,即使你并不是在创建对象。
换句话说, 只需要在你创建对象的时候生效。一旦对象被创建,不需要任何同步。实际上,这种方法耗时很少。同步方法耗时是只同步临界区的10到20倍。
这是的UML图:
顺便提一句,有几种方法可以创建线程安全的单例,包括枚举,在这个问题里面我们也能提一下、
如果你想多学点,可以看这个免费课
问题14:在HashMap
的get()
操作中,equals()
方法和hashCode()
方法什么时候起作用?()
这个问题是前面问题的更进一步,候选人需要知道一但你提hashCode
,很有可能被问HashMap
里面的应用。
一但你提供一个key对象,hashcode方法会被调用用来计算桶位置。一个桶包含一个链表,每一个Map.Entry
对象使用equals()
方法来看是否已经存在相同key的value。
强烈推荐你阅读我的博客, 可以帮助你学习这个主题。
问题15: Java中如何避免死锁()
答: 死锁发生是因为两个线程试图获取被对方持有的资源。但是要想发生这种情况,必须满足以下四个条件:
- 相互排斥——至少一个进程必须处于非共享模式
- 保持并等待——必须有一个进程持有一个资源并等待另一个资源
- 没有抢占—— 资源不能被抢占
- 循环等待 —— 存在进程集合
通过中断循环等待可以避免死锁。可以通过在代码中指定获取和释放锁的顺序来达到这一目的。
如果多个锁通过一致的顺序被获取和释放,不会有互相等待对方释放锁的情况。
你可以看我的博客, 查看示例代码和更加详细的解释。 同时推荐 这个课程来更好的理解多线程模式。
问题16:双引号直接创建字符串和使用new()创建字符串有什么区别?
答: 使用new()创建String对象,实例被创建在中, 不会被添加到String池中,当通过 创建时,会被放到堆中的永久区的String池中。
String str = new String("Test")
不会把str放到String池中,我们需要调用String.intern()
方法,会把它放到String池中。
当我们使用String字面量创建String对象时,如通过String s = "Test", java会自动放入String池中。
另外,如果我们把"Test"这样的String字面量传进去,也会创建另外一个对象:"Test" 在
这是我的知识盲区直到读者在我的 中给我提建议,如果想学习更多关于String字面量和String对象的知识,看
问题17:什么是不可变对象?你可以写一个不可变类吗?()
不可变对象是指Java类的对象一单被创建,不能被修改。任何不可变对象对象的修改在创建时候就已经完成,例如,
大多数不可变类是的, 这样可以防止因子类重写方法而导致不可变失效。
你也可以实现相同的功能通过让成员非final但是,且除了构造方法任何其他方法无法修改。
另外,要确保没有暴露不可变对象的内部,尤其是它包含可变成员的时候。
同时,当你从客户端接收到可变的对象时,例如java.util.Date
, 使用 来获取一个独立的拷贝,防止恶意修改可变对象带来的风险。
相同的优化需要在返回一个可变成员时执行。返回另一个独立拷贝给客户端;不要返回可变对象的原始引用。你也可以看我的这篇博客, 这里有按步骤的引导和示例代码。
问题18:不用性能分析工具,给出一个简单的办法找到一个方法运行耗时
答:请求前和请求后记录时间,计算时间差值。如果一个方法耗时太小可能显示0毫秒,那么可以让方法变的足够大,比如重复执行足够多次。算总时间。
问题19:当你使用Object作为HashMap里面的key的时候,哪两个方法需要实现?
为了在hashMap
或者hashtable
中把对象作为key,它必须实现和方法。
你也可以阅读,了解相关实现细节。
问题20:如何防止客户端直接实例化你的具体类?例如你有一个Cache
接口和两个实现类:MemoryCache
和DiskCache
。如何确保没有任何这两个类的对象通过new()关键字被创建出来?
答:在我给答案之前,自己研究一下这个问题。我相信你可以找到正确答案,从代码维护的角度来,控制你的类是非常重要的。
现在你已经准备好Java面试了
这是一些最通用的关于和的问题,他们可以帮忙你准备投行的技术面试。祝你好运!