Java集合

目录

集合框架:

Collection 接口的常用功能:

迭代器:

Iterator接口:

迭代器的常用方法:

迭代方式:

迭代示例:

并发修改异常 ConcurrentModificationException:

ListIterator接口:

forEachRemaining()方法:

for-each(增强for循环):

集合中的泛型:

List接口:

ArrayList实现类:

LinkedList实现类:

Vector实现类:

Set接口:

HashSet实现类:

哈希值:

哈希表结构:

HashSet集合存储元素不重复的原理:

LinkHashSet集合:

可变参数:

操作集合的工具类(Collections):

双列集合(Map):

Map常用实现类:

Map接口的常用方法:

遍历Map实现类:

Map集合存储自定义类型的Key值:

Hashtable:

JDK9的新特性:


集合框架:

集合按照其存储结构可以分为两大类,分别是单列集合(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();    // 没给迭代器泛型,必须使用强制类型转换,否则不用。
}

创建集合对象时使用泛型: 

好处:

  1. 安全,方便管理。
  2. 集合内元素类型一致,存储的是什么类型,在取出来时就是什么类型,不需要强制类型转换。
  3. 把运行期异常(代码运行之后会抛出的异常)提升到了编译期(写代码的时候会报错)。(如对集合所有元素调用 String 类型的 length() 方法,如果使用了泛型,在存储其他类型时就会报错,不至于等到运行时才抛出异常)

弊端:

  1. 泛型是什么类型,只能存储什么类型的数据

创建集合对象时不使用泛型:

好处:

  1. 集合不使用泛型,默认的类型就是object类型,可以存储任意类型的数据

弊端:

  1. 不安全,会引发异常
  2. 取出须强制类型转换,麻烦。

List接口:

List接口的实现类的特点:

  1. 有序的集合,存储的和取出的元素顺序是一样的。
  2. 有索引,因此有针对索引的方法。
  3. 允许存储重复的元素。

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接口的实现类的特点:

  1. 无索引,不能使用普通的for循环遍历。但可以使用增强for循环或迭代器来遍历。(增强for循环底层就是一个迭代器)
  2. 不允许存储重复的元素。

其实现类中有无序的集合,也有有序的集合。

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("小明");

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秦矜

对你有帮助的话,请我吃颗糖吧~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值