原文: https://howtodoinjava.com/interview-questions/core-java-interview-questions-series-part-1/

您打算学习核心 Java 吗? 还是计划在未来几天进行面试? 不用担心,请阅读下面给出的所有面试问题,以刷新您的概念,并可能在最佳 Java 列表中添加一些新内容。

  1. Interview Questions List
  2. How to create a immutable object in Java? Count all benefits?
  3. Is Java Pass by Reference or Pass by Value?
  4. What is the use of the finally block? Is finally block in Java guaranteed to be called? When finally block is NOT called?
  5. Why there are two Date classes; one in java.util package and another in java.sql?
  6. What is Marker interface?
  7. Why main() in java is declared as public static void main?
  8. What is the difference between creating String as new() and literal?
  9. How does substring() inside String works?
  10. Explain the working of HashMap.
  11. Difference between interfaces and abstract classes?
  12. When do you override hashCode and equals()?

如何用 Java 创建不可变的对象? 算上所有好处?

不变类是一种一旦创建便无法更改其状态的类。 在这里,对象状态本质上是指存储在类的实例变量中的值,无论它们是原始类型还是引用类型。

要使类不可变,需要遵循以下步骤:

  1. 不要提供设置器方法或修改字段或字段引用的对象的方法。 设置器方法旨在更改对象的状态,这是我们在此要避免的。
  2. 将所有字段设为finalprivate。 声明为private的字段将无法在该类之外访问,将其设置为final将确保即使您不小心也无法更改它们。
  3. 不允许子类覆盖方法。 最简单的方法是将类声明为final。 Java 中的最终类不能被覆盖。
  4. 永远记住,您的实例变量将是可变的或不可变的。 标识它们并返回具有所有可变对象(对象引用)复制内容的新对象。 不变的变量(原始类型)可以安全地返回,而无需付出额外的努力。

另外,您应该记住不变类的以下好处。 面试期间您可能需要它们。 不变的类:

  • 易于构建,测试和使用
  • 自动是线程安全的,并且没有同步问题
  • 不需要复制构造器
  • 不需要克隆的实现
  • 允许hashCode使用延迟初始化,并缓存其返回值
  • 用作字段时不需要防御性地复制
  • 制作好Map键和Set元素(这些对象在集合中时不得更改状态)
  • 在构造时就建立了其类不变式,因此无需再次检查
  • 始终具有“失败原子性”(Joshua Bloch 使用的术语):如果不可变对象抛出异常,则永远不会处于不希望或不确定的状态。

看一下这篇在 Java 中编写的示例

Java 是按引用传递还是按值传递?

Java 规范说 Java 都是按值传递。 在 Java 中,没有“引用传递”之类的东西。 这些术语与方法调用关联,并将变量作为方法参数传递。 好吧,原始类型总是按值传递而不会造成任何混淆。 但是,应该在复杂类型的方法参数的上下文中理解该概念。

在 Java 中,当我们将复杂类型的引用作为任何方法参数传递时,总是将内存地址一点一点地复制到新的引用变量中。 见下图:

Java 核心面试问题 – 第 1 部分 - 图1

在上面的示例中,第一个实例的地址位被复制到另一个引用变量,因此导致两个引用都指向存储实际对象的单个存储位置。 请记住,再次引用null不会使第一次引用也为null。 但是,从任一引用变量更改状态也会对其他参考产生影响。

在此处详细阅读: Java 是值或引用传递?

finally块的用途是什么? Java 中的finally块是否可以保证被调用? 何时不调用finally块?

try块退出时,finally块总是执行。 这样可以确保即使发生意外异常也执行finally块。 但是finally不仅可以用于异常处理,还可以使清除代码意外地被returncontinuebreak绕过。 将清除代码放在finally块中始终是一个好习惯,即使没有异常的情况也是如此。

如果在执行trycatch代码时退出 JVM,则finally块可能不会执行。 同样,如果执行trycatch代码的线程被中断或杀死,则即使整个应用继续运行,finally块也可能不会执行。

为什么有两个Date类; 一个在java.util包中,另一个在java.sql中?

java.util.Date代表一天中的日期和时间,java.sql.Date仅代表日期。 java.sql.Date的补码为java.sql.Time,仅代表一天中的某个时间。
java.sql.Datejava.util.Date的子类(扩展)。 因此,java.sql.Date中发生了什么变化:

toString()生成不同的字符串表示形式:yyyy-mm-dd
– 一种static valueOf(String)方法,从具有上述表示形式的字符串中创建日期
– 获取器和设置器数小时 ,不建议使用分钟和秒

java.sql.Date类与 JDBC 一起使用,并且不应包含时间部分,即小时,分钟,秒和毫秒应为零…但是该类未强制执行。

解释标记接口?

标记接口模式是计算机科学中的一种设计模式,与提供有关对象的运行时类型信息的语言一起使用。 它提供了一种方法,可将元数据与该语言不明确支持此类的类相关联。 在 Java 中,它用作未指定方法的接口。

在 Java 中使用标记接口的一个很好的例子是Serializable接口。 一个类实现此接口,以指示可以将其非瞬态数据成员写入字节流或文件系统。

标记接口的主要问题是,接口定义用于实现类的协定,并且该协定被所有子类继承。 这意味着您不能“取消实现”标记。 在给定的示例中,如果创建不想序列化的子类(可能是因为它依赖于瞬态),则必须诉诸显式抛出NotSerializableException

为什么将 Java 中的main()声明为public static void

为什么公开? main方法是public,因此它可以在任何地方访问,并且对于可能希望使用它启动应用的每个对象都可以访问。 在这里,我并不是说 JDK / JRE 具有类似的原因,因为java.exejavaw.exe(对于 Windows)使用 Java 本机接口(JNI)调用来调用方法,因此,无论使用哪种访问修饰符,他们都可以通过任何一种方式来调用它。

为什么是静态? 假设我们没有像static那样的主要方法。 现在,要调用任何方法,您需要它的一个实例。 对? 众所周知,Java 可以有重载的构造器。 现在,应该使用哪一个,重载的构造器的参数从何而来。

为什么void 因此,没有任何将值返回给实际调用此方法的 JVM 的用途。 应用唯一想与调用过程进行通信的是:正常终止或异常终止。 使用System.exit(int)已经可以实现。 非零值表示异常终止,否则一切正常。

将 String 创建为new()和字面值之间有什么区别?

当我们使用new()创建String时,它会在堆中创建并添加到字符串池中,而使用字面值创建的String仅在字符串池中创建,而字符串池仅存在于堆的永久区域中。

那么,您真的需要非常深入地了解字符串池的概念,才能回答此问题或类似问题。 我的建议…“认真学习”字符串类和字符串池

String中的substring()如何工作?

Java 中的String与其他任何编程语言一样,都是字符序列。 这更像是用于该char序列的工具类。 此char序列在以下变量中维护:

  1. /** The value is used for character storage. */
  2. private final char value[];

要在不同情况下访问此数组,请使用以下变量:

  1. /** The offset is the first index of the storage that is used. */
  2. private final int offset;
  3. /** The count is the number of characters in the String. */
  4. private final int count;

每当我们从任何现有的字符串实例创建子字符串时,substring()方法都只会设置offsetcount变量的新值。 内部char数组不变。 如果不小心使用substring()方法,这可能是内存泄漏的原因。 在此处了解更多信息

解释HashMap的工作。 如何解决重复冲突?

你们大多数人都会同意,HashMap是当今面试中最喜欢讨论的话题。 如果有人要我描述“HashMap如何工作?”,我只是回答:“关于哈希的原理”。 就这么简单。

现在,以最简单的形式进行哈希处理是一种在对属性应用任何公式/算法之后为任何变量/对象分配唯一代码的方法。

定义的映射是:“将键映射到值的对象”。 很容易..对吗? 因此,HashMap有一个内部类Entry,它看起来像这样:

  1. static class Entry<k ,V> implements Map.Entry<k ,V>
  2. {
  3. final K key;
  4. V value;
  5. Entry<k ,V> next;
  6. final int hash;
  7. ...//More code goes here
  8. }

当某人尝试将键值对存储在HashMap中时,会发生以下情况:

  • 首先,检查键对象是否为空。 如果keynull,则值存储在table[0]位置。 因为null的哈希码始终为 0。

  • 然后,下一步,通过调用键的hashCode()方法,使用键的哈希码计算哈希值。 该哈希值用于计算数组中用于存储Entry对象的索引。 JDK 设计人员很好地假设可能存在一些编写不当的hashCode()函数,它们可能返回非常高或很低的哈希码值。 为解决此问题,他们引入了另一个hash()函数,并将对象的哈希码传递给此hash()函数,以将哈希值带入数组索引大小的范围内。

  • 现在,调用indexFor(hash, table.length)函数来计算用于存储Entry对象的精确索引位置。

  • 这是主要部分。 现在,我们知道两个不相等的对象可以具有相同的哈希码值,如何将两个不同的对象存储在相同的数组位置(称为存储桶)中。 答案是LinkedList。 如果您还记得的话,Entry类的属性为next。 此属性始终指向链中的下一个对象。 这正是LinkedList的行为。
    因此,在发生碰撞的情况下,Entry对象以LinkedList形式存储。 当Entry对象需要存储在特定索引中时,HashMap检查是否已经有一个项目? 如果尚无项目,则Entry对象存储在此位置。
    如果已经有一个对象位于计算索引上,则检查其next属性。 如果为null,则当前Entry对象成为LinkedList中的next节点。 如果next变量不为空,则遵循步骤直到next被求值为空。
    如果我们添加另一个具有与之前输入相同的键的值对象,该怎么办。 从逻辑上讲,它应该替换旧值。 怎么做的? 好吧,在确定Entry对象的index位置之后,在对计算索引上的LinkedList进行迭代时,HashMap为每个Entry对象调用关键对象上的equals()方法。 LinkedList中的所有这些Entry对象将具有相似的哈希码,但equals()方法将测试真实相等性。 如果key.equals(k)true,则两个键都被视为相同的键对象。 这将仅导致替换Entry对象中的value对象。

这样,HashMap确保键的唯一性。

接口和抽象类之间的区别?

如果您正在面试初级程序员,这是一个非常常见的问题。 好吧,最明显的区别如下:

  • 在 Java 接口中声明的变量默认为final。 抽象类可能包含非最终变量。
  • Java 接口是隐式abstract,不能具有实现。 Java 抽象类可以具有实现默认行为的实例方法。
  • 默认情况下,Java 接口的成员是公开的。 Java 抽象类可以具有类成员通常的风格,例如privateabstract等。
  • Java 接口应使用关键字“implements”实现; Java 抽象类应使用关键字“extends”来扩展。
  • Java 类可以实现多个接口,但只能扩展一个抽象类。
  • 接口是绝对抽象,不能实例化; Java 抽象类也无法实例化,但是可以在存在main()的情况下调用。 从 Java8 开始,您可以在接口中定义默认方法
  • 抽象类比接口要快一些,因为接口涉及在 Java 中调用任何覆盖方法之前进行的搜索。 在大多数情况下,这并不是显着的差异,但是如果您正在编写时间紧迫的应用,那么您可能不想无所事事。

何时覆盖hashCode()equals()

hashCode()equals()方法已在Object类中定义,该类是 Java 对象的父类。 因此,所有 java 对象都继承这些方法的默认实现。

hashCode()方法用于获取给定对象的唯一整数。 当此对象需要存储在类似数据结构的HashTable中时,该整数用于确定存储桶位置。 默认情况下,对象的hashCode()方法返回并以整数形式表示存储对象的内存地址。 顾名思义,
equals()方法用于简单地验证两个对象的相等性。 默认实现只是检查两个对象的对象引用以验证它们的相等性。

请注意,通常有必要在每次覆盖此方法时都覆盖hashCode方法,以维护hashCode()方法的常规约定,该约定规定相等的对象必须具有相等的哈希码。

  • equals()必须定义一个相等关系(它必须是自反,对称和可传递)。 另外,它必须是一致的(如果未修改对象,则它必须保持返回相同的值)。 此外,o.equals(null)必须始终返回false
  • hashCode()也必须保持一致(如果未根据equals()修改对象,则它必须保持返回相同的值)。

两种方法之间的关系为:

每当a.equals(b)时,a.hashCode()必须与b.hashCode()相同。

祝您学习愉快!