List,Set,Map
集合的继承实现关系
Iterator迭代器,下面所有的都可以用迭代器来遍历。
Collecton接口常用的子接口有:List接口、Set接口。
List接口常用的子类有:ArrayList类、LinkedList类。
Set接口常用的子类有:HashSet类、LinkedHashSet类。
List允许有重复的元素,有序。
Set不予许有重复元素,无序。
保持顺序:以输入的顺序输出。
Collection接口概述
既然Collection接口是集合中的顶层接口,那么它中定义的所有功能子类都可以使用,是共性的功能,是集合中所有实现类必须拥有的方法。
所有我们学习的时候首先从父类Collection开始,然后再学习子类中特有的内容。
Collection接口的基本方法
创建集合的格式
Collection<元素类型> 变量名 = new ArrayList<元素类型>();
只能存储<>中指定的元素类型,该方式为常用方式。
Collection 变量名 = new ArrayList();
集合的元素类型默认为Object类型,即任何类型的元素都可以存储。
示例
@Test
public void collection(){
Collection<String> collection = new ArrayList<>();
collection.add("张三");
collection.add("李四");
collection.add("王五");
collection.add("张三");
System.out.println(collection);
collection.remove("张三");
System.out.println(collection);
Object[] array = collection.toArray();
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
}
[张三, 李四, 王五, 张三]
[李四, 王五, 张三]
李四 王五 张三
从Object继承来的toArray()方法可以将其转换为数组然后用数组的方式进行遍历。
Iterator迭代器
java中提供了很多个集合,它们在存储元素时,采用的存储方式不同。我们要取出这些集合中的元素,可通过一种通用的获取方式来完成。
Collection集合元素的通用获取方式:在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
集合中把这种取元素的方式描述在Iterator接口中。
Collection继承Iterable,Iterable里写了一个iterator()方法。
我们所写的Iterator接口里有两个常用的方法。
hasNext()方法:用来判断集合中是否有下一个元素可以迭代。如果返回true,说明可以迭代。
next()方法:用来返回迭代的下一个元素,并把指针向后移动一位。
在Collection接口描述了一个抽象方法iterator方法,所有Collection子类都实现了这个方法,并且有自己的迭代形式。
@Test
public void iterator1() {
Collection<String> collection = new ArrayList<>();
collection.add("张三");
collection.add("李四");
collection.add("王五");
collection.add("张三");
Iterator<String> iterator = collection.iterator();//要先调用iterator()方法
while (iterator.hasNext()) {//判断有没有下一个,有下一个返回true
String data = iterator.next();//用来返回迭代的下一个元素,并把指针向后移动一位
System.out.println(data);
}
}
张三
李四
王五
张三
可重复
List接口特点(也是所有实现类的特点)
1、它是一个元素存取有序的集合。例如,存元素的顺序是11、22、33。那么集合中,元素的存储就是按照11、22、33的顺序完成的)。
2、它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
3、 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。
List接口的常用子类有:
ArrayList集合
LinkedList集合
ArrayList
1、增加元素方法
add(Object e):向集合末尾处,添加指定的元素
add(int index, Object e):向集合指定索引处,添加指定的元素,原有元素依次后移
2、删除元素删除
remove(Object e):将指定元素对象,从集合中删除,返回值为被删除的元素
remove(int index):将指定索引处的元素,从集合中删除,返回值为被删除的元素
3、替换元素方法
set(int index, Object e):将指定索引处的元素,替换成指定的元素,返回值为替换前的元素
4、查询元素方法
get(int index):获取指定索引处的元素,并返回该元素
@Test
public void list1(){
List<String> list = new ArrayList<String>();
list.add("abc1");
list.add("abc2");
list.add("abc3");
list.add("abc4");
list.add(2, "Java");//List接口定义的方法
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
System.out.println(str);
}
System.out.println("--------------");
//由于List集合拥有索引,因此List集合迭代方式除了使用迭代器之外,还可以使用索引进行迭代(类似数组)。
for (int i = 0; i < list.size(); i++) {
String str = list.get(i);
System.out.println(str);
}
System.out.println("--------------");
//迭代器的并发修改异常 java.util.ConcurrentModificationException
//就是在遍历的过程中,使用了集合方法修改了集合的长度,不允许的
//对集合使用迭代器进行获取,获取时候判断集合中是否存在 "abc3"对象
//如果有,添加一个元素 "ABC3"
Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
//对获取出的元素s,进行判断,是不是有"abc3"
if(s.equals("abc3")){
list.add("ABC3");
}
System.out.println(s);
}
}
@Test
public void list2() {
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
for (String item : list) {
if (item.equals("3")) {
System.out.println(item);
list.remove(item);
}
}
System.out.println(list.size());
}
ConcurrentModificationException迭代器的并发修改异常
就是在遍历的过程中,使用了集合方法修改了集合的长度,不允许的,无论是添加还是删除,也不仅仅局限于迭代。
那要是想添加的话怎么操作呢?
并发修改异常解决办法:在迭代时,不要使用集合的方法操作元素。
通过ListIterator迭代器操作元素是可以的,ListIterator的出现,解决了使用Iterator迭代过程中可能会发生的错误情况。
@Test
public void list2() {
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
// for (String item : list) {
// if (item.equals("3")) {
// System.out.println(item);
// list.remove(item);
// }
// }
System.out.println(list.size());
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
String data = (String) iterator.next();
if (data.equals("3")) {
// 不管在foreach还是Iterator遍历过程中都不允许使用list改变长度
// 如果是移除元素,可以使用Iterator里面的remove,但是对于添加
// Iterator就没有提供,可以参考ListIterator
iterator.remove();
System.out.println(data);
}
}
System.out.println(list.size());
}
5
3
4
LinkedList:
LinkedList集合数据存储的结构是链表结构。方便元素添加、删除的集合。实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。
LinkedList是List的子类,List中的方法LinkedList都是可以使用,这里就不做详细介绍,我们只需要了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。
@Test
public void list3() {
LinkedList<String> link = new LinkedList<String>();
//添加元素
link.addFirst("abc1");
link.addFirst("abc2");
link.addFirst("abc3");
//获取元素
System.out.println(link.getFirst());
System.out.println(link.getLast());
//删除元素
System.out.println(link.removeFirst());
System.out.println(link.removeLast());
while(!link.isEmpty()){ //判断集合是否为空
System.out.println(link.pop()); //弹出集合中的栈顶元素
}
}
abc3
abc1
abc3
abc1
abc2
Vector:
Vector集合数据存储的结构是数组结构,为JDK中最早提供的集合。Vector中提供了一个独特的取出方式,就是枚举Enumeration,它其实就是早期的迭代器。此接口Enumeration的功能与 Iterator 接口的功能是类似的。Vector集合已被ArrayList替代。枚举Enumeration已被迭代器Iterator替代。
Vector & ArrayList比较
相同点:Vector与ArrayList本质上都是一个Object[] 数组
不同点:
1)Vector是线程安全的集合类,ArrayList并不是线程安全的类。Vector类对集合的元素操作时都加了synchronized,保证线程安全。
2)Vector与ArrayList的扩容并不一样,Vector默认扩容是增长一倍的容量,Arraylist是增长50%的容量。ArrayList就有利于节约内存空间。
Vector无论查询还是增删都很慢,所以被ArrayList替代了。
Set接口
Set中元素无序不重复
HashSet
HashSet的底层是HashMap,HashMap底层由hashCode()与equals()方法实现。
可以使用for和迭代器进行遍历。
无序:HashSet集合不能保证的迭代顺序与元素存储顺序相同。
不重复:HashSet集合,采用哈希表结构存储数据,保证元素唯一性的方式依赖于两个方法:hashCode()与equals()方法。
主要的判断依据是地址,但是并不是简单的根据地址断定。当然想排除值相同的可以对这两个方法进行重写。
散列表(Hash table,也叫哈希表)
是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。
HashSet元素不能重复,如何实现?
当向哈希表中存放元素时,需要根据元素的特有数据结合相应的算法,这个算法其实就是Object类中的hashCode方法。由于任何对象都是Object类的子类,所以任何对象有拥有这个方法。即就是在给哈希表中存放对象时,会调用对象的hashCode方法,算出对象在表中的存放位置,这里需要注意,如果两个对象hashCode方法算出结果一样,这样现象称为哈希冲突,这时会调用对象的equals方法,比较这两个对象是不是同一个对象,如果equals方法返回的是true,那么就不会把第二个对象存放在哈希表中,如果返回的是false,就会把这个值存放在哈希表中。
总结:保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。
给HashSet中存储JavaAPI中提供的类型元素时,不需要重写元素的hashCode和equals方法,因为这两个方法,在JavaAPI的每个类中已经重写完毕,如String类、Integer类等。
例一
@Test
public void set1() {
// 创建HashSet对象
Set<String> set = new HashSet<String>();
Set<Student> set1 = new HashSet<Student>();
Student stu1 = new Student(1,"张三", 33, "20");
Student stu2 = new Student(1,"张三", 33, "20");
// 给集合中添加自定义对象
set.add("zhangsan");
set.add("lisi");
set.add("wangwu");
set.add("zhangsan");
set1.add(stu1);
set1.add(stu2);
for (String string : set) {
System.out.println(string);
}
for (Student s1 : set1) {
System.out.println(s1);
}
}
lisi
zhangsan
wangwu
Student{id=1, name=‘张三’, age=33, gender=‘20’}
Student{id=1, name=‘张三’, age=33, gender=‘20’}
stu1和stu2都能够加入HashSet里面,因为他们的地址不一样,但是这其实是同一个Student,
只是分别通过不同的方式new出来,如果stu1和stu2都能够加入的话,那么这个学生就在HashSet里面重复了。
例二
为什么对于第二组,堆中新new的,地址不一样的,在没有我们重写之前依然放不进去?
系统中String自己重写了hashCode()和equals()方法。
@Test
public void set2(){
Set<String> set = new HashSet<String>();
set.add("abc");
set.add("abc");
set.add("def");
set.add(new String("def"));
for (String s : set) {
System.out.println(s);
}
}
abc
例:对Student类里的hashCode()与equals()方法
自定义类型,如果要使用HashSet,必须手动实现hashCode和equals。
我选择的判定方法是id和name,当这两项以当时就断定是相同的对象。
这个是自动加载的,不需要自己写。
import java.util.Objects;
public class Student {
private int id;
private String name;
private int age;
private String gender;
。。。。。。
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return id == student.id && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
进行方法的重写之后,输出:
Student{id=1, name=‘张三’, age=33, gender=‘20’}
已经只能添加上一个了。
Map接口
1、Collection中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储。
2、Map中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值。
3、Collection中的集合称为单列集合,Map中的集合称为双列集合。
4、需要注意的是,Map中的集合不能包含重复的键,值可以重复;每个键只能对应一个值。
5、Map中常用的集合为HashMap集合、LinkedHashMap集合。
6、HashSet的底层是HashMap,HashMap底层由hashCode()与equals()方法实现。
Map的子类:HashMap、LinkedHashMap
HashMap<K,V>
存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
LinkedHashMap<K,V>
HashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。