尚硅谷2019版Java集合和泛型
第十一章 Java集合框架
集合框架全景图
mindmap
root((Java集合))
Collection单列
List有序可重复
ArrayList
LinkedList
Vector
Set无序唯一
HashSet
LinkedHashSet
TreeSet
Map双列
HashMap
LinkedHashMap
TreeMap
Hashtable
Properties
Tools
Collections
Arrays
三大核心接口对比
特性 | List | Set | Map |
---|---|---|---|
元素顺序 | 插入顺序 | 无顺序 | Key唯一无序 |
元素重复性 | 允许重复 | 不允许重复 | Key唯一,Value可重复 |
常用实现类 | ArrayList/LinkedList | HashSet/TreeSet | HashMap/TreeMap |
线程安全实现 | CopyOnWriteArrayList | ConcurrentSkipListSet | ConcurrentHashMap |
ArrayList vs LinkedList
特性 | ArrayList | LinkedList |
---|---|---|
底层结构 | 动态数组 | 双向链表 |
随机访问速度 | O(1) | O(n) |
头尾操作效率 | 尾部快,头部慢 | 头尾都快 |
内存占用 | 连续空间,节省 | 节点存储,占用更多 |
扩容机制 | 1.5倍扩容 | 无扩容概念 |
HashMap深度解析
JDK1.8+ 存储结构
graph LR
A[数组] --> B[链表]
A --> C[红黑树]
B -->|长度>8 & 数组长度≥64| C
核心参数
static final int DEFAULT_INITIAL_CAPACITY = 16; // 初始容量
static final float DEFAULT_LOAD_FACTOR = 0.75f; // 负载因子
static final int TREEIFY_THRESHOLD = 8; // 树化阈值
static final int UNTREEIFY_THRESHOLD = 6; // 链化阈值
哈希冲突解决
// 经典扰动函数(JDK1.8)
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
集合线程安全解决方案
实现方式 | 示例 | 特点 |
---|---|---|
同步包装 | Collections.synchronizedList | 方法级synchronized锁 |
CopyOnWrite | CopyOnWriteArrayList | 写时复制,读无锁 |
CAS机制 | ConcurrentHashMap | 分段锁/Node+CAS |
并发队列 | LinkedBlockingQueue | 双锁设计,put/take分开 |
集合工具类妙用
Collections魔法方法
// 创建不可变集合
List<String> unmodifiableList = Collections.unmodifiableList(list);
// 排序+二分查找
Collections.sort(list);
int index = Collections.binarySearch(list, "key");
// 频率统计
int count = Collections.frequency(list, "element");
性能提升技巧
// 预设集合容量(避免扩容)
ArrayList<String> list = new ArrayList<>(1000);
// 使用批量添加
list.addAll(Arrays.asList(data));
// 优先迭代器遍历
for(Iterator it = list.iterator(); it.hasNext();){
System.out.println(it.next());
}
高频面试题精解
-
HashMap工作原理
- 通过hashcode计算存储位置
- JDK1.8采用数组+链表+红黑树
- 扩容时重新计算位置(2次幂扩展)
-
ConcurrentHashMap实现
- JDK1.7:分段锁(Segment)
- JDK1.8:Node+CAS+synchronized
-
ArrayList扩容机制
// 扩容核心代码(JDK1.8) int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍
-
Iterator快速失败机制
- modCount记录修改次数
- 遍历时检测modCount变化
📘 实战建议
- 根据场景选择集合:查询多用ArrayList,增删多用LinkedList
- 预估数据量时显式指定初始容量(特别对HashMap)
- 多线程环境优先使用JUC并发集合
- 复杂对象作为Map键时,必须正确重写hashCode和equals
- 遍历时优先使用增强for循环或迭代器,避免用fori遍历链表结构
第十二章 Java泛型
泛型基础概念
泛型本质
泛型优势
特性 | 非泛型 | 泛型 |
---|---|---|
类型安全 | 运行时可能ClassCastException | 编译时类型检查 |
代码简洁度 | 需要显式类型转换 | 自动类型推断 |
代码复用性 | 针对特定类型编写 | 一套代码处理多种类型 |
集合元素操作 | 只能存储Object | 可存储指定类型 |
泛型使用场景
集合框架中的泛型
// 传统方式
List list = new ArrayList();
list.add("hello");
String str = (String) list.get(0); // 需要强制转换
// 泛型方式
List<String> list = new ArrayList<>();
list.add("hello");
String str = list.get(0); // 自动类型推断
自定义泛型类
public class Box<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
// 使用
Box<String> stringBox = new Box<>();
stringBox.setItem("Hello");
String str = stringBox.getItem();
泛型高级特性
泛型继承关系
通配符使用
// 上界通配符
public static double sum(List<? extends Number> list) {
double sum = 0;
for (Number n : list) {
sum += n.doubleValue();
}
return sum;
}
// 下界通配符
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
泛型方法
定义与使用
// 泛型方法定义
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
// 使用示例
Integer[] intArray = {1, 2, 3};
printArray(intArray);
String[] strArray = {"A", "B", "C"};
printArray(strArray);
类型推断
// 显式指定类型
Collections.<String>emptyList();
// 自动类型推断
List<String> list = Collections.emptyList();
泛型限制与注意事项
使用限制
常见问题
- 类型擦除:泛型信息在编译后被擦除
// 编译前
List<String> list = new ArrayList<>();
// 编译后(类型擦除)
List list = new ArrayList();
- 泛型数组:不能直接创建
// 错误示例
// List<String>[] array = new List<String>[10];
// 正确方式
List<?>[] array = new List<?>[10];
- 泛型异常:不能捕获泛型异常
// 错误示例
// try { ... } catch (T e) { ... }
最佳实践建议
-
命名规范:使用单个大写字母作为类型参数
-
E:Element(集合元素)
-
K:Key(键)
-
V:Value(值)
-
T:Type(类型)
-
-
边界控制:合理使用上下界通配符
-
生产者使用
<? extends T>
-
消费者使用
<? super T>
-
-
类型安全:优先使用泛型集合
// 推荐
List<String> list = new ArrayList<>();
// 不推荐
List list = new ArrayList();
- 代码复用:善用泛型方法
public static <T> T getFirst(List<T> list) {
return list.get(0);
}
Java泛型实验
实验1:集合中使用泛型
实验目标
- 掌握在集合中使用泛型的方法
- 理解Comparable和Comparator接口的使用
- 实现自定义对象的排序
实验步骤
- 定义Employee类
public class Employee implements Comparable<Employee> {
private String name;
private int age;
private MyDate birthday;
// 构造器、getter、setter省略
@Override
public int compareTo(Employee o) {
return this.name.compareTo(o.name);
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", birthday=" + birthday +
'}';
}
}
- 定义MyDate类
public class MyDate {
private int year;
private int month;
private int day;
// 构造器、getter、setter省略
}
- 实现排序
// 按姓名排序(自然排序)
TreeSet<Employee> set1 = new TreeSet<>();
set1.add(new Employee("Alice", 25, new MyDate(1998, 5, 12)));
// 添加其他Employee对象
// 按生日排序(定制排序)
TreeSet<Employee> set2 = new TreeSet<>(new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
// 实现日期比较逻辑
}
});
实验结果
- 成功创建Employee对象并存入TreeSet
- 实现按姓名和生日的两种排序方式
- 正确遍历输出排序结果
实验2:自定义泛型类的使用
实验目标
- 掌握自定义泛型类的定义和使用
- 理解泛型在数据访问层(DAO)中的应用
- 使用JUnit进行单元测试
实验步骤
- 定义泛型DAO类
public class DAO<T> {
private Map<String, T> map = new HashMap<>();
public void save(String id, T entity) {
map.put(id, entity);
}
public T get(String id) {
return map.get(id);
}
public void update(String id, T entity) {
map.put(id, entity);
}
public List<T> list() {
return new ArrayList<>(map.values());
}
public void delete(String id) {
map.remove(id);
}
}
- 定义User类
public class User {
private int id;
private int age;
private String name;
// 构造器、getter、setter省略
}
- 编写测试类
public class DAOTest {
@Test
public void testDAO() {
DAO<User> dao = new DAO<>();
User u1 = new User(1, 25, "Alice");
dao.save("001", u1);
assertEquals(u1, dao.get("001"));
User u2 = new User(2, 30, "Bob");
dao.update("001", u2);
assertEquals(1, dao.list().size());
dao.delete("001");
assertNull(dao.get("001"));
}
}
实验结果
- 成功实现泛型DAO类
- 完成对User对象的CRUD操作
- 通过JUnit测试验证功能正确性
实验总结
关键知识点
-
泛型集合
- 类型安全
- 自动类型转换
- 减少代码重复
-
排序实现
- Comparable接口:自然排序
- Comparator接口:定制排序
-
泛型类设计
- 类型参数化
- 提高代码复用性
- 增强类型安全性
-
单元测试
- 使用JUnit验证功能
- 保证代码质量
- 支持重构
常见问题
-
类型擦除的影响
- 运行时无法获取泛型类型信息
- 解决方案:通过Class对象传递类型信息
-
泛型数组创建
- 不能直接创建泛型数组
- 解决方案:使用Object数组转换
-
通配符使用
- 上界通配符:
<? extends T>
- 下界通配符:
<? super T>
- 无界通配符:
<?>
- 上界通配符:
Java泛型与集合练习题
泛型类设计
题目1:开发泛型Apple类
要求:
- 设计一个泛型Apple类,包含重量属性weight
- 实例化三个对象:
- a1:weight为String类型,值为"500克"
- a2:weight为Integer类型,值为500
- a3:weight为Double类型,值为500.0
- 输出各对象的weight值
- 思考:为什么a2和a3需要使用包装类Integer和Double?
实现:
public class Apple<T> {
private T weight;
public Apple(T weight) {
this.weight = weight;
}
public T getWeight() {
return weight;
}
public static void main(String[] args) {
Apple<String> a1 = new Apple<>("500克");
Apple<Integer> a2 = new Apple<>(500);
Apple<Double> a3 = new Apple<>(500.0);
System.out.println("a1 weight: " + a1.getWeight());
System.out.println("a2 weight: " + a2.getWeight());
System.out.println("a3 weight: " + a3.getWeight());
}
}
思考:
-
泛型类型参数必须是引用类型,不能是基本类型(如int、double)
-
因此需要使用包装类Integer和Double
集合操作
题目2:新闻类与ArrayList集合
要求:
-
封装新闻类News,包含标题、作者、内容、类型属性
-
重写toString方法,输出格式为"标题;类型;作者"
-
重写equals和hashCode方法,标题相同即为同一条新闻
-
创建ArrayList集合,添加三条新闻
-
遍历集合,打印新闻标题,截取标题到10个汉字长度
实现:
public class News {
private String title;
private String author;
private String content;
private String type;
// 构造器、getter、setter省略
@Override
public String toString() {
return title + ";" + type + ";" + author;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
News news = (News) o;
return title.equals(news.title);
}
@Override
public int hashCode() {
return title.hashCode();
}
}
public class NewsTest {
public static void main(String[] args) {
List<News> newsList = new ArrayList<>();
newsList.add(new News("标题1", "作者1", "内容1", "类型1"));
newsList.add(new News("标题2", "作者2", "内容2", "类型2"));
newsList.add(new News("标题3", "作者3", "内容3", "类型3"));
for (News news : newsList) {
String title = news.getTitle();
System.out.println(title.length() > 10 ? title.substring(0, 10) : title);
}
}
}
Map集合操作
题目3:HashMap操作
要求:
-
使用HashMap存储员工姓名和工资
-
更新张三的工资为2600元
-
为所有员工加薪100元
-
遍历所有员工
-
遍历所有工资
实现:
public class EmployeeSalary {
public static void main(String[] args) {
Map<String, Integer> salaryMap = new HashMap<>();
salaryMap.put("张三", 800);
salaryMap.put("李四", 1500);
salaryMap.put("王五", 3000);
// 更新张三工资
salaryMap.put("张三", 2600);
// 所有员工加薪100元
salaryMap.replaceAll((k, v) -> v + 100);
// 遍历所有员工
System.out.println("员工列表:");
for (String name : salaryMap.keySet()) {
System.out.println(name);
}
// 遍历所有工资
System.out.println("工资列表:");
for (Integer salary : salaryMap.values()) {
System.out.println(salary);
}
}
}