`声明:本文部分内容引用了大佬程序员大彬的博客`
# 1.Java的特点
**Java是一门面向对象的编程语言**。面向对象和面向过程的区别参考下一个问题。
**Java具有平台独立性和移植性**。
- Java有一句口号:`Write once, run anywhere`,一次编写、到处运行。这也是Java的魅力所在。而实现这种特性的正是Java虚拟机JVM。已编译的Java程序可以在任何带有JVM的平台上运行。你可以在windows平台编写代码,然后拿到linux上运行。只要你在编写完代码后,将代码编译成.class文件,再把class文件打成Java包,这个jar包就可以在不同的平台上运行了。
**Java具有稳健性**。
- Java是一个强类型语言,它允许扩展编译时检查潜在类型不匹配问题的功能。Java要求显式的方法声明,它不支持C风格的隐式声明。这些严格的要求保证编译程序能捕捉调用错误,这就导致更可靠的程序。
- 异常处理是Java中使得程序更稳健的另一个特征。异常是某种类似于错误的异常条件出现的信号。使用`try/catch/finally`语句,程序员可以找到出错的处理代码,这就简化了出错处理和恢复的任务
# 2.Java是如何实现跨平台的?
Java是通过JVM(Java虚拟机)实现跨平台的。
JVM可以理解成一个软件,不同的平台有不同的版本。我们编写的Java代码,编译后会生成.class 文件(字节码文件)。Java虚拟机就是负责将字节码文件翻译成特定平台下的机器码,通过JVM翻译成机器码之后才能运行。不同平台下编译生成的字节码是一样的,但是由JVM翻译成的机器码却不一样。
只要在不同平台上安装对应的JVM,就可以运行字节码文件,运行我们编写的Java程序。
因此,运行Java程序必须有JVM的支持,因为编译的结果不是机器码,必须要经过JVM的翻译才能执行
# 3.JDK/JRE/JVM三者的关系
**JVM**
英文名称(Java Virtual Machine),就是我们耳熟能详的 Java 虚拟机。Java 能够跨平台运行的核心在于 JVM 。
![img](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3d807fc9b7fb40afab22c25552cdb26e~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=837&h=469&s=19772&e=webp&b=cddee2)
所有的java程序会首先被编译为.class的类文件,这种类文件可以在虚拟机上执行。也就是说class文件并不直接与机器的操作系统交互,而是经过虚拟机间接与操作系统交互,由虚拟机将程序解释给本地系统执行。
针对不同的系统有不同的 jvm 实现,有 Linux 版本的 jvm 实现,也有Windows 版本的 jvm 实现,但是同一段代码在编译后的字节码是一样的。这就是Java能够跨平台,实现一次编写,多处运行的原因所在。
**JRE**
英文名称(Java Runtime Environment),就是Java 运行时环境。我们编写的Java程序必须要在JRE才能运行。它主要包含两个部分,JVM 和 Java 核心类库。
![img](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fb8b77e7f33e420eaaf20fb021b459a4~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=301&h=131&s=4203&e=png&b=e1d5e7)
JRE是Java的运行环境,并不是一个开发环境,所以没有包含任何开发工具,如编译器和调试器等。
如果你只是想运行Java程序,而不是开发Java程序的话,那么你只需要安装JRE即可。
**JDK**
英文名称(Java Development Kit),就是 Java 开发工具包
学过Java的同学,都应该安装过JDK。当我们安装完JDK之后,目录结构是这样的
![img](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a115185a1bd84e05a69d7d9c23224dba~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=662&h=303&s=35887&e=png&b=fefdfd)
可以看到,JDK目录下有个JRE,也就是JDK中已经集成了 JRE,不用单独安装JRE。
另外,JDK中还有一些好用的工具,如jinfo,jps,jstack等。
![img](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/234718ab9af44285ac83602421e447fd~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=633&h=551&s=74580&e=png&b=fffefe)
最后,总结一下JDK/JRE/JVM,他们三者的关系
**JRE = JVM + Java 核心类库**
**JDK = JRE + Java工具 + 编译器 + 调试器**
![img](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/20686c7b0e754327bcc8e1c481f53b40~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1280&h=720&s=47582&e=png&b=efefef)
# 4.Java程序是编译执行还是解释执行?
先看看什么是编译型语言和解释型语言。
**编译型语言**
在程序运行之前,通过编译器将源程序编译成机器码可运行的二进制,以后执行这个程序时,就不用再进行编译了。
优点:编译器一般会有预编译的过程对代码进行优化。因为编译只做一次,运行时不需要编译,所以编译型语言的程序执行效率高,可以脱离语言环境独立运行。
缺点:编译之后如果需要修改就需要整个模块重新编译。编译的时候根据对应的运行环境生成机器码,不同的操作系统之间移植就会有问题,需要根据运行的操作系统环境编译不同的可执行文件。
**总结**:执行速度快、效率高;依靠编译器、跨平台性差些。
**代表语言**:C、C++、Pascal、Object-C以及Swift。
**解释型语言**
定义:解释型语言的源代码不是直接翻译成机器码,而是先翻译成中间代码,再由解释器对中间代码进行解释运行。在运行的时候才将源程序翻译成机器码,翻译一句,然后执行一句,直至结束。
优点:
1. 有良好的平台兼容性,在任何环境中都可以运行,前提是安装了解释器(如虚拟机)。
1. 灵活,修改代码的时候直接修改就可以,可以快速部署,不用停机维护。
缺点:每次运行的时候都要解释一遍,性能上不如编译型语言。
总结:解释型语言执行速度慢、效率低;依靠解释器、跨平台性好。
代表语言:JavaScript、Python、Erlang、PHP、Perl、Ruby。
对于Java这种语言,它的**源代码**会先通过javac编译成**字节码**(.class文件),再通过jvm将字节码(.calss文件)转换成**机器码**执行,即解释运行 和编译运行配合使用,所以可以称为混合型或者半编译型。
# 5.面向对象和面向过程的区别?
面向对象和面向过程是一种软件开发思想。
两者的主要区别在于解决问题的方式不同:
- 面向过程把解决问题的过程拆成一个个方法,通过一个个方法的执行解决问题。
- 面向对象会先抽象出对象,然后用对象执行方法的方式解决问题。
# 6.面向对象四大特性
面向对象四大特性:封装,继承,多态,抽象
1、封装就是将类的信息隐藏在类内部,不允许外部程序直接访问,而是通过该类的方法实现对隐藏信息的操作和访问。 良好的封装能够减少耦合。
2、继承是从已有的类中派生出新的类,新的类继承父类的属性和行为,并能扩展新的能力,大大增加程序的重用性和易维护性。在Java中是单继承的,也就是说一个子类只有一个父类。
3、多态是同一个行为具有多个不同表现形式的能力。在不修改程序代码的情况下改变程序运行时绑定的代码。实现多态的三要素:继承、重写、父类引用指向子类对象。
- 静态多态性:通过重载实现,相同的方法有不同的參数列表,可以根据参数的不同,做出不同的处理。
- 动态多态性:在子类中重写父类的方法。运行期间判断所引用对象的实际类型,根据其实际类型调用相应的方法。
| 区别点 | 重载方法 | 重写方法 |
| ----- | ---- | -------------------------------- |
| 发生范围 | 同一个类 | 子类 |
| 参数列表 | 必须修改 | 一定不能修改 |
| 返回类型 | 可修改 | 子类方法返回值类型应比父类方法返回值类型更小或相等 |
| 异常 | 可修改 | 子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等; |
| 访问修饰符 | 可修改 | 一定不能做更严格的限制(可以降低限制) |
| 发生阶段 | 编译期 | 运行期 |
4、抽象。把客观事物用代码抽象出来。
# 7.Java的基本数据类型有哪些?
- byte,8bit
- char,16bit
- short,16bit
- int,32bit
- float,32bit
- long,64bit
- double,64bit
- boolean,只有两个值:true、false,可以使⽤用 1 bit 来存储
| 简单类型 | boolean | byte | char | short | Int | long | float | double |
| ----- | ------- | ---- | --------- | ----- | ------- | ---- | ----- | ------ |
| 二进制位数 | 1 | 8 | 16 | 16 | 32 | 64 | 32 | 64 |
| 包装类 | Boolean | Byte | Character | Short | Integer | Long | Float | Double |
在Java规范中,没有明确指出boolean的大小。在《Java虚拟机规范》给出了单个boolean占4个字节,和boolean数组1个字节的定义,具体 **还要看虚拟机实现是否按照规范来**,因此boolean占用1个字节或者4个字节都是有可能的。
# 8.了解Java的包装类型吗?为什么需要包装类?
Java包装类型有8种,分别是:1、Byte;2、Integer;3、Short;4、Long;5、Float;6、Double;7、Boolean;8、Character。
Java 是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将 int 、double 等类型放进去的。因为集合的容器要求元素是 Object 类型。
为了让基本类型也具有对象的特征,就出现了包装类型。相当于将基本类型包装起来,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。
# 9.自动装箱和拆箱
Java中基础数据类型与它们对应的包装类见下表:
| 原始类型 | 包装类型 |
| ------- | --------- |
| boolean | Boolean |
| byte | Byte |
| char | Character |
| float | Float |
| int | Integer |
| long | Long |
| short | Short |
| double | Double |
装箱:将基础类型转化为包装类型。
拆箱:将包装类型转化为基础类型。
当基础类型与它们的包装类有如下几种情况时,编译器会**自动**帮我们进行装箱或拆箱:
- 赋值操作(装箱或拆箱)
- 进行加减乘除混合运算 (拆箱)
- 进行>,<,==比较运算(拆箱)
- 调用equals进行比较(装箱)
- ArrayList、HashMap等集合类添加基础类型数据时(装箱)
示例代码:
```
Integer x = 1; // 装箱 调⽤ Integer.valueOf(1)
int y = x; // 拆箱 调⽤了 X.intValue()
```
# 10.equals和引用相等==的区别
- 对于基本数据类型,==比较的是他们的值。基本数据类型没有equal方法;
- 对于复合数据类型,==比较的是它们的存放地址(是否是同一个对象)。`equals()`默认比较地址值等价于==,重写的话一般按照属性是否相等去比较。
```
String a = new String("ab"); // a 为一个引用
String b = new String("ab"); // b为另一个引用,对象的内容一样
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
System.out.println(aa == bb);// true
System.out.println(a == b);// false
System.out.println(a.equals(b));// true
System.out.println(42 == 42.0);// true
```
`String` 中的 `equals` 方法是被重写过的,因为 `Object` 的 `equals` 方法是比较的对象的内存地址,而 `String` 的 `equals` 方法比较的是对象的值。
# 11.为什么浮点数运算的时候会有精度丢失的风险?
浮点数运算精度丢失代码演示:
```
float a = 2.0f - 1.9f;
float b = 1.8f - 1.7f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999905
System.out.println(a == b);// false
```
为什么会出现这个问题呢?
这个和计算机保存浮点数的机制有很大关系。我们知道计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。这也就是解释了为什么浮点数没有办法用二进制精确表示。
# 12.如何解决浮点数运算的精度丢失问题?
`BigDecimal` 可以实现对浮点数的运算,不会造成精度丢失。通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 `BigDecimal` 来做的。
```
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);
System.out.println(x); /* 0.1 */
System.out.println(y); /* 0.1 */
System.out.println(Objects.equals(x, y)); /* true */
```
# 13.什么是值传递和引用传递?
- 值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。
- 引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本,并不是原对象本身,两者指向同一片内存空间。所以对引用对象进行操作会同时改变原对象。
**java中不存在引用传递,只有值传递**。即不存在变量a指向变量b,变量b指向对象的这种情况。
# 14.深拷贝和浅拷贝
具体见博客:[最通俗易懂详解深拷贝和浅拷贝 - 掘金 (juejin.cn)](https://juejin.cn/post/7298923167619629083#heading-1)
## 1.如何实现对象克隆
实现`Cloneable`接口,重写 `clone()` 方法。这种方式是浅拷贝,即如果类中属性有自定义引用类型,只拷贝引用,不拷贝引用指向的对象。如果对象的属性的Class也实现 `Cloneable` 接口,那么在克隆对象时也会克隆属性,即深拷贝。
## 2.什么是深拷贝和浅拷贝
**浅拷贝**
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。”里面的对象“会在原来的对象和它的副本之间共享。
简而言之,`浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。`
**深拷贝**
深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
简而言之,`深拷贝把要复制的对象所引用的对象都复制了一遍。`
![浅拷贝、深拷贝、引用拷贝示意图](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/02ce782f626e49f9870e4f9060d1d28c~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1625&h=451&s=18139&e=png&b=4ca498)
# 15.Object常用方法有哪些?
见博客:[Object常用方法 - 掘金 (juejin.cn)](https://juejin.cn/post/7298927261209788442)
Object常用方法有:`toString()`、`equals()`、`hashCode()`、`clone()`等。
**toString**
默认输出对象地址。可以重写toString方法,按照重写逻辑输出对象值。
**equals**
默认比较两个引用变量是否指向同一个对象(内存地址)。可以重写equals方法,按照属性是否相等来判断
**hashCode**
将与对象相关的信息映射成一个哈希值,默认的实现hashCode值是根据内存地址换算出来。
**clone**
Java赋值是复制对象引用,如果我们想要得到一个对象的副本,使用赋值操作是无法达到目的的。Object对象有个clone()方法,实现了对象中各个属性的复制,但它的可见范围是protected的。
```
protected native Object clone() throws CloneNotSupportedException;
```
所以实体类使用克隆的前提是:
- 实现Cloneable接口,这是一个标记接口,自身没有方法,这应该是一种约定。调用clone方法时,会判断有没有实现Cloneable接口,没有实现Cloneable的话会抛异常CloneNotSupportedException。
- 覆盖clone()方法,可见性提升为public。
```
public class Cat implements Cloneable {
private String name;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
Cat c = new Cat();
c.name = "程序员大彬";
Cat cloneCat = (Cat) c.clone();
c.name = "大彬";
System.out.println(cloneCat.name);
}
//output
//程序员大彬
}
```
**getClass**
返回此 Object 的运行时类,常用于java反射机制。
# 16.为什么重写 equals 时一定要重写 hashCode?
因为两个相等的对象的 `hashCode` 值必须是相等。也就是说如果 `equals` 方法判断两个对象是相等的,那这两个对象的 `hashCode` 值也要相等。
如果重写 `equals()` 时没有重写 `hashCode()` 方法的话就可能会导致 `equals` 方法判断是相等的两个对象,`hashCode` 值却不相等。这样,当用其中的一个对象作为键保存到hashMap、hashTable或hashSet中,再以另一个对象作为键值去查找他们的时候,则会查找不到。
# 17.两个对象的hashCode()相同,则 equals()是否也一定为 true?
- `equals` 方法判断两个对象是相等的,那这两个对象的 `hashCode` 值也要相等。
- 两个对象有相同的 `hashCode` 值,他们也不一定是相等的(哈希碰撞)。
hashcode方法主要是用来**提升对象比较的效率**,先进行hashcode()的比较,如果不相同,那就不必在进行equals的比较,这样就大大减少了equals比较的次数,当比较对象的数量很大的时候能提升效率。
# 18.String
## 1.String, StringBuffer 和 StringBuilder区别
**1. 可变性**
- String 不可变
- StringBuffer 和 StringBuilder 可变
**2. 线程安全**
- String 不可变,因此是线程安全的
- StringBuilder 不是线程安全的
- StringBuffer 是线程安全的,内部使用 synchronized 进行同步
**对于三者使用的总结:**
1. 操作少量的数据: 适用 `String`
1. 单线程操作字符串缓冲区下操作大量数据: 适用 `StringBuilder`
1. 多线程操作字符串缓冲区下操作大量数据: 适用 `StringBuffer`
## 2.String 为什么不可变?
先看看什么是不可变的对象。
如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。
接着来看Java8 String类的源码:
```
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
}
```
从源码可以看出,String对象其实在内部就是一个个字符,存储在这个value数组里面的。
(1)value数组用final修饰,final 修饰的变量,值不能被修改。因此value不可以指向其他对象。
(2)String类内部所有的字段都是私有的,也就是被private修饰。而且String没有对外提供修改内部状态的方法,因此value数组不能改变。
所以,String是不可变的。
那为什么String要设计成不可变的?
主要有以下几点原因:
1. **线程安全**。同一个字符串实例可以被多个线程共享,因为字符串不可变,本身就是线程安全的。
1. **支持hash映射和缓存**。因为String的hash值经常会使用到,比如作为 Map 的键,不可变的特性使得 hash 值也不会变,不需要重新计算。
1. **出于安全考虑**。网络地址URL、文件路径path、密码通常情况下都是以String类型保存,假若String不是固定不变的,将会引起各种安全隐患。比如将密码用String的类型保存,那么它将一直留在内存中,直到垃圾收集器把它清除。假如String类不是固定不变的,那么这个密码可能会被改变,导致出现安全隐患。
1. **字符串常量池优化**。String对象创建之后,会缓存到字符串常量池中,下次需要创建同样的对象时,可以直接返回缓存的引用。
既然我们的String是不可变的,它内部还有很多substring, replace, replaceAll这些操作的方法。这些方法好像会改变String对象?怎么解释呢?
其实不是的,我们每次调用replace等方法,其实会在堆内存中创建了一个新的对象。然后其value数组引用指向不同的对象。
## 3.String.equals() 和 Object.equals() 有何区别?
`String` 中的 `equals` 方法是被重写过的,比较的是 String 字符串的值是否相等。 `Object` 的 `equals` 方法是比较的对象的内存地址。
## 4.什么是字符串常量池?
**字符串常量池** /(String Pool)是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。在创建字符串时,JVM首先会检查字符串常量池,如果该字符串已经存在池中,则返回其引用,如果不存在,则创建此字符串并放入池中,并返回其引用。
```
// 将字符串对象”ab“的引用保存在字符串常量池中
String aa = "ab";
// 直接返回字符串常量池中字符串对象”ab“的引用
String bb = "ab";
System.out.println(aa==bb);// true
```
## 5.new String("abc")会创建几个对象?
会创建 1 或 2 个字符串对象。
1、如果字符串常量池中不存在字符串对象“abc”的引用,那么它将首先在字符串常量池中创建,然后在堆空间中创建,因此将创建总共 2 个字符串对象。
示例代码(JDK 1.8):
```
String s1 = new String("abc");
```
2、如果字符串常量池中已存在字符串对象“abc”的引用,则只会在堆中创建 1 个字符串对象“abc”。
示例代码(JDK 1.8):
```
// 字符串常量池中已存在字符串对象“abc”的引用
String s1 = "abc";
// 下面这段代码只会在堆中创建 1 个字符串对象“abc”
String s2 = new String("abc");
```
# 19.Java创建对象有几种方式?
Java创建对象有以下几种方式:
- 用new语句创建对象。
- 使用反射,使用Class.newInstance()创建对象。
- 调用对象的clone()方法。
- 运用反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法。
# 20.说说类实例化的顺序
Java中类实例化顺序:
1. 静态属性,静态代码块。
1. 普通属性,普通代码块。
1. 构造方法。
# 21.常见的关键字
## **static**
static可以用来修饰类的成员方法、类的成员变量
## **final**
1. **基本数据**类型用final修饰,则不能修改,是常量;**对象引用**用final修饰,则引用只能指向该对象,不能指向别的对象,但是对象本身可以修改。
1. final修饰的方法不能被子类重写
1. final修饰的类不能被继承。
## **this**
`this.属性名称`指访问类中的成员变量,可以用来区分成员变量和局部变量。
## **super**
super 关键字用于在子类中访问父类的变量和方法。
# 22.异常
## 1.Exception 和 Error 有什么区别?
在 Java 中,所有的异常都有一个共同的祖先 `java.lang` 包中的 `Throwable` 类。`Throwable` 类有两个重要的子类:
- **`Exception`** :程序本身可以处理的异常,可以通过 `catch` 来进行捕获。`Exception` 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
- **`Error`**:`Error` 属于程序无法处理的错误 ,我们没办法通过 `catch` 来进行捕获不建议通过`catch`捕获 。例如 Java 虚拟机运行错误(`Virtual MachineError`)、虚拟机内存不够错误(`OutOfMemoryError`)、类定义错误(`NoClassDefFoundError`)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
## 2.运行时异常(RuntimeException)和非运行时异常(checkedExecption)的区别?
`unchecked exception`包括`RuntimeException`和`Error`类,其他所有异常称为检查(checked)异常。
1. `RuntimeException`由程序错误导致,应该修正程序避免这类异常发生。Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。
1. `checked Exception`由具体的环境(读取的文件不存在或文件为空或sql异常)导致的异常。必须进行处理,不然编译不通过,可以catch或者throws。
## 3.Throwable 类常用方法有哪些?
- `String getMessage()`: 返回异常发生时的简要描述
- `String toString()`: 返回异常发生时的详细信息
- `String getLocalizedMessage()`: 返回异常对象的本地化信息。使用 `Throwable` 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 `getMessage()`返回的结果相同
- `void printStackTrace()`: 在控制台上打印 `Throwable` 对象封装的异常信息
## 4.Java中的finally一定会被执行吗?
答案是不一定。
有以下两种情况finally不会被执行:
- 程序未执行到try代码块
- 如果当一个线程在执行 try 语句块或者 catch 语句块时被打断(interrupted)或者被终止(killed),与其相对应的 finally 语句块可能不会执行。还有更极端的情况,就是在线程运行 try 语句块或者 catch 语句块时,突然死机或者断电,finally 语句块肯定不会执行了。
## 5.throw和throws的区别?
**(1)throws用于方法头,表示的只是异常的申明,而throw用于方法内部,抛出的是`异常对象`**
**(2)throws可以一次性抛出多个异常,而throw只能一个** **(3)throws抛出异常时,它的上级(调用者)也要申明抛出异常或者捕获,不然编译报错。而throw的话,可以不申明或不捕获(这是非常不负责任的方式)但编译器不会报错。**
![在这里插入图片描述](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/664ec4da229b4e52b6788c4fc98e3d83~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=784&h=526&s=61109&e=png&b=fffcfc)
# 23.方法重载和重写的区别?
**同个类中的多个方法可以有相同的方法名称,但是有不同的参数列表,这就称为方法重载**。参数列表又叫参数签名,包括参数的类型、参数的个数、参数的顺序,只要有一个不同就叫做参数列表不同。
重载是面向对象的一个基本特性。
```
public class OverrideTest {
void setPerson() { }
void setPerson(String name) {
//set name
}
void setPerson(String name, int age) {
//set name and age
}
}
```
**方法的重写描述的是父类和子类之间的。当父类的功能无法满足子类的需求,可以在子类对方法进行重写**。方法重写时, 方法名与形参列表必须一致。
如下代码,Person为父类,Student为子类,在Student中重写了dailyTask方法。
```
public class Person {
private String name;
public void dailyTask() {
System.out.println("work eat sleep");
}
}
public class Student extends Person {
@Override
public void dailyTask() {
System.out.println("study eat sleep");
}
```
# 24.泛型
Java泛型是JDK 5中引⼊的⼀个新特性, 允许在定义类和接口的时候使⽤类型参数。声明的类型参数在使⽤时⽤具体的类型来替换。
泛型最⼤的好处是可以提⾼代码的复⽤性。以List接口为例,我们可以将String、 Integer等类型放⼊List中, 如不⽤泛型, 存放String类型要写⼀个List接口, 存放Integer要写另外⼀个List接口, 泛型可以很好的解决这个问题
见博客:[详解泛型类 - 掘金 (juejin.cn)](https://juejin.cn/post/7291482092830867517)
# 25.反射
见博客:[详解反射机制和常见应用场景 - 掘金 (juejin.cn)](https://juejin.cn/post/7291680355810869311#heading-1)
## 1.什么是反射
动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。 通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
## 2.反射应用场景
正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。**这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。**
Java 中的一大利器 **注解** 的实现也用到了反射。 为什么你使用 Spring 的时候 ,一个`@Component`注解就声明了一个类为 Spring Bean 呢?为什么你通过一个 `@Value`注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?
这些都是因为你可以基于反射分析类,然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后,就可以做进一步的处理。
# 26.序列化与反序列化
## 1.什么是序列化?什么是反序列化?
如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。
简单来说:
- **序列化**:将数据结构或对象转换成二进制字节流的过程
- **反序列化**:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
## 2.常见应用场景
下面是序列化和反序列化常见应用场景:
- 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
- 将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化;
- 将对象存储到数据库(如 Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化;
- 将对象存储到内存之前需要进行序列化,从内存中读取出来之后需要进行反序列化。
## 3.如果有些字段不想进行序列化怎么办?(transient关键字)
Java语言的关键字,变量修饰符,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。
也就是说被transient修饰的成员变量,在序列化的时候其值会被忽略,在被反序列化后, transient 变量的值被设为初始值, 如 int 型的是 0,对象型的是 null。
## 4.实现序列化和反序列化为什么要实现 Serializable 接口?
一个对象序列化的接口,一个类只有实现了Serializable接口,它的对象才能被序列化。
如果一个对象既不是**字符串**、**数组**、**枚举**,而且也没有实现`Serializable`接口的话,在序列化时就会抛出`NotSerializableException`异常!`Serializable`接口也仅仅只是做一个标记用!它告诉代码只要是实现了`Serializable`接口的类都是可以被序列化的!
见博客:[为什么定义实体类要实现Serializable接口以及何为序列化和反序列化 - 掘金 (juejin.cn)](https://juejin.cn/post/7291165614978072633#heading-5)
# 27.I/O流
## 1.Java IO 流了解吗?
IO 即 `Input/Output`,输入和输出。数据输入到计算机内存的过程即输入,反之输出到外部存储(比如数据库,文件,远程主机)的过程即输出。数据传输过程类似于水流,因此称为 IO 流。IO 流在 Java 中分为输入流和输出流,而根据数据的处理方式又分为字节流和字符流。
Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。
- `InputStream`/`Reader`: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- `OutputStream`/`Writer`: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
## 2.I/O 流为什么要分为字节流和字符流呢?
问题本质想问:**不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?**
首先明确字节流适用于任何场景,而且有字节缓冲流,能提高读取和输入的效率,也就是BufferedOutputStream/BufferedInputStream。其操作与字节流基本都一样。 而字符流是为了应对汉字出现的情况。在GBK中汉字占2个字节,在UTF-8中汉字占3个字节,所以我们通过字节流读取文件的时候一般都是逐个字节转换就会导致乱码,而手动去根据不同编码去拼接则不方便,所以有字符流。
## 3.BIO、NIO 和 AIO 的区别?
见博客:[故事讲解BIO与NIO - 掘金 (juejin.cn)](https://juejin.cn/post/7299017022944149556)
BIO、NIO和AIO是Java编程语言中用于处理输入输出(IO)操作的三种不同的机制,它们分别代表同步阻塞I/O,同步非阻塞I/O和异步非阻塞I/O。
BIO(同步阻塞I/O):线程发起IO请求,不管内核是否准备好IO操作,从发起请求起,线程一直阻塞,直到操作完成。 NIO(同步非阻塞I/O):线程发起IO请求,立即返回;内核在做好IO操作的准备之后,通过调用注册的回调函数通知线程做IO操作,线程开始阻塞,直到操作完成。 AIO(异步非阻塞I/O):线程发起IO请求,立即返回;内存做好IO操作的准备之后,做IO操作,直到操作完成或者失败,通过调用注册的回调函数通知线程做IO操作完成或者失败。
- BIO是一个连接一个线程。
- NIO是一个请求一个线程。
- AIO是一个有效请求一个线程
# 28.同步和异步的区别
同步:发出一个调用时,在没有得到结果之前,该调用就不返回。
异步:在调用发出后,被调用者返回结果之后会通知调用者,或通过回调函数处理这个调用。
# 29.阻塞和非阻塞的区别
阻塞和非阻塞关注的是线程的状态。
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会恢复运行。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
> 举个例子,理解下同步、阻塞、异步、非阻塞的区别:
>
> 同步就是烧开水,要自己来看开没开;异步就是水开了,然后水壶响了通知你水开了(回调通知)。阻塞是烧开水的过程中,你不能干其他事情,必须在旁边等着;非阻塞是烧开水的过程里可以干其他事情。
# 30.过滤器和拦截器的区别
见博客:[过滤器和拦截器的区别 - 掘金 (juejin.cn)](https://juejin.cn/post/7299123674577190962)
归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术
拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强
实现原理不同:过滤器是基于函数回调的,拦截器是基于Java的反射机制(动态代理)实现的。一般自定义的过滤器中都会实现一个doFilter()方法,这个方法有一个FilterChain参数,而实际上它是一个回调接口。
# 31.为什么在阿里巴巴Java开发手册中强制要求使用包装类型定义属性呢?
嗯,以布尔字段为例,当我们没有设置对象的字段的值的时候,Boolean类型的变量会设置默认值为`null`,而boolean类型的变量会设置默认值为`false`。
也就是说,包装类型的默认值都是null,而基本数据类型的默认值是一个固定值,如boolean是false,byte、short、int、long是0,float是0.0f等。
举一个例子,比如有一个扣费系统,扣费时需要从外部的定价系统中读取一个费率的值,我们预期该接口的返回值中会包含一个浮点型的费率字段。当我们取到这个值得时候就使用公式:金额*费率=费用 进行计算,计算结果进行划扣。
如果由于计费系统异常,他可能会返回个默认值,如果这个字段是Double类型的话,该默认值为null,如果该字段是double类型的话,该默认值为0.0。
如果扣费系统对于该费率返回值没做特殊处理的话,拿到null值进行计算会直接报错,阻断程序。拿到0.0可能就直接进行计算,得出接口为0后进行扣费了。这种异常情况就无法被感知。
**那我可以对0.0做特殊判断,如果是0就阻断报错,这样是否可以呢?**
不对,这时候就会产生一个问题,如果允许费率是0的场景又怎么处理呢?
使用基本数据类型只会让方案越来越复杂,坑越来越多。
这种使用包装类型定义变量的方式,通过异常来阻断程序,进而可以被识别到这种线上问题。如果使用基本数据类型的话,系统可能不会报错,进而认为无异常。
因此,建议在POJO和RPC的返回值中使用包装类型。