本文目录如下:
一、Java 基础知识
1、Java基础
JDK 和 JRE 有什么区别?
JDK
:包含 JRE,提供了 Java 的 程序开发工具包,用于 编写、编译 和 运行 Java 代码。JRE
:提供了 Java 的 运行环境,主要包括 Java 虚拟机、Java 基础类库。
Java8 相比于之前版本有哪些新的特性?
Lambda表达式
方法引用
Stream流
2、数据类型
Java中的 基础类型 有哪些?
基础类型有 8 种:
- 整数类型:
byte
(8位),short
(16位),int
(32位),long
(64位)- 浮点类型:
float
(32位),double
(64位)- 逻辑类型:
boolean
(8位)- 文本类型:
char
(16位)
- 一个字节(byte) => 8个二进制位 (bit–比特)
String
不是 基本数据类型,而是一个类(class)
。
自动装箱与拆箱的作用?原理是什么?
- 作用:自动完成 基本数据类型 与 包装类 的转换。
- 原理:以 Integer 为例,装箱 的时候会自动调用
Integer.valueOf()
方法。
为什么 基本类型 需要 包装类?
基本类型
不能在 泛型 、集合 中使用。基本类型
不符合 面向对象思维。
Java 中如何进行 整型大数字 运算?
BigInteger
: 整型大数字 运算。BigDecimal
: 浮点数大数字 运算。- 原理:BigInteger 内部使用 int[] 数组来存储任意大小的 整型数据。
针对浮点型数据运算出现的误差的问题,怎么解决 (BigDecimal)?
- 使用
BigDecimal
进行 浮点型数据 运算。- 原理:将 浮点型数据 扩大 N倍 让它转换成
BigInteger
类型 数据,并记录 小数点位置 即可。
为什么浮点数运算的时候会 丢失精度?
计算机在表示一个数字时,宽度 是有限的,例如 无限循环的小数 在计算机中存储时,只能被 截断,所以就会导致 小数精度损失。
3、String【重要】
String 为什么是不可变的?
String 内部是一个 char数组,并且被
private
、final
修饰,因此 创建后不可改变,不可以继承。
String、StringBuilder、StringBuffer 的区别?
String
创建的是不可变的对象,每次 操作 都会生成新的 String 对象。StringBuffer
、StringBuilder
可以在原有对象的基础上进行操作。String
、StringBuffer
是 线程安全 的,而StringBuilder
是 非线程安全 的。StringBuilder
的性能高于StringBuffer
。
String、StringBuilder、StringBuffer 使用场景?
对于三者使用的总结:
String
:操作 少量数据。StringBuilder
:单线程 操作 大量数据。StringBuffer
:多线程 操作 大量数据。
字符串常量池 的作用了解吗?
字符串常量池
是 JVM 针对 字符串 (String类) 专门开辟的一块区域,可以避免 重复创建字符串,从而 提升性能 和 减少内存消耗。
4、方法 & 关键字
Java中到底是 值传递 还是 引用传递?
Java 中是 值传递 的,只不过当参数是 对象 时,值的内容 是 对象的地址。
final 关键字有什么作用?
- final修饰
变量
: 该 变量 在 初始化 后 不可更改。(常量)- final修饰
方法
: 该 类 的 继承类 不能 修改 此方法。- final修饰
类
: 该类 不可被继承。该类所有 成员方法 都将被 隐式修饰 为 final 方法。
static 关键字有什么作用?
static
修饰的东西被我们称为 类成员,它会随着 类的加载 而 加载,比如:静态变量、静态方法、静态代码块。
- static修饰
变量
: 即 静态变量,属于 类的变量,不依赖于任何对象就可以进行访问。仅当 类初次加载时 会被 初始化。- static修饰
方法
: 即 静态方法,属于 类的方法,不依赖于任何对象就可以进行访问。- static修饰
代码块
: 即 静态代码块, 可以 优化程序性能。类初次加载时,会执行每个 static静态代码块。静态导包
:采用 static 导入包后,可以直接使用 方法名 调用类的方法,就好像是该类自己的方法一样使用即可。
super() 和 this() 的区别?
- 相同之处:
super()
和this()
都必须在构造方法的第一行,且不能同时出现。- 查找范围不同:
super()
只能查找 父类,而this()
会先查找 本类,如果找不到则会去 父类 中查找。
静态方法 和 非静态方法 之间的可调用关系是怎样的?
- 非静态方法
可以调用
静态方法。- 静态方法
不可以调用
非静态方法。
- 静态方法 是属于 类 的,在 类加载 的时候就会 分配内存。
- 非静态方法 是属于 对象 的,只有在 对象实例化 之后才存在。
二、面向对象
1、面向对象基础
如何理解面向对象编程?
面向对象编程 三大特性 –
封装
、继承
、多态
:
封装
:万物皆对象,任何事物都可以在 代码 中用 类 表示,将事物拥有的 属性 和 行为 隐藏起来。继承
:从 父类 中派生出 子类,子类 能吸收 父类 的所有 属性 和 行为,并能扩展 新的能力。多态
:通过将 子类的对象 作为 父类的对象 实现 多态。
面向对象 和 面向过程 的区别?
面向过程
:分析出 解决问题 所需要的 方法,然后一步一步 执行这些 方法。面向对象
:会先 抽象 出 对象,然后用 对象执行方法 的方式解决问题。
重载 和 重写 有什么区别?
重载
发生在本类,重写
发生在 父类与子类 之间;重载
是 方法名 相同,参数列表 必须不同,返回值 可以不同。重写
是 方法名 相同, 参数列表 必须相同。
抽象类 和 普通类 有哪些区别?
抽象类
不能直接 实例化,只能被 继承。抽象类
可以包含 抽象方法 (不需要实现方法体),且 继承者 必须实现 抽象方法。
接口(interface) 和 抽象类 有什么区别?
- 子类 通过
implements
实现 接口,子类 通过extends
继承 抽象类。- 一个类可以实现多个 接口,但只能继承一个 抽象类。
- 接口 中的方法都是 抽象的,抽象类 中可以 具体实现方法(非抽象)。
- 抽象类的方法 可以用 public、protected 修饰 。接口方法 只能用 public 修饰符。
注:接口 只能 继承接口,而不是 实现接口 。
为什么要使用克隆?
克隆
会创建一个 副本对象,对 副本对象 进行操作不会影响 原来的对象。clone()
默认是浅拷贝
。
深拷贝 和 浅拷贝 区别了解吗?什么是 引用拷贝?
浅拷贝
:浅拷贝 会在堆上创建一个 新的对象,但是 浅拷贝 会共用 内部对象。深拷贝
:深拷贝会 完全复制整个对象,包括 内部对象。
2、Object
Object 类的常见方法有哪些?
equals()
:用于比较 对象 的 内存地址 是否相等。String类 中用于比较 字符串的值 是否相等。hashCode()
:用于返回对象的 哈希码。wait()
:暂停 线程 的执行。notify()
:唤醒一个在此对象监视器上等待的 线程。clone()
:用于 拷贝对象。
== 和 equals() 的区别是什么?
==
对于 基本类型 来说是 值比较,对于 对象 来说是比较的是 内存地址;equals()
比较的是 内存地址,但是对于 String类 来说是 值比较。
【equals() 内部就是 ==,但是 String类 重写了 equals() 方法】
hashCode() 有什么用?
hashCode()
:获取 对象 的 哈希码。hashCode()
可以通过 哈希吗 来 初步判断 对象 是否相等。
【需要通过equal()
进一步判断 是否真的相等】
3、序列化
什么是 序列化?什么情况下需要序列化?
序列化
:将 Java对象 转换成 字节流 的过程。反序列化
:将 字节流 转换成 Java对象 的过程。
当 Java对象 需要 在网络上传输 或者 持久化存储 时,就需要对 Java对象 进行序列化 处理。
哪些 变量 不会被 序列化?
静态变量
:static
关键字 修饰的 变量,不属于任何 对象,不会被 序列化。瞬态变量
:transient
关键字 修饰的 变量,不会被 序列化。
三、容器
1、容器基础
Java容器 都有哪些?
Java 中 Set(集合), List(列表) 与 Map(映射) 的区别: 查看详情
List集合
: 有序、可重复 的集合;Set集合
: 无序、不可重复 的集合;Map集合
: 键值对集合。Key 不可重复,Value 可重复;
Collection 和 Collections 有什么区别?
Collection
是 集合 的顶层 接口(interface)。Collections
类是 Java 中集合的 工具类, 包括sort()
、reverse()
等方法。
2、List【重要】
数组(Array) 和 ArrayList 有何区别?
- 数组 容量是 固定 的; ArrayList 容量大小可 自动调整。
- 数组 可存储 基本类型 和 对象; ArrayList 只能存储 对象。
- ArrayList 可以使用 泛型 来确保 类型安全,Array 则不可以。
ArrayList 和 LinkedList 的区别是什么?
ArrrayList
基于 数组, 支持 随机访问。LinkedList
基于 双向循环链表,不支持 随机访问。- ArrayList 的 查询效率 很高;LinkedList 的 插入、删除效率 很高。
项目中一般不会使用 LinkedList,一般都可以使用 ArrayList 代替 LinkedList,并且通常性能会更好!
谈一下 ArrayList扩容机制 的扩容机制?
类 | 初始大小 | 加载因子 | 扩容倍数 |
---|---|---|---|
ArrayList | 10 | 1 | 1.5倍 |
Vector | 10 | 1 | 2倍 |
ArrayList 和 Vector 的主要区别是什么?
线程安全
:Vector
是 线程安全 的,ArrayList
不是 线程安全 的。性能
:Vector
因为方法都是 同步 的,所以 效率不高。
Vector、HashTable、synchronizedMap、StringBuffer 如何实现线程安全的?
- 通过
synchronized
关键字修饰每个方法。- 缺点:因为方法都是 同步 的,所以 效率不高。
3、Map【面试专用,日常不重要】
简介一下 HashMap?
HashMap
是一种基于 Map接口 实现的 哈希表 数据结构,主要用来存放 key-value 键值对。
HashTable 和 ConcurrentHashMap 有什么区别?
Hashtable
和ConcurrentHashMap
都是线程安全的。Hashtable
使用 synchronized 修饰 get/put方法,性能很差。ConcurrentHashMap
每次只给一个 桶 (数组项) 加锁,性能很好。
HashMap 和 TreeMap 有什么区别?
HashMap,TreeMap,LinkedHashMap的区别
HashMap
:底层是 数组 + 链表 + 红黑树,基本可以达到 常数时间 的性能。TreeMap
:提供了 有序的元素,底层是 红黑树,默认按 key 升序排序,也可以根据 key 进行 搜索。
HashMap原理 - 说一下 HashMap 的数据结构?
- HashMap 底层存储结构 初始 为 链表散列: (数组 + 链表) 的结构 (链地址法 解决 哈希冲突)。
- 链表长度大于8 且 数组长度大于等于64, 该链表会转为
红黑树
来 提高查询效率。
HashMap原理 - 为什么加载因子为 0.75?
目的尽量取得 提高空间利用率 和 减少查询成本 的平衡。
加载因子
: 决定了 HashMap 的 扩容阀值,如果 数组长度 是16
,那么扩容阈值为16 * 0.75=12
,也就存储12
个 元素 的时候 扩容。设为0.75的依据
: 用 0.75 作为 加载因子,每个碰撞位置的 链表长度 超过 8 个概率非常低,小于 千万分之一。
注:HasmMap 扩容时, 会重新进行重新 Hash分配,并且会遍历 Hash表 中所有的元素,是非常耗时的。在编写程序中,要尽量避免 resize.
HashMap原理 - HashMap 何时扩容?
HashMap 扩容的条件:
- 初次使用 时容量为 0,会进行 扩容,设置初始容量为 16。
- 触发 加载因子,会进行 扩容。
- 链表长度大于8,且 数组长度<64,会进行 扩容。
注:扩容 都是针对 数组长度 而言的,而不是 HashMap容量。
HashMap原理 - 谈一下 HashMap 扩容的原理?
扩容的原理: 判断 旧数组容量 是否已经达到最大 (2^30)了
- 若达到最大值,则修改 数组大小 为 Integer的最大值 (2^31-1),以后就不会再进行扩容。
- 若没达到,则修改 数组大小 为原来的 2倍。
注
:数组是无法自动扩容的,所以HashMap 扩容的本质是创建一个 新的数组 装填元素。
HashMap原理 - 链表中结点数大于8时,会发生什么事 | 扩容 和 转换为红黑树?
当 链表深度 过大时(长度>8), 查询效率变低,需要
扩容
或 转换为红黑树
:
- 链表长度大于8,且 数组长度<64 会进行
扩容
。- 链表长度大于8,且 数组长度>=于64,会转化为
红黑树
,来 提高查询效率。
HashMap原理 - 红黑树 的原理了解吗?
- 红黑树 是一种特殊的 平衡二叉树。 用 非严格的平衡 在 增删节点 时候的
降低旋转次数
,任何 不平衡 都会在 三次旋转 之内解决。- 红黑树 中不仅有 自旋 操作,还有 变色 操作,因此能够降低
旋转次数
。
四、泛型 & 异常 & 反射 & 注解
1、泛型
什么是泛型?
泛型
:参数化类型,即 数据类型 被设置为 一个参数,数据类型 从外部传入。- 泛型 可以用在 类、接口 和 方法 中,分别被称为 泛型类、泛型接口、泛型方法。
为什么要使用泛型?
泛型
提供了一种类型 安全检测机制,传入参数 要和 数据类型 匹配,否则 编译器 会报错。
2、异常
常见的异常类有哪些?
NullPointerException
:空指针 异常。IndexOutOfBoundsException
:数组 下角标越界 异常。IOException
:当发生某种 IO异常 时抛出。SQLException
:数据库 相关的异常。
Java的异常匹配机制?
Java异常匹配机制遵循以下规则:
最具体匹配
:JVM会寻找与抛出的异常类型 最具体的catch块。如果找到了一个完全匹配的catch块,则执行该块中的代码。如果没有找到完全匹配的catch块,JVM会继续搜索 该异常类型的父类。单一匹配
:一旦找到了匹配的catch块,就会执行该块中的代码,并且不会执行后续的catch块,即使它们也可能匹配。finally块
:finally块是可选的,但它提供了无论是否发生异常都会执行的代码块。这通常用于执行清理操作,如关闭文件、释放资源等。
Exception 和 Error 有什么区别?
Throwable 有两个直接的子类:
java.lang.Error
、java.lang.Exception
。
Error
:JVM内部 的严重问题,比如 资源不足 等,无法恢复。Exception
:通过处理 可以恢复。Exception
可分 Checked Exception 和 Unchecked Exception。
Checked Exception
:IOException、SQLException 等。
【这种 异常 必须在程序中捕获 (catch)
或抛出 (throws)
】
Unchecked Exception
:NullPointerException、IndexOutOfBoundsException 等。
【处理 或者 不处理 都可以】
catch 里 return 了,finally 是否执行?
- 无论是否 捕获异常,finally 块里的语句都会被 执行。
- 当在 try 或 catch 中遇到 return 语句时,finally代码块 都将在 方法返回之前 被执行。
3、反射
谈谈 Java 中的 反射?
Java 的 反射机制 可以获取 任意对象 的
属性
和方法
,可以调用这些 属性 和 方法。
获取 Class类 的三种方式?
创建实例对象:Person p = new Person(),获取方式:
Class clazz = p.getClass();
Class clazz = Person.class;
Class clazz = Class.forName("类的全路径");
【最安全,性能最好】
使用反射机制的场景有哪些?
- Spring Boot、MyBatis 等框架中都 大量使用 了
反射机制
。- 注解 (@) 的实现也用到了
反射机制
。
4、注解
什么是注解?
Annotation
(注解) 可以看作是一种 特殊的注释,主要用于修饰 类、方法 或 变量,提供 特定信息 供程序在 编译 或 运行 时使用。
注解的解析方法有哪几种?
注解 只有 被解析后 才会 生效,常见的 解析方法 有两种:
编译时直接扫描
:编译器在 编译代码 的时候扫描对应的 注解 并处理,例如:@Override。运行期通过反射处理
:框架中的注解 都是通过 反射 来处理的,例如:@Controller、@Service。
五、IO & 设计模式
1、IO
Java 中 IO 流分为几种? | 字节流 与 字符流的区别?
两种:字节流 和 字符流。
字节流
按 8 位传输以 字节 为单位输入输出数据。
【适用场景:图片
、音频
。Java 相关类:InputStream
、OutputStream
】字符流
按 16 位传输以 字符 为单位输入输出数据。
【适用场景:文本文件
。Java 相关类:Reader
、Writer
】
其中:
InputStream / Reader
: 所有的 输入流 的基类。OutputStream / Writer
: 所有 输出流 的基类。
什么是 字节缓冲流?
缓冲流
:将数据加载至 缓冲区,一次性 读取/写入 多个字节,可以避免 频繁的 IO 操作。字节缓冲输入流
:采用 装饰器模式 来增强 InputStream 对象的功能,即BufferedInputStream
。
BIO、NIO、AIO 有什么区别?
BIO
:同步阻塞 IO: 并发处理能力低。NIO
:同步非阻塞 IO: 提供 非阻塞、面向缓冲、基于通道 (Channel ) 的 I/O,实现了 IO多路复用。
【适用于 数目多、时间短 的 IO操作,例如:聊天服务器】AIO
:异步非阻塞 IO。
注:当 连接数较少、并发程度较低 时,
NIO
的性能并不一定优于BIO
。
简单介绍一下 NIO?
NIO
提供 非阻塞、面向缓冲
和通道
(Channel ) 的 I/O。NIO
使用少量线程
处理多个 Channel 连接
,大大提高了 并发能力。
NIO 的 核心组件:
Buffer
(缓冲区):NIO 读写数据 都是通过 缓冲区 进行操作的。读操作 将 Channel 中的 数据 填充到 Buffer 中,写操作 相反。类似于 BIO 中的 缓冲流。Channel
(通道):Channel 是一个 双向 的 数据传输通道,数据传输 过程由 Selector 进行控制。Selector
(选择器):允许 一个线程 处理 多个 Channel,Selector 是基于 事件驱动 的 I/O 多路复用模型。
2、设计模式【重要】
说几个常用的设计模式?
单例模式的写法?
饿汉式
:
- 支持多线程
- 不支持懒加载
- 性能很高
静态内部类
(推荐):
- 支持多线程:Classloder机制 保证 实例初始化 时只有 一个线程。
- 支持懒加载
- 性能很高
public class Singleton {
// 内部类也是私有的
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
// 构造方法一定是 私有的
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
简单说说 静态代理 和 动态代理 (简单说说 代理模式)?
Java代理模式–静态代理与动态代理的使用 【依赖 接口
实现】
代理模式
的优点:
- 可以隐藏 目标服务 的 具体实现。
- 可以在不修改 目标服务代码 的情况下能够做一些 额外的增强。
静态代理
: 代理类 在程序运行前就已经存在。【静态代理案例】
- 缺点: 需要为每个 目标服务 写一个 代理类,代码复杂。
动态代理
: 代理类 在程序运行时 动态创建。
反射的应用:动态代理?
JDK动态代理,步骤如下:
- 自定义
InvocationHandler
重写invoke()
方法,invoke() 方法 里调用 目标服务,并 附加一些逻辑。
// 如果不附加逻辑的话,这段代码甚至可以不用任何更改
public class MyInvocationHandler implements InvocationHandler {
private final Object target;
public MyInvocationHandler (Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("正在执行的方法是:" + method.getName());
Object result = method.invoke(target, args);
return result;
}
}
- 通过
Proxy.newProxyInstance()
方法 创建代理对象。
【传入什么 对象,生成的就是什么对象的 代理类】
public class JdkProxyFactory {
// 用于获取代理类的方法
// 传入什么对象,生成的就是什么对象的 代理类
public static Object getProxy(Object target) {
// 参数2: interfaces: 代理类需要实现哪些接口
// 参数3: InvocationHandler: 用于实现 代理类 中的 方法调用。
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new MessageInvocationHandler(target));
}
// 测试
public static void main(String[] args) {
// 生成代理类
SmsService smsS ervice = (SmsService)JdkProxyFactory.getProxy(new SmsServiceImpl());
// 调用代理类方法
smsService.send("jdk动态代理");
}
}