Java进阶06 集合
一、集合及其体系结构
集合是一个长度可变的容器
1、集合的体系结构
1.1 单列集合
单列集合使用add()方法添加集合元素,一次只能添加一个元素。
单列集合均实现了Collection接口,该接口还有两个子接口List和Set。
List接口
List集合的特点是存取有序、有索引、可以存储重复的;包含ArrayList、LinkedList两个集合
Set接口
Set集合的特点是存取无序、没有索引、不可以存储重复的;包含TreeSet、HashSet、LinkedHashSet
1.2 双列集合
双列集合使用put()方法添加集合元素,一次可以添加两个元素。
双列集合均实现了Map接口
双列集合包括:TreeMap、HashMap、LinkedHashMap
二、Collection的使用
方法名
说明
public booleanadd(E e)
把给定的对象添加到当前集合中,返回是否添加成功
public voidclear()
清空集合中所有的元素
public booleanremove(E e)
把给定的对象在当前集合中删除,返回是否删除成功
public booleancontains(Object obj)
判断当前集合中是否包含给定的对象
public booleanisEmpty()
判断当前集合是否为空
public intsize()
返回集合中元素的个数/集合的长度
注意事项:
remove()、contains()底层都是依赖equals方法
clear()是清空集合中所有元素,不是销毁集合容器。清空后还是可以继续往集合中添加元素的
三、集合遍历方式(5种)
1、普通for循环
ArrayList<String> list = new ArrayList<>(); ? for (int i = 0; i < list.size(); i++) { ? ?String s = list.get(i); }2、迭代器遍历
2.1 Collection接口的方法
方法
说明
public Iteratoriterator()
获取遍历集合的迭代器对象
public booleanhasNext()
判断集合中是否还有元素
public Enext()
取出集合中元素,并且将指针向后移动一位
2.2 迭代器遍历
public class CollectionDemo2 { ? ?public static void main(String[] args) { ? ? ? ?//多态创建集合容器,左边为接口引用,右边为实现类对象 ? ? ? ?Collection<Student> c = new ArrayList<>(); ? ? ? ? ? ? ? ?c.add(new Student("张三", 23)); ? ? ? ?c.add(new Student("李四", 24)); ? ? ? ?c.add(new Student("王五", 25)); ? ? ? ? ? ? ? ?// 1. 获取迭代器 其实这句代码相当于 Iterator<Student> it = new Itr(); ? ? ? ?Iterator<Student> it = c.iterator(); ? ? ? ?// 2. 循环的判断, 集合中是否还有元素 ? ? ? ?while (it.hasNext()) { ? ? ? ? ? ?// 3. 通过迭代器取出集合的元素 ? ? ? ? ? ?Student stu = it.next(); ? ? ? ? ? ?System.out.println(stu.getName() + "---" + stu.getAge()); ? ? ? ? ? ? ? ? ? ? ? ?//这样调用会出现信息错乱!!! ? ? ? ? ? ?System.out.println(it.next().getName() + "---" + it.next().getAge()); ? ? ? } ? } }**注意:**next()方法每调用一次,迭代器指针会后移一位,就会把不同集合元素的信息拼接到一起打印,为了避免这种信息错乱,建议在循环中,next()只调用一次
2.3 迭代器源码分析
private class Itr implements Iterator<E> { ? ?//定义游标,表示指针指向 ? ?int cursor; ? ? ? ?public boolean hasNext() { ? ? ? ?//判断指针值是否等于集合长度 ? ? ? ?return cursor != size; ? ? ? } ? ? ? ? ?public E next() { ? ? ? ?//定义i变量记录当前指针所指向的元素下标 ? ? ? ?int i = cursor; ? ? ? ? ? ? ? ?//指针后移 ? ? ? ?cursor = i + 1; ? ? ? ? ? ? ? ?//返回i队应下标所记录的元素值 ? ? ? ?return (E) elementData[lastRet = i]; ? ? } }3、增强for循环
增强for循环是JDK5之后出现的,其内部原理就是一个Iterator迭代器,它简化迭代器的代码书写,是迭代器遍历的语法糖。
3.1 格式
for(元素的数据类型 变量名 : 数据或者集合){ }快捷键需要迭代的集合.for再回车
3.2 Demo
public class CollectionDemo3 { ? ?public static void main(String[] args) { ? ? ? ?Collection<Student> c = new ArrayList<>(); ? ? ? ?c.add(new Student("张三", 23)); ? ? ? ?c.add(new Student("李四", 24)); ? ? ? ?c.add(new Student("王五", 25)); ? ? ? ? ?//增强for循环遍历集合 ? ? ? ?for (Student stu : c) { ? ? ? ? ? ?System.out.println(stu); ? ? ? } ? ? ? ? ?System.out.println("----------------------"); ? ? ? ? ?//增强for循环遍历数组 ? ? ? ?int[] nums = {11, 22, 33}; ? ? ? ?for (int num : nums) { ? ? ? ? ? ?System.out.println(num); ? ? ? } ? } }注意细节:增强for循环遍历数组时,循环变量直接代表每一份元素,并不是下标。为了避免和出错和fori搞混,这个循环变量我们一般不会取名为i
4、foreach方法
//遍历集合 default void forEach(Consumer<? super I>action)跟进源码后发现该方法需要的参数Consumer是一个接口类型,那么我们就要传入该接口的实现类对象,可以创建一个并传入。然而源码中验证该接口还是一个函数式接口,因此可以传入匿名内部类,还可以将其改写成Lambda表达式。
public class CollectionDemo4 { ? ?public static void main(String[] args) { ? ? ? ?Collection<Student> c = new ArrayList<>(); ? ? ? ?c.add(new Student("张三",23)); ? ? ? ?c.add(new Student("李四",24)); ? ? ? ?c.add(new Student("王五",25)); ? ? ? ? ?//匿名内部类写法 ? ? ? ?c.forEach(new Consumer<Student>() { ? ? ? ? ? ?@Override ? ? ? ? ? ?public void accept(Student student) { ? ? ? ? ? ? ? ?System.out.println(student); ? ? ? ? ? } ? ? ? }); ? ? ? ? ?//Lambda表达式写法 ? ? ? ?c.forEach(s-> System.out.println(s)); ? } }5、ListIterator遍历
继承了Iterator,是List集合派系所特有的迭代器,遍历方式与Iterator遍历类似,但也有其特殊之处:它内部含有hasPrevious()方法和previous()方法,可以配合使用进行倒序遍历,前提是必须要先正序遍历让指针移至最后,否则倒叙遍历没有效果
public static void main(String[] args) { ? ? ? ?List<String> list = new ArrayList<>(); ? ? ? ? ?list.add("张三"); ? ? ? ?list.add("李四"); ? ? ? ?list.add("王五"); ? ? ? ? ? ?ListIterator<String> it = list.listIterator(); ? ? ? ? ?//正序遍历 ? ? ? ?while (it.hasNext()) { ? ? ? ? ? ?String s = it.next(); ? ? ? ? ? ?System.out.println(s); ? ? ? } ? ? ? ? ?System.out.println("---------------------------------"); ? ? ? ? ?//倒序遍历,前提必须先正序遍历让指针后移至最后,否则没有效果 ? ? ? ?while (it.hasPrevious()) { ? ? ? ? ? ?String s = it.previous(); ? ? ? ? ? ?System.out.println(s); ? ? ? } ? }四、List接口
list接口因为支持索引,所以多了很多索引操作的独特API
方法名
说明
voidadd(int index,E element)
在此集合中的指定位置插入指定的元素
Eremove(int index)
删除指定索引处的元素,返回被删除的元素
Eset(int index,E element)
修改指定索引处的元素,返回被修改的元素
Eget(int index)
返回指定索引处的元素
五、数据结构
数据结构是计算机底层存储、组织数据的方式,是指数据相互之间是以什么方式排列在一起的
1、栈和队列
栈
队列
一端开口(栈顶),一端封闭(栈底)
两端均开口
栈顶出入栈
队尾入队,队头出队
后进先出
先进先出
2、数组和链表
数组
链表
内存连续区域
在内存中游离,不连续
查询速度快:通过地址和索引定位,查任意数据耗时相同
查询速度慢:没有索引,无论查询哪个数据都要从头遍历
增删效率低:增删有可能大批量的移动数组中其他元素
增删效率相对数组快:增删不用移动大量元素,只需修改指针即可
- 单链表&双链表
链表元素在内存中是游离的,其中每个结点是独立的对象,在内存中不是连续的,每个结点有自己的存储地址,包含其存储的具体数据值和下一个结点的地址。见名知义,单链表即链接方向是单向的,对链表的访问要通过顺序读取从头部开始。双链表的链接方向是双向的,即每个数据结点中都有两个指针,分别指向直接后继和直接前驱。因此双向链表首尾操作极快!!!
六、ArrayList类&LinkedList类
1、ArrayList类
ArrayList底层是基于数组实现的,所以查询元素快,增删相对慢
1.1 ArrayList长度可变原理
ArrayList底层是数据结构,数组默认长度为10;当数组添加满了之后,会自动扩容为1.5倍,扩容时会先将原数组数据拷贝到新数组中,再将新元素添加到新数组
1.2 ArrayList源码解析
①使用空参构造器创建的集合,在底层创建一个默认长度为0的数组;
②添加第一个元素时,底层会创建一个新的长度为10的数组
③存满时,会扩容1.5倍
2、LinkedList类
LinkedList底层基于双链表实现的,查询元素慢,增删首尾元素是非常快的
特有方法
说明
public void addFirst(E e)
在该列表开头插入指定的元素
public void addLast(E e)
将指定的元素追加到此列表的末尾
public E getFirst()
返回此列表中的第一个元素
public E getLast()
返回此列表中的最后一个元素
public E removeFirst()
从此列表中删除并返回第一个元素
public E removeLast()
从此列表中删除并返回最后一个元素
- **注意:**LinkedList的get()方法,表面看起来是根据索引获取元素,实际并非如此。它的原理很简答,是通过遍历链表来查找指定索引的元素。具体来说,get()方法从链表的表头开始遍历,它经过一个节点,就将计数器加一。当计数器的值等于要查找的索引时,get()方法就返回该节点的元素值,否则继续遍历直到表尾。
七、泛型
JDK5引入泛型,可以在编译阶段约束操作的数据类型,并进行检查。使用泛型的**好处是:统一数据类型,将运行期的错误提升到了编译期。**泛型中只能编写引用型数据,如果不指定泛型的具体类型,则系统默认创建Object对象
1、泛型类
1.1 使用场景
当类中的属性或是方法却无法确定具体类型时,可以设计泛型类
1.2 确定具体类型
在创建对象的时候确定到具体数据类型
//泛型类 public class ArrayList<E>{ ? ?private E e; ? ?public E getE(){ ? ? ? ?return e; ? } ? ?public void setE(E e){ ? ? ? ?this.e = e; ? } } ? public static void main(String[] args){ ? ?//创建对象,指定类型为Integer ? ?Student<Integer> stu = new Student<>; }2、泛型方法
2.1 非静态泛型方法
泛型是根据类的泛型去匹配的
public class ArrayList<E>{ ? ?public boolean add(E e){ ? } }2.2 静态泛型方法
需要声明出自己独立的泛型
public static<T> void printArray(T[] array){} public class Demo3 { ? ?public static void main(String[] args) { ? ? ? ?Integer[] arr1 = {11,22,33}; ? ? ? ?Double[] arr2 = {11.1,22.2,33.3}; ? ? ? ?String[] arr3 = {"张三","李四","王五"}; ? ? ? ? ?printArray(arr1); ? ? ? ?printArray(arr2); ? ? ? ?printArray(arr3); ? } ? ? ?//该方法在main函数中调用,因此必须是static修饰,又想接收各种类型,所以自己定义独立的泛型 ? ?private static<T> void printArray(T[] arr) { ? ? ? ?System.out.print("["); ? ? ? ?for (int i = 0; i < arr.length-1; i++) { ? ? ? ? ? ?System.out.print(arr[i]+","); ? ? ? } ? ? ? ?System.out.println(arr[arr.length-1]+"]"); ? } }3、泛型接口
3.1 使用场景
接口中的某个抽象方法确定不了参数的具体类型,就可以声明泛型,让该方法的泛型去匹配接口的泛型
3.2 确定具体数据类型
类实现接口时,如果接口带有泛型,有两种操作方式
类实现接口的时候,直接确定类型
实现类延续接口的泛型,等创建对象的时候再确定
//泛型接口
interface Inter{
? ?//抽象方法的参数匹配接口的泛型
? ?void show(E e);
}
?
//类实现接口的时候直接指定类型为String
class InterAImpl implements Inter{
?
? ? @Override
? ? public void show(String s) {
?
? ? }
}
?
//实现类延用接口泛型,则在该实现类创建对象的时候一定要给出具体类型
class InterBImpl implements Inter{
?
? ? @Override
? ? public void show(E e) {
? ? ? ? ?
? ? }
}
4、泛型通配符
书写位置在<>内,有以下三种用法
?:任意类型
?extends E:只能接收E或者E的子类
?super E:只能接收E或者E的父类