# 第十一章 集合和泛型

# 集合进阶

image-20230508150213337

# 集合体系结构

集合体系一般分为两类:

单列集合:Collection

双列集合:Map

image-20230105204940740

list系列集合: 添加的元素是有序的、可重复、有索引

Set系列集合:添加的元素是无序、不重复、无索引

# Collection

Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。

方法名称 说明
public boolean add(E e) 把给定的对象添加到当前集合中
public void clear() 清空集合中所有的元素
public boolean remove(E e) 把给定的对象在当前集合中删除
public boolean contains(Object obj) 判断当前集合中是否包含给定的对象
public boolean isEmpty() 判断当前集合是否为空
public int size() 返回集合中元素的个数/集合的长度

# collection集合遍历方式

# 迭代器遍历

迭代器不依赖索引

迭代器在到java中的类是Iterator,迭代器是集合专用的遍历方式。

方法名称 说明
Iterator iterator() 返回迭代器对象,默认指向当前集合的0索引

# Iterator中的常用方法

方法名称 说明
boolean hasNext() 判断当前位置是否有元素,有元素返回true,没有元素返回false
E next() 获取当前位置的元素,并将迭代器对象移向下一个位置
Iterator<String> it = list.iterator();//创建指针
while(it.hasNext()){//判断是否有元素
	String str = it.next();//获取元素然后移动指针
	System.out.println(str);
}
1
2
3
4
5

细节:

如果当前位置没有元素,还要强行获取,会报NoSuchElementException

迭代器遍历完毕,指针不会复位

循环中只能用一次next方法

迭代器遍历时,不能用集合的方法进行增加或者删除

# 总结

1.迭代器在遍历集合的时候是不依赖索引的

2.迭代器需要掌握三个方法

3.迭代器的四个细节:

如果当前位置没有元素,还要强行获取,会报NoSuchElementException

迭代器遍历完毕,指针不会复位

循环中只能用一次next方法

迭代器遍历时,不能用集合的方法进行增加或者删除

# 增强for遍历

增强for的底层就是迭代器,为了简化迭代器的代码书写的。

它是JDK5之后出现的,其内部原理就是一个Iterator迭代器

所有的单列集合和数组才能用增强for来遍历

格式:

for(元素的数据类型 变量名 : 数组或者集合名){

}
1
2
3

# 增强for的细节

修改增强for中的变量,不会改变集合中原本的数据。

for(String s : list){
	s = "lmx";
}
1
2
3

# Lambda表达式遍历

得益于JDK8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。

方法名称 说明
default void forEach(Consumer<? super T> action): 结合Lambda遍历
集合名.forEach((String s)->{
	System.out.println(s);
});
1
2
3
集合名.forEach(s -> System.out.println(s));
1

# 总结

1.Collection是单列集合的顶层接口,所有方法被List和Set系列集合共享

2.常见成员方法:add、clear、remove、contains、isEmpty、size

3.三种通用的遍历方式:

迭代器:在遍历的过程中需要删除元素,请使用迭代器。(最好少用吧)

增强for、Lambda:(多用)

仅仅想遍历,那么使用增强for或Lambda表达式。

# List系列集合

# List集合的特有方法

  • Collection的方法List都继承了
  • List集合因为有索引,所以多了很多索引操作的方法

image-20230120183946886

利用list集合存储数据,并使用多种方式遍历

1.迭代器

2.增强for

3.Lambda表达式

List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
1
2
3
4

1.迭代器

Iterator<String> it = list.iterator();
while(it.hasNext()){
	String str = it.next();
	System.out.println(str);
}
1
2
3
4
5

2.增强for

for(String s : list){
	System.out.println(s);
}
1
2
3

3.Lambda表达式

list.forEach(s->System.out.println(s));
1

# Set系列集合

无序:存取顺序不一致(存了之后顺序就固定了)

不重复:可以去除重复

无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素

# Set集合的实现类

HashSet:无序、不重复、无索引

LinkedHashSet:有序、不重复、无索引

TreeSet:可排序、不重复、无索引

Set接口中的方法上基本与Collection的API一致

# 练习:存储字符串并遍历

利用Set系列的集合,添加字符串,并使用多种方式遍历

1.迭代器

2.增强for

3.Lambda表达式

//添加一个Set集合的对象
Set<String> s = new HashSet<>();

//2.添加元素
boolean r1 = s.add("张三");
boolean r2 = s.add("张三");
System.out.println(r1);//true
System.out.println(r2);//false
System.out.println(s);//[张三]
1
2
3
4
5
6
7
8
9

打印集合

Set<String> s = new HashSet<>();
        s.add("张三");
        s.add("李四");
        s.add("王麻子");
        s.add("六筒");
        //打印集合
        System.out.println(s);//无序的
1
2
3
4
5
6
7

迭代器遍历

Set<String> s = new HashSet<>();
        s.add("张三");
        s.add("李四");
        s.add("王麻子");
        s.add("六筒");
//迭代器
Iterator<String> iterable = s.iterator();
        while (iterable.hasNext()){
            String next = iterable.next();
            System.out.println(next);
        }
1
2
3
4
5
6
7
8
9
10
11

增强for

Set<String> s = new HashSet<>();
        s.add("张三");
        s.add("李四");
        s.add("王麻子");
        s.add("六筒");
//增强for
for (String str:s) {
            System.out.println(str);
        }
1
2
3
4
5
6
7
8
9

Lambda表达式

     Set<String> s = new HashSet<>();
        s.add("张三");
        s.add("李四");
        s.add("王麻子");
        s.add("六筒");
	s.forEach(s->System.out.println(s));
1
2
3
4
5
6

# 总结

# Set系列集合的特点

  • 无序、不重复、无索引
  • Set集合的方法上基本与Collection的API一致

# Set集合的实现类特点

  • HashSet:无序、不重复、无索引

    LinkedHashSet:有序、不重复、无索引

    TreeSet:可排序、不重复、无索引

# HashSet

# HashSet底层原理

  • HashSet集合底层采取哈希表存储数据
  • 哈希表是一种对于增删改查数据性能都能较好的结构

# 哈希表的组成

  • JDK8之前:数组+链表
  • JDK8开始:数组+链表+红黑树

# 哈希值(重要)

是JDK根据对象的地址,按照某种规则算出来的int类型的数值。

Object类的API

  • public int hashCode() : 返回对象的哈希值

# 对象的哈希值特点

  • 同一个对象多次调用hashCode()方法返回的哈希值是相同的
  • 默认情况下,不同对象的哈希值是不同的

# HashSet底层原理

image-20230509092827391

1.创建一个默认长度16,默认加载为0.75的数组,数组名table

2.根据元素的哈希值跟数组的长度计算出应存入的位置

3.判断当前位置是否为null,如果是null直接存入

4.如果位置不为null,表示有元素,则调用equals.方法比较属性值

5.一样:不存,不一样:存入数组,形成链表

JDK8以前:新元素存入数组,老元素挂在新元素下面

JDK8以后:新元素直接挂在老元素下面

JDK8以后,当链表长度超过8,而且数组长度大于等于64时,自动转换为红黑树

如果集合中存储的是自定义对象,必须要重写hashcodeequals方法

JDK8开始后,哈希表对于红黑树的引入近一步提高了操作数据的性能

# HashSet的三个问题

问题1:HashSet为什么存和取的顺序不一样?

无序是跟hashset算法有关系的,

问题2:HashSet为什么没有索引?

底层是数组+链表+红黑树

问题3:Hashset是利用什么机制保证数据去重的?

三步:

判断当前位置是否为null,如果是null直接存入

如果位置不为null,表示有元素,则调用equals.方法比较属性值

一样:不存,不一样:存入数组,形成链表

# 总结:

1.HashSet集合的底层数据结构是什么样的?(JDK1.8的底层数据结构)

数组+链表+红黑树

2.HashSet添加元素的过程?

哈希值 % 16(数组的长度:创建HashSet) = 值对应数组的索引

3.HashSet为什么存和取的顺序不一样?

无序是跟hashset算法有关系的

4.HashSet为什么没有索引?

底层是数组+链表+红黑树

5.Hashset是利用什么机制保证数据去重的?

三步:

判断当前位置是否为null,如果是null直接存入

如果位置不为null,表示有元素,则调用equals.方法比较属性值

一样:不存,不一样:存入数组,形成链表

需要去重写equals和hashCode方法

# 案例:利用HashSet集合去除重复元素

需求:创建一个存储学生对象的集合,存储多个学生对象

使用程序实现在控制台遍历该集合

要求:学生对象的成员变量相同,我们就认为是同一个对象

分析:

  • 定义学生类,创建HashSet集合对象,创建学生对象
  • 把学生添加到集合
  • 在学生类中重写两个方法,hashCode()和equals(),自动生成即可

# Map集合体系

# Map集合的概述

Map集合是一种双列集合 ,每个元素包含两个数据(我们要把这两个数据看成一个元素)

Map集合的每个元素的格式:key=value(键值对元素)。

Map集合也被称为“键值对集合”

# Map集合整体格式

Collection集合的格式:[元素1,元素2,元素3....]

Map集合的完整格式:{key1=value1,key2=value2,key3=value3,…}

# Map集合体系特点

image-20230515095641434

说明

  • 使用最多的Map集合是HashMap
  • 重点掌握HashMap其他的后续理解

特点:

Map集合的特点都是由键(key)决定的

Map集合的键是无序,不重复的,无索引,值(value)不做要求(可以重复)

Map集合后面重复的键对应的值会覆盖前面重复键的值(后面如果存相同的key,后面的key会覆盖前面的key)

Map集合的键值对都可以为null。(null:null)

Map集合实现类的特点:

HashMap:元素按照键是无序,不重复,无索引,值不做要求。(与Map体系一致)

# Map集合常用API

Map是双列集合的祖宗接口,它的功能是全部双列集合都可以继承使用的。

方法名称 说明
V put(K key , V value) 添加元素
V remove(Object key) 根据键删除键值对元素
void clear() 移除所有的键值对元素
boolean containsKey(Object key) 判断集合是否包含指定的键
boolean containsValue(Object value) 判断集合是否包含指定的值
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,也就是集合中键值对的个数
Set keySet() 获取全部键的集合
Collection values() 获取全部值的集合

# Map集合的遍历方式一:键找值

先获取Map集合的全部键的Set集合。

遍历键的Set集合,然后通过键提取对应值。

键找值涉及的API:

方法名称 说明
Set keySet() 获取所有键的集合
V get(Object key) 根据键获取值
Set<String> strings = map.keySet();
        for (String s: strings) {
            Integer integer = map.get(s);
            System.out.println(s+":"+integer);
        }
1
2
3
4
5

# Map集合的遍历方式二:键值对

先把Map集合转换成Set集合,Set集合中每个元素都是键值对实体类型了。

遍历Set集合,然后提取键以及提取值。

键值对涉及到的API:

方法名称 说明
Set<Map.Entry<K,V>> entrySet() 获取所有键值对对象的集合
K getKey() 获得键
V getValue() 获取值
 Set<Map.Entry<String, Integer>> entries = map.entrySet();
         for (Map.Entry<String, Integer> m: entries) {
             String key = m.getKey();
             Integer value = m.getValue();
             System.out.println(key+":"+value);
         }
1
2
3
4
5
6

# Map集合的遍历方式三:lambda表达式

得益于JDK8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。

Map结合Lambda遍历的API

方法名称 说明
default void forEach(BiConsumer<? super K , ? super V> action) 结合Lambda遍历Map集合
map.forEach(new BiConsumer<String,Integer>(){
            @Override
            public void accept(String key, Integer value) {
                System.out.println(key+":"+value);
            }
        });
1
2
3
4
5
6
map.forEach((k,v)->{
             System.out.println(k+":"+v);
        });
1
2
3

# 案例:Map集合案例-统计投票人数

需求:某个班级80名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A、B、C、D),每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多。

# 案例:存储学生对象并遍历

需求:

创建个HashMap:集合,键是学生对象(Student),值是籍贯(String)

存储三个键值对元素,并遍历

要求:同姓名,同年龄认为是同一个学生

# 泛型

看一个需求

请编写程序,在ArrayList中,添加3个Dog对象

Dog对象含有name和age,并输出name和age(要求使用getXXX())

我们先使用传统方式来解决

class Dog {
    private String name;
    private int age;

    public Dog() {
    }

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

使用传统方法的问题分析

传统方法的问题分析

  1.不能对加入到集合ArrayList中的数据类型进行约束(不安全)
  2.遍历的时候,需要进行类型转换,如果集合中的数据量过大,对效率有影响
使用泛型的好处

  1.编译时,可以添加元素的类型,提高了安全性
  2.减少了类型转换的次数,提高效率

# 泛型介绍

理解:泛型 => Integer,String,Dog

泛型是一种表示数据类型的数据类型

泛型又称为参数化类型,解决了数据类型的安全性问题,在类声明或者实例化时,只需要指定要需要的具体的类型即可

Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常,同时代码更加简洁,健壮

泛型的作用:可以在类声明时通过一个标识类中某个属性的类型,或者是某个方法的返回值的类型,或者是参数类型.

# 泛型的细节

泛型中不能写基本数据类型

指定泛型的具体类型后,传递数据时,可以传入该类类型或者其子类类型

如果不写泛型,类型默认是Object

# 泛型类

使用场景:当一个类中,某个变量的数据类型不确定时,就可以定义带有泛型的类

image-20230123154457075

此处E可以理解为变量,但是不是用来记录数据的,而是记录数据的类型,可以写成T、E、K、V等

# 练习:泛型方法的练习

案例1:

创建3个学生对象

放入到HashSet中

放入到HashMap中,要求 key 是String name,value 是学生对象

使用两种方式遍历

案例2:

image-20240222135448159

# 泛型接口

image-20230123160929657

重点:如何使用一个带泛型的接口

方式1:实现类给出具体类型

方式2:实现类延续泛型,创建对象时再确定

# 自定义泛型类

基本语法

class 类名<T,R....>{
	成员
}
1
2
3

注意细节:

1.普通成员可以使用泛型

2.使用泛型的数组,不能初始化

3.静态方法中不能使用类的泛型

4.泛型类的类型,是在创建对象时确定的(因为创建对象的时候,需要确定类型)

5.如果在创建对象的时候没有指定类型,默认为object

# 自定义泛型接口

基本语法

interface 接口名<T,R.....>{

}
1
2
3

注意细节:

1.接口中,静态成员也不能使用泛型(这个和泛型类规定一样)

2.泛型接口的类型,在继承接口或实现接口确定

3.没有指定类型,默认为Object

最近更新: 5/20/2025, 2:44:56 PM
失败才是人生的主旋律   |