Java学习——Collection集合
今天我们来学习Collection集合,学习这个集合之前我们先要知道为什么要引入集合。我们都直到Java语言是一种面向对象的语言,所以为了存取对象方便,就引入了集合的概念。
那么集合和数组到底又有什么区别呢?
- 长度区别:数组的长度一旦定义就不可变的;但是集合的长度是可变的,是可以自动扩容的
- 存储的数据类型区别:我们知道数组是可以存储基本数据类型也可以存储引用数据类型;但是集合只能存储引用数据类型(当然我们Java语言有自动拆箱和自动装箱,这个存储的数据类型是无法限制集合的。比如我们想要存储int类型的数据,Java语言就会为我们自动装箱成Integer类型的数据存入,取的时候道理相同)
- 存的内容种类区别:数组只能存一种数据类型类型的数据;而集合可以存储多种数据类型的数据
了解了集合的基本概念以后,我们也必须要掌握常用的Collection集合的继承体系,如下:
在Collection中我们最常用的就是List集合和Set集合这两种集合,那么就先要只知道顶层父类的方法:
- boolean add(Object obj):添加一个元素
- boolean addAll(Collection c):添加一个集合的元素 (给一个集合添加进另一个集合中的所有元素)
- void clear():移除所有元素
- boolean remove(Object o):移除一个元素
- boolean removeAll(Collection c):移除一个集合的元素(移除一个以上返回的就是true) 删除的元素两个集合的交集元素,如果没有交集元素 则删除失败 返回false
- boolean contains(Object o):判断集合中是否包含指定的元素
- boolean containsAll(Collection c):判断集合中是否包含指定的集合元素(这个集合 包含 另一个集合中所有的元素才算包含 才返回true)
- boolean isEmpty():判断集合是否为空
- Iterator iterator():获取集合的迭代器对象
- int size():获取元素的个数
- boolean retainAll(Collection c):获取两个集合的交集元素(交集:两个集合都有的元素)
- Object[] toArray():把集合转换为数组
这些方法简单易懂,就不做详细的解释了
当然子类的方法会更加丰富,现在我们就先从List这个分支学起:
一、 List
List集合的特点:元素有序,并且每一个元素都存在一个索引,元素可以重复
1.ArrayList
ArrayList集合的底层数据结构是数组,查询快,增删慢;线程不安全,效率高
2.Vector
Vector集合底层数据结构是数组,查询快,增删慢。线程安全,效率低
Vector集合线程安全是因为它底层源码都用synchronized关键字所修饰
3.LinkedList
LinkedList集合底层数据结构是链表,查询慢,增删快,线程不安全,效率高
下面我们来看看使用迭代器对象遍历集合:
public class Test {
public static void main(String[] args) {
//创建ArrayList集合对象,泛型定为Integer类型
ArrayList<Integer> list = new ArrayList<>();
//向集合中添加元素
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
list.add(8);
list.add(9);
list.add(10);
//获取Collection集合的父类迭代器对象
Iterator<Integer> iterator = list.iterator();
//iterator.hasNext()判断下一个是否有元素
while (iterator.hasNext()){
//拿出下个元素,并打印
Integer next = iterator.next();
System.out.println(next);
}
}
}
/**
* 输出结果:1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
* 10
*/
当然List集合也有它特有的迭代器对象,listIterator,也都是一样的用法。
下来我们来看用迭代器对象遍历集合的时候,如果往集合中增加元素,会产生一个异常:
public class Test {
public static void main(String[] args) {
//创建ArrayList集合对象,泛型为Integer类型
ArrayList<Integer> list = new ArrayList<>();
//向集合中添加元素
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
list.add(8);
list.add(9);
list.add(10);
//获取Collection集合的父类迭代器对象
Iterator<Integer> iterator = list.iterator();
//iterator.hasNext()判断下一个是否有元素
while (iterator.hasNext()){
//拿出元素
Integer next = iterator.next();
if (next==5){
list.add(100);
}
System.out.println(next);
}
}
}
输出:
1
2
3
4
5
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at cn.xiyou.Test.main(Test.java:26)
ConcurrentModificationException并发修改异常,产生这个异常的原因是因为使用迭代器进行遍历的时候,迭代器会计算好集合的长度,但是你在迭代的过程中添加元素,集合长度改变,迭代器就不知道长度为多少了,就会出现并发修改异常。
那么解决的办法有两个:
- 使用迭代器特有的add方法去添加元素
- 使用for循环遍历集合
这里我们介绍一种新式的for循环:
public class Test {
public static void main(String[] args) {
//创建ArrayList集合对象,泛型为Integer类型
ArrayList<Integer> list = new ArrayList<>();
//向集合中添加元素
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.add(7);
list.add(8);
list.add(9);
list.add(10);
//新式for循环遍历集合
for (Integer i : list) {
System.out.println(i);
}
}
}
二、Set
Set集合的特点:元素无序(存取的顺序不一致)且唯一
1.HashSet
HashSet的底层数据结构是哈希表(JDK1.7之前:链表+数组+红黑树;JDK1.7之后:链表+数组+红黑树),线程不安全,可以存null值。
那么HashSet集合是如何保证元素唯一性的呢?
为了保证HashSet集合的元素唯一性,HashSet集合必须要重写hashCode()和equals()方法。
1.当向 HashSet 集合中存入一个元素时,HashSet 会调用该对象的 hashCode() 方法来得到该对象的 hashCode 值,然后根据算出来的hashCode值去确定该对象在HashSet中的存储位置。
2.当两个对象的hashCode值相等时,我们会重写equals()方法,让两个对象去比较其内容,如果不一致则存储,如果一致则不存储
所以,要保证HashSet集合元素唯一就必须重写hashCode()和equals()方法。
2.LinkedHashSet
LinkedHashSet底层数据结构是哈希表+链表。链表保证元素有序,哈希表保证元素唯一。
3.TreeSet
TreeSet底层数据结构是二叉树,保证了元素有序
那么在TreeSet中有两种排序方式:
1). 自然排序
比较的对象要实现Comparable接口并重写该接口中compareTo方法,如下:
//实现Comparable接口,并将泛型定为Student
public class Student implements Comparable<Student>{
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int compareTo(Student student) {
//以姓名的字典顺序排序,如果姓名一样,则以年龄大小排序
int num = student.name.compareTo(this.name);
int num2 = num==0?student.age-this.age:num;
return num2;
}
}
然后新建一个测试类,使用TreeSet集合添加元素,遍历显示结果
public class Test {
public static void main(String[] args) {
//创建TreeSet集合并存储Student对象
TreeSet<Student> set = new TreeSet<>();
set.add(new Student("张三",23));
set.add(new Student("李四",24));
set.add(new Student("王五",25));
set.add(new Student("赵六",26));
set.add(new Student("田七",27));
set.add(new Student("牛二",28));
//遍历集合,查看自然排序结果
for (Student student : set) {
System.out.println(student.getName()+"=="+student.getAge());
}
}
}
结果:
赵六==26
田七==27
王五==25
牛二==28
李四==24
张三==23
2). 比较器排序
比较器排序就比较简单了,比较的类不用实现任何接口,直接向TreeSet集合中传入一个比较器Comparator即可:
public class Test {
public static void main(String[] args) {
//创建TreeSet集合并传入一个Comparator比较器,然后重写compare方法
TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student s1, Student s2) {
int num = s1.getName().compareTo(s2.getName());
int num2 = num==0?s1.getAge()-s2.getAge():num;
return num2;
}
});
//向集合中存入对象
set.add(new Student("张三",23));
set.add(new Student("李四",24));
set.add(new Student("王五",25));
set.add(new Student("赵六",26));
set.add(new Student("田七",27));
set.add(new Student("牛二",28));
//遍历集合看结果
for (Student student : set) {
System.out.println(student.getName()+"=="+student.getAge());
}
}
}
结果:
张三==23
李四==24
牛二==28
王五==25
田七==27
赵六==26
我们可以看到,比较器排序与自然排序的结果是一样的,在我们平时,建议使用比较器排序
集合的学习是为了以后在编码中选取更合适的集合去存储数据,后面我们还会学到map集合,敬请期待哦!