Java⾼级⾯试题整理(附答案)
这是我收集的10道⾼级Java⾯试问题列表。这些问题主要来⾃ Java 核⼼部分 ,不涉及 Java EE 相关问题。你可能知道这些棘⼿的 Java 问题的答案,或者觉得这些不⾜以挑战你的 Java 知识,但这些问题都是容易在各种 Java ⾯试中被问到的,⽽且包括我的朋友和同事在内的许多程序员都觉得很难回答。
rocky linux我还收集整理了⼀份PDF版的Java⾯试⼿册,超详细!关注博主的:Java团长,然后回复“⾯试⼿册”即可获取~
1. 为什么等待和通知是在 Object 类⽽不是 Thread 中声明的?
⼀个棘⼿的 Java 问题,如果 Java编程语⾔不是你设计的,你怎么能回答这个问题呢。Java编程的常识和深⼊了解有助于回答这种棘⼿的Java 核⼼⽅⾯的⾯试问题。nexus3仓库管理 有什么用
为什么 wait,notify 和 notifyAll 是在 Object 类中定义的⽽不是在 Thread 类中定义
这是有名的 Java ⾯试问题,招2~4年经验的到⾼级 Java 开发⼈员⾯试都可能碰到。
这个问题的好在它能反映了⾯试者对等待通知机制的了解, 以及他对此主题的理解是否明确。就像为什么 Java 中不⽀持多继承或者为什么String 在 Java 中是 final 的问题⼀样,这个问题也可能有多个答案。
为什么在 Object 类中定义 wait 和 notify ⽅法,每个⼈都能说出⼀些理由。从我的⾯试经验来看, wait 和 nofity 仍然是⼤多数Java 程序员最困惑的,特别是2到3年的开发⼈员,如果他们要求使⽤ wait 和 notify, 他们会很困惑。因此,如果你去参加 Java ⾯试,请确保对 wait 和notify 机制有充分的了解,并且可以轻松地使⽤ wait 来编写代码,并通过⽣产者-消费者问题或实现阻塞队列等了解通知的机制。
为什么等待和通知需要从同步块或⽅法中调⽤, 以及 Java 中的 wait,sleep 和 yield ⽅法之间的差异,如果你还没有读过,你会觉得有趣。为何 wait,notify 和 notifyAll 属于 Object 类? 为什么它们不应该在 Thread 类中? 以下是我认为有意义的⼀些想法:
1) wait 和 notify 不仅仅是普通⽅法或同步⼯具,更重要的是它们是 Java 中两个线程之间的通信机制。对语⾔设计者⽽⾔, 如果不能通过Java 关键字(例如 synchronized)实现通信此机制,同时⼜要确保这个机制对每个对象可⽤, 那么 Object 类则是的正确声明位置。记住同步和等待通知是两个不同的领域,不要把它们看成是相同的或相关的。同步是提供互斥并确保 Java 类的线程安全,⽽ wait 和 notify 是两个线程之间的通信机制。
2) 每个对象都可上锁,这是在 Object 类⽽不是 Thread 类中声明 wait 和 notify 的另⼀个原因。
3) 在 Java 中为了进⼊代码的临界区,线程需要锁定并等待锁定,他们不知道哪些线程持有锁,⽽只是知道锁被某个线程持有,并且他们应该等待取得锁, ⽽不是去了解哪个线程在同步块内,并请求它们
释放锁定。
4) Java 是基于 Hoare 的监视器的思想(。在Java中,所有对象都有⼀个监视器。
线程在监视器上等待,为执⾏等待,我们需要2个参数:
⼀个线程
⼀个监视器(任何对象)
在 Java 设计中,线程不能被指定,它总是运⾏当前代码的线程。但是,我们可以指定监视器(这是我们称之为等待的对象)。这是⼀个很好的设计,因为如果我们可以让任何其他线程在所需的监视器上等待,这将导致“⼊侵”,导致在设计并发程序时会遇到困难。请记住,在 Java 中,所有在另⼀个线程的执⾏中侵⼊的操作都被弃⽤了(例如 stop ⽅法)。
2. 为什么Java中不⽀持多重继承?
我发现这个 Java 核⼼问题很难回答,因为你的答案可能不会让⾯试官满意,在⼤多数情况下,⾯试官正在寻答案中的关键点,如果你提到这些关键点,⾯试官会很⾼兴。在 Java 中回答这种棘⼿问题的关键是准备好相关主题, 以应对后续的各种可能的问题。
这是⾮常经典的问题,与为什么 String 在 Java 中是不可变的很类似; 这两个问题之间的相似之处在于它们主要是由 Java 创作者的设计决策使然。
为什么Java不⽀持多重继承, 可以考虑以下两点:
1)第⼀个原因是围绕钻⽯ 形继承问题产⽣的歧义,考虑⼀个类 A 有 foo() ⽅法, 然后 B 和 C 派⽣⾃ A, 并且有⾃⼰的 foo() 实现,现在 D 类使⽤多个继承派⽣⾃ B 和C,如果我们只引⽤ foo(), 编译器将⽆法决定它应该调⽤哪个 foo()。这也称为 Diamond 问题,因为这个继承⽅案的结构类似于菱形,见下图:
A foo()
/ \
/ \
foo() B C foo()
\ /
\ /
D foo()
即使我们删除钻⽯的顶部 A 类并允许多重继承,我们也将看到这个问题含糊性的⼀⾯。如果你把这个理由告诉⾯试官,他会问为什么 C++
可以⽀持多重继承⽽ Java不⾏。嗯,在这种情况下,我会试着向他解释我下⾯给出的第⼆个原因,它不是因为技术难度, ⽽是更多的可维护和更清晰的设计是驱动因素, 虽然这只能由 Java ⾔语设计师确认,我们只是推测。链接有⼀些很好的解释,说明在使⽤多重继承时,由于钻⽯问题,不同的语⾔地址问题是如何产⽣的。
2)对我来说第⼆个也是更有说服⼒的理由是,多重继承确实使设计复杂化并在转换、构造函数链接等过程中产⽣问题。假设你需要多重继承的情况并不多,简单起见,明智的决定是省略它。此外,Java 可以通过使⽤接⼝⽀持单继承来避免这种歧义。由于接⼝只有⽅法声明⽽且没有提供任何实现,因此只有⼀个特定⽅法的实现,因此不会有任何歧义。
3. 为什么Java不⽀持运算符重载?
另⼀个类似棘⼿的Java问题。为什么 C++ ⽀持运算符重载⽽ Java 不⽀持? 有⼈可能会说+运算符在 Java 中已被重载⽤于字符串连接,不要被这些论据所欺骗。
与 C++ 不同,Java 不⽀持运算符重载。Java 不能为程序员提供⾃由的标准算术运算符重载,例如+,-,*和/等。如果你以前⽤过 C++,那么 Java 与 C++ 相⽐少了很多功能,例如 Java 不⽀持多重继承,Java中没有指针,Java中没有引⽤传递。另⼀个类似的问题是关于 Java 通过引⽤传递,这主要表现为 Java 是通过值还是引⽤传参。虽然我不知道背后的真正原因,但我认为以下说法有些道理,为什么 Java 不⽀持运算符重载。
1)简单性和清晰性。清晰性是Java设计者的⽬标之⼀。设计者不是只想复制语⾔,⽽是希望拥有⼀种清晰,真正⾯向对象的语⾔。添加运算符重载⽐没有它肯定会使设计更复杂,并且它可能导致更复杂的编译器, 或减慢 JVM,因为它需要做额外的⼯作来识别运算符的实际含义,并减少优化的机会, 以保证 Java 中运算符的⾏为。
2)避免编程错误。Java 不允许⽤户定义的运算符重载,因为如果允许程序员进⾏运算符重载,将为同⼀运算符赋予多种含义,这将使任何开发⼈员的学习曲线变得陡峭,事情变得更加混乱。据观察,当语⾔⽀持运算符重载时,编程错误会增加,从⽽增加了开发和交付时间。由于Java 和 JVM 已经承担了⼤多数开发⼈员的责任,如在通过提供垃圾收集器进⾏内存管理时,因为这个功能增加污染代码的机会, 成为编程错误之源, 因此没有多⼤意义。
3)JVM复杂性。从JVM的⾓度来看,⽀持运算符重载使问题变得更加困难。通过更直观,更⼲净的⽅
式使⽤⽅法重载也能实现同样的事情,因此不⽀持 Java 中的运算符重载是有意义的。与相对简单的 JVM 相⽐,复杂的 JVM 可能导致 JVM 更慢,并为保证在 Java 中运算符⾏为的确定性从⽽减少了优化代码的机会。
4)让开发⼯具处理更容易。这是在 Java 中不⽀持运算符重载的另⼀个好处。省略运算符重载使语⾔更容易处理,这反过来⼜更容易开发处理语⾔的⼯具,例如 IDE 或重构⼯具。Java 中的重构⼯具远胜于 C++。
4. 为什么 String 在 Java 中是不可变的?
我最喜欢的 Java ⾯试问题,很棘⼿,但同时也⾮常有⽤。⼀些⾯试者也常问这个问题,为什么 String 在 Java 中是 final 的。
字符串在 Java 中是不可变的,因为 String 对象缓存在 String 池中。由于缓存的字符串在多个客户之间共享,因此始终存在风险,其中⼀个客户的操作会影响所有其他客户。例如,如果⼀段代码将 String “Test” 的值更改为 “TEST”,则所有其他客户也将看到该值。由于 String 对象的缓存性能是很重要的⼀⽅⾯,因此通过使 String 类不可变来避免这种风险。
同时,String 是 final 的,因此没有⼈可以通过扩展和覆盖⾏为来破坏 String 类的不变性、缓存、散列值的计算等。String 类不可变的另⼀个原因可能是由于 HashMap。
由于把字符串作为 HashMap 键很受欢迎。对于键值来说,重要的是它们是不可变的,以便⽤它们检索存储在 HashMap 中的值对象。由于HashMap 的⼯作原理是散列,因此需要具有相同的值才能正常运⾏。如果在插⼊后修改了 String 的内容,可变的 String将在插⼊和检索时⽣成两个不同的哈希码,可能会丢失 Map 中的值对象。
如果你是印度板球迷,你可能能够与我的下⼀句话联系起来。字符串是Java的 VVS Laxman,即⾮常特殊的类。我还没有看到⼀个没有使⽤String 编写的 Java 程序。这就是为什么对 String 的充分理解对于 Java 开发⼈员来说⾮常重要。
String 作为数据类型,传输对象和中间⼈⾓⾊的重要性和流⾏性也使这个问题在 Java ⾯试中很常见。
为什么 String 在 Java 中是不可变的是 Java 中最常被问到的字符串访问问题之⼀,它⾸先讨论了什么是 String,Java 中的 String 如何与 C 和 C++ 中的 String 不同,然后转向在Java中什么是不可变对象,不可变对象有什么好处,为什么要使⽤它们以及应该使⽤哪些场景。这个问题有时也会问:“为什么 String 在 Java 中是 final 的”。在类似的说明中,如果你正在准备Java ⾯试,我建议你看看,这是⾼级和中级Java 程序员的优秀资源。它包含来⾃所有重要 Java 主题的问题,包括多线程,集合,GC,JVM内部以及 Spring和 Hibernate 框架等。
正如我所说,这个问题可能有很多可能的答案,⽽ String 类的唯⼀设计者可以放⼼地回答它。我在 Jo
shua Bloch 的 Effective Java 书中期待⼀些线索,但他也没有提到它。我认为以下⼏点解释了为什么 String 类在 Java 中是不可变的或 final 的:
1)想象字符串池没有使字符串不可变,它根本不可能,因为在字符串池的情况下,⼀个字符串对象/⽂字,例如 “Test” 已被许多参考变量引⽤,因此如果其中任何⼀个更改了值,其他参数将⾃动受到影响,即假设
String A="Test";
String B="Test";
现在字符串 B 调⽤"Test".toUpperCase(), 将同⼀个对象改为“TEST”,所以 A 也是 “TEST”,这不是期望的结果。ssl协议是一种安全通信协议
下图显⽰了如何在堆内存和字符串池中创建字符串。
2)字符串已被⼴泛⽤作许多 Java 类的参数,例如,为了打开⽹络连接,你可以将主机名和端⼝号作为字符串传递,你可以将数据库 URL 作为字符串传递, 以打开数据库连接,你可以通过将⽂件名作为参
数传递给 File I/O 类来打开 Java 中的任何⽂件。如果 String 不是不可变的,这将导致严重的安全威胁,我的意思是有⼈可以访问他有权授权的任何⽂件,然后可以故意或意外地更改⽂件名并获得对该⽂件的访问权限。由于不变性,你⽆需担⼼这种威胁。这个原因也说明了,为什么 String 在 Java 中是最终的,通过使java.lang.String final,Java设计者确保没有⼈覆盖 String 类的任何⾏为。
3)由于 String 是不可变的,它可以安全地共享许多线程,这对于多线程编程⾮常重要. 并且避免了 Java 中的同步问题,不变性也使得String 实例在 Java 中是线程安全的,这意味着你不需要从外部同步 String 操作。关于 String 的另⼀个要点是由截取字符串 SubString 引起的内存泄漏,这不是与线程相关的问题,但也是需要注意的。
4)为什么 String 在 Java 中是不可变的另⼀个原因是允许 String 缓存其哈希码,Java 中的不可变 String 缓存其哈希码,并且不会在每次调⽤String 的 hashcode ⽅法时重新计算,这使得它在 Java 中的 HashMap 中使⽤的 HashMap 键⾮常快。简⽽⾔之,因为 String 是不可变的,所以没有⼈可以在创建后更改其内容,这保证了 String 的 hashCode 在多次调⽤时是相同的。
5)String 不可变的绝对最重要的原因是它被类加载机制使⽤,因此具有深刻和基本的安全考虑。如果 String 是可变的,加载“java.io.Writer”的请求可能已被更改为加载 “mil.vogoon.DiskErasingWriter”. 安全性和字符串池是使字符串不可变的主要原因。顺便说⼀句,上⾯的理由很好回答另⼀个Java⾯试问
题: “为什么String在Java中是最终的”。要想是不可变的,你必须是最终的,这样你的⼦类不会破坏不变性。你怎么看?
documented matlab5. 为什么 char 数组⽐ Java 中的 String 更适合存储密码?
另⼀个基于 String 的棘⼿ Java 问题,相信我只有很少的 Java 程序员可以正确回答这个问题。这是⼀个真正艰难的核⼼Java⾯试问题,并且需要对 String 的扎实知识才能回答这个问题。
这是最近在 Java ⾯试中向我的⼀位朋友询问的问题。他正在接受技术主管职位的⾯试,并且有超过6年的经验。如果你还没有遇到过这种情况,那么字符数组和字符串可以⽤来存储⽂本数据,但是选择⼀个⽽不是另⼀个很难。但正如我的朋友所说,任何与 String 相关的问题都必须对字符串的特殊属性有⼀些线索,⽐如不变性,他⽤它来说服访提问的⼈。在这⾥,我们将探讨为什么你应该使⽤char[]存储密码⽽不
是String的⼀些原因。
字符串:1)由于字符串在 Java 中是不可变的,如果你将密码存储为纯⽂本,它将在内存中可⽤,直到垃圾收集器清除它. 并且为了可重⽤性,会存在 String 在字符串池中, 它很可能会保留在内存中持续很长时间,从⽽构成安全威胁。
由于任何有权访问内存转储的⼈都可以以明⽂形式到密码,这是另⼀个原因,你应该始终使⽤加密密码⽽不是纯⽂本。由于字符串是不可变的,所以不能更改字符串的内容,因为任何更改都会产⽣新的字符串,⽽如果你使⽤char[],你就可以将所有元素设置为空⽩或零。因此,在字符数组中存储密码可以明显降低窃取密码的安全风险。
2)Java 本⾝建议使⽤JPasswordField的getPassword()⽅法,该⽅法返回⼀个char[]和不推荐使⽤的getTex()⽅法,该⽅法以明⽂形式返回密码,由于安全原因。应遵循 Java 团队的建议, 坚持标准⽽不是反对它。
3)使⽤ String 时,总是存在在⽇志⽂件或控制台中打印纯⽂本的风险,但如果使⽤ Array,则不会打印数组的内容⽽是打印其内存位置。虽
然不是⼀个真正的原因,但仍然有道理。
String strPassword =“Unknown”;
char [] charPassword = new char [] {'U','n','k','w','o','n'};
System.out.println(“字符密码:”+ strPassword);
System.out.println(“字符密码:”+ charPassword);
输出
字符串密码:Unknown
字符密码:[C @110b053
我还建议使⽤散列或加密的密码⽽不是纯⽂本,并在验证完成后⽴即从内存中清除它。因此,在Java中,⽤字符数组⽤存储密码⽐字符串是更好的选择。虽然仅使⽤char[]还不够,还你需要擦除内容才能更安全。
6. 如何使⽤双重检查锁定在 Java 中创建线程安全的单例?
艰难的核⼼ Java ⾯试问题.这个 Java 问题也常被问: 什么是线程安全的单例,你怎么创建它。好吧,在Java 5之前的版本, 使⽤双重检查锁定创建单例Singleton时,如果多个线程试图同时创建Singleton实例,则可能有多个Singleton实例被创建。从 Java 5 开始,使⽤ Enum 创建线程安全的Singleton很容易。但如果⾯试官坚持双重检查锁定,那么你必须为他们编写代码。记得使⽤volatile变量。
为什么枚举单例在 Java 中更好
枚举单例是使⽤⼀个实例在 Java 中实现单例模式的新⽅法。虽然Java中的单例模式存在很长时间,
但枚举单例是相对较新的概念,在引⼊Enum作为关键字和功能之后,从Java5开始在实践中。本⽂与之前关于 Singleton 的内容有些相关, 其中讨论了有关Singleton模式的⾯试中的常见问题, 以及 10 个 Java 枚举⽰例, 其中我们看到了如何通⽤枚举可以。这篇⽂章是关于为什么我们应该使⽤Eeame作为Java中的单例,它⽐传统的单例⽅法相⽐有什么好处等等。
Java 枚举和单例模式
Java 中的枚举单例模式是使⽤枚举在 Java 中实现单例模式。单例模式在 Java 中早有应⽤, 但使⽤枚举类型创建单例模式时间却不长. 如果感兴趣, 你可以了解下构建者设计模式和装饰器设计模式。
1) 枚举单例易于书写
sql 语句顺序这是迄今为⽌最⼤的优势,如果你在Java 5之前⼀直在编写单例, 你知道, 即使双检查锁定, 你仍可以有多个实例。虽然这个问题通过 Java 内存模型的改进已经解决了, 从 Java 5 开始的volatile类型变量提供了保证, 但是对于许多初学者来说, 编写起来仍然很棘⼿。与同步双检查锁定相⽐,枚举单例实在是太简单了。如果你不相信, 那就⽐较⼀下下⾯的传统双检查锁定单例和枚举单例的代码:
在 Java 中使⽤枚举的单例
这是我们通常声明枚举的单例的⽅式,它可能包含实例变量和实例⽅法,但为了简单起见,我没有使
⽤任何实例⽅法,只是要注意,如果你使⽤的实例⽅法且该⽅法能改变对象的状态的话, 则需要确保该⽅法的线程安全。默认情况下,创建枚举实例是线程安全的,但 Enum 上的任何其他⽅法是否线程安全都是程序员的责任。
/**
* 使⽤ Java 枚举的单例模式⽰例
*/
public enum EasySingleton{
INSTANCE;
}
你可以通过EasySingleton.INSTANCE来处理它,这⽐在单例上调⽤getInstance()⽅法容易得多。
具有双检查锁定的单例⽰例
下⾯的代码是单例模式中双重检查锁定的⽰例,此处的getInstance()⽅法检查两次,以查看 INSTANC
E 是否为空,这就是为什么它被称为双检查锁定模式,请记住,双检查锁定是代理之前Java 5,但Java5内存模型中易失变量的⼲扰,它应该⼯作完美。
/**
* 单例模式⽰例,双重锁定检查
*/
public class DoubleCheckedLockingSingleton{
private volatile DoubleCheckedLockingSingleton INSTANCE;
private DoubleCheckedLockingSingleton(){}
public DoubleCheckedLockingSingleton getInstance(){
if(INSTANCE == null){
synchronized(DoubleCheckedLockingSingleton.class){
//double checking Singleton instance
if(INSTANCE == null){
INSTANCE = new DoubleCheckedLockingSingleton();
}
}
}
return INSTANCE;
}java常见笔试题
}
你可以调⽤Instance()来获取此单例类的访问权限。
现在,只需查看创建延迟加载的线程安全的 Singleton 所需的代码量。使⽤枚举单例模式, 你可以在⼀⾏中具有该模式, 因为创建枚举实例是线程安全的, 并且由 JVM 进⾏。
⼈们可能会争辩说,有更好的⽅法来编写 Singleton ⽽不是双检查锁定⽅法, 但每种⽅法都有⾃⼰的优点和缺点, 就像我最喜欢在类加载时创建的静态字段 Singleton, 如下⾯所⽰, 但请记住, 这不是⼀个延迟加载单例:
单例模式⽤静态⼯⼚⽅法
这是我最喜欢的在 Java 中影响 Singleton 模式的⽅法之⼀,因为 Singleton 实例是静态的,并且最后⼀个变量在类⾸次加载到内存时初始化,因此实例的创建本质上是线程安全的。
/**
* 单例模式⽰例与静态⼯⼚⽅法
*/
public class Singleton{
//initailzed during class loading
private static final Singleton INSTANCE = new Singleton();
/
/to prevent creating another instance of Singleton
private Singleton(){}
public static Singleton getSingleton(){
return INSTANCE;
}
}
你可以调⽤Singleton()来获取此类的访问权限。
2) 枚举单例⾃⾏处理序列化
传统单例的另⼀个问题是,⼀旦实现可序列化接⼝,它们就不再是 Singleton, 因为 readObject() ⽅法总是返回⼀个新实例, 就像 Java 中的构造函数⼀样。通过使⽤ readResolve() ⽅法, 通过在以下⽰例中替换 Singeton 来避免这种情况:
//readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}
如果 Singleton 类保持内部状态, 这将变得更加复杂, 因为你需要标记为 transient(不被序列化),但使⽤枚举单例, 序列化由 JVM 进⾏。
3) 创建枚举实例是线程安全的
如第 1 点所述,因为 Enum 实例的创建在默认情况下是线程安全的, 你⽆需担⼼是否要做双重检查锁定。
总之, 在保证序列化和线程安全的情况下,使⽤两⾏代码枚举单例模式是在 Java 5 以后的世界中创建 Singleton 的最佳⽅式。你仍然可以使⽤其他流⾏的⽅法, 如你觉得更好, 欢迎讨论。
7. 编写 Java 程序时, 如何在 Java 中创建死锁并修复它?
经典但核⼼Java⾯试问题之⼀。
如果你没有参与过多线程并发 Java 应⽤程序的编码,你可能会失败。
如何避免 Java 线程死锁?
如何避免 Java 中的死锁?是 Java ⾯试的热门问题之⼀, 也是多线程的编程中的重⼝味之⼀, 主要在招⾼级程序员时容易被问到, 且有很多后续问题。尽管问题看起来⾮常基本, 但⼤多数 Java 开发⼈员⼀旦你开始深⼊, 就会陷⼊困境。
⾯试问题总是以“什么是死锁 ?”开始
当两个或多个线程在等待彼此释放所需的资源(锁定)并陷⼊⽆限等待即是死锁。它仅在多任务或多线程的情况下发⽣。
如何检测 Java 中的死锁?
虽然这可以有很多答案, 但我的版本是⾸先我会看看代码, 如果我看到⼀个嵌套的同步块,或从⼀个同步的⽅法调⽤其他同步⽅法, 或试图在不同的对象上获取锁, 如果开发⼈员不是⾮常⼩⼼,就很容易造成死锁。
另⼀种⽅法是在运⾏应⽤程序时实际锁定时到它, 尝试采取线程转储,在 Linux 中,你可以通过kill -
3命令执⾏此操作, 这将打印应⽤程序⽇志⽂件中所有线程的状态, 并且你可以看到哪个线程被锁定在哪个线程对象上。
你可以使⽤⽹站等⼯具分析该线程转储, 这些⼯具允许你上载线程转储并对其进⾏分析。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论