集合产生背景:传统的容器(数组)在进行增、删等破坏性操作时,需要移动元素,可能导致性能问题;同时添加、删除等算法和具体业务耦合在一起,增加了程序开发的复杂度。
Java集合框架提供了一套性能优良、使用方便的接口和类,它们位于java.util包中
一,Collection
Collection是java集合框架种的顶层接口
Collecion接口是一个容器,容器中只能存储引用数据类型,建议存同一类型的引用类型,方便后续遍历等操作。
那简单介绍几种集合常用方法(无非也是围绕:增,删,改,查四点)
public static void main(String[] args) { Collection c1=new ArrayList(); //增 用的是 .add c1.add("apple");//其实这里本质上算一个多态:Object object=New String("apple"); c1.add("banana"); System.out.println(c1); //增另外一个集合 用的是 .addAll Collection c2=new ArrayList(); c2.add("java"); c2.add("c++"); c1.addAll(c2); System.out.println(c1); //删 .clear .remove/All .retainAll //c1.clear(); //全部清除 // System.out.println(c1); c1.remove("c++"); System.out.println(c1); c1.add("c++"); //其它两个形式和add的差不多,就不写了 //查 .contains/All isEmpty Size System.out.println(c1.contains("apple")); System.out.println(c1.containsAll(c2)); System.out.println(c1.isEmpty());//判断c1是不是空集合 System.out.println(c1.size());//判断c1集合里面有几个元素 //改(其子类会有具体方法表示) }
集合的遍历
Iterable可遍历的接口,几个接口继承于它,集合支持快速遍历
for(Object item:c1){ System.out.println(item); }//这个遍历写法都是为求方便的写法
那我们一起看下快速遍历的本质
Collection是继承Iterable接口的。这个Iterable接口里面定义了一个方法iterator()用于获取集合的迭代器,是一个Iterator接口类型,iterator()内部返回一个实现类实现Iterator接口。这个实现类一定具有hasNext和next方法用于判断是否有下一个元素和获取下一个元素。快速遍历就是基于迭代器工作的。
//遍历本质 Iterator it=c1.iterator(); //迭代器 while (it.hasNext()) { Object item = it.next(); System.out.println(item);
不过上面这种写法把迭代器写在循环外面,导致循环完了jvm也不能回收这个内存空间,所以再优化一下(把迭代器放到循环条件里面)
//优化版本 for(Iterator it2=c1.iterator();it2.hasNext();){ Object item = it2.next(); System.out.println(item); }
二,list接口
list接口种的元素,有序,可重复。list接口中的元素通过索引来确定元素顺序(可初步理解为数组)
因为list是继承自collection接口的,所以常用方法也与collection差不多
public static void main(String[] args) { List list1=new ArrayList(); //增:add/addAll/add(index,el)/addAll(index,collection) // 可以观察到与collection不同的是list可以在确定的索引处添加元素或者另一个集合 list1.add("apple"); list1.add("banana"); System.out.println(list1); list1.add(1, "coco");//指定把元素添加在几号位上 System.out.println(list1); //删:clear/remove/removeAll/remove(index) list1.remove(0);//把几号位上的元素删除 System.out.println(list1); //改:set(index,el) list1.set(0, "c++"); System.out.println(list1); //查:get(index)/indexOf/lastIndexOf() System.out.println(list1.get(1)); list1.add("java"); list1.add("js"); System.out.println(list1); System.out.println(list1.indexOf("java")); System.out.println(list1.lastIndexOf("js")); }
//其它方法:contains/containsAll/isEmpty/size 都是继承自collection接口的方法,就不多说了
list接口遍历
list的遍历和collection相同点都有快速遍历,迭代器遍历(继承性质)(iterator)
不同点是list有自己的遍历方式:普通for,正(逆)向遍历(listiterator)
public static void main(String[] args) { List list1=new ArrayList(); list1.add("apple"); list1.add("banana"); list1.add("coco"); System.out.println(list1); //快速遍历 for(Object item:list1){ System.out.println(item); } //普通for (因为list是有序排列的,所以可以用到数组的遍历方式) for(int i=0;i<list1.size();i++){ System.out.println(list1.get(i)); } //迭代器(优化的写法我就不写了,把迭代器放到循环里就ok了) Iterator it=list1.iterator(); while (it.hasNext()) { System.out.println(it.next()); } //正向遍历 ListIterator it2=list1.listIterator(); while (it2.hasNext()) { System.out.println(it2.next()); } //逆序遍历(把next都改成previous就ok了) while (it2.hasPrevious()) { System.out.println(it2.previous()); } //正逆序遍历之从index位开始遍历 ListIterator it3=list1.listIterator(1); while (it3.hasNext()) { System.out.println(it3.next()); } }
ArrayList与Vector
ArrayList 是List接口的实现类,底层数据结构是数组,实现大小可变的数组。
ArrayList 线程不安全,jdk1.2
ArrayList 底层数据结构是数组,默认数组大小是10,如果添加的元素个数超过默认容量,ArrayList会自动拓容,拓容原则:newCapacity = oldCapacity + oldCapacity / 2;
如果未来确定序列的元素不在增加,通过调用trimToSize()调制容量至合适的空间。
ArrayList作为List接口的实现类,常用方法和遍历方法参考List接口。
Vector 是List接口的实现类,底层数据结构也是数组,也是大小可变的数组。
Vector是线程安全的,jdk1.0
Vector底层数据结构是数组,默认数组大小是10,如果添加的元素个数超过默认容量,Vector会自动拓容,拓容原则:newCapacity = oldCapacity +capacityIncrement(增长因子);如果未来确定序列的元素不在增加,通过调用trimToSize()调制容量至合适的空间。
注意:Vector 在实现List接口的同时,同添加了自身特有的方法xxxElement,未来使用时为了程序的可拓展性,一定要按照接口来操作Vector
LinkedList是List接口的实现类,底层数据结构是链表。(区别数组)
LinekList常用方法和遍历方法参照List接口。
LinkedList 线程不安全。
public static void main(String[] args) { //linkedlist 除了实现list接口,也实现栈接口(用push入栈和pop出栈) LinkedList list=new LinkedList(); list.push("apple"); list.push("banana"); list.push("coco"); System.out.println(list); System.out.println(list.pop()); System.out.println(list.pop()); System.out.println(list.pop()); //但是这样元素出栈完之后继续出栈会发生异常 System.out.println(list.pop()); }
队列接口(Queue)
抛出异常 | 返回特殊值(null) | |
增加 | add() | offer(e) |
删除 | remove() | poll() |
查找 | element() | peek() |
区别就是抛出异常会抛出异常( java.util.NoSuchElementException)
返回特殊值就是不会出现异常,但会返回null
public static void main(String[] args) { LinkedList queue=new LinkedList(); queue.add("apple"); queue.add("banana"); queue.add("coco"); System.out.println(queue); System.out.println(queue.remove()); System.out.println(queue.element()); //返回值类型 queue.offer("java"); System.out.println(queue); }
双向队列(Deque)接口 | 第一个元素(头部) | 最后一个元素(尾部) | ||
抛出异常 | 特殊值 | 抛出异常 | 特殊值 | |
增加 | addFirst() | offerFirst() | addLast() | offerLast() |
删除 | removeFirst() | pollFirst() | removeLast() | pollLast() |
检查 | getFirst() | peekFirst() | getLast() | peekLast() |
与队列接口不一样的就是两边都可以进行出栈和入栈(代码使用形式和上面一样就不写了)
Iterator和lisIterator
区别:Iterator在迭代过程中不允许向集合种添加元素(会发生ConcurrentModificationException异常)
但是listiterator可以按任一方向便利列表
那我这里只写listiterator
public static void main(String[] args) { ArrayList list=new ArrayList(); list.add("apple"); list.add("banana"); list.add("coco"); System.out.println(list); //区别就在下面这句代码 ListIterator it=list.listIterator(); while (it.hasNext()) { String item=(String) it.next(); if(item.equals("coco")){ it.add("java"); } } System.out.println(list); }
泛型(generic)
将类型参数化(规定之后后面的类型只能是同一类型)
形式:ArrayList<E>list=new ArrayList<E>(); //<E> 这个东西就是泛型
而且泛型在编译器起作用,运行时jvm不会察觉到。(个人理解泛型就是用来规范咱们程序员代码习惯的)
泛型类(一个类中属性数据类型不确定时,就用泛型类)
形式:类名后面加<E>
//定义一个泛型类 public class Q1<E> { private E e; public E getE() { return e; } public void setE(E e) { this.e = e; } public Q1(E e) { super(); this.e = e; } public Q1() { super(); // TODO Auto-generated constructor stub } }
public static void main(String[] args) { Q1<String> q1=new Q1<String>();//只准后面对象设置成字符串类型 q1.setE("apple"); Q1<Integer> q2=new Q1<Integer>();//只准后面对象设置成Integer类型 q2.setE(1); }
泛型方法(同理,方法参数类型不确定就用泛型)
public <T> void xxx(T a) { System.out.println(a); }
例如:
public class Test { //同时声明不同类型的同一方法,之前一般是用到方法重载,但代码量大 public void showInfo(int a) { System.out.println(a); } public void showInfo(float a) { System.out.println(a); } public void showInfo(String a) { System.out.println(a); } //如果用一个泛型那只需要先写个形式,后面在调方法时想写什么类型都可以 public <T> void showInfo(T a) { System.out.println(a); } }
public static void main(String[] args) { Student stu = new Student(); stu.showInfo(1); stu.showInfo("apple"); stu.showInfo(1.0f); }
所以泛型就是方法重载的进化版了