集合、泛型、枚举、注解、反射
1集合
1.1概述
集合时javaAPI中提供的一种容器,可以用来存储多个数据;
集合和数组之间的区别:
- 数组的长度是固定的,集合的长度是可变的;
- 数组中存储的是同一种类型的元素,集合中存储的数据可以是不同类型的;
- 数组中可以存放基本类型数据或者对象,集合中只能存放对象(基本类型自动装箱);
- 数组是由JVM中现有的类型+[]而组成的,除了一个length属性,还有从Object中继承过来的方法之外,数据对象就调用不到其他属性和方法了;
- 集合是由javaAPI中的java.util包里面提供的接口和实现类组成的,这里面定义并实现了很多方法,可以使用集合对象直接调用这些方法,从而操作集合存放的数据;
集合框架主要由三个要素组成:
-
接口
整个集合框架的上层结构,都是用接口进行组织的。接口中定义了集合中必须要有的方法;
通过接口还把集合划分成了几种不同的类型,每一种集合都有自己对应的接口;
-
实现类
对于上层使用接口划分好的集合类型,每种集合的接口都有对应的实现类。
每一种接口的实现类很可能有多个,每个的实现方式也会各有不同。
-
数据结构
每个实现类都实现了接口中所定义的方法,例如对数据的存储、检索、操作等方法。但是不同的实现类,他们存储数据的方式不同,也就是使用的数据结构不同;
集合按照其存储结构可以分为两大类:
java.util.Collection
java.util.Map
其他的集合接口,都是由这两个接口派生出来的:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m7qcBNt5-1638149457742)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616135702248.png)]
注意1:图中列出来的是java集合框架中的主要接口,以及他们之间的继承关系;
注意2:接口中定义了该种集合具有的主要方法,以及集合的基本特点;
注意3:将来真正要使用的,是这些接口的实现类,每种实现类对接口的实现方式不同,那么其具有的特点也不同
1.2 Collection接口
Collecton接口是单列集合类的父接口,这种集合可以将数据一个一个存放到集合中。它有两个重要的子接口,分别是java.util.List
和java.util.Set
如图[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4y0fCmpX-1638149457745)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616183544537.png)]
Collection是父接口,其中定义了单列集合(List和Set)通用的一些方法,这些方法可以用于操作所有单列集合对象。
Collection类型集合必须要有的基本方法:
//向集合中添加元素
boolean add(E e)
//把一个指定集合中的所有数据,添加到当前集合中
boolean addAll(Collection<? extends E> c)
//清空集合中所有的元素。
void clear()
//判断当前集合中是否包含给定的对象。
boolean contains(Object o)
//判断当前集合中是否包含给定的集合的所有元素。
boolean containsAll(Collection<?> c)
//判断当前集合是否为空。
boolean isEmpty()
//返回遍历这个集合的迭代器对象
Iterator<E> iterator()
//把给定的对象,在当前集合中删除。
boolean remove(Object o)
//把给定的集合中的所有元素,在当前集合中删除。
boolean removeAll(Collection<?> c)
//判断俩个集合中是否有相同的元素,如果有当前集合只保留相同元素,如果没有当前集合元素清空
boolean retainAll(Collection<?> c)
//返回集合中元素的个数。
int size()
//把集合中的元素,存储到数组中。
Object[] toArray()
//把集合中的元素,存储到数组中,并指定数组的类型
<T> T[] toArray(T[] a)
package com.zyz.chapter8.Collection;
import java.util.ArrayList;
import java.util.Collection;
public class ListTest {
public static void main(String[] args) {
Collection coll=new ArrayList();
coll.add("hello");
coll.add("hi");
coll.add("world");
coll.add("zyz");
System.out.println(coll);
System.out.println(coll.size());
System.out.println(coll.contains("hello"));
System.out.println(coll.remove("hello"));
System.out.println(coll.contains("hello"));
System.out.println(coll);
Object[] objects=coll.toArray();
for (int i=0;i<objects.length;i++){
System.out.println(objects[i]);
}
coll.clear();
System.out.println(coll.isEmpty());
}
}
[hello, hi, world, zyz]
4
true
true
false
[hi, world, zyz]
hi
world
zyz
true
1.3 迭代器
为了能够方便的遍历集合中的每一个元素,API中提供了一个迭代器接口:java.util.Iterator
该接口可以很方便的迭代出集合中的元素。
java.util.Iterable
接口中,定义了获取迭代器的方法
public interface Iterable{
Iterator iterator;
}
java.util.Collection
接口继承了 java.lang.Iterable
接口
public interface Collection extends Iterable {
//...
}
所以, Collection
接口及其子接口中,都有一个获取迭代器对象的方法: Iterator iterator()
;
java.util.Iterator
接口中,主要定义俩个方法:
public interface Iterator {
boolean hasNext();//返回当前迭代器中是否还有下一个对象
Object next();//获取迭代器中的下一个对象
}
public static void main(String[] args) {
Collection c1 = new ArrayList();
c1.add("hello1");
c1.add("hello2");
c1.add("hello3");
//获取c1集合的迭代器对象
Iterator iterator=c1.iterator();
//判断迭代器中是否还有下一个元素
while (iterator.hasNext()){
//如果有的话,就取出来
Object o=iterator.next();
System.out.println(o);
}
}
注意,这种迭代器方式获取集合中的每一个元素,是Collection集合及其子类型集合通用的方式
迭代器next方法示意图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-naHXdYcR-1638149457748)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616185102797.png)]
1.2 foreach循环
除了使用迭代器遍历集合之外,使用JDK1.5以上提供的增强for循环,也可以遍历集合。
foreach循环的格式:
for(变量类型 变量名:集合){
//操作变量
}
相当于每次循环,使用指定变量,去指向集合中的一个对象,然后再循环体中对该变量进行操作。
public static void main(String[] args) {
Collection c1 = new ArrayList();
c1.add("hello1");
c1.add("hello2");
c1.add("hello3");
//获取c1集合的迭代器对象
Iterator iterator=c1.iterator();
for (Object o:
c1) {
System.out.println(o);
}
}
同时可以使用foreach循环遍历数组:
int arr[]={1,2,3,5,6};
for (int i:
arr) {
//每次循环,使用变量i接收数组中的一个数据
System.out.println(i);
}
注意,Collection类型及其子类型的集合,还有数组,都可以使用foreach循环进行遍历其中的元素数据
增强for:
for(数据类型 变量名:集合|数组){}//注意:集合必须是实现了Iterable接口的集合;
1.5 数据结构
集合接口都会有不同的实现类,每种实现类的底层,采用了不同的数据结构对数据元素进行存储
数据存储的常用结构有:
- 栈;
- 队列
- 数组
- 链表
- 红黑树
- 哈希表
栈
栈(Stack),又称堆栈,仅仅允许栈的一端进行插入删除操作,并且不允许再其他任何位置进行操作。
特点:先进后出
栈的入口、出口的都是栈的顶端位置:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wowfskjH-1638149457750)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616190931707.png)]
入栈也称为压栈,把数据存入到栈的顶端位置;
出栈也称为弹栈,把栈顶位置的数据取出
思考,JVM中的栈区中,为什么把main方法标注在最低端位置?
队列
队列(queue),仅允许在队列的一端进行插入,而在队列的另一端进行删除。
特点:先进先出
队列的入口和出口分别在队列的两端:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7KnjcU18-1638149457753)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616191931529.png)]
数组
数组(array),内存中一块连续的空间,元素数据在其中按照索引依次存储,比较常用的数据结构。
特点:通过下标索引,可以快速访问到指定位置的元素,但是在数组中间位置添加数据或者删除数据会比较慢,因为数组中间位置的添加和删除元素,为了元素数据能紧凑的排列在一起,那么就会引起其他数据移动位置。
所以,数组查询元素比较块,中间位置的插入和删除元素比较慢;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B2du08CJ-1638149457755)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616192325977.png)]
可以看出,数组中间添加数据后,之后的数据都要依次移动位置。同理,中间位置删除的时候也是 这样
链表
链表(linked list),由一个个node节点组成,每个node节点中存储了一个数据,以及一个指向下一个node节点对象的引用(单向链表),如果是双向链表的话,还会存储另一个引用,指向上一个node节点对象。
特点:
- **查找元素慢;**因为需要通过连接的节点,依次向后查找指定的元素(没有直接的下标索引);
- 新增和删除元素比较快,例如删除,只需要让当前node节点中的引用指向另一个节点对象即可,原来 的指向的node节点就相当于删除了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N3e3tXFe-1638149457757)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616192826045.png)]
可以看出,只需要将数据2节点中的引用,指向数据4的节点对象即可 head表示链表的头部,tail表示链表的尾部
红黑树
二叉树(Binary tree)是树形结构的一个重要类型。二叉树特点是每个结点最多只能有两棵子树,且有 左右之分。
二叉树顶上的叫根结点,两边被称作“左子树”和“右子树”。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MxzhWz8X-1638149457759)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616193002483.png)]
二叉树中有一种叫做红黑树(Red/Black Tree),它最早被称为平衡二叉B树(symmetric binary Btrees),后来被称为红黑树。
红黑树是一种特殊化的平衡二叉树,它可以在进行插入和删除的时候,如果左右子数的高度相差较大, 那么就通过特定操作(左旋、右旋)保持二叉查找树的平衡(动态平衡),从而获得较高的查找性能。
红黑树的每一个节点的左子树的所有数据都比自己小,而右子树的所有数据都比自己大,并且左右子树 的高度近似
红黑树的约束:
- 根节点必须是黑色;
- 其他节点可以是红色或者黑色;
- 叶子节点(特指null节点)是黑色的;
- 每个红色节点的子节点都是黑色的;
- 任何一个节点到其每一个叶子节点的所有路径上的黑色节点数相同;
注意,红黑树的指定颜色的目的,是利用颜色值作为二叉树的平衡对称性的检查
例如,从空树开始演示一个案例:
插入9
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q14ymdsU-1638149457761)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616193407383.png)]
插入8:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-puMFZXqm-1638149457764)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616193430152.png)]
插入12
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ypu1HjGM-1638149457767)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616193447095.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AV7aqRqn-1638149457769)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616193507868.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c3PpR7M3-1638149457771)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616193528234.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S96JvUrE-1638149457772)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616193539444.png)]
可以通过在线工具,进行节点的添加,查看红黑树的动态调整的动画效果,建议使用chrome浏览器打 开:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vqZjevRD-1638149457774)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616193649075.png)]
哈希表
java中的哈希表(hash),在JDK1.8之前是采用数组+链表进行实现,根据数据的哈希值,把数据存在数 组中,但是当前哈希值冲突的时候,再使用链表进行存储,那么在数组中,同一hash值的数据都存在一 个链表里。
注意,之前学习过Object中hashCode方法的作用,hash值的特点以及和对象之间的关系
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I15sF5yg-1638149457775)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616193806858.png)]
例如,如果数据的哈希值相同,在数组使用使用链表存储哈希值相同的几个数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D9Lw06UW-1638149457776)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616193830150.png)]
可以看出,当链表中元素过多,即hash值相等的元素较多时,查找的效率会变低
JDK1.8中,哈希表存储采用数组+链表+红黑树进行实现,当链表长度超过阈值(8)时,将链表转换为 红黑树,这样可以大大提高查找的性能。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fugZ1rc4-1638149457778)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616193916376.png)]
思考,java的集合框架中,三个重要组成部分是什么?
接口、实现类、数据结构
1.6 List集合
java.util.List
接口继承了 Collection
接口,是常用的一种集合类型。
List
集合具有 Collection
集合的特点之外,还具有自己的一些特点:
-
List是一种有序的集合
例如,向集合中存储的元素顺序是8、2、5。那么集合中就是按照这个顺序进行存储的
-
list是一种带索引的集合
可以通过元素的下标索引,精确查找对应的元素数据
-
List是一种可以存放重复数据的集合
可以把相同的数据,在List集合中多次保存
List接口中常用方法:
//返回集合中指定位置的元素。
E get(int index);
//用指定元素替换集合中指定位置的元素,并返回被替代的旧元素。
E set(int index, E element);
//将指定的元素,添加到该集合中的指定位置上。
void add(int index, E element);
//从指定位置开始,把另一个集合的所有元素添加进来
boolean addAll(int index, Collection<? extends E> c);
//移除列表中指定位置的元素, 并返回被移除的元素。
E remove(int index);
//查收指定元素在集合中的所有,从前往后查到的第一个元素(List集合可以重复存放数据)
int indexOf(Object o);
//查收指定元素在集合中的所有,从后往前查到的第一个元素(List集合可以重复存放数据)
int lastIndexOf(Object o);
//根据指定开始和结束位置,截取出集合中的一部分数据
List<E> subList(int fromIndex, int toIndex);
注意,除了这些方法之外,还有从父接口Collection中继承过来的方
案例
package com.zyz.chapter8.Collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
public class ListTest {
public static void main(String[] args) {
//创建list对象
List list=new ArrayList();
//往尾部添加指定元素
list.add("hello");
list.add("hi");
list.add("hello");
list.add("world");
System.out.println(list);
//在指定的位置上添加元素
list.add(1,"zyz");
System.out.println(list);
//删除索引位置上为2的元素
System.out.println("删除索引位置为2的元素");
System.out.println(list.remove(2));
System.out.println(list);
//修改指定位置的元素
System.out.println(list.set(2, "me"));
System.out.println(list);
//遍历集合
for (int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
System.out.println("-----------------");
//使用增强for遍历
for (Object o:
list) {
System.out.println(o);
}
//使用迭代器进行遍历
System.out.println("============");
Iterator iterator=list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
可以看出,List集合的特点:有序可重复,并且可以使用下标索引进行访问
1.7 List实现类
List接口的实现类有:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nUuOHINn-1638149457780)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616201513074.png)]
注意,这些实现类中,都已经实现了List接口、Collection接口、Iterable接口中的方法,我们只要了 解并能使用这些接口中的方法,就已经能够操作这些集合对象了(面向接口)。
额外的,我们还需要了解这些常用的接口实现类,分别都是什么特点,使用的什么数据结构,以及 适合在什么样的场景下使用。
List接口实现类:ArrayList
java.util.ArrayList
是最常用的一种List类型集合, ArrayList 类中使用数组来实现数据的存储, 所以它的特点是就是:增删慢,查找快。
在日常的开发中,查询数据也是用的最多的功能,所以ArrayList是最常用的集合。
但是,如果项目中对性能要求较高,并且在集合中大量的数据做增删操作,那么 ArrayList 就不太适 合了。
List接口实现类:LinkedList
java.util.LinkedList
存储数据采用的数据结构是链表,所以它的特点是:增删快,查找慢
它的特点刚好和 ArrayList
相反,所以在代码中,需要对集合中的元素做大量的增删操作的时候,可 以选择使用 LinkedList
注意,这里描述的快和慢,需要在大量的数据操作下,才可以体现,如果数据量不大的话,集合每一种 集合的操作几乎没有任何区别。
public static void main(String[] args) {
//操作集合的次数
final int NUM = 100000;
List list;
//list = new ArrayList();
list = new ArrayList();
long start1 = System.currentTimeMillis();
for (int i = 0; i < NUM; i++) {
list.add(0,"hello"+i);
}
long end1 = System.currentTimeMillis();
System.out.println(list.getClass().getSimpleName()+"插入"+NUM+"条数据耗时"+
(end1-start1)+"毫秒");
long start2 = System.currentTimeMillis();
for(int i=0;i<list.size();i++){
list.get(i);
}
long end2 = System.currentTimeMillis();
System.out.println(list.getClass().getSimpleName()+"检索"+NUM+"条数据耗时"+
(end2-start2)+"毫秒");
}
//根据电脑的当前情况,每次运行的结果可能会有差异
//以下是我的电脑运行俩次的实验结果,第一次使用ArrayList,第二次使用LinkedList
ArrayList插入100000条数据耗时587毫秒
ArrayList检索100000条数据耗时0毫秒
LinkedList插入100000条数据耗时31毫秒
LinkedList检索100000条数据耗时15252毫秒
注意,调用的方法都一样,但是每次改变list指向的对象,分别执行
ArrayList
对象和LinkedList
对象注意,
list.getClass
().getSimpleName
() 方法可以获取list引用当前指向对象的实际类型的简 单名字注意,
System.currentTimeMillis
(); 可以获取当前时刻的时间戳注意,集合中操作的数据量小的时候,使用哪种实现类,性能差别都不大
下面对 LinkedList
的介绍内容,可以作为额外的了解
LinkedList
同时还是一个双向链表:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lKJ45p89-1638149457781)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616202259384.png)]
可以看出,双向链表,除了头节点(head)和尾节点(tail)之外,其他节点都含有俩个引用,一 个引用指向它的上一个节点,另一个引用指向它的的下一个节点
在 LinkedList
中,定义了一些操作头节点和尾节点的方法:
//将指定元素插入此列表的开头
void addFirst(E e)
//将指定元素添加到此列表的结尾
void addLast(E e)
//返回此列表的第一个元素
E getFirst()
//返回此列表的最后一个元素
E getLast()
//从此列表所表示的堆栈处弹出一个元素
E pop()
//将元素推入此列表所表示的堆栈
void push(E e)
//移除并返回此列表的第一个元素
E removeFirst()
//移除并返回此列表的最后一个元素
E removeLast()
LinkedList
类不仅实现了 List
接口,还有 Queue
接口以及子接口 Deque
也实现了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mWRD6ROZ-1638149457783)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616202454597.png)]
Queue
是队列接口,Deque
是双端队列JavaAPI中提供了
java.util.Stack
来实现栈结构,但官方目前已不推荐使用,而是推荐使用java.util.Deque
双端队列来实现队列与栈的各种需求所以
LinkedList
同时具有队列和栈的操作方法,pop和push都是栈结构的操作方法
Queue
中方法和 Deque
中方法对比:
Queue | Deque | 说明 |
---|---|---|
add(e) | addLast(e) | 向队为插入元素,失败则抛出异常 |
offer(e) | offerLast(e) | 向队尾插入元素,失败则返回false |
remove(e) | removeFirst() | 获取并删除队首元素,失败则抛出异常 |
poll() | pollFirst() | 获取并删除队首元素,失败则返回null |
element() | getFirst() | 获取但不删除队首元素,失败则抛出异常 |
peek() | peekFirst() | 获取但不删除队首元素,失败则返回null |
Stack
中方法和 Deque
中方法对比:
Stack | Deque | 说明 |
---|---|---|
push(e) | addFirst(e) | 向栈顶插入元素,失败则抛出异常 |
无 | offerFirst(e) | 向栈顶插入元素,失败则返回false |
pop() | removeFirst() | 获取并删除元素,失败则抛出异常 |
无 | pollFirst() | 获取并删除元素,失败则返回null |
peek() | peekFirst() | 获取但不删除元素,失败则返回bull |
所以,
LinkedList
集合也可以作为栈和队列的数据结构来使用
List接口实现类:Vector
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-baxjVnhv-1638149457784)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616204128381.png)]
Vector
内部也是采用了数组来存储数据,但是 Vector
中的方法大多数都是线程安全的方法,所以在 多线并发访问的环境中,可以使用 Vector
来保证集合中元据操作的安全。
查看 Vector 中方法的定义,可以看到多大数方法都使用了 synchronized 关键字,来给当前方法加 锁。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9fIsx49d-1638149457786)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616204219490.png)]
- List 可以重复,顺序,有索引
- ArratList的底层是数组:所以适合做查询,不适合做插入和删除;
- LinkedList是用双向链表实现的,还结合了队列和栈,适合插入,删除;
1.8 Set集合
java.util.Set
接口继承了 Collection
接口,是常用的一种集合类型。 Set
集合具有 Collection
集合的特点之外,还具有自己的一些特点:
- Set是一种无序的集合;
- Set是一种不带下标的集合;
- Set是一种不能存放重复数据的集合;
注意,和List完全相反哦;
Set接口中的方法,都继承自它的父接口:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SjGNbePI-1638149457788)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616204443042.png)]
package com.zyz.chapter8.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class SetTest {
public static void main(String[] args) {
Set set = new HashSet();
set.add("hello1");
set.add("hello2");
set.add("hello3");
set.add("hello4");
set.add("hello5");
set.add("hello5");
for(Object obj:set){
System.out.println(obj);
}
System.out.println("-----------------");
Iterator it = set.iterator();
while(it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}
}
}
//运行结果
hello1
hello4
hello5
hello2
hello3
-----------------
hello1
hello4
hello5
hello2
hello3//由此可以看出:无序、不可重复
1.9 Set实现类
里面的数据不能重复、无序的、没有索引;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DSOSbuV6-1638149457789)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616204928875.png)]
Set接口常用的的实现类有俩个:
- HashSet
- TreeSet
注意,TreeSet是Set接口的子接口SortedSet的实现类
Set接口实现类:HashSet
java.util.HashSet
类的实现,主要依靠的是 HashMap
:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-97Bv8zwI-1638149457791)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616205104041.png)]
注意,HashMap的实现主要是利用上面介绍的【哈希表】
思考,HashSet为什么是无序的,它是怎么判断元素是否是重复的?
- HashSet中存储元素是无序的,主要因为它是靠对象的哈希值来确定元素在集合中的存储位置。
- HashSet中元素不可重复,主要是靠对象的
hashCode
和equals
方法来判断对象是否重复。
将自定义对象存放到HashSet中:
package com.zyz.chapter8.Collection;
import java.util.HashSet;
import java.util.Set;
public class Student {
String name;
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 +
'}';
}
public static void main(String[] args) {
Set set=new HashSet();
set.add(new Student("tom",18));
set.add(new Student("tom",18));
set.add(new Student("tom",18));
for (Object o:set){
System.out.println(o);
}
}
}
//运行结果
Student{name='tom', age=18}
Student{name='tom', age=18}
Student{name='tom', age=18}
可以看出,HashSet并没有把name和age都相同的对象,当做相同的对象进行去重
原因是是因为,HashSet中判断对象是否重复是根据对象的hashCode值和equals的比较结果,而不是根 据对象的name和age值是否相等!
如果俩个对象的hashCode值相等,那么再使用equals判断是否俩对象是否相同, 如果俩个对象的hashCode值不同等,那么就不再使用equals进行判断了,因为hashCode不同的俩个对象 一定是不同的俩个对象!
所以,想要将Student对象根据我们的要求进行去重,就要重写Student中的hashCode方法和equals方法
package com.zyz.chapter8.Collection;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class Student {
String name;
int age;
public Student() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public static void main(String[] args) {
Set set=new HashSet();
set.add(new Student("tom",18));
set.add(new Student("tom",18));
set.add(new Student("tom",18));
for (Object o:set){
System.out.println(o);
}
}
}
//运行结果
Student{name='tom', age=18}
java.util.Objects 是JDK1.7提供的一个对象的工具类,里面定义的一些静态方法,提供了操作 对象的方法
例如,equals(Object a, Object b)方法可以判断俩个对象是否相等,当然也可以直接进行equals判断
例如,hash(Object… values)方法,可以根据若干个参数计算出一个哈希值,当然也可以自己设置 哈希值
根据输出结果,可以看出,重写hashCode和equals后,HashSet就可以按照我们的要求进行去重了
Set接口实现类:TreeSet
TreeSet是Set接口的子接口SortedSet的实现类
TreeSet可以将我们存进去的数据进行排序,排序的方式有俩种:
- 自然排序;
- 比较器排序(也称客户化排序);
TreeSet:自然排序
如果一个类,实现了 java.lang.Comparable
接口,那么这个类的俩个对象就是可以比较大小的。
public interface Comparable<T> {
public int compareTo(T o);
}
compareTo方法使用说明: int result = o1.compareTo(o2);
- result的值大于0,说明o1比o2大;
- result的值小于0,说明o1比o2小;
- result的值等于0,说明o1与o2相等;
例如
public static void main(String[] args) {
Set set = new TreeSet();
set.add(3);
set.add(5);
set.add(1);
set.add(7);
for(Object obj : set){
System.out.println(obj);
}
}
//输出结果:
1
3
5
7
可以看出,存进去的数字已经安从小到大进行了排序
set.add(3);
代码执行的时候,会自动进行装箱,把基本类型数据3变为Integer
对象,然后再存放到集 合中。
Integer
类是实现了 Comparable
接口的,那么Integer
类的俩个对象之间就可以调用 compareTo
方法比 较大小了,当前对象比较出来大小,那么对象就可以按照从小到大的顺序进行排序。
所以,数据排序的前提,一定是可以比较出数据的大小
Integer类中的部分代码如下:
public class Integer implements Comparable<Integer>{
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}
public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
}
可以看出,Integer的俩个对象比较大小,其实就是比较Integer对象对应的int值的大小
注意,compareTo方法返回值代表的含义(三种情况:正数、负数、零)
给自定义类型进行排序:
public class Student{
String name;
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 +
'}';
}
}
public static void main(String[] args) {
Set set = new TreeSet();
set.add(new Student("mary",23));
set.add(new Student("jack",21));
set.add(new Student("tom",20));
set.add(new Student("lucy",22));
for(Object obj : set){
System.out.println(obj);
}
}
//运行结果报错:
Exception in thread "main" java.lang.ClassCastException: com.briup.demo.Student
cannot be cast to java.lang.Comparable
报错原因,TreeSet中会把对象强制为Comparable类型,因为转成Comparable类型就可以调用 compareTo方法进行俩个对象的比较大小了。但是这里的Student没有实现该接口,所以报错了。
Student
类实现 Comparable
接口,并在 compareTo
方法中,编写比较大小的规则:
public class Student implements Comparable{
String name;
int age;
public Student() {
}
// @Override
// public boolean equals(Object o) {
// if (this == o) return true;
// if (!(o instanceof Student)) return false;
// Student student = (Student) o;
// return age == student.age &&
// Objects.equals(name, student.name);
// }
//
// @Override
// public int hashCode() {
// return Objects.hash(name, age);
// }
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public static void main(String[] args) {
Set set=new TreeSet();
set.add(new Student("tom",31));
set.add(new Student("tom",18));
set.add(new Student("tom",22));
for (Object o:set){
System.out.println(o);
}
}
@Override
public int compareTo(Object o) {
//注意,这里this代表我,other代表你,也就是当前要和我比较大小的对象
Student other=(Student)o;
//如果我的年龄比你大,就返回正数,说明我比你大
if (this.age>other.age){
return 1;
}else if (this.age<other.age){
return -1;
}
//其他情况返回0,说明俩个对象(我和你)一样大
return 0;
}
}
//运行结果
Student{name='tom', age=18}
Student{name='tom', age=22}
Student{name='tom', age=31}
可以看出,现在学生对象在TreeSet中,就可以按照我们定义的规则进行从小到大排序了
注意,compareTo方法的放回结果,只关心正数、负数、零,不关心具体的值是多少
TreeSet:比较器排序
假设现在Student类已经写好了,但是没有实现Comparable接口,同时我们又不愿意直接修改Student类 的代码,那么在这种情况下,Student对象在TreeSet中是否还能排序呢?
java.util.Comparator
接口,是一个比较器接口,它的实现类可以对俩个对象作出大小的比较,即 使对象没有实现 Comparable
接口,也可以进行比较。
public interface Comparator<T> {
int compare(T o1, T o2);
}
比较器的使用规则: int result = compare(o1, o2);
- result的值大于0,说明o1比o2大
- result的值小于0,说明o1比o2小
- result的值等于0,说明o1与o2相等
注意,这里和自然排序的规则是一样的,只关心正数、负数、零,不关心具体的值是多少
在TreeSet类重载的构造器中,有一个构造器可以接收比较器对象:
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
例如,
package com.zyz.chapter8.Collection;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
public class Stu1 {
String name;
int age;
public Stu1() {
}
public Stu1(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Stu1{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public static void main(String[] args) {
//使用Comparator接口,创建出匿名内部类对象,这个对象就是要用的比较器对象
Comparator c=new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Stu1 s1=(Stu1)o1;
Stu1 s2=(Stu1)o2;
return s1.age>s2.age?1:(s1.age==s2.age?0:-1);
}
};
//创建TreeSet对象的时候,把比较器对象传入
Set set = new TreeSet(c);
set.add(new Stu1("mary",23));
set.add(new Stu1("jack",21));
set.add(new Stu1("tom",20));
set.add(new Stu1("lucy",22));
for (Object o:set){
System.out.println(o);
}
}
}
//运行结果
Stu1{name='tom', age=20}
Stu1{name='jack', age=21}
Stu1{name='lucy', age=22}
Stu1{name='mary', age=23}
1.10 Map集合
很多时候,我们会遇到成对出现的数据,例如,姓名和电话,身份证和人,IP和域名等等,这种成对出 现,并且一一对应的数据关系,叫做映射。
java.util.Map
接口,就是专门处理这种映射关系数据的集合类型。
Map类型集合与Collection类型集合,存储数据的形式不同:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9WeqhknT-1638149457793)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210616212506451.png)]
Collection类型集合中,每次存一个数据。
Map类型集合中,每次需要存一对数据,key-value(键值对)
- key值必须是唯一的,value值允许重复
- 键(key)和值(value)一一映射,一个key对应一个value
- 在Map中,通过key值(唯一的),可以快速的找到对应的value值
Map接口中的方法:
//把key-value存到当前Map集合中
V put(K key, V value)
//把指定map中的所有key-value,存到当前Map集合中
void putAll(Map<? extends K,? extends V> m)
//当前Map集合中是否包含指定的key值
boolean containsKey(Object key)
//当前Map集合中是否包含指定的value值
boolean containsValue(Object value)
//清空当前Map集合中的所有数据
void clear()
//在当前Map集合中,通过指定的key值,获取对应的value
V get(Object key)
//在当前Map集合中,移除指定key及其对应的value
V remove(Object key)
//返回当前Map集合中的元素个数(一对key-value,算一个元素数据)
int size()
//判断当前Map集合是否为空
boolean isEmpty()
//返回Map集合中所有的key值
Set<K> keySet()
//返回Map集合中所有的value值
Collection<V> values()
//把Map集合中的的key-value封装成Entry类型对象,再存放到set集合中,并返回
Set<Map.Entry<K,V>> entrySet()
具体使用
package com.zyz.chapter8.Map;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class te1 {
public static void main(String[] args) {
Map map = new HashMap();
map.put(1,"tom");
map.put(2,"jack");
map.put(3,"lucy");
map.put(4,"mary");
System.out.println("map是否为空:"+map.isEmpty());
System.out.println("map中元素的个数:"+map.size());
System.out.println("map中是否包含指定key值1:"+map.containsKey(1));
System.out.println("map中是否包含指定value值mary:"+map.containsValue("mary"));
System.out.println("map中获取指定key值1对应的value值:"+map.get(1));
System.out.println("map中获取指定key值5对应的value值:"+map.get(5));
/* Map集合的这三种遍历方式,在【Map的遍历】部分有详细的画图说明 */
System.out.println("--------获取map中所有的kay值--------");
Set keys = map.keySet();
for(Object key : keys){
System.out.println(key+" : "+map.get(key));
}
System.out.println("--------获取map中所有的value值--------");
Collection values = map.values();
for(Object value : values){
System.out.println(value);
}
System.out.println("--------获取map中所有的key-value(键值对),封装成Entry类型对象-- ------");
Set entrySet = map.entrySet();
for(Object obj : entrySet){
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey()+" : "+entry.getValue());
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t2mi9n06-1638149457794)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210617213918485.png)]
1.11 Map实现类
四种实现类
Map 接口有很多实现类,以及子接口:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EQ4eOhcq-1638149457796)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210617214010402.png)]
代码中经常会用到的有: HashMap
、 HashTable
、 TreeMap
、 LinkedHashMap
;
HashMap
:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重 复,需 要重写键的hashCode
()方法、equals
()方法。(重要,最常用)
HashTable
:和之前List集合中的 Vector 的功能类似,可以在多线程环境中,保证集合中的数据的操 作安全,类中的方法大多数使用了 synchronized
修饰符进行加锁。(线程安全)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lM38CbWK-1638149457797)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210619213210500.png)]
TreeMap
:该类是 Map 接口的子接口 SortedMap
下面的实现类,和 TreeSet 类似,它可以对key值进 行排序,同时构造器也可以接收一个比较器对象作为参数。支持key值的自然排序和比较器排序俩种方 式。(支持key排序)
LinkedHashMap
:该类是 HashMap
的子类,存储数据采用的哈希表结构+链表结构。通过链表结构可 以保证元素的存取顺序一致;(存入顺序就是取出顺序)
TreeMap
TreeMap 的使用(自然排序):
public static void main(String[] args) {
Map map=new TreeMap();
map.put(4,"lisa");
map.put(3,"mary");
map.put(1,"nana");
Set keys=map.keySet();
for (Object key :keys) {
System.out.println(key+" "+map.get(key));
}
}
//运行结果
1 nana
3 mary
4 lisa
TreeMap 的使用(比较器排序):
public static void main(String[] args) {
Comparator c = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Integer k1 = (Integer) o1;
Integer k2 = (Integer) o2;
//注意,这里在默认的比较结果基础上加了一个符号,即原来返回的正数变负数、返回的负数变正数
return -k1.compareTo(k2);
}
};
//构造器中传入比较器对象
Map map = new TreeMap(c);
map.put(4,"mary");
map.put(2,"jack");
map.put(1,"tom");
map.put(3,"lucy");
Set keys = map.keySet();
for(Object key : keys){
System.out.println(key+" : "+map.get(key));
}
}
//运行结果:
4 : mary
3 : lucy
2 : jack
1 : tom
LinkedHashMap
的使用:
public static void main(String[] args) {
Map map = new LinkedHashMap();
map.put(4,"mary");
map.put(2,"jack");
map.put(1,"tom");
map.put(3,"lucy");
Set keys = map.keySet();
for(Object key : keys){
System.out.println(key+" : "+map.get(key));
}
}
//运行结果:
4 : mary
2 : jack
1 : tom
3 : lucy
可以看出,数据存入Map中的顺序,就是存储的顺序,也是取出的顺序!
1.12 Map的遍历
Map中的keySet方法,可以返回Map集合中所有的key值的集合:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1HgdT2MO-1638149457800)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210617220125521.png)]
拿到Set集合后,就可以遍历Set集合拿到Map中每个key值,通过key值可以获取到对应的value值 (get方法)
Map中的values方法,可以返回Map集合中所有的value值的集合:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J6peF4NV-1638149457802)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210617220223689.png)]
拿到Collection集合后,可以可以遍历Collection集合拿到Map中每一个value值,但是这时候拿不到 相应的key
entrySet方法
Map 接口中有定义了一个内部接口 Entry :(类似于内部类)
public interface Map<K,V>{
interface Entry<K,V> {
K getKey();
V getValue();
}
}
一个Entry
类型的对象,可以代表Map
集合中的一组key
-value
(键值对),并且提供了获取key
值和value
值的方法。
Map
接口中的 entrySet
() 方法,就是将 Map 集合中的每一组key-value(键值对)都封装成一个 Entry 类型对象,并且把这些个 Entry 对象存放到Set集合中,并返回。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZaPX8RrJ-1638149457803)(C:\Users\ZYZ\AppData\Roaming\Typora\typora-user-images\image-20210617220518228.png)]
public static void main(String[] args) {
Map map = new LinkedHashMap();
map.put(4,"mary");
map.put(2,"jack");
map.put(1,"tom");
map.put(3,"lucy");
Set entrySet = map.entrySet();
for(Object obj : entrySet){
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey()+" : "+entry.getValue());
}
}
//运行结果:
4 : mary
2 : jack
1 : tom
3 : lucy
1.13集合工具类
java.util.Arrays
是一个工具类,专门用来操作数组对象的,里面都是静态方法,可直接调用。 java.util.Collections
也是一个工具类,专门用来操作集合对象的,里面都是静态方法,可以直接 调用。
注意, Collection 接口 和 Collections 类 的区别!
Collections 中的常用方法:
-
fill方法,使用指定元素替换指定列表中的所有元素
List list = new ArrayList(); list.add(1); list.add(2); list.add(3); Collections.fill(list, 20); for(Object o:list){ System.out.println(o); }
-
max方法,根据元素的自然顺序,返回给定集合的最大元素
List list = new ArrayList(); list.add(1); list.add(9); list.add(3); System.out.println(Collections.max(list));
-
min方法,根据元素的自然顺序,返回给定集合的最小元素
-
reverse方法,反转集合中的元素
List list = new ArrayList(); list.add(1); list.add(9); list.add(3); Collections.reverse(list); for(Object o:list){ System.out.println(o); }
-
sort方法,根据元素的自然顺序,对指定列表按升序进行排序
List list = new ArrayList(); list.add(1); list.add(9); list.add(3); //如果需要,也可以在第二个参数位置传一个比较器对象 //Collections.sort(list,c); Collections.sort(list); for(Object o:list){ System.out.println(o); }
-
shuffle方法,使用默认随机源对指定列表进行置换
List list = new ArrayList(); list.add(1); list.add(9); list.add(3); Collections.shuffle(list); for(Object o:list){ System.out.println(o); }
-
addAll方法,往集合中添加一些元素
List list = new ArrayList(); //注意,addAll的第二个参数,是可变参数 Collections.addAll(list,1,3,5,7); for(Object o:list){ System.out.println(o); }
-
synchronizedCollection
,把非线程安全的Collection
类型集合,转为线程安全的集合 -
synchronizedList
,把非线程安全的List
类型集合,转为线程安全的集合 -
synchronizedSet
,把非线程安全的Set
类型集合,转为线程安全的集合 -
synchronizedMap
,把非线程安全的Map
类型集合,转为线程安全的集合
1.14 案例
完成斗地主游戏的生成牌、洗牌、发牌、查看三名玩家的牌及三张底牌
package com.zyz.chapter8.Map;
import java.util.*;
public class Games {
public static void main(String[] args) {
//准备三个List集合,分别存放花色信息、点数信息、以及组合好的54张牌
List colors = new ArrayList();
List numbers = new ArrayList();
List cards = new ArrayList();
//向colors集合中添加4种花色
Collections.addAll(colors,"♠","♥","♣","♦");
//向numbers集合中添加13种点数
Collections.addAll(numbers,"A","2","3","4","5","6","7","8","9","10","J","Q","K"
);
//存储每种牌的权重信息(比较大小用的,方便排序)
Map cardWeight = new HashMap();
cardWeight.put("3",1);
cardWeight.put("4",2);
cardWeight.put("5",3);
cardWeight.put("6",4);
cardWeight.put("7",5);
cardWeight.put("8",6);
cardWeight.put("9",7);
cardWeight.put("10",8);
cardWeight.put("J",9);
cardWeight.put("Q",10);
cardWeight.put("K",11);
cardWeight.put("A",12);
cardWeight.put("2",13);
cardWeight.put("小王",14);
cardWeight.put("大王",15);
//让4种花色和13种点数 分别配对组合成52张牌
for(int i=0;i<colors.size();i++){
String color = (String) colors.get(i);
for(int j=0;j<numbers.size();j++){
String number = (String) numbers.get(j);
//根据number从Map集合中获取这个牌的权重值
int weight = (int) cardWeight.get(number);
//把当前花色、点数、权重封装成Card对象,保存到cards集合中
//Card是下面自定义的一个类型,封装了牌的信息
cards.add(new Card(color,number,weight));
}
}
//单独处理并存放,大王和小王这俩张牌
cards.add(new Card("","小王",(int) cardWeight.get("小王")));
cards.add(new Card("","大王",(int) cardWeight.get("大王")));
//此时,cards集合中共有54张牌,进行随机元素调换位置(洗牌)
Collections.shuffle(cards);
//map集合存放3名玩家和三张底牌
Map map = new LinkedHashMap();
List player1 = new ArrayList();
List player2 = new ArrayList();
List player3 = new ArrayList();
List threeCard = new ArrayList();
map.put("1号玩家",player1);
map.put("2号玩家",player2);
map.put("3号玩家",player3);
map.put("底牌",threeCard);
//循环遍历cards,进行发牌
for(int i=0;i<cards.size();i++){
//先拿出这张牌
Card card = (Card) cards.get(i);
//前51张牌发给三名玩家
if(i<51){
//三名玩家,依次抓牌
if(i%3==0){
player1.add(card);
}
else if(i%3==1){
player2.add(card);
}
else if(i%3==2){
player3.add(card);
}
}
//最后三张,存到指定集合中
else{
threeCard.add(card);
}
}
//创建比较器对象(匿名内部类),根据牌的权重比较大小进行排序
Comparator c = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Card c1 = (Card) o1;
Card c2 = (Card) o2;
return c1.weight-c2.weight;
}
};
//对三名玩家手里的牌和底牌进行按权重排序
Collections.sort(player1,c);
Collections.sort(player2,c);
Collections.sort(player3,c);
Collections.sort(threeCard,c);
//最后,显示三名玩家手里的牌,以及三张底牌
for(Object key:map.keySet()){
System.out.println(key+" : "+map.get(key));
}
}
}
//封装扑克牌的信息
class Card{
//花色
String color;
//点数
String number;
//权重(比较大小用的)
int weight;
public Card(String color, String number, int weight) {
this.color = color;
this.number = number;
this.weight = weight;
}
//重写toString方法,方便输出牌的信息
@Override
public String toString() {
return color+number;
}
}
//运行结果:
1号玩家 : [♥3, ♥4, ♠5, ♣6, ♠6, ♥7, ♥8, ♠9, ♥9, ♦10, ♠10, ♥10, ♥Q, ♦K, ♣A, ♥A,
♦2]
2号玩家 : [♠3, ♦4, ♥5, ♣5, ♦7, ♣7, ♦8, ♣J, ♠J, ♦J, ♠Q, ♦Q, ♣Q, ♣K, ♠K, ♥2, ♠2]
3号玩家 : [♣3, ♦3, ♣4, ♠4, ♦5, ♦6, ♠8, ♣8, ♣9, ♦9, ♣10, ♥J, ♦A, ♠A, ♣2, 小王,
大王]
底牌 : [♥6, ♠7, ♥K]