五年经验Java面试题

2025年3月30日 | 15分钟阅读

在本节中,我们将介绍对拥有5年经验的候选人提出的常见面试问题。

Q1. 区分Java中的Transient和Volatile变量。

瞬态变量Volatile变量
当不希望某个变量被序列化时,使用transient关键字。变量名称前的volatile关键字表示变量中的所有内容都存储在主内存中。因此,所有对该变量的读取都必须通过主内存进行,而不是从CPU缓存中进行,并且所有写入也必须写入主内存。
Transient关键字为对象的属性提供了一种控制和灵活性,使其不会被序列化。volatile关键字保证Java虚拟机不会对变量进行重排序,并确保避免了与同步相关的问题。
在反序列化发生时,用transient关键字修饰的变量将被分配为其数据类型的默认值。volatile变量没有分配默认值。
Static关键字不能与transient变量一起使用。这是因为静态变量直接与类相关,而不是与类的实例相关,这在序列化过程中非常重要。可以使用static关键字与volatile关键字一起使用。

Q2. 观察以下代码并回答以下问题。

我们得到了两个线程:Th1和Th3。Th1正在访问method1()方法。Th3能否在同一时间访问method2()方法,并且在同一个实例上?

答案:是的,Th3可以访问method2(),因为它没有被synchronized关键字修饰,因此不需要锁定就可以访问它。


Q3. 解释在以下方法参数中…的意义。

在Java 5中,引入了3个点(...)特性。该特性也称为varargs(表示可变数量的参数)。它意味着该方法可以接收一个或多个字符串,如下所示:

fooMethod("Tpoint", "T", "ech");

fooMethod("Interview", "Java", "Questions");

fooMethod(new String[]{"Questions", "of", "Java", "Interview", "Questions"});

这些接收的参数可以用作数组,并通过循环迭代来访问,如下所示:

通过循环访问,如下所示:


Q4. 区分Java中的ArrayList和Vector。

Vector和ArrayList是集合的类。这两个类都派生自AbstractList并实现List接口。

ArrayListVector
ArrayList既不同步也不线程安全。Vector默认是同步的且线程安全的。这意味着即使多个线程同时操作,类的内部状态也不会受到影响。
由于ArrayList不同步,因此它的工作速度比Vector快。Vector附带同步的开销。因此,它比ArrayList慢。

Q5. 解释equals()和hashCode()契约的意义。

考虑HashMap对象的一个场景。众所周知,HashMap的键使用hashCode()和equals()方法来查找索引或查找键的值。因此,需要正确实现这些方法。如果这些方法未正确实现,则hashMap将无法正常工作。HashMap将错误地选择键来更新值。因此,正确实现equals()和hashCode()方法很重要。只有当我们正确遵循hashCode-equals契约时,才能正确完成。

hashCode-equals契约规定:

如果两个对象相同或相等,则hashCode()方法应始终为这两个对象生成相同的结果。

为了确保上述契约,每当重写equals()方法时,都必须重写hashCode()方法。


Q6. 如果运行以下打印语句,将在控制台上打印什么?

System.out.println(1.0/0.0);

值1.0或0.0是double值。Double类有一组特定的规则,例如-0.0、NaN、Double.INFINITY等,用于支持算术计算。上述打印语句将在控制台上打印Infinity,而不会给出任何算术异常。


Q7. 路径变量和类路径变量之间有什么区别?

在操作系统中,存在path变量,用于定位系统可执行文件。classpath变量用于查找.class文件,并与Java可执行文件相关。


Q8. 获得以下代码的结果。同时,找出结果的原因。

上述程序的输出是“String方法被调用。”。

众所周知,Java中的任何引用类型对象都可以赋值为null。因此,对象o和字符串str都可以接受null值。但是,输出仅与接受字符串作为参数的方法meth1()的打印语句有关。原因是Java编译器的特性。Java编译器会选择具有更具体参数的方法,在本例中是String。请注意,Object类是Java中每个类的父类。


Q9. 在给定的两种调用wait()方法的_方法中,确定更合适的方法。

1) 使用循环结构

2) 使用if结构

第一种方法更合适。这是因为应该使用循环结构调用wait()方法。这是因为当任何线程获得所需的资源以重新开始执行时,建议在开始执行之前验证条件。

以下是实现方式:


Q10. 在多线程环境中,可以使用HashMap吗?

是的,可以在多线程环境中进行HashMap的使用。但是HashMap是否能正常工作取决于用户。如果HashMap的初始化仅由一个线程完成,而其他线程仅执行HashMap的读取操作,那么HashMap将正常工作。

问题出现在当其中一个线程进行更新操作,如删除、更新或添加映射内容时。因此,调整HashMap的大小可能导致无限循环或死锁。在这种情况下,可以使用ConcurrentHashMap或HashTable。


Q11. 找出以下代码的结果。同时,找出其原因。

输出

System.out.println(0.2 == 0.1*2); // gives a true value
System.out.println(0.3 == 0.1*3); // gives a false value

原因:上述输出是由于浮点数舍入错误造成的。只有2的幂次的浮点数才能通过二进制表示精确表示。其他则被舍入。因此,第一个打印语句给出true值,第二个打印语句给出false值。


Q12. 评论以下陈述。

所有不可变对象都必须声明为final。

答案是:并非所有不可变对象都必须声明为final。通过将类成员声明为private且不定义setter方法来更新或修改值,可以实现不变性。对于引用成员,确保它们不会泄露到类外部。可以通过参数化构造函数初始化引用成员。请注意,用final关键字修饰的变量仅阻止重新分配给其他值。它不能保证对象各个方面不会被更改。


Q13. 简要讨论工厂设计模式。

工厂设计模式是软件行业中最常用的模式之一。工厂设计模式属于创建型模式。它用于根据需求创建不同类型的对象。对象的创建逻辑不对客户开放。此模式的实现使用接口。工厂设计模式允许子类决定实例化哪个类。


Q14. 区分使用new()运算符和字符串字面量创建字符串。

使用字符串字面量创建对象时,字符串在字符串池中创建;而使用new()运算符创建对象时,字符串在堆中创建。请注意,在堆中,字符串池是永久区的_部分。通过字面量创建的许多(相同值的)字符串具有相同的值并指向同一个对象。因此,它阻止了对象重复。


Q15. 给出以下代码的输出及解释。

输出

0.0

原因:乍一看,似乎打印了Double.MIN_VALUE的值,因为最小值应该是最小的负值。然而,对于Double来说并非如此。在Java中,Double类型具有MAX_VALUE & MIN_VALUE,两者都是正数。因此,Double.MIN_VALUE大于0.0。


Q16. 列出Java 8的一些重要特性。

Java 8的一些重要特性如下:

  • JDBC和IO增强
  • StringJoiner & Collectors类
  • 时间/日期API
  • 接口中默认方法的引入
  • 函数式接口
  • Optional类
  • Lambda 表达式
  • Stream API
  • forEach方法

Q17. 在单例模式中,双重锁定有什么意义?

双重锁定的意义在于使其线程安全。

假设两个线程(Th1和Th3)验证了第一个if条件(if(instance == null)),然后到达了用于null的同步块(synchronized (Singleton.class)),并且两者都到达了synchronized (Singleton.class)。Th1获得锁的访问权限并创建一个Singleton实例,然后返回。现在Th3进入同步块,在同步块中再次检查if条件,发现instance不为null。因此,不会再次发生实例化。


Q18. 讨论依赖注入及其在面向对象编程中的意义。

当一个软件组件依赖于其他资源来完成其预期目的时,它需要知道它应该与哪些资源通信,在哪里找到它们,以及如何与它们通信。

当软件的一个组件依赖于其他资源来完成其工作时,该组件必须知道如何与适当的资源通信。

一种方法是将代码结构化,以便为组件所需的每个资源进行适当的映射。另一种方法是使用依赖注入,并让外部代码负责定位资源。通常,该外部代码使用框架实现,例如Spring Framework。

依赖注入有助于生成松散耦合的结构,同时遵循SOLID原则。通过依赖注入增强了代码的可重用性。

一种代码结构方式是映射每个必需资源的_位置。另一种方式是使用依赖注入,并让外部代码承担定位资源的责任。通常,外部代码是通过使用框架来实现的,例如Java应用程序的Spring Framework。

依赖注入有助于创建松散耦合的程序,同时遵循SOLID软件设计原则。它有助于提高代码的可重用性,同时减少更改类、方法模板或对象变量的频率。由于程序是松散耦合的,因此有助于开发可测试的代码。


Q19. 预测以下程序的输出。并给出原因。

输出

false
true

解释:第一个打印语句打印false,即使值相同。这是因为n1和n2指向不同的对象。因此,引用不同,它们的比较结果为false。如果继续按此逻辑,第二个打印语句也应显示false值。然而,这是一个true值。这是因为Integer类有一个私有的内部类,称为IntegerCache类。IntegerCache类缓存范围在-128到127之间的Integer对象。因此,当我们定义Integer n3 = 10时,它在内部会被转换为

Integer n3 = Integer.valueOf(10)。Integer类的valueOf()方法定义为

从上面的valueOf()方法的定义可以看出,当值在范围(-128到127)内时,执行第一个return语句,因此返回缓存中的实例。当num的值超出范围时,执行第二个return语句,导致创建一个全新的实例。因此,对于n3和n4引用,将返回缓存中的实例,从而使比较生成true值。


Q20. main方法可以被重载吗?

是的,main方法可以被重载。像对待其他方法一样对待它。我们重载其他方法的方式就是重载main方法。JVM搜索方法签名来启动程序。普通的main方法充当程序的入口点,JVM首先执行普通的main方法。请注意,程序无法执行重载的main方法,除非我们在程序中显式调用该重载的main方法。


Q21. 区分枚举(Enumeration)和迭代器(Iterator)。

Enumeration和Iterator之间的区别在于Enumeration没有remove()方法,而Iterator有remove()方法。因此,使用Iterator可以对对象进行操作,因为可以删除或添加集合中的对象。枚举只能进行对象遍历和获取,其行为类似于只读接口。此外,Enumeration是一个遗留接口,而Iterator不是遗留接口。Enumeration仅适用于遗留类,并且不是通用光标。Iterator适用于所有集合类,并且是通用光标。


Q22. 描述EnumSet。

EnumSet主要为枚举类型的对象实现了Set接口。EnumSet类扩展了AbstractSet类并实现了Set接口。EnumSet不同步。EnumSet类是Collections Framework的一部分。EnumSet类用于创建包含特定元素类型中所有元素的枚举集。


Q23. 解释SerialVersionUID。

当对对象进行序列化时,会在该对象上打上版本ID号。这就是SerialVersionUID。在反序列化过程中使用它来验证将要反序列化的对象是否是正确的对象。


Q24. 在基于哈希的集合中,说明负载因子的默认大小。

当负载因子增加时,容量会增加,以维护HashMap,如果初始容量与当前容量的比率超过阈值。操作复杂度为O(1)。操作复杂度为O(1)表示插入和检索消耗常量时间。负载因子的默认大小为0.75。


Q25. 区分fail-fast和fail-safe。

区别如下:

Fail - FastFail - Safe
当对象在迭代过程中被修改时,会抛出ConcurrentModificationException。不会抛出异常。
对于处理,Fail - Fast需要较少的内存。对于处理,Fail - Safe需要更多的内存。
在迭代过程中,不创建克隆对象。在迭代过程中,创建克隆对象或副本。
Fail - Fast速度快。与Fail - Fast相比,Fail - Safe稍慢。
在Fail - Fast的迭代过程中,不允许修改。在Fail - Safe中,允许修改。
HashSet, HashMap, Vector, ArrayList等。CopyOnWriteArrayList, ConcurrentHashMap等。

Q26. 讨论IdentityHashMap。

IdentityHashMap通过HashMap实现Map接口。它使用引用相等性而不是对象相等性来比较键和值。该类确实实现了Map接口,但有意打破了Map的通用契约,该契约要求对象是通过equals()方法进行比较的。当用户允许通过引用比较对象时,就会使用该类。该类位于java.util包中。

有关该类的更多信息,请单击此处


Q27. 提及Spring AOP的优点和局限性。

使用Spring AOP最大的优点之一是其配置非常简单。此外,不需要单独的类加载器或单独的编译单元。它还具有其他优点,例如使用@aspetJ创建切面或将横切关注点集成到类或XML注解中。至于局限性,通常会发现AOP框架中的代码调试非常耗时。另外,切面无法通知其他切面。这是因为当一个类被标记为切面时,Spring框架会阻止它被自动代理。


Q28. Properties类是什么?

Properties类是HashTable的子类。Properties类保存一个值列表,其值是字符串,其键也是字符串。

Properties类的特性

  • HashTable的子类。
  • Properties文件用于在一个值列表(其中值和键都是字符串)中存储和获取字符串数据类型。
  • 如果主属性列表不包含指定的键属性,则会查找默认属性列表。
  • 可以在多个对象之间共享对象,而无需外部同步。
  • 使用Properties类检索系统属性。

Q29. 预测以下代码的输出。

说明

乍一看,似乎程序会在控制台上打印16。然而,事实并非如此。程序在编译时会产生编译错误。这是因为print语句2。在语句s = s + 8;中,数字8的数据类型是int,我们将其赋值给short变量,这是不正确的。这是因为int的容量大于short数据类型的容量。当我们编译上面的代码时,会得到类似以下的错误:

输出

/JTPProgram.java:7: error: incompatible types: possible lossy conversion from int to short
s = s + 8;
      ^
1 error

Q30. 提及与线程一起使用的5个最佳实践。

5个最佳实践是:

1) 提及线程的名称

2) 将线程和任务分开。使用Callable或Runnable与线程池执行器。

3) 使用线程池

4) 使用volatile关键字向编译器指示可见性、排序和原子性。

5) 避免使用本地线程变量。这是因为ThreadLocal类使用不当可能导致内存泄漏。


Q31. 何时应使用享元模式?

享元模式通过共享对象来支持大量对象,而无需实际创建过多的对象。为了使用享元模式,需要确保对象是不可变的,以便可以安全地共享它们。Long和Integer对象的池以及String池是享元模式的示例。


Q32. SAX解析器和DOM解析器有什么区别?

SAX解析器是事件解析器,因此不会将完整的XML加载到内存中,而DOM解析器会将完整的XML加载到内存中以创建基于树的DOM模型。这有助于定位节点并更改XML的结构。正因为如此,SAX比DOM慢。DOM比SAX需要更多的内存,并且不适合解析大型XML文件。


下一主题#