继上篇 JVM 学习之后, 后面将分三期深入介绍剩余 JAVA 基础面试题, 每期 3 题.
题目一, final,finally,finalize 有什么区别?
- /* 请尊重作者劳动成果, 转载请标明原文链接:*/
- /* https://www.cnblogs.com/jpcflyer/p/10739217.html*/
大家一般都这么回答:
final 可以用来修饰类, 方法, 变量, 分别有不同的意义, final 修饰的 class 代表不可以继承扩展, final 的变量是不可以修改的, 而 final 的方法也是不可以重写的(override).
finally 则是 Java 保证重点代码一定要被执行的一种机制. 我们可以使用 try-finally 或者 try-catch-finally 来进行类似关闭 JDBC 连接, 保证 unlock 锁等动作.
finalize 是基础类 java.lang.Object 的一个方法, 它的设计目的是保证对象在被垃圾收集前完成特定资源的回收. finalize 机制现在已经不推荐使用, 并且在 JDK 9 开始被标记为 deprecated.
从语法层次, 这样说完是没有问题的, 但让我们深入一下, 什么时候会用到 final?
如果你关注过 Java 核心类库的定义或源码, 有没有发现 java.lang 包下面的很多类, 相当一部分都被声明成为 final class? 在第三方类库的一些基础类中同样如此, 这可以有效避免 API 使用者更改基础功能, 某种程度上, 这是保证平台安全的必要手段.
使用 final 修饰参数或者变量, 也可以清楚地避免意外赋值导致的编程错误, 甚至, 有人明确推荐将所有方法参数, 本地变量, 成员变量声明成 final.
final 变量产生了某种程度的不可变 (immutable) 的效果, 所以, 可以用于保护只读数据, 尤其是在并发编程中, 因为明确地不能再赋值 final 变量, 有利于减少额外的同步开销, 也可以省去一些防御性拷贝的必要.
另外, 有一些常被考到的 finally 问题(也比较偏门), 至少需要了解一下. 比如, 下面代码会输出什么?
- try {
- // do something
- System.exit(1);
- } finally{
- System.out.println("Print from finally");
- }
上面 finally 里面的代码可不会被执行的哦, 这是一个特例.
如果需要关闭连接等资源, 推荐使用 Java 7 中添加的 try-with-resources 语句, 因为通常 Java 平台能够更好地处理异常情况, 编码量也要少很多, 何乐而不为呢.
对于 finalize, 我们要明确它是不推荐使用的, 业界实践一再证明它不是个好的办法, 在 Java 9 中, 甚至明确将 Object.finalize() 标记为 deprecated! 如果没有特别的原因, 不要实现 finalize 方法, 也不要指望利用它来进行资源回收.
为什么呢? 简单说, 你无法保证 finalize 什么时候执行, 执行的是否符合预期. 使用不当会影响性能, 导致程序死锁, 挂起等.
题目二, String, StringBuffer, StringBuilder 有什么区别?
接下来聊一聊日常使用的字符串, 别看它似乎简单, 但实际上字符串涉及到的知识点特别多, 在编程的大部分时候也都是在和字符串打交道.
一般回答:
String 是 Java 语言非常基础和重要的类, 提供了构造和管理字符串的各种基本逻辑. 它是典型的 Immutable 类, 被声明成为 final class, 所有属性也都是 final 的. 也由于它的不可变性, 类似拼接, 裁剪字符串等动作, 都会产生新的 String 对象. 由于字符串操作的普遍性, 所以相关操作的效率往往对应用性能有明显影响.
StringBuffer 是为解决上面提到拼接产生太多中间对象的问题而提供的一个类, 我们可以用 append 或者 add 方法, 把字符串添加到已有序列的末尾或者指定位置. StringBuffer 本质是一个线程安全的可修改字符序列, 它保证了线程安全, 也随之带来了额外的性能开销, 所以除非有线程安全的需要, 不然还是推荐使用它的后继者, 也就是 StringBuilder.
StringBuilder 是 Java 1.5 中新增的, 在能力上和 StringBuffer 没有本质区别, 但是它去掉了线程安全的部分, 有效减小了开销, 是绝大部分情况下进行字符串拼接的首选.
再继续扩展一下:
首先看看 StringBuffer 实现的一些细节, 它的 线程安全是通过把各种修改数据的方法都加上 synchronized 关键字实现的, 非常直白.
为了实现修改字符序列的目的, StringBuffer 和 StringBuilder 底层都是利用可修改的 (char,JDK 9 以后是 byte) 数组, 二者都继承了 AbstractStringBuilder, 里面包含了基本操作, 区别仅在于最终的方法是否加了 synchronized.
另外, 这个内部数组应该创建成多大的呢? 如果太小, 拼接的时候可能要重新创建足够大的数组; 如果太大, 又会浪费空间. 目前的实现是, 构建时初始字符串长度加 16(这意味着, 如果没有构建对象时输入最初的字符串, 那么初始值就是 16). 我们如果确定拼接会发生非常多次, 而且大概是可预计的, 那么就可以指定合适的大小, 避免很多次扩容的开销. 扩容会产生多重开销, 因为要抛弃原有数组, 创建新的 (可以简单认为是倍数) 数组, 还要进行 arraycopy.
如对此感兴趣, 可以继续阅读前文 "深入理解 String, StringBuffer, StringBuilder 的区别(基于 JDK1.8)"
题目三, Exception 和 Error 有什么区别?
直接看 JAVA 异常结构吧, JAVA 异常的继承结构: Throwable 为基类, Error 和 Exception 继承 Throwable,RuntimeException 和 IOException 等继承 Exception.Error 和 RuntimeException 及其子类成为未检查异常(unchecked), 其它异常成为已检查异常(checked).
Error 异常
Error 表示程序在运行期间出现了十分严重, 不可恢复的错误, 在这种情况下应用程序只能中止运行, 例如 JAVA 虚拟机出现错误. Error 是一种 unchecked Exception, 编译器不会检查 Error 是否被处理, 在程序中不用捕获 Error 类型的异常. 一般情况下, 在程序中也不应该抛出 Error 类型的异常.
RuntimeException 异常
Exception 异常包括 RuntimeException 异常和其他非 RuntimeException 的异常.
RuntimeException 是一种 Unchecked Exception, 即表示编译器不会检查程序是否对 RuntimeException 作了处理, 在程序中不必捕获 RuntimException 类型的异常, 也不必在方法体声明抛出 RuntimeException 类. RuntimeException 发生的时候, 表示程序中出现了编程错误, 所以应该找出错误修改程序, 而不是去捕获 RuntimeException.
Checked Exception 异常
Checked Exception 异常, 这也是在编程中使用最多的 Exception, 所有继承自 Exception 并且不是 RuntimeException 的异常都是 checked Exception, 上图中的 IOException 和 ClassNotFoundException.JAVA 语言规定必须对 checked Exception 作处理, 编译器会对此作检查, 要么在方法体中声明抛出 checked Exception, 要么使用 catch 语句捕获 checked Exception 进行处理, 不然不能通过编译.
来源: https://www.cnblogs.com/jpcflyer/p/10739217.html