Java学习之路(六):集合
集合的由来
- 数组的长度是固定的,当添加的元素超过了数组的长度,就需要对数组重新定义
- java内部给我们提供的集合类,能存储任意对象,长度是可以改变的。随着元素的增加而增加,随着元素的减少而减少
数组和集合的区别
- 数组既可以存储基本数据类型,又可以存储引用数据类型,基本数据类型存储的是值,引用数据类型存储的是地址值
- 集合只能存储引用数据类型(对象Object),集合中也可以存储基本数据类型,但是在存储的时候会自动装箱变成对象 eg:int==>Integer
- 数组长度是固定的,不能自动增长
- 集合的长度是可变的,可以根据元素的增加而增长
如果元素的个数是固定的,我们使用数组
如果元素的个数不固定,我们使用集合
集合Collection类:
- list:有序集合,有索引。存与取的顺序一样,可以重复
- ArrayList(数组实现)
- LinkedList(链表实现)
- Vector(数组实现)
- Set:无序集合,无索引。存与取的顺序不一样,不可以重复
- HashSet (哈希算法)
- TreeSet(二叉树算法)
ArrayList集合中部分数组实现的原理
eg:有个容量为10的初始化数组,当它不够用的时候,就会自动生成一个1.5倍大的数组,将值重新赋值后,把以前的那个小的数组垃圾回收了
Collection的几个基本方法
- boolean add(E e) 添加一个元素
- boolean remove(object o) 移除某一个元素
- void clear() 清空这个集合
- boolean contains(Object o)
- boolean isEmpty() 判断这个集合是否为空
- int size() 大小
- toArray() 将集合转化为数组
- boolean addAll(Collection c1) 将c1添加进来
- boolean removeAll(Collection c1) 移除this中还有的c1中的所有元素
- boolean containsAll(Collection c1) 判断this是否包含有c1中的所有元素
- boolean retainAll(Collection c1) 求并集
遍历一个集合,我们使用迭代器
迭代器是用来遍历集合中的每一个元素的
方法:hasNext()和next()
package lesson3; //这是一个小例子 import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; public class null03 { public static void main(String[] args) { Collection c1 = new ArrayList();//这不就是多态? c1.add("A"); c1.add("B"); c1.add("C"); Iterator iterator = c1.iterator(); while(iterator.hasNext()){ Object o = iterator.next(); System.out.println(o); } } }
List集合
- void add(int index,E element)
- E remove(int index)
- E get(int index)
- E set(int index,E element)
List可以通过size()和get()方法结合来遍历List集合
package day19_null; import java.util.ArrayList; import java.util.List; public class null01List { public static void main(String[] args) { List li = new ArrayList(); li.add("A"); li.add("B"); li.add("C"); li.add("D"); li.add(0,"E"); for(int i=0;i<li.size();i++){ System.out.println(li.get(i)); } } }
结果:
E
A
B
C
D
这里我们提出一道问题
有一个集合,判断里面有没有“B”这个元素,如果有,就添加一个“b”元素。
当我们使用iterator来遍历的时候,是不能一边遍历一遍添加值的
package day19_null; //错误的写法 import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class null01List { public static void main(String[] args) { List li = new ArrayList(); li.add("A"); li.add("B"); li.add("C"); li.add("D"); li.add(0,"E"); Iterator iterator = li.iterator(); while(iterator.hasNext()){ //System.out.println(iterator.next()); Object o = iterator.next(); String o_str = (String)o; if(o_str.equals("B")){ li.add("b"); } } } }
所以呢,我们就要使用ListIterator这个了
- boolean hasNext() 是否有下一个
- boolean hasPrevious() 是否有前一个
- Object next() 下一个元素
- Object previous() 上一个元素
对比一下:
package day19_null; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ListIterator; public class null01List { public static void main(String[] args) { List li = new ArrayList(); li.add("A"); li.add("B"); li.add("C"); li.add("D"); li.add(0,"E"); ListIterator iterator = li.listIterator(); while(iterator.hasNext()){ Object o = iterator.next(); String o_str = (String)o; if(o_str=="B"){ iterator.add("b"); } } while(iterator.hasPrevious()){ System.out.println(iterator.previous()); } } }
Vector的使用
vector在JDK1.0版本就已经有了,从v1.2开始,此类改进为可以实现List接口,使他成为Java Collections Framework成员
Vector是同步的,也就是线程是有锁的,是安全的,当然也是效率低的
Vector的特有功能:
- public void addElement(E obj)
- public E elementAt(int index)
- public Enumeration elements()0
package day19_null; import java.util.Enumeration; import java.util.Vector; public class null02_vector { public static void main(String[] args) { //创建一个vector对象 Vector vec = new Vector(); vec.addElement("A"); vec.addElement("B"); vec.addElement("C"); vec.addElement("D"); vec.addElement("E"); System.out.println(vec.elementAt(0)); Enumeration enumeration = vec.elements(); while(enumeration.hasMoreElements()){ System.out.println(enumeration.nextElement()); } } }
List的三个子类的特点
ArrayList:
- 底层数据结构是数组,查询比较块,增删比较慢
- 线程不安全,所以块
Vector:
- 底层数据结构是数组,查询比较块,增删比较慢
- 线程安全,效率较低(相对于ArrayList)
LinkedList:
- 底层的数据结构是链表,查询慢,增删快
- 线程不安全,所以快
例子:去除ArrayList中重复的对象
思路:创建一个新的ArrayList,然后借由新创建的ArrayList来进行判断
package day20_nullnull; import java.util.ArrayList; import java.util.List; public class null01_removeSame { public static void main(String[] args) { // TODO Auto-generated method stub //TheUsualWayToGetRidOfWeight(); TheUsualWayToGetRidOfWeightObject(); } /** * 如果ArrayList中存在Object */ private static void TheUsualWayToGetRidOfWeightObject() { List li = new ArrayList(); li.add(new Student("null1",1)); li.add(new Student("null2",2)); li.add(new Student("null3",3)); li.add(new Student("null4",4)); li.add(new Student("null1",1)); List li2 = new ArrayList(); for(int i=0;i<li.size();i++){ // System.out.println(li.get(i)); if(!li2.contains(li.get(i))){ li2.add(li.get(i)); } } for(int i=0;i<li2.size();i++){ System.out.println(li2.get(i)); } } /** * 如果ArrayList中都是String */ private static void TheUsualWayToGetRidOfWeight() { //创建一个数组 List li = new ArrayList(); li.add("A"); li.add("B"); li.add("C"); li.add("D"); li.add("A"); List li2 = new ArrayList(); for(int i=0;i<li.size();i++){ // System.out.println(li.get(i)); if(!li2.contains(li.get(i))){ li2.add(li.get(i)); } } for(int i=0;i<li2.size();i++){ System.out.println(li2.get(i)); } } } class Student{ String name; int age; public Student() { super(); } public Student(String name, int age) { super(); this.name = name; this.age = age; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } }
注意:我们会很神奇的发现,当ArrayList中存在Object的时候,居然不行?!!
补充:
我们是使用contains来进行判断新的列表是否存在的。但是在contains的内部调用的其实还是equals方法。
在String中因为重写了equals方法,所以他是根据String的内容进行判断的
但是,在Object判断的却是内存地址。
所以,我们要重写class的equals方法。
package day20_nullnull; import java.util.ArrayList; import java.util.List; public class null02 { public static void main(String[] args) { List li1 = new ArrayList(); li1.add(new Student2("null1",1)); li1.add(new Student2("null2",2)); li1.add(new Student2("null3",3)); li1.add(new Student2("null4",4)); li1.add(new Student2("null1",1)); List li2 = new ArrayList(); for(int i=0;i<li1.size();i++){ if(!li2.contains(li1.get(i))){ li2.add(li1.get(i)); } } for(int i=0;i<li2.size();i++){ System.out.println(li2.get(i)); } } } class Student2{ String name; int age; public Student2() { super(); } public Student2(String name, int age) { super(); this.name = name; this.age = age; } @Override public String toString() { return "Student2 [name=" + name + ", age=" + age + "]"; } @Override public boolean equals(Object obj){ Student2 other = (Student2) obj; return this.name.equals(other.name)&&this.age==other.age; } }
OK,没有问题
注意:contains内部调用的还是equals方法
LinkedList的使用
我们现在学了集合中list的两个ArrayList和Vector两个,这两个的底层都是Array数组
接下来的LinkedList的底层是链表
方法:
- public void addFirst(E e)以及addLast(E e)
- public E getFirst()以及getLast()
- public E reoveFirst()以及removeLast()
- public E get()
使用LinkedList实现栈和队列
- 栈:先进后出
- 队列:先进先出
package lesson2; //栈的模拟 import java.util.LinkedList; public class null01 { public static void main(String[] args) { Stack stack = new Stack(); //进栈 stack.in("A"); stack.in("B"); stack.in("C"); stack.in("D"); stack.in("E"); //出栈 Object o = stack.out(); System.out.println(o); System.out.println(stack.list); } } class Stack{ //栈:先进后出 LinkedList list = new LinkedList(); //进栈的方法 public void in(Object o){ list.addFirst(o); } //出栈的方法 public Object out(){ return list.removeFirst(); } }
队列的模拟和其类似,只不过是相反而已,队列是先进先出的
当然,其实不用写,在LinkedList的内部就已经实现了栈的一系列方法
package lesson2; import java.util.LinkedList; public class null02 { public static void main(String[] args) { //创建一个栈对象 LinkedList stack = new LinkedList(); //进栈的方法 stack.push("A"); stack.push("B"); stack.push("C"); stack.push("D"); //出栈的方法 Object o1 = stack.pop(); System.out.println(o1); Object o2 = stack.pop(); System.out.println(o2); Object o3 = stack.pop(); System.out.println(o3); Object o4 = stack.pop(); System.out.println(o4); stack.pop(); //没有了还要弹,就报错了 /** Exception in thread "main" java.util.NoSuchElementException at java.util.LinkedList.removeFirst(Unknown Source) at java.util.LinkedList.pop(Unknown Source) at lesson2.null02.main(null02.java:26)*/ } }
泛性的概述和简单使用
作用:把类型明确的工作推前到创建对象或者调用方法的时候
泛型是一种参数化类型,把类型当做参数一样传递来明确集合的元素类型
好处:
- 提高安全性
- 省去了强转的麻烦
基本使用:
- 声明集合泛型的格式:List<String> list = new ArrayList<String>();
- <>中放的必须是引用数据类型