你是否具备在 Java 面试中胜出的条件?我们在这里帮助你巩固你在 Java 中的知识和概念。下面的文章将深入介绍所有针对新生和有经验的候选人的流行 Java 面试问题。
仔细检查所有问题,以增加你在面试中表现出色的机会。这些问题将围绕 Java 的基本和核心基础知识展开。
因此,让我们深入探讨有关 Java 的大量有用的面试问题。
基础Java常见面试题和答案合集
1. 为什么 Java 是一种平台无关语言?
Java面试题解析:Java 语言是以这样一种方式开发的,它不依赖于任何硬件或软件,因为编译器编译代码,然后将其转换为可以在多个系统上运行的独立于平台的字节码。
- 运行该字节码的唯一条件是机器中安装了运行时环境 (JRE)。
2. Java常见面试题有哪些:为什么Java 不是纯粹的面向对象语言?
Java 支持原始数据类型——byte、boolean、char、short、int、float、long 和 double,因此它不是纯面向对象的语言。
3. C/C++ 中使用指针。为什么Java不使用指针?
指针对于初学者来说非常复杂且不安全。Java 注重代码的简单性,指针的使用使其具有挑战性。指针的使用也可能导致潜在的错误。此外,如果使用指针,安全性也会受到影响,因为用户可以在指针的帮助下直接访问内存。
因此,通过在 Java 中不包含指针,提供了一定程度的抽象。此外,指针的使用会使垃圾收集过程变得非常缓慢和错误。Java 使用引用,因为它们与指针不同,无法操作。
4、你怎么理解实例变量和局部变量?
实例变量是类中所有方法都可以访问的变量。它们在方法外部和类内部声明。这些变量描述了一个对象的属性,并且不惜任何代价保持与它的绑定。
该类的所有对象都将拥有它们的变量副本以供使用。如果对这些变量进行了任何修改,那么只有该实例会受到它的影响,而所有其他类实例将继续保持不受影响。
例子:
class Athlete {
public String athleteName;
public double athleteSpeed;
public int athleteAge;
}
局部变量是存在于块、函数或构造函数中并且只能在它们内部访问的那些变量。变量的使用仅限于块范围。每当在方法内部声明局部变量时,其他类方法对局部变量一无所知。
例子:
public void athlete() {
String athleteName;
double athleteSpeed;
int athleteAge;
}
5、数据封装是什么意思?
- 数据封装是一种面向对象的编程概念,将数据属性及其行为隐藏在一个单元中。
- 它通过确保每个对象通过拥有自己的方法、属性和功能而独立于其他对象,帮助开发人员在开发软件时遵循模块化。
- 它用于对象私有属性的安全性,因此用于数据隐藏的目的。
6. 告诉我们一些关于 JIT 编译器的事情。
- JIT 代表 Just-In-Time,用于提高运行时的性能。它同时完成编译部分具有相似功能的字节码的任务,从而减少代码运行的编译时间。
- 编译器只不过是源代码到机器可执行代码的翻译器。但是 JIT 编译器有什么特别之处呢?让我们看看它是如何工作的:
- 首先,Java 源代码 (.java) 到字节码 (.class) 的转换是在 javac 编译器的帮助下发生的。
- 然后,JVM 在运行时加载 .class 文件,并在解释器的帮助下,将这些文件转换为机器可理解的代码。
- JIT 编译器是 JVM 的一部分。启用 JIT 编译器后,JVM 会分析 .class 文件中的方法调用并对其进行编译以获得更高效的原生代码。它还确保优化优先级方法调用。
- 完成上述步骤后,JVM 将直接执行优化后的代码,而不是再次解释代码。这提高了执行的性能和速度。
7.你能分辨Java中的equals()方法和相等运算符(==)的区别吗?
equals() | == |
---|---|
这是在 Object 类中定义的方法。 | 它是 Java 中的二元运算符。 |
此方法用于根据指定的业务逻辑检查两个对象之间的内容是否相等。 | 该运算符用于比较地址(或引用),即检查两个对象是否指向相同的内存位置。 |
注意:
- 在类中未覆盖 equals 方法的情况下,该类将使用最接近父类的 equals 方法的默认实现。
- Object类被认为是所有java类的父类。Object 类中 equals 方法的实现使用 == 运算符来比较两个对象。可以根据业务逻辑覆盖此默认实现。
8. Java 中如何声明无限循环?
无限循环是那些无限运行而没有任何中断条件的循环。有意识地声明无限循环的一些例子是:
- 使用 For 循环:
for (;;)
{
// Business logic
// Any break logic
}
- 使用while循环:
while(true){
// Business logic
// Any break logic
}
- 使用 do-while 循环:
do{
// Business logic
// Any break logic
}while(true);
9.简述构造函数重载的概念
构造函数重载是在类中创建多个由同名但构造函数参数不同的构造函数组成的过程。根据参数的数量及其对应的类型,由编译器来区分不同类型的构造函数。
class Hospital {
int variable1, variable2;
double variable3;
public Hospital(int doctors, int nurses) {
variable1 = doctors;
variable2 = nurses;
}
public Hospital(int doctors) {
variable1 = doctors;
}
public Hospital(double salaries) {
variable3 = salaries
}
}
此处定义了三个构造函数,但它们因参数类型和编号而异。
10. 引用相关例子评论方法重载和覆盖。
在 Java 中,通过在由相同名称组成的同一类中引入不同的方法来实现方法重载。尽管如此,所有函数在参数的数量或类型上都不同。它发生在一个类中,并增强了程序的可读性。
方法返回类型的唯一区别不促进方法重载。以下示例将为你提供清晰的图片。
class OverloadingHelp {
public int findarea (int l, int b) {
int var1;
var1 = l * b;
return var1;
}
public int findarea (int l, int b, int h) {
int var2;
var2 = l * b * h;
return var2;
}
}
这两个函数具有相同的名称,但参数数量不同。第一种方法计算矩形的面积,而第二种方法计算长方体的面积。
方法覆盖是两个具有相同方法签名的方法存在于两个不同的类中的概念,其中存在继承关系。通过使用方法覆盖,派生类可以实现特定的方法实现(已经存在于基类中)。
让我们看一下这个例子:
class HumanBeing {
public int walk (int distance, int time) {
int speed = distance / time;
return speed;
}
}
class Athlete extends HumanBeing {
public int walk(int distance, int time) {
int speed = distance / time;
speed = speed * 2;
return speed;
}
}
这两个类方法都具有名称 walk 和相同的参数、距离和时间。如果调用派生类方法,则基类方法 walk 将被派生类的方法覆盖。
11. Java 程序中可以同时存在一个 try 块和多个 catch 块。解释。
是的,可以存在多个 catch 块,但特定的方法应该在一般方法之前出现,因为只有满足 catch 条件的第一个 catch 块才会被执行。给定的代码说明了相同的内容:
public class MultipleCatch {
public static void main(String args[]) {
try {
int n = 1000, x = 0;
int arr[] = new int[n];
for (int i = 0; i <= n; i++) {
arr[i] = i / x;
}
}
catch (ArrayIndexOutOfBoundsException exception) {
System.out.println("1st block = ArrayIndexOutOfBoundsException");
}
catch (ArithmeticException exception) {
System.out.println("2nd block = ArithmeticException");
}
catch (Exception exception) {
System.out.println("3rd block = Exception");
}
}
}
在这里,第二个 catch 块将被执行,因为除以 0 (i / x)。如果 x 大于 0,那么第一个 catch 块将执行,因为 for 循环运行直到 i = n 并且数组索引直到 n-1。
12.解释final关键字在变量、方法和类中的使用。
在 Java 中,final 关键字用于定义常量 /final 并表示非访问修饰符。
- final变量:
- 当一个变量在 Java 中被声明为 final 时,该值一旦被赋值就不能被修改。
- 如果任何值尚未分配给该变量,则它只能由类的构造函数分配。
- final方法:
- 声明为 final 的方法不能被其子类覆盖。
- 构造函数不能被标记为 final,因为无论何时继承一个类,构造函数都不会被继承。因此,将其标记为 final 是没有意义的。Java 抛出编译错误说 -
modifier final not allowed here
- final类:
- 不能从声明为 final 的类继承任何类。但是最终的类可以扩展其他类以供其使用。
13、final、finally和finalize关键字的作用一样吗?
Java面试题解析:所有三个关键字在编程时都有自己的效用。
Final:如果需要对类、变量或方法进行任何限制,则 final 关键字会派上用场。final 类的继承和 final 方法的覆盖受 final 关键字的使用限制。合并 final 关键字后,变量值变为固定值。例子:
final int a=100;
a = 0; // error
第二个语句会抛出错误。
Finally:它是程序中存在的块,其中写入的所有代码都会执行,而不管异常处理如何。例子:
try {
int variable = 5;
}
catch (Exception exception) {
System.out.println("Exception occurred");
}
finally {
System.out.println("Execution of finally block");
}
Finalize:在对象的垃圾回收之前,调用 finalize 方法以便实现清理活动。例子:
public static void main(String[] args) {
String example = new String("InterviewBit");
example = null;
System.gc(); // Garbage collector called
}
public void finalize() {
// Finalize called
}
14.Java常见面试题有哪些:什么时候可以使用super关键字?
- super 关键字用于访问父类的隐藏字段和覆盖的方法或属性。
- 以下是可以使用此关键字的情况:
- 访问父类的数据成员,当该类及其子类的成员名称相同时。
- 在子类内部调用父类的默认构造函数和参数化构造函数。
- 当子类覆盖它们时访问父类方法。
- 以下示例演示了使用 super 关键字时的所有 3 种情况。
public class Parent{
protected int num = 1;
Parent(){
System.out.println("Parent class default constructor.");
}
Parent(String x){
System.out.println("Parent class parameterised constructor.");
}
public void foo(){
System.out.println("Parent class foo!");
}
}
public class Child extends Parent{
private int num = 2;
Child(){
System.out.println("Child class default Constructor");
super(); // to call default parent constructor
super("Call Parent"); // to call parameterised constructor.
}
void printNum(){
System.out.println(num);
System.out.println(super.num); //prints the value of num of parent class
}
@Override
public void foo(){
System.out.println("Parent class foo!");
super.foo(); //Calls foo method of Parent class inside the Overriden foo method of Child class.
}
}
15.静态方法可以重载吗?
是的!一个类中可以有两个或多个具有相同名称但输入参数不同的静态方法。
16. 静态方法可以覆盖吗?
- 不!可以在子类中声明具有相同签名的静态方法,但在这种情况下不能发生运行时多态性。
- 覆盖或动态多态发生在运行时,但静态方法被加载并在编译时静态地查找。因此,这些方法不能被覆盖。
17.垃圾收集的主要目标是什么?
这个过程的主要目的是通过删除那些不可达的对象来释放Java程序执行过程中不必要的和不可达的对象所占用的内存空间。
- 这可确保有效使用内存资源,但不能保证有足够的内存用于程序执行。
18. 内存的哪一部分 - 堆栈或堆 - 在垃圾收集过程中被清理?
堆。
中级Java常见面试题和答案合集
19. 除了安全方面,在 Java 中使字符串不可变的原因是什么?
由于以下原因,String 变得不可变:
- 字符串池: Java 的设计者意识到程序员和开发人员将主要使用字符串数据类型这一事实。因此,他们从一开始就想要优化。他们提出了使用字符串池(Java 堆中的一个存储区域)来存储字符串文字的概念。他们打算借助共享来减少临时 String 对象。需要一个不可变的类来促进共享。在两个未知方之间共享可变结构是不可能的。因此,不可变的 Java 字符串有助于执行字符串池的概念。
- 多线程:关于 String 对象的线程安全是 Java 中的一个重要方面。如果 String 对象是不可变的,则不需要外部同步。因此,可以编写更清晰的代码来跨不同线程共享 String 对象。这种方法促进了并发的复杂过程。
- 集合:在 Hashtables 和 HashMaps 的情况下,键是 String 对象。如果 String 对象不是不可变的,那么它可以在它驻留在 HashMap 期间被修改。因此,无法检索所需的数据。这种不断变化的状态会带来很多风险。因此,使字符串不可变是非常安全的。
20. 你如何区分 String、StringBuffer 和 StringBuilder?
- 存储区:在字符串中,字符串池作为存储区。对于 StringBuilder 和 StringBuffer,堆内存是存储区域。
- 可变性: String 是不可变的,而 StringBuilder 和 StringBuffer 都是可变的。
- 效率:使用字符串非常慢。但是,StringBuilder 是执行操作最快的。StringBuffer 的速度大于 String 而小于 StringBuilder。(例如,附加字符在 StringBuilder 中最快,而在 String 中非常慢,因为带有附加字符的新字符串需要新的内存。)
- 线程安全:在线程环境的情况下,使用 StringBuilder 和 StringBuffer 而没有使用 String。但是StringBuilder适合单线程环境,StringBuffer适合多线程。
句法:
// String
String first = "InterviewBit";
String second = new String("InterviewBit");
// StringBuffer
StringBuffer third = new StringBuffer("InterviewBit");
// StringBuilder
StringBuilder fourth = new StringBuilder("InterviewBit");
21. 使用相关属性突出接口和抽象类之间的差异。
- 方法的可用性:接口中只有抽象方法可用,而非抽象方法可以与抽象类中的抽象方法一起出现。
- 变量类型:静态和最终变量只能在接口的情况下声明,而抽象类也可以有非静态和非最终变量。
- 继承:接口促进了多重继承,而抽象类不促进多重继承。
- 数据成员可访问性:默认情况下,接口的类数据成员是公共类型的。相反,抽象类的类成员也可以是受保护的或私有的。
- 实现:借助抽象类,可以轻松实现接口。然而,反之则不然。
抽象类示例:
public abstract class Athlete {
public abstract void walk();
}
接口示例:
public interface Walkable {
void walk();
}
22. 在 Java 中,可以覆盖静态方法和私有方法。对声明发表评论。
上下文中的陈述是完全错误的。静态方法与对象无关,这些方法是类级别的。在子类的情况下,具有与父类完全相同的方法签名的静态方法可以存在,甚至不会引发任何编译错误。
这里提到的现象通常称为方法隐藏,覆盖肯定是不可能的。私有方法覆盖是不可想象的,因为私有方法的可见性仅限于父类。因此,只能促进隐藏而不是覆盖。
23. HashSet 与 TreeSet 的区别是什么?
尽管 HashSet 和 TreeSet 都不同步并确保不存在重复项,但还是有某些属性可以将 HashSet 与 TreeSet 区分开来。
- 实现:对于HashSet,哈希表用于以无序方式存储元素。然而,TreeSet 使用红黑树以排序的方式存储元素。
- 复杂性/性能:对于添加、检索和删除元素,HashSet 的时间分摊复杂度为 O(1)。TreeSet 执行相同操作的时间复杂度稍高,等于 O(log n)。总的来说,HashSet 的性能比 TreeSet 更快。
- 方法: hashCode() 和 equals() 是 HashSet 用来在对象之间进行比较的方法。相反,TreeSet 使用 compareTo() 和 compare() 方法来促进对象比较。
- 对象类型:可以借助 HashSet 存储异构和空对象。在 TreeSet 的情况下,插入异构对象或空对象时会发生运行时异常。
24. 为什么字符数组比字符串更适合存储机密信息?
在 Java 中,字符串基本上是不可变的,即它不能被修改。在它声明之后,只要不以垃圾的形式被移除,它就会继续留在字符串池中。换句话说,在执行字符串值处理后,字符串会在内存的堆部分驻留一段不受限制且未指定的时间间隔。
因此,如果黑客非法访问内存转储,黑客可能会因为进行有害活动而窃取重要信息。可以通过使用可变对象或结构(如字符数组)来存储任何变量来消除此类风险。字符数组变量的工作完成后,可以同时将变量配置为空白。因此,它有助于节省堆内存,也不会给黑客提取重要数据的机会。
25、Java中JVM、JRE、JDK的区别是什么?
标准 | JDK | JRE | JVM |
---|---|---|---|
缩写 | Java 开发工具包 | Java运行时环境 | Java虚拟机 |
定义 | JDK 是用于开发 Java 应用程序的完整软件开发工具包。它包括 JRE、JavaDoc、编译器、调试器等。 | JRE 是一个软件包,提供 Java 类库、JVM 和运行 Java 应用程序所需的所有组件。 | JVM 是一个依赖于平台的抽象机器,由 3 个规范组成——描述 JVM 实现要求的文档、满足 JVM 要求的计算机程序和用于执行 Java 字节码并提供运行时环境的实例对象。 |
主要目的 | JDK 主要用于代码开发和执行。 | JRE 主要用于创建环境来执行代码。 | JVM 为 JRE 的所有实现提供了规范。 |
提供的工具 | JDK 为代码开发提供了编译器、调试器等工具 | JRE 提供了 JVM 运行程序所需的库和类。 | JVM 不包含任何工具,而是提供了实现规范。 |
概括 | JDK = (JRE) + 开发工具 | JRE = (JVM) + 执行应用程序的库 | JVM = 执行 Java 字节码的运行时环境。 |
26、Java中HashMap和HashTable的区别是什么?
HashMap | HashTable |
---|---|
HashMap 不是同步的,因此更适合非线程应用程序。 | HashTable 是同步的,因此适用于线程应用程序。 |
只允许一个空键,但值中允许任意数量的空值。 | 这不允许在键或值中使用 null。 |
通过使用其子类 LinkedHashMap 支持插入顺序。 | HashTable 中不保证插入顺序。 |
27. 反射在 Java 中的重要性是什么?
- 该术语
reflection
用于描述代码对其自身或其系统的其他代码的检查能力,并在运行时对其进行修改。 - 考虑一个例子,我们有一个未知类型的对象,我们有一个方法“fooBar()”,我们需要在该对象上调用它。Java 的静态类型系统不允许这种方法调用,除非事先知道对象的类型。这可以使用反射来实现,它允许代码扫描对象并识别它是否有任何称为“fooBar()”的方法,然后仅在需要时调用该方法。
Method methodOfFoo = fooObject.getClass().getMethod("fooBar", null);
methodOfFoo.invoke(fooObject, null);
- 使用反射有其自身的缺点:
- 速度——由于反射引起的方法调用比直接方法调用慢大约三倍。
- 类型安全——当一个方法被错误地使用反射通过其引用调用时,调用会在运行时失败,因为它在编译/加载时没有被检测到。
- 可追溯性——每当反射方法失败时,由于巨大的堆栈跟踪,很难找到失败的根本原因。必须深入研究 invoke() 和 proxy() 方法日志以找出根本原因。
- 因此,建议遵循不涉及反射的解决方案并将此方法用作最后的手段。
28、线程的使用方式有哪些?
- 我们可以通过两种方式在java中定义和实现线程:
- 扩展 Thread 类
class InterviewBitThreadExample extends Thread{
public void run(){
System.out.println("Thread runs...");
}
public static void main(String args[]){
InterviewBitThreadExample ib = new InterviewBitThreadExample();
ib.start();
}
}
- 实现 Runnable 接口
class InterviewBitThreadExample implements Runnable{
public void run(){
System.out.println("Thread runs...");
}
public static void main(String args[]){
Thread ib = new Thread(new InterviewBitThreadExample());
ib.start();
}
}
- 使用 Runnable 接口的方法实现线程是更优选和有利的,因为 Java 不支持类的多重继承。
start()
方法用于为线程执行创建单独的调用堆栈。一旦创建了调用堆栈,JVM 就会调用该run()
调用堆栈中执行线程的方法。
29. Java常见面试题有哪些:Java 中类的构造函数和方法有什么区别?
构造函数 | 方法 |
---|---|
构造函数用于初始化对象状态。 | 方法用于暴露对象的行为。 |
构造函数没有返回类型。 | 方法应该有一个返回类型。即使它不返回任何内容,返回类型也是无效的。 |
构造函数被隐式调用。 | 必须在对象上显式调用方法。 |
如果未定义构造函数,则由 java 编译器提供默认构造函数。 | 如果未定义方法,则编译器不提供它。 |
构造函数名称应等于类名称。 | 方法名可以是任意名称,也可以是类名。 |
构造函数不能被标记为 final,因为无论何时继承一个类,构造函数都不会被继承。因此,将其标记为 final 是没有意义的。Java 抛出编译错误说 -modifier final not allowed here | 一个方法可以被定义为 final,但它不能在它的子类中被覆盖。 |
最终变量实例化在构造函数中是可能的,并且 this 的范围适用于整个类及其对象。 | 如果在方法内初始化,则最终变量可确保变量不能仅在该方法的范围内更改。 |
30. Java 会出现“值传递”还是“引用传递”现象?
Java 总是作为“传值”工作。Java 中没有所谓的“通过引用传递”。但是,当对象在任何方法中传递时,由于 Java 中对象处理的性质,传递的是值的地址。传递对象时,Java 创建引用的副本并将其传递给方法。对象指向相同的内存位置。方法内部可能会发生2种情况:
- 情况 1:当对象指向另一个位置时:在这种情况下,对该对象所做的更改在传递给方法之前不会反映原始对象,因为引用指向另一个位置。
例如:
class InterviewBitTest{
int num;
InterviewBitTest(int x){
num = x;
}
InterviewBitTest(){
num = 0;
}
}
class Driver {
public static void main(String[] args)
{
//create a reference
InterviewBitTest ibTestObj = new InterviewBitTest(20);
//Pass the reference to updateObject Method
updateObject(ibTestObj);
//After the updateObject is executed, check for the value of num in the object.
System.out.println(ibTestObj.num);
}
public static void updateObject(InterviewBitTest ibObj)
{
// Point the object to new reference
ibObj = new InterviewBitTest();
// Update the value
ibObj.num = 50;
}
}
Output:
20
- 情况 2:当对象引用未被修改时:在这种情况下,由于我们拥有指向同一内存位置的主对象引用的副本,因此对象内容的任何更改都会反映在原始对象中。
例如:
class InterviewBitTest{
int num;
InterviewBitTest(int x){
num = x;
}
InterviewBitTest(){
num = 0;
}
}
class Driver{
public static void main(String[] args)
{
//create a reference
InterviewBitTest ibTestObj = new InterviewBitTest(20);
//Pass the reference to updateObject Method
updateObject(ibTestObj);
//After the updateObject is executed, check for the value of num in the object.
System.out.println(ibTestObj.num);
}
public static void updateObject(InterviewBitTest ibObj)
{
// no changes are made to point the ibObj to new location
// Update the value of num
ibObj.num = 50;
}
}
Output:
50
31. 当数据需要进行大量更新时,应该优先选择String或String Buffer中的哪一个?
StringBuffer 本质上是可变的和动态的,而 String 是不可变的。String 的每次更新/修改都会创建一个新的 String,从而使字符串池中包含不必要的对象超载。因此,在大量更新的情况下,总是首选使用 StringBuffer,因为它会减少在字符串池中创建多个 String 对象的开销。
32. Java 中如何不允许类的属性序列化?
- 为了实现这一点,可以在使用
transient
关键字的同时声明属性,如下所示:
public class InterviewBitExample {
private transient String someInfo;
private String name;
private int id;
// :
// Getters setters
// :
}
- 在上面的例子中,除了
someInfo
可以序列化的所有字段。
33.如果静态修饰符没有包含在Java的main方法签名中,会发生什么?
不会有任何编译错误。但随后程序运行,由于JVM无法映射主方法签名,代码在运行时抛出“NoSuchMethodError”错误。
34.如果Java中一个类中有多个main方法会怎样?
程序无法编译,因为编译器说该方法已在类中定义。
35、你对Object Cloning的理解是什么,你是如何在Java中实现的?
- 它是创建任何对象的精确副本的过程。为了支持这一点,java 类必须实现 java.lang 包的 Cloneable 接口并覆盖 Object 类提供的 clone() 方法,其语法为:
protected Object clone() throws CloneNotSupportedException{
return (Object)super.clone();
}
- 如果未实现 Cloneable 接口而仅覆盖该方法,则会导致 Java 中的 CloneNotSupportedException。
36.异常如何在代码中传播?
Java面试题解析:当异常发生时,它首先搜索以定位匹配的 catch 块。如果找到匹配的 catch 块,则将执行该块。否则,异常会通过方法调用堆栈传播并进入调用者方法,在那里执行匹配 catch 块的过程。这种传播一直持续到找到匹配的 catch 块。如果未找到匹配项,则程序将在 main 方法中终止。
37. 在 try 块之后是否必须遵循 catch 块?
不,在 try 块之后没有必要存在 catch 块。- try 块之后应该跟一个 catch 块或一个 finally 块。如果异常可能性更大,则应使用方法的 throws 子句声明它们。
38. 当return语句写在try块和catch块的末尾时,finally块会不会被执行,如下图所示?
public int someMethod(int i){
try{
//some statement
return 1;
}catch(Exception e){
//some statement
return 999;
}finally{
//finally block statements
}
}
无论异常与否,finally 块都将被执行。不执行 finally 块的唯一情况是当它在 try/catch 块中的任何位置遇到“System.exit()”方法时。
39. 你能在另一个构造函数中调用一个类的构造函数吗?
是的,这个概念可以称为构造函数链接,可以使用this()
.
40. 连续的内存位置通常用于在数组中而不是在 ArrayList 中存储实际值。解释。
在 ArrayList 的情况下,无法以原始数据类型(如 int、float 等)的形式存储数据。ArrayList 中存在的数据成员/对象具有对位于内存中不同位置的对象的引用。因此,实际对象或非原始数据类型(如整数、双精度等)的存储发生在不同的内存位置。
然而,这同样不适用于数组。对象或原始类型值可以存储在连续内存位置的数组中,因此每个元素不需要对下一个元素的任何引用。
高级Java常见面试题和答案合集
41. 虽然继承是一个流行的 OOP 概念,但它不如组合有利。解释。
在以下场景中,继承落后于组合:
- Java 中无法实现多重继承。类只能从一个超类扩展。在需要多种功能的情况下,例如 - 将信息读取和写入文件,组合模式是首选。可以通过将作者和读者功能视为私有成员来使用作者和读者功能。
- 组合物有助于获得高灵活性并防止封装破裂。
- 单元测试可以通过组合而不是继承来实现。当开发者想要测试一个组成不同类的类时,可以创建 Mock Object 来表示组成的类,以方便测试。这种技术在继承的帮助下是不可能的,因为如果没有继承中超类的帮助,就无法测试派生类。
- 组合的松散耦合特性优于继承的紧耦合特性。
让我们举个例子:
package comparison;
public class Top {
public int start() {
return 0;
}
}
class Bottom extends Top {
public int stop() {
return 0;
}
}
在上面的例子中,遵循了继承。现在,对 Top 类进行了一些修改,如下所示:
public class Top {
public int start() {
return 0;
}
public void stop() {
}
}
如果按照Top类的新实现,Bottom类必然会出现编译时错误。Top.stop() 函数存在不兼容的返回类型。必须对 Top 或 Bottom 类进行更改以确保兼容性。然而,组合技术可以用来解决给定的问题:
class Bottom {
Top par = new Top();
public int stop() {
par.start();
par.stop();
return 0;
}
}
42. 使用 new() 创建 String 与使用文字创建 String 有何不同?
当字符串在赋值运算符的帮助下形成为文字时,它会进入字符串常量池,以便可以进行字符串实习。如果两个对象的内容相同,则堆中的同一个对象将被不同的字符串引用。
public bool checking() {
String first = "InterviewBit";
String second = "InterviewBit";
if (first == second)
return true;
else
return false;
}
当两个变量引用相同的内容时,checking() 函数将返回 true。
相反,当在 new() 运算符的帮助下形成字符串时,不会发生实习。即使存在相同的内容对象,该对象也会在堆内存中创建。
public bool checking() {
String first = new String("InterviewBit");
String second = new String("InterviewBit");
if (first == second)
return true;
else
return false;
}
由于两个变量未引用相同的内容,因此checking() 函数将返回false。
43. 尽管有垃圾收集器,程序中是否可能超过内存限制?
是的,尽管存在垃圾收集器,程序仍有可能耗尽内存。垃圾收集有助于识别和消除程序中不再需要的对象,以释放它们使用的资源。
在程序中,如果某个对象不可访问,则垃圾收集的执行将针对该对象进行。如果创建新对象所需的内存量不足,则在垃圾收集器的帮助下为不再在范围内的对象释放内存。当释放的内存不足以创建新对象时,就会超出程序的内存限制。
此外,如果对象的创建方式使其保留在作用域中并消耗内存,则会耗尽堆内存。开发人员应确保在完成工作后取消引用该对象。尽管垃圾收集器尽最大努力尽可能多地回收内存,但仍然可能超出内存限制。
让我们看一下下面的例子:
List<String> example = new LinkedList<String>();
while(true){
example.add(new String("Memory Limit Exceeded"));
}
44. 为什么需要同步?借助相关示例进行解释。
通过同步可以同时执行不同的进程。当多个线程共享特定资源时,可能会出现多个线程需要相同共享资源的情况。
同步有助于解决问题,资源一次由单个线程共享。让我们举个例子来更清楚地理解它。例如,你有一个 URL,你必须找出对它发出的请求数。两个同时请求可能会使计数不稳定。
无同步:
package anonymous;
public class Counting {
private int increase_counter;
public int increase() {
increase_counter = increase_counter + 1;
return increase_counter;
}
}
如果线程 Thread1 将计数视为 10,则将其增加 1 到 11。同时,如果另一个线程 Thread2 将计数视为 10,则将其增加 1 到 11。因此,计数值不一致,因为预期的最终值是 12,但我们得到的实际最终值将是 11。
现在,函数increase() 是同步的,因此不能同时访问。
同步:
package anonymous;
public class Counting {
private int increase_counter;
public synchronized int increase() {
increase_counter = increase_counter + 1;
return increase_counter;
}
}
如果线程 Thread1 将计数视为 10,它将增加 1 到 11,然后线程 Thread2 将看到计数为 11,它将增加 1 到 12。因此,计数值发生了一致性。
45. 在下面给定的代码中,...的意义是什么?
public void fooBarMethod(String... variables){
// method code
}
- 能够提供
...
一个称为 varargs(可变参数)的特性,它是作为 Java 5 的一部分引入的。 ...
上面例子中的函数表明它可以接收数据类型字符串的多个参数。- 例如, fooBarMethod 可以通过多种方式调用,我们仍然可以使用一种方法来处理数据,如下所示:
fooBarMethod("foo", "bar");
fooBarMethod("foo", "bar", "boo");
fooBarMethod(new String[]{"foo", "var", "boo"});
public void myMethod(String... variables){
for(String variable : variables){
// business logic
}
}
46.你能解释一下Java线程生命周期吗?
Java线程生命周期如下:
- New – 当线程的实例被创建并且 start() 方法未被调用时,线程被认为是活动的,因此处于 NEW 状态。
- Runnable – 一旦 start() 方法被调用,在 JVM 调用 run() 方法之前,线程被称为处于 RUNNABLE(准备运行)状态。这种状态也可以从线程的 Waiting 或 Sleeping 状态进入。
- 运行- 当 run() 方法被调用并且线程开始执行时,线程被称为处于 RUNNING 状态。
- 不可运行(阻塞/等待) ——当线程尽管处于活动状态而无法运行时,该线程被称为处于不可运行状态。理想情况下,在其活跃一段时间后,线程应该进入可运行状态。
- 如果一个线程想要进入同步代码,但它无法进入,则称该线程处于阻塞状态,因为另一个线程正在同一对象的同步块中运行。第一个线程必须等到另一个线程退出同步块。
- 如果一个线程正在等待来自另一个线程的信号执行,则它被称为处于等待状态,即它等待工作直到接收到信号。
- 终止- 一旦 run() 方法执行完成,线程被认为进入了 TERMINATED 步骤并被认为是不活跃的。
下面的流程图清楚地解释了 Java 中线程的生命周期。
47. 使用无序数组与使用有序数组之间的权衡是什么?
- 拥有有序数组的主要优点是降低了搜索时间复杂度 ,
O(log n)
而无序数组的时间复杂度为O(n)
。 - 有序数组的主要缺点是其插入时间增加了 O(n),因为它的元素必须在每次插入期间重新排序以保持数组的顺序,而无序数组的时间复杂度仅为 O(1 )。
- 考虑到以上2个关键点,根据开发者需要什么样的场景,可以使用合适的数据结构来实现。
48. 是否可以在 Java 中两次导入同一个类或包,在运行时会发生什么?
可以多次导入一个类或包,但是,这是多余的,因为 JVM 在内部只加载一次包或类。
49.如果一个包有子包,是否只导入主包就可以了?例如,导入 com.myMainPackage.* 是否也导入 com.myMainPackage.mySubPackage.*?
这是一个很大的问题。我们需要明白,一个包的子包的导入需要显式的完成。导入父包只会导致导入其中的类,而不是其子/子包的内容。
50.如果代码System.exit(0)写在try块的末尾会不会执行finally块?
不。程序 post 的控制System.exit(0)
立即消失,程序终止,这就是 finally 块永远不会执行的原因。
51、Java中的标记接口是怎么理解的?
标记接口,也称为标记接口,是那些没有定义方法和常量的接口。它们用于帮助编译器和 JVM 获取有关对象的运行时相关信息。
52.解释Java中的术语“双括号初始化”?
这是在 Java 中初始化任何集合的便捷方式。考虑下面的例子。
import java.util.HashSet;
import java.util.Set;
public class IBDoubleBraceDemo{
public static void main(String[] args){
Set<String> stringSets = new HashSet<String>()
{
{
add("set1");
add("set2");
add("set3");
}
};
doSomething(stringSets);
}
private static void doSomething(Set<String> stringSets){
System.out.println(stringSets);
}
}
在上面的例子中,我们看到 stringSets 是使用双括号初始化的。
- 第一个大括号的任务是创建一个匿名内部类,该类具有访问父类行为的能力。在我们的示例中,我们正在创建 HashSet 的子类,以便它可以使用 HashSet 的 add() 方法。
- 第二个大括号执行初始化实例的任务。
通过此方法初始化时应小心,因为该方法涉及创建匿名内部类,这可能会在垃圾收集或序列化过程中引起问题,也可能导致内存泄漏。
53、Java常见面试题有哪些:为什么说String类的length()方法返回的结果不准确?
- length 方法返回 String 的 Unicode 单元数。让我们了解什么是 Unicode 单位以及下面的混淆是什么。
- 我们知道 Java 使用 UTF-16 来表示字符串。有了这个 Unicode,我们就需要了解下面两个 Unicode 相关的术语:
- 代码点:这表示一个整数,表示代码空间中的一个字符。
- 代码单元:这是用于编码代码点的位序列。为此,可能需要一个或多个单元来表示一个代码点。
- 在 UTF-16 方案下,代码点在逻辑上分为 17 个平面,第一个平面称为基本多语言平面 (BMP)。BMP 具有经典字符 - U+0000 到 U+FFFF。其余的字符 - U+10000 到 U+10FFFF 被称为补充字符,因为它们包含在其余平面中。
- 来自第一个平面的代码点使用一个16 位代码单元进行编码
- 其余平面的代码点使用两个代码单元进行编码。
现在,如果字符串包含补充字符,则长度函数会将其计为 2 个单位,并且 length() 函数的结果将与预期不同。
换句话说,如果有 2 个单位的 1 个增补字符,则该单个字符的长度被认为是两个 - 注意这里的不准确吗?根据java文档,这是预期的,但根据实际逻辑,它是不准确的。
54. 下面代码的输出是什么,为什么?
public class InterviewBit{
public static void main(String[] args)
{
System.out.println('b' + 'i' + 't');
}
}
如果在双引号(或字符串文字)中使用字母,则“位”将是打印的结果。但问题是使用了字符文字(单引号),这就是为什么不会发生连接。将添加每个字符的相应 ASCII 值,并打印该总和的结果。
'b'、'i'、't' 的 ASCII 值是:
- ‘b’ = 98
- ‘i’ = 105
- ‘t’ = 116
98 + 105 + 116 = 319
因此将打印 319。
55. 在 Java 中使对象符合垃圾回收 (GC) 条件的可能方法有哪些?
第一种方法:一旦达到对象创建目的,将对象引用设置为 null。
public class IBGarbageCollect {
public static void main (String [] args){
String s1 = "Some String";
// s1 referencing String object - not yet eligible for GC
s1 = null; // now s1 is eligible for GC
}
}
第二种方法:将引用变量指向另一个对象。这样做,引用变量之前引用的对象将有资格进行 GC。
public class IBGarbageCollect {
public static void main(String [] args){
String s1 = "To Garbage Collect";
String s2 = "Another Object";
System.out.println(s1); // s1 is not yet eligible for GC
s1 = s2; // Point s1 to other object pointed by s2
/* Here, the string object having the content "To Garbage Collect" is not referred by any reference variable. Therefore, it is eligible for GC */
}
}
第三种方法:孤岛方法:当2个引用变量指向同一个类的实例,并且这些变量只相互引用,而这2个变量所指向的对象没有任何其他引用时,则称有形成了一个“隔离岛”,这两个对象有资格进行 GC。
public class IBGarbageCollect {
IBGarbageCollect ib;
public static void main(String [] str){
IBGarbageCollect ibgc1 = new IBGarbageCollect();
IBGarbageCollect ibgc2 = new IBGarbageCollect();
ibgc1.ib = ibgc2; //ibgc1 points to ibgc2
ibgc2.ib = ibgc1; //ibgc2 points to ibgc1
ibgc1 = null;
ibgc2 = null;
/*
* We see that ibgc1 and ibgc2 objects refer
* to only each other and have no valid
* references- these 2 objects for island of isolcation - eligible for GC
*/
}
}
编程问题:Java常见面试题和答案合集
56. 使用递归检查给定的字符串是否是回文。
/*
* Java program to check if a given inputted string is palindrome or not using recursion.
*/
import java.util.*;
public class InterviewBit {
public static void main(String args[]) {
Scanner s = new Scanner(System.in);
String word = s.nextLine();
System.out.println("Is "+word+" palindrome? - "+isWordPalindrome(word));
}
public static boolean isWordPalindrome(String word){
String reverseWord = getReverseWord(word);
//if word equals its reverse, then it is a palindrome
if(word.equals(reverseWord)){
return true;
}
return false;
}
public static String getReverseWord(String word){
if(word == null || word.isEmpty()){
return word;
}
return word.charAt(word.length()- 1) + getReverseWord(word.substring(0, word.length() - 1));
}
}
57. 编写一个 Java 程序来检查两个字符串是否是字谜。
Java面试题解析:主要思想是验证字符串的长度,如果发现相等,则将字符串转换为字符数组,然后对数组进行排序并检查两者是否相等。
import java.util.Arrays;
import java.util.Scanner;
public class InterviewBit {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
//Input from two strings
System.out.print("First String: ");
String string1 = s.nextLine();
System.out.print("Second String: ");
String string2 = s.nextLine();
// check for the length
if(string1.length() == string2.length()) {
// convert strings to char array
char[] characterArray1 = string1.toCharArray();
char[] characterArray2 = string2.toCharArray();
// sort the arrays
Arrays.sort(characterArray1);
Arrays.sort(characterArray2);
// check for equality, if found equal then anagram, else not an anagram
boolean isAnagram = Arrays.equals(characterArray1, characterArray2);
System.out.println("Anagram: "+ isAnagram);
}
}
58. 编写一个 Java 程序来找出给定数字的阶乘。
public class FindFactorial {
public static void main(String[] args) {
int num = 10;
long factorialResult = 1l;
for(int i = 1; i <= num; ++i)
{
factorialResult *= i;
}
System.out.println("Factorial: "+factorialResult);
}
}
59. 给定一个从 1 到 n 的非重复数字数组,其中缺少一个数字,编写一个高效的 java 程序来找到那个丢失的数字。
想法是使用公式找到n个自然数的总和,然后找到给定数组中数字的总和。减去这两个总和得出的数字是实际缺失的数字。这导致 O(n) 时间复杂度和 O(1) 空间复杂度。
public class IBMissingNumberProblem {
public static void main(String[] args) {
int[] array={4,3,8,7,5,2,6};
int missingNumber = findMissingNum(array);
System.out.println("Missing Number is "+ missingNumber);
}
public static int findMissingNum(int[] array) {
int n=array.length+1;
int sumOfFirstNNums=n*(n+1)/2;
int actualSumOfArr=0;
for (int i = 0; i < array.length; i++) {
actualSumOfArr+=array[i];
}
return sumOfFirstNNums-actualSumOfArr;
}
}
60. 编写一个 Java 程序来检查任何数字是否是幻数。如果在每个步骤中进行数字总和并依次进行该总和的数字总和后,最终结果(仅剩一位数字时)为 1,则称该数字为幻数。
例如,考虑数字:
- 第 1 步:163 => 1+6+3 = 10
- 第 2 步:10 => 1+0 = 1 => 因此 163 是一个幻数
public class IBMagicNumber{
public static void main(String[] args) {
int num = 163;
int sumOfDigits = 0;
while (num > 0 || sumOfDigits > 9)
{
if (num == 0)
{
num = sumOfDigits;
sumOfDigits = 0;
}
sumOfDigits += num % 10;
num /= 10;
}
// If sum is 1, original number is magic number
if(sumOfDigits == 1) {
System.out.println("Magic number");
}else {
System.out.println("Not magic number");
}
}
}
结论
61. 结论
Java 是一种简单的高级语言,它提供了应用程序开发所需的强大工具和令人印象深刻的标准。它也是最早为处理基于并发的问题提供惊人的线程支持的语言之一。Java 易于使用的语法和内置特性,再加上它为应用程序提供的稳定性,是这种语言在软件社区中得到越来越多使用的主要原因。