1.集合
2.Collection
3.Iterator迭代器
4.泛型
一:集合
1.1集合的使用回顾
-
Arrarylist增删改查
-
代码示例:
public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<Integer>(); //添加 list.add(111); list.add(222); list.add(333); //删除 list.remove(0); //修改 list.set(2,256); //查看 for(int i=0; i<list.size(); i++){ //获取 System.out.println(list.get(i)); } }
1.2:什么是集合
-
集合,集合是Java中提供的一种容器,可以用来存储多个数据
-
为什么学习集合
-
在前面的学习中,我们知道的数据多了,可以使用数组存放或者使用ArraryList集合进行存放数据。
-
-
-
那么,集合和数组既然都是容器,它们有啥区别呢?
-
-
数组的长度是固定的。集合的长度是可变的。
-
集合中存储的元素必须是引用类型数据
-
-
-
Java如何表示集合
-
-
Java提供了集合API
-
-
用大白话来说就是一个家族
-
-
1.3集合家族
-
如下图所示
-
清爽版
-
-
难受版
-
简约版
-
总结:
-
-
Collection:所有单根集合父类
-
-
List:是一个有序集合,可以放重复的数据
-
Set:是一个无序集合,不允许放重复的数据
-
-
Map:双根
-
-
是一个无序集合,集合中包含一个键对象,一个值对象,键对象不允许重复,值对象可以重复
-
hashMap
-
-
二:Collection
2.1什么是Collection?
-
是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素 JDK 不提供此接口的任何直接实现,它提供更具体的子接口(如Set和List)实现
-
Collection所代表的是一种规则(接口),它所包含的元素都必须遵循一条或者多条规则。
-
-
有些集合允许重复元素
-
而其他集合不允许
-
方法名统一了(单根)
-
-
add
-
get
-
set
-
-
2.2:使用Collection
Collection c =null; //创建对象实例化 c = new ArrayList<>();
2.2.3:构造方法
-
因为该类是接口,所以没有构造方法
-
创建实例
-
-
多态
-
-
Collection c = new ArrayList<>();
-
-
匿名内部类
-
方法
-
2.2.4:常用方法
三:Iterator迭代器
3.1:什么是Iterator迭代器
-
迭代器(Iterator)是一个对象,它的工作是遍历目标序列中的对象(获取集合中的数据),它提供了一种访问一个容器(container)对象中的各个元素的方法(提供了一种手段,专业获取数据),把访问逻辑从不同类型的集合类中抽象出来,又不必暴露该对象内部细节。通过迭代器,开发人员不需要了解容器底层的结构,就可以实现对容器的遍历。由于创建迭代器的代价小,因此迭代器通常被称为轻量级的容器。
-
常常使用JDK提供的迭代接口进行Java集合的迭代。
-
大白话就是专业的事情,找专业的人办
-
-
代码实例
-
Iterator iterator = list.iterator(); while(iterator.hasNext()){ String string = iterator.next(); //do something }
-
在没有迭代器时,我们都是这么进行遍历元素的,如下:
-
-
对于数组,我们是使用下标进行处理的P:
-
-
代码实例
-
-
int[] arrays = new int[10]; for(int i = 0 ; i < arrays.length ; i++){ int a = arrays[i]; //do something }
-
对于这两种方式,我们总是都是先知道集合的内部结构,访问代码和集合本身是紧密耦合的,无法将访问逻辑从集合类和客户端代码中分离出来。同时每一种集合对应一种遍历方法,客户端代码无法复用。 在实际应用中如何需要将上面将两个集合进行整合是相当麻烦的。所以为了解决以上问题,Iterator模式腾空出世,它总是用同一种逻辑来遍历集合。使得客户端自身不需要来维护集合的内部结构,所有的内部状态都由Iterator来维护。客户端从不直接和集合类打交道,它总是控制Iterator,向它发送"向前","向后","取当前元素"的命令,就可以间接遍历整个集合。
-
集合的内部结构不同,造成了一个结果。取值,也应该有自己的专业方式
-
总结
-
-
迭代器,就是一个类。作用是专业取数据!
-
3.2:如何使用迭代器
-
迭代器,其实是一个内部类。每个集合都给自己提供了一个方法,可以帮我们得到迭代器的实例
-
获取迭代器
-
判断是否有元素
-
获取元素
-
代码实例
package 获取迭代器; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; public class Demo { public static void main(String[] args) { Collection list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); Iterator it = list.iterator(); while (it.hasNext()){ System.out.println(it.next()); } } }
3.3迭代器的原理
-
每个集合的内部都维护了一个Iterator迭代器(内部类)
例如:ArrayList集合
-
-
在ArrayList内部首先是定义一个内部类Itr,该内部类实现Iterator接口,如下:
-
private class Itr implements Iterator<E> { int cursor; // index of next element to return 前进 int lastRet = -1; // index of last element returned; -1 if no such 后退 int expectedModCount = modCount; //do something }
-
-
而ArrayList的iterator()方法实现:
-
public Iterator<E> iterator() { return new Itr(); }
-
-
所以通过使用ArrayList.iterator()方法返回的是Itr()内部类,所以现在我们需要关心的就是Itr()内部类的实现:
-
-
在Itr内部定义了三个int型的变量:cursor、lastRet、expectedModCount。
-
-
其中cursor表示下一个元素的索引位置
-
lastRet表示上一个元素的索引位置。
-
从cursor、lastRet定义可以看出,lastRet一直比cursor少一所以hasNext()实现方法异常简单,只需要判断cursor和lastRet是否相等即可。
-
代码实例
-
-
-
public boolean hasNext() { return cursor != size; }
-
-
-
对于next()实现其实也是比较简单的,只要返回cursor索引位置处的元素即可,然后修改cursor、lastRet即可:
-
-
代码实例
-
-
-
public E next() { checkForComodification(); int i = cursor; //记录索引位置 if (i >= size) //如果获取元素大于集合元素个数,则抛出异常 throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; //cursor + 1 return (E) elementData[lastRet = i]; //lastRet + 1 且返回cursor处元素 } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
-
-
-
-
heckForComodification()主要用来判断集合的修改次数是否合法,即用来判断遍历过程中集合是否被修改过。modCount用于记录ArrayList集合的修改次数,初始化为0,每当集合被修改一次(结构上面的修改,内部update不算),如add、remove等方法,modCount + 1,所以如果modCount不变,则表示集合内容没有被修改。该机制主要是用于实现ArrayList集合的快速失败机制,在Java的集合中,较大一部分集合是存在快速失败机制的。所以要保证在遍历过程中不出错误,我们就应该保证在遍历过程中不会对集合产生结构上的修改(当然remove方法除外),出现了异常错误,我们就应该认真检查程序是否出错而不是catch后不做处理。
-
-
对于remove()方法的是实现,它是调用ArrayList本身的remove()方法删除lastRet位置元素,然后修改modCount即可
-
-
代码实例
-
-
-
public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }
-
ListIterator:
-
-
接口Iterator在不同的子接口中会根据情况进行功能的扩展,例如针对List的迭代器ListIterator,该迭代器只能用于各种List类的访问。ListIterator可以双向移动。添加了previous()等方法。如果是List集合,想要在迭代中操作元素可以使用List集合的特有迭代器ListIterator,该迭代器支持在迭代过程中,添加元素和修改元素。
-
3.3:for循环、forEach、Irerator对比
-
代码实例
package 三种遍历方式的区别; import java.util.ArrayList; import java.util.Iterator; public class Demo { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); for (int i = 0;i< list.size();i++){ System.out.println(list.get(i)); } for (Integer arrayList:list){ System.out.println(list); } Iterator it = list.iterator(); while (it.hasNext()){ System.out.println(it.next()); } } }
-
相同点:都是用于遍历集合元素的。
-
不同点
-
形似差别(样子差的多)
//for循环的形式是: for(int i=0;i<arr.size();i++){...} //foreach的形式是: for(int i:arr){...} //iterator的形式是 Iterator it = arr.iterator(); while(it.hasNext()){ object o =it.next(); ...}
2.条件差别:()内容不一样
-
-
for循环需要知道集合或数组的大小,而且需要是有序的,不然无法遍历;
-
foreach和iterator都不需要知道集合或数组的大小,他们都是得到集合内的每个元素然后进行处理;
-
3.多态差别:
-
-
-
需要访问内部的成员,不能实现多态;
-
-
iterator是一个接口类型,可以使用相同方式去遍历不同集合中元素,而不用考虑集合类的内部实现(只要它实现了 java.lang.Iterable 接口),而且他还能随时修改和删除集合的元素,能够将遍历序列的操作与序列底层的结构分离。迭代器统一了对容器的访问方式。这也是接口的解耦的最好体现。
-
例如:如果使用 Iterator 来遍历集合中元素,一旦不再使用 List 转而使用 Set 来组织数据,那遍历元素的代码不用做任何修改,如果使用 for 来遍历,那所有遍历此集合的算法都得做相应调整,因为List有序,Set无序,结构不同,他们的访问算法也不一样。
-
iterator获取到的内容,如果不使用泛型约束,获取到数据类型都会提升到Object类型
-
4.效率差别:
-
-
采用ArrayList对随机访问比较快,而for循环中的get()方法,采用的即是随机访问的方法,因此在ArrayList里,for循环较快。
-
-
如果集合是ArrayList集合,我们想要随机获取。三种方式,建议使用for
-
-
采用LinkedList则是顺序访问比较快,iterator中的next()方法,采用的即是顺序访问的方法,因此在LinkedList里,使用iterator较快。
-
-
如果集合是LinkedList集合,而且顺序访问。使用iterator较快
-
-
从数据结构角度分析,for循环适合访问顺序结构,可以根据下标快速获取指定元素.而Iterator 适合访问链式结构,因为迭代器是通过next()和Pre()来定位的.可以访问没有顺序的集合。
-
集合的数据结构不一样,需求不一样。三者的取值速度就会有所差异
-
5.foreach 和 iterator 的其他区别:
-
-
使用foreach循环语句的优势在于更加简洁,更不容易出错,不必关心下标的起始值和终止值,底层由iterator实现的,他们最大的不同之处就在于remove()方法上。
-
如果在forEach循环的过程中调用集合的remove()方法,就会导致循环出错,因为循环过程中list.size()的大小变化了,就导致了错误。 所以,如果想在循环语句中删除集合中的某个元素,就要用迭代器iterator的remove()方法,因为它的remove()方法不仅会删除元素,还会维护一个标志,用来记录目前是不是可删除状态,例如,你不能连续两次调用它的remove()方法,调用之前至少有一次next()方法的调用。
-
3.4:集合迭代中的转型
-
在使用集合时,我们需要注意以下几点:
-
-
集合中存储其实都是对象的地址。
-
集合中可以存储基本数值吗?jdk1.5版本以后可以存储了。
-
-
因为出现了基本类型包装类,它提供了自动装箱操作(基本类型对象),这样,集合中的元素就是基本数值的包装类对象。
-
-
2.存储时提升了Object。取出时要使用元素的特有内容,必须向下转型。
package 集合向下转型; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; public class Demo { public static void main(String[] args) { Collection c= new ArrayList(); c.add("a"); c.add("b"); c.add("c"); Iterator it = c.iterator(); while (it.hasNext()){ System.out.println(((String) it.next()).substring(0)); } } }
四、泛型
4.1什么是泛型
-
泛型,即“参数化类型”
-
-
在前面学习集合时,我们都知道集合中是可以存放任意对象的,只要把对象存储集合后,那么这时他们都会被提升成Object类型。当我们在取出每一个对象,并且进行相应的操作,这时必须采用类型转换
-
2.ArrayList可以存放任意类型,例子中添加了一个String类型,添加了一个Integer类型,再使用时都以String的方式使用,因此程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。
-
我们将第一行声明初始化list的代码更改一下,编译器会在编译阶段就能够帮我们发现类似这样的问题。
package 泛型; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; public class Demo { public static void main(String[] args) { ArrayList<String> c= new ArrayList(); c.add("a"); c.add("b"); c.add("c"); Iterator it = c.iterator(); while (it.hasNext()){ System.out.println(((String) it.next()).substring(0)); } } }
4.2:Java中的伪泛型
-
Java中的伪泛型:
-
-
泛型只在编译时存在,编译后就被擦除,在编译之前我们就可以限制集合的类型,起到作用
-
例如:
-
-
编译前:ArrayList al=new ArrayList();
-
编译后:ArrayList al=new ArrayList();
-
-
4.3如何使用泛型
-
泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法
4.3.1:泛型类
-
泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。
-
-
在定义类的时候,该类后后面如果有泛型,该类就是泛型类
-
class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{ private 泛型标识 /*(成员变量类型)*/ var; ..... } }
-
一个最普通的泛型类:
-
-
泛型类
-
package 泛型.泛型类; import java.util.ArrayList; import java.util.Collection; public class Demo { public static void main(String[] args) { A<String> a = new A<>(); a.m(""); } } class A<E>{ public void m(E e){ System.out.println("张毅是个小可爱"); } }
4.3.2:泛型接口
-
格式:
package 泛型.泛型类; public class Demo1 { public static void main(String[] args) { new B<>(){ //匿名内部类 @Override public void m(Object o) { } }; } } interface B <E>{ public void m(E e); }
4.3.3:泛型方法
-
格式:修饰符 <泛型> 返回值类型 方法名 (参数){}
-
注意
package 泛型方法12; public class Demo { public static void main(String[] args) { m("abc"); } public static <E> void m(E e){ System.out.println(e.getClass()); } }
4.4:泛型优点
-
泛型的好处
-
将运行时期的ClassCastException,转移到了编译时期变成了编译失败。
-
避免了类型强转的麻烦。
4.5:泛型通配符问题
-
通配符类型可以理解为一种泛型调用时传递的一种特殊数据类型,表示参数允许在某个范围内变化。通配符类型有三种,分别是
-
-
?
-
? extends
-
? super
-
-
通配符表示一种未知类型,并且对这种未知类型存在约束关系.
4.5.1:通配符类型(?)
-
:无边界
-
-
-
不要单独使用(直接用)
-
参数化
-
-
-
代码实例(直接用)
package 通配符14; import java.util.ArrayList; public class Demo { public static void main(String[] args) { ArrayList<?> list = new ArrayList<>(); } }
package 通配符14; import java.util.ArrayList; public class Demo { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(); ArrayList<String> list2 = new ArrayList<>(); m(list2); } public static void m(ArrayList<?> list){ } }
4.5.2: 上限通配
-
-
这里?表示一个未知的类,而T是一个具体的类,在实际使用的时候T需要替换成一个具体的类,表示实例化的时候泛型参数要是T或T的子类。
-
import java.util.ArrayList; public class Demo { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(); ArrayList<Double> list2 = new ArrayList<>(); ArrayList<Object> list3 = new ArrayList<>(); m(list3); } public static void m(ArrayList<? extends Number> list){ } }
4.5.3: 下限通配
import java.util.ArrayList; public class Demo { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(); ArrayList<Double> list2 = new ArrayList<>(); ArrayList<Object> list3 = new ArrayList<>(); ArrayList<Number> list4 = new ArrayList<>(); m(list4); } public static void m(ArrayList<? super Number> list){ } }
4.5.4:常见泛型表示
-
E、T、K、V、N
-
本质上以下字母都可以相互替换,但我们按照下面定义约定俗成的含义来使用。
-
E - Element (在集合中使用,因为集合中存放的是元素)
-
T - Type(Java 类)
-
K - Key(键)
-
V - Value(值)
-
N - Number(数值类型)
-
? - 表示不确定的java类型