欢迎关注文章系列,一起学习
《提升能力,涨薪可待篇》
《面试知识,工作可待篇》
《实战演练,拒绝996篇》
也欢迎关注微信公众号【Ccww笔记】,原创技术文章第一时间推出
如果此文对你有帮助、喜欢的话,那就点个赞呗,点个关注呗!
《面试知识,工作可待篇》-Java笔试面试基础知识大全
前言
是不是感觉找工作面试是那么难呢?
在找工作面试应在学习的基础进行总结面试知识点,工作也指日可待,欢迎一起学习【面试知识,工作可待】系列
《面试知识,工作可待篇》
- 《面试知识,工作可待:集合篇》-java集合面试知识大全
- 《面试知识,工作可待:多线程篇》java多线程并发系列–基础知识点(笔试、面试必备)
- 《面试知识,工作可待:JVM篇》JVM虚拟机(内存、垃圾回收、性能优化)(笔试、面试必备)
- ….
1. Java环境
1. 1 Java 和 C++ 的区别?
- 都是面向对象的语言,都支持封装、继承和多态。
- Java 不提供指针来直接访问内存,程序内存更加安全。
- Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承。
【重要】Java 有自动内存管理机制,不需要程序员手动释放无用内存。
1.2 JDK、JRE、JVM
1.2.1 JDK
JDK 即为 Java 开发工具包,包含编写 Java 程序所必须的编译、运行等开发工具以及 JRE。开发工具如:
- 用于编译 Java 程序的 javac 命令。
- 用于启动 JVM 运行 Java 程序的 Java 命令。
- 用于生成文档的 Javadoc 命令。
- 用于打包的 jar 命令等等。
1.2..2 JRE
JRE 即为 Java 运行环境,提供了运行 Java 应用程序所必须的软件环境,包含有 Java 虚拟机(JVM)和丰富的系统类库。系统类库即为 Java 提前封装好的功能类,只需拿来直接使用即可,可以大大的提高开发效率。
1.2..3 JVM
JVM 即为 Java 虚拟机,提供了字节码文件(.class)的运行环境支持。
1.2..4 三者关系
- JDK > JRE > JVM
1.2 为什么 Java 被称作是“平台无关的编程语言”?
- Java 虚拟机是一个可以执行 Java 字节码的虚拟机进程。
- Java 源文件( .java )被编译成能被 Java 虚拟机执行的字节码文件( .class )。
-
Java 被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java 虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。
1.3 什么是字节码?
这个问题,面试官可以衍生提问,Java 是编译执行的语言,还是解释执行的语言。
Java 中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。
编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在 Java 中,这种供虚拟机理解的代码叫做字节码(即扩展名为 .class 的文件),它不面向任何特定的处理器,只面向虚拟机。
每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java 源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了 Java 的编译与解释并存的特点。
1.4 Java 源代码
=> 编译器 => JVM 可执行的 Java 字节码(即虚拟指令)=> JVM => JVM 中解释器 => 机器可执行的二进制机器码 => 程序运行
1.5 采用字节码的好处?
Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行。
解释型语言:解释型语言,是在运行的时候将程序翻译成机器语言。解释型语言的程序不需要在运行前编译,在运行程序的时候才翻译,专门的解释器负责在每个语句执行的时候解释程序代码。这样解释型语言每执行一次就要翻译一次,效率比较低
例如:Python、PHP 。
2. 面向对象和面向过程
2.1 什么是面向对象?
面向对象是一种思想,万事万物抽象成一个对象,这里只讨论面向对象编程(OOP),Java 是一个支持并发、基于类和面向对象的计算机编程语言。
2.1.1 类class
类是抽象的概念,是万事万物的抽象,是一类事物的共同特征的集合。
用计算机语言来描述类,是属性和方法的集合。
2.1.2 对象instance、object
对象是类的具象,是一个实体。
对于我们每个人这个个体,都是抽象概念人 类 的不同的 实体 。
面向对象软件开发具有以下优点:
- 代码开发模块化,更易维护和修改。
- 代码复用性强。
- 增强代码的可靠性和灵活性。
- 增加代码的可读性。
2.2 面向对象的特征
2.1 封装
封装,给对象提供了隐藏内部特性和行为的能力。对象提供一些能被其他对象访问的方法来改变它内部的数据。
在 Java 当中,有 4 种修饰符: default、public、private 和 protected 。每一种修饰符给其他的位于同一个包或者不同包下面对象赋予了不同的访问权限,权限如下:
访问权限 | 类 | 包 | 子类 | 其他包 |
---|---|---|---|---|
public | ∨ | ∨ | ∨ | ∨ |
protect | ∨ | ∨ | ∨ | × |
default | ∨ | ∨ | × | × |
private | ∨ | × | × | × |
封装好处:
- 通过隐藏对象的属性来保护对象内部的状态。
- 提高了代码的可用性和可维护性,因为对象的行为可以被单独的改变或者是扩展。
- 禁止对象之间的不良交互提高模块化。
2.2 继承
继承,使对象基于基类字段和方法,新增自定义的的方法和属性。继承提供了代码的重用行,也可以在不修改类的情况下给现存的类添加新特性。
继承属性:
- 子类拥有父类非 private 的属性和方法。
- 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法
2.3 多态
多态,程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
Java中有两种形式可以实现多态:
- 继承(多个子类对同一方法的重写)
- 接口(实现接口并覆盖接口中同一方法)
2.3 面向对象和面向过程的区别?
面向过程:就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了,
面向对象:是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
3.1 面向过程
- 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源。比如,单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开发,性能是最重要的因素。
- 缺点:没有面向对象易维护、易复用、易扩展。
3.2 面向对象
- 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
- 缺点:性能比面向过程低。
3. Java数据类型(基本数据类型和引用类型)
3. 1 基本数据类型如下:
- 整数值型:byte、short、int、long
- 字符型:char
- 浮点类型:float、double
- 布尔型:boolean
整数型:默认 int 型,小数默认是 double 型。Float 和 Long 类型的必须加后缀。
比如:float f = 100f 。
引用类型声明的变量是指该变量在内存中实际存储的是一个引用地址,实体在堆中。
3.2 引用类型
引用类型指向一个对象,不是原始值,指向对象的变量是引用变量
在java里面除去基本数据类型的其他类型都是引用类型,自己定义的class类都是引用类型,可以像基本类型一样使用。
引用类型常见的有:String、StringBuffer、ArrayList、HashSet、HashMap等
特别注意,String 是引用类型不是基本类型。
3.3 引用类型简介
引用类型 | 对象是否可引用 | 回收时间 | 使用场景 |
---|---|---|---|
强引用 | 可以 | 从不回收 | 普遍对象的状态 |
软引用 | 可以 | 内存不足时 | 内存敏感的高速缓存 |
弱引用 | 可以 | 下一次GC | 对象缓存 |
虚引用 | 不可以 | 下一次GC | 一般用于追踪垃圾收集器的回收动作 |
3. 4 什么是值传递和引用传递?
- 值传递,是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。
-
引用传递,一般是对于对象型变量而言的,传递的是该对象地址的一个副本,并不是原对象本身。
一般认为,Java 内的传递都是值传递,Java 中实例对象的传递是引用传递。
3. 5 char 型变量中能不能存贮一个中文汉字?为什么?
- C 语言中,char 类型占 1 个字节,而汉字占 2 个字节,所以不能存储。
- Java 语言中,char 类型占 2 个字节,而且 Java 默认采用 Unicode 编码,一个 Unicode 码是 16 位,所以一个 Unicode 码占两个字节,Java 中无论汉字还是英文字母,都是用 Unicode 编码来表示的。所以,在 Java 中,char 类型变量可以存储一个中文汉字。
3.6 equals 与 == 的区别?
- 值类型(int,char,long,boolean 等)都是用 == 判断相等性。
-
对象引用的话
- == 判断引用所指的对象是否是同一个。
- equals 方法,是 Object 的成员函数,有些类会覆盖(override) 这个方法,用于判断对象的等价性。
例如 String 类,两个引用所指向的 String 都是 “abc” ,但可能出现他们实际对应的对象并不是同一个(和 JVM 实现方式有关),因此用 == 判断他们可能不相等,但用 equals 方法判断一定是相等的。
4. Java类Class
类是对事物的抽象,抽象类是对类的抽象,接口是对抽象类的抽象。
4.1 Java 对象(Class)创建的方式?
- 使用 new 关键字创建对象。
- 使用 Class 类的 newInstance 方法(反射机制)。
- 使用 Constructor 类的 newInstance 方法(反射机制)。
- 使用 clone 方法创建对象。
- 使用(反)序列化机制创建对象。
4.2 抽象类与接口
4.2.1 抽象类
从面向对象的角度来讲,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样,并不是 所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就可以认为是抽象类。
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。
4.2.2 接口
接口,在JAVA编程语言中是一个抽象类型,主要是抽象方法的集合,接口中的变量定义必须为public static final类型。接口通常以interface来声明。
4.2.3 抽象类与接口的对比
参数 | 抽象类 | 接口 |
---|---|---|
默认的方法实现 | 它可以有默认的方法实现 | 接口完全是抽象的。它根本不存在方法的实现 |
实现 | 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 | 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现 |
构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
与正常Java类的区别 | 除了你不能实例化抽象类之外,它和普通Java类没有任何区别 | 接口是完全不同的类型 |
访问修饰符 | 抽象方法可以有public、protected和default这些修饰符 | 接口方法默认修饰符是public。你不可以使用其它修饰符。 |
main方法 | 抽象方法可以有main方法并且我们可以运行它 | 接口没有main方法,因此我们不能运行它。(java8以后接口可以有default和static方法,所以可以运行main方法) |
多继承 | 抽象方法可以继承一个类和实现多个接口 | 接口只可以继承一个或多个其它接口 |
速度 | 它比接口速度要快 | 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。 |
添加新方法 | 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码 | 如果你往接口中添加方法,那么你必须改变实现该接口的类。 |
4.3 讲讲类的实例化顺序
初始化顺序如下:
->父类静态变量
->父类静态代码块
->子类静态变量、
->子类静态代码块
->父类非静态变量(父类实例成员变量)
->父类构造函数
->子类非静态变量(子类实例成员变量)
->子类构造函数
4.4 内部类
简单的说,就是在一个类、接口或者方法的内部创建另一个类。这样理解的话,创建内部类的方法就很明确了。当然,详细的可以看看 《Java 内部类总结(吐血之作)》 文章。
4.4.1 内部类的作用是什么?
- 内部类可以很好的实现隐藏(一般的非内部类,是不允许有 private 与protected权限的,但内部类可以)
- 内部类拥有外围类的所有元素的访问权限
- 可是实现多重继承
- 可以避免修改接口而实现同一个类中两种同名方法的调用。
4.5 Anonymous Inner Class(匿名内部类)是否可以继承其它类?是否可以实现接口?
可以继承其他类或实现其他接口,在 Java 集合的流式操作中,我们常常这么干。
4.6 内部类可以引用它的包含类(外部类)的成员吗?有没有什么限制?
一个内部类对象可以访问创建它的外部类对象的成员,包括私有成员。
如果你把静态嵌套类当作内部类的一种特例,那在这种情况下不可以访问外部类的普通成员变量,而只能访问外部类中的静态成员,例如:
class Outer {
static int x;
static class Inner {
void test() {
syso(x);
}
}
}
4.7 构造方法、构造方法重载
4.7.1 构造方法
当新对象被创建的时候,构造方法会被调用。每一个类都有构造方法。在程序员没有给类提供构造方法的情况下,Java 编译器会为这个类创建一个默认的构造方法。
4.7.2 构造方法重载
Java 中构造方法重载和方法重载很相似。可以为一个类创建多个构造方法。每一个构造方法必须有它自己唯一的参数列表。
4. 8 重载和重写的区别?
4.8.1 重写 override
- 方法名、参数、返回值相同。
- 子类方法不能缩小父类方法的访问权限。
- 子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。
- 存在于父类和子类之间。
- 方法被定义为 final 不能被重写。
4.8.2 重载 overload
- 参数类型、个数、顺序至少有一个不相同。
- 不能重载只有返回值不同的方法名。
- 存在于父类和子类、同类中。
4.9 hashCode() 以及equals()
4.9.1 为什么需要子类实现这两个方法?
父类的 equals ,一般情况下是无法满足子类的 equals 的需求。
比如所有的对象都继承 Object ,默认使用的是 Object 的 equals 方法,在比较两个对象的时候,是看他们是否指向同一个地址。但是我们的需求是对象的某个属性相同,就相等了,而默认的 equals 方法满足不了当前的需求,所以我们要重写 equals 方法。
如果重写了 equals 方法,就必须重写 hashCode 方法,否则就会降低 Map 等集合的索引速度。
4.9.2 说一说你对 java.lang.Object 对象中 hashCode 和 equals 方法的理解。在什么场景下需要重新实现这两个方法?
理解答案与4.8.1差不多,
equals 方法,用于比较对象的内容是否相等。
当覆盖了 equals 方法时,比较对象是否相等将通过覆盖后的 equals 方法进行比较(判断对象的内容是否相等)。
hashCode 方法,大多在集合中用到。
将对象放入到集合中时,首先判断要放入对象的 hashCode 值与集合中的任意一个元素的 hashCode 值是否相等,如果不相等直接将该对象放入集合中。
如果 hashCode 值相等,然后再通过 equals 方法判断要放入对象与集合中的任意一个对象是否相等,如果 equals 判断不相等,直接将该元素放入到集合中,否则不放入。
4.9.3 有没有可能 2 个不相等的对象有相同的 hashCode?
可能会发生,这个被称为哈希碰撞。当然,相等的对象,即我们重写了 equals 方法,一定也要重写 hashCode 方法,否则将出现我们在 HashMap 中,相等的对象作为 key ,将找不到对应的 value 。
4.9.4 equals 和 hashCode 的关系
- equals 不相等,hashCode 可能相等。
-
equals 相等,请重写 hashCode 方法,保证 hashCode 相等。
一般来说,hashCode 方法的重写,可以看看 《科普:为什么 String hashCode 方法选择数字31作为乘子》 方法。
5. 常用类
5.1 String、StringBuffer、StringBuilder
5.1.1 String、StringBuffer、StringBuilder 的区别?
可变性:
String 类中使用 final 关键字字符数组保存字符串,代码:
private final char value[],
所以string对象是不可变的。
StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在 AbstractStringBuilder 中也是使用字符数组保存字符串 char[] value ,但是没有用 final 关键字修饰,代码:
char[]value
这两种对象都是可变的。
线程安全性:
- String中的对象是不可变的,也就可以理解为常量,线程安全。
- StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。
- StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
AbstractStringBuilder是StringBuilder与StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexOf等公共方法。
性能:
每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。
StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
5.1.2对于三者使用的总结
- 操作少量的数据用 = String
- 单线程操作字符串缓冲区 下操作大量数据 = StringBuilder,甚至有时,我们为了避免每个线程重复创建 StringBuilder 对象,会通过 ThreadLocal + StringBuilder 的方式,进行对 StringBuilder 的重用
- 多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
5.1.3 String s = new String(“xyz”) 会创建几个对象?
首先,在 String 池内找,找到 “xyz” 字符串,不创建 “xyz” 对应的 String 对象,否则创建一个对象。
然后,遇到 new 关键字,在内存上创建 String 对象,并将其返回给 s ,又一个对象。
所以,总共是 1 个或者 2 个对象
5.1.4 StringTokenizer 是什么?
StringTokenizer ,是一个用来分割字符串的工具类。
示例代码如下:
StringTokenizer st = new StringTokenizer(”Hello World”);
while (st.hasMoreTokens()) {
System.out.println(st.nextToken());
}
输出如下:
Hello
World
5.2 什么是自动拆装箱?
自动装箱和拆箱,就是基本类型和引用类型之间的转换。
5.2.1 什么要转换?
如果你在 Java5 下进行过编程的话,你一定不会陌生这一点,你不能直接地向集合( Collection )中放入原始类型值,因为集合只接收对象。
5.3 int 和 Integer 有什么区别?
- int 是基本数据类型。
-
Integer 是其包装类,注意是一个类。
需要注意下 Integer 的缓存策略
5.2.3 理解Java Integer 的缓存策略
6. 关键字
6.1 final、finally、finalize
6.1.1 final
final ,是修饰符关键字。
- Class 类:如果一个类被声明为 final ,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract 的,又被声明为 final 的。
-
变量或方法声明为 final ,可以保证它们在使用中不被改变。被声明为 final 的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为 final 的方法也同样只能使用,不能重写。
另外,在早期的 Java 实现版本中,会将 final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的 Java 版本已经不需要使用 final 方法进行这些优化了)。类中所有的private 方法都隐式地指定为 final 。
6.1.2 finally
在异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入 finally 块(如果有的话)。
在以下 4 种特殊情况下,finally块不会被执行:
- 在 finally 语句块中发生了异常。
- 在前面的代码中用了 System.exit() 退出程序。
- 程序所在的线程死亡。
- 关闭 CPU 。
6.1.3 finalize
finalize ,是方法名。
Java 允许使用 #finalize() 方法,在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。
它是在 Object 类中定义的,因此所有的类都继承了它。
子类覆盖 finalize() 方法,以整理系统资源或者执行其他清理工作。
finalize() 方法,是在垃圾收集器删除对象之前对这个对象调用的。
一般情况下,我们在业务中不会自己实现这个方法,更多是在一些框架中使用。
6.1.4 String 类能被继承吗,为什么?
不能,因为 String 是 final 修饰。
6.2 static
6.2.1 static特点
- static是一个修饰符,用于修饰成员。(成员变量,成员函数)static修饰的成员变量 称之为静态变量或类变量。
- static修饰的成员被所有的对象共享。
- static优先于对象存在,因为static的成员随着类的加载就已经存在。
- static修饰的成员多了一种调用方式,可以直接被类名所调用,(类名.静态成员)。
- static修饰的数据是共享数据,对象中的存储的是特有的数据
6.2.2 是否可以在 static方法中访问非 static 变量?
static 变量在 Java 中是属于类的,它在所有的实例中的值是一样的。当类被 Java 虚拟机载入的时候,会对 static 变量进行初始化。如果你的代码尝试不用实例来访问非 static 的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。
由于静态方法可以不通过对象进行调用,因此在静态方法里,不能调用其他非静态变量,也不可以访问非静态变量成员。
如果你的代码尝试不用实例来访问非 static 的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。
6.2.3 成员变量和静态变量的区别:
-
生命周期的不同:
- 成员变量随着对象的创建而存在随着对象的回收而释放。
- 静态变量随着类的加载而存在随着类的消失而消失。
-
调用方式不同:
- 成员变量只能被对象调用。
- 静态变量可以被对象调用,也可以用类名调用。(推荐用类名调用)
-
别名不同:
- 成员变量也称为实例变量。
- 静态变量称为类变量。
-
数据存储位置不同:
- 成员变量数据存储在堆内存的对象中,所以也叫对象的特有数据。
- 静态变量数据存储在方法区(共享数据区)的静态区,所以也叫对象的共享数据。
6.2.4 static 关键字修饰的加载顺序
->父类静态变量
->父类静态代码块
->子类静态变量
->子类静态代码块
->父类普通变量
->父类普通代码块
->父类构造函数
->子类普通变量
->子类普通代码块
->子类构造函数
6.3 transient 关键字
transient声明一个实例变量,当对象存储时,它的值不需要维持。换句话来说就是,用transient关键字标记的成员变量不参与序列化过程,
transient 只能修饰变量,不能修饰类和方法。
6.3.1 Java 序列话中,如果有些字段不想进行序列化怎么办?
对于不想进行序列化的变量,使用 transient 关键字修饰,
- 当对象被序列化时,阻止实例中那些用此关键字修饰的的变量序列化。
- 当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。
6.4 volatile关键词
volatile 关键字用在多线程同步中,可保证读取的可见性,JVM只是保证从主内存加载到线程工作内存的值是最新的读取值,而非 cache 中
6.4.1 volatile关键字是否能保证线程安全?
不能 , 多个线程对 volatile 的写操作,无法保证线程安全。例如假如线程 1,线程 2 在进行 read,load 操作中,发现主内存中 count 的值都是 5,那么都会加载这个最新的值,在线程 1 堆 count 进行修改之后,会 write 到主内存中,主内存中的 count 变量就会变为 6;线程 2 由于已经进行 read,load 操作,在进行运算之后,也会更新主内存 count 的变量值为 6;导致两个线程及时用 volatile 关键字修改之后,还是会存在并发的情况
7. Java IO
7.1 Java IO 相关的类
Java IO 相关的类,在 java.io 包下,具体操作分成面向字节(Byte)和面向字符(Character)两种方式。如下图所示:
7.2 什么是 Java 序列化?
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。
可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。
序列化是为了解决在对对象流进行读写操作时所引发的问题。
反序列化的过程,则是和序列化相反的过程。
我们不能将序列化局限在 Java 对象转换成二进制数组,比如,将一个 Java 对象转换成 JSON 字符串等,这也可以理解为是序列化。
7.2.1如何实现 Java 序列化?
将需要被序列化的类,实现 Serializable 接口,该接口没有需要实现的方法,implements Serializable 只是为了标注该对象是可被序列化的。
-
序列化
- 首先使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象
- 接着,使用 ObjectOutputStream 对象的 #writeObject(Object obj) 方法,就可以将参数为 obj 的对象写出(即保存其状态)。
-
反序列化
- 要恢复的话则用输入流。
7.3 如何实现对象克隆(浅克隆和深克隆)?
- 实现 Cloneable 接口,并重写 Object 类中的 #clone() 方法。可以实现浅克隆,也可以实现深克隆。
- 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆。可以实现真正的深克隆。
实际场景下,我们使用的克隆比较少,更多是对象之间的属性克隆。例如说,将 DO 的属性复制到 DTO 中,又或者将 DTO 的属性复制到 VO 中。此时,我们一般使用 BeanUtils 工具类。
8.异常
8.1 异常机制的概述
异常机制是指当程序出现错误后,程序如何处理。具体来说,异常机制提供了程序退出的安全通道。当出现错误后,程序执行的流程发生改变,程序的控制权转移到异常处理器。
程序错误分为三种:
- 编译错误:因为程序没有遵循语法规则,编译程序能够自己发现并且提示我们错误的原因和位置,这个也是大家在刚接触编程语言最常遇到的问题。
- 运行时错误:因为程序在执行时,运行环境发现了不能执行的操作。
- 逻辑错误:因为程序没有按照预期的逻辑顺序执行。异常也就是指程序运行时发生错误,而异常处理就是对这些错误进行处理和控制。
8.2 Throwable
Throwable 类图
![](https://oss.jqhtml.com/wp-content/uploads/2020/7/QFjIbi.png)
Throwable有两个重要的子类 :
- Exception(异常)
- Error(错误)
二者都是 Java 异常处理的重要子类,各自都包含大量子类
8.2.1 Exception(异常)和 Error(错误)
-
Error(错误),表示系统级的错误和程序不必处理的异常,是 Java 运行环境中的内部错误或者硬件问题。
- 例如:内存资源不足等。
- 对于这种错误,程序基本无能为力,除了退出运行外别无选择,它是由 Java 虚拟机抛出的
-
Exception(异常),表示需要捕捉或者需要程序进行处理的异常,它处理的是因为程序设计的瑕疵而引起的问题或者在外的输入等引起的一般性问题,是程序必须处理的。Exception 又分为运行时异常,受检查异常。
- RuntimeException(运行时异常),表示无法让程序恢复的异常,导致的原因通常是因为执行了错误的操作,建议终止逻辑,因此,编译器不检查这些异常。
- CheckedException(受检查异常),是表示程序可以处理的异常,也即表示程序可以修复(由程序自己接受异常并且做出处理),所以称之为受检查异常
8.3 error 和 exception 有什么区别?
-
Error:Error类对象由 Java 虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关 。比如:
- OutOfMemoryError
- NoClassDefFoundError
- LinkageError
-
Exception : 在Exception分支中有一个重要的子类RuntimeException(运行时异常),该类型的异常自动为你所编写的程序定义 异常,比如:
- ArrayIndexOutOfBoundsException(数组下标越界)
- NullPointerException(空指针异常)
- ArithmeticException(算术异常)
- MissingResourceException(丢失资源)
- ClassNotFoundException(找不到类)
- BufferOverflowException
- ClassCastException
8.4 CheckedException 和 RuntimeException 有什么区别?
- RuntimeException运行异常:表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。
- CheckedException受检异常:跟程序运行的上下文环境有关,即使程序设计无误,仍然可能因使用的问题而引发。Java编译器要求方法必须声明抛出可能发生的受检异常,但是并不要求必须声明抛出未被捕获的运行时异常
Effective Java中对异常的使用给出了以下指导原则 :
不要将异常处理用于正常的控制流(设计良好的API不应该强迫它的调用者为了正常的控制流而使用异常)
对可以恢复的情况使用受检异常,对编程错误使用运行时异常
避免不必要的使用受检异常(可以通过一些状态检测手段来避免异常的发生)
优先使用标准的异常
每个方法抛出的异常都要有文档
保持异常的原子性
不要在catch中忽略掉捕获到的异常
8.5 Throwable 类常用方法?
- getMessage() 方法:返回异常发生时的详细信息。
- getCause() 方法:获得导致当前 Throwable 异常的 Throwable 异常。
- getStackTrace() 方法:获得 Throwable 对象封装的异常信息。
- printStackTrace() 方法:在控制台上打印。
8.6 throw 与 throws 的区别 ?
- throw ,用于在程序中显式地抛出一个异常。
- throws ,用于指出在该方法中没有处理的异常。每个方法必须显式指明哪些异常没有处理,以便该方法的调用者可以预防可能发生的异常。最后,多个异常用逗号分隔。
8.7 异常处理中 finally 语句块的重要性?
不管程序是否发生了异常, finally 语句块都会被执行,甚至当没有catch 声明但抛出了一个异常时, finally 语句块也会被执行。
finally 语句块通常用于释放资源, 如 I/O 缓冲区, 数据库连接等等。
8.8 UnsupportedOperationException 是什么?
UnsupportedOperationException ,是用于表明操作不支持的异常。
在 JDK 类中已被大量运用,在集合框架java.util.Collections.UnmodifiableCollection 将会在所有 add 和 remove 操作中抛出这个异常。
9.反射
9.1 反射简介
当程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言。我们认为 Java 并不是动态语言,但是它却又一个非常突出的动态相关的机制
9.2 反射的用途及实现?
Java 反射机制主要提供了以下功能:
- 在运行时构造一个类的对象。
- 判断一个类所具有的成员变量和方法。
- 调用一个对象的方法。
- 生成动态代理。
反射的主要用途, 开发各种通用框架 :
- Spring 框架的 IoC 基于反射创建对象和设置依赖属性。
- Spring MVC 的请求调用对应方法,也是通过反射。
- JDBC 的 Class#forName(String className) 方法,也是使用反射。
9.3 反射中,Class.forName 和 ClassLoader 区别?
- Class#forName(…) 方法,除了将类的 .class 文件加载到JVM 中之外,还会对类进行解释,执行类中的 static 块。
- ClassLoader 只干一件事情,就是将 .class 文件加载到 JVM 中,不会执行 static 中的内容,只有在 newInstance 才会去执行 static 块。
- Class#forName(name, initialize, loader) 方法,带参函数也可控制是否加载 static 块,并且只有调用了newInstance 方法采用调用构造函数,创建类的对象。
9.4 什么时候用断言(assert)?
断言,在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。
一般来说,断言用于保证程序最基本、关键的正确性。断言检查通常在开发和测试时开启。为了保证程序的执行效率,在软件发布后断言检查通常是关闭的。
断言是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为true;如果表达式的值为 false ,那么系统会报告一个AssertionError 错误。断言的使用如下面的代码所示:
assert(a > 0); // throws an AssertionError if a <= 0
断言可以有两种形式:
assert Expression1; 。
assert Expression1 : Expression2;
Expression1 应该总是产生一个布尔值。
Expression2 可以是得出一个值的任意表达式;这个值用于生成显示更多调试信息的字符串消息。
要在运行时启用断言,可以在启动 JVM 时使用 -enableassertions 或者 -ea 标记。要在运行时选择禁用断言,可以在启动 JVM 时使用 -da 或者 -disableassertions 标记。要在系统类中启用或禁用断言,可使用 -esa 或 -dsa 标记。还可以在包的基础上启用或者禁用断言。
也欢迎关注微信公众号【Ccww笔记】,原创技术文章第一时间推出
如果此文对你有帮助、喜欢的话,那就点个赞呗,点个关注呗!
![](https://oss.jqhtml.com/wp-content/uploads/2020/7/3uqyIn.png)
原文地址:https://segmentfault.com/a/1190000021113522