当前位置: 首页 > article >正文

每日 Java 面试题分享【第 12 天】

欢迎来到每日 Java 面试题分享栏目!
订阅专栏,不错过每一天的练习

今日分享 3 道面试题目!

评论区复述一遍印象更深刻噢~

目录

  • 问题一:Java 中 final、finally 和 finalize 各有什么区别?
  • 问题二:为什么在 Java 中编写代码时会遇到乱码问题?
  • 问题三:为什么 JDK 9 中将 String 的 char 数组改为 byte 数组?

问题一:Java 中 final、finally 和 finalize 各有什么区别?

finalfinallyfinalize 是 Java 中三个容易混淆的概念,它们的作用和使用场景完全不同。以下是详细分析:


1. final

作用

final 是一个关键字,用于声明类、方法或变量,表示某些特性不可更改。

使用场景
  1. 修饰类

    • 表示类不能被继承。

    • 示例:

      public final class FinalClass {
          // 类体
      }
      
      // 报错:无法继承 FinalClass
      class SubClass extends FinalClass {}
      
  2. 修饰方法

    • 表示方法不能被子类重写。

    • 示例:

      public class Parent {
          public final void display() {
              System.out.println("This is a final method.");
          }
      }
      
      class Child extends Parent {
          // 报错:不能重写 final 方法
          // @Override
          // public void display() {}
      }
      
  3. 修饰变量

    • 表示变量值不能被重新赋值(常量)。

    • 示例:

      public class Example {
          public static final double PI = 3.14159;
      
          public void test() {
              final int x = 10;
              // 报错:不能修改 final 变量
              // x = 20;
          }
      }
      
总结

final 用于 限制修改:类不能被继承、方法不能被重写、变量不能重新赋值。


2. finally

作用

finally 是一个用于 异常处理 的关键字,表示无论是否发生异常,finally 块中的代码都会执行。

使用场景
  1. 确保释放资源

    • 通常用于关闭文件、释放锁、关闭数据库连接等操作。

    • 示例:

      public class Example {
          public static void main(String[] args) {
              try {
                  int result = 10 / 0; // 抛出异常
              } catch (ArithmeticException e) {
                  System.out.println("Caught exception: " + e.getMessage());
              } finally {
                  System.out.println("This is the finally block.");
              }
          }
      }
      
    • 输出:

      Caught exception: / by zero
      This is the finally block.
      
  2. 注意事项

    • 即使 try 块中有 returnfinally 块仍然会执行。

    • 示例:

      public class Example {
          public static void main(String[] args) {
              System.out.println(test());
          }
      
          public static int test() {
              try {
                  return 10;
              } finally {
                  System.out.println("Finally block executed");
              }
          }
      }
      
    • 输出:

      Finally block executed
      10
      
总结

finally 块是 异常处理机制 的一部分,用于执行清理代码。


3. finalize

作用

finalizeObject 类的一个方法,用于在对象被 垃圾回收(GC)之前执行清理操作。

使用场景
  1. 定义对象的清理逻辑

    • 可以在 finalize 方法中释放资源,如关闭文件或网络连接。

    • 示例:

      public class Example {
          @Override
          protected void finalize() throws Throwable {
              System.out.println("finalize method called");
          }
      
          public static void main(String[] args) {
              Example obj = new Example();
              obj = null; // 使对象变为垃圾
      
              System.gc(); // 显式调用垃圾回收器
          }
      }
      
  2. 注意事项

    • 不建议使用finalize 的执行时间不确定,依赖垃圾回收机制,可能导致性能问题。
    • 已被弃用:从 Java 9 开始,finalize 方法已被标记为 Deprecated,推荐使用 try-with-resources 或显式清理资源代替。
总结

finalize 用于对象被回收前执行清理操作,但已被弃用,不推荐使用。


区别总结表

特性finalfinallyfinalize
类型关键字关键字方法
作用限制类、方法、变量的修改或继承异常处理中用于清理操作对象回收前执行清理操作
使用场景定义不可更改的类、方法或常量异常处理中释放资源垃圾回收前释放对象持有的资源
是否推荐推荐推荐不推荐(已被废弃)

扩展:考察重点

  • 面试时通常结合实际场景考察:
    • final:用于实现不可变类、确保线程安全等。
    • finally:确保资源释放,如何正确使用异常处理机制。
    • finalize:了解其作用及替代方案(如 AutoCloseable)。

最佳实践

  • final 修饰变量时,配合 static 创建常量(例如:public static final)。
  • 资源释放首选 try-with-resources,避免依赖 finallyfinalize
  • 了解 finalize 的替代方案(如 PhantomReference 或显式清理)。

问题二:为什么在 Java 中编写代码时会遇到乱码问题?

在 Java 中编写代码时,遇到乱码问题通常是由于 字符编码 处理不当导致的。Java 对字符编码有一套标准机制,但如果开发、存储和传输的编码方式不一致,就可能产生乱码。以下是详细分析:


常见原因

1. 文件编码与环境编码不一致
  • 问题描述:源代码文件保存时的编码与编译或运行时的编码不一致。
  • 典型场景
    • 用 UTF-8 编写的文件,在运行时被解释为 GBK 或 ISO-8859-1 等其他编码。
    • 比如:文件中有中文字符,保存为 UTF-8 编码,但 Java 默认按系统编码(如 Windows 常用的 GBK)读取文件,导致乱码。
2. 控制台显示编码问题
  • 问题描述:Java 程序在控制台输出文本时,控制台的编码与程序的编码不同步。
  • 典型场景
    • 在 Windows 上运行程序时,控制台默认编码是 GBK,而程序的输出编码是 UTF-8,结果中文显示乱码。
3. 数据存储或传输中的编码问题
  • 问题描述:文本在文件、数据库、网络传输中,存储的编码方式与读取时的编码方式不匹配。
  • 典型场景
    • 数据库中保存的文本为 UTF-8,但读取时用 GBK 解码。
    • 通过网络传输的数据编码方式未统一,导致接收端解析失败。
4. 未显式指定编码
  • 问题描述:在开发或运行时,没有明确指定字符编码,导致 Java 使用了平台默认编码(可能是 GBK、UTF-8、ISO-8859-1 等)。
  • 典型场景
    • 读取文件时未指定编码:

      BufferedReader reader = new BufferedReader(new FileReader("test.txt"));
      

      默认会使用平台编码,可能与文件实际编码不一致。

5. 数据流中的编码转换不当
  • 问题描述:在字符流和字节流之间转换时,编码方式未匹配。
  • 典型场景
    • 使用 String 的构造方法或 getBytes() 方法时,未指定编码:

      String str = new String(byteArray); // 编码方式默认依赖系统
      

如何避免和解决乱码问题

1. 确定并统一编码
  • 统一开发环境编码

    • 确保 IDE(如 IntelliJ IDEA、Eclipse)保存的文件编码一致。
    • 常用设置:文件编码使用 UTF-8。
    • 在 IntelliJ IDEA 中:
      • 设置路径:File -> Settings -> Editor -> File Encodings
      • 推荐将项目、全局和默认编码均设置为 UTF-8。
  • 统一运行时编码

    • 显式指定 JVM 的默认编码:

      java -Dfile.encoding=UTF-8 YourClassName
      
    • 在代码中指定读取或写入时的编码:

      BufferedReader reader = new BufferedReader(new InputStreamReader(
          new FileInputStream("test.txt"), "UTF-8"));
      
2. 控制台编码匹配
  • 解决控制台显示乱码
    • 在 Windows 系统下,手动将 CMD 控制台编码改为 UTF-8:

      chcp 65001
      
    • 或者直接在代码中设置:

      System.setOut(new PrintStream(System.out, true, "UTF-8"));
      
3. 数据库编码
  • 确保数据库编码正确
    • 数据库表和列的编码设置为 UTF-8(如 MySQL 中 CHARACTER SET utf8)。

    • 确保 JDBC URL 中设置字符编码,例如:

      jdbc:mysql://localhost:3306/dbname?useUnicode=true&characterEncoding=UTF-8
      
4. 网络传输编码
  • 统一客户端和服务端的编码
    • 在 HTTP 请求或响应时,显式指定内容的编码格式:

      response.setCharacterEncoding("UTF-8");
      
5. 避免依赖系统默认编码
  • 字符串与字节数组之间转换时指定编码
    • 错误示例

      String str = new String(byteArray);
      
    • 正确示例

      String str = new String(byteArray, "UTF-8");
      
6. 使用工具检查编码
  • 文件编码检查
    • 使用工具(如 Notepad++、VS Code)检查和修改文件编码。

    • Linux 中使用 file 命令检查:

      file -i test.txt
      

扩展:乱码的本质

乱码的根本原因是:

  1. 字节数据和字符数据之间的解码不匹配
    • 字节流中的数据按照错误的字符编码方式被解码。
  2. 平台编码差异
    • Java 默认使用的编码(如系统默认编码)和实际数据编码不同。

例如:

  • 编码阶段:字符 "中" 在 UTF-8 中表示为 0xE4B8AD
  • 解码阶段:按 GBK 解码时,0xE4B8AD 会被解释为乱码字符。

总结

为了避免 Java 中的乱码问题,应做到:

  1. 统一编码:开发环境、运行环境和数据存储都使用 UTF-8。
  2. 显式指定编码:文件读取、网络传输、数据库连接时,明确设置编码格式。
  3. 遵循最佳实践:避免依赖系统默认编码,尤其是在跨平台开发中。

通过以上方法,可以有效避免和解决 Java 开发中的乱码问题。


问题三:为什么 JDK 9 中将 String 的 char 数组改为 byte 数组?

在 JDK 9 中,String 类的底层实现从使用 char[] 数组改为使用 byte[] 数组,这是 Java 性能优化 的一部分。这一改变被称为 Compact Strings,是为了解决内存占用问题并提升性能。下面详细解答这一问题的背景、原因以及优缺点:


背景

  1. JDK 8 及之前的实现

    • String 类底层使用 char[] 数组存储字符,每个字符占用 2 字节(基于 UTF-16 编码)。
    • 无论字符串是否只包含单字节字符(如 ASCII 字符),都统一分配 2 字节存储空间。
    • 举例:
      • 字符串 "abc",实际只需要 3 字节,但用 char[] 存储需要占用 6 字节。
  2. 内存浪费问题

    • 大多数应用中的字符串包含大量 ASCII 字符(单字节字符)。
    • 使用 char[] 数组存储所有字符串,导致约 50% 的内存被浪费。

Compact Strings 的实现

在 JDK 9 中,String 类的底层实现改为 byte[] 数组,并添加了一个 coder 字段,表示字符串的编码方式:

  1. byte[] 数组存储实际数据

    • 如果字符串只包含单字节字符(如 ASCII),使用单字节编码(ISO-8859-1)。
    • 如果字符串包含多字节字符(如中文、阿拉伯文),使用双字节编码(UTF-16)。
  2. coder 字段

    • 一个额外的字节字段,标记当前字符串是单字节(ISO-8859-1)还是双字节(UTF-16)。

具体原因

1. 内存占用优化
  • 对于大多数只包含单字节字符(如英文和数字)的字符串,使用 byte[] 存储只需占用原来一半的内存。
  • 举例:
    • JDK 8 中,字符串 "abc" 使用 char[] 需要 6 字节。
    • JDK 9 中,字符串 "abc" 使用 byte[] 只需 3 字节。
  • 对于包含多字节字符的字符串,byte[] 仍使用 UTF-16,但内存占用与之前相同,不会额外浪费。
2. 提升 CPU 缓存利用率
  • byte[] 占用的内存更少,意味着在运行时,更多的字符串数据可以装入 CPU 的缓存,从而减少缓存未命中的概率。
  • 这对性能要求高的应用(如大规模 web 应用)尤为重要。
3. 减少 GC 压力
  • 内存占用减少,意味着堆中存储的字符串对象更小,从而降低垃圾回收器(GC)的压力,提升程序整体性能。
4. 保持向后兼容
  • 尽管底层改用 byte[] 实现,但在 API 层面,String 的行为保持不变。
  • 用户无需修改代码,仍然可以像以前一样使用字符串操作。

优缺点分析

优点
  1. 内存效率显著提升
    • 减少字符串对象的内存开销,特别是在大多数字符串为 ASCII 字符的场景下(如 web 服务、日志系统)。
  2. 性能优化
    • 更少的内存占用,提升 CPU 缓存利用率。
    • 更低的 GC 开销,提升运行效率。
  3. 无缝向后兼容
    • 不需要修改现有代码,API 保持一致。
  4. 提升整体应用性能
    • 尤其对使用大量短字符串的应用效果显著。
缺点
  1. 稍微增加的复杂性
    • 为了支持两种编码(ISO-8859-1UTF-16),需要维护额外的 coder 字段。
    • 一些字符串操作(如 charAtsubstring)需要根据编码动态判断字节或字符长度,稍微增加了运算复杂度。
  2. 特定场景下优化有限
    • 如果字符串包含大量多字节字符(如中文、阿拉伯文),内存占用优化效果不明显。

代码示例

以下是 String 在 JDK 8 和 JDK 9 中的底层实现对比(伪代码展示):

JDK 8 实现
public final class String {
    private final char[] value; // 使用 char[] 存储字符串,每个字符占 2 字节
    private final int hash;     // 缓存 hashCode 值
    // …
}
JDK 9 实现
public final class String {
    private final byte[] value; // 使用 byte[] 存储字符串,节省内存
    private final byte coder;   // 标记编码类型(0: ISO-8859-1, 1: UTF-16)
    private final int hash;     // 缓存 hashCode 值
    // …
}

扩展:实际应用场景

1. Web 应用
  • 大量处理 HTTP 请求、响应头和 JSON 数据,这些数据大多是 ASCII 字符。
  • 使用 Compact Strings 可以显著降低内存使用。
2. 数据库连接和日志
  • 数据库查询返回的结果、系统日志文件等通常以英文为主,Compact Strings 带来巨大优化。
3. 内存敏感型应用
  • 如移动设备上的 Java 应用,内存资源有限,使用 Compact Strings 是提升性能的关键。

总结

JDK 9 将 String 的底层实现从 char[] 改为 byte[] 的主要目的是为了 优化内存使用提升性能,通过支持两种编码(单字节和双字节)显著减少了字符串对象的内存占用。这种改进在不改变 API 的情况下,提升了 Java 应用在内存和运行效率方面的表现,是一项重要的性能优化措施。


总结

今天的 3 道 Java 面试题,您是否掌握了呢?持续关注我们的每日分享,深入学习 Java 面试的各个细节,快速提升技术能力!如果有任何疑问,欢迎在评论区留言,我们会第一时间解答!

明天见!🎉


http://www.kler.cn/a/520843.html

相关文章:

  • gradle和maven的区别以及怎么选择使用它们
  • 前端力扣刷题 | 4:hot100之 子串
  • Linux 入门 常用指令 详细版
  • 数据的秘密:如何用大数据分析挖掘商业价值
  • vite环境变量处理
  • 活动回顾和预告|微软开发者社区 Code Without Barriers 上海站首场活动成功举办!
  • Ansible自动化运维实战--yaml的使用和配置(7/8)
  • 企业知识管理平台的对比分析与优化策略探讨
  • 蓝桥杯模拟算法:蛇形方阵
  • cloc下载和使用
  • C#新语法
  • ES filter和post_filter的区别
  • IDEA全局搜索Ctrl+Shift+F失效
  • vim的特殊模式-可视化模式
  • 超市数字化落地:RWA + 智能体赋能实体零售数字化
  • 2025美赛MCM数学建模A题:《石头台阶的“记忆”:如何用数学揭开历史的足迹》(全网最全思路+模型)
  • Ubuntu Server 安装 XFCE4桌面
  • leetcode152-乘积最大的子数组
  • Forsaken喜欢数论(线性筛)
  • H266/VVC 量化编码中量化矩阵 QM 技术
  • 是否参加26年冬奥会?30岁羽生结弦:没有重返赛场打算
  • 小A的回文串
  • 无耳科技 Solon v3.0.7 发布(2025农历新年版)
  • ChatGPT从数据分析到内容写作建议相关的46个提示词分享!
  • 【S32K3 RTD LLD篇7】K344中心对齐PWM中心点触发ADC BCTU采样
  • 2025MCM美国大学生数学建模竞赛B题-可持续旅游管理思路详解+建模论文+源代码