集合 集合的关注点 1.是否允许为空
2.是否允许重复数据
3.是否有序(有序的意思是读取数据的顺序和存放数据的顺序是否一致)
4.是否线程安全
Collection集合 集合概述 在前面基础班我们已经学习过并使用过集合ArrayList<E>
,那么集合到底是什么呢?
集合 :集合是java中提供的一种容器,可以用来存储多个引用数据类型的数据。
集合和数组既然都是容器,它们有什么区别呢?
数组的长度是固定的。集合的长度是可变的。
集合存储的都是引用数据类型。如果想存储基本类型数据需要存储对应的包装类类型。
单列集合常用类的继承体系 Collection:是单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是
java.util.List:
List的特点是元素有序、元素可重复 ;
List
接口的主要实现类有java.util.ArrayList
和java.util.LinkedList
,
java.util.Set:
Set的特点是元素不可重复 。
Set
接口的主要实现类有java.util.HashSet
和java.util.LinkedHashSet
,java.util.TreeSet
。
为了便于初学者进行系统地学习,接下来通过一张图来描述集合常用类的继承体系
注意 :上面这张图只是我们常用的集合有这些,不是说就只有这些集合。
1 2 3 4 5 6 7 8 9 10 单列集合常用类的继承体系: Collection集合:接口,是所有单列集合的顶层父接口,该集合中的方法可以被所有单列集合共享 List集合: 接口,元素可重复,元素有索引,元素存取有序 ArrayList集合: 实现类,查询快,增删慢 LinkedList集合: 实现类,查询慢,增删快 Set集合: 接口, 元素不可重复(唯一),元素无索引 HashSet集合: 实现类,元素存取无序 LinkedHashSet集合:实现类,元素存取有序 TreeSet集合:实现类,可以对集合中的元素进行排序
Collection 常用功能 Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:
public boolean add(E e)
: 把给定的对象添加到当前集合中 。
public void clear()
:清空集合中所有的元素。
public boolean remove(E e)
: 把给定的对象在当前集合中删除。
public boolean contains(Object obj)
: 判断当前集合中是否包含给定的对象。
public boolean isEmpty()
: 判断当前集合是否为空。
public int size()
: 返回集合中元素的个数。
public Object[] toArray()
: 把集合中的元素,存储到数组中
tips: 有关Collection中的方法可不止上面这些,其他方法可以自行查看API学习。
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class Test { public static void main (String[] args) { Collection<String> col = new ArrayList <>(); col.add("范冰冰" ); col.add("李冰冰" ); col.add("林心如" ); col.add("赵薇" ); System.out.println("col集合:" +col); col.remove("李冰冰" ); System.out.println("col集合:" +col); boolean res1 = col.contains("李冰冰" ); System.out.println("res1:" +res1); boolean res2 = col.contains("林心如" ); System.out.println("res2:" +res2); boolean res3 = col.isEmpty(); System.out.println("res3:" +res3); System.out.println("集合中元素的个数:" +col.size()); Object[] arr = col.toArray(); System.out.println(Arrays.toString(arr)); } }
Iterator迭代器 为什么用迭代器? set集合没有索引,所以不能通过普通for循环遍历拿到数据,所以对于单列集合通用的遍历方式:迭代器
Iterator接口 在程序开发中,经常需要遍历单列集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator
。
迭代的概念 迭代 :即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续再判断,如果还有就再取出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
获取迭代器对象 Collection集合提供了一个获取迭代器的方法:
public Iterator iterator()
: 获取集合对应的迭代器,用来遍历集合中的元素的。
Iterator接口的常用方法
public E next()
:返回迭代的下一个元素。
public boolean hasNext()
:如果仍有元素可以迭代,则返回 true。
案例演示 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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package com.nbchen.demo3_Iterator接口;import java.util.ArrayList;import java.util.Collection;import java.util.Iterator;public class Test { public static void main (String[] args) { Collection<String> col = new ArrayList <>(); col.add("范冰冰" ); col.add("李冰冰" ); col.add("林心如" ); col.add("赵薇" ); Iterator<String> it = col.iterator(); while (it.hasNext()){ String e = it.next(); System.out.println(e); } } }
迭代器的常见问题 常见问题一 在进行集合元素获取时,如果集合中已经没有元素可以迭代了,还继续使用迭代器的next方法,将会抛出java.util.NoSuchElementException没有集合元素异常。
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package com.nbchen.demo4_迭代器的常见问题;import java.util.ArrayList;import java.util.Collection;import java.util.Iterator;public class Test1 { public static void main (String[] args) { Collection<String> col = new ArrayList <>(); col.add("范冰冰" ); col.add("李冰冰" ); col.add("林心如" ); col.add("赵薇" ); Iterator<String> it = col.iterator(); while (it.hasNext()) { String e = it.next(); System.out.println(e); } System.out.println("====================================" ); Iterator<String> it2 = col.iterator(); while (it2.hasNext()) { System.out.println(it2.next()); } } }
解决办法: 如果还需要重新迭代,那么就重新获取一个新的迭代器对象进行操作
常见问题二 在进行集合元素迭代时,如果添加或移除集合中的元素 , 将无法继续迭代 , 将会抛出ConcurrentModificationException并发修改异常.
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package com.itheima.demo4_迭代器的常见问题;import java.util.ArrayList;import java.util.Collection;import java.util.Iterator;public class Test2 { public static void main (String[] args) { Collection<String> col = new ArrayList <>(); col.add("范冰冰" ); col.add("李冰冰" ); col.add("林心如" ); col.add("赵薇" ); Iterator<String> it = col.iterator(); while (it.hasNext()) { String e = it.next(); System.out.println(e); if (e.equals("李冰冰" )){ it.remove(); } } System.out.println("集合:" +col); } }
迭代器的实现原理 我们在之前案例已经完成了Iterator遍历集合的整个过程。当遍历集合时,首先通过调用t集合的iterator()方法获得迭代器对象,然后使用hashNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素。
Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素。在调用Iterator的next方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。
增强for 增强for循环(也称for each循环)是JDK1.5 以后出来的一个高级for循环,专门用来遍历数组和集合的。它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作,增删操作影响指针。
其实是语法糖,idea查看out反编译class文件,可以看到实际操作。数组的增强for底层是普通for集合的增强for递增是迭代器
格式:
1 2 3 for (元素的数据类型 变量 : Collection集合or数组){ }
它用于遍历Collection和数组。通常只进行遍历元素,不要在遍历的过程中对集合元素进行增删操作。
代码演示
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 package com.nbchen.demo5_增强for 循环;import java.util.ArrayList;import java.util.Collection;import java.util.Iterator;public class Test { public static void main (String[] args) { Collection<String> col = new ArrayList <>(); col.add("范冰冰" ); col.add("李冰冰" ); col.add("林心如" ); col.add("赵薇" ); for (String e : col) { System.out.println(e); } System.out.println("======================================" ); String[] arr = {"范冰冰" , "李冰冰" , "林心如" , "赵薇" }; for (String e : arr){ System.out.println(e); } System.out.println("======================================" ); for (String s : col) { System.out.println(s); } System.out.println("=======================================" ); for (String s : arr) { System.out.println(s); } System.out.println("=======================================" ); Iterator<String> it = col.iterator(); while (it.hasNext()) { String next = it.next(); System.out.println(next); } System.out.println("=======================================" ); } }
tips:
增强for循环必须有被遍历的目标,目标只能是Collection或者是数组;
增强for(迭代器)仅仅作为遍历操作出现,不能对集合进行增删元素操作,否则抛出ConcurrentModificationException并发修改异常
泛型 泛型的作用 集合不使用泛型的时候,存的时候什么类型都能存。但是取的时候就懵逼了。取出来啥也不是。
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 public class Test1 { public static void main (String[] args) { ArrayList list1 = new ArrayList (); list1.add("杨颖" ); list1.add("迪丽热巴" ); list1.add(100 ); list1.add(3.14 ); System.out.println(list1); for (Object obj : list1) { String name = (String)obj; System.out.println("姓名的长度:" +name.length()); } } }
使用泛型:使用泛型在编译期直接对类型作出了控制,只能存储泛型定义的数据
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 package com.nbchen.demo6_泛型的作用;import java.util.ArrayList;public class Test2 { public static void main (String[] args) { ArrayList<String> list1 = new ArrayList <>(); list1.add("杨颖" ); list1.add("迪丽热巴" ); System.out.println(list1); for (String s : list1) { System.out.println(s.length()); } } }
泛型 :定义的时候表示一种未知的数据类型,在使用的时候确定其具体的数据类型。
tips:泛型的作用是在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为Object类型。
定义和使用含有泛型的类 定义含有泛型的类 定义格式:
1 2 修饰符 class 类名<代表泛型的变量> { } 代表泛型的变量: 可以是任意字母 例如: T,E...
泛型在定义的时候不具体类型,使用的时候才具体类型。在使用的时候确定泛型的具体数据类型。
1 2 3 4 5 6 class ArrayList <E>{ public boolean add (E e) { } public E get (int index) { } .... }
确定泛型具体类型 在创建对象的时候确定泛型
例如,ArrayList<String> list = new ArrayList<String>();
此时,变量E的值就是String类型,那么我们的类型就可以理解为:
1 2 3 4 5 6 class ArrayList <String>{ public boolean add (String e) { } public String get (int index) { } ... }
课堂代码
1 2 3 4 5 6 7 8 9 10 11 12 package com.nbchen.demo7_定义和使用含有泛型的类;public class MyArrayList <E> { E e; public E method (E e) { return e; } }
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 29 30 package com.nbchen.demo7_定义和使用含有泛型的类;public class Test { public static void main (String[] args) { MyArrayList<String> list1 = new MyArrayList <>(); list1.e = "itheima" ; String res1 = list1.method("itcast" ); System.out.println("res1:" +res1); System.out.println("=======================================" ); MyArrayList<Integer> list2 = new MyArrayList <>(); list2.e = 100 ; Integer res2 = list2.method(10 ); System.out.println("res2:" +res2); } }
定义和使用含有泛型的方法 1 修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }
例如
1 2 3 4 5 6 public class Test { public static <T> T method1 (T t) { return t; } }
确定泛型具体类型
调用方法时,确定泛型的类型
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 public class Test { public static void main (String[] args) { Integer i1 = method1(100 ); System.out.println(i1); System.out.println("============================" ); String s = method1("itheima" ); System.out.println(s); } public static <T> T method1 (T t) { return t; } }
定义和使用含有泛型的接口 1 修饰符 interface接口名<代表泛型的变量> { }
例如
1 2 3 4 5 6 7 8 public interface IA <E> { public abstract void method1 (E e) ; public default E method2 (E e) { return e; } }
确定泛型具体类型
使用格式:
1、定义实现类时确定泛型的类型
例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.nbchen.demo09_定义和使用含有泛型的接口;public class Imp1 implements IA <String> { @Override public void method1 (String s) { } @Override public String method2 (String s) { return null ; } }
此时,泛型E的值就是String类型。
2、始终不确定泛型的类型,直到创建对象时,确定泛型的类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.nbchen.demo09_定义和使用含有泛型的接口;public class Imp2 <E> implements IA <E> { @Override public void method1 (E e) { System.out.println("实现类 method1" ); } @Override public E method2 (E e) { return e; } }
确定泛型:
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 29 30 31 32 33 34 35 36 37 38 39 package com.nbchen.demo09_定义和使用含有泛型的接口;public class Test { public static void main (String[] args) { Imp2<String> imp1 = new Imp2 <>(); imp1.method1("itheima" ); String s1 = imp1.method2("itcast" ); System.out.println(s1); System.out.println("==========================" ); Imp2<Integer> imp2 = new Imp2 <>(); imp2.method1(100 ); Integer i = imp2.method2(100 ); System.out.println(i); } }
1 2 3 4 5 6 泛型:定义的时候表示一种未知的数据类型,在使用的时候确定其具体的数据类型。 使用含有泛型的类: 创建该类对象的时候,指定泛型的具体数据类型 使用含有方向的方法: 调用该方法的时候,确定泛型的具体数据类型 使用含有泛型的接口: 1. 创建实现类实现接口的时候,指定泛型的具体数据类型 2. 创建实现类实现接口的时候,不知道泛型的具体数据类型,而是创建实现类对象的时候指定泛型的具体数据类型
泛型通配符 通配符基本使用 泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。
此时只能接受数据,不能往该集合中存储数据。
例如:
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class Test { public static void main (String[] args) { ArrayList<Object> list1 = new ArrayList <>(); ArrayList<String> list2 = new ArrayList <>(); ArrayList<Integer> list3 = new ArrayList <>(); ArrayList<Number> list4 = new ArrayList <>(); list2.add("itheima" ); method1(list2); method2(list2); } public static void method1 (ArrayList list) { Object obj = list.get(0 ); list.add("jack" ); System.out.println("obj:" +obj); System.out.println("list:" +list); } public static void method2 (ArrayList<?> list) { Object obj = list.get(0 ); System.out.println("obj:" +obj); System.out.println("list:" +list); } }
通配符高级使用:受限泛型 之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定一个泛型的上限 和下限 。
泛型的上限 :
格式 : 类型名称 <? extends 类 > 对象名称
意义 : 只能接收该类型及其子类
泛型的下限 :
格式 : 类型名称 <? super 类 > 对象名称
意义 : 只能接收该类型及其父类型
比如:现已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public class Test { public static void main (String[] args) { ArrayList<Object> list1 = new ArrayList <>(); ArrayList<String> list2 = new ArrayList <>(); ArrayList<Integer> list3 = new ArrayList <>(); ArrayList<Number> list4 = new ArrayList <>(); method1(list1); method1(list2); method1(list3); method1(list4); method2(list3); method2(list4); method3(list1); method3(list3); method3(list4); } public static void method2 (ArrayList<? extends Number> list) { } public static void method3 (ArrayList<? super Integer> list) { } public static void method1 (ArrayList<?> list) { } public static void method (ArrayList list) { } }
List接口 List接口介绍 我们掌握了Collection接口的使用后,再来看看Collection接口中的子类,他们都具备那些特性呢?
接下来,我们一起学习Collection中的常用几个子类(java.util.List
集合、java.util.Set
集合)。
List接口的概述 java.util.List
接口继承自Collection
接口,是单列集合的一个重要分支,习惯性地会将实现了List
接口的对象称为List集合。
List接口特点
它是一个元素存取有序 的集合。例如,存元素的顺序是11、22、33。那么集合中,元素的存储就是按照11、22、33的顺序完成的)。
它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
集合中可以有重复 的元素。
tips:我们在基础班的时候已经学习过List接口的子类java.util.ArrayList类,该类中的方法都是来自List中定义。
List接口中常用方法 List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法
List接口新增常用方法 List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法,如下:
public void add(int index, E element)
: 将指定的元素,添加到该集合中的指定位置上。
public E get(int index)
:返回集合中指定位置的元素。
public E remove(int index)
: 移除列表中指定位置的元素, 返回的是被移除的元素。
public E set(int index, E element)
:用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
List集合特有的方法都是跟索引相关,我们在基础班都学习过。
List接口新增常用方法的使用 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 29 30 31 32 33 34 35 36 37 public class Test { public static void main (String[] args) { List<String> list = new ArrayList <>(); list.add("苍老师" ); list.add("波老师" ); list.add("吉泽老师" ); System.out.println(list); list.add(1 , "小泽老师" ); System.out.println(list); System.out.println("索引为1的元素:" +list.get(1 )); String removeE = list.remove(1 ); System.out.println("被删除的元素:" +removeE); System.out.println(list); String setE = list.set(0 , "大桥老师" ); System.out.println("被替换的元素:" +setE); System.out.println(list); } }
List的子类 ArrayList集合 java.util.ArrayList
集合数据存储的结构是数组结构。元素增删慢,查找快 ,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList
是最常用的集合。
许多程序员开发时非常随意地使用ArrayList完成任何需求,并不严谨,这种用法是不提倡的。
原理:底层实现是Object[]
数组
1 Transient Object[] elementData;
只能存储Object类型或者他的子类,引用类型只能存引用类型(除了包装类自动拆箱)。所以,ArrayList存不了int基本类型。ArrayList<int>
是错的
1 System.arraycopy(es,i+1,es,i,newSize-i);
将数组后面的数组往前移动一位,进行覆盖
ArrayList源码:
1.为什么ArrayList源码中要用成员内部类表示迭代器的实现子类?从调用的时候封装具体内容方面理解这种定义的好处吗? 内部类,因为只在这个类里面用,定义在外面别的类也用不到,就更private封装类似。
调用: 外部类名.内部类名 对象名 = new 外部类型().new 内部类型();Collection<String> coll=newArrayList<>();
Iterator<String> it=coll.iterator();
2.为什么要复制给局部变量,直接操作有什么弊端?
1 Object[]elementData=ArrayList.this.elementData;
这里存的是地址值,直接用也是可以的
LinkedList集合 java.util.LinkedList
集合数据存储的结构是链表结构。方便元素添加、删除的集合。查询慢,增删快 。
LinkedList是一个双向链表,那么双向链表是什么样子的呢,我们用个图了解下
实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。这些方法我们作为了解即可 :
public void addFirst(E e)
:将指定元素插入此列表的开头。
public void addLast(E e)
:将指定元素添加到此列表的结尾。
public E getFirst()
:返回此列表的第一个元素。
public E getLast()
:返回此列表的最后一个元素。
public E removeFirst()
:移除并返回此列表的第一个元素。
public E removeLast()
:移除并返回此列表的最后一个元素。
public E pop()
:从此列表所表示的堆栈处弹出一个元素。
public void push(E e)
:将元素推入此列表所表示的堆栈。
Pop(弹栈)和push(压栈)就是调用的removeFirst和addFirst
LinkedList是List的子类,List中的方法LinkedList都是可以使用,这里就不做详细介绍,我们只需要了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 public class Test { public static void main (String[] args) { LinkedList<String> list = new LinkedList <>(); list.add("蔡徐坤" ); list.add("鹿晗" ); list.add("吴亦凡" ); System.out.println(list); list.addFirst("罗志祥" ); list.addLast("陈冠希" ); System.out.println(list); String firstE = list.getFirst(); String lastE = list.getLast(); System.out.println("第一个元素是:" +firstE); System.out.println("最后一个元素是:" +lastE); String removeFirst = list.removeFirst(); String removeLast = list.removeLast(); System.out.println("被删除的第一个元素是:" +removeFirst); System.out.println("被删除的最后一个元素是:" +removeLast); System.out.println(list); String popE = list.pop(); System.out.println("被删除的第一个元素是:" +popE); System.out.println(list); list.push("蔡徐坤" ); System.out.println(list); } }
集合综合案例 按照斗地主的规则,完成造牌洗牌发牌的动作。 具体规则:
使用54张牌打乱顺序,三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌。
分析
准备牌:
牌可以设计为一个ArrayList<String>
,每个字符串为一张牌。 每张牌由花色数字两部分组成,我们可以使用花色集合与数字集合嵌套迭代完成每张牌的组装。 牌由Collections类的shuffle方法进行随机排序。
发牌
将每个人以及底牌设计为ArrayList<String>
,将最后3张牌直接存放于底牌,剩余牌通过对3取模依次发牌。
看牌
直接打印每个集合。
实现 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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 import java.util.ArrayList;import java.util.Collections;public class Test { public static void main (String[] args) { ArrayList<String> pokerBox = new ArrayList <>(); ArrayList<String> numbers = new ArrayList <>(); ArrayList<String> colors = new ArrayList <>(); numbers.add("A" ); numbers.add("K" ); numbers.add("Q" ); numbers.add("J" ); for (int i = 2 ; i <= 10 ; i++) { numbers.add(i + "" ); } colors.add("♥" ); colors.add("♠" ); colors.add("♣" ); colors.add("♦" ); pokerBox.add("大王" ); pokerBox.add("小王" ); for (String number : numbers) { for (String color : colors) { String pai = color + number; pokerBox.add(pai); } } System.out.println(pokerBox); System.out.println(pokerBox.size()); Collections.shuffle(pokerBox); System.out.println("打乱顺序后:" + pokerBox); System.out.println("打乱顺序后:" + pokerBox.size()); ArrayList<String> play1 = new ArrayList <>(); ArrayList<String> play2 = new ArrayList <>(); ArrayList<String> play3 = new ArrayList <>(); ArrayList<String> diPai = new ArrayList <>(); for (int i = 0 ; i < pokerBox.size(); i++) { String pai = pokerBox.get(i); if (i >= 51 ) { diPai.add(pai); } else if (i % 3 == 0 ) { play1.add(pai); } else if (i % 3 == 1 ) { play2.add(pai); } else if (i % 3 == 2 ) { play3.add(pai); } } System.out.println("玩家1:" +play1+",牌数:" +play1.size()); System.out.println("玩家2:" +play2+",牌数:" +play2.size()); System.out.println("玩家3:" +play3+",牌数:" +play3.size()); System.out.println("底牌:" +diPai); } }
Collections类 Collections常用功能
java.utils.Collections
是集合工具类,用来对集合进行操作。
常用方法如下:
public static void shuffle(List<?> list)
:打乱集合顺序。
public static <T> void sort(List<T> list)
:将集合中元素按照默认规则排序。
public static <T> void sort(List<T> list,Comparator<? super T> )
:将集合中元素按照指定规则排序。
自定义规则:
1 2 3 4 5 6 7 8 1.对象必须实现Comparable接口:默认排序,较死板 2.排序时实现comparator比较器:compare(String o1,String o2); 升序:我-它 o1 < o2,返回负数,相等返回0,o1 > o2返回正数 降序: 它-我o1 < o2,返回正数,相等返回0,o1 > o2返回负数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Test1_shuffle { public static void main (String[] args) { List<Integer> list = new ArrayList <>(); list.add(300 ); list.add(100 ); list.add(200 ); list.add(500 ); list.add(400 ); System.out.println("打乱顺序之前的集合:" +list); Collections.shuffle(list); System.out.println("打乱顺序之后的集合:" +list); } }
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 public class Student implements Comparable <Student>{ int age; public Student (int age) { this .age = age; } @Override public String toString () { return "Student{" + "age=" + age + '}' ; } @Override public int compareTo (Student o) { return this .age - o.age; } } public class Test2_sort { public static void main (String[] args) { List<Integer> list = new ArrayList <>(); list.add(300 ); list.add(100 ); list.add(200 ); list.add(500 ); list.add(400 ); System.out.println("排序之前的集合:" +list); Collections.sort(list); System.out.println("排序之后的集合:" +list); System.out.println("=========================================" ); List<Student> list1 = new ArrayList <>(); Student stu1 = new Student (19 ); Student stu2 = new Student (18 ); Student stu3 = new Student (20 ); Student stu4 = new Student (17 ); list1.add(stu1); list1.add(stu2); list1.add(stu3); list1.add(stu4); System.out.println("排序之前的集合:" +list1); Collections.sort(list1); System.out.println("排序之后的集合:" +list1); } }
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 public class Test3_sort { public static void main (String[] args) { List<Integer> list = new ArrayList <>(); list.add(300 ); list.add(100 ); list.add(200 ); list.add(500 ); list.add(400 ); System.out.println("排序之前的集合:" + list); Collections.sort(list, new Comparator <Integer>() { @Override public int compare (Integer o1, Integer o2) { return o2 - o1; } }); System.out.println("排序之后的集合:" + list); Collections.sort(list, new Comparator <Integer>() { @Override public int compare (Integer o1, Integer o2) { return o1 - o2; } }); System.out.println("排序之后的集合:" + list); System.out.println("=======================================" ); List<Student> list1 = new ArrayList <>(); Student stu1 = new Student (19 ); Student stu2 = new Student (18 ); Student stu3 = new Student (20 ); Student stu4 = new Student (17 ); list1.add(stu1); list1.add(stu2); list1.add(stu3); list1.add(stu4); System.out.println("排序之前的集合:" + list1); Collections.sort(list1, new Comparator <Student>() { @Override public int compare (Student o1, Student o2) { return o2.age - o1.age; } }); System.out.println("排序之后的集合:" + list1); Collections.sort(list1, new Comparator <Student>() { @Override public int compare (Student o1, Student o2) { return o1.age - o2.age; } }); System.out.println("排序之后的集合:" + list1); } }
小结 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - public static void shuffle (List<?> list) :打乱集合顺序。 - public static <T> void sort (List<T> list) :将集合中元素按照默认规则排序。 默认规则: 事先写好的排序规则 在哪里写好排序规则?---->集合元素所属的类中写好排序规则(通过实现Comparable接口,重写compareTo(T o)方法写好排序规则) 排序规则: 前减后 升序 后减前 降序 前: this 后:参数 - public static <T> void sort (List<T> list,Comparator<? super T> com) :将集合中元素按照指定规则排序。 指定规则排序: 通过Comparator参数来指定 通过传入Comparator接口的匿名内部类,重写compare(T o1,T o2)方法,在该方法中指定排序规则 排序规则: 前减后 升序 后减前 降序 前: 第一个参数 后:第二个参数
可变参数 可变参数的使用 在JDK1.5 之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以对其简化.
格式:
1 修饰符 返回值类型 方法名(参数类型... 形参名){ }
代码演示:
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class Test1 { public static void main (String[] args) { method3(10 ,20 ,30 ,40 ,50 ); } public static void method3 (int ... nums) { for (int i = 0 ; i < nums.length; i++) { System.out.println(nums[i]); } } public static void method2 (int [] arr) { for (int i = 0 ; i < arr.length; i++) { System.out.println(arr[i]); } } public static void method1 (int num1,int num2,int num3,int num4,int num5) { } }
注意事项 1.一个方法只能有一个可变参数
2.如果方法中有多个参数,可变参数要放到最后。
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 29 30 31 32 33 package com.itheima.demo2_可变参数;public class Test2 { public static void main (String[] args) { method3("itheima" ,10 ,20 ); } public static void method3 (String str,int ... nums) { } }
应用场景: Collections 在Collections中也提供了添加一些元素方法:
public static <T> boolean addAll(Collection<T> c, T... elements)
:往集合中添加一些元素。
代码演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Test3 { public static void main (String[] args) { ArrayList<String> list = new ArrayList <>(); Collections.addAll(list,"2" ,"A" ,"K" ,"Q" ,"J" ,"10" ,"9" ,"8" ,"7" ,"6" ,"5" ,"4" ,"3" ); System.out.println(list); } }
Set接口 Set接口介绍 1 2 3 4 5 6 7 8 Set接口:也称Set集合,但凡是实现了Set接口的类都叫做Set集合 特点: 元素无索引,元素不可重复(唯一) HashSet集合: 实现类--元素存取无序 LinkedHashSet集合:实现类--元素存取有序 TreeSet集合:实现类--> 对元素进行排序 注意: 1. Set集合没有特殊的方法,都是使用Collection接口的方法 2. Set集合没有索引,所以遍历元素的方式就只有: 增强for 循环,或者迭代器
HashSet集合 java.util.HashSet
是Set
接口的一个实现类,它所存储的元素是不可重复 的,并且元素都是无序 的(即存取顺序不能保证不一致)。
底层实现是hashMap,采用的数据结构是哈希表结构,此结构特点:快
我们先来使用一下Set集合存储,看下现象,再进行原理的讲解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Test { public static void main (String[] args) { HashSet<String> set = new HashSet <>(); set.add("nba" ); set.add("cba" ); set.add("bac" ); set.add("abc" ); set.add("nba" ); System.out.println(set); } }
HashSet集合存储数据的结构(哈希表) 对象的哈希值 就是一个十进制的值,它就是一个对象的特征码(返回值int,-21亿~+21亿,哈希值有限,万物皆对象,hash值有可能重复!{通话和重地的hashCode一样})
Object的toString返回的地址值是hashCode(十进制)的十六进制表示形式。 Object,hashCode因为存的是真正的物理地址值,所以基本上不太可能重复,equals用==
比较。
每个对象的特征码应该与该对象的内容有关,父类的hashCode不满足的需求:重写hashCode和equals
哈希表底层结构 在JDK1.8 之前,哈希表底层采用数组+链表 实现,即使用数组处理冲突,同一hash值的链表都存储在一个数组里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
简单的来说,哈希表是由数组+链表+红黑树 (JDK1.8增加了红黑树部分)实现的,如下图所示。
HashSet保证元素唯一原理
hashCode+equals
hashCode不一样,对象肯定不一样 hashCode一样,equals不一样,对象不一样 hashCode一样,equals一样,对象一样 为啥不直接调用equals判断两个对象是否一样,而要先掉hashCode? 哈希加速,提高运算效率 这就是为什么用到set集合的时候必须重写hashCode和equals
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 -HashSet集合存储数据的结构---哈希表结构 哈希表结构: jdk8以前: 数组+链表 jdk8以后: 数组+链表+红黑树 链表元素个数没有超过8 : 数组+链表 链表元素个数超过8 : 数组+链表+红黑树 -HashSet集合保证元素唯一的原理--依赖hashCode()和equals()方法 1. 当存储元素的时候,就会调用该元素的hashCode()方法计算该元素的哈希值 2. 判断该哈希值对应的位置上,是否有元素: 3. 如果该哈希值对应的位置上,没有元素,就直接存储 4. 如果该哈希值对应的位置上,有元素,说明产生了哈希冲突 5. 产生了哈希冲突,就得调用该元素的equals方法,与该位置上的所有元素进行一一比较: 如果比较的时候,有任意一个元素与该元素相同,那么就不存储 如果比较完了,没有一个元素与该元素相同,那么就直接存储 补充: Object类: hashCode()和equals()方法; hashCode():Object类中的hashCode()方法是根据地址值计算哈希值 equals方法():Object类中的equals()方法是比较地址值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Demo { public static void main (String[] args) { HashSet<String> set = new HashSet <>(); set.add("nba" ); set.add("cba" ); set.add("bac" ); set.add("abc" ); set.add("nba" ); for (String e : set) { System.out.println(e); } System.out.println("nba" .hashCode()); System.out.println("cba" .hashCode()); System.out.println("bac" .hashCode()); System.out.println("abc" .hashCode()); } }
HashSet的源码分析 HashSet的成员属性及构造方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class HashSet <E> extends AbstractSet <E> implements Set <E>, Cloneable, java.io.Serializable{ private transient HashMap<E,Object> map; private static final Object PRESENT = new Object (); public HashSet () { map = new HashMap <>(); } }
通过构造方法可以看出,HashSet构造时,实际上是构造一个HashMap
HashSet的add方法源码解析 1 2 3 4 5 6 7 public class HashSet { public boolean add (E e) { return map.put(e, PRESENT)==null ; } }
HashMap的put方法源码解析 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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class HashMap { public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); } static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); } final V putVal (int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0 ) n = (tab = resize()).length; if ((p = tab[i = (n - 1 ) & hash]) == null ) tab[i] = newNode(hash, key, value, null ); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this , tab, hash, key, value); else { for (int binCount = 0 ; ; ++binCount) { if ((e = p.next) == null ) { p.next = newNode(hash, key, value, null ); if (binCount >= TREEIFY_THRESHOLD - 1 ) treeifyBin(tab, hash); break ; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break ; p = e; } } if (e != null ) { V oldValue = e.value; if (!onlyIfAbsent || oldValue == null ) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null ; } }
HashSet存储自定义类型元素 给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一.
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 29 30 31 32 33 34 35 36 37 38 39 40 41 public class Person { public String name; public int age; public Person () { } public Person (String name, int age) { this .name = name; this .age = age; } @Override public String toString () { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}' ; } @Override public boolean equals (Object o) { if (this == o) return true ; if (o == null || getClass() != o.getClass()) return false ; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode () { return Objects.hash(name, 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 public class Demo { public static void main (String[] args) { Person p1 = new Person ("张三" , 18 ); Person p2 = new Person ("李四" , 38 ); Person p3 = new Person ("王五" , 28 ); Person p4 = new Person ("张三" , 18 ); HashSet<Person> set = new HashSet <>(); set.add(p1); set.add(p2); set.add(p3); set.add(p4); for (Person p : set) { System.out.println(p); } System.out.println(p1.hashCode()); System.out.println(p2.hashCode()); System.out.println(p3.hashCode()); System.out.println(p4.hashCode()); } }
LinkedHashSet 我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?
在HashSet下面有一个子类java.util.LinkedHashSet
,速度很快,而且能够保证顺序,它是链表(有序)+哈希表(去重)组合 的一个数据存储结构。
演示代码如下:
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 29 import java.util.HashSet;import java.util.LinkedHashSet;public class Test { public static void main (String[] args) { LinkedHashSet<Integer> set = new LinkedHashSet <>(); set.add(300 ); set.add(100 ); set.add(200 ); set.add(500 ); set.add(400 ); set.add(400 ); System.out.println(set); } }
TreeSet集合 TreeSet集合是Set接口的一个实现类,底层依赖于TreeMap,是一种基于红黑树 的实现,其特点为:
元素唯一
元素没有索引
使用元素的自然顺序 对元素进行排序,或者根据创建 TreeSet 时提供的Comparator 比较器 进行排序,具体取决于使用的构造方法:
1 2 public TreeSet () : 根据其元素的自然排序进行排序public TreeSet (Comparator<E> comparator) : 根据指定的比较器进行排序
案例
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 import java.util.Comparator;import java.util.LinkedHashSet;import java.util.TreeSet;public class Test { public static void main (String[] args) { TreeSet<Integer> set = new TreeSet <>(); set.add(300 ); set.add(100 ); set.add(200 ); set.add(500 ); set.add(400 ); set.add(400 ); System.out.println(set); System.out.println("===========================================" ); TreeSet<Integer> set1 = new TreeSet <>(new Comparator <Integer>() { @Override public int compare (Integer o1, Integer o2) { return o2 - o1; } }); set1.add(300 ); set1.add(100 ); set1.add(200 ); set1.add(500 ); set1.add(400 ); set1.add(400 ); System.out.println(set1); System.out.println("===========================================" ); TreeSet<Integer> set2 = new TreeSet <>(new Comparator <Integer>() { @Override public int compare (Integer o1, Integer o2) { return o1 - o2; } }); set2.add(300 ); set2.add(100 ); set2.add(200 ); set2.add(500 ); set2.add(400 ); set2.add(400 ); System.out.println(set2); } }
Map集合 Map概述
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Map<K,V>集合的特点: K用来限制键的类型,V用来限制值的类型 1. Map集合存储元素是以键值对的形式存储,每一个键值对都有键和值 2. Map集合的键是唯一,值可以重复,如果键重复了,那么值就会被覆盖 3. 根据键取值 Map集合子类: - HashMap<K,V>:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。 由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。 - LinkedHashMap<K,V>:HashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构。 通过链表结构可以保证键值对的存取顺序一致; 通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。 - TreeMap<K,V>:TreeMap集合和Map相比没有特有的功能,底层的数据结构是红黑树; 可以对元素的键进行排序,排序方式有两种:自然排序和比较器排序
Map的常用方法 Map接口中定义了很多方法,常用的如下:
public V put(K key, V value)
: 把指定的键与指定的值添加到Map集合中。
public V remove(Object key)
: 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
public V get(Object key)
根据指定的键,在Map集合中获取对应的值。
public boolean containsKey(Object key)
:判断该集合中是否有此键
public Set<K> keySet()
: 获取Map集合中所有的键,存储到Set集合中。
public Set<Map.Entry<K,V>> entrySet()
: 获取到Map集合中所有的键值对对象的集合(Set集合)。
Map接口的方法演示
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 import java.util.Collection;import java.util.HashMap;import java.util.Map;import java.util.Set;public class Test { public static void main (String[] args) { Map<String, String> map = new HashMap <>(); map.put("黄晓明" , "杨颖" ); map.put("文章" , "马伊琍" ); map.put("谢霆锋" , "王菲" ); System.out.println(map); String v1 = map.put("文章" , "姚笛" ); System.out.println("v1:" +v1); System.out.println(map); String v2 = map.put("李亚鹏" , "王菲" ); System.out.println("v2:" +v2); System.out.println(map); String v3 = map.remove("文章" ); System.out.println("被删除键值对的值:" +v3); System.out.println(map); String value = map.get("黄晓明" ); System.out.println("value:" +value); System.out.println(map.containsKey("黄晓明" )); System.out.println(map.containsKey("文章" )); System.out.println(map.containsValue("杨颖" )); System.out.println(map.containsValue("马伊琍" )); Set<String> keys = map.keySet(); System.out.println("keys:" +keys); Collection<String> values = map.values(); System.out.println("values:" +values); Set<Map.Entry<String, String>> set = map.entrySet(); System.out.println(set); } }
tips:
使用put方法时,若指定的键(key)在集合中没有,则没有这个键对应的值,返回null,并把指定的键值添加到集合中;
若指定的键(key)在集合中存在,则返回值为集合中键对应的值(该值为替换前的值),并把指定键所对应的值,替换成指定的新值。
Map的遍历 方式1:键找值方式 通过元素中的键,获取键所对应的值
分析步骤:
获取Map中所有的键,由于键是唯一的,所以返回一个Set集合存储所有的键。方法提示:keyset()
遍历键的Set集合,得到每一个键。
根据键,获取键所对应的值。方法提示:get(K key)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Demo { public static void main (String[] args) { Map<String, String> map = new HashMap <>(); map.put("黄晓明" , "杨颖" ); map.put("文章" , "马伊琍" ); map.put("谢霆锋" , "王菲" ); Set<String> keys = map.keySet(); for (String key : keys) { String value = map.get(key); System.out.println("键:" +key+",值:" +value); } } }
方式2:键值对方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Entry<K,V>接口:简称Entry项,表示键值对对象,用来封装Map集合中的键值对 Entry<K,V>接口:是Map接口中的内部接口,在外部使用的时候是这样表示: Map.Entry<K,V> Map集合中提供了一个方法来获取所有键值对对象: public Set<Map.Entry<K,V>> entrySet() 根据键值对对对象获取键和值: - public K getKey():获取Entry对象中的键。 - public V getValue():获取Entry对象中的值。 Map遍历方式二:根据键值对对象的方式 1.获取集合中所有键值对对象,以Set集合形式返回。 Set<Map.Entry<K,V>> entrySet() 2.遍历所有键值对对象的集合,得到每一个键值对(Entry)对象。 3.在循环中,可以使用键值对对对象获取键和值 getKey()和getValue()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Demo { public static void main (String[] args) { Map<String, String> map = new HashMap <>(); map.put("黄晓明" , "杨颖" ); map.put("文章" , "马伊琍" ); map.put("谢霆锋" , "王菲" ); Set<Map.Entry<String, String>> entrySet = map.entrySet(); for (Map.Entry<String, String> entry : entrySet) { String key = entry.getKey(); String value = entry.getValue(); System.out.println("键:" +key+",值:" +value); } } }
HashMap存储自定义类型 练习:每位学生(姓名,年龄)都有自己的家庭住址。那么,既然有对应关系,则将学生对象和家庭住址存储到map集合中。学生作为键, 家庭住址作为值。
注意,学生姓名相同并且年龄相同视为同一名学生。
编写学生类:
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 29 30 31 32 33 34 35 36 37 38 39 40 public class Student { public String name; public int age; public Student () { } public Student (String name, int age) { this .name = name; this .age = age; } @Override public String toString () { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}' ; } @Override public boolean equals (Object o) { if (this == o) return true ; if (o == null || getClass() != o.getClass()) return false ; Student student = (Student) o; return age == student.age && Objects.equals(name, student.name); } @Override public int hashCode () { return Objects.hash(name, age); } }
编写测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Demo { public static void main (String[] args) { HashMap<Student,String> map = new HashMap <>(); Student stu1 = new Student ("张三" , 18 ); Student stu2 = new Student ("李四" , 38 ); Student stu3 = new Student ("王五" , 28 ); Student stu4 = new Student ("张三" , 18 ); map.put(stu1,"北京" ); map.put(stu2,"上海" ); map.put(stu3,"深圳" ); map.put(stu4,"广州" ); System.out.println(map); System.out.println(map.size()); } }
当给HashMap中存放自定义对象时,如果自定义对象作为key存在,这时要保证对象唯一,必须复写对象的hashCode和equals方法(如果忘记,请回顾HashSet存放自定义对象)。
如果要保证map中存放的key和取出的顺序一致,可以使用java.util.LinkedHashMap
集合来存放。
LinkedHashMap介绍 我们知道HashMap保证成对元素唯一,并且查询速度很快,可是成对元素存放进去是没有顺序的,那么我们要保证有序,还要速度快怎么办呢?
通过链表结构可以保证元素的存取顺序一致;
通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import java.util.LinkedHashMap;public class Test { public static void main (String[] args) { LinkedHashMap<Integer, String> map = new LinkedHashMap <>(); map.put(400 , "武汉" ); map.put(400 , "深圳" ); System.out.println(map); } }
TreeMap集合 TreeMap介绍 TreeMap集合和Map相比没有特有的功能,底层的数据结构是红黑树;可以对元素的键 进行排序,排序方式有两种:自然排序 和比较器排序 ;到时使用的是哪种排序,取决于我们在创建对象的时候所使用的构造方法;
构造方法 1 2 public TreeMap () 使用自然排序public TreeMap (Comparator<? super K> comparator) 通过比较器指定规则排序
案例演示 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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 import java.util.Comparator;import java.util.LinkedHashMap;import java.util.TreeMap;public class Test { public static void main (String[] args) { TreeMap<Integer, String> map = new TreeMap <>(); map.put(300 , "深圳" ); map.put(100 , "北京" ); map.put(200 , "广州" ); map.put(500 , "上海" ); map.put(400 , "武汉" ); map.put(400 , "深圳" ); System.out.println(map); System.out.println("+=================================" ); TreeMap<Integer, String> map1 = new TreeMap <>(new Comparator <Integer>() { @Override public int compare (Integer o1, Integer o2) { return o2 - o1; } }); map1.put(300 , "深圳" ); map1.put(100 , "北京" ); map1.put(200 , "广州" ); map1.put(500 , "上海" ); map1.put(400 , "武汉" ); map1.put(400 , "深圳" ); System.out.println(map1); } }
Map集合练习 需求
分析
获取一个字符串对象
创建一个Map集合,键代表字符,值代表次数。
遍历字符串得到每个字符。
判断Map中是否有该键。
如果没有,第一次出现,存储次数为1;如果有,则说明已经出现过,获取到对应的值进行++,再次存储。
打印最终结果
实现 方法介绍
public boolean containKey(Object key)
:判断该集合中是否有此键。
代码:
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 29 30 31 32 33 34 35 36 import java.util.HashMap;import java.util.Scanner;public class Test { public static void main (String[] args) { Scanner sc = new Scanner (System.in); System.out.println("请输入一个字符串:" ); String s = sc.nextLine(); HashMap<Character, Integer> map = new HashMap <>(); for (int i = 0 ; i < s.length(); i++) { char c = s.charAt(i); if (map.containsKey(c)) { Integer value = map.get(c); value++; map.put(c, value); } else { map.put(c, 1 ); } } System.out.println(map); } }
集合的嵌套
任何集合内部都可以存储其它任何集合
List嵌套List 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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class Test1 { public static void main (String[] args) { List<String> list1 = new ArrayList <>(); list1.add("王宝强" ); list1.add("贾乃亮" ); list1.add("陈羽凡" ); List<String> list2 = new ArrayList <>(); list2.add("马蓉" ); list2.add("李小璐" ); list2.add("白百何" ); List<List<String>> list = new ArrayList <>(); list.add(list1); list.add(list2); for (List<String> e : list) { for (String name : e) { System.out.println(name); } System.out.println("=============" ); } System.out.println(list); } }
List嵌套Map 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 29 30 31 32 33 34 35 public class Test2 { public static void main (String[] args) { Map<String,String> map1 = new HashMap <>(); map1.put("it001" ,"迪丽热巴" ); map1.put("it002" ,"古力娜扎" ); Map<String,String> map2 = new HashMap <>(); map2.put("heima001" ,"蔡徐坤" ); map2.put("heima002" ,"李易峰" ); List<Map<String,String>> list = new ArrayList <>(); list.add(map1); list.add(map2); System.out.println(list.size()); for (Map<String, String> map : list) { Set<String> keys = map.keySet(); for (String key : keys) { System.out.println(key + "," + map.get(key)); } } } }
Map嵌套Map 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 29 30 31 32 33 34 35 36 37 38 39 40 public class Test3 { public static void main (String[] args) { Map<String,String> map1 = new HashMap <>(); map1.put("it001" ,"迪丽热巴" ); map1.put("it002" ,"古力娜扎" ); Map<String,String> map2 = new HashMap <>(); map2.put("heima001" ,"蔡徐坤" ); map2.put("heima002" ,"李易峰" ); Map<String, Map<String, String>> map = new HashMap <>(); map.put("传智博客" ,map1); map.put("黑马程序员" ,map2); System.out.println(map.size()); Set<String> keys = map.keySet(); for (String key : keys) { Map<String, String> value = map.get(key); Set<String> keySet = value.keySet(); for (String k : keySet) { String v = value.get(k); System.out.println(k+"," +v); } } } }
模拟斗地主洗牌发牌 需求 按照斗地主的规则,完成洗牌发牌的动作。
具体规则:
组装54张扑克牌
54张牌顺序打乱
三个玩家参与游戏,三人交替摸牌,每人17张牌 ,最后三张留作底牌。
查看三人各自手中的牌(按照牌的大小排序)、底牌
规则:手中扑克牌从大到小的摆放顺序:大王,小王,2,A,K,Q,J,10,9,8,7,6,5,4,3
分析 1.准备牌:
完成数字与纸牌的映射关系:
使用双列Map(HashMap)集合,完成一个数字与字符串纸牌的对应关系(相当于一个字典)。
2.洗牌:
通过数字完成洗牌发牌
3.发牌:
将每个人以及底牌设计为ArrayList<String>
,将最后3张牌直接存放于底牌,剩余牌通过对3取模依次发牌。
存放的过程中要求数字大小与斗地主规则的大小对应。
将代表不同纸牌的数字分配给不同的玩家与底牌。
4.看牌:
通过Map集合找到对应字符展示。
通过查询纸牌与数字的对应关系,由数字转成纸牌字符串再进行展示。
实现 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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 import java.util.ArrayList;import java.util.Collections;import java.util.HashMap;public class Test { public static void main (String[] args) { HashMap<Integer, String> pokeBox = new HashMap <>(); ArrayList<String> colors = new ArrayList <>(); ArrayList<String> numbers = new ArrayList <>(); Collections.addAll(colors, "♥" , "♦" , "♠" , "♣" ); Collections.addAll(numbers, "2" , "A" , "K" , "Q" , "J" , "10" , "9" , "8" , "7" , "6" , "5" , "4" , "3" ); int id = 0 ; pokeBox.put(id++, "大王" ); pokeBox.put(id++, "小王" ); for (String number : numbers) { for (String color : colors) { String pai = color + number; pokeBox.put(id++,pai); } } System.out.println(pokeBox.size()); System.out.println(pokeBox); Set<Integer> keySet = pokeBox.keySet(); ArrayList<Integer> idList = new ArrayList <>(); idList.addAll(keySet); Collections.shuffle(idList); System.out.println("打乱顺序后的牌编号:" +idList.size()); System.out.println("打乱顺序后的牌编号:" +idList); ArrayList<Integer> play1Id = new ArrayList <>(); ArrayList<Integer> play2Id = new ArrayList <>(); ArrayList<Integer> play3Id = new ArrayList <>(); ArrayList<Integer> diPaiId = new ArrayList <>(); for (int i = 0 ; i < idList.size(); i++) { Integer paiId = idList.get(i); if (i >= 51 ){ diPaiId.add(paiId); }else if (i%3 ==0 ){ play1Id.add(paiId); }else if (i%3 ==1 ){ play2Id.add(paiId); }else if (i%3 ==2 ){ play3Id.add(paiId); } } Collections.sort(play1Id); Collections.sort(play2Id); Collections.sort(play3Id); Collections.sort(diPaiId); System.out.print("玩家一的牌:" ); for (Integer paiId : play1Id) { String pai = pokeBox.get(paiId); System.out.print(pai+" " ); } System.out.println(); System.out.print("玩家二的牌:" ); for (Integer paiId : play2Id) { String pai = pokeBox.get(paiId); System.out.print(pai+" " ); } System.out.println(); System.out.print("玩家三的牌:" ); for (Integer paiId : play3Id) { String pai = pokeBox.get(paiId); System.out.print(pai+" " ); } System.out.println(); System.out.print("底牌的牌:" ); for (Integer paiId : diPaiId) { String pai = pokeBox.get(paiId); System.out.print(pai+" " ); } } }