- JavaGuide(Java学习&面试指南)
- Java后端真实面试题大全(有详细答案)–高频/真题
JDK
:包含 JRE,提供了 Java 的 程序开发工具包,用于 编写、编译 和 运行 Java 代码。JRE
:提供了 Java 的 运行环境,主要包括 Java 虚拟机、Java 基础类库。
Java
没有指针,C++
有指针。Java
自动回收 垃圾内存,C++
手动 释放内存。Java
只能 单继承,C++
允许 多继承。
- 面向对象
- 平台无关性
- 自动垃圾回收
JDK各版本新特性
Lambda表达式
方法引用
Stream流
点击查看
基础类型有 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 包装类 利用 缓存机制 来 提升性能。
- Byte、Short、Integer、Long 默认创建了 [-128,127] 内数据的相应 包装类型 的
缓存数据
。
java的大数字运算
BigInteger
: 整型大数字 运算。BigDecimal
: 浮点数大数字 运算。- 原理:BigInteger 内部使用 int[] 数组来存储任意大小的 整型数据。
BigDecimal为何能解决浮点数运算时精度丢失问题
- 使用
Bigdecimal
进行 浮点型数据 运算。- 原理:将 浮点型数据 扩大 N倍 让它转换成
BigInteger
类型 数据,并记录 小数点位置 即可。
计算机在表示一个数字时,宽度 是有限的,例如 无限循环的小数 在计算机中存储时,只能被 截断,所以就会导致 小数精度损失。
String 内部是一个 char数组,并且被
private
、final
修饰,因此 创建后不可改变,不可以继承。
点击查看
String
创建的是不可变的对象,每次操作都会生成新的 String 对象。StringBuffer
、StringBuilder
可以在原有对象的基础上进行操作。String
、StringBuffer
是线程安全的,而StringBuilder
是非线程安全的。StringBuilder
的性能高于StringBuffer
。
对于三者使用的总结:
String
:操作 少量数据。StringBuilder
:单线程 操作 大量数据。StringBuffer
:多线程 操作 大量数据。
- 基本 没有区别。通过
+
拼接字符串,实际上也是转换为StringBuilder
实现的,拼接完成后再转换为 String 对象 。- 但是在 循环体 使用
+
拼接字符串,会创建很多个StringBuilder
对象,应该避免。
字符串常量池
是 JVM 针对 字符串 (String类) 专门开辟的一块区域,可以避免 重复创建字符串,从而 提升性能 和 减少内存消耗。
为什么说Java中只有值传递?
Java 中是 值传递 的,只不过当参数是 对象 时,值的内容 是 对象的地址。
点击查看
- final修饰
变量
: 该 变量 在 初始化 后 不可更改。(常量)- final修饰
方法
: 该 类 的 继承类 不能 修改 此方法。- final修饰
类
: 该类 不可被继承。该类所有 成员方法 都将被 隐式修饰 为 final 方法。
点击查看
static
修饰的东西被我们称为 类成员,它会随着 类的加载 而 加载,比如:静态变量、静态方法、静态代码块。
- static修饰
变量
: 即 静态变量,属于 类的变量,不依赖于任何对象就可以进行访问。仅当 类初次加载时 会被 初始化。- static修饰
方法
: 即 静态方法,属于 类的方法,不依赖于任何对象就可以进行访问。- static修饰
代码块
: 即 静态代码块, 可以 优化程序性能。类初次加载时,会执行每个 static静态代码块。静态导包
:采用 static 导入包后,可以直接使用 方法名 调用类的方法,就好像是该类自己的方法一样使用即可。
super()和this() 的区别?
- 相同之处:
super()
和this()
都必须在构造方法的第一行,且不能同时出现。- 查找范围不同:
super()
只能查找 父类,而this()
会先查找 本类,如果找不到则会去 父类 中查找。
静态方法与非静态方法的相互调用
- 非静态方法
可以调用
静态方法。- 静态方法
不可以调用
非静态方法。
- 静态方法 是属于 类 的,在 类加载 的时候就会 分配内存。
- 非静态方法 是属于 对象 的,只有在 对象实例化 之后才存在。
Java三大特性:封装,继承与多态
面向对象编程 三大特性 –
封装
、继承
、多态
:
封装
:万物皆对象,任何事物都可以在 代码 中用 类 表示,将事物拥有的 属性 和 行为 隐藏起来。继承
:从 父类 中派生出 子类,子类 能吸收 父类 的所有 属性 和 行为,并能扩展 新的能力。多态
:通过将 子类的对象 作为 父类的对象 实现 多态。
面向过程
:分析出 解决问题 所需要的 方法,然后一步一步 执行这些 方法。面向对象
:会先 抽象 出 对象,然后用 对象执行方法 的方式解决问题。
重载
发生在本类,重写
发生在 父类与子类 之间;重载
是 方法名 相同,参数列表 必须不同,返回值 可以不同。重写
是 方法名 相同, 参数列表 必须相同。
抽象类
不能直接 实例化,只能被 继承。抽象类
可以包含 抽象方法 (不需要实现方法体),且 继承者 必须实现 抽象方法。
抽象类和接口有什么区别
接口中成员 (变量&方法) 的特点
- 子类 通过
implements
实现 接口,子类 通过extends
继承 抽象类。- 一个类可以实现多个 接口,但只能继承一个 抽象类。
- 接口 中的方法都是 抽象的,抽象类 中可以 具体实现方法(非抽象)。
- 抽象类的方法 可以用 public、protected 修饰 。接口方法 只能用 public 修饰符。
注:接口 只能 继承接口,而不是 实现接口 。
克隆
会创建一个 副本对象,对 副本对象 进行操作不会影响 原来的对象。clone()
默认是浅拷贝
。
点击查看
equals()
:用于比较 对象 的 内存地址 是否相等。String类 中用于比较 字符串的值 是否相等。hashCode()
:用于返回对象的 哈希码。wait()
:暂停 线程 的执行。notify()
:唤醒一个在此对象监视器上等待的 线程。clone()
:用于 拷贝对象。
点击查看
==
对于 基本类型 来说是 值比较,对于 对象 来说是比较的是 内存地址;equals()
比较的是 内存地址,但是对于 String类 来说是 值比较。
【equals() 内部就是 ==,但是 String类 重写了 equals() 方法】
hashCode()
:获取 对象 的 哈希码。hashCode()
可以通过 哈希吗 来 初步判断 对象 是否相等。
【需要通过equal()
进一步判断 是否真的相等】
序列化
:将 Java对象 转换成 字节流 的过程。反序列化
:将 字节流 转换成 Java对象 的过程。
当 Java对象 需要 在网络上传输 或者 持久化存储 时,就需要对 Java对象 进行序列化 处理。
静态变量
:static
关键字 修饰的 变量,不属于任何 对象,不会被 序列化。瞬态变量
:transient
关键字 修饰的 变量,不会被 序列化。
Java 中 Set(集合), List(列表) 与 Map(映射) 的区别: 查看详情
点击查看
Collection
是 集合 的顶层 接口(interface)。Collections
类是 Java 中集合的 工具类, 包括sort()
、reverse()
等方法。
点击查看
- 数组 容量是 固定 的; ArrayList 容量大小可 自动调整。
- 数组 可存储 基本类型 和 对象; ArrayList 只能存储 对象。
- ArrayList 可以使用 泛型 来确保 类型安全,Array 则不可以。
ArrrayList
基于 数组, 支持 随机访问。LinkedList
基于 双向循环链表,不支持 随机访问。- ArrayList 的 查询效率 很高;LinkedList 的 插入、删除效率 很高。
项目中一般不会使用 LinkedList,一般都可以使用 ArrayList 代替 LinkedList,并且通常性能会更好!
Java–ArrayList扩容的原理
类 | 初始大小 | 加载因子 | 扩容倍数 |
---|---|---|---|
ArrayList |
10 | 1 | 1.5倍 |
Vector |
10 | 1 | 2倍 |
线程安全
:Vector
是 线程安全 的,ArrayList
不是 线程安全 的。性能
:Vector
因为方法都是 同步 的,所以 效率不高。
Java高并发综合
- 通过
synchronized
关键字修饰每个方法。- 缺点:因为方法都是 同步 的,所以 效率不高。
HashMap
是一种基于 Map接口 实现的 哈希表 数据结构,主要用来存放 key-value 键值对。
Java–HashMap保证线程安全的方法
- 线程安全:
HashMap
是 线程不安全的。Hashtable
是 线程安全 的。- 使用效率:HashMap 要比 Hashtable 效率高一点。
【Hashtable 基本被 淘汰 了,不要在代码中使用 HashTable】
Java–HashMap保证线程安全的方法
Hashtable
使用 synchronized 修饰 get/put方法,性能很差。ConcurrentHashMap
每次只给一个 桶 (数组项) 加锁,性能很好。
HashMap,TreeMap,LinkedHashMap的区别
HashMap
:底层是 数组 + 链表 + 红黑树,基本可以达到 常数时间 的性能。TreeMap
:底层是 红黑树,默认按 key 升序排序,也可以根据 key 进行 搜索。
HashMap原理技术知识整理
- HashMap 底层存储结构 初始 为 链表散列: (数组 + 链表) 的结构 (链地址法 解决 哈希冲突)。
- 链表长度大于8 且 数组长度大于等于64, 该链表会转为
红黑树
来 提高查询效率。
目的尽量取得 提高空间利用率 和 减少查询成本 的平衡。
加载因子
: 决定了 HashMap 的 扩容阀值,如果 数组长度 是16
,那么扩容阈值为16 * 0.75=12
,也就存储12
个 元素 的时候 扩容。设为0.75的依据
: 用 0.75 作为 加载因子,每个碰撞位置的 链表长度 超过 8 个概率非常低,小于 千万分之一。
注:HasmMap 扩容时, 会重新进行重新 Hash分配,并且会遍历 Hash表 中所有的元素,是非常耗时的。在编写程序中,要尽量避免 resize.
Java–HashMap扩容的原理
HashMap 扩容的条件:
- 初次使用 时容量为 0,会进行 扩容,设置初始容量为 16。
- 触发 加载因子,会进行 扩容。
- 链表长度大于8,且 数组长度<64,会进行 扩容。
注:扩容 都是针对 数组长度 而言的,而不是 HashMap容量。
扩容的原理: 判断 旧数组容量 是否已经达到最大 (2^30)了
- 若达到最大值,则修改 数组大小 为 Integer的最大值 (2^31-1),以后就不会再进行扩容。
- 若没达到,则修改 数组大小 为原来的 2倍。
注
:数组是无法自动扩容的,所以HashMap 扩容的本质是创建一个 新的数组 装填元素。
Java–HashMap扩容的原理
当 链表深度 过大时(长度>8), 查询效率变低,需要
扩容
或 转换为红黑树
:
- 链表长度大于8,且 数组长度<64 会进行
扩容
。- 链表长度大于8,且 数组长度>=于64,会转化为
红黑树
,来 提高查询效率。
红黑树(RB-tree)比AVL树的优势在哪?
- 红黑树 是一种特殊的 平衡二叉树。 用 非严格的平衡 在 增删节点 时候的
降低旋转次数
,任何 不平衡 都会在 三次旋转 之内解决。- 红黑树 中不仅有 自旋 操作,还有 变色 操作,因此能够降低
旋转次数
。
Java–IO–字节流与字符流的区别
两种:字节流 和 字符流。
字节流
按 8 位传输以 字节 为单位输入输出数据。
【适用场景:图片
、音频
。Java 相关类:InputStream
、OutputStream
】字符流
按 16 位传输以 字符 为单位输入输出数据。
【适用场景:文本文件
。Java 相关类:Reader
、Writer
】
其中:
InputStream / Reader
: 所有的 输入流 的基类。OutputStream / Writer
: 所有 输出流 的基类。
缓冲流
:将数据加载至 缓冲区,一次性 读取/写入 多个字节,可以避免 频繁的 IO 操作,提高流的 传输效率。字节缓冲输入流
:采用 装饰器模式 来增强 InputStream 对象的功能,即BufferedInputStream
。BufferedInputStream
内部维护了一个 缓冲区,这个 缓冲区 实际就是一个 字节数组。
Java–BIO、NIO、AIO–区别/使用/实例
BIO
:同步阻塞 IO: 并发处理能力低。NIO
:同步非阻塞 IO: 提供 非阻塞、面向缓冲、基于通道 (Channel ) 的 I/O,实现了 IO多路复用。
【适用于 数目多、时间短 的 IO操作,例如:聊天服务器】AIO
:异步非阻塞 IO。
注:当 连接数较少、并发程度较低 时,
NIO
的性能并不一定优于BIO
。
Java NIO 核心知识总结
NIO
提供 非阻塞、面向缓冲、基于通道 (Channel ) 的 I/O,使用 少量线程 处理 多个连接,大大提高了 并发能力。NIO 的 核心组件:
常用的设计模式汇总,超详细!
- 单例模式: 一个应用程序中,某个类的 实例对象 只能有一个。
- 装饰器模式: 对已有的业务逻辑 进一步的封装,使其增加额外的功能。【依赖
继承
实现, 不断加料】- 适配器模式: 把一个类的接口变换成客户端所期待的另一种接口。(创建一个
中间类
来解决无法调用的问题)- 观察者模式: 目标对象 发生变化时,会通知 观察者,观察者 从而作出相应 任务。
单例模式Java实战–6种写法
饿汉式
:
- 支持多线程
- 不支持懒加载
- 性能很高
静态内部类
(推荐):
- 支持多线程: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动态代理");
}
}
Java 中的泛型(两万字超全详解)
泛型
:即 “参数化类型”,即 数据类型 被设置为 一个参数,数据类型 从外部传入。- 泛型 可以用在 类、接口 和 方法 中,分别被称为 泛型类、泛型接口、泛型方法。
- 泛型 又提供了一种类型 安全检测机制,传入参数 要和 数据类型 匹配,否则 编译器 会报错。
- 泛型 提高了程序代码的可读性,根据代码能够快速知道所要操作的 数据类型。
常见的异常类有哪些?
NullPointerException
:空指针 异常。IndexOutOfBoundsException
:数组 下角标越界 异常。IOException
:当发生某种 IO异常 时抛出。SQLException
:数据库 相关的异常。
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 等。
【处理 或者 不处理 都可以】
- 无论是否 捕获异常,finally 块里的语句都会被 执行。
- 当在 try 或 catch 中遇到 return 语句时,finally代码块 都将在 方法返回之前 被执行。
Java反射 (通俗易懂)
Java 的 反射机制 可以获取 任意对象 的
属性
和方法
,可以调用这些 属性 和 方法。
创建实例对象:Person p = new Person(),获取方式:
Class clazz = p.getClass();
Class clazz = Person.class;
Class clazz = Class.forName("类的全路径");
【最安全,性能最好】
- Spring Boot、MyBatis 等框架中都 大量使用 了
反射机制
。- 注解 (@) 的实现也用到了
反射机制
。
Annotation
(注解) 可以看作是一种 特殊的注释,主要用于修饰 类、方法 或 变量,提供 特定信息 供程序在 编译 或 运行 时使用。
注解 只有 被解析后 才会 生效,常见的 解析方法 有两种:
编译时直接扫描
:编译器在 编译代码 的时候扫描对应的 注解 并处理,例如:@Override。运行期通过反射处理
:框架中的注解 都是通过 反射 来处理的,例如:@Controller、@Service。