集合(进阶List系列)
- 集合的体系结构
Collection
- 体系结构
List和Set2种系列的集合特点
有序指的是存和取的顺序一样,不是数值从大到小和从小到大排序
2种系列的特点正好相反
Collection是单列集合祖宗接口,他的功能所用的单列集合都可以使用
添加 清理 删除元素
package Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class CollectionTest {
public static void main(String[] args) {
//由于Collection是接口,无法直接实例化,需要借助其子类进行实例化
Collection<String> coll=new ArrayList<>();//向上转型
//1.添加元素
/*
细节1:List系列的集合 add永远返回true,因为List系列的允许元素重复
细节2:Set系列的集合 add当元素重复时返回false,标识添加失败
*/
coll.add("hello");
coll.add("the");
coll.add("world");
System.out.println(coll);//[hello, the, world]
//2.清空集合内的元素
coll.clear();
System.out.println(coll);//[]
System.out.println("------------------------------------------------");
//3.把给定的对象在当前集合在当前集合中删除:注意此处不能指定索引删除元素,因为该方法在ArrayList类中没有
//细节:1.删除成功返回true,失败返回false(当要删除的元素不存在时)
coll.add("hello");
coll.add("the");
coll.add("world");
coll.remove("hello");//把hello从当前集合中删除
System.out.println(coll);//[the, world]
System.out.println("------------------------------------------");
}
}
//重要经验:多个接口的相同的抽象方法,子类在实现的时候只用实现一个即可
toString 调用的2种情况辨析
package Test;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList<String> ar=new ArrayList<>();
ar.add("hello");
ar.add("the");
ar.add("world");
System.out.println(ar);//[hello, the, world]
}
}
在这种情况下之所以,ar的toString输出的是这种形式,ArrayList类没有toString方法,则从下往上找父类中是否存在该方法的实现(从下往上依次寻找),所以此处调用的是AbstractList类中的toString方法
contains方法:判断当前集合中是否包含给定对象
经验:当子类中没有该方法,但是父类或者祖宗类中有该方法,由父类一直向上寻找
- 源码分析
idea快捷键:当接口中的方法没有实现,可以选中该方法右键鼠标--转到---实现,可以查看该方法在其子类中的具体实现
可以发现该方法是由传入对象的equals方法实现的,如果我们的类没有实现equals方法,则默认是调用Object类的equals方法(Object方法默认比较地址),当该对象是我们自己定义的将会出现比较失误
当使用contains方法时,我们会重写equals方法
package Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
class Person{
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Person{name = " + name + ", age = " + age + "}";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
}
public class CollectionTest {
public static void main(String[] args) {
Collection<Person> coll=new ArrayList<>();//实例化Collection
Person pr1=new Person("zhangsan",23);
Person pr2=new Person("lisi",24);
Person pr3=new Person("wangwu",25);
Person pr4=new Person("wangwu",26);
coll.add(pr1);
coll.add(pr2);
coll.add(pr3);
System.out.println(coll.contains(pr4));//false
}
}
//重要经验:多个接口的相同的抽象方法,子类在实现的时候只用实现一个即可
- isEmpty&size()
package Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
public class CollectionTest {
public static void main(String[] args) {
Collection<String > coll=new ArrayList<>();//实例化Collection
//1.isEmpty 判断集合是否为空:底层通过判断集合长度实现的
coll.add("hello");
coll.add("the");
coll.add("world");
System.out.println(coll.isEmpty());//false
//2.size 获取集合的长度
System.out.println(coll.size());//3
}
}
//重要经验:多个接口的相同的抽象方法,子类在实现的时候只用实现一个即可
Collection的遍历方式(通用遍历方式)
Lambda表达式遍历
package com.an.a;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.function.Consumer;
public class LambdaPractice {
public static void main(String[] args) {
/*
验证Collection的通用遍历方式之 Lambda表达式遍历
遍历用到的方法:default void forEach(Consumer<? super T>action)
*/
Collection<String>coll =new ArrayList<>();
//添加元素
coll.add("hello");
coll.add("hi");
coll.add("world");
//1.使用匿名内部类的形式遍历
/*
forEach方法的形参是一个接口,通过查看源码可以知道其是一个函数式的接口,可以用Lambda改写
*/
//参数是接口 要传递其的子类(可以用匿名内部类的形式)
/*
forEach函数底层原理:
其底层会一次遍历集合并得到里面的每一个元素
然后依次将得到的每一个元素交给accept方法处理
*/
/* coll.forEach(new Consumer<String>() {
@Override
public void accept(String s) {//s表示集合中的每一个元素
System.out.println(s);
}
});*/
//2.用Lambda进行简化处理
/*
()表示函数的形参(将重写函数的形参作为调用函数的形参)
->固定格式
{} 表示函数体(重写函数的函数体)
*/
//Lambda完整版本
coll.forEach((String s)-> {//s表示集合中的每一个元素
System.out.println(s);
}
);
}
}
关于写Lambda表达式的思路,可以按照()->{}的格式先写出Lambda的完整格式,然后再进行简化处理,这样的话必须要求作者要熟悉接口中的抽象方法在子类中的具体实现
迭代器遍历
因为为通用遍历,而set类型的集合没有索引,所以没有用索引进行遍历的
举例解释迭代器
可以将迭代器的对象开成是指针或者是箭头,获取对象后,迭代器指针默认指向0索引的位置
迭代器结合while实现对集合的遍历
迭代器的使用演示
package Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
//测试迭代器的使用
public class IteartorTest {
public static void main(String[] args) {
/*
迭代器相关的3个方法:
Iterator<E> iterator() 获取迭代器的对象
boolean hasNext(() 判断迭代器当前所指的位置是否有元素
E next() 返回迭代器当前所指的元素并移动迭代器
*/
//1.创建集合并添加元素
Collection<String> coll= new ArrayList<>();
coll.add("today");
coll.add("is");
coll.add("a");
coll.add("good");
coll.add("day");
//获取迭代器对象
Iterator<String> it =coll.iterator();//获取迭代器的对象 默认指向0索引
//遍历集合
while(it.hasNext()){//判断当前位置是否有元素
String str =it.next();//获取当前位置的元素并往后移动迭代器
System.out.print(str+" ");//today is a good day
}
}
}
迭代器遍历的注意点
1.当迭代器已经指向了没有元素,但是还是强行取调用这个迭代器,将会报NoSuchElementException(没有该元素异常)
2.当迭代器遍历完,指针不会复位,要想再遍历只能重新再定义一个迭代器
3.循环中只能使用一次next方法,当需要多次使用可以使用变量将该元素储存起来
4.迭代器遍历的时候,不能使用集合的方法进行增加或删除
注意点的演示
- 1-2
package Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
//测试迭代器的使用
public class IteartorTest {
public static void main(String[] args) {
/*
迭代器相关的3个方法:
Iterator<E> iterator() 获取迭代器的对象
boolean hasNext(() 判断迭代器当前所指的位置是否有元素
E next() 返回迭代器当前所指的元素并移动迭代器
*/
//1.创建集合并添加元素
Collection<String> coll= new ArrayList<>();
coll.add("today");
coll.add("is");
coll.add("a");
coll.add("good");
coll.add("day");
//获取迭代器对象
Iterator<String> it =coll.iterator();//获取迭代器的对象 默认指向0索引
//遍历集合
while(it.hasNext()){//判断当前位置是否有元素
String str =it.next();//获取当前位置的元素并往后移动迭代器
System.out.print(str+" ");//today is a good day
}
//1.NoSuchException
//当前迭代器已经遍历完:如果还调用next将指针往后移动,将会报错
//it.next();//NoSuchElementException
//2.迭代器遍历完毕 指针不会复位
//获取当前位置是否有元素
System.out.println(it.hasNext());//false
System.out.println(it.hasNext());//false
System.out.println(it.hasNext());//false
//要想再次遍历必须重新创建一个新的的迭代器对象
}
}
- 3循环中只能使用一次next方法
当循环中使用2次或者多次next方法,就极有可能导致hasNext方法指向的位置已经没有元素,但是next方法还需要获取元素并移动指针的情况,从而导致报NoSuchElementException
package Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
//测试迭代器的使用
public class IteartorTest {
public static void main(String[] args) {
/*
迭代器相关的3个方法:
Iterator<E> iterator() 获取迭代器的对象
boolean hasNext(() 判断迭代器当前所指的位置是否有元素
E next() 返回迭代器当前所指的元素并移动迭代器
*/
//1.创建集合并添加元素
Collection<String> coll= new ArrayList<>();
coll.add("today");
coll.add("is");
coll.add("a");
coll.add("good");
coll.add("day");
Iterator<String> it =coll.iterator();//获取迭代器对象
while(it.hasNext()){
System.out.println(it.next());//.NoSuchElementException
System.out.println(it.next());
}
}
}
得出结论:以后next方法和hasNext方法的使用要一一对应,使用一个hasNext方法只能使用一个next方法
4在遍历迭代器的时候不能使用集合的方法添加或者删除
package Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
//测试迭代器的使用
public class IteartorTest {
public static void main(String[] args) {
/*
迭代器相关的3个方法:
Iterator<E> iterator() 获取迭代器的对象
boolean hasNext(() 判断迭代器当前所指的位置是否有元素
E next() 返回迭代器当前所指的元素并移动迭代器
*/
//1.创建集合并添加元素
Collection<String> coll= new ArrayList<>();
coll.add("today");
coll.add("is");
coll.add("a");
coll.add("good");
coll.add("day");
Iterator<String> it =coll.iterator();//获取迭代器对象
while(it.hasNext()){
//在迭代器遍历的时候删除
//coll.remove("good");//ConcurrentModificationException
//用迭代器提供的删除,可以删除
//remove返回迭代器当前指向的元素
if(it.next().equals("good")){
it.remove();
}
}
System.out.println(coll);//[today, is, a, day]
}
}
对于在遍历时,向集合中删除一个元素可以用迭代器提供的remove方法实现,而向集合中添加一个元素现在还不能实现
增强for遍历
双列集合不能使用该方式遍历
idea快捷键 集合名.for 直接生成 集合的增强for循环
增强for循环演示
package Test;
import java.util.ArrayList;
import java.util.Collection;
//测试集合的增强for循环遍历
public class StrongForTest {
public static void main(String[] args) {
//创建一个Collection集合
Collection <String> coll =new ArrayList<>();
//添加元素
coll.add("hello");
coll.add("the");
coll.add("world");
//用增强for进行遍历:快捷键 coll.for
for (String s : coll) {
//s是第三方遍历,在循环过程中表示集合中的每一个数据
System.out.print(s+" ");//hello the world
}
}
}
增强for循环的细节
for循环里面的s表示的第三方变量,仅仅起到将集合里面的数据临时记录的作用,所以修改s并不能修改集合里面的数据,如下面举例
package Test;
import java.util.ArrayList;
import java.util.Collection;
//测试集合的增强for循环遍历
public class StrongForTest {
public static void main(String[] args) {
//创建一个Collection集合
Collection <String> coll =new ArrayList<>();
//添加元素
coll.add("hello");
coll.add("the");
coll.add("world");
//用增强for进行遍历:快捷键 coll.for
for (String s : coll) {
//s是第三方遍历,在循环过程中表示集合中的每一个数据
s="hi";//修改s
}
System.out.println(coll);//[hello, the, world] 集合内的数据并没有被改变
}
}
Lambda表达式遍历(该部分没有学lambda表达式没有看懂)
1.用匿名内部类实现
2.Lambda表达式遍历
List中常见的方法和5种遍历方式
List特有的方法
Collection的所有方法,List都支持,不再演示,List为单列集合,里面的元素有序,于是List特有的方法都是对索引进行操作的方法
package Test;
import java.util.ArrayList;
import java.util.List;
//测试List中特有的方法
public class ListTest {
public static void main(String[] args) {
/*
void add(int index,E element) 在此集合中的指定位置插入元素
E remove(int index) 删除指定索引处的元素并将删除的元素返回
E set(int index,E ) 将指定索引处的元素修改成指定元素,并返回被修改的元素
E get(int index) 返回指定索引处的元素
*/
//创建集合
List<String> list =new ArrayList<>();
//添加元素
list.add("hello");
list.add("the");
list.add("word");
//1.add:在集合中指定位置插入指定的元素
list.add(1,"NO");//在1索引处插入NO
//原来索引上的许多元素将以此往后移动
System.out.println(list);//[hello, NO, the, word]
//remove 删除指定索引处的元素并将删除的元素返回
String remove = list.remove(1);
System.out.println(remove);//NO
System.out.println(list);//[hello, the, word]
//3.set将指定索引处的元素改成指定元素,并返回被修改的元素
String str = list.set(0, "QQQ");
System.out.println(str);// hello打印被修改的元素
System.out.println(list);//[QQQ,the,world]
//get返回指定索引处的元素
String s = list.get(0);
System.out.println(s);//QQQ
}
}
删除元素的注意事项
package Test;
import java.util.ArrayList;
import java.util.List;
//测试remove方法的注意点
public class RemoveTest {
public static void main(String[] args) {
//创建集合
List<Integer> list=new ArrayList<>();
//添加元素
list.add(1);
list.add(2);
list.add(3);
//删除元素
// list.remove(1);
/*
问题:以上的调用remove是1索引上的元素还是删除元素1
解释:当函数重载时,优先调用实参和形参一样的方法
remove(Object e) 方法需要自动装箱成Integer
调用的是remove(int index)
*/
//如果一定要删除1
//1.可以找到其下标
//list.remove(0);//删除了下标是0是元素1
//2.可以手动装箱:将1转化成Integer然后再调用
Integer i=1;
list.remove(i);//删除值为1的对象i
System.out.println(list);//[2, 3]
}
}
List集合的遍历方式
package Test;
import java.util.*;
//演示List的5种遍历方式
public class ListTest2 {
public static void main(String[] args) {
/*
迭代器遍历
增强for循环遍历
Lambda表达式遍历
普通for循环遍历
列表迭代器遍历
*/
//创建集合并添加元素
List<String> list=new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
//1.迭代器遍历
Iterator<String> it = list.iterator();//创建迭代器对象
//开始遍历
while(it.hasNext()){
String str=it.next();//获取对象并移动迭代器
System.out.print(str+" ");//aaa bbb ccc
}
//2.增强for循环遍历:快捷键:list.for
for (String s : list) {//s是一个第三方遍历储存每一个对象
System.out.print(s+" ");//aaa bbb ccc
}
//3.Lambda表达式遍历
list.forEach(s->System.out.print(s+" "));//aaa bbb ccc
//4.普通for循环 :借助size() get(int index) 和循环
for (int i = 0; i <list.size() ; i++) {
String s=list.get(i);//按索引获取元素
System.out.print(s+" ");//aaa bbb ccc
}
}
}
第5种方式-----列表迭代器
- previous()和hasPrevious()方法不需要掌握
和next 和hasNext()类比学习。hasPervious判断该位置是否还有元素,previous获取当前位置的元素并往前移动指定
局限性:迭代器默认0索引处,当hasPervious为true,然后将指针往前移动并获取元素(previous)会报错
只有把迭代器往后移动后,才能调用
列表迭代器,是一个接口ListIterator,他是Iterator的子接口,和普通迭代器相比,列表迭代器的用法没有什么不同,只是为如果要想在迭代器遍历时,添加 修改集合中的内容提供了方法
package Test;
import java.util.*;
//演示List的5种遍历方式
public class ListTest2 {
public static void main(String[] args) {
// 列表迭代器遍历
//创建集合并添加元素
List<String> list=new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
// 定义一个迭代器的对象
ListIterator<String> it = list.listIterator();
while(it.hasNext()){
String str = it.next();
if(str.equals("ccc"))
//添加
//it.add("NNN");//[aaa, bbb, ccc, NNN]
//修改
//it.set("good");//[aaa, bbb, good]
//删除
it.remove();//[aaa, bbb]
}
System.out.println(list);
}
}
迭代器在遍历的时候不能通过集合的方法进行增删改查,只能通过迭代器的方法进行操作
2种迭代器对比:Iterator所有的集合都适用,但是该迭代器只提供了删除元素操作(remove),而ListIterator迭代器是Iterator的子接口,仅仅适用于List系列的集合,用法和Iterator相同,但是它对于集合的操作提供了增删改查所有的操作
数组或者集合普通for循环 集合.fori
数据结构
- 数据结构概括
数据结构(栈)
数据结构(队列)
数据结构(数组)
- 链表特点总结
双向链表
在查找的时候先判断是离头近还是离尾近,然后决定是从头开始查找还是从尾开始查找**
ArrayList源码分析
实现原理
创建集合时,数组长度为0,添加一个元素时长度变为10,数组名为elementData,size为元素的个数,size既表示元素的个数又代表下次存入的位置
当数组存满的时候,将会创建一个新的数组(长度为原数组的1.5倍),并将原数组里面的数据拷贝到新数组里面,如果数据还被装满了,数组将会继续扩容到原来的1.5倍
- 源码分析
LinkList(双向链表)
链表和数组相比在插入和删除时的效率更高,而在查询时的效率很低,但是如果操作的是头元素或者是尾元素,速度也是极快的,对此java专门提供了相应的api来完成相应的操作(对于以下的方法了解即可,我们一般是使用Collection或者List里面的方法来完成相应的操作)
LinkList源码分析
迭代器的底层源码分析
- 并发修改异常
在使用迭代器或者增强for循环遍历集合时,使用集合的方法修改集合内容将会出现并发修改异常