经典Java面试题收集(二)

当前位置: 钓虾网 > 圈子 > 经典Java面试题收集(二)

经典Java面试题收集(二)

2024-11-16 作者:钓虾网 1

21、JVM如何加载class文件?

经典Java面试题收集(二)

JVM中的类加载任务是由类加载器(ClassLoader)及其子类来完成的。这是一个关键的Java运行时组件,负责在运行时期查找并装载class文件。由于Java的跨平台特性,编译后的Java程序并不是可执行文件,而是一或多个class文件。当我们的Java程序需要使用某个类时,JVM会确保该类已被加载、连接(包括验证、准备和解析)并初始化。

类的加载过程开始于读取.class文件到内存,通常是通过创建一个字节数组来读取。之后,会产生与所加载类对应的Class对象。但此时的类还不完整,需要经过连接阶段,包括验证、准备(为静态变量分配内存并设置默认值)和解析(将符号引用转换为直接引用)。JVM对类进行初始化。

类的加载由多种类加载器完成,包括根加载器(Bootstrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。从Java 2(JDK 1.2)开始,类加载过程采用了父亲委托机制(PDM),以确保Java平台的安全性。在这个机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器进行,只有在父类加载器无法完成加载时,才由子类加载器自行完成。

22、char型变量能否存储中文汉字?

当然可以。Java使用Unicode编码,这意味着char类型可以存储任何Unicode字符,包括中文汉字。一个char类型占据2个字节(16位),足以存储一个中文汉字。

值得注意的是,在JVM内部和外部,字符的表现形式是不同的。在JVM内部,字符都是Unicode;但当它们被转移到外部(例如,保存到文件系统中)时,需要进行编码转换。Java有字节流和字符流,以及用于在这两者之间进行转换的转换流,如InputStreamReader和OutputStreamReader。这些类负责处理编码转换的任务。

23、抽象类(abstract class)和接口(interface)有何异同?

抽象类和接口都不能被实例化,但都可以定义引用类型。一个类如果继承自抽象类或者实现了接口,都需要实现其中的所有抽象方法(如果有的话)。抽象类和接口在多个方面存在差异。

抽象类中可以包含构造器、抽象方法和具体方法,而接口中则全是抽象方法,不能定义构造器。抽象类中的成员可以是private、默认、protected或public的,而接口中的成员默认都是public的。虽然两者都可以定义成员变量,但在接口中定义的成员变量实际上是常量(即public static final)。有抽象方法的类必须被声明为抽象类,但抽象类不一定需要有抽象方法。

24、静态嵌套类(Static Nested Class)和内部类(Inner Class)有何不同?

静态嵌套类是声明为static的内部类,它可以不依赖于外部类实例被实例化。而常规的内部类需要在外部类的实例基础上进行实例化。语法上看,内部类的实例化方式相对特殊。静态嵌套类是独立于外部类的类,它更像是一个顶级类的独立存在。而内部类是外部类的一部分,与外部类紧密相连。您的代码大致上是没有问题的,但在Java中,非静态内部类的实例化确实需要通过其外部类的实例来完成。这是因为在非静态内部类中,可以访问外部类的所有成员(包括私有成员),所以需要通过外部类的一个对象来创建内部类的一个对象。但是在您的代码中,有一个小地方需要注意。

在测试代码部分,您正确地使用了外部类的对象 `poker` 来创建内部类 `Card` 的对象 `c1`,这是正确的做法。您又试图直接通过 `Poker.Card` 来创建一个新的 `Card` 对象 `c2`,这是不正确的。因为 `Card` 是一个非静态内部类,不能直接通过类名来创建实例。正确的做法应该是通过外部类的实例来创建内部类的实例。这里的代码会产生编译错误。

修改后的测试代码如下:

```java

class PokerTest {

public static void main(String[] args) {

Poker poker = new Poker();

poker.shuffle(); // 洗牌

// 通过外部类实例创建内部类实例

Poker.Card c1 = poker.deal(0); // 发第一张牌

Poker.Card c2 = poker.new Card("红心", 1); // 通过外部类实例创建新的Card对象

System.out.println(c1); // 洗牌后的第一张

System.out.println(c2); // 打印: 红心A

}

}

```

而对于第二种方式,通过序列化和反序列化实现深度克隆,可以确保所有的对象都被完全复制,包括嵌套的对象。这种方法更为复杂,但更为强大和可靠。在序列化和反序列化过程中,Java会自动处理所有的对象复制工作,无需我们手动去复制每一个字段。这种方法还可以利用Java的泛型机制来检查对象是否支持序列化,这一检查是在编译时完成的,而不是在运行时抛出异常。这是一种更优雅、更安全的方式来实现对象的克隆。

以下是关于这两种方式的代码示例:

首先是关于如何实现浅克隆的示例代码:

然后是使用序列化和反序列化实现深度克隆的示例代码:

在这段代码中,我们定义了一个Person类和一个Car类,Person类中包含一个Car类型的成员变量。我们通过序列化和反序列化实现了Person对象的深度克隆,这意味着克隆的Person对象不仅自身被复制了,其内部的Car对象也被复制了。这种深度克隆的方式可以确保原始对象和克隆对象之间的数据不会相互干扰。我们还展示了基于序列化和反序列化实现的克隆的优势,包括它可以检测出对象是否支持序列化,以及在编译时完成检查而非运行时抛出异常等。

序列化和反序列化是一种强大且可靠的方式来实现深度克隆,尤其适用于需要复制复杂对象结构的情况。答:匿名内部类可以继承其他类或者实现接口。由于匿名内部类没有名字,它必须借助接口或者抽象类来定义。它可以继承一个具体的类或者实现一个或多个接口。匿名内部类的使用场景通常是在需要实现某个接口或继承某个类的情况下,但是只需要使用一次,因此不需要为其命名。

35、关于内部类与包含类(外部类)的关系

内部类是否能够访问外部类的成员呢?答案是肯定的。一个内部类对象可以轻松地访问创建它的外部类对象的所有成员,无论是私有还是非私有。这种紧密的关联为开发者提供了极大的便利,尤其在Swing编程和Android开发中,我们经常利用这种方式来实现事件监听和回调。

36、Java中的final关键字的多重身份

在Java的世界里,final关键字是个多才多艺的角色。它可以担任多种角色:

当它修饰一个类时,这个类就无法被其他类所继承,即它充当了终结者的角色。

当它修饰一个变量时,这个变量就变成了常量,一旦赋值,就不能再更改。

37、关于类加载与执行流程的小揭秘

当执行以下代码时,会发生什么呢?让我们一步步揭晓:

我们有一个A类和一个B类,B是A的子类。当创建B的实例时,发生了什么呢?答案是:先初始化静态成员(输出“1a”),然后调用父类(A类)的构造器(输出“2”),最后才是B类的构造器(输出“b”)。当你运行这段代码时,答案是:`1a2b2b`。如果对这个流程还不熟悉,那就再去复习一下Java的类加载和执行机制吧。

38、数据类型间的华丽转身

有时,我们需要让字符串和基本数据类型进行互换。这怎么做呢?

将字符串转换为基本数据类型,可以调用基本数据类型对应的包装类中的方法,如`Integer.parseInt()`或`Double.parseDouble()`。而将基本数据类型转为字符串,一种方法是使用连接符(+)与空字符串连接;另一种方法是使用String类中的`valueOf()`方法。

39、字符串的魔法反转与替换

你是否想过如何优雅地反转和替换字符串?除了常见的String或StringBuffer/StringBuilder中的方法外,还有递归这一神奇的技巧。以下是一个递归反转字符串的示例:

```java

public static String reverse(String originStr) {

if (originStr == null || originStr.length() <= 1)

return originStr;

return reverse(originStr.substring(1)) + originStr.charAt(0);

}

```

40、编码转换的小技巧

如何将GB2312编码的字符串转换为ISO-8859-1编码的字符串呢?使用Java的内置方法轻松实现:

```java

String s1 = "你好";

String s2 = new String(s1.getBytes("GB2312"), "ISO-8859-1");

```

41、日期与时间的探索之旅

在Java中探索日期与时间的奥秘吧!获取年月日、小时分钟秒?使用java.util.Calendar或Java 8中的java.time包。从1970年1月1日0时0分0秒到现在的毫秒数?使用System.currentTimeMillis()。获取某月的最后一天?使用Calendar的getActualMaximum方法。格式化日期?使用SimpleDateFormat。探索这些功能,你将发现日期与时间处理的无限可能!

日期时间测试之旅

让我们开始探索Java的日期时间API。我们创建一个DateTimeTest类来展示如何使用旧的API获取当前日期和时间。

```java

public class DateTimeTest {

public static void main(String[] args) {

// 使用旧的API获取当前日期和时间

Calendar calendar = Calendar.getInstance();

System.out.println("当前年份:" + calendar.get(Calendar.YEAR));

System.out.println("当前月份:" + (calendar.get(Calendar.MONTH) + 1)); // 注意月份是从0开始的,所以要加1

System.out.println("当前日期:" + calendar.get(Calendar.DATE));

System.out.println("当前小时:" + calendar.get(Calendar.HOUR_OF_DAY));

System.out.println("当前分钟:" + calendar.get(Calendar.MINUTE));

System.out.println("当前秒数:" + calendar.get(Calendar.SECOND));

// 使用Java 8的新API获取当前日期和时间

LocalDateTime localDateTime = LocalDateTime.now();

System.out.println("Java 8下的当前年份:" + localDateTime.getYear());

System.out.println("Java 8下的当前月份:" + localDateTime.getMonthValue()); // 月份从1开始

System.out.println("Java 8下的当前日期:" + localDateTime.getDayOfMonth());

System.out.println("Java 8下的当前小时:" + localDateTime.getHour());

System.out.println("Java 8下的当前分钟:" + localDateTime.getMinute());

System.out.println("Java 8下的当前秒数:" + localDateTime.getSecond());

}

}

```

关于获取毫秒数的方法,我们有多种途径:

1. Calendar.getInstance().getTimeInMillis();

2. System.currentTimeMillis();

3. 在Java 8中,可以使用Clock类的默认时区获取毫秒数:Clock.systemDefaultZone().millis();

接下来,我们要获取一个月中的最大天数。使用以下代码:

```java

Calendar time = Calendar.getInstance();

int maxDays = time.getActualMaximum(Calendar.DAY_OF_MONTH);

System.out.println("一个月中的最大天数:" + maxDays);

```

关于日期格式化,我们可以使用java.text.SimpleDateFormat类(旧API)或Java 8中的java.time.formatDateTimeFormatter类。例如:

```java

import java.text.SimpleDateFormat;

import java.time.LocalDate;

import java.time.formatDateTimeFormatter;

import javautilDate;

class DateFormatTest {

public static void main(String[] args) {

SimpleDateFormat oldFormatter = new SimpleDateFormat("yyyy/MM/dd");

Date date1 = new Date();

System.outprintln(oldFormatterformat(date1)); //(旧API)格式化日期并打印出来。

DateTimeFormatter newFormatter = DateTimeFormatterofPattern("yyyy/MM/dd"); LocalDate date2 = LocalDatenow(); Systemoutprintln(date2format(newFormatter)); //使用Java 8的新API格式化日期并打印出来。 } } 额外补充: Java的时间日期API在过去一直受到批评。为了解决这一问题,Java 8引入了全新的时间日期API,包括LocalDate、LocalTime、LocalDateTime、Clock等类。这些类遵循不变模式设计,因此是线程安全的。 现在,让我们打印昨天的当前时刻。我们可以使用LocalDateTime类配合LocalDate的minusDays方法来实现这一目标: LocalDateTime yesterdayTime = LocalDateTimenow().minusDays(1); Systemoutprintln("昨天的当前时刻:" + yesterdayTime); 这将输出昨天的日期和时间。 昨天日期的Java实现方式对比

第一种实现方式(使用Java 7的Calendar类)

在Java 7中,我们可以使用`Calendar`类来获取昨天的日期。这种方式虽然可以实现需求,但代码稍显繁琐。

```java

import java.util.Calendar;

public class YesterdayCurrent {

public static void main(String[] args) {

Calendar cal = Calendar.getInstance(); // 获取当前日历实例

cal.add(Calendar.DATE, -1); // 将日期回退一天

System.out.println(cal.getTime()); // 输出昨天的日期和时间

}

}

```

第二种实现方式(使用Java 8的LocalDateTime类)

在Java 8中,我们可以使用更简洁的`LocalDateTime`类来实现相同的功能,并且代码更加直观和易读。

```java

import java.time.LocalDateTime;

public class YesterdayCurrent {

public static void main(String[] args) {

LocalDateTime today = LocalDateTime.now(); // 获取当前日期和时间

LocalDateTime yesterday = today.minusDays(1); // 获取昨天的日期和时间

System.out.println(yesterday); // 输出昨天的日期和时间

}

}

```

Java与JavaScript的比较

基本概述:

Java 是由 Sun Microsystems 开发的一种面向对象的编程语言,适用于多种应用领域和网络应用程序开发。JavaScript 是由 Netscape 开发的一种解释型语言,主要用于网页交互和用户界面开发。两者的主要区别在于其设计目的和应用场景。下面是它们之间的一些主要差异:

- 设计初衷:Java 设计用于大型应用程序的开发,而 JavaScript 主要用于网页的交互和动态内容展示。

- 运行环境:Java 在 JVM 上运行,需要编译成字节码;JavaScript 在浏览器上运行,无需编译。

- 语言特性:Java 是静态类型语言,具有严格的类型检查;JavaScript 是动态类型语言,类型检查相对灵活。

更深入的比较:

除了上述基本差异外,Java 和 JavaScript 在其他方面也有显著的不同:

- 面向对象:Java 是完全面向对象的编程语言,而 JavaScript 也支持面向对象编程,但它也支持基于过程的编程。

- 类型系统:Java 采用强类型系统,所有变量必须事先声明类型;而 JavaScript 的类型系统相对灵活,变量可以在代码执行过程中更改类型。

- 函数的重要性:在 Java 中,函数是一等公民;而在 JavaScript 中,函数是对象并且可以像对象一样传递和操作。这使得 JavaScript 支持函数式编程风格。

断言(assert)的使用:

断言是一种调试工具,用于验证程序的某个假设是否为真。如果假设为假,则程序会抛出一个 AssertionError。在 Java 中使用断言可以帮助开发者确保程序的关键部分按预期工作。断言的使用通常限于开发和测试阶段,在生产环境中通常会被禁用。断言的使用形式如下:

```java

assert (expression); // 如果 expression 为 false,则抛出 AssertionError

assert expression1 : expression2; // expression2 用于生成错误消息

``` 可以通过 JVM 启动参数来启用或禁用断言。例如,-ea 参数用于启用断言,-da 参数用于禁用断言。断言应该谨慎使用,仅在必要时才启用。在实际开发中,断言常用于验证程序的内部条件或假设是否满足要求,以确保程序的健壮性和稳定性。 总结来说,Java 和 JavaScript 是两种截然不同的编程语言,各有其特点和优势。了解它们之间的区别有助于根据实际需求选择合适的语言进行开发。在面试或讨论中,用自己的语言回答问题并分享真实的见解往往比背诵所谓的标准答案更加有效和可信。45. Error和Exception有什么区别?

Error和Exception都是Java中表示异常的类,但它们之间存在明显的区别。Error通常表示系统级的错误,这些错误通常是不可恢复的,比如内存溢出等。这些错误通常不是程序能够处理的,它们更多的是表示程序运行环境的问题。而Exception则代表程序设计中预期可能会遇到的问题,比如空指针访问、数组越界等。这些异常是可以通过编程手段进行捕获并处理的。在Java编程中,我们通常会使用try-catch语句来捕获和处理异常。在面试题中提到的StackOverflowError是一个典型的Error类型,它表示栈溢出,通常是因为递归调用过深导致的。

46. try{}里有一个return语句,那么紧跟在这个try后的finally{}里的代码会不会被执行,什么时候被执行,在return前还是后?

在Java中,无论try块中的代码是否遇到return语句,finally块中的代码总是会执行。即使在try块中有return语句,其执行过程也会被暂停,然后执行finally块中的代码。finally块中的代码通常在try块中的return语句之后执行。但是需要注意的是,如果在finally块中也有return语句,那么它会覆盖try块中的return语句的返回值。如果在执行finally块时发生异常,那么该异常会覆盖之前的异常。

47. Java语言如何进行异常处理,关键字:throws、throw、try、catch、finally分别如何使用?

Java使用关键字throws、throw、try、catch和finally来进行异常处理。具体使用方法如下:

throw关键字用于抛出异常对象。当一个方法内部发生异常时,可以通过throw关键字抛出一个异常对象,将该异常对象传递给上级调用者处理。

throws关键字用于声明方法可能抛出的异常类型。当方法可能会抛出某些异常时,需要在方法声明中使用throws关键字声明这些异常类型。

try关键字用于标识一段可能引发异常的代码块。当try块中的代码出现异常时,可以通过catch块捕获并处理该异常。

catch关键字用于捕获try块中发生的异常。catch块紧跟在try块后面,用于指定捕获特定类型的异常并进行处理。

finally关键字用于标识一段无论是否发生异常都会执行的代码块。无论try块中的代码是否发生异常,finally块中的代码都会执行。

通过这五个关键字,Java可以实现良好的异常处理机制,确保程序的稳定性和可靠性。

48. 运行时异常与受检异常有何异同?

在Effective Java的世界里,异常处理和继承这两个面向对象编程的重要方面常常被开发人员广泛使用,也常常在某些情境下被误用。针对这两个方面,这里给出了一些关键原则:

对于异常处理来说,我们应该将其视为应对程序异常状况的手段,而不是用于正常的程序控制流中。设计优秀的API时,我们不应该强迫其使用者为了常规的控制流程而去使用异常处理机制。我们希望将异常作为特殊情况的处理工具,而不是作为日常操作的一部分。

对于可以恢复的情况,我们应使用受检异常。这些异常在编译期间就可以被捕获和处理。而对于编程过程中的错误,我们应使用运行时异常。这些异常在程序运行时发生,通常是由于程序员的一些错误操作导致的。我们需要避免不必要的使用受检异常,可以通过一些状态检测手段来预防异常的发生。我们也应该优先使用标准的异常类型,使得我们的代码更加易于理解和维护。每个方法抛出的异常都应该有详细的文档说明,以帮助使用者理解和处理这些异常。我们还应注意保持异常的原子性,避免在catch块中捕获异常后发生更多的复杂问题。对于捕获的异常,我们不应该在catch块中简单地忽略它们,而应该进行适当的处理或记录。

关于运行时异常,它们是在程序运行时发生的异常。一些常见的运行时异常包括:ArithmeticException(算术异常)、ClassCastException(类转换异常)、IllegalArgumentException(非法参数异常)、IndexOutOfBoundsException(下标越界异常)以及NullPointerException(空指针异常)。还有SecurityException(安全异常)等其他的运行时异常。这些异常的准确理解和处理是每一个Java程序员必须掌握的技能。这些经典Java面试题收集系列为我们提供了深入理解Java的机会。感谢整理者nnngu为我们带来这些宝贵的资源。

文章来自《钓虾网小编|www.jnqjk.cn》整理于网络,文章内容不代表本站立场,转载请注明出处。

本文链接:https://www.jnqjk.cn/quanzi/161987.html

AI推荐

Copyright 2024 © 钓虾网 XML

蜀ICP备2022021333号-1