第一单元

亮子 2025-07-20 16:27:57 278 0 0 0

1、你是怎样理解面向对象?

(1) 四大核心特性

  • 封装(Encapsulation)
    将数据(属性)和操作数据的方法(行为)绑定在一起,隐藏内部实现细节,仅暴露必要的接口。
    目的:提高安全性(如私有属性)、降低复杂度(使用者无需关心内部逻辑)。

  • 继承(Inheritance)
    子类可以继承父类的属性和方法,并扩展或重写功能。
    目的:实现代码复用,建立类之间的层次关系(如Dog extends Animal)。

  • 多态(Polymorphism)
    同一操作作用于不同对象时,可能产生不同的行为。通常通过方法重写(Override)或接口实现来实现。
    例子Animal类的speak()方法,Dog返回“Woof!”,Cat返回“Meow!”。

  • 抽象(Abstraction)
    提取共性特征形成抽象类或接口,忽略非关键细节。
    例子Shape抽象类定义calculateArea()方法,由子类CircleSquare具体实现。

(2) 核心思想

  • 对象为中心:程序由对象交互构成,每个对象是数据和行为的封装体。
  • 模拟现实世界:将问题域中的实体映射为代码中的类(Class)和对象(Object)。
  • 高内聚低耦合:类内部高度自治,类之间依赖最小化,提升可维护性。

(3) 通俗理解

想象你造一辆车:
- 是设计图纸(定义车的属性和功能);
- 对象是按图纸造出的具体车辆;
- 继承是基于基础车型设计豪华版;
- 多态是踩油门时,电动车和燃油车响应方式不同;
- 封装是隐藏发动机细节,只给你方向盘和油门接口。

面向对象的本质是**用代码模拟现实世界的协作关系**,让复杂系统更易于理解和构建。

2、重载与重写有什么区别?

重载(Overload)和重写(Override)是Java中两个重要的概念,主要区别如下:

重载(Overload)
- 发生范围:同一个类中
- 方法签名:方法名相同,但参数列表不同(参数类型、个数或顺序)
- 返回值:无关,可以不同
- 访问修饰符:无关,可以不同
- 用途:提供功能相似但参数不同的方法变体

重写(Override)
- 发生范围:子类与父类之间
- 方法签名:方法名、参数列表、返回值类型必须与父类完全相同(Java 5+支持协变返回类型)
- 访问修饰符:子类方法的访问权限不能小于父类(如父类是protected,子类只能是protectedpublic
- 异常:子类方法抛出的异常范围不能大于父类
- 用途:子类修改或扩展父类的方法实现

示例

// 重载示例(同一个类中)
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 APIlist.stream().filter(x -> x>0).map(...)
3. 默认方法interfacedefault void foo(){}
4. 方法引用System.out::println
5. 新日期APILocalDateLocalDateTime
6. OptionalOptional.ofNullable(x).orElse(...)
7. 函数式接口@FunctionalInterface(如 PredicateFunction

5、==和EQUALS区别?

==
- 基本类型:比较值是否相等(如int a=1; b=1; a==btrue)。
- 引用类型:比较内存地址是否相同(是否指向同一对象)。

equals()
- 默认行为:同==(比较地址,如Object类)。
- 重写后:通常比较对象内容(如StringInteger等类)。

示例

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(有序可重复):ArrayListLinkedListVector
- Set(无序唯一):HashSetTreeSetLinkedHashSet
- Queue(队列):LinkedListPriorityQueue
2. Map(键值对)
- HashMapTreeMapLinkedHashMapConcurrentHashMap

关键抽象类
- AbstractListAbstractSetAbstractMap 等。

工具类
- Collections(静态方法)、Arrays(数组转集合)。

并发集合
- ConcurrentHashMapCopyOnWriteArrayList

注意
- fail-fastArrayListHashMap(迭代时修改抛ConcurrentModificationException)。
- fail-safeConcurrentHashMapCopyOnWriteArrayList(迭代时允许修改)。

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 适用于以下核心场景:

  1. 键值对映射存储:需通过唯一键(如 ID、名称)快速关联对应值(如对象、数据),例如存储用户信息(键为用户 ID,值为用户对象)。
  2. 快速查找/判断存在:基于哈希表实现,查询、插入、删除操作平均时间复杂度为 O(1),适合频繁判断“键是否存在”或“获取对应值”的场景(如缓存、字典)。
  3. 无需排序的场景:不要求元素有序(若需维护插入顺序可改用 LinkedHashMap),例如临时存储中间结果、统计频次(键为元素,值为计数)。
  4. 数据去重辅助:利用键的唯一性,快速过滤重复数据(如将待去重元素作为键存入,自动去重)。

核心优势:以空间换时间,提供高效的键值操作,是 Java 中最常用的映射容器。

12、请说说集合分类与接口关系的关系?

Java集合分类与接口关系(记忆口诀)
👉 “单列双列分清楚,有序无序再区分”

(1)、两大根接口

  1. 单列集合Collection):
  • List:有序可重复 → 像排队
    • 实现类:ArrayList(数组)、LinkedList(链表)、Vector(线程安全)
  • Set:无序唯一 → 像数学集合
    • 实现类:HashSet(哈希表)、TreeSet(红黑树排序)
  1. 双列集合Map):键值对存储 → 像字典
  • 实现类:HashMapTreeMapHashtable(线程安全)

(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)。
  • 扩容步骤
  1. 数组长度翻倍(如 16 → 32)。
  2. 重新计算所有元素的索引(无需重新哈希,通过 e.hash & oldCap 判断元素在新数组中的位置)。
  3. 迁移元素:链表按高低位拆分,红黑树可能退化为链表(节点数 ≤ 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)
  1. 计算 key 的哈希值和数组索引。
  2. 若数组为空,初始化数组。
  3. 若索引位置为空,直接插入新节点。
  4. 若存在节点,遍历链表/红黑树:
    • 若 key 已存在,覆盖 value。
    • 若为链表且无匹配 key,插入新节点(尾插法),判断是否树化。
    • 若为红黑树,插入新节点并调整树结构。
  5. 检查是否需要扩容。
  • get(Object key)
  1. 计算 key 的哈希值和数组索引。
  2. 遍历对应位置的链表/红黑树,通过 equals() 方法匹配 key。

(8) 常见问题

  • 哈希冲突优化:通过扰动函数和扩容机制减少冲突,树化进一步提高查询效率。
  • 为什么容量是 2 的幂?:保证 (table.length - 1) & hash 等价于取模运算,且扩容时无需重新计算哈希值。
  • JDK 8 的改进
  • 链表插入方式从头插法改为尾插法(避免多线程扩容时的环形链表问题)。
  • 引入红黑树优化长链表查询性能。

总结

HashMap 通过哈希函数快速定位元素,用链表/红黑树处理冲突,动态扩容保证性能。在单线程场景中高效易用,但多线程环境需选择线程安全的替代方案。理解其底层原理有助于合理设置参数(如初始容量)和避免常见陷阱。