集合框架
接下来将要学习的内容为:
1.Collection接口(线性存储)(容器的最高父接口)
a)List接口(有序(指有下标/索引)、数据可重复)
b)Set接口(无序,数据不可重复)
c)queue队列
2.Map接口(键(key)-值(value)对存储)(映射关系)
首先了解一个设计模式:适配者模式(通俗的理解是功能基本不变,只是改了方法名)(最主要的功能就是将一个接口转换为另一个接口)
List接口的实现类:
-
ArrayList(底层为数组结构)
在数组中赋值方式为a[0] = 1; ArrayList底层还是数组,数组赋值是通过脚标赋值的,我现在还要用他赋值的功能,但是对外提供接口变了,赋值变为了add();
优点:查、改效率高
2.LinkedList(底层为双向链表)
优点:增、删效率高
3.Vector(和ArraysList基本上一样(数组结构),只不过他是线程安全的,访问效率低)
自己实现容器就需要实现Collection接口,并且实现所有需要实现的方法。Collection没有get()方法,所以不能定位。它也没有set()方法。但List里面有这两个方法
ArrayList
ArrayList 无参的构造方法,创建了一个长度为0的数组
ArrayList list = new ArrayList();//默认容量为0
也有有参的构造方法,指定了初始大小
本质上就是创建了一个参数大小的数组
ArraysList list = new ArrayList(10);//容量设置为10
//还可以传一个集合,将传进的集合中的元素copy给新集合
来看看源码
public ArrayList(int initialCapacity) { //创建时可传参
if (initialCapacity > 0) { //如果传的参数容量大于0
this.elementData = new Object[initialCapacity]; //创建一个大小为initialCapacity的数组
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA; //如果传入的参数为0,那就是默认的那个空数组
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ //如果参数小于0则抛出一个异常
initialCapacity);
}
}
1.添加元素(add())
添加过程:
- 先将底层数组扩容
- 将元素赋值到数组对应位置
- 返回true
list.add(10);//这里的这个10是基础数据类型的对象类型:Integer类型
list.add("abc")//添加元素的位置跟add的次序有关,第一个add在数组的第一个位置
源码
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e; //核心代码:把这个元素赋给了数组相应位置。
return true; //添加成功,返回一个true。如果添加不成功在上一行代码中就会报错,不会执行到这一行
}
//这就是适配者模式,把给数组赋值的功能的名称转为了add,外面添加元素调用add方法就行了,功能没有变
add还可以给指定位置添加元素
//为指定位置添加元素 第一个参数:指定位置,第二个参数:元素值
add(index,data);//给index位置添加data数据,其他数据会往后挤
注意:
- 只能向前面有数据的位置插入,不能中间空了好多位置向后插入,比如容量大小为10,目前只有两个元素,则只能往0,1,2的位置插入,不能向3,4,5…等位置隔着插入
- ArrayList对象不能存储基本类型,只能存储引用类型的数据。类似 不能写,但是存储基本数据类型对应的包装类型是可以的。所以,想要存储基本类型数据, <> 中的数据类型,必须转换后才能编写。
2.获取元素(get())
list.get(0);
源码
//get
public E get(int index) {
rangeCheck(index);
return elementData(index);//如果上句不抛异常,则执行到这句,返回相应的元素
}
//rangeCheck()
private void rangeCheck(int index) {
if (index >= size) //如果脚标大于size则抛异常
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//elementData()
E elementData(int index) {
return (E) elementData[index];//返回数组相应脚标位置的元素
}
//适配者模式把上面的取值过程转换为了一个方法名get()
大家注意一下上面的为什么要用index跟size比较而不用index跟elementData.length?(elementData就是我们存数据的数组)
答:size记录的是现在集合中的元素个数,而elementData.length获取到的是数组的长度。如果我指定初始容量为100,而只添加了两个元素。用elementData.length获取到的是100,但容量里面只有两个数据是有效的,所以存几个只能取几个。所以自己在写底层实现时也要有一个变量来记录数据的个数,因为初始数组大小不准。
如果是数组的话,获取没放过元素的位置,返回的是默认值。而arraylist的get方法就会报错
获取元素个数:size()方法(数组中获取长度是length,String获取长度是length())
3.修改元素(set())
返回值为修改之前的值
list.set(0,"first");//注意他修改后会返回原来的数据
4.删除元素(remove())
按照下标删返回删除之前的元素;按照元素删除,返回布尔值。
list.remove(int index);//按下标删除,返回值是删之前的元素
list.remove(Object o);//按内容删,返回一个布尔值,删成功或删失败
//删除一个元素后,后面的元素统一前移
注意:如果元素为整数,remove的参数为int,按照下标删除;参数为Integer,按照元素删除
list.remove(3);//按脚标删
list.remove(new Integer(3));//按元素删,注意存入的基础数据类型元素默认是它的对象类型的
如果容器中有多个3,remove一次,删除的是第一个3
ArrayList的遍历
ArrayList<String> list = new ArrayList<>();//指定容器寸的内容必须是String的(泛型的一个特点,编译时就把类型确定了)
//1.8版本之前,后面的<>里面需要加类型,
//1.8版本之后 可以不用加,因为1.8版本之后加入了语法糖,可以进行类型推断
//泛型不支持基本数据类型只支持对象数据类型
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
//遍历ArrayList
for(int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
//迭代器:将Collection集合的遍历统一起来,迭代器一开始指向空null
Iterator<String> iterator = list.iterator();//注意不是new
/*
//判断是否有下一个元素,并没有获取它的值,而是他的内存地址,如果内存地址都没有,则值一定没有
boolean hasNext = iterator.hasNext();
//如果有,返回true,如果没有,返回false
System.out.println(hasNext);
//过去下一个元素
iterator.next();
*/
while (iterator.hasNext()) {
String e = iterator.next();
// 删除当前迭代到的对象
//iterator.remove(); 输出还会是原来的那些元素,这里是为了不影响迭代的过程(如果删除了第一个元素,后面的元素会往前挤),但实际上这个元素确实被删了,不信你试试输出size()
System.out.println(e);
}
只有实现Itreable接口才可以使用增强for循环,他的底层就是用迭代器实现的
Iterator只需要记住hasNext()、next()、remove()即可
注:
- 通过迭代器删除元素,不会影响整个遍历过程
- 迭代完成后,迭代器的指针就会指向最后一个元素,所以要想再次遍历就需要重新过去迭代器对象
list集合的迭代器
ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
ListIterator<String> listIterator = list.listIterator();
//从上向下
while (listIterator.hasNext()) {
System.out.println(listIterator.next());
}
//从下向上
while (listIterator.hasPrevious()) {
System.out.println(listIterator.previous());
}
//listIterator独有的方法
//添加元素
listIterator.add("e");
//获取上一个元素的下标
listIterator.previousIndex();
//获取下一个元素的下标
listIterator.nextIndex();
Iterator只能从上往下找(相当於单链表),ListIterator的特点是既能从上往下找,也能从下往上找。(相当于双向链表)
两个集合关系的方法
集合的一些方法
ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("bb");
list.add("dd");
list.add("ee");
list.add("ff");
//根据元素获取下标,第一次出现时的下标, -1表示该元素不存在
System.out.println(list.indexOf("aa"));//输出0
System.out.println(list.indexOf("h"));//输出-1
System.out.println(list.indexOf("bb"));//输出1
//最后一次出现的位置,比如获取某个人的最后一次登录时间
System.out.println(list.lastIndexOf("bb"));//输出2
//判断是否包含某个元素
System.out.println(list.contains("a"));//其实index方法就包含了这个功能,他的底层实现用的就是indexOf
//是否为空,即size()是不是为0。返回true为空
list.isEmpty();
//删除集合中的所有元素
list.clear();
集合求子集,两集合求合集、差集、交集
ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
ArrayList<String> list2 = new ArrayList<>();
list2.add("aa");
list2.add("bb");
list2.add("bb");
list2.add("cc");
list2.add("ff");
list2.add("gg");
/*
子集
第一个参数:起始下标(包含)
第二个参数:结束下标(不包含)
*/
List<String> list1 = list.subList(1,3); //父类引用子类对象
System.out.println(list1);
//stulist不能单独使用,他是定义在ArrayList里的内部类(且他是私有的,只能由外部类使用),就比如有一个私有成员变量age,如果你都没这个对象了,那这个age也就不存在了
/*
//求合集 将参数集合中的所有元素都添加到调用方法的集合中
//参数传的是集合,返回值是布尔型
list.addAll(list2);
System.out.println(list);
*/
/*
//求差集:将list中 list合list1的交集删除
//参数传的是集合,返回值是布尔型
//这里注意一下:如果list2里只有一个dd元素,而list里有多个dd元素,则list里所有的dd都会被删除
list.removeAll(list2);
System.out.println(list);
*/
//求交集
list.retainAll(list2);
System.out.println(list);
subList源码
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);//先检查范围是否正常
return new SubList(this, 0, fromIndex, toIndex);//当上面不抛异常才返回
}
static void subListRangeCheck(int fromIndex, int toIndex, int size) {
if (fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if (toIndex > size)
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if (fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex +
") > toIndex(" + toIndex + ")");
}
数组和集合的转换
1.集合转数组
list.toArray()
,他不灵活,返回值是Object
toArray有个可传参的方法,他是泛型方法,传进的参数是什么类型的它就返回什么类型<T> T[] toArray(T[] a);
eg:
List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
Object[] objectArray = list1.toArray();
List<String> list2= new ArrayList<>();
list2.add("A");
list2.add("B");
list2.add("C");
String[] strArray= list2.toArray(new String[list2.size()]);
2.数组转集合
int[] array_int = {1,2,3};
//asList:如果参数为基本数据类型数组,将整个数组作为集合的一个元素。如果是对象类型的数组,将数组中的元素转换到集合中
List list = Arrays.asList(array_int); //将会输出数组首元素的内存地址
Integer[] array_integer = {1,2,3};//这些元素就是一个一个的Integer对象
Arrays.asList(array_integer);//这样就将元素一个一个的放进了集合中