集合【迭代器、增强for、泛型】
1.1.1 集合介绍
前面的学习,我们知道数据多了,使用数组存放。而且数组中存放的都是基本类型的数据,并且数组是定长的。当在程序中创建的对象比较多的时候,需要对这些对象进行统一的管理和操作,那么首先我们就需要把这些对象存储起来。使用数组是可以存放对象的,我们可以定义对象数组来存放,但是数组这个容器存放对象,要对其中的对象进行更复杂操作时,数据就显的很麻烦。那怎么办呢?
Java中给我们提供了另外一类容器,专门用来存放对象,这个容器就是我们要学习的集合。
集合和数组既然都是容器,它们有啥区别呢?
数组的长度是固定的。集合的长度是可变的。
数组中存储的是同一类型的元素,可以存储基本数据类型值。集合存储的都是对象。而且对象的类型可以不一致。
集合貌似看起来比较强大,它啥时用呢?
当对象多的时候,先进行存储。
1.1.2 集合框架的由来
集合本身是一个工具,它存放在java.util包中。
JDK最早的1.0版本中。提供的集合容器很少。升级到1.2版,为了更多的需求,出现了集合框架。有了更多的容器。可以完成不同的需求。
这些容器怎么区分?区分的方式:每一个容器的数据结构(数据存储到的一种方式)不一样。
不同的容器进行不断的向上抽取,最后形成了一个集合框架,这个框架就是Collection接口。在Collection接口定义着集合框架中最最共性的内容。
在学习时:我们需要看最顶层怎么用, 创建底层对象即可。因为底层继承了父类中的所有功能。
1.1.3 Collection接口的描述
既然Collection接口是集合中的顶层接口,那么它中定义的所有功能子类都可以使用。查阅API中描述的Collection接口。Collection 层次结构中的根接口。Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。
其实我们在使用ArrayList类时,该类已经把所有抽象方法进行了重写。那么,实现Collection接口的所有子类都会进行方法重写。
l Collecton接口常用的子接口有:List接口、Set接口
l List接口常用的子类有:ArrayList类、LinkedList类
l Set接口常用的子类有:HashSet类、LinkedHashSet类
继续查阅API,发现Collection接口中很多集合的操作方法,那么这些方法都具体能做什么呢?
1.1.4 Collection基本方法了解
1.2 Collection接口的基本方法
这里我们不关心具体创建的Collection中的那个子类对象,这里重点演示的是Collection接口中的方法
Collection<String> coll = newArrayList<String>();
//1,往集合中添加对象元素。add(Object);
coll.add("tiger1");
coll.add("tiger2");
coll.add("tiger3");
//2,删除。
coll.remove("tiger2");
//3,判断是否包含。
System.out.println(coll.contains("tiger11"));
//4,清除。
coll.clear();
//把集合打印一下。
System.out.println(coll);//[itcast1, itcast2, itcast3]
第2章 Iterator迭代器
2.1 Iterator迭代器概述
java中提供了很多个集合,它们在存储元素时,采用的存储方式不同。我们要取出这些集合中的元素,可通过一种通用的获取方式来完成。
Collection集合元素的通用获取方式:在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
集合中把这种取元素的方式描述在Iterator接口中。Iterator接口的常用方法如下:
l hasNext()方法:用来判断集合中是否有下一个元素可以迭代。如果返回true,说明可以迭代。
l next()方法:用来返回迭代的下一个元素,并把指针向后移动一位。
迭代集合元素图解:
2.2 Iterator迭代方式的代码体现
在Collection接口描述了一个抽象方法iterator方法,所有Collection子类都实现了这个方法,并且有自己的迭代形式。
进行代码演示:
//1,创建集合对象。
Collection<String> coll= newArrayList<String>();
coll.add("tiger1");
coll.add("tiger2");
coll.add("tiger3");
coll.add("tiger4");
//2.获取容器的迭代器对象。通过iterator方法。
Iterator it = coll.iterator();
//3,使用具体的迭代器对象获取集合中的元素。参阅迭代器的方法
while(it.hasNext()){
System.out.println(it.next());
}
/*
迭代器for循环的形式的使用
for(Iterator it = coll.iterator(); it.hasNext(); ) {
System.out.println(it.next());
}
*/
注意:在进行集合元素取出时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会发生java.util.NoSuchElementException没有集合元素的错误。
下边分别介绍以上内容:
a) Collection接口的iterator
方法声明为:
Iterator<集合中数据类型>iterator()
用来返回专属于该集合对象的迭代器对象(Iterator的子类对象)。
b) Iterator接口
该接口规定了迭代集合所需要的方法
c) Iterator接口的两个方法:hasNext与next方法
Iterator规定了两个方法,集合对象产生的迭代器对象正是通过这两个方法帮助集合进行迭代工作的。
调用迭代器的hasNext方法判断是否有下一个元素
调用迭代器的next获取下一个元素
2.3 并发修改异常
迭代的常规用法中我们要尽量避免在迭代过程中为集合添加/删除数据。否则会报错,原因是Java抛出了并发修改异常。
迭代过程中并发修改异常的原因为迭代器中”记忆”的集合长度与集合中实际长度不同,而导致出现索引与实际元素不符甚至无限循环的情况发生。
所以在使用Iterator时,避免类似操作,for循环底层为迭代器实现,所以也需要避免类似操作。
有些迭代器避免了这样的问题,如ListIterator,但该类并不通用也不常用,实际开发中很少使用,只需要简单了解。
java中提供了很多个集合,它们在存储元素时,采用的存储方式不同。我们要取出这些集合中的元素,可通过一种通用的获取方式来完成。
2.4 增强for循环
增强for循环是JDK1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。
格式:
for(元素的数据类型 变量 : Collection集合or数组){
}
它用于遍历Collection和数组。通常只进行遍历元素,不要在遍历的过程中对集合元素进行增删操作。
练习一:遍历数组
int[] arr = new int[]{11,22,33};
for (int n : arr) {//变量n代表被遍历到的数组元素
System.out.println(n);
}
练习二:遍历集合
Collection<String> coll = new ArrayList<String>();
coll.add("tiger1");
coll.add("tiger2");
coll.add("tiger3");
for(String str : coll){//变量Str代表被遍历到的集合元素
System.out.println(str);
}
增强for循环和老式的for循环有什么区别?
注意:新for循环必须有被遍历的目标。目标只能是Collection或者是数组。
建议:遍历数组时,如果仅为遍历,可以使用增强for如果要对数组的元素进行操作,使用老式for循环可以通过角标操作(可以获取指定角标位置数据)。
第3章 泛型
3.1 泛型概述
泛型用来灵活地将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数传递。
泛型是数据类型的一部分,我们将类名与泛型合并一起看做数据类型。
泛型的定义:定义泛型可以在类中预支地使用未知的类型。
泛型的使用:一般在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为Object类型。
3.2 使用泛型的好处
l 将运行时期的ClassCastException,转移到了编译时期变成了编译失败。
l 避免了类型强转的麻烦。
演示下列代码:
publicclass GenericDemo {
publicstaticvoidmain(String[] args) {
Collection<String>list = newArrayList<String>();
list.add("abc");
list.add("tiger");
//list.add(5);//当集合明确类型后,存放类型不一致就会编译报错
//集合已经明确具体存放的元素类型,那么在使用迭代器的时候,迭代器也同样会知道具体遍历元素类型
Iterator<String>it = list.iterator();
while(it.hasNext()){
String str = it.next();
//当使用Iterator<String>控制元素类型后,就不需要强转了。获取到的元素直接就是String类型
System.out.println(str.length());
}
}
}
3.3 泛型的定义与使用
我们在集合中会大量使用到泛型,这里来完整地学习泛型知识。
泛型,用来灵活地将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数进行传递。
3.3.1 含有泛型的类
定义格式:修饰符 class 类名<代表泛型的变量> { }
n 例如,API中的ArrayList集合:
class ArrayList<E>{
public boolean add(E e){ }
public Eget(int index){ }
}
使用格式:创建对象时,确定泛型的类型
n 例如,ArrayList<String> list = new ArrayList<String>();
此时,变量E的值就是String类型
class ArrayList<String>{
public boolean add(String e){ }
public Stringget(int index){ }
}
n 例如,ArrayList<Integer> list = new ArrayList<Integer>();
此时,变量E的值就是Integer类型
class ArrayList<Integer>{
public boolean add(Integer e){ }
public Integerget(int index){ }
}
举例自定义泛型类
publicclass GenericClass<E>{//自定义的类中,可以写<>泛型
//E 表示未知的数据类型 调用者创建对象的时候,才能明确数据类型
private E e;
publicvoid setE(E e){
this.e = e;
}
public E getE(){
return e;
}
}
publicclass GenericClassTest {
publicstaticvoid main(String[] args) {
//对自定义的泛型类,进行测试
GenericClass<Integer>g = newGenericClass<Integer>();
//E传递什么类型就是什么类型
g.setE(100);
Integer i =g.getE();
System.out.println(i);
}
}
3.3.2 含有泛型的方法
定义格式:修饰符 <代表泛型的变量> 返回值类型方法名(参数){ }
例如,
publicclass GenericMethod <E>{
publicvoid show(E e){
System.out.println(e);
}
public<T>void function(Tt){//自定义泛型的方法
//自己写一个方法,方法中的数据类型,采用<>泛型
//如果方法中的泛型,和类上的泛型不同
// 在方法返回值前加入<>
System.out.println(t);
}
}
使用格式:调用方法时,确定泛型的类型
publicclass GenericMethodTest {
publicstaticvoid main(String[] args) {
GenericMethod<Double>g = newGenericMethod<Double>();
g.show(1.1);
g.function(1.2F);//传递什么类型就是什么类型
}
}
3.3.3 含有泛型的接口
定义格式:修饰符 interface接口名<代表泛型的变量> { }
n 例如,
publicinterface Inter <E>{
publicabstractvoid show(E e);
}
使用格式:
1、定义类时确定泛型的类型
n 例如
publicclass InterImpl implementsInter<Integer>{
publicvoid show(Integer i){
System.out.println(i);
}
}
此时,变量E的值就是Integer类型。
2、始终不确定泛型的类型,直到创建对象时,确定泛型的类型
n 例如
InterImpl<String> imp= new InterImpl<String>();
此时,变量E的值就是String类型。
publicclass InterImpl<E>implements Inter<E>{
publicvoid show(E e){
System.out.println(e);
}
}
3.4 泛型通配符
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
定义:(查看ArrayList的构造方法)无法在类中使用
使用:调用方法时可以给予任意类型。参照Arraylist的构造方法
? extends E代表只要是E类型的子类即可
? super E代表只要是E类型的父类即可
/*
* 泛型通配符?,代表任意的数据类型
*
* 定义:(查看ArrayList的构造方法)无法在类中使用
*
* 使用:调用方法时可以给予任意类型。参照Arraylist的构造方法
* publicArrayList(Collection<? extends E> c)
* 为了便于?的理解,我们将以上方法重写为public ArrayList(ArrayList<? extends E> c)
*
* 该方法的意思:创建集合对象A时,给于另外一个集合对象B作为参数,则创建好的集合A中包含了集合B中的元素
*
* ? extends E代表只要是E类型的子类即可
* ? super E代表只要是E类型的父类即可
*/
publicclass Demo01 {
publicstaticvoid main(String[] args) {
//定义集合b,包含3个元素
ArrayList<String>listB = newArrayList<String>();
listB.add("Jack");
listB.add("Rose");
listB.add("Trump");
//使用集合b创建集合a
ArrayList<Object>listA = newArrayList<Object>(listB);
listA.add("Obama");
//观察集合A
System.out.println(listA);
}
第4章 集合综合案例
4.1 案例介绍
按照斗地主的规则,完成洗牌发牌的动作。
具体规则:
使用54张牌打乱顺序
三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌。
4.2 案例需求分析
l 准备牌:
牌可以设计为一个ArrayList<String>,每个字符串为一张牌。
每张牌由花色数字两部分组成,我们可以使用花色集合与数字集合嵌套迭代完成每张牌的组装。
牌由Collections类的shuffle方法进行随机排序。
l 发牌:
将每个人以及底牌设计为ArrayList<String>,将最后3张牌直接存放于底牌,剩余牌通过对3取模依次发牌。
l 看牌:
直接打印每个集合。
4.3 实现代码步骤
修改文件编码由GBK修改为UTF-8,因为GBK没有我们要的梅花、方片、黑桃、红桃(♠♥♦♣)等字符。
publicclass Poker {
publicstaticvoidmain(String[] args) {
//♠♥♦♣
//准备牌
ArrayList<String> poker = new ArrayList<String>();
//花色
ArrayList<String> color = new ArrayList<String>();
color.add("♠");
color.add("♥");
color.add("♦");
color.add("♣");
//数字
ArrayList<String> number = new ArrayList<String>();
for (int i = 2; i <= 10; i++) {
number.add(i+"");
}
number.add("J");
number.add("Q");
number.add("K");
number.add("A");
//完成新牌
for (StringthisColor : color) {
for(String thisNumber : number) {
String thisCard = thisColor + thisNumber;
poker.add(thisCard);
}
}
poker.add("小☺");
poker.add("大☻");
//洗牌
Collections.shuffle(poker);
//发牌
//玩家1
ArrayList<String> player1 = new ArrayList<String>();
//玩家2
ArrayList<String> player2 = new ArrayList<String>();
//玩家3
ArrayList<String> player3 = new ArrayList<String>();
//底牌
ArrayList<String> secretCards = new ArrayList<String>();
for (int i = 0; i < poker.size(); i++) {
if(i>=51){
//最后三张发给底牌
secretCards.add(poker.get(i));
}else {
//剩余牌通过对3取模依次摸牌
if(i%3==0){
player1.add(poker.get(i));
}elseif(i%3==1) {
player2.add(poker.get(i));
}else{
player3.add(poker.get(i));
}
}
}
//看牌
System.out.println(player1);
System.out.println(player2);
System.out.println(player3);
System.out.println(secretCards);
}
}
l 最后发到三个人手中的牌是无序的,在明天学习完Map集合后,我们提供一个排序的解决方案。
01.集合_集合框架体系结构介绍
Collection(接口):单列集合
|--List(接口):特点:1.有序的;2.可以存储重复元素;
|--ArrayList(子类--基础班学过了):数组实现
|--LinkedList(子类):链表实现
|--Set(接口):特点:1.无序的;2.不能存储重复元素;
|--HashSet(子类):哈希表实现
|--LinkedHashSet(子类):链表、哈希表实现。
Map(接口):双列集合,注意:数据结构都是用在"键"上
|--HashMap(子类):"键"是哈希表结构。
|--LinkedHashMap(子类):"键"是链表和哈希表结构。
02.集合_Collection接口的常用方法
1).boolean add(E e):将元素e添加到集合中。返回值:存储成功返回true(对于List集合基本都返回true),存储失败返回false(对于Set集合如果存储重复元素返回false)
2).void clear() : 清空集合。
3).boolean contains(Object o) : 判断集合中是否包含元素o
4).boolean remove(Object o) : 在集合中移除元素o。如果有多个,只移除一个。
5).int size():获取集合大小。
6).Object[] toArray() : 将集合内的元素封装到一个Object[]数组中返回。将集合转换为数组。
7).Iterator<E> iterator():获取一个迭代器,用于遍历集合元素。
03.集合_迭代器的概念_基本使用
Collection<Integer> list = new ArrayList<>();
list.add(10);
list.add(5);
list.add(2);
list.add(7);
//获取一个迭代器
Iterator<Integer> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
迭代器(Iterator)作用:遍历集合
04.集合_迭代器的执行流程图解
1).常见问题:
1).Collection<Integer> list = new ArrayList<>();
list.add(10);
list.add(5);
list.add(2);
list.add(7);
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());//打印
}
while(it.hasNext()){//hasNext()返回false,不进入循环。
System.out.println(it.next());
}
2).
Collection<Integer> list = new ArrayList<>();
list.add(10);
list.add(5);
list.add(2);
list.add(7);
Iterator<Integer> it = list.iterator();
while(it.hasNext()){//必须要保证一次hasNext()对应一次next()
Integer v = it.next();//v = 10,v = 2
System.out.println(it.next());//5,7
}
05.集合_练习_迭代器操作自定义对象
Collection<Student> stuList = new ArrayList<>();
stuList.add(new Student("章子怡",20,'女'));
stuList.add(new Student("汪峰",22,'男'));
stuList.add(new Student("撒贝宁",21,'男'));
stuList.add(new Student("李白",18,'女'));
Iterator<Student> stuIt = stuList.iterator();
while(stuIt.hasNext()){
Student stu = stuIt.next();
System.out.println(stu);
}
06.集合_迭代器的并发修改异常
1).示例:
Collection<Integer> 撒贝宁List = new ArrayList<>();
撒贝宁List.add(200000);
撒贝宁List.add(300000);
撒贝宁List.add(150000);
撒贝宁List.add(700000);//灰色收入
Iterator<Integer> 李白It = 撒贝宁List.iterator();
while(李白It.hasNext()){
Integer money = 李白It.next();
System.out.println(money);
if(money == 700000){
撒贝宁List.remove(700000);
}
}
2).并发修改异常产生的原因:通过迭代器遍历元素时,通过集合对象删除了元素,导致迭代器的数量与集合内元素的数量不一致,这时会产生并发修改异常。
3).解决并发修改异常:通过迭代器遍历时,就通过迭代器删除。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
07.增强for循环
1).可以遍历:数组--底层编译为:普通for循环
int[] arr = {1,2,432,4,324,32};
for(int n : arr){
System.out.println(n);
}
2).可以遍历:集合--底层编译为:迭代器(注意:并发修改异常)
Collection<String> strList = new ArrayList<>();
strList.add("孙悟空");
strList.add("猪八戒");
strList.add("白骨精");
strList.add("牛魔王");
for(String s : strList){
System.out.println(s);
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
08.泛型_基本概念_使用方式
ArrayList<String> list = new ArrayList<>();
list.add("孙悟空");
list.add("猪八戒");
for(int i = 0;i < list.size(); i++){
String s = list.get(i);
....
}
//使用增强for
for(String s : list){
System.out.println(s);
}
09.泛型_泛型的优点
1).泛型的优点:可以为集合指定只装某种具体的类型,取出时非常方便,不用向下转型。
2).泛型的特点:泛型只存在于编译期,编译后在class文件中没有泛型的信息。
10.泛型_泛型类的定义
public class Student<A> {
public void add(A obj){
}
public A get(){
return null;
}
}
泛型定义的说明:
1).泛型名称:可以由一个字符组成,也可以由多个字符组成
public class Student<Abc>{
public void add(Abc obj){
}
public Abc get(){
return null;
}
}
2).可以同时定义多个泛型名称,中间用逗号隔开。
public class Student<a,b,c>{
public void add(a obj){
}
public b get(){
return null;
}
}
3).使用时可以不使用泛型,那么内部方法应用泛型的位置全部变为:Object
Student stu = new Student();
stu.add(Object ...);
11.泛型_泛型方法的定义
public class Student {
public <T> ArrayList<T> show(T t1,T t2,T t3,T t4,T t5){
ArrayList<T> list = new ArrayList<>();
list.add(t1);
list.add(t2);
list.add(t3);
list.add(t4);
list.add(t5);
return list;
}
}
-------------------------------------------------------------------------------------------------------------
//有五个字符串:"黄渤","孙红雷","张一山","黄磊","罗志祥"
//将这5个字符串装到集合中
Student stu = new Student();
ArrayList<String> strList = stu.<String>show("黄渤","孙红雷","张一山","黄磊","罗志祥");
System.out.println(strList);
12.泛型_泛型接口的定义
1).泛型接口的定义同"泛型类"
interface IA<T>{
public void add(T t);
public T get();
}
2).当子类实现一个具有泛型的接口时:
1).子类丢掉泛型:
class Zi implements IA{
public void add(Object obj){
}
public Object get(){
}
}
2).子类丢掉泛型,指定为某种具体类型:
class Zi implements IA<Integer>{
public void add(Integer t){
}
public Integer get(){
}
}
3).子类继续继承并使用泛型【常用】:
class Zi<T> implements IA<T>{
public void add(T t){
}
public T get(){
}
}
13.泛型_泛型通配符
1).<?> : 例如:
public class Demo {
public static void main(String[] args) {
ArrayList<Integer> intList = new ArrayList<>();
ArrayList<String> strList = new ArrayList<>();
ArrayList<Object> objList2 = new ArrayList<Object>();
printList1(intList);//OK的
printList1(strList);//OK的
printList1(objList2);//OK的
printList2(intList);//错误
printList2(strList);//错误
printList2(objList2);//OK的
}
public static void printList1(ArrayList<?> objList){
}
public static void printList2(ArrayList<Object> objList){
}
}
2).<? extends E> : 例如:
class A{}
class B extends A{}
class B1 extends B{}
class B2 extends B{}
-------------------------------------------------------------------------------
public class Demo {
public static void main(String[] args) {
ArrayList<A> aList = new ArrayList<>();
ArrayList<B> bList = new ArrayList<>();
ArrayList<B1> b1List = new ArrayList<>();
ArrayList<B2> b2List = new ArrayList<>();
// printList3(aList);//错误
printList3(bList);//OK的
printList3(b1List);//OK的
printList3(b2List);//OK的
}
public static void printList3(ArrayList<? extends B> bList){
}
}
3).<? super E> : 例如:
class A{}
class B extends A{}
class B1 extends B{}
class B2 extends B{}
------------------------------------------------------------------------------
public class Demo {
public static void main(String[] args) {
ArrayList<A> aList = new ArrayList<>();
ArrayList<B> bList = new ArrayList<>();
ArrayList<B1> b1List = new ArrayList<>();
ArrayList<B2> b2List = new ArrayList<>();
printList4(aList);//OK的
printList4(bList);//OK的
// printList4(b1List);//错误
// printList4(b2List);//错误
}
public static void printList4(ArrayList<? super B> bList){
}
}