【Java入门】集合框架


学习目标

  • 掌握集合框架体系结构

  • 掌握List接口存储特点

  • 掌握ArrayList类常用方法

  • 了解泛型的定义和作用

  • 掌握使用集合时使用泛型来约束元素类型


一、集合框架体系

思考:既然数组可以存储多个数据,为什么要出现集合?

  • 数组的长度是固定的,集合的长度是可变的。

  • 使用Java类封装出一个个容器类,开发者只需要直接调用即可,不用再手动创建容器类。

1.1、集合框架概述

集合是Java中提供的一种容器,可以用来存储多个数据,根据不同存储方式形成的体系结构,就叫做集合框架体系(掌握)。集合也时常被称为容器。

在这里插入图片描述

List、Set集合是继承了Colection接口,跟Collection是继承关系

而ArrayList、LinkedList跟List是继承关系,HashSet、TreeSet跟Set也是继承关系,HashMap、TreeMap跟Map也是继承关系

  • Collection接口:泛指广义上集合,主要表示List和Set两种存储方式。

  • List接口:表示列表,规定了允许记录添加顺序,允许元素重复的规范。

  • Set接口:表示狭义上集合,规定了不记录添加顺序,不允许元素重复的规范。

  • Map接口:表示映射关系,规定了两个集合映射关系的规范。

1.2、集合的分类

根据容器的存储特点的不同,可以分成三种情况:
在这里插入图片描述

  • List(列表):允许记录添加顺序,允许元素重复。=> 元素有序且可重复

  • Set(数据集):不记录添加顺序,不允许元素重复。=> 元素无序且唯一

二、List接口

List接口是Collection接口子接口,List接口定义了一种规范,要求该容器允许记录元素的添加顺序,也允许元素重复。那么List接口的实现类都会遵循这一种规范。

List集合存储特点:

  • 允许元素重复

  • 允许记录元素的添加先后顺序

该接口常用的实现类有:

  • ArrayList类:数组列表,表示数组结构,底层采用数组实现,开发中使用对多的实现类,重点。

  • LinkedList类:链表,表示双向列表和双向队列结构,采用链表实现,使用不多。

  • Stack类:栈,表示栈结构,采用数组实现,使用不多。

  • Vector类:向量,其实就是古老的ArrayList,采用数组实现,使用不多。

一般来说,集合接口的实现类命名规则:(底层数据结构 + 接口名)例如:ArrayList

2.1、ArrayList类

ArrayList类,基于数组算法的列表,通过查看源代码会发现底层其实就是一个Object数组。

ArrayList常用方法的API:

在这里插入图片描述

public class ArrayListDemo1 {
	public static void main(String[] args) {
		// 创建一个默认长度的列表对象
		List list = new ArrayList();
		// 打印集合中元素的个数
		System.out.println("元素数量:"+list.size());//0
		// 添加操作:向列表中添加4个元素
		list.add("Will");
		list.add(100);
		list.add(true);
		list.add("Lucy");
		// 查询操作:
		System.out.println("列表中所有元素:"+list);//输出:[Will, 100, true, Lucy]
		System.out.println("元素数量:"+list.size());//4
		System.out.println("第一个元素:"+list.get(0));//Will
		
		// 修改操作:把索引为2的元素,替换为wolfcode
		list.set(2, "wolfcode");
		System.out.println("修改后:"+list);//输出:[Will, 100, wolfcode, Lucy]
		
		// 删除操作:删除索引为1的元素
		list.remove(1);
		System.out.println("删除后:"+list);//输出:[Will, wolfcode, Lucy]
	}
}

2.2、LinkedList类

LinkedList类,底层采用链表算法,实现了链表,队列,栈的数据结构。无论是链表还是队列主要操作的都是头和尾的元素,因此在LinkedList类中除了List接口的方法,还有很多操作头尾的方法。

LinkedList常用方法的API:

在这里插入图片描述
其余方法:

  • boolean offerFirst(Object e) 在此列表的开头插入指定的元素。

  • boolean offerLast(Object e) 在此列表末尾插入指定的元素。

  • Object peekFirst() 获取但不移除此列表的第一个元素;如果此列表为空,则返回 null。

  • Object peekLast() 获取但不移除此列表的最后一个元素;如果此列表为空,则返回 null。

  • Object pollFirst() 获取并移除此列表的第一个元素;如果此列表为空,则返回 null。

  • Object pollLast() 获取并移除此列表的最后一个元素;如果此列表为空,则返回 null。

  • void push(Object e) 将元素推入此列表所表示的栈。

  • Object pop() 从此列表所表示的栈处弹出一个元素。

  • Object peek() 获取但不移除此列表的头(第一个元素)。

LinkedList之所以有这么多方法,是因为自身实现了多种数据结构,而不同的数据结构的操作方法名称不同,在开发中LinkedList使用不是很多,知道存储特点就可以了。

public class LinkedListDemo {
	public static void main(String[] args) {
		LinkedList list = new LinkedList();
		//添加元素
		list.addFirst("A");
		list.addFirst("B");
		System.out.println(list);
		list.addFirst("C");
		System.out.println(list);
		list.addLast("D");
		System.out.println(list);
		//获取元素
		System.out.println("获取第一个元素:" + list.getFirst());//C
		System.out.println("获取最后一个元素:" + list.getLast());//D
		//删除元素
		list.removeFirst();
		System.out.println("删除第一个元素后:" + list);//[B, A, D]
		list.removeLast();
		System.out.println("删除最后一个元素后:" + list);//[B, A]
	}
}

运行结果

[B, A]
[C, B, A]
[C, B, A, D]
获取第一个元素:C
获取最后一个元素:D
删除第一个元素后:[B, A, D]
删除最后一个元素后:[B, A]

三、泛型

3.1、什么是泛型

其实就是一种类型参数主要用于某个类或接口中数据类型不确定时,可以使用一个标识符来表示未知的数据类型,然后在使用该类或接口时指定该未知类型的真实类型。

泛型可用到的接口、类、方法中,将数据类型作为参数传递,其实更像是一个数据类型模板。

如果不使用泛型,从容器中获取出元素,需要做类型强转,也不能限制容器只能存储相同类型的元素。

List list = new ArrayList();
list.add("A");
list.add("B");
String ele = (String) list.get(0);

3.2、自定义和使用泛型

定义泛型:使用一个标识符,比如T在类中表示一种未知的数据类型。

使用泛型:一般在创建对象时,给未知的类型设置一个具体的类型,当没有指定泛型时,默认类型为Object类型。

需求:定义一个类Point,x和y表示横纵坐标,分别使用String、Integer、Double表示坐标类型。

如果没有泛型需要设计三个类,如下:

在这里插入图片描述

定义泛型:

在类上声明使用符号T,表示未知的类型

//在类上声明使用符号T,表示未知的类型
public class Point<T> {
	private T x;
	private T y;
     //省略getter/setter
}

使用泛型:

//没有使用泛型,默认类型是Object
Point p1 = new Point();
Object x1 = p1.getX();
//使用String作为泛型类型
Point<String> p2 = new Point<String>();
String x2 = p2.getX();
//使用Integer作为泛型类型
Point<Integer> p3 = new Point<Integer>();
Integer x3 = p3.getX();

在这里插入图片描述

注意:这里仅仅是演示泛型类是怎么回事,并不是要求定义类都要使用泛型。

3.3、在集合中使用泛型

拿List接口和ArrayList类举例。

class ArrayList<E>{ 
	public boolean add(E e){
	
	}
	public E get(int index){  
	
	}
}

此时的E也仅仅是一个占位符,表示元素(Element)的类型,那么当使用容器时给出泛型就表示该容器只能存储某种类型的数据。

//只能存储String类型的集合
List<String> list1 = new ArrayList<String>();
list1.add("A");
list1.add("B");

//只能存储Integer类型的集合
List<Integer> list2 = new ArrayList<Integer>();
list2.add(11);
list2.add(22);

因为前后两个泛型类型相同(也必须相同),泛型类型推断:

List<String> list1 = new ArrayList<String>();
// 可以简写为
List<String> list1 = new ArrayList<>();

通过反编译工具,会发现泛型其实是语法糖,也就是说编译之后,泛型就不存在了。

注意:泛型必须是引用类型,不能是基本数据类型(错误如下):

List<int> list = new ArrayList<int>();//编译错误

泛型不存在继承的关系(错误如下):

List<Object> list = new ArrayList<String>();	//错误的

四、集合遍历

4.1、集合元素遍历

对集合中的每一个元素获取出来。

List<String> list = new ArrayList<>();
list.add("西施");
list.add("王昭君");
list.add("貂蝉");
list.add("杨玉环");

使用for遍历

for (int index = 0; index < list.size(); index++) {
	String ele = list.get(index);
	System.out.println(ele);
}

使用迭代器遍历

Iterator表示迭代器对象,迭代器中拥有一个指针,默认指向第一个元素之前,

  • boolean hasNext():判断指针后是否存在下一个元素

  • Object next():获取指针位置下一个元素,获取后指针向后移动一位

Iterator<String> it = list.iterator();
while(it.hasNext()) {
	String ele = it.next();
	System.out.println(ele);
}

操作原理如下图:

在这里插入图片描述
注意: 此时,就不能使用两次迭代器,因为第一个迭代器就已经将指针指向了最后一个元素,这样的话第二个迭代器开始的指针一直都是最后一个

使用for-each遍历(推荐使用)

for (String ele : list) {
	System.out.println(ele);
}

通过反编译工具会发现,for-each操作集合时,其实底层依然是Iterator,我们直接使用for-each即可。

4.2、并发修改异常

需求:在迭代集合时删除集合元素,比如删除王昭君。

List<String> list = new ArrayList<>();
list.add("西施");
list.add("王昭君");
list.add("貂蝉");
list.add("杨玉环");

System.out.println(list);
for (String ele : list) {
	if("王昭君".equals(ele)) {
		list.remove(ele);
	}
}
System.out.println(list);

此时报错java.util.ConcurrentModificationException,并发修改异常。

造成该错误的原因是,不允许在迭代过程中改变集合的长度(不能删除和增加)。如果要在迭代过程中删除元素,就不能使用集合的remove方法,只能使用迭代器的remove方法,此时只能使用迭代器来操作,不能使用foreach。

List<String> list = new ArrayList<>();
list.add("西施");
list.add("王昭君");
list.add("貂蝉");
list.add("杨玉环");
System.out.println(list);
//获取迭代器对象
Iterator<String> it = list.iterator();
while(it.hasNext()) {
	String ele = it.next();
	if("王昭君".equals(ele)) {
		it.remove();
	}
}
System.out.println(list);

五、Set接口

​ Set是Collection子接口,Set接口定义了一种规范,也就是该容器不记录元素的添加顺序,也不允许元素重复,那么Set接口的实现类都遵循这一种规范。

Set集合存储特点:

  • 不允许元素重复

  • 不会记录元素的添加先后顺序

    Set只包含从Collection继承的方法,不过Set无法记住添加的顺序,不允许包含重复的元素。当试图添加两个相同元素进Set集合,添加操作失败,add()方法返回false。

Set接口定义了一种规范,也就是该容器不记录元素的添加顺序,也不允许元素重复。

Set接口常用的实现类有:

  • HashSet类:底层采用哈希表实现,开发中使用对多的实现类,重点。
  • TreeSet类:底层采用红黑树实现,可以对集合中元素排序,使用不多。

HashSet和TreeSet的API都是一样的:
在这里插入图片描述

5.1、HashSet类

HashSet 是Set接口的实现类,底层数据结构是哈希表,集合容器不记录元素的添加顺序,也不允许元素重复。通常我们也说HashSet中的元素是无序的、唯一的

public class HashSetDemo {
	public static void main(String[] args) {
		Set<String> set = new HashSet<>();
		//添加操作:向列表中添加4个元素
		set.add("Will");
		set.add("wolf");
		set.add("code");
		set.add("Lucy");
        
		//查询操作:
		System.out.println("集合中所有元素:" + set);//[code, wolf, Will, Lucy]
		System.out.println("元素数量:" + set.size());//4
		System.out.println("是否存在某个元素:" + set.contains("code"));//true
		System.out.println("是否存在某个元素:" + set.contains("code2"));//false
        
		//删除操作:删除code元素
		set.remove("code");
		System.out.println("删除后:" + set);//[wolf, Will, Lucy]
		
        //使用for-each遍历
		for (String ele : set) {
			System.out.println(ele);
		}
        
		//使用迭代器遍历
		Iterator<String> it = set.iterator();
		while (it.hasNext()) {
			Object ele = it.next();
			System.out.println(ele);
		}
	}
}

哈希表工作原理:

在这里插入图片描述

HashSet底层采用哈希表实现,元素对象的hashCode值决定了在哈希表中的存储位置。

向HashSet添加元素时,经过以下过程:

step 1 : 首先计算要添加元素e的hashCode值,根据 y = k( hashCode ) 计算出元素在哈希表的存储位置,一般计算位置的函数会选择 y = hashcode % 9, 这个函数称为散列函数,可见,散列的位置和添加的位置不一致。

step 2:根据散列出来的位置查看哈希表该位置是否有无元素,如果没有元素,直接添加该元素即可。如果有元素,查看e元素和此位置所有元素的是否相等,如果相等,则不添加,如果不相同,在该位置连接e元素即可。

在哈希表中元素对象的hashCode和equals方法的很重要。

每一个存储到哈希表中的对象,都得覆盖hashCode和equals方法用来判断是否是同一个对象,一般的,根据对象的字段数据比较来判断,通常情况下equals为true的时候hashCode也应该相等。

5.2、TreeSet类

TreeSet类底层才有红黑树算法,会对存储的元素对象默认使用自然排序(从小到大)。

在这里插入图片描述

注意:必须保证TreeSet集合中的元素对象是相同的数据类型,否则报错。

public class TreeSetDemo{
	public static void main(String[] args) {
		Set<String> set = new TreeSet<>();
		set.add("wolf");
		set.add("will");
		set.add("sfef");
		set.add("allen");
		System.out.println(set);// [allen, sfef, will, wolf]
	}
}

第一步:插入第一个节点,无须比较,直接作为根节点。
在这里插入图片描述

第二步:插入第二个节点,和wolf节点做比较,较小,往左移动。

在这里插入图片描述

第三步:插入第三个节点,和wolf节点比较,较小,左移,再和will节点比较,较小,再左移。

在这里插入图片描述

第四步:由于TreeSet是平衡二叉树,如果树不平衡,会对节点进行调整。

在这里插入图片描述

第五步:插入第四个节点,此时和will节点作比较,较小,左移,再和stef节点做比较,较小,左移。

在这里插入图片描述

六、Map接口

6.1、认识Map

Map,翻译为映射,在数学中的解释为:

设A、B是两个非空集合,如果存在一个法则f,使得A中的每个元素a,按法则f,在B中有唯一确定的元素b与之对应,则称f为从A到B的映射,记作f:A→B。

在这里插入图片描述

也就是说映射表示两个集合之间各自元素的一种“对应”的关系,在面向对象中我们使用Map来封装和表示这种关系。

在这里插入图片描述

​ 从定义和结构图上,可以看出Map并不是集合,而表示两个集合之间的一种关系,故Map没有实现Collection接口。

​ 在Map中,要求A集合中的每一个元素都可以在B集合中找到唯一的一个值与之对应。这句话可以解读为一个A集合元素只能对应一个B集合元素,也就说A集合中的元素是不允许重复的,B集合中的元素可以重复,也可不重复。那么不难推断出A集合应该是一个Set集合,B集合应该是List集合。

在这里插入图片描述

我们把A集合中的元素称之为key,把B集合中的元素称之为value。

在这里插入图片描述

其实能看出一个Map其实就有多个key-value(键值对)组成的,每一个键值对我们使用Entry表示。

在这里插入图片描述

不难发现,一个Map结构也可以理解为是Entry的集合,即Set。

在这里插入图片描述

一般的,我们依然习惯把Map称之为集合,不过要区分下,Set和List是单元素集合,Map是双元素集合。

  • 单元素集合:每次只能存储一个元素,比如Set和List。

  • 双元素集合:每次需要存储两个元素(一个key和一个value),比如Map。

注意:

  • Map接口并没有继承于Collection接口也没有继承于Iterable接口,所以不能直接对Map使用for-each操作。

  • 如果不能理解Map的结构,就直接记住Map每次需要存储两个值,一个是key,一个是value,其中value表示存储的数据,而key就是这一个value的名字。

6.2、Map常用的API

添加操作

  • boolean put(Object key,Object value):存储一个键值对到Map中

  • boolean putAll(Map m):把m中的所有键值对添加到当前Map中

删除操作

  • Object remove(Object key):从Map中删除指定key的键值对,并返回被删除key对应的value

修改操作

  • 无专门的方法,可以调用put方法,存储相同key,不同value的键值对,可以覆盖原来的。

查询操作

  • int size():返回当前Map中键值对个数

  • boolean isEmpty():判断当前Map中键值对个数是否为0.

  • Object get(Object key):返回Map中指定key对应的value值,如果不存在该key,返回null

  • boolean containsKey(Object key):判断Map中是否包含指定key

  • boolean containsValue(Object value):判断Map中是否包含指定value

  • Set keySet():返回Map中所有key所组成的Set集合

  • Collection values():返回Map中所有value所组成的Collection集合

  • Set entrySet():返回Map中所有键值对所组成的Set集合

注意,标红的是重度使用的方法。

在这里插入图片描述

6.3、HashSet

HashMap底层基于哈希表算法,Map中存储的key对象的hashCode值决定了在哈希表中的存储位置,因为Map中的key是Set,所以不能保证添加的先后顺序,也不允许重复。

HashMap key的底层数据结构是哈希表

案例: 统计一个字符串中每个字符出现次数

public class HashMapDemo2{
	public static void main(String[] args) {
		String str = "ABCDEFABCDEABCDABCABA";
		//把字符串转换为char数组
		char[] charArray = str.toCharArray();
		//Map的key存储字符,value存储出现的次数
		Map<Character, Integer> map = new HashMap<>();
		//迭代每一个字符
		for (char ch : charArray) {
			//判断Map中是否已经存储该字符
			if (map.containsKey(ch)) {
				Integer count = map.get(ch);
				//如果已经存储该字符,则把出现次数加上1
				map.put(ch, count+1);
			}else {
				//如果没有存储该字符,则把设置次数为1
				map.put(ch, 1);
			}
		}
		System.out.println(map); 

	}
}

6.4、TreeMap

TreeMap key底层基于红黑树算法,因为Map中的key是Set,所以不能保证添加的先后顺序,也不允许重复,但是Map中存储的key会默认使用自然排序(从小到大),和TreeSet一样,除了可以使用自然排序也可以自定义排序。

需求:测试HashMap和TreeMap中key的顺序

public class App {
	public static void main(String[] args) {
		Map<String, String> map = new HashMap<>();
		map.put("girl4", "杨玉环");
		map.put("girl2", "王昭君");
		map.put("key1", "西施");
		map.put("key3", "貂蝉");
		System.out.println(map);
		//-------------------------------------------
		map = new TreeMap<>(map);
		System.out.println(map);
	}
}

输出结果:

{key1=西施, girl4=杨玉环, key3=貂蝉, girl2=王昭君}
{girl2=王昭君, girl4=杨玉环, key1=西施, key3=貂蝉}
  • 5
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java入门经典是一本非常经典的Java编程入门教材,它以广大Java初学者为目标读者,通过简明易懂的语言和丰富的示例代码,帮助读者逐步掌握Java编程的基础知识和技能。 该书首先介绍了Java的起源和发展历程,让读者了解Java的背景和优势。然后详细介绍了Java的基本语法、数据类型、控制结构、运算符等基础知识,让读者掌握编写简单的Java程序的能力。 随后,该书深入介绍了Java的面向对象编程思想,包括类、对象、继承、封装、多态等概念,让读者理解面向对象编程的重要性和优势,并能够使用Java创建和使用自定义的类和对象。 此外,该书还介绍了Java的常用工具和库,如Java集合框架、输入输出流、异常处理等,帮助读者掌握更加复杂和实用的Java编程技巧。 最后,该书提供了一些项目实战和案例分析,通过实际的示例和练习,让读者应用所学知识解决实际问题,并提高自己的编程能力。 总之,Java入门经典PDF是一本非常值得推荐的Java编程入门教材,无论是想要学习Java基础知识的初学者,还是想要进一步提升Java编程能力的中级开发者,都能从中受益匪浅。通过学习该书,读者能够系统地掌握Java编程语言,为今后进一步学习Java开发打下坚实的基础。 ### 回答2: 《Java入门经典PDF》是一本经典的Java编程入门教材,内容涵盖了Java程序设计的基础知识和常用技巧。本书适合想要学习Java编程的初学者。 首先,该书详细介绍了Java语言的基本语法和特性,包括数据类型、控制流语句、循环结构等基础知识。通过学习这些基础内容,读者可以了解Java编程的基本原理和操作方法。 其次,本书还介绍了如何使用Java进行面向对象的编程。面向对象是Java编程的核心概念,也是Java语言的优势之一。通过学习本书,读者可以了解面向对象的基本概念和原则,并学会如何在Java中应用面向对象的思想进行程序设计。 此外,本书还会介绍Java编程中常用的工具和技巧,如Java开发环境的搭建、调试技巧、常用的类库等。这些工具和技巧可以帮助读者提高编程效率和代码质量,让初学者能够更快地进行Java编程。 总之,《Java入门经典PDF》是一本良好的Java编程入门教材,通过系统地学习本书,读者可以掌握Java编程的基础知识和技巧,为进一步学习和应用Java打下坚实的基础。无论是想要学习编程的初学者,还是想要进一步提高Java编程技能的读者,本书都是一本值得推荐的入门教材。 ### 回答3: "Java入门经典pdf"是一本经典的Java入门教程,适合初学者学习和掌握Java编程语言。这本书全面介绍了Java的基础知识,包括语法、数据类型、变量、运算符、控制流等。读者可以通过阅读这本书了解Java的基本概念和原理,学会编写简单的Java程序。 这本书的特点是由浅入深,从简单到复杂,结构清晰。初学者可以通过按部就班地阅读,逐渐增加对Java的理解和掌握。每个章节都有练习题和案例,读者可以通过实践巩固所学知识。 同时,这本书还介绍了Java的面向对象编程思想和常用的类和方法。读者可以学习如何定义类、创建对象、封装、继承和多态等概念。这些知识对于Java程序的设计和开发都非常重要。 除了基础知识,这本书还介绍了Java的常用开发工具和环境,包括Eclipse和IntelliJ IDEA等。读者可以学习如何使用这些工具来编辑、调试和运行Java程序。 总之,"Java入门经典pdf"是一本经典的Java入门教程,内容全面、易于理解。对于想要学习Java编程语言的初学者来说,这本书是一个很好的选择。它可以帮助读者建立扎实的Java基础,为以后深入学习和应用Java打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值