目录
并发修改异常 ConcurrentModificationException:
集合框架:
集合按照其存储结构可以分为两大类,分别是单列集合(java.util.Collection)和双列集合(java.util.Map)。
Collection,所有单列集合的根接口,用于存储一系列符合某种规定的元素,他有两个重要的子接口:java.util.List 和 java.util.Set 。他们还有一些重要的实现类,如下图所示:
1、Collection接口定义了所有单列集合中的共性方法,所有单列集合均可使用。(没有遍历方法)
2、Collection接口有两个子接口,分别是List接口和Set接口。
3、List接口下的实现类都是有序的集合,且允许存储重复的元素。并且还有索引,可以使用get方法来获得某下标的某元素,从而使用for循环遍历集合。
4、Set接口下的实现类中,TreeSet集合和HashSet集合都是无序的集合,而LinkedHashSet集合是有序的集合。Set接口下的所有实现类都不允许存储重复的元素。并且没有索引,无法使用for循环遍历集合。
Collection 接口的常用功能:
Collection是所有单列集合的父接口,再Collection接口中定义了单列集合(List和Set)通用的一些方法,这些方法可以操纵所有的单列集合。方法如下:
public boolean add(E e); 把给点的元素添加到当前集合中。
public void clear(E e); 清空集合中所有元素。但集合还存在,只不过为空。
public boolean remove(E e); 把给定元素从当前的集合中删除。(List集合可以传一个数字表示把当前下标的元素删除,也可以传一个元素表示把该元素从集合中删除,而Set只能传一个元素,因为Set集合是无序的。)Collection的实现类调用的是集合内元素所在类的equals()方法比较两两元素是否相等,所以元素必须重写equals()方法才能正确地删除。
public boolean contains(E e); 判断当前集合中是否包含给定的元素。Collection的实现类调用的是集合内元素所在类的equals()方法,如果是字符串,因为String类内已经重写了equals()方法所有比较的是对象的值。但如果不是字符串且没有重写equals()方法则会默认调用Object类的equals()方法,比较的是内存地址。所以集合内自定义类的元素需要重写该元素的equals()方法。
public boolean isEmpty(E e); 判断当前集合是否为空。
public int size(); 返回集合中元素的个数。
public Object[] toArray(); 把集合中的元素存储到数组中。
public Iterator<E> iterator(); 构建迭代器。
public Object[] toArray(); 把集合中的元素存储到数组中。
public <T> T[] toArray(T[] arrayToFill);返回调用该方法的集合中的对象的数组。如果arrayToFill足够大,就将集合中的元素填入这个数组中,剩下的空间填补null。否则,创建一个新数组,其成员类型与arrayToFill的成员类型一致,其长度等于集合的大小,并填充了集合的所有元素。
public boolean addAll(Collection<? extends E> c);
list1.addAll( list2 ); 将 list2 集合中所有的元素添加到 list1 集合中,如果这个调用改变了集合,返回true。
public static <T> boolean addAll(Collection<? super T> c, T... elements);
将所有指定的元素添加到指定的集合,elements为泛型本身/子类的集合/数组。
如:Collections.addAll(list, strs);
public boolean removeAll(Collection<?> c);
list1.removeAll(list2); 把 list1 中存在的 list2 的元素全部删除,如果做个调用改变了集合,返回true。Collection的实现类调用的是集合内元素所在类的equals()方法比较两两元素是否相等,所以元素必须重写equals()方法才能正确地删除。
public boolean containsAll(Collection<?> c);
boolean flag = list1.containAlls(list2); 如果 list1 包含 list2 的所有元素,返回true。调用的是contains()方法来比较两两元素之间是否相等,元素需要重写equals()方法否则比较的是地址。
public boolean retainAll(Collection<?> c);
boolean flag = list1.retainAll(list2); 把 list1 中 list2 没有的元素全部删除掉,如果中国调用改变了集合,返回true。
addAll(Collection<? extends E> c)通常可以配合Array类的asList方法使用:
public static <T> List<T> asList(T... a); 把数组转为List集合。
addAll( asList( xxx ) ); 把数组中所有元素都添加到集合中。
迭代器:
Iterator接口:
在程序开发中,经常需要遍历集合中的所有元素,针对这一需求,JDK专门提供了一个接口(java.util.Iterator)。Iterator接口也是Java集合中的一员,但它与Collection、Map接口有所不同,Collection接口与Map接口主要用于存储元素,而Iterator接口主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器。
Collection接口下有一个iterator()方法,调用该方法将返回一个迭代器Iterator接口的一个实现类对象,获得一个集合的迭代器。
Collection接口所定义的iterator()方法:
Iterator<E> iterator();
Iterator<E>接口也是有泛型的,迭代器的泛型跟着集合走,集合是什么泛型,迭代器就是什么泛型。
ArrayList类对iterator()方法的实现:
public Iterator<E> iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
......
}
创建迭代器示例:
ArrayList<String> str = new ArrayList<>();
Iterator<String> a = str.iterator(); // 方法返回的是一个Iterator类型
// Iterator<E>接口也是有泛型的,迭代器的泛型跟着调用该方法的对象所属的集合类型走,集合是什么泛型,迭代器就是什么泛型。
迭代器的常用方法:
hasNext(); 判断集合中有无元素可迭代,有则返回true,否则返回false。
next(); 返回迭代的下一个元素。
remove(); 删除最后一次所返回的元素。
forEachRemaining(); 对集合中剩余的元素进行操作,直到元素完毕或者抛出异常。
迭代方式:
在取元素之前要先判断集合中有无元素,如果有,调用方法取出,继续再判断,如果还有再取出来,以此往复,直到把集合中所有元素全部取出。这种取出方式专业术语称为迭代。(一般配合while循环使用)
如果没有元素但依旧调用next()方法试图取出,则会抛出一个NoSuchElementException异常(没有元素异常)。
迭代示例:
import java.util.ArrayList;
import java.util.Iterator;
class Main {
public static void main(String[] args) {
ArrayList<String> str = new ArrayList<>();
str.add("AAA");
str.add("BBB");
str.add("CCC");
str.add("DDD");
Iterator a = str.iterator(); // 多态
while (a.hasNext()){
System.out.println(a.next());
}
System.out.println("---------");
Iterator b = str.iterator(); // 多态
if(b.hasNext()){
System.out.println(b.next());
}
if(b.hasNext()){
System.out.println(b.next());
}
b.remove();
System.out.println("---------");
Iterator c = str.iterator(); // 多态
while (c.hasNext()){
System.out.println(c.next());
}
}
}
输出:
并发修改异常 ConcurrentModificationException:
我们在调用集合的 iterator方法 后,如果在调用 next()方法 之前对集合内的元素进行修改(增删改),Java会抛出一个ConcurrentModificationException异常,也称为并发修改异常。
LinkedList<String> list = new LinkedList<>();
list.addFirst("1");
list.addFirst("2");
list.addFirst("3");
list.addFirst("4");
list.addFirst("5");
Iterator<String> iterator = list.iterator();
list.addFirst("6");
iterator.next(); // ConcurrentModificationException
ArrayList<Character> list = new ArrayList<>();
// 添加元素
for (char c = 'A'; c <= 'G'; c++) {
list.add(c);
}
// 获得集合的迭代器
Iterator<Character> it = list.iterator();
while (it.hasNext()) {
char c = it.next();
if (c == 'D') {
list.remove(new Character(c)); // ConcurrentModificationException
}
System.out.println(c);
}
ListIterator接口:
ListIterator接口是Iterator接口的子接口,因此,这个接口继承了Iterator接口的 next() 和 hasNext() 等方法,并且ListIterator接口还定义了自己一些特有的方法。通过ListIterator接口,我们可以在用迭代器遍历集合的过程中对集合元素进行操作而不报 ConcurrentModificationException 异常。
常用方法:
boolean hasNext ( ); 当前光标位置的下一位如果有元素,则返回true;没有元素,则返回false。
E next ( ); 返回当前光标位置的下一个元素。
boolean hasPrevious ( ); 当前光标位置的上一位如果有元素,则返回true;没有元素,则返回false。(如果想倒叙遍历集合,得手动把迭代器变量的光标移到最后一位)。
E previous ( ); 返回当前光标位置的上一个元素。
void add ( E e ); 将指定元素插入到当前光标位置之前。(多次调用add方法,将按所调用的次序依次把元素添加进链表中。他们都将被添加到迭代器当前元素之前)
void remove ( ); 从列表中删除最后一次执行next()或previous()方法返回的元素。
1.调用remove()方法之前,一定要有next()或previous()方法执行,否则报错:java.lang.IllegalStateException。
2.执行next() 或 previous()之后与执行remove()之前,不能执行add(E)方法,否则报错:java.lang.IllegalStateException。
3.不能连续两次调用remove方法。
void set ( E e ); 用实参元素取代 next() 或 previous() 返回的最后一个元素。
并且在调用set(E)方法前,不能调用remove() 和 add(E) 方法,否则会报错:java.lang.IllegalStateException。
int nextIndex(); 返回光标下一位元素的下标。
int previousIndex(); 返回光标上一位元素的下标。
注意,在执行 next() 或 previous ( ) 方法之后,光标是夹在两个元素之间的,所有如果nextIndex() 返回的是3,那么 previousIndex() 返回的就是2。
在List接口的集合中,我们可以使用 iterator() 方法获得该集合的Iterator接口的迭代器,同样的,我们也可以调用 ListIterator() 方法获得该集合的ListIterator接口的迭代器。
forEachRemaining()方法:
在迭代器中有一个 forEachRemaining(Consumer<? super E> action) 方法,用于对集合中剩余的元素进行操作,直到元素完毕或者抛出异常。
我们在调用这个 forEachRemaining(Consumer<? super E> action) 方法的时候,需要往方法中传递一个对 consumer 接口的实现,可以是匿名内部类,也可以是lambda表达式。
public static void main(String[] args) {
ArrayList<Integer> i = new ArrayList<>();
i.add(100);
i.add(200);
i.iterator().forEachRemaining(num -> doSometing(num));
i.iterator().forEachRemaining(num -> System.println.out(num));
}
public static void doSometing(int a){
}
注意这个剩余的元素:
import java.util.*;
public class Test
{
public static void main(String[] args)
{
//创建一个元素类型为Integer的集合
Collection<Integer> collection = new HashSet<>();
for (int i=0;i<10 ;i++ )
{
//向集合中添加元素
collection.add(i);
}
//获取该集合的迭代器
Iterator<Integer> iterator= collection.iterator();
//调用forEachRemaining()方法遍历集合元素
int i=0;
while(iterator.hasNext())
{
System.out.println(iterator.next());
i++;
if (i==5)
{
break;
}
}
System.out.println("--------------");
//调用forEachRemaining()方法遍历集合元素
iterator.forEachRemaining(ele -> System.out.println(ele));
}
}
/*
这时输出:
0
1
2
3
4
--------------
5
6
7
8
9
*/
for-each(增强for循环):
增强for循环的定义:
for( 所存放的类型的变量定义 : 数组/集合名 ){
对数组/集合的每一个元素的执行代码
}
增强for循环底层其实也是调用了迭代器来对数组/集合进行遍历。
Java.lang包下有一个Iterable<T>接口,实现了该接口的类都可以使用增强for循环来遍历。
Collection<E>接口继承了Iterable<T>接口,所有所有单列集合都可以使用增强for循环来遍历。
基本类型的数组和String类型的数组也可以使用增强for循环。
代码示例:
import java.util.ArrayList;
class Main {
public static void main(String[] args) {
ArrayList<String> str = new ArrayList<>();
str.add("AAA");
str.add("BBB");
str.add("CCC");
str.add("DDD");
for(String s:str){
System.out.println(s);
}
}
}
注意:增强for循环中取得的是数组/集合中保存元素的副本,在增强for循环中,对基本数据类型修改是无效的,但可以对引用数据类型所指向的值进行修改。(因为引用保存的是地址,副本保存的也是地址,都可以对该地址所存储的内容进行修改)
public class Main {
public static void main(String[] args) {
int[] nums = { 1, 2, 3, 4, 5 };
Person[] persons = { new Person("张三", 12), new Person("李四", 33), new Person("王五", 90) };
for(int i : nums) {
i=100;
}
for (int i : nums) {
System.out.print(i + "\t");
}
// 输出:1 2 3 4 5
// 对nums数组无修改
for (Person person : persons) {
if (person.name.equals("张三")) {
person.age = 100;
}
}
for (Person person : persons) {
System.out.println(person);
}
// 输出:Person [name=张三,age=100] Person [name=李四,age=33] Person [name=王五,age=90]
// 对persons数组有修改
}
}
但是对于集合来说:
假如想把number集合用增强for循环赋值1到13:
ArrayList<Integer> number = new ArrayList<>();
Integer i = 1;
for (Integer num : number){
if(i<=13){
num=i++; // 被赋值的永远是元集合元素的副本,并不能修改到元素。
}else{
break;
}
}
Integer i = 1;
for (Integer num : number){
if(i<=13){
number.add(i++); // 这样也是行不通的
}else{
break;
}
}
Integer[] in = {1,2,3,4,5,6,7,8,9};
for (Integer integer : in) { // 正确的方法应该是遍历须添加进集合的数组,在调用add方法
number.add(integer);
}
Integer[] in = {1,2,3,4,5,6,7,8,9};
number.addAll(Arrays.asList(in)); // 或者直接使用Collection的addAll方法
集合中的泛型:
单列集合中都有一个泛型(尖括号),用于传递所存储的参数的类型。尖括号中的泛型会让该类的方法有了一个明确的操作类型。如下图所示:
注意:元素在被存储进集合中时,会被自动提升为Object类型。如果此时不给迭代器一个泛型,则迭代器只知道他是一个Object类型,在取出时必须强制类型转换。(所以迭代器的泛型跟着集合走,集合是什么泛型,迭代器就是什么泛型。)
ArrayList<String> str = new ArrayList<>();
str.add("AAA");
str.add("BBB");
str.add("CCC");
str.add("DDD");
Iterator a = str.iterator();
String s;
if(a.hasNext()){
s = (String) a.next(); // 没给迭代器泛型,必须使用强制类型转换,否则不用。
}
创建集合对象时使用泛型:
好处:
- 安全,方便管理。
- 集合内元素类型一致,存储的是什么类型,在取出来时就是什么类型,不需要强制类型转换。
- 把运行期异常(代码运行之后会抛出的异常)提升到了编译期(写代码的时候会报错)。(如对集合所有元素调用 String 类型的 length() 方法,如果使用了泛型,在存储其他类型时就会报错,不至于等到运行时才抛出异常)
弊端:
- 泛型是什么类型,只能存储什么类型的数据
创建集合对象时不使用泛型:
好处:
- 集合不使用泛型,默认的类型就是object类型,可以存储任意类型的数据
弊端:
- 不安全,会引发异常
- 取出须强制类型转换,麻烦。
List接口:
List接口的实现类的特点:
- 有序的集合,存储的和取出的元素顺序是一样的。
- 有索引,因此有针对索引的方法。
- 允许存储重复的元素。
List接口中带索引的方法:
public void add(int index,E element); 将指定的元素,添加到该集合中的指定下标上。index是添加完后元素将所处的下标 。
public E get(int index); 返回集合中指定下标的元素。
public E remove(int index); 移除列表中指定下标的元素,返回值为被移除的元素。
public E set(int index,E element); 用指定元素替换集合中指定下标的元素,返回值为更新前的元素。
注意:操作索引的时候,一定要防止索引越界异常。
ArrayList实现类:
ArrayList是List接口的数组实现,查询快,增删满(须创建新数组并copy)。该实现不是同步的,即多线程的。
LinkedList实现类:
LinkedList是List接口的链表实现,查询慢,增删快。该实现不是同步的,即多线程的。
LinkedList特有的方法:
public void addFirst(E e); 将指定元素插入此列表的开头。
public void addLast(E e); 将指定元素添加到此列表的结尾。
public E getFirst(); 返回此列表的第一个元素。
public E getLast(); 返回此列表的最后一个元素。
public E removeFirst(); 移除并返回此列表的第一个元素。
public E removeLast(); 移除并返回此列表的最后一个元素。
public E pop(); 从此列表所表示的堆栈处弹出一个元素。( 等效于removeFirst()方法 )
public void push(E e); 将元素推入此列表所表示的堆栈。( 等效于addFirst()方法 )
public boolean isEmpty(); 如果列表不包含元素,则返回true。
Vector实现类:
Vector是List的数组实现,但他并不是同步的,即单线程的,现多被ArrayList所取代。
Set接口:
Set接口的实现类的特点:
- 无索引,不能使用普通的for循环遍历。但可以使用增强for循环或迭代器来遍历。(增强for循环底层就是一个迭代器)
- 不允许存储重复的元素。
其实现类中有无序的集合,也有有序的集合。
HashSet实现类:
HashSet实现类Set接口,它不保证每次迭代的时候每个元素的顺序都不变,是一个无序的集合,存储和取出顺序不一定相同。底层是一个哈希表结构,查询速度非常快。
哈希值:
哈希值是一个十进制的整数,由系统随机给出(就是对象的地址值,是一个逻辑地址,,是模拟出来的地址,不是数据实际存储的物理地址。
在0bject类有一个方法,可以获取对象的哈希值:
public native int hashCode(); // 无方法体
native:代表该方法调用的是本地操作系统的方法
字符串相同,则哈希值相同(即便是通过new来创建字符串,也是指向同一个字符串,只不过堆中的对象不同而已)。基本数据类型亦是如此。
注意:“重地”和“通话”两个字符串的哈希值是一样的,这是一个巧合。
一般如果重写了equals方法,建议都重写hashCode方法,避免两个内容完全一致的本应判断为相等的对象而因采用的是HashCode方法来比较导致判断结果为不相等的情况。
重写实例:
class Demo{
String name;
String home;
int age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Demo o2 = (Demo) o; // 不然访问不了变量name和home
return this.name.equals(o2.name) && this.home.equals(o2.home);
}
@Override
public int hashCode() {
return this.name.hashCode()+this.home.hashCode();
} // this.age.hashCode 不能访问,因为是基本数据类型。
}
另外,可以采用Objects类的equals方法和hashCode方法,它们是NULL安全的。而且这样一来,基本数据类型的变量也可以参与到hashCode的计算中来了。
class Demo{
String name;
String home;
int age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Demo o2 = (Demo) o; // 不然访问不了变量name和home
return Objects.equals(this.name,o2.name) && Objects.equals(this.home,o2.home);
} // Objects的方法的NULL安全的
@Override
public int hashCode() {
return Objects.hashCode(this.name)+Objects.hashCode(this.home)+Objects.hashCode(this.age);
}
}
如果要对多个参数计算哈希值并拼凑起来,可以调用Objects类中的hash方法,更便捷。实际参数列表中包括但不限于基本数据类型、引用数据类型、哈希值。
class Demo{
String name;
String home;
int age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Demo o2 = (Demo) o; // 不然访问不了变量name和home
return Objects.equals(this.name,o2.name) && Objects.equals(this.home,o2.home);
} // Objects的方法的NULL安全的
@Override
public int hashCode() {
return Objects.hash(name,home,age);
}
}
虽然重写了hashCode方法,但如果直接判断 对象1==对象2 ,结果也就是false,因为这样比较的是对象本身的内存地址,而不是其保存的内容的内存地址。
哈希表结构:
在JDK1.8之前,哈希表是数组与链表的结合,在JDK1.8之后,哈希表是数组与链表与红黑树的结合,进一步提高了查询效率。
哈希表有着查询速度快的特点。
HashSet集合存储元素不重复的原理:
当用HashSet集合存储自定义类型的元素时,需要重写对象所在类中的equals方法和hashCode方法,建立自己的比较方式,以保证HashSet集合中元素的唯一性。(String、Integer等类已重写)
LinkHashSet集合:
LinkHashSet集合继承了HashSet集合,也具有不可重复、无索引的特点,但底层是一个哈希表(数组+链表/红黑树)+链表的结构,多了一条链表(记录元素的存储顺序),保证了元素的有序性。
可变参数:
在JDK1.5之后,如果我们定义一个方法数据类型已决定,但变量个数不确定时,我们可以对其简化成如下格式:
修饰符列表 返回值类型 方法名(参数类型... 形参名) { }
其实这个书写完全等价与
修饰符列表 返回值类型 方法名(参数类型[] 形参名) { }
只是后面这种定义,在调用时必须传递数组,而前者可以直接传递数据即可。
同样是代表数组,但是在调用这个带有可变参数的方法时,不用创建数组(这就是简单之处),直接将数组中的元素作为实际参数进行传递。其实是编译成class文件,将这些元素先封装到一个数组中,再进行传递。这些动作都在编译class文件时,自动完成了。
示例:
public int sub(int...arr){
int sum=0;
for (int i = 0;i<arr.length;i++){
sum+=arr[i];
}
return sum;
}
注意事项:
1、一个形参列表最多只能有一个可变参数。
2、可变参数可搭配其他普通参数使用,但可变参数必须卸载形参最后边。
3、调用方法时,可变参数可为空。
4、不同类型的可变参数间可发生重载。
5、如果有类似 add(int...i){} 和 add(double...d){} 两个方法同时存在,将会报错,方法调用不明确。
小技巧:
如果想接收任意类型任意数量的参数,可设置参数列表为 Object...o ,因为Object类是所有类的父类。
操作集合的工具类(Collections):
java.utils.Collections是集合的工具类,用于对集合进行操作。
部分常用方法如下:
public static <T> boolean addAll(Collection<T> c,T... elements); 往集合中添加一些元素。
public static void shuffle(List<?> list); 打乱集合顺序。
public static <T> void sort(List<T> list); 将集合中元素按照默认规则排序。
public static <T> void sort(List<T> list,Comparator<? super T> c): 将集合中元素按照指定规则排序。(对Comparable接口的实现)(Comparable为函数式接口,有一个compareTo方法)(有一个Comparator类已经实现了Comparable接口,也可重写该方法)
public static void reverse(List<?> list); 反转集合中元素的顺序。
public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key);
在单列表list中查询key元素的下标。(Comparable<? super T>为一个完整类名)
public static <T> void copy(List<? super T> dest, List<? extends T> src);
将集合src中的元素全部复制到dest中,并且覆盖相应索引的元素。
public static <T> void fill(List<? super T> list, T obj); 用对象obj替换集合list中的所有元素
双列集合(Map):
java.util.Map<k,v>集合 Map集合的特点:
1、Map集合是一个双列集合,一个元素包含两个值(一个key,称为键。一个value,称为值)。
2、Map集合中的元素,key和value的数据类型可以相同,也可以不同。
3、Map集合中的元素,key是不允许重复的,value是可以重复的。
4、Map集合中的元素,key和value是一 一对应的。存在两个不同类型的key对应同一个类型的value的情况。
Map常用实现类:
HashMap<k,v>集合 实现了 Map<k,v>接口:
HashMap集合的特点:
1、HashMap集合底层是哈希表:查询的速度特别的快
JDK1.8之前:数组+单向链表
JDK1.8之后:数组+单向链表/红黑树(但链表的长度超过8时):提高查询的速度。
2、hashMap集合是一个无序的集合,存储元素和取出元素的顺序有可能不一致
LinkedHashMap<k,v>集合 继承了 HashMap<k,v>集合:
LinkedHashMap的特点:
1、LinkedHashMap集合底层是哈希表+链表(保证迭代的顺序)
2、LinkedHashMap集合是一个有序的集合,存储元素和取出元素的顺序是一致的
Map接口的常用方法:
Map接口中定义了很多方法,常用的如下:
public V put(K key,V value); 把指定的键与指定的值添加到Map集合中。当新键与集合中所有键都不重复时,返回NULL。当新键与集合中某个键重复时,会用新值替换掉旧值,并返回旧值。
public V remove(0bject key); 把指定的键和所对应的值从Map集合中删除。如果键存在,返回被删除元素的值(value)。如果键不存在,返回NULL。
public V get(object key); 根据指定的键,在Map集合中获取对应的值。key存在,返回对应的值。不存在,返回NULL。
public boolean containsKey(Object key); 判断集合中是否包含指定的键。
public Set<k> keySet(); 获取Map集合中所有的键,存储到Set集合中。 可遍历集合。
public Set<Map.Entry<k,v>> entrySet(); 获取到Map集合中所有的键值对对象的集合(Set集合)
遍历Map实现类:
Map集合的第一种遍历方式:通过键找值的方式。
实现步骤:
1、使用Map集合中的方法keySet()把Map集合所有的key取出来,存储到一个Set集合中。
2、使用迭代器或增强for循环遍历set集合,获取Map集合中的每一个key。
3、通过Map集合中的方法get(key),通过key找到value。
Map<String,Integer> amap = new HashMap<>();
for (String s : amap.keySet()) {
Integer value = amap.get(s);
System.out.println("key:"+s+" value:"+value);
}
Map集合的第二种遍历方式:使用Entry对象遍历。
在Map接口中有一个内部接口Entry,当Map集合一创建,那么就会在Map集合中创建一个Entry对象,用于记录键与值之间的映射关系。
Map集合中有一个entrySet()方法,但调用该方法时,会把Map集合内部的多个Entry对象取出来存储到一个Set集合中。(一个Entry对象包含了一个键和与其对应的值)
然后我们可以遍历Set集合,对每一个被取出来的Entry对象调用其getKey()和getValue()方法,分别取出它所存的键与值。
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
class Demo{
public static void main(String[] args) {
HashMap<String,String> m = new HashMap<>();
m.put("1","one");
m.put("2","two");
m.put("3","three");
Set<Map.Entry<String, String>> set = m.entrySet();
// Set集合存储的类型是Map.Entry类型(内部类)
// 使用迭代器遍历Set集合 :
Iterator<Map.Entry<String, String>> iterator = set.iterator();
while (iterator.hasNext()){
Map.Entry<String, String> nx = iterator.next();
String key = nx.getKey();
String value = nx.getValue();
System.out.println(key+"="+value);
}
System.out.println("------------");
// 使用增强for循环遍历Set集合 :
for (Map.Entry<String, String> entry : set) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"="+value);
}
}
}
Map集合存储自定义类型的Key值:
Map集合中的Key值是唯一的,所有为了Key值的唯一性,必须重新Key值所在类的hashCode()方法和equals()方法。
为了输出自定义类对象时更明确,还应该修改所输出类型的toString()方法。
示例如下:
import java.util.*;
class Demo{
public static void main(String[] args) {
HashMap<Person,String> map = new HashMap<>();
map.put(new Person("小何",19),"中国");
map.put(new Person("旺财",9),"韩国");
map.put(new Person("小何",19),"中国");
map.put(new Person("王桑",19),"日本");
for (Map.Entry<Person, String> entry : map.entrySet()) {
Person key = entry.getKey();
String value = entry.getValue();
System.out.println(key+"="+value);
}
}
}
class Person{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
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 boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && name.equals(person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Hashtable:
Hashtable也是Map接口的实现类。
Hashtable:底层也是一个哈希表,是一个线程安全的单线程集合,速度慢。
HashMap:底层是一个哈希表,是一个线程不安全的多线程集合,速度快。
之前学的单列集合和双列集合:都可以存储null值/键。
Hashtable集合:不能存储nutl值/键。
Hashtable和Vector集合一样,在jdk1.2版本之后分别被更先进的HashMap和Arraylist集合所取代。
但Hashtable的子类Properties依然活跃在历史舞台,Properties集合是一个唯一和IO流相结合的集合。
Hashtable<String,String> hashtable = new Hashtable<>();
hashtable.put(null,null); // 报错
JDK9的新特性:
List接口,Set接口,Map接口:里边增加了一个静态的方法of,可以给集合一次性添加多个元素。
static <e> list<e> of(E... elements)
注意:
1.of方法只适用于List接口Set接口,Map接口,不适用于这些接口的实现类,如hashSet。
2.of方法的返回值是一个不能改变的集合,集合不能再使用add,put方法添加元素,会抛出异常。
3.Set接口和Map接口在调用of方法的时候,不能有重复的元素,否则会抛出异常。
ArrayList<String> map = Map.of("小明");