5.1 java类集(java学习笔记)Collection、List接口及ArrayList、LinkedList类。
一、类集
类集就是一组动态的对象数组,说类集可能不好理解,类集又称容器,容器顾名思义就是放东西的地方。
类集就是为了让我们更加简洁,方便的存放、修改、使用数据的。
二、Collection接口
我们看下文档中的描述,Collection这个接口主要是代表容器的规范,主要用于实现其他具体功能的子接口。
Collection更像是一个抽象出来的规则,它代表了一种标准和规范,具体的容器的实现是依靠子接口去实现的。
Collection只是定义了一种标准,其他的子接口必须按照这个标准来,也可是说是其他子类必须完成这个标准所规定的内容。
三、List接口、ArrayList类
List是Collection的一个子接口,在Collection原有的基础上进行了一定扩充,List中存放的内容是允许重复的。
我们来看文档对它的描述:
An ordered collection (also known as a sequence).
有序集合(也称为序列)。更直白一点就是一个放数据的容器,而且是有序的。
ArrayList是一个类,它继承自abstractArrayList,而abstractArrayList继承了Collection接口。
这关系有点乱,我们来看下图。
ArrayList看名字就知道和数组有关,新建一个ArrayList对象里面存储方式是数组。
这就是ArrayList中存储数据的数组。
我们先介绍一个方法,就是向容器中添加数据。
add(E e);
我们简单的看下源码,将传递进来的数据e添加数组elementData中,然后size++,就是一个普通的往数组后面添加元素的操作。
这里E是泛型,由使用时决定。
1 import java.util.ArrayList; 2 import java.util.List; 3 4 public class test { 5 //ArrayList Collection 6 public static void main(String[] args) { 7 List<Integer> list = new ArrayList<>(); 8 list.add(1); 9 list.add(2); 10 list.add(3); 11 System.out.println(list); 12 } 13 }
运行结果:
[1, 2, 3]
代码中为什么要像List<Integer> l = new ArrayList<>();这样写?
而不直接ArrayList<Integer> i = new ArrayList<>()写?
可能有的人会有疑问,我一开始也有疑问。
关于这个问题可以参考:
https://www.cnblogs.com/huang-changfan/p/9613971.html
四、ArrayList类中常用方法
1、E get(int index);
返回指定数组指定下标的数据。
1 import java.util.ArrayList; 2 import java.util.List; 3 4 public class test { 5 //ArrayList Collection 6 public static void main(String[] args) { 7 List<Integer> list = new ArrayList<>(); 8 list.add(1); 9 list.add(2); 10 list.add(3); 11 System.out.println(list.get(1)); 12 } 13 }
运行结果:
2
我们看下源码中get方法的实现也很简单,首先是一个下标检查,判断下标是否合法(大于等于0小于size),然后直接返回数组中指定下标的元素。
2、addAll(Collection<? extends E> c)
添加一组数据,传递进去的泛型参数设置了上限。
import java.util.ArrayList; import java.util.List; public class test { //ArrayList Collection public static void main(String[] args) { List<Integer> list = new ArrayList<>(); List<Integer> list2 = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list2.add(4); list2.add(5); list2.add(6); list2.addAll(list); //将list里面的全部数据添加到list2中 System.out.println(list2); } }
运行结果:
[4, 5, 6, 1, 2, 3]
list中存有1,2,3.list2中存有4,5,6.然后将list中的内容添加到list2中,
list2中就有了4,5,6,1,2,3。
3、indexOf(Object obj)
返回指定内容的下标。
1 import java.util.ArrayList; 2 import java.util.List; 3 4 public class test { 5 //ArrayList Collection 6 public static void main(String[] args) { 7 List<Integer> list = new ArrayList<>(); 8 list.add(1); 9 list.add(2); 10 list.add(3); 11 System.out.println(list.indexOf(3)); 12 } 13 }
运行结果:
2
我们来看indexOf的源码,首选项判断是否为为空,是的话对数组遍历,如果有元素为空的话直接返回该元素下标。
反之,也是遍历,调用Object类中equals方法进行比较,注意Object中的equals方法是比较值用的是==。
4.remove(int index)
删除指定位置的元素。
1 import java.util.ArrayList; 2 import java.util.List; 3 4 public class test { 5 //ArrayList Collection 6 public static void main(String[] args) { 7 List<Integer> list = new ArrayList<>(); 8 list.add(1); 9 list.add(2); 10 list.add(3); 11 list.remove(2); 12 13 System.out.println(list); 14 System.out.println(list.size()); 15 } 16 }
运行结果:
[1, 2] 2
我们来看下源码是怎么操作的。
我们看实现功能的核心部分,
System.arraycopy(elementData, index+1, elementData, index,numMoved);
这是一个数组拷贝函数,例如我这里index为1,那么从2开始将后面的所有数据前移一位,最后一位加个null。
这样index = 1的那一项就被覆盖了,达到了删除效果。
其余remove函数也大同小异。
五、Vector类
Vector是一个比较古老的类,考虑到很多人员已经习惯了Vector,所以后来将其实现了List接口被保留了下来。
因为Vector也实现了List接口,所以用法与之前的无太大差异。
六、LinkedList类
LinkList同样是实现了List接口的一个子类。
它的存储方式是链表,相关的一些操作方法由于是实现List接口的,所以功能上与ArrayList大概是相同的。只是由于数据的存储方式不同
所以具体功能的实现与ArrayList有区别。
下面举个例子:
add(E e);
1 import java.util.ArrayList; 2 import java.util.LinkedList; 3 import java.util.List; 4 5 public class test { 6 //ArrayList Collection 7 public static void main(String[] args) { 8 List<Integer> list = new LinkedList<>(); 9 list.add(1); 10 list.add(2); 11 list.add(3); 12 13 System.out.println(list); 14 System.out.println(list.size()); 15 } 16 }
运行结果: [1, 2, 3] 3
可以看出实现的功能是相同的。
但我们来看下源码:
可以看到,这里添加元素并不是想ArrayList那样添加,通过链表的连接添加数据。
我可以尝试下,自己实现一个简易的LinkedList
下面写的简易版的LinkedList实现了添加,正序遍历和反序遍历,remove,get。
1 public class test { 2 //ArrayList Collection 3 public static void main(String[] args) { 4 MyLinkedList<Integer> list = new MyLinkedList<>(); 5 list.add(1); 6 list.add(2); 7 list.add(3); 8 list.fOutLinked(); 9 // System.out.println(list.size); 10 //list.get(1); 11 list.remove(1); 12 list.fOutLinked(); 13 14 } 15 } 16 17 class MyLinkedList<E>{ //自己实现的简易LinkedList类 18 int size = 0; 19 private Node<E> first;//创建用于存放头结点结点。 20 private Node<E> last; //创建用于存放末尾结点的结点。 21 public void add(E e){ //添加方法 22 if(first == null){ //如果没有头结点,即什么结点都没有创建,是第一次创建结点时 23 Node<E> n = new Node<E>();//首先创建一个结点 24 n.setDate(e); //将该节点中的数据区方法传递进来的数据 25 n.setFirst(null); //并将头结点的头部放入null 26 n.setLast(null); //同时将头结点的尾部放入null 27 first = n; //然后将n给头结点和尾结点 28 last = n; //因为这时双向链表,所以在只有一个结点的时候, 29 size++; //该节点即是头结点,也是尾结点。 30 }else{ 31 Node<E> n = new Node<E>();//如果已经有了结点。 32 n.setFirst(last); //则将新建结点的头部存放前一结点的信息 33 last.setLast(n); //然后将前一个结点的尾部设置为新建结点。 34 n.setDate(e); //然后将新建结点中的数据区放入传递进来的数据 35 n.setLast(null); //然后将新建结点的尾部放null 36 last = n; //然后将新建结点作为尾结点 37 size++; 38 } 39 } 40 public void fOutLinked(){//正序输出 41 Node<E> f = new Node<E>(); 42 f= first; //新建一个结点,并将头结点给它。 43 System.out.println(f.getDate()); //打印该结点的数据。 44 while(f.getLast()!= null){ //如果该节点的尾部不为null则: 45 f = f.getLast(); //将f结点之后的一个结点赋给f,此时f存放的是f之后的结点。 46 System.out.println(f.getDate());//打印出该节点的数据 47 } 48 } 49 public void lOutLinked(){ //逆序与正序思想相同 50 Node<E> l = new Node<E>(); 51 l= last; 52 System.out.println(l.getDate()); 53 while(l.getFirst()!= null){ 54 l = l.getFirst(); 55 System.out.println(l.getDate()); 56 } 57 } 58 59 public void get(int index){//获取指定位的数据。 60 int temp = 0; 61 Node<E> f = new Node<E>(); 62 f= first; 63 if(index == 0){//如果index ==0 ,直接打印头结点的数据。 64 System.out.println(f.getDate()); 65 }else{ 66 while(f.getLast()!= null){ //反之,判断下该节点的尾部是否为空 67 f = f.getLast(); //不为null则将f结点存放后一结点信息。 68 temp++; //同时temp++,每次后移一个结点就加一 69 if(temp == index){ //如果temp == index,则代表找到了指定位的结点 70 System.out.println(f.getDate());//打印 71 } 72 } 73 } 74 75 } 76 77 public void remove(int index){//移处指定位的结点 78 Node<E> l = new Node<>(); //首先创建两个结点,用于存放头结点,和尾结点。 79 Node<E> f = new Node<>(); 80 int count = 0; //判断当前结点是否为指定结点,每次后移一位会加一 81 f = first; 82 l = last; 83 if(index == 0){//如果移除的结点为头结点,则将头结点后面结点的头部设置为null 84 first.getLast().setFirst(null);//first.getLast()代表头结点后的结点, 85 }else //然后后面的结点设置本身的头部为null 86 if(index == size - 1){ //如果移除的结点为尾结点 87 last.getFirst().setLast(null);//则将尾结点之前结点的尾部设置为null。 88 }else{ 89 while(count<size-1){//如果要移除的结点既不是头结点,也不是尾结点。 90 f = f.getLast();//那么从头结点开始遍历, 91 count++;//每后移一个结点,count++ 92 if(count == index){ //如果找到了指定结点 93 f.getFirst().setLast(f.getLast()); //将指定结点的前一个结点的尾部,设置为指定结点的后一个结点。 94 f.getLast().setFirst(f.getFirst());//将指定结点的后一个结点的头部,设置为指定结点的前一个结点。 95 f.setFirst(null);//将指定结点的头部和尾部设置为null 96 f.setLast(null); 97 } 98 } 99 100 } 101 } 102 } 103 104 class Node<E>{ //结点 105 private Node<E> first; //存放前一结点的结点 106 private E date; //存放信息 107 private Node<E> last; //存放后一个结点的结点 108 public Node<E> getFirst() {//后面就是一些set,get方法 109 return first; 110 } 111 public void setFirst(Node<E> first) { 112 this.first = first; 113 } 114 public E getDate() { 115 return date; 116 } 117 public void setDate(E date) { 118 this.date = date; 119 } 120 public Node<E> getLast() { 121 return last; 122 } 123 public void setLast(Node<E> last) { 124 this.last = last; 125 } 126 }