Collection、泛型
1,、Collection概念
- Collection集合:集合是java中提供的一种容器,可以用来存储多个数据。
集合和数组区别:
数组:长度固定 .................集合:长度可变
数组:同一类型的元素 .......集合:存储的都是对象。而且对象的类型可以不一致
Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,子接口分别是java.util.List
和java.util.Set
。
List
的特点是元素有序、元素可重复、有索引可用for循环
Set
的特点是元素无序,存取无序,不允许元素重复,没索引(不能用普通for循环)。
2、单列集合的体系结构
List集合:
ArrayList:底层是数组实现的,查询快,增删慢
LinkedList:底层是链表式的,查询慢,增删快
Set集合:
HashSet:底层是哈希表+(红黑树)实现的,无索引、不可以存储重复元素、存取无序
LinkedHashSet:底层是哈希表+链表实现的,无索引,不可以存储重复元素,可以保证存取顺序。
TreeSet:底层是二叉树实现,一般用于排序。
3 Collection 常用功能
集合(List和Set)常用的一些方法:
boolean add(E e)
: 向集合中添加元素 。void clear()
:清空集合中所有的元素。boolean remove(E e)
: 把给定的对象在当前集合中删除。boolean contains(E e)
: 判断当前集合中是否包含给定的对象。boolean isEmpty()
: 判断当前集合是否为空。int size()
: 返回集合中元素的个数。Object[] toArray()
: 把集合中的元素,存储到数组中。
创建集合对象,可以使用多态:Collection coll = new ArrayList<>();
3、Iterator迭代器
在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator
。
- 迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。对集合进行遍历
Iterator接口的常用方法:
public <E> next()
:返回迭代的下一个元素。public boolean hasNext()
:判断,如果仍有元素可以迭代,则返回 true。
Iterator迭代器是一个借口,无法直接使用,需要借助实现类对象
迭代器使用步骤(重点):
1、使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态)
2、使用Iterator接口中的方法hasNext()判断还有没有下一个元素
3、使用Iterator接口中的方法next()取出集合中的下一个元素
示例:Collection<String> coll = new ArrayList<> [];
注意:Iterator接口也是泛型的,迭代器的泛型跟着集合走,迭代就是什么泛型迭代器取出集合中的元素代码,是一个重复的过程,可以循环优化。
不知道元素数量,可以使用while循环:
while (coll.hasNext()){
String e = coll.next();
System.out.printlln(e);
}
迭代器&增强for:
迭代器
1.通过集合对象取代迭代器对象
2.使用循环遍历迭代器
3.判断迭代器对象中是都还有元素
4.有元素则获取
增强for循环:底层使用的也是迭代器,简化了迭代器的书写。
for(数据类型 变量: 容器对象 (Collection集合or数组)){
循环体语句
}
注意事项:新for循环必须有被遍历的目标。目标只能是Collection或者是数组。新式for仅仅作为遍历操作出现。
例:
int[ ] arry = {1,2,3,4,5}
//使用增强for循环遍历数组
for(int i:arr){
Symstem.out.prinln(i);
}
迭代器的实现原理
coll.iterator();获取迭代器的实现类对象,并把指针指向集合的-1索引
while (coll.hasNext()) 判断集合中还有没有下一个元素
String e = coll.next(); 1.取出下一个元素 2.把指针向后移动一位
2.泛型
泛型:是一种未知的数据类型,当我们不知道使用数据类型的时候可以使用泛型。
泛型也可以看成是一个变量,用来接收数据类型 E e:Element T t:Type
使用泛型的好处
创建集合对象,不适用泛型
好处:集合不使用泛型,默认的类型就是Object类型,可以存储任意类型的数据。
弊端:不安全,会引发异常。
创建集合对象,使用泛型
好处
- 避免了类型强转的麻烦。
- 将运行时期的运行异常ClassCastException,转移到了编译时期变成了编译失败。
弊端:
泛型是什么类型,只能存储什么类型的数据。
泛型的定义和使用
创建对象的时候确定泛型的类型
修饰符 class 类名<代表泛型的变量> { }
含有泛型的方法
定义格式:
修饰符<泛型>返回值类型 方法名(参数列表(使用泛型)){
方法体
}
例:
public <M> void method02(M m){
方法体
}
含有泛型的接口
定义格式:
修饰符 interface接口名<代表泛型的变量> { }
public interface MyGenericInterface<E>{
public abstract void add(E e);
public abstract E getE(); }
两种使用方法:
1、定义类时确定泛型的类型
2、始终不确定泛型的类型,直到创建对象时,确定泛型的类型
4、泛型通配符(?)
4.1 概念
泛型通配符(?):代表任意的数据类型
使用方法:不能创建对象使用,只能作为方法使用。
注意:泛型是没有继承概念的。
泛型的上限限定:?extens E代表使用的泛型只能是E类型的子类/本身
泛型的下限限定:?super E代表使用的泛型只能是E类型的父类/本身
4.2 斗地主案例
1,准备牌:
54张牌,存储到一个集合中
特殊性:大王小王 其他52张牌:♠ ♥ ♦ ❀
定义一个数组/集合,存储4种花色
定义一个数组/集合,存储13个序号
循环遍历两个数组/集合:组装52张牌
2,洗牌
使用集合工具类Collection方法
static void shuffle(List<?> list)使用指定的随机源对指定列表进行置换。
会随机打乱集合的顺序
3、发牌
要求:1人17张牌,剩余3张伟底牌,一人一张轮流发票:集合的索引(0-53)%3
4,看牌
直接打印集合,遍历存储玩家和底牌的集合
练习代码
public class DouDiZHU {
public static void main(String[] args) {
//准备牌 准备54张牌 ArrayList集合,String泛型
ArrayList<String> poker = new ArrayList<>();
//连个数组 一个花色一个序号
String[] colors = {"♠", "♥", "♦", "❀"};
String[] numbers = {"2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"};
poker.add("大王");
poker.add("小王");
//2.组装扑克 循环嵌套遍历两个数组,组装52张牌
for (String number : numbers)
for (String color : colors) {
//System.out.println(color+number);
poker.add(color + number);
}
System.out.println(poker);
//3,洗牌,使用shuffle方法
Collections.shuffle(poker);
System.out.println(poker);
//发牌 定义4集合,存储玩家的牌和底牌
ArrayList<String> p1 = new ArrayList<>();
ArrayList<String> p2 = new ArrayList<>();
ArrayList<String> p3 = new ArrayList<>();
ArrayList<String> p4 = new ArrayList<>();
//遍历poker集合,获取每一张牌,轮流发牌
//剩余3张给底牌
//要先留底牌不然牌发完了
for (int i = 0; i < poker.size(); i++) {
//获取牌
String p = poker.get(i);
if (i >= 51) {
p4.add(p);
} else if (i % 3 == 0) {
p1.add(p);
} else if (i % 3 == 2) {
p2.add(p);
} else if (i % 3 == 1) ;
p3.add(p);
}
System.out.println("玩家1:"+p1);
System.out.println("玩家1:"+p2);
System.out.println("玩家1:"+p3);
System.out.println("底牌:"+p4);
}
}
5.常见数据结构
数据结构:数据存储的常用结构有:栈、队列、数组、链表和红黑树
栈:先进后出(像弹匣) :
栈的入口、出口的都是栈的顶端位置。
队列
先进先出: 队列的入口、出口各占一侧。例如,下图中的左侧为入口,右侧为出口。
数组:
查询快:数组的地址是连续的,通过数组的首地址可以找到数组,通过数组的索引可以快速查找某一个元素。
增删慢:数组的长度是固定的,我们想要增加/删除一个元素,必须创建一个新数组,把数组的数据复制过来。
在堆内存中,频繁地创建数组、复制数组中的元素,销毁数组,效率低下。
链表
查询慢:链表中地址不是连续的,每次查询元素都要从头开始
增删快:链表结构,增加/删除一个元素,对链表的整体结构没有影响,所以增删快。
单向链表:链表中只有一条链子,不能保证元素的顺序(存储和取出的顺序可能不一致)
双向链表:链表中有两条链子,有一条链子是专门记录元素的顺序,是一个有序的集合
红黑树
二叉树:binary tree ,是每个结点不超过2的有序树(tree) 。 每个结点上都最多只能有两个子结点。
排序树/查找树:在二叉树基础上,元素是有大小顺序的,左子树小,右子树大
平衡树:左孩子和右孩子是相等的
不平衡树:左孩子不等于右孩子
红黑树:趋近于平衡树,查询速度非常快,查询叶子节点最大次数和最小次数和最小次数不能超过2倍。
红黑树的约束:
- 节点可以是红色的或者黑色的
- 根节点是黑色的
- 叶子节点(特指空节点)是黑色的
- 每个红色节点的子节点都是黑色的
- 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同