1、你是怎样理解面向对象?
(1) 四大核心特性
-
封装(Encapsulation)
将数据(属性)和操作数据的方法(行为)绑定在一起,隐藏内部实现细节,仅暴露必要的接口。
目的:提高安全性(如私有属性)、降低复杂度(使用者无需关心内部逻辑)。 -
继承(Inheritance)
子类可以继承父类的属性和方法,并扩展或重写功能。
目的:实现代码复用,建立类之间的层次关系(如Dog extends Animal)。 -
多态(Polymorphism)
同一操作作用于不同对象时,可能产生不同的行为。通常通过方法重写(Override)或接口实现来实现。
例子:Animal类的speak()方法,Dog返回“Woof!”,Cat返回“Meow!”。 -
抽象(Abstraction)
提取共性特征形成抽象类或接口,忽略非关键细节。
例子:Shape抽象类定义calculateArea()方法,由子类Circle、Square具体实现。
(2) 核心思想
- 对象为中心:程序由对象交互构成,每个对象是数据和行为的封装体。
- 模拟现实世界:将问题域中的实体映射为代码中的类(Class)和对象(Object)。
- 高内聚低耦合:类内部高度自治,类之间依赖最小化,提升可维护性。
(3) 通俗理解
想象你造一辆车:
- 类是设计图纸(定义车的属性和功能);
- 对象是按图纸造出的具体车辆;
- 继承是基于基础车型设计豪华版;
- 多态是踩油门时,电动车和燃油车响应方式不同;
- 封装是隐藏发动机细节,只给你方向盘和油门接口。
面向对象的本质是**用代码模拟现实世界的协作关系**,让复杂系统更易于理解和构建。
2、重载与重写有什么区别?
重载(Overload)和重写(Override)是Java中两个重要的概念,主要区别如下:
重载(Overload)
- 发生范围:同一个类中
- 方法签名:方法名相同,但参数列表不同(参数类型、个数或顺序)
- 返回值:无关,可以不同
- 访问修饰符:无关,可以不同
- 用途:提供功能相似但参数不同的方法变体
重写(Override)
- 发生范围:子类与父类之间
- 方法签名:方法名、参数列表、返回值类型必须与父类完全相同(Java 5+支持协变返回类型)
- 访问修饰符:子类方法的访问权限不能小于父类(如父类是protected,子类只能是protected或public)
- 异常:子类方法抛出的异常范围不能大于父类
- 用途:子类修改或扩展父类的方法实现
示例
// 重载示例(同一个类中)
class Calculator {
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; } // 参数类型不同
}
// 重写示例(子类与父类之间)
class Animal {
void makeSound() { System.out.println("Animal sound"); }
}
class Dog extends Animal {
@Override
void makeSound() { System.out.println("Bark"); } // 方法实现不同
}
3、说说对深拷贝与浅拷贝的理解?
浅拷贝(Shallow Copy)和深拷贝(Deep Copy)是对象复制的两种方式,区别在于是否递归复制对象的所有层级:
(1)浅拷贝:
- 复制对象本身,但仅复制引用类型的内存地址(不复制对象内容)
- 原始对象和副本共享引用类型的内部对象
- 修改引用类型成员会影响所有关联对象
- 常见实现方式:Object.clone()(默认是浅拷贝)
(2)深拷贝:
- 递归复制对象及其所有嵌套的引用类型对象
- 原始对象和副本完全独立,无共享内存
- 修改任何一方不会影响另一方
- 常见实现方式:序列化 / 反序列化、手动递归复制
简单记忆: /浅拷贝是 “表面复制”,深拷贝是 “彻底复制”。/
4、java8都有哪些新特性
Java 8 核心新特性:
1. Lambda表达式:(a, b) -> a + b
2. Stream API:list.stream().filter(x -> x>0).map(...)
3. 默认方法:interface 中 default void foo(){}
4. 方法引用:System.out::println
5. 新日期API:LocalDate、LocalDateTime
6. Optional:Optional.ofNullable(x).orElse(...)
7. 函数式接口:@FunctionalInterface(如 Predicate、Function)
5、==和EQUALS区别?
==
- 基本类型:比较值是否相等(如int a=1; b=1; a==b为true)。
- 引用类型:比较内存地址是否相同(是否指向同一对象)。
equals()
- 默认行为:同==(比较地址,如Object类)。
- 重写后:通常比较对象内容(如String、Integer等类)。
示例
String s1 = new String("abc");
String s2 = new String("abc");
s1 == s2; // false(地址不同)
s1.equals(s2); // true(内容相同)
6、final修饰类、方法、属性有什么作用?
final 类:禁止被继承(如String)。
final 方法:禁止被子类重写。
final 属性:赋值后不可变(基本类型值不变,引用类型地址不变)。
7、请说说啥是内存泄露?
内存泄漏(Memory Leak):
程序中已不再使用的对象无法被垃圾回收(GC),导致持续占用内存,最终可能引发OutOfMemoryError。
常见场景:
1. 静态集合持有对象:如static List添加元素后未清理。
2. 未关闭资源:如InputStream、数据库连接等。
3. 内部类持有外部类引用:匿名内部类隐式引用外部对象。
4. 缓存未失效:缓存条目不再使用但未被移除。
排查工具:
- 堆转储(Heap Dump)分析(如Eclipse MAT)。
- 内存分析器(如JProfiler、VisualVM)。
示例:
static List<Object> cache = new ArrayList<>();
public void addToCache(Object obj) {
cache.add(obj); // 对象无法被GC回收,即使不再使用
}
8、Java的集合框架框架有哪些?
Java集合框架核心接口:
1. Collection
- List(有序可重复):ArrayList、LinkedList、Vector。
- Set(无序唯一):HashSet、TreeSet、LinkedHashSet。
- Queue(队列):LinkedList、PriorityQueue。
2. Map(键值对)
- HashMap、TreeMap、LinkedHashMap、ConcurrentHashMap。
关键抽象类:
- AbstractList、AbstractSet、AbstractMap 等。
工具类:
- Collections(静态方法)、Arrays(数组转集合)。
并发集合:
- ConcurrentHashMap、CopyOnWriteArrayList。
注意:
- fail-fast:ArrayList、HashMap(迭代时修改抛ConcurrentModificationException)。
- fail-safe:ConcurrentHashMap、CopyOnWriteArrayList(迭代时允许修改)。
9、ArrayList和LinkedList有啥区别?
ArrayList 和 LinkedList 是 List 接口的两种主要实现,区别如下:
(1)数据结构:
- ArrayList:动态数组
- LinkedList:双向链表
(2)适用场景:
- ArrayList:频繁随机访问,较少插入删除
- LinkedList:频繁插入删除,较少随机访问
(3)注意:
- ArrayList 扩容会导致性能损耗
- LinkedList 不支持高效随机访问(需遍历链表)
- 线程不安全场景优先使用 ArrayList,线程安全场景可用 CopyOnWriteArrayList
简单记忆:随机访问选 ArrayList,频繁插入删除选 LinkedList。
10、list和set有啥区别?
List 和 Set 的区别(记忆口诀)
👉 “List 有序可重复,Set 无序独一份”
List(如 ArrayList、LinkedList):
- 有序:存进去的顺序就是取出来的顺序(像排队)
- 可重复:能存多个相同的元素(像购物车里的商品)
- 按索引访问:直接用 get(0) 取第一个
Set(如 HashSet、TreeSet):
- 无序(*HashSet*)或 按规则排序(*TreeSet*)
- 唯一性:自动去重(像数学集合,相同元素存不进)
- 无索引:只能遍历或转成数组访问
场景选择:
- 需要重复元素?用 List ✅
- 需要自动去重?用 Set ✅
11、哪些场景需要hashmap?
HashMap 适用于以下核心场景:
- 键值对映射存储:需通过唯一键(如 ID、名称)快速关联对应值(如对象、数据),例如存储用户信息(键为用户 ID,值为用户对象)。
- 快速查找/判断存在:基于哈希表实现,查询、插入、删除操作平均时间复杂度为 O(1),适合频繁判断“键是否存在”或“获取对应值”的场景(如缓存、字典)。
- 无需排序的场景:不要求元素有序(若需维护插入顺序可改用
LinkedHashMap),例如临时存储中间结果、统计频次(键为元素,值为计数)。 - 数据去重辅助:利用键的唯一性,快速过滤重复数据(如将待去重元素作为键存入,自动去重)。
核心优势:以空间换时间,提供高效的键值操作,是 Java 中最常用的映射容器。
12、请说说集合分类与接口关系的关系?
Java集合分类与接口关系(记忆口诀)
👉 “单列双列分清楚,有序无序再区分”
(1)、两大根接口
- 单列集合(
Collection):
- List:有序可重复 → 像排队
- 实现类:
ArrayList(数组)、LinkedList(链表)、Vector(线程安全)
- 实现类:
- Set:无序唯一 → 像数学集合
- 实现类:
HashSet(哈希表)、TreeSet(红黑树排序)
- 实现类:
- 双列集合(
Map):键值对存储 → 像字典
- 实现类:
HashMap、TreeMap、Hashtable(线程安全)
(2)、接口继承关系图
Collection Map
├── List ├── HashMap
│ ├── ArrayList ├── TreeMap
│ └── LinkedList └── Hashtable
└── Set
├── HashSet
└── TreeSet
(3)、特殊接口
- Queue(队列):
LinkedList也实现了它 - SortedSet/SortedMap:支持排序(如
TreeSet/TreeMap)
一句话总结:
“先分单双列,再看序与序,最后挑实现类!”
13、请说说cookie 和 session的区别?
Cookie 和 Session 的区别(记忆口诀)
👉 “Cookie 客户存,Session 服务端,一个明晃晃,一个暗藏藏”
(1)存储位置
- Cookie:数据存在**浏览器**(客户端)
- 像小纸条:用户自己拿着,每次请求递给服务器
- Session:数据存在**服务器**(如内存/Redis)
- 像保险箱:服务器保管,只给用户一把钥匙(SessionID)
(2)安全性
- Cookie:
- 明文存储,可能被篡改(需配合
HttpOnly防XSS) - 例子:记住用户名这种非敏感信息
- Session:
- 敏感数据(如用户ID)更安全,实际数据用户看不到
(3)生命周期
- Cookie:
- 可设置长期有效(如
setMaxAge(3600)) - 关闭浏览器后依然存在
- Session:
- 默认浏览器关闭失效(依赖JSESSIONID Cookie)
- 服务器可主动销毁
(4)存储大小
- Cookie:单个≤4KB,域名下总数有限(约20~50个)
- Session:理论上无限制(但受服务器内存影响)
(5)典型应用场景
| Cookie | Session |
|---|---|
| 记住登录状态(7天免密) | 保存购物车商品详情 |
| 保存用户偏好(如语言主题) | 存敏感信息(如权限角色) |
一句话总结:
“Cookie 是用户口袋里的便利贴,Session 是服务器的秘密备忘录!”
14、请说说String、StringBuffer、StringBuilder的区别?
String、StringBuffer、StringBuilder 的区别(记忆口诀)
👉 “String 不可变,Buffer 线程安,Builder 单线程快如箭!”
(1) 可变性
- String:**不可变**(每次修改都生成新对象)
- 像刻在石头上的字,改内容就要换石头
- StringBuffer / StringBuilder:**可变**(原地修改字符数组)
- 像黑板擦写,可随时修改内容
(2) 线程安全
- StringBuffer:线程安全(关键方法加
synchronized锁) - 多线程操作字符串时用它
- StringBuilder:线程不安全(无锁,性能更高)
- 单线程操作字符串首选
(3) 性能对比
| 操作 | String | StringBuffer | StringBuilder |
|---|---|---|---|
| 频繁拼接字符串 | 最慢 | 较快 | 最快 |
示例代码:
// String:产生大量临时对象(不推荐循环拼接)
String s = "a";
for (int i = 0; i < 100; i++) { s += i; } // 慢!
// StringBuilder:单线程首选
StringBuilder sb = new StringBuilder("a");
for (int i = 0; i < 100; i++) { sb.append(i); } // 快!
// StringBuffer:多线程兼容
StringBuffer sbf = new StringBuffer("a");
for (int i = 0; i < 100; i++) { sbf.append(i); } // 安全但稍慢
(4) 使用场景
- String:存储固定字符串(如配置常量)
- StringBuilder:单线程字符串拼接(如SQL拼接、JSON组装)
- StringBuffer:多线程字符串操作(如全局日志处理)
一句话总结:
“String 只读不写,Builder 单线程飞起,Buffer 为多线程保驾护航!”
15、请说说HashMap的底层原理?
HashMap 是 Java 中最常用的哈希表实现,其底层原理基于 数组 + 链表 + 红黑树 结构,核心机制包括哈希函数、碰撞处理和扩容策略。以下是详细解析:
(1)**数据结构**
- JDK 7 及之前:数组 + 链表(Entry 数组,每个元素是链表头节点)。
- JDK 8 及之后:数组 + 链表 + 红黑树(Node/TreeNode 数组,链表长度超过 8 且数组长度 ≥ 64 时转为红黑树)。
// Node 节点结构(简化版)
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; // 哈希值(经扰动函数处理)
final K key;
V value;
Node<K,V> next; // 链表下一个节点
}
(2)**哈希函数与索引计算**
- 哈希函数:通过
key.hashCode()计算原始哈希值,再经 扰动函数(hash = key.hashCode() ^ (key.hashCode() >>> 16))降低哈希冲突概率。 - 索引计算:
index = (table.length - 1) & hash,等价于hash % table.length(数组长度为 2 的幂时位运算更高效)。
(3) 碰撞处理(拉链法)
- 链表:当多个 key 的哈希值相同时,它们会被放入同一索引位置的链表中(尾插法,JDK 8 改进)。
- 红黑树:当链表长度超过 8 且数组长度 ≥ 64 时,链表转换为红黑树(时间复杂度从 O(n) 降至 O(log n)),以提高查询效率。
(4) 扩容机制(resize)
- 触发条件:元素数量超过 阈值(threshold = capacity × loadFactor) 时扩容(默认容量 16,负载因子 0.75)。
- 扩容步骤:
- 数组长度翻倍(如 16 → 32)。
- 重新计算所有元素的索引(无需重新哈希,通过
e.hash & oldCap判断元素在新数组中的位置)。 - 迁移元素:链表按高低位拆分,红黑树可能退化为链表(节点数 ≤ 6 时)。
(5) 关键参数
- 初始容量(initialCapacity):默认 16,建议设置为 2 的幂(如 32、64)以避免频繁扩容。
- 负载因子(loadFactor):默认 0.75,权衡空间与时间开销。调高可减少空间浪费,但会增加碰撞概率;调低反之。
- 树化阈值(TREEIFY_THRESHOLD):链表转红黑树的长度阈值(8)。
- 链表阈值(UNTREEIFY_THRESHOLD):红黑树退化为链表的长度阈值(6)。
(6) 线程安全性
- 非线程安全:多线程环境下可能出现数据不一致(如 put 操作时的环形链表问题)。
- 替代方案:
ConcurrentHashMap(线程安全,分段锁机制,JDK 8 改用 CAS + synchronized)。Collections.synchronizedMap()(全局锁,性能较差)。
(7) 典型操作流程
- put(K key, V value):
- 计算 key 的哈希值和数组索引。
- 若数组为空,初始化数组。
- 若索引位置为空,直接插入新节点。
- 若存在节点,遍历链表/红黑树:
- 若 key 已存在,覆盖 value。
- 若为链表且无匹配 key,插入新节点(尾插法),判断是否树化。
- 若为红黑树,插入新节点并调整树结构。
- 检查是否需要扩容。
- get(Object key):
- 计算 key 的哈希值和数组索引。
- 遍历对应位置的链表/红黑树,通过
equals()方法匹配 key。
(8) 常见问题
- 哈希冲突优化:通过扰动函数和扩容机制减少冲突,树化进一步提高查询效率。
- 为什么容量是 2 的幂?:保证
(table.length - 1) & hash等价于取模运算,且扩容时无需重新计算哈希值。 - JDK 8 的改进:
- 链表插入方式从头插法改为尾插法(避免多线程扩容时的环形链表问题)。
- 引入红黑树优化长链表查询性能。
总结
HashMap 通过哈希函数快速定位元素,用链表/红黑树处理冲突,动态扩容保证性能。在单线程场景中高效易用,但多线程环境需选择线程安全的替代方案。理解其底层原理有助于合理设置参数(如初始容量)和避免常见陷阱。