Java 面试准备:15 个 Java 面试问题
Q1:Java平台无关性是什么意思?
Q2:解释一下JRE、JDK、JVM的概念
Q3:如何在 Java 中将实体包标记为私有?
问题4:为什么应该避免使用 Object 类中的 finalize() 方法?有哪些替代方案?
Q5:您能否更改最终数组的内容,如下面的代码片段所示?
问题6:解释一下接口和抽象类之间的区别?什么时候应该使用其中一种?
Q7:什么是多态?能举个例子吗?
Q8:main方法可以重载吗?
Q9:如何在每次调用时向方法传递多个参数?
Q10:信号量可以充当互斥锁吗?
Q11:解释一下 Externalizable 接口
Q12:如果代码块抛出多个异常,该如何处理?
Q13:如果要使用集合,如何判断是使用 HashSet 还是 TreeSet?
Q14:有哪些方法可以改善 Java 应用程序的内存占用?
Q15:实现单例类的最佳方法是什么?
精通
并非所有面试都会关注算法和数据结构——很多时候,面试只会关注你声称自己是专家的语言或技术。在这类面试中,通常不会出现任何“陷阱”问题,而是要求你运用记忆和使用该语言的经验——换句话说,面试官会测试你对该编程语言的了解程度。
然而,我们很容易忘记 Java 等语言的所有细节,因为简单地说,我们不会每天处理诸如“JVM 管理什么样的内存?”和“用一个例子描述多态性”之类的问题。
这篇文章概述了 Java 面试中常见的一些问题。由于 Java 面试题种类繁多,因此本文将作为指南,帮助您思考可能遇到的不同类型的问题以及应该准备哪些主题。如果您想了解掌握 Java 语言的完整指南,可以查看《Java 面试权威手册》。
今天,我们将讨论与以下内容相关的面试问题和答案:
- Java 生态系统
- Java 类
- 接口
- 遗产
- 多线程
- 内存管理
- 收藏
- 异常处理
- 序列化
- 单例模式
让我们开始吧!
Q1:Java平台无关性是什么意思?
Java 的工作原理是“一次编写,随处运行”。Java 程序编写完成后,会被编译成字节码,然后可以在任何 Java 虚拟机(简称 JVM)上运行。
编译为字节码是 Java 互操作性背后的魔力。不同的操作系统和硬件架构都有专门设计的 JVM,并且所有 JVM 都可以运行相同的字节码。因此,如果您在 Linux 上编写 Java 程序,它将在为 Windows 操作系统设计的 JVM 上无缝运行,从而使代码与底层硬件和操作系统无关。
Q2:解释一下JRE、JDK、JVM的概念
-
JRE(Java 运行时环境)包含 Java 虚拟机和标准 Java API(核心类和支持文件)。JRE 包含的内容仅够执行 Java 应用程序,但不足以编译它。
-
JDK(Java 开发工具包)由 JRE、Java 编译器以及一组用于编译和调试代码的其他工具组成。JRE 包含 Java 平台库、Java 虚拟机 (JVM)、Java 插件和 Java Web Start,用于运行 Java 应用程序。JRE 本身不包含编译器和调试工具。如果您需要开发 Java 程序,则需要完整的 Java SDK。JRE 不足以完成程序开发。只有完整的 Java SDK 才包含 Java 编译器,它可以将您的 .java 源文件转换为字节码 .class 文件。
-
JVM(Java 虚拟机)是规范的实现,详细描述了 JVM 的预期行为。任何符合 JVM 规范的实现都应该能够运行编译成 Java 字节码的代码,无论代码最初是用什么语言编写的。在 Java 编程语言中,所有源代码首先都以纯文本文件的形式编写,并以 .java 为扩展名。然后,这些源文件会被 javac 编译器编译成 .class 文件。.class 文件不包含处理器的原生代码;它包含字节码——Java 虚拟机的机器语言。之后,java 启动器工具会使用 Java 虚拟机的实例运行您的应用程序。
Q3:如何在 Java 中将实体包标记为私有?
没有显式修饰符来修饰包私有 (package private)。如果没有任何修饰符,则类或成员变量都是包私有的。标记为包私有的成员仅在其所属包内可见。考虑下面的类。
// class can be accessed by other classes within the same
// package but not outside of it.
class IamPackagePrivateClass {
int IamPackagePrivate;
private int IamPrivate;
public IamPackagePrivate(int a, int b) {
this.IamPackagePrivate = a;
this.IamPrivate = b;
}
}
包私有 (package private) 是私有 (private) 的一种略微宽泛的形式。包私有 (package-private) 的一个好处是,你可以用它来访问那些你原本认为是单元测试类私有的方法。因此,如果你使用的辅助类除了帮助你的公共类执行一些用户需要的操作之外没有其他用途,那么将它们设置为包私有 (package-private) 是合理的,因为你想让库的用户尽可能简单易用。
问题4:为什么应该避免使用 Object 类中的 finalize() 方法?有哪些替代方案?
Object 类提供了一个回调方法 finalize(),当对象变为垃圾时可以调用该方法。Object 类的 finalize() 实现不执行任何操作——您可以重写 finalize() 来执行清理操作,例如释放资源。
该finalize()
方法可能会被系统自动调用,但何时调用,甚至是否调用,都不确定。因此,您不应依赖此方法为您完成清理工作。例如,如果您在执行 I/O 操作后未在代码中关闭文件描述符,而希望 finalize() 为您关闭它们,则可能会耗尽文件描述符。
以下是一些替代方案:
- try-with-resources 习惯用法可用于清理对象。这需要实现 AutoCloseable 接口。
- 当对象被垃圾回收时,使用 PhantomReference 执行清理
- 使用 Cleaner 类执行清理操作。
- 实现一种
close()
方法,进行清理并记录该方法被调用的情况。
Q5:您能否更改最终数组的内容,如下面的代码片段所示?
final int[] array = new int[5];
array[0] = 1;
这看起来可能违反直觉,但即使数组被标记为 final,我们实际上也可以更改其内容。数组变量指向内存中存放数组内容的特定起始位置。该位置或内存地址无法更改。例如,以下代码将无法编译:
final int[] array = new int [5]
array = new int[10];
但是,下面的代码可以工作。
public class FinalArrayExample {
final int[] array = new int[5];
// allowed
void changeArrayContents(int i, int val) {
array[i] = val;
}
// not allowed and will not compile
/*
void changeArray() {
array = new int [10]
}*/
}
问题6:解释一下接口和抽象类之间的区别?什么时候应该使用其中一种?
抽象类无法实例化,但可以被子类化。抽象类通常包含抽象方法和非抽象方法,子类必须提供这些方法的实现。
接口是一个完全的“抽象类”,用于将相关方法与空体组合在一起。
以下是抽象类和接口之间的四个主要区别:
-
抽象类可以有最终变量、静态变量或类成员变量,而接口只能有默认的最终变量和静态变量。
-
抽象类可以具有静态、抽象或非抽象方法。接口可以具有静态、抽象或默认方法。
-
抽象类的成员可以具有不同的可见性:私有 (private)、受保护 (protected) 或公共 (public)。而接口中的所有方法和常量都是公共的。
-
一个类只能扩展另一个类,但可以实现多个接口。同样,一个接口也可以扩展多个接口。接口永远不会实现任何类或接口。
当子类共享状态或使用通用功能时,请使用抽象类。或者,您需要声明非静态、非 final 字段,或者需要使用除 public 之外的访问修饰符。
如果您希望不相关的类实现您的接口,请使用接口。例如,Comparable 和 Cloneable 接口由许多不相关的类实现。接口也适用于需要多继承类型的情况。
Q7:什么是多态?能举个例子吗?
多态性是指编程中为不同的底层形式或数据类型呈现相同接口的能力。多态性是指你可以将一个对象视为某个对象的泛型版本,但当你访问它时,代码会确定它的具体类型并调用相关的代码。这意味着多态性允许你的代码处理不同的类,而无需知道它正在使用哪个类。
多态性用于使应用程序更加模块化和可扩展。与其使用描述不同操作过程的杂乱条件语句,不如创建可根据需要选择的可互换对象。这是多态性的基本目标。
多态性的经典示例是Shape
类。我们从父类派生出Circle
、Triangle
和类,父类公开一个抽象方法。派生类为该方法提供自定义实现。现在,通过在每个对象上调用该方法,可以非常轻松地渲染同一数组中包含的不同类型的形状。这免去了我们为每个形状(例如、等)创建单独的绘制方法的麻烦。Rectangle
Shape
draw()
draw()
draw()
drawTriangle()
drawCircle()
Q8:main方法可以重载吗?
是的,main 方法是一个静态方法,可以重载。但public static void main(String[] args)
即使你指定了一两个命令行参数,它也只会在 JVM 启动你的类时使用。不过,可以通过编程方式调用 main 方法的重载版本。
Q9:如何在每次调用时向方法传递多个参数?
我们可以使用可变参数功能向方法传递可变数量的参数。下面是一个向方法传递多个相同类型的参数的示例。
public void childrenNames(string... names) {
for(int i= 0; i < names.length; i++)
system.out.println(names[i]);
}
- 类型名称后跟三个点、一个空格,然后是变量名称。
- varargs 变量被视为数组。
- varargs 变量必须出现在方法签名的最后。
- 因此,方法签名中只能有一个可变参数。
上述方法可以通过以下方式调用:调用 Varargs 方法
childrenNames();
childrenNames("Jane");
childrenNames("Jane", "Tom", "Peter");
Q10:信号量可以充当互斥锁吗?
如果将信号量可以发出的许可证数量设置为 1,则信号量可以充当互斥锁。但是,两者之间最重要的区别在于,对于互斥锁,同一个线程必须调用获取并随后释放互斥锁,而对于二进制信号量,不同的线程可以调用获取和释放信号量。
这引出了“所有权”的概念。互斥锁归获取它的线程所有,直到它释放它为止。而信号量则没有所有权的概念。
需要复习一下多线程吗?可以看看这篇文章《Java 多线程和并发:高级工程师面试必备知识》。
Q11:解释一下 Externalizable 接口
Serializable 接口为我们类的对象提供了自动序列化的能力。另一方面,Externalizable 接口提供了一种实现自定义序列化机制的方法。实现 Externalizable 接口的类负责保存和恢复其自身实例的内容。
Externalizable 接口扩展了 Serializable 接口,并提供了两种方法来序列化和反序列化对象,writeExternal()
和readExternal()
。
Q12:如果代码块抛出多个异常,该如何处理?
一段代码抛出的多种类型的异常可以通过多个 catch 子句(后跟 try 代码块)来处理。以下是一段异常处理的示例代码:
void process(int val) {
try {
if (val == 1)
//checked exception
throw new FileNotFoundException();
if (val == 2)
// runtime exception
throw new NullPointerExxception();
if (val == 3)
// error exception
throw new StackOverflowError
} catch (RuntimeException re) {
// catches all unchecked exceptions
} catch (Exception e) {
// catches all checked exceptions
} catch (Error err) {
// catches all errors
}
}
Q13:如果要使用集合,如何判断是使用 HashSet 还是 TreeSet?
最初,您可能想使用 HashSet,因为它会给您更好的时间复杂度,但它不能保证集合的迭代顺序;特别是,它不能保证顺序会随着时间的推移保持不变。
因此,如果您希望保持顺序,最好使用 TreeSet,因为它按升序存储键,而不是按插入顺序存储。它不是线程安全的。但是,请记住,TreeSet 不是线程安全的,而 HashSet 是线程安全的。
Q14:有哪些方法可以改善 Java 应用程序的内存占用?
您可以采取以下三个关键步骤来改善内存占用:
- 限制局部变量的作用域。每次从堆栈顶部弹出作用域时,该作用域的引用都会丢失,这可能导致对象被垃圾回收。
- 不需要时,请将变量引用显式设置为 null。这将使对象符合垃圾回收的条件。
- 避免使用终结器。它们会降低程序性能,并且不保证任何事情。
Q15:实现单例类的最佳方法是什么?
Josh Bloch 认为,实现单例的最佳方法是使用枚举类型。由于 Java 确保每次只创建一个枚举实例,因此通过枚举实现的单例类可以避免反射和序列化攻击。
class Demonstration {
public static void main( String args[] ) {
Superman superman = Superman.INSTANCE;
superman.fly();
}
}
enum Superman {
INSTANCE;
private final String name = "Clark Kent";
private String residence = "USA";
public void fly() {
System.out.println("I am flyyyyinggggg ...");
}
}
精通
这篇文章涵盖了 Java 编程语言的诸多方面,从 Java 生态系统(问题 1)到多线程(问题 10)以及异常(问题 12)。以上就是你可能会遇到的 Java 面试题类型。最好将以上列出的内容作为参考,了解你想要学习的主题以及可能遇到的题目类型。
然而,这里的内容只是触及皮毛。还有更多概念需要重新审视或探索,例如面向对象编程、静态变量和方法重载。如果您想深入研究 Java,并探索有关上述主题的数百个问题,那么《权威 Java 面试手册》将是您重温 Java 核心原则的绝佳资源——从基础知识到更高级的功能。
学习愉快!
鏂囩珷鏉ユ簮锛�https://dev.to/educative/java-interview-prep-15-java-interview-questions-i64