集合框架-day10

集合框架-day02

今日学习内容:

  • 集合元素迭代操作
  • Set接口和实现类
  • Map接口和实现类
  • 集合的工具类

今日学习目标:

  • 掌握使用for循环对集合做迭代
  • 掌握使用for-each对集合做迭代
  • 掌握使用Iterator对集合做迭代
  • 掌握Set接口存储特点
  • 掌握HashSet类常用方法
  • 了解Comparable和Comparator接口
  • 掌握Map接口和存储特点
  • 掌握HashMap类常用方法
  • 掌握Arrays和Collections常用方法
27.6. 集合元素迭代(掌握)
27.6.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(元素类型 变量 : 数组/Iterable实例对象){
//TODO
}

这里Iterable实例对象表示,Iterable接口实现类对象,其实就是Collection,不包括Map。

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

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

27.6.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);
27.7.Set接口(掌握)

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

Set集合存储特点:

  • 不允许元素重复

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

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

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

Set接口常用的实现类有:

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

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

​ 当往HashSet集合中添加新的元素对象时,先回判断该对象和集合对象中的hashCode值:

  • 不等: 直接把该新的对象存储到hashCode指定的位置。

  • 相等: 再继续判断新对象和集合对象中的equals做比较。

    • 若equals为true:则视为是同一个对象,则不保存。
    • 若equals为false:存储在之前对象同槽位的链表上,此时操作比较麻烦,不讨论。

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

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

    Eclipse可以根据对象哪些字段做比较而自动生成hashCode和equals方法。

需求1:操作Set接口常用方法

public class ArrayListDemo2 {
	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);
		}
	}
}

需求2:创建三个User对象,覆盖equals和hashCode方法,存储在HashSet中。

class User {
	private String name;
	private int age;

	public User(String name, int age) {
		this.name = name;
		this.age = age;
	}
	
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	//
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		User other = (User) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

	public String toString() {
		return "User [name=" + name + ", age=" + age + "]";
	}
}

其中equals和hashCode方法表示根据User对象的name和age来做对比,两个方法时IDE工具自动生成的,不要求掌握。

public class HashSetDemo2 {
	public static void main(String[] args) {
		Set<User> users = new HashSet<>();
		users.add(new User("will", 17));
		users.add(new User("lucy", 18));
		users.add(new User("stef", 19));
		users.add(new User("allen", 20));
		System.out.println(users);
	}
}

27.7.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("stef");
		set.add("allen");
		System.out.println(set);//[allen, sfef, will, wolf]
	}
}

第一步:插入第一个节点,无须比较,直接作为根节点。

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

27.7.3.Comparable接口(了解)

​ 缺省情况下,TreeSet中的元素会采用自然排序(从小到大),此时要求元素对象必须实现java.util.Comparable接口,大多数JDK自带的类都实现了该接口,比如八大包装类和String。

​ TreeSet会调用元素的compareTo方法来比较元素的大小关系,然后将集合元素按照升序排列。

public interface Comparable<T> {
 public int compareTo(T o);
}

比较规则,拿当前元素和另一个元素做比较:

  • this > o:返回正整数 1 ,优先级较高
  • this < o:返回负整数 -1 ,优先级较低
  • this == o: 返回 0 ,此时认为两个对象为同一个对象。

此时compareTo方法返回0,则认为两个对象是同一个对象,返回正数排前面,返回负数排后面。

​ 如果我们自定义一个类,需要存储到TreeSet中,此时我们需要让该类实现Comparable接口,并覆盖compareTo方法,在该方法编写比较规则。

需求:按照用户的年龄从小到大排序

class User implements java.lang.Comparable<User> {
	private String name;
	private int age;
	public User(String name, int age) {
		this.name = name;
		this.age = age;
	}
	//比较规则
	public int compareTo(User o) {
		if (this.age > o.age) {
			return 1;
		} else if (this.age < o.age) {
			return -1;
		}
		return 0;
	}
	public String toString() {
		return "User [name=" + name + ", age=" + age + "]";
	}
}

public class ComparableDemo{
	public static void main(String[] args) {
		Set<User> set = new TreeSet<>();
		set.add(new User("stef", 28));
		set.add(new User("will", 17));
		set.add(new User("allen", 15));
		set.add(new User("Lucy", 22));
		System.out.println(set);
	}
}

输出结果:

[User [name=allen, age=15], User [name=will, age=17], User [name=Lucy, age=22], User [name=stef, age=28]]

第一步:第一个节点进去,直接作为根节点,无需比较

在这里插入图片描述

第二步:第二个节点进去和第一个节点作比较,此时的this表示第二个节点,如果compareTo返回-1,则插入节点往左边走。

在这里插入图片描述

第三步:第三个节点进去和第一个节点做比较,返回-1,再和第二个节点做比较。

在这里插入图片描述

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

在这里插入图片描述

第五步:第四个节点进去先和will比较,再和stef比较。

在这里插入图片描述

27.7.4.Comparator接口(了解)

​ TreeSet除了默认支持自然排序外,还支持自定义排序,此时需要在构建TreeSet对象时传递java.util.Comparator接口的实现类对象,Comparator表示比较器,里面封装比较规则。

public interface Comparator<T> {
    int compare(T o1, T o2);
}

此时compare方法返回0,则认为两个对象是同一个对象,返回正数排前面,返回负数排后面。

需求:根据用户的名字长度排序,如果名字相同按照年龄排序。

class User {
	private String name;
	private int age;
	public User(String name, int age) {
		this.name = name;
		this.age = age;
	}
	public int getAge() {
		return age;
	}
	public String getName() {
		return name;
	}
	public String toString() {
		return "User [name=" + name + ", age=" + age + "]";
	}
}
public class App {
	public static void main(String[] args) {
		Set<User> set = new TreeSet<>(new NameLengthComparator());
		set.add(new User("James", 30));
		set.add(new User("Bryant", 22));
		set.add(new User("Allen", 28));
		set.add(new User("Will", 17));
		System.out.println(set);//
	}
}

class NameLengthComparator implements java.util.Comparator<User> {
	public int compare(User o1, User o2) {
		int ret = o1.getName().length() - o2.getName().length();
		if (ret > 0) {
			return 1;
		} else if (ret < 0) {
			return -1;
		} else {
			if (o1.getAge() > o2.getAge()) {
				return 1;
			} else if (o1.getAge() < o2.getAge()) {
				return -1;
			} else {
				return 0;
			}
		}
	}
}

输出结果:

[User [name=Will, age=17], User [name=Allen, age=28], User [name=James, age=30], User [name=Bryant, age=22]]

小结:HashSet做等值查询效率高,TreeSet做范围查询效率高,在开发中一般使用HashSet就可以了。

27.8.Map接口(重点)
27.8.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的名字。
27.8.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集合

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

27.8.3.HashMap(重点)

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

需求1:操作Map接口常用方法

public class HashMapDemo1{
	public static void main(String[] args) {
		Map<String, String> map = new HashMap<>();
		map.put("girl1", "西施");
		map.put("girl2", "王昭君");
		map.put("girl3", "貂蝉");
		map.put("girl4", "杨玉环");
		System.out.println("map中有多少键值对:"+map.size());
		System.out.println(map);
		System.out.println("是否包含key为girl1:"+map.containsKey("girl1"));
		System.out.println("是否包含value为貂蝉:"+map.containsValue("貂蝉"));
		//替换key为girl3的value值
		map.put("girl3", "小乔");
		System.out.println(map);
		//删除key为girl3的键值对
		map.remove("girl3");
		System.out.println(map);
	}
}

Map的迭代遍历:

//获取Map中所有的key
Set<String> keys = map.keySet();
System.out.println("Map中所有key:"+keys);
//获取Map中所有的value
Collection<String> values = map.values();
System.out.println("Map中所有value:"+values);
//获取Map中所有的key-value(键值对)
Set<Entry<String, String>> entrys = map.entrySet();
for (Entry<String, String> entry : entrys) {
    String key = entry.getKey();
    String value = entry.getValue();
    System.out.println(key+"->"+value);
}

需求2:统计一个字符串中每个字符出现次数

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); 

	}
}

27.8.4.TreeMap(了解)

​ TreeMap底层基于红黑树算法,因为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=貂蝉}

28. 集合框架工具类和方法

28.1.Arrays(掌握)

Arrays类是数组的工具类,其中有一个方法比较常用。

  • public static List asList(T… a):该方法可以把一个Object数组转换为List集合。
public class ArraysDemo{
	public static void main(String[] args) {
		//把Integer[]转换为List<Integer>
		List<Integer> list1 = Arrays.asList(1, 2, 3);
		System.out.println(list1);
		//把String[]转换为List<String>
		List<String> list2 = Arrays.asList("A", "B", "C");
		System.out.println(list2);
	}
}

注意:通过Arrays.asList方法得到的List对象的长度是固定的,不能增,也不能减。

28.2.Collections(了解)

Collections是集合的工具类,封装了Set、List、Map操作的工具方法,比如拷贝、排序、搜索、比较大小等。

28.3. 斗地主发牌案例(了解)

​ 按照斗地主游戏的规则,模拟对54张扑克牌的洗牌和发牌以及对手中牌排序操作。

​ 具体规则:

  • 将54张不同花色的扑克牌打乱(♠♣♥♦☻)
  • 三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌
  • 查看三人各自手中的牌(按照牌的大小排序)、底牌
  • 手中扑克牌从大到小的摆放顺序:大王、小王、2、A、K、Q、J、10、9、8、7、6、5、4、3
public class App {
	public static void main(String[] args) {
		//花色♠♣♥♦
		List<String> colors = new ArrayList<>();
		colors.add("♠");
		colors.add("♣");
		colors.add("♥");
		colors.add("♦");

		//数字345678910JQKA2
		List<String> numbers = new ArrayList<>();
		Collections.addAll(numbers,"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2");

		//大小王: 大☻ 小☻
		//定义Map,用数字来记录每一张牌
		Map<Integer, String> map = new HashMap<>();
		Integer squence = 0;//牌的顺序
		//加入其它牌
		for (String number : numbers) {
			for (String color : colors) {
				//一副牌54张,序号范围在[0,51]之间
				if (squence <= 51) {
					map.put(squence, number + "(" + color + ")");
					squence++;
				}
			}
		}
		map.put(squence, "小☻");
		squence++;
		map.put(squence, "大☻");
		squence++;
		//使用0~53来记录每一张牌
		List<Integer> cards = new ArrayList<>(map.keySet());
		Collections.shuffle(cards);
		System.out.println(cards);

		//三个玩家和底牌
		List<Integer> player1 = new ArrayList<Integer>();
		List<Integer> player2 = new ArrayList<Integer>();
		List<Integer> player3 = new ArrayList<Integer>();
		List<Integer> end = new ArrayList<Integer>();

		//发牌
		for (int index = 0; index < cards.size(); index++) {
			if (index < 51) {
				int mod = index % 3;//index除以3的余数
				int card = cards.get(index);
				if (mod == 1) {
					player1.add(card);
				} else if (mod == 2) {
					player2.add(card);
				} else {
					player3.add(card);
				}
			} else {
				end.add(cards.get(index));
			}
		}

		System.out.println(player1);
		System.out.println(player2);
		System.out.println(player3);
		System.out.println(end);
		//排序
		Collections.sort(player1);
		Collections.sort(player2);
		Collections.sort(player3);
		Collections.sort(end);

		//各自手上的牌
		List<String> player1Cards = new ArrayList<>();
		List<String> player2Cards = new ArrayList<>();
		List<String> player3Cards = new ArrayList<>();
		List<String> endCards = new ArrayList<>();

		for (Integer key : player1) {
			player1Cards.add(map.get(key));
		}
		for (Integer key : player2) {
			player2Cards.add(map.get(key));
		}
		for (Integer key : player3) {
			player3Cards.add(map.get(key));
		}
		for (Integer key : end) {
			endCards.add(map.get(key));
		}

		//看牌
		System.out.println("玩家1:" + player1Cards);
		System.out.println("玩家2:" + player2Cards);
		System.out.println("玩家3:" + player3Cards);
		System.out.println("底牌  :" + endCards);
	}
}

28.4. 集合框架小结

List、Set、Map选用

一般的在存储元素时候,是否需要给元素起一个名字:

  • 需要:此时使用Map。
  • 不需:存储的元素使用需要保证先后添加的顺序
    • 需要:此时使用List
    • 不需:此时使用Set(如果需要保证集合元素不重复,也选用Set)

学习优势:

1.包含java前后端从 0 ->1 全过程教学, 内容全面, 知识点不遗漏, 学完即可参加实际工作.
2.课程为目前项目开发常用的技术知识,向用人单位对标,学以致用。那些脱离实际,废弃不用的,太前沿的框架技术前期不建议学。
3.一起学习,打卡,一起交流,希望能营造一个和线下一样的学习环境。

需要进微信学习交流群, 或者想领取代码,文档,全套视频的同学请+v:lmm99964

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值