如标题,你将获得Collection、List、Set接口,以及其实现类ArrayList,LinkedList,HashSet、TreeSet类的讲解。众所周知,Java的学习就是从API里有啥、是啥、怎么用开始,再进阶至源码。
注意:
集合体系
为啥要诞生集合,还得从数组的缺陷说起。
数组(存储同一种数据类型的集合容器)的特点:
注意: Object类型的数组可以存储任意类型的数据。
Object[] arr = new Object[10];
arr[1] = "abc";
arr[2] = 'a';
arr[3] = 12;
基于数组的仅能存储同一种类型和容量不可变化的劣势下,诞生的集合!
集合的定义: 是存储任何对象数据的集合容器。在同一个容器中,可以同时存储int型和double型,甚至是自定义类型。
//试一试
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList array = new ArrayList();
array.add("我是字符串");
array.add(134);
array.add(3.14534);
for(int i=0; i<array.size(); i++) {
System.out.println(array.get(i));
}
}
}
/*
输出:
我是字符串
134
3.14534
*/
集合比数组的优势:
1. 集合可以存储任意类型的对象数据,数组只能存储同一种数据类型的数据。
2. 集合的长度是允许发生变化的,数组的长度是固定的。
Collection的体系结构(图片来自“Java3y”):
看别人的技术文章和看API(源码)的根本区别就是,技术文章会把最重要、最核心的方法先罗列出来,还会把方法进行分门别类,这样学习才能事半功倍!
方法:(增、删、查、判断、遍历/迭代)
增加
boolean add(E e)
添加成功返回true,添加失败返回false.boolean addAll(Collection c)
把一个集合的元素添加到另外一个集合中去。删除
void clear()
//移除所有元素boolean remove(Object o)
//指定集合中的元素删除,删除成功返回true,删除失败返回false.boolean removeAll(Collection c)
//删除交集元素boolean retainAll(Collection c)
//保留交集元素,其他元素删除;retain[保持、记住]查看
判断:Boolean类型;
boolean isEmpty()
boolean contains(Object o)
//判断集合中是否存在参数的元素;boolean containsAll(Collection c)
//判断调用集合是否包含参数集合的全部元素,即判断集合是否完全相等;//试一试
import java.util.ArrayList;
import java.util.Collection;
public class Main {
public static void main(String[] args) {
Collection col = new ArrayList();
col.add("por*hub"); //增
col.add("xvideo*");
col.remove("xvideo*"); //删
System.out.println("数量:"+col.size()); //查
System.out.println("含por*hub?"+col.contains("por*hub"));
System.out.println("空?"+col.isEmpty());//判断
}
}
/*
输出:
数量:1
含por*hub?true
空?false
*/
Object[] toArray()
//返回一个Object的数组,把集合中的元素全部存储到一个Object的数组中返回;Iterator iterator()
//迭代器遍历集合中的元素两种迭代方法示例:
//迭代:toArray()方法
import java.util.ArrayList;
import java.util.Collection;
public class Main {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<String>();
coll.add("abc1");
coll.add("abc2");
coll.add("abc3");
coll.add("abc4");
Object[] arr = coll.toArray(); //将ArrayList集合转换为Object数组
for(Object i:arr) { //遍历Object数组
System.out.println(i);
}
}
}
//迭代:迭代器方法
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<String>();
coll.add("abc1");
coll.add("abc2");
coll.add("abc3");
coll.add("abc4");
// 获取迭代器对象
Iterator it = coll.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
迭代器诞生的背景:
取出
集合中的元素,每个集合都需要不同的操作。减少
操作类,Java使用一个专业术语“迭代器iterator”来统称所有集合的取出操作。迭代器功能:
取出任意种类的集合中的元素。
方法概览:
boolean hasNext();
E next();
void remove();
迭代器的应用的底层原理:
快速理解迭代器的使用:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<String>();
coll.add("abc1");
coll.add("abc2");
coll.add("abc3");
coll.add("abc4");
//迭代器,对集合ArrayList中的元素进行取出
//调用集合的方法iterator()获取出,Iterator接口的实现类的对象
Iterator<String> it = coll.iterator();
//接口实现类对象,调用方法hasNext()判断集合中是否有元素,集合中没元素, hasNext()返回了false
//接口的实现类对象,调用方法next()取出集合中的元素
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
}
}
实现类:
特点:
(除了Collection)特有方法:(增、删、获取、改、迭代)
添加
boolean add(int index, E element)
//把元素添加到指定的索引值里void add(int index, E element)
//把元素插入指定位置boolean addAll(int index, Collection extends E> c)
//把指定的集合添加到指定的索引里;删除
E remove(int index)
//移除指定索引上的元素,返回被删除的元素获取:
E get(int index)
//根据索引值,返回值int indexOf(Object o)
//找元素第一次出现的索引值int lastIndexOf(Object o)
//找元素最后一次出现的索引值E subList(int fromIndex, int toIndex)
//截取元素:从fromIndex到toIndex【注意:顾头不顾尾】;修改:
E set(int index, E element)
//使用指定的元素替换该索引值的内容,返回修改之前的元素总结:
特性:
超接口是Iterator
;并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。只能用来遍历List
,不能遍历其他。前向
也可以后向
遍历,而Iterator只能是前向遍历。因为List接口的本质是个双向链表。方法:(ListIterator接口除了Iterator接口的额外方法有)
增加操作:void add(E e);
删除操作:void remove();
修改操作:void set(E e);
取出操作(查询):
boolean hasNext()
向前判断是否有元素E next()
先取出当前指针指向的元素,然后指针向下移动一个单位。int nextIndex()
返回调用next()的元素索引boolean hasPrevious()
判断是否存在上一个元素。E previous()
当前指针先向上移动一个单位,然后再取出当前指针指向的元素。//实例
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class Main {
public static void main(String[] args) {
List col = new ArrayList();
col.add("thepor*tube com");
col.add("por**hub com");
col.add("javbus com");
col.add("3atv com");
ListIterator it = col.listIterator(); //获取到List的迭代器
System.out.println("向前迭代:");
while(it.hasNext()) {
System.out.println(it.next());
}
System.out.println("向后迭代:");
while(it.hasPrevious()) {
System.out.println(it.previous());
}
}
}
/*
输出:
向前迭代:
thepor*tube com
por**hub com
javbus com
3atv com
向后迭代:
3atv com
javbus com
por**hub com
thepor*tube com
*/
不会立即添加
到本个循环的指针所在位置;而是等待本个迭代器完成后再添加入;不允许
使用集合对象的方法改变集合中元素个数;完毕后
,才能使用集合的操作方法
,即在使用迭代器进行迭代的时候,不能改变集合的元素个数;在迭代器里next()和add():
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class Main {
public static void main(String[] args) {
List list = new ArrayList();
list.add("AA");
list.add("BB");
list.add("CC");
ListIterator it = list.listIterator(); //获取到迭代器
while(it.hasNext()){
System.out.print(it.next()+",");
it.add("additional"); // 把元素添加到当前指针指向位置;它不会立即添加到本个循环的指针所在位置;而是等待本个迭代器完成后再添加入;
}
System.out.print("\n集合list的内容是:"+list);
}
}
/* 输出:
AA,BB,CC,
集合list的内容是:[AA, additional, BB, additional, CC, additional]
*/
迭代时不能改变个数:
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class Main {
public static void main(String[] args) {
List list = new ArrayList();
list.add("AA");
list.add("BB");
list.add("CC");
ListIterator it = list.listIterator(); // 获取到迭代器
while (it.hasNext()) {
System.out.println(it.next());
/*
如果使用集合对象的方法操作集合元素个数,则有异常抛出: ConcurrentModificationException
list.add("aa"); // add方法是把元素添加到集合的末尾处的。相当于改变集合个数
list.remove("BB"); //remove方法是删除集合中的指定元素。相当于改变集合个数
*/
}
}
}
/* 输出:
AA
BB
CC
*/
只有在迭代器使用完毕后,才能改变集合个数:
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class Main {
public static void main(String[] args) {
List list = new ArrayList();
list.add("AA");
list.add("BB");
list.add("CC");
ListIterator it = list.listIterator(); //获取到迭代器
while(it.hasNext()){
System.out.print(it.next()+",");
}
list.add("aa"); //改变了集合元素个数的后面不能出现迭代器的使用,只能在迭代器使用完毕后才能使用集合对象方法来操作集合
//it.next(); 异常抛出!
System.out.print("\n集合中的元素"+list);
}
}
/* 输出:
AA,BB,CC,
集合中的元素[AA, BB, CC, aa]
*/
三种遍历集合的方式:
第一种: 使用get方法遍历。
第二种: 使用迭代器正序遍历。
第三种: 使用迭代器逆序遍历。
import java.util.ArrayList;
import java.util.Collection;
import java.util.ListIterator;
import java.util.List;
public class TestMain {
public static void main(String[] args) {
List list = new ArrayList();
list.add("AA");
list.add("BB");
list.add("CC");
//get遍历
for(int i=0;i<list.size();i++) {
System.out.println(list.get(i));
}
//迭代器正序遍历
ListIterator it = list.listIterator();
while(it.hasNext()) {
System.out.println(it.next());
}
//迭代器逆序遍历
while(it.hasPrevious()) {
System.out.println(it.previous());
}
}
}
再次提醒:只能在List接口的实例中使用ListIterator!!!
学习目标:
由于该类的绝大部分方法都是其父接口中继承而来,故需要学习的是该类的实现原理与特点!
实现原理:
底层是维护一个Object数组来实现;
使用指导: (什么时候使用ArrayList?)
如果目前的数据是查询比较多,增删比较少的时候,那么就使用ArrayList存储这批数据。 比如:高校的图书馆。
特点:
特有方法:
void ensureCapacity(int minCapacity)
//自己制定ArrayList数组的初始容量,亦可用构造方法来实现;trimToSize()
//为当前ArrayList实例根据已经存储的内容个数量调整容量增:
public boolean add(E e)
:添加元素public void add(int index, E element)
:在指定的索引处添加一个元素删:
public boolean remove(Object o)
:删除指定的元素,返回删除是否成功public E remove(int index)
:删除指定索引处的元素,返回被删除的元素改:
public E set(int index,E element)
:修改指定索引处的元素,返回被修改的元素public E get(int index)
:返回指定索引处的元素public int size()
:返回集合中的元素的个数增方法详解
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> array = new ArrayList<String>();
array.add("hello");
array.add("kyle");
array.add("Java");
// add(int index,E element):在指定的索引处添加一个元素
array.add(1, "android");
System.out.println("ArrayList目前的状态是:" + array); //ArrayList目前的状态是:[hello, android, kyle, Java]
}
}
删方法详解
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> array = new ArrayList<String>();
array.add("hello");
array.add("kyle");
array.add("Java");
array.remove("hello"); //根据元素名删除
System.out.println("删除某元素后ArrayList目前的状态是:" + array); //删除某元素后ArrayList目前的状态是:[kyle, Java]
String s = array.remove(0); //根据下标删除
System.out.println("删除第一个元素后的ArrayList目前的状态是:" + array); //删除第一个元素后的ArrayList目前的状态是:[Java]
}
}
改方法详解
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> array = new ArrayList<String>();
array.add("hello");
array.add("kyle");
array.add("Java");
array.set(0, "olleh");
System.out.println("ArrayList目前的状态是:" + array); //输出:ArrayList目前的状态是:[olleh, kyle, Java]
}
}
查方法详解
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> array = new ArrayList<String>();
array.add("hello");
array.add("kyle");
array.add("Java");
//public E get(int index):返回指定索引处的元素
System.out.println("获取的元素是:" + array.get(0));
//public int size():返回集合中的元素的个数
System.out.println("元素的个数:" + array.size());
}
}
遍历方法:
类似数组的遍历方法,通过size()和get()配合实现的
import java.util.ArrayList;
public class ArrayListDemo3 {
public static void main(String[] args) {
//创建集合对象
ArrayList<String> array = new ArrayList<String>();
//添加元素
array.add("hello");
array.add("world");
array.add("java");
//获取元素
//原始做法
System.out.println(array.get(0));
System.out.println(array.get(1));
System.out.println(array.get(2));
System.out.println("----------");
for(int x=0; x<3; x++) {
System.out.println(array.get(x));
}
System.out.println("----------");
//如何知道集合中元素的个数呢?size()
for(int x=0; x<array.size(); x++) {
System.out.println(array.get(x));
}
System.out.println("----------");
//最标准的用法
for(int x=0; x<array.size(); x++) {
String s = array.get(x);
System.out.println(s);
}
}
}
迭代器遍历ArrayList
import java.util.ArrayList;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
ArrayList<String> array = new ArrayList<String>();
array.add("hello");
array.add("kyle");
array.add("Java");
Iterator it = array.iterator();
while(it.hasNext()) {
System.out.println("数据是:" + it.next());
}
}
}
/* 输出:
数据是:hello
数据是:kyle
数据是:Java
*/
特点:
实现原理:
LinkedList 同时实现了 List 接口和 Deque 接口,也就是说它既可以看作一个顺序容器,又可以看作一个队列(Queue),同时又可以看作一个栈(Stack)。
底层原理:
增:
addFirst(E e)
//把元素添加到集合的首位置上。addLast(E e)
//把元素添加到集合的末尾处。删:
removeFirst()
//删除集合中的首位置元素,并返回removeLast()
//查:
getFirst()
//获取集合中的首位置元素getLast()
//获取集合中末尾的元素方法的使用方式类似于ArrayList,遍历的方式和ArrayList一样!
由于Vector已经被时间所淘汰,这里将不再详解。待后面Java集合进阶(研究源码和线程安全)时再做分析!
Set的特点:
方法
从其父接口(Collection)那里继承了绝大部分方法;
增:
删:
迭代:
查:
特有方法(相对于List接口):
//试一试
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set ss = new HashSet();
ss.add("Aaa"); //增
ss.add("Bbb");
ss.add(1234);
ss.add(8848);
ss.add(3.1415926);
ss.remove(8848); //删
System.out.println("长度:"+ss.size()); //获取
System.out.println("空?"+ss.isEmpty());
System.out.println("含1234?"+ss.contains(1234));
//迭代
System.out.println("内容迭代:");
Iterator it = ss.iterator();
while(it.hasNext()) {
System.out.print(it.next() + " ");
}
}
}
/*
输出:
长度:4
空?false
含1234?true
内容迭代:
Aaa 1234 Bbb 3.1415926
*/
遍历迭代
Set接口的三种遍历方式:
遍历Set
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Main {
public static void main(String[] args) {
//创建集合对象
//HashSet hs = new HashSet();
Set<String> set = new HashSet<String>(); //父接口引用指向子类对象,无法使用子类对象中特有的成员
//添加元素
set.add("hello"); //实现了Collection接口的add方法
set.add("world");
set.add("java");
//遍历集合的三种方式
//第一种:转Object数组
Object[] objs = set.toArray();
for(int i=0;i<objs.length;i++) {
System.out.println(objs[i]);
}
//第二种:迭代器
Iterator<String> it = set.iterator();
while(it.hasNext()) {
String s = it.next();
System.out.println(s);
}
//第三种:增强for
for(String s:set) {
System.out.println(s);
}
}
}
HashSet的核心功能就是自动去重,保证整个容器中的每个元素都是唯一的。
注:图片来自Java3y
特性:
HashSet如何检查重复(add方法的执行流程):
为什么要调用equals方法?
//HashSet保证元素唯一,自动去重
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set ss = new HashSet();
ss.add("cc");
ss.add("oo");
ss.add("bd");
ss.add("ba");
ss.add("ba");
ss.add("oo");
ss.add("cc");
Iterator it = ss.iterator();
while(it.hasNext()) {
System.out.print(it.next() + " ");
}
}
}
//输出:cc oo bd ba
需要解决的问题: HashSet保存自定义对象时不会进行去重!
如何解决: 重写hashCode和equals方法
HashSet保存自定义对象时不会进行去重
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Main {
public static void main(String[] args) {
//创建集合对象
HashSet<Student> hs = new HashSet<Student>();
// 创建元素对象
Student s = new Student("zhangsan", 18);
Student s2 = new Student("lisi", 19);
Student s3 = new Student("lisi", 19);
// 添加元素对象
hs.add(s);
hs.add(s2);
hs.add(s3);
// 遍历集合对象
for (Student student : hs) {
System.out.println(student);
}
}
}
class Student {
String name;
int age;
public Student(String name,int age) {
this.name=name;
this.age=age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
//输出:
Student [name=lisi, age=19]
Student [name=zhangsan, age=18]
Student [name=lisi, age=19]
实现去重:重写hashCode和equals方法
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Main {
public static void main(String[] args) {
//创建集合对象
HashSet<Student> hs = new HashSet<Student>();
// 创建元素对象
Student s = new Student("zhangsan", 18);
Student s2 = new Student("lisi", 19);
Student s3 = new Student("lisi", 19);
// 添加元素对象
hs.add(s);
hs.add(s2);
hs.add(s3);
// 遍历集合对象
for (Student student : hs) {
System.out.println(student);
}
}
}
class Student {
String name;
int age;
public Student(String name,int age) {
this.name=name;
this.age=age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
@Override
public int hashCode() {
return 1; //如果hashCode返回一样的内容,则就会调用equals方法
}
@Override
public boolean equals(Object obj) {
//System.out.println("-------------------");
Student s = (Student)obj;//向下转型,可以获取子类特有成员
//比较年龄是否相等,如果不等则返回false
if(this.age != s.age) {
return false;
}
//比较姓名是否相等,如果不等则返回false
if(!this.name.equals(s.name)) {
return false;
}
//默认返回true,说明两个学生是相等的
return true;
}
}
//输出:
Student [name=zhangsan, age=18]
Student [name=lisi, age=19]
优化
问题:
解决方案:
@Override
public int hashCode() {
return age + name.hashCode();
}
优化代码:
import java.util.HashSet;
public class Main {
public static void main(String[] args) {
//创建集合对象
HashSet<Person> hs = new HashSet<Person>();
//创建元素对象
Person p = new Person("zhangsan",20);
Person p2 = new Person("lisi",20);
Person p3 = new Person("lisi",20);
//添加元素对象
hs.add(p);
hs.add(p2);
hs.add(p3);
//遍历集合对象
for (Person person : hs) {
System.out.println(person);
}
}
}
class Person {
String name;
int age;
public Person(String name,int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
//自动生成hashCode方法和equals方法:"source","generator xX() and xX()"
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass()) //提高程序的健壮性、安全性
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
LinkedHashSet架构(图片来自Java3y):
特性:
基于链表的哈希表实现;
继承自HashSet;
维护着一个双重链表;
具有顺序,存储和取出的顺序相同
不允许有重复的元素
线程不安全,运行速度快
HashSet的核心功能就是自动排序,保证整个容器中的所有元素都是有序的!
//试一试
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
public class Main {
public static void main(String[] args) {
Set<String> ss = new TreeSet<String>(); //尖括号中是泛型,表明这个Set容器中只能存放String类型的数据
ss.add("por*hub");
ss.add("porahub");
ss.add("xvideo*");
ss.add("thepor*tuebe");
ss.add("javbus");
Iterator<String> it = ss.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
/* 输出已经排好序了的串:
javbus
por*hub
porahub
thepor*tuebe
xvideo*
*/
如果元素具备自然顺序(如数字、单个字符、字符串) 的特性,那么就按照元素自然顺序的特性进行排序存储。 其进行排序的底层实现是:红-黑树;
而元素没有具备自然顺序的时候(如自定义的类型、对象等):
1 .往TreeSet添加元素的时候,如果元素本身不具备自然顺序的特性,那么该元素所属的类必须要实现Comparable接口,把元素的比较规则定义在compareTo(T o)方法上。
2 .如果比较元素的时候,compareTo方法返回的是0,那么该元素就被视为重复元素,不允许添加.(注意:TreeSet与HashCode、equals方法是没有任何关系。)
3 .往TreeSet添加元素的时候, 如果元素本身没有具备自然顺序的特性,而元素所属的类也没有实现Comparable接口,那么必须要在创建TreeSet的时候传入一个比较器。
4 .往TreeSet添加元素的时候,如果元素本身不具备自然顺序的特性,而元素所属的类已经实现了Comparable接口,在创建TreeSet对象的时候也传入了比较器,那么是以比较器的比较规则优先使用。
5 .如何自定义比较器: 自定义一个类实现Comparator接口即可,把元素与元素之间的比较规则定义在compare方法内即可。
class 类名 implements Comparator{
...
}
例子:
import java.util.Comparator;
import java.util.TreeSet;
class Emp implements Comparable<Emp>{
int id;
String name;
int salary;
public Emp(int id, String name, int salary) {
super();
this.id = id;
this.name = name;
this.salary = salary;
}
@Override
public String toString() {
return "{ 编号:"+ this.id+" 姓名:"+ this.name+" 薪水:"+ this.salary+"}";
}
//@Override //元素与元素之间的比较规则。
// 负整数、零或正整数,根据此对象是小于、等于还是大于指定对象。
public int compareTo(Emp o) {
// System.out.println(this.name+"compare"+ e.name);
return this.salary- o.salary;
}
}
//自定义一个比较器
class MyComparator implements Comparator<Emp>{
@Override
public int compare(Emp o1, Emp o2) {
return o1.id-o2.id;
}
//根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。
/*@Override
public int compare(Object o1, Object o2) {
Emp e1 = (Emp) o1;
Emp e2 = (Emp) o2;
return e1.id - e2.id;
}*/
}
public class TestMain {
public static void main(String[] args) {
//创建一个比较器对象
MyComparator comparator = new MyComparator();
//创建TreeSet的时候传入比较器
TreeSet tree = new TreeSet(comparator);
tree.add(new Emp(110, "老陆", 100));
tree.add(new Emp(113, "老钟", 200));
tree.add(new Emp(220, "老汤", 300));
tree.add(new Emp(120, "老蔡", 500));
System.out.println("集合的元素:"+tree);
}
}