java十题

这是我收集的10个最棘手的Java面试问题列表。这些问题主要来自 Java 核心部分 ,不涉及 Java EE 相关问题。你可能知道这些棘手的 Java 问题的答案,或者以为这些不足以挑战你的 Java 知识,但这些问题都是容易在各类 Java 面试中被问到的,并且包括个人朋友和同事在内的许多程序员都以为很难回答。javascript

1.为何等待和通知是在 Object 类而不是 Thread 中声明的?

一个棘手的 Java 问题,若是 Java编程语言不是你设计的,你怎么能回答这个问题呢。Java编程的常识和深刻了解有助于回答这种棘手的 Java 核心方面的面试问题。为何 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 设计中,线程不能被指定,它老是运行当前代码的线程。可是,咱们能够指定监视器(这是咱们称之为等待的对象)。这是一个很好的设计,由于若是咱们可让任何其余线程在所需的监视器上等待,这将致使“入侵”,致使在设计并发程序时会遇到困难。请记住,在 Java 中,全部在另外一个线程的执行中侵入的操做都被弃用了(例如 stop 方法)。程序员

2.为何Java中不支持多重继承?

我发现这个 Java 核心问题很难回答,由于你的答案可能不会让面试官满意,在大多数状况下,面试官正在寻找答案中的关键点,若是你提到这些关键点,面试官会很高兴。在 Java 中回答这种棘手问题的关键是准备好相关主题, 以应对后续的各类可能的问题。这是很是经典的问题,与为何 String 在 Java 中是不可变的很相似; 这两个问题之间的类似之处在于它们主要是由 Java 创做者的设计决策使然。为何Java不支持多重继承, 能够考虑如下两点:1)第一个缘由是围绕钻石形继承问题产生的歧义,考虑一个类 A 有 foo() 方法, 而后 B 和 C 派生自 A, 而且有本身的 foo() 实现,如今 D 类使用多个继承派生自 B 和C,若是咱们只引用 foo(), 编译器将没法决定它应该调用哪一个 foo()。这也称为 Diamond 问题,由于这个继承方案的结构相似于菱形,见下图:面试

 

即便咱们删除钻石的顶部 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程序员面试宝典(第4版) 》,这是高级和中级Java程序员的优秀资源。它包含来自全部重要 Java 主题的问题,包括多线程,集合,GC,JVM内部以及 Spring和 Hibernate 框架等。正如我所说,这个问题可能有不少可能的答案,而 String 类的惟一设计者能够放心地回答它。我在 Joshua Bloch 的 Effective Java 书中期待一些线索,但他也没有提到它。我认为如下几点解释了为何 String 类在 Java 中是不可变的或 final 的:1)想象字符串池没有使字符串不可变,它根本不可能,由于在字符串池的状况下,一个字符串对象/文字,例如 “Test” 已被许多参考变量引用,所以若是其中任何一个更改了值,其余参数将自动受到影响,即假设swift

 

如今字符串 B 调用 "Test".toUpperCase(), 将同一个对象改成“TEST”,因此 A 也是 “TEST”,这不是指望的结果。下图显示了如何在堆内存和字符串池中建立字符串。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中是最终的”。要想是不可变的,你必须是最终的,这样你的子类不会破坏不变性。你怎么看?设计模式

5.为何 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,则不会打印数组的内容而是打印其内存位置。虽然不是一个真正的缘由,但仍然有道理。数组

 

输出缓存

 

我还建议使用散列或加密的密码而不是纯文本,并在验证完成后当即从内存中清除它。所以,在Java中,用字符数组用存储密码比字符串是更好的选择。虽然仅使用char[]还不够,还你须要擦除内容才能更安全。

6.如何使用双重检查锁定在 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) 枚举单例易于书写这是迄今为止最大的优点,若是你在Java 5以前一直在编写单例, 你知道, 即便双检查锁定, 你仍能够有多个实例。虽然这个问题经过 Java 内存模型的改进已经解决了, 从 Java 5 开始的 volatile 类型变量提供了保证, 可是对于许多初学者来讲, 编写起来仍然很棘手。与同步双检查锁定相比,枚举单例实在是太简单了。若是你不相信, 那就比较一下下面的传统双检查锁定单例和枚举单例的代码:

在 Java 中使用枚举的单例

这是咱们一般声明枚举的单例的方式,它可能包含实例变量和实例方法,但为了简单起见,我没有使用任何实例方法,只是要注意,若是你使用的实例方法且该方法能改变对象的状态的话, 则须要确保该方法的线程安全。默认状况下,建立枚举实例是线程安全的,但 Enum 上的任何其余方法是否线程安全都是程序员的责任。

 

你能够经过EasySingleton.INSTANCE来处理它,这比在单例上调用getInstance()方法容易得多。

具备双检查锁定的单例示例

下面的代码是单例模式中双重检查锁定的示例,此处的 getInstance() 方法检查两次,以查看 INSTANCE 是否为空,这就是为何它被称为双检查锁定模式,请记住,双检查锁定是代理以前Java 5,但Java5内存模型中易失变量的干扰,它应该工做完美。



你能够调用DoubleCheckedLockingSingleton.getInstance() 来获取此单例类的访问权限。如今,只需查看建立延迟加载的线程安全的 Singleton 所需的代码量。使用枚举单例模式, 你能够在一行中具备该模式, 由于建立枚举实例是线程安全的, 而且由 JVM 进行。人们可能会争辩说,有更好的方法来编写 Singleton 而不是双检查锁定方法, 但每种方法都有本身的优势和缺点, 就像我最喜欢在类加载时建立的静态字段 Singleton, 以下面所示, 但请记住, 这不是一个延迟加载单例:

单例模式用静态工厂方法

这是我最喜欢的在 Java 中影响 Singleton 模式的方法之一,由于 Singleton 实例是静态的,而且最后一个变量在类首次加载到内存时初始化,所以实例的建立本质上是线程安全的。



你能够调用 Singleton.getSingleton() 来获取此类的访问权限。2) 枚举单例自行处理序列化传统单例的另外一个问题是,一旦实现可序列化接口,它们就再也不是 Singleton, 由于 readObject() 方法老是返回一个新实例, 就像 Java 中的构造函数同样。经过使用 readResolve() 方法, 经过在如下示例中替换 Singeton 来避免这种状况:

 

若是 Singleton 类保持内部状态, 这将变得更加复杂, 由于你须要标记为 transient(不被序列化),但使用枚举单例, 序列化由 JVM 进行。3) 建立枚举实例是线程安全的如第 1 点所述,由于 Enum 实例的建立在默认状况下是线程安全的, 你无需担忧是否要作双重检查锁定。总之, 在保证序列化和线程安全的状况下,使用两行代码枚举单例模式是在 Java 5 之后的世界中建立 Singleton 的最佳方式。你仍然可使用其余流行的方法, 如你以为更好, 欢迎讨论。

7. 编写 Java 程序时, 如何在 Java 中建立死锁并修复它?

经典但核心Java面试问题之一。若是你没有参与过多线程并发 Java 应用程序的编码,你可能会失败。

如何避免 Java 线程死锁?

如何避免 Java 中的死锁?是 Java 面试的热门问题之一, 也是多线程的编程中的重口味之一, 主要在招高级程序员时容易被问到, 且有不少后续问题。尽管问题看起来很是基本, 但大多数 Java 开发人员一旦你开始深刻, 就会陷入困境。

面试问题老是以“什么是死锁?”开始

当两个或多个线程在等待彼此释放所需的资源(锁定)并陷入无限等待便是死锁。它仅在多任务或多线程的状况下发生。

如何检测 Java 中的死锁?

虽然这能够有不少答案, 但个人版本是首先我会看看代码, 若是我看到一个嵌套的同步块,或从一个同步的方法调用其余同步方法, 或试图在不一样的对象上获取锁, 若是开发人员不是很是当心,就很容易形成死锁。另外一种方法是在运行应用程序时实际锁定时找到它, 尝试采起线程转储,在 Linux 中,你能够经过kill -3命令执行此操做, 这将打印应用程序日志文件中全部线程的状态, 而且你能够看到哪一个线程被锁定在哪一个线程对象上。你可使用 fastthread.io 网站等工具分析该线程转储, 这些工具容许你上载线程转储并对其进行分析。另外一种方法是使用 jConsole 或 VisualVM, 它将显示哪些线程被锁定以及哪些对象被锁定。若是你有兴趣了解故障排除工具和分析线程转储的过程, 我建议你看看 Uriah Levy 在多元视觉(PluraIsight)上《分析 Java 线程转储》课程。旨在详细了解 Java 线程转储, 并熟悉其余流行的高级故障排除工具。

编写一个将致使死锁的Java程序?

一旦你回答了前面的问题,他们可能会要求你编写代码,这将致使Java死锁。这是个人版本之一






若是 method1() 和 method2() 都由两个或多个线程调用,则存在死锁的可能性, 由于若是线程 1 在执行 method1() 时在 Sting 对象上获取锁, 线程 2 在执行 method2() 时在 Integer 对象上获取锁, 等待彼此释放 Integer 和 String 上的锁以继续进行一步, 但这永远不会发生。此图精确演示了咱们的程序, 其中一个线程在一个对象上持有锁, 并等待其余线程持有的其余对象锁。你能够看到, Thread1 须要 Thread2 持有的 Object2 上的锁,而 Thread2 但愿得到 Thread1 持有的 Object1 上的锁。因为没有线程愿意放弃, 所以存在死锁, Java 程序被卡住。其理念是, 你应该知道使用常见并发模式的正确方法, 若是你不熟悉这些模式,那么 Jose Paumard 《应用于并发和多线程的常见 Java 模式》是学习的好起点。

如何避免Java中的死锁?

如今面试官来到最后一部分, 在我看来, 最重要的部分之一; 如何修复代码中的死锁?或如何避免Java中的死锁?若是你仔细查看了上面的代码,那么你可能已经发现死锁的真正缘由不是多个线程, 而是它们请求锁的方式, 若是你提供有序访问, 则问题将获得解决。下面是个人修复版本,它经过避免循环等待,而避免死锁, 而不须要抢占, 这是须要死锁的四个条件之一。





如今没有任何死锁,由于两种方法都按相同的顺序访问 Integer 和 String 类文本上的锁。所以,若是线程 A 在 Integer 对象上获取锁, 则线程 B 不会继续, 直到线程 A 释放 Integer 锁, 即便线程 B 持有 String 锁, 线程 A 也不会被阻止, 由于如今线程 B 不会指望线程 A 释放 Integer 锁以继续。

8. 若是你的Serializable类包含一个不可序列化的成员,会发生什么?你是如何解决的?

任何序列化该类的尝试都会因NotSerializableException而失败,但这能够经过在 Java中 为 static 设置瞬态(trancient)变量来轻松解决。

Java 序列化相关的常见问题

Java 序列化是一个重要概念, 但它不多用做持久性解决方案, 开发人员大多忽略了 Java 序列化 API。根据个人经验, Java 序列化在任何 Java核心内容面试中都是一个至关重要的话题, 在几乎全部的网面试中, 我都遇到过一两个 Java 序列化问题, 我看过一次面试, 在问几个关于序列化的问题以后候选人开始感到不自在, 由于缺少这方面的经验。他们不知道如何在 Java 中序列化对象, 或者他们不熟悉任何 Java 示例来解释序列化, 忘记了诸如序列化在 Java 中如何工做, 什么是标记接口, 标记接口的目的是什么, 瞬态变量和可变变量之间的差别, 可序列化接口具备多少种方法, 在 Java 中,Serializable 和 Externalizable 有什么区别, 或者在引入注解以后, 为何不用 @Serializable 注解或替换 Serializalbe 接口。在本文中,咱们将从初学者和高级别进行提问, 这对新手和具备多年 Java 开发经验的高级开发人员一样有益。

关于Java序列化的10个面试问题

大多数商业项目使用数据库或内存映射文件或只是普通文件, 来知足持久性要求, 只有不多的项目依赖于 Java 中的序列化过程。不管如何,这篇文章不是 Java 序列化教程或如何序列化在 Java 的对象, 但有关序列化机制和序列化 API 的面试问题, 这是值得去任何 Java 面试前先看看以避免让一些未知的内容惊到本身。对于那些不熟悉 Java 序列化的人, Java 序列化是用来经过将对象的状态存储到带有.ser扩展名的文件来序列化 Java 中的对象的过程, 而且能够经过这个文件恢复重建 Java对象状态, 这个逆过程称为 deserialization。

什么是 Java 序列化

序列化是把对象改为能够存到磁盘或经过网络发送到其余运行中的 Java 虚拟机的二进制格式的过程, 并能够经过反序列化恢复对象状态. Java 序列化API给开发人员提供了一个标准机制, 经过 java.io.Serializable 和 java.io.Externalizable 接口, ObjectInputStream 及ObjectOutputStream 处理对象序列化. Java 程序员可自由选择基于类结构的标准序列化或是他们自定义的二进制格式, 一般认为后者才是最佳实践, 由于序列化的二进制文件格式成为类输出 API的一部分, 可能破坏 Java 中私有和包可见的属性的封装.

如何序列化

让 Java 中的类能够序列化很简单. 你的 Java 类只须要实现 java.io.Serializable 接口, JVM 就会把 Object 对象按默认格式序列化. 让一个类是可序列化的须要有意为之. 类可序列会可能为是一个长期代价, 可能会所以而限制你修改或改变其实现. 当你经过实现添加接口来更改类的结构时, 添加或删除任何字段可能会破坏默认序列化, 这能够经过自定义二进制格式使不兼容的可能性最小化, 但仍须要大量的努力来确保向后兼容性。序列化如何限制你更改类的能力的一个示例是 SerialVersionUID。若是不显式声明 SerialVersionUID, 则 JVM 会根据类结构生成其结构, 该结构依赖于类实现接口和可能更改的其余几个因素。假设你新版本的类文件实现的另外一个接口, JVM 将生成一个不一样的 SerialVersionUID 的, 当你尝试加载旧版本的程序序列化的旧对象时, 你将得到无效类异常 InvalidClassException。问题 1) Java 中的可序列化接口和可外部接口之间的区别是什么?这是 Java 序列化访谈中最常问的问题。下面是个人版本 Externalizable 给咱们提供 writeExternal() 和 readExternal() 方法, 这让咱们灵活地控制 Java 序列化机制, 而不是依赖于 Java 的默认序列化。正确实现 Externalizable 接口能够显著提升应用程序的性能。问题 2) 可序列化的方法有多少?若是没有方法,那么可序列化接口的用途是什么?可序列化 Serializalbe 接口存在于java.io包中,构成了 Java 序列化机制的核心。它没有任何方法, 在 Java 中也称为标记接口。当类实现 java.io.Serializable 接口时, 它将在 Java 中变得可序列化, 并指示编译器使用 Java 序列化机制序列化此对象。问题 3) 什么是 serialVersionUID ?若是你不定义这个, 会发生什么?我最喜欢的关于Java序列化的问题面试问题之一。serialVersionUID 是一个 private static final long 型 ID, 当它被印在对象上时, 它一般是对象的哈希码,你可使用 serialver 这个 JDK 工具来查看序列化对象的 serialVersionUID。SerialVerionUID 用于对象的版本控制。也能够在类文件中指定 serialVersionUID。不指定 serialVersionUID的后果是,当你添加或修改类中的任何字段时, 则已序列化类将没法恢复, 由于为新类和旧序列化对象生成的 serialVersionUID 将有所不一样。Java 序列化过程依赖于正确的序列化对象恢复状态的,并在序列化对象序列版本不匹配的状况下引起 java.io.InvalidClassException 无效类异常。问题 4) 序列化时,你但愿某些成员不要序列化?你如何实现它?另外一个常常被问到的序列化面试问题。这也是一些时候也问, 如什么是瞬态 trasient 变量, 瞬态和静态变量会不会获得序列化等,因此,若是你不但愿任何字段是对象的状态的一部分, 而后声明它静态或瞬态根据你的须要, 这样就不会是在 Java 序列化过程当中被包含在内。问题 5) 若是类中的一个成员未实现可序列化接口, 会发生什么状况?关于Java序列化过程的一个简单问题。若是尝试序列化实现可序列化的类的对象,但该对象包含对不可序列化类的引用,则在运行时将引起不可序列化异常 NotSerializableException, 这就是为何我始终将一个可序列化警报(在个人代码注释部分中), 代码注释最佳实践之一, 指示开发人员记住这一事实, 在可序列化类中添加新字段时要注意。问题 6) 若是类是可序列化的, 但其超类不是, 则反序列化后从超级类继承的实例变量的状态如何?Java 序列化过程仅在对象层次都是可序列化结构中继续, 即实现 Java 中的可序列化接口, 而且从超级类继承的实例变量的值将经过调用构造函数初始化, 在反序列化过程当中不可序列化的超级类。一旦构造函数连接将启动, 就不可能中止, 所以, 即便层次结构中较高的类实现可序列化接口, 也将执行构造函数。正如你从陈述中看到的, 这个序列化面试问题看起来很是棘手和有难度, 但若是你熟悉关键概念, 则并不难。问题 7) 是否能够自定义序列化过程, 或者是否能够覆盖 Java 中的默认序列化过程?答案是确定的, 你能够。咱们都知道,对于序列化一个对象需调用 ObjectOutputStream.writeObject(saveThisObject), 并用 ObjectInputStream.readObject() 读取对象, 但 Java 虚拟机为你提供的还有一件事, 是定义这两个方法。若是在类中定义这两种方法, 则 JVM 将调用这两种方法, 而不是应用默认序列化机制。你能够在此处经过执行任何类型的预处理或后处理任务来自定义对象序列化和反序列化的行为。须要注意的重要一点是要声明这些方法为私有方法, 以免被继承、重写或重载。因为只有 Java 虚拟机能够调用类的私有方法, 你的类的完整性会获得保留, 而且 Java 序列化将正常工做。在我看来, 这是在任何 Java 序列化面试中能够问的最好问题之一, 一个很好的后续问题是, 为何要为你的对象提供自定义序列化表单?问题 8) 假设新类的超级类实现可序列化接口, 如何避免新类被序列化?在 Java 序列化中一个棘手的面试问题。若是类的 Super 类已经在 Java 中实现了可序列化接口, 那么它在 Java 中已经能够序列化, 由于你不能取消接口, 它不可能真正使它没法序列化类, 可是有一种方法能够避免新类序列化。为了不 Java 序列化,你须要在类中实现 writeObject() 和 readObject() 方法, 而且须要从该方法引起不序列化异常NotSerializableException。这是自定义 Java 序列化过程的另外一个好处, 如上述序列化面试问题中所述, 而且一般随着面试进度, 它做为后续问题提出。问题 9) 在 Java 中的序列化和反序列化过程当中使用哪些方法?这是很常见的面试问题, 在序列化基本上面试官试图知道: 你是否熟悉 readObject() 的用法、writeObject()、readExternal() 和 writeExternal()。Java 序列化由java.io.ObjectOutputStream类完成。该类是一个筛选器流, 它封装在较低级别的字节流中, 以处理序列化机制。要经过序列化机制存储任何对象, 咱们调用 ObjectOutputStream.writeObject(savethisobject), 并反序列化该对象, 咱们称之为 ObjectInputStream.readObject()方法。调用以 writeObject() 方法在 java 中触发序列化过程。关于 readObject() 方法, 须要注意的一点很重要一点是, 它用于从持久性读取字节, 并从这些字节建立对象, 并返回一个对象, 该对象须要类型强制转换为正确的类型。问题 10) 假设你有一个类,它序列化并存储在持久性中, 而后修改了该类以添加新字段。若是对已序列化的对象进行反序列化, 会发生什么状况?这取决于类是否具备其本身的 serialVersionUID。正如咱们从上面的问题知道, 若是咱们不提供 serialVersionUID, 则 Java 编译器将生成它, 一般它等于对象的哈希代码。经过添加任何新字段, 有可能为该类新版本生成的新 serialVersionUID 与已序列化的对象不一样, 在这种状况下, Java 序列化 API 将引起 java.io.InvalidClassException, 所以建议在代码中拥有本身的 serialVersionUID, 并确保在单个类中始终保持不变。11) Java序列化机制中的兼容更改和不兼容更改是什么?真正的挑战在于经过添加任何字段、方法或删除任何字段或方法来更改类结构, 方法是使用已序列化的对象。根据 Java 序列化规范, 添加任何字段或方法都面临兼容的更改和更改类层次结构或取消实现的可序列化接口, 有些接口在非兼容更改下。对于兼容和非兼容更改的完整列表, 我建议阅读 Java 序列化规范。12) 咱们能够经过网络传输一个序列化的对象吗?是的 ,你能够经过网络传输序列化对象, 由于 Java 序列化对象仍以字节的形式保留, 字节能够经过网络发送。你还能够将序列化对象存储在磁盘或数据库中做为 Blob。13) 在 Java 序列化期间,哪些变量未序列化?这个问题问得不一样, 但目的仍是同样的, Java开发人员是否知道静态和瞬态变量的细节。因为静态变量属于类, 而不是对象, 所以它们不是对象状态的一部分, 所以在 Java 序列化过程当中不会保存它们。因为 Java 序列化仅保留对象的状态,而不是对象自己。瞬态变量也不包含在 Java 序列化过程当中, 而且不是对象的序列化状态的一部分。在提出这个问题以后,面试官会询问后续内容, 若是你不存储这些变量的值, 那么一旦对这些对象进行反序列化并从新建立这些变量, 这些变量的价值是多少?这是大家要考虑的。

9. 为何Java中 wait 方法须要在 synchronized 的方法中调用?

另外一个棘手的核心 Java 问题,wait 和 notify。它们是在有 synchronized 标记的方法或 synchronized 块中调用的,由于 wait 和 modify 须要监视对其上调用 wait 或 notify-get 的 Object。大多数Java开发人员都知道对象类的 wait(),notify() 和 notifyAll()方法必须在Java中的 synchronized 方法或 synchronized 块中调用, 可是咱们想过多少次, 为何在 Java 中 wait, notify 和 notifyAll 来自 synchronized 块或方法?最近这个问题在Java面试中被问到个人一位朋友,他思索了一下,并回答说: 若是咱们不从同步上下文中调用 wait() 或 notify() 方法,咱们将在 Java 中收到 IllegalMonitorStateException。他的回答从实际效果上年是正确的,但面试官对这样的答案不会彻底满意,并但愿向他解释这个问题。面试结束后 他和我讨论了一样的问题,我认为他应该告诉面试官关于 Java 中 wait()和 notify()之间的竞态条件,若是咱们不在同步方法或块中调用它们就可能存在。让咱们看看竞态条件如何在Java程序中发生。它也是流行的线程面试问题之一,并常常在电话和面对面的Java开发人员面试中出现。所以,若是你正在准备Java面试,那么你应该准备这样的问题,而且能够真正帮助你的一本书是《Java程序员面试公式书》的。这是一本罕见的书,涵盖了Java访谈的几乎全部重要主题,例如核心Java,多线程,IO 和 NIO 以及 Spring 和 Hibernate 等框架。你能够在这里查看。为何要等待来自 Java中的 synchronized 方法的 wait方法为何必须从 Java 中的 synchronized 块或方法调用 ?咱们主要使用 wait(),notify() 或 notifyAll() 方法用于 Java 中的线程间通讯。一个线程在检查条件后正在等待,例如,在经典的生产者 - 消费者问题中,若是缓冲区已满,则生产者线程等待,而且消费者线程经过使用元素在缓冲区中建立空间后通知生产者线程。调用notify()或notifyAll()方法向单个或多个线程发出一个条件已更改的通知,而且一旦通知线程离开 synchronized 块,正在等待的全部线程开始获取正在等待的对象锁定,幸运的线程在从新获取锁以后从 wait() 方法返回并继续进行。让咱们将整个操做分红几步,以查看Java中wait()和notify()方法之间的竞争条件的可能性,咱们将使用Produce Consumer 线程示例更好地理解方案:

  • Producer 线程测试条件(缓冲区是是否完整)并确认必须等待(找到缓冲区已满)。
  • Consumer 线程在使用缓冲区中的元素后设置条件。
  • Consumer 线程调用 notify() 方法; 这是不会被听到的,由于 Producer 线程尚未等待。
  • Producer 线程调用 wait() 方法并进入等待状态。

所以,因为竞态条件,咱们可能会丢失通知,若是咱们使用缓冲区或只使用一个元素,生产线程将永远等待,你的程序将挂起。“在java同步中等待 notify 和 notifyall 如今让咱们考虑如何解决这个潜在的竞态条件?这个竞态条件经过使用 Java 提供的 synchronized 关键字和锁定来解决。为了调用 wait(),notify() 或 notifyAll(), 在Java中,咱们必须得到对咱们调用方法的对象的锁定。因为 Java 中的 wait() 方法在等待以前释放锁定并在从 wait() 返回以前从新获取锁定方法,咱们必须使用这个锁来确保检查条件(缓冲区是否已满)和设置条件(从缓冲区获取元素)是原子的,这能够经过在 Java 中使用 synchronized 方法或块来实现。我不肯定这是不是面试官实际期待的,但这个我认为至少有意义,请纠正我若是我错了,请告诉咱们是否还有其余使人信服的理由调用 wait(),notify() 或 Java 中的 notifyAll() 方法。总结一下,咱们用 Java 中的 synchronized 方法或 synchronized 块调用 Java 中的 wait(),notify() 或 notifyAll() 方法来避免:1) Java 会抛出 IllegalMonitorStateException,若是咱们不调用来自同步上下文的wait(),notify()或者notifyAll()方法。2) Javac 中 wait 和 notify 方法之间的任何潜在竞争条件。

10.你能用Java覆盖静态方法吗?若是我在子类中建立相同的方法是编译时错误?

不,你不能在Java中覆盖静态方法,但在子类中声明一个彻底相同的方法不是编译时错误,这称为隐藏在Java中的方法。你不能覆盖Java中的静态方法,由于方法覆盖基于运行时的动态绑定,静态方法在编译时使用静态绑定进行绑定。虽然能够在子类中声明一个具备相同名称和方法签名的方法,看起来能够在Java中覆盖静态方法,但实际上这是方法隐藏。Java不会在运行时解析方法调用,而且根据用于调用静态方法的 Object 类型,将调用相应的方法。这意味着若是你使用父类的类型来调用静态方法,那么原始静态将从父类中调用,另外一方面若是你使用子类的类型来调用静态方法,则会调用来自子类的方法。简而言之,你没法在Java中覆盖静态方法。若是你使用像Eclipse或Netbeans这样的Java IDE,它们将显示警告静态方法应该使用类名而不是使用对象来调用,由于静态方法不能在Java中重写。








输出:此输出确认你没法覆盖Java中的静态方法,而且静态方法基于类型信息而不是基于Object进行绑定。若是要覆盖静态mehtod,则会调用子类或 ColorScreen 中的方法。这一切都在讨论中咱们能够覆盖Java中的静态方法。咱们已经确认没有,咱们不能覆盖静态方法,咱们只能在Java中隐藏静态方法。建立具备相同名称和mehtod签名的静态方法称为Java隐藏方法。IDE将显示警告:"静态方法应该使用类名而不是使用对象来调用", 由于静态方法不能在Java中重写。这些是个人核心Java面试问题和答案的清单。对于有经验的程序员来讲,一些Java问题看起来并不那么难,但对于Java中的中级和初学者来讲,它们真的很难回答。顺便说一句,若是你在面试中遇到任何棘手的Java问题,请与咱们分享。若是喜欢本篇文章,欢迎转发、点赞。关注订阅号「Web项目汇集地」,回复「进群」即进入咱本身的技术交流群。

 

 

 

https://mp.weixin.qq.com/s/L2F3VB0hX4-U8RfJOJKFeA

相关文章
相关标签/搜索