【Java学习总结】chapter06:集合

第6章:集合


在这里插入图片描述

本章学习目标:

  1. 掌握List集合、Set集合、Map集合的使用
  2. 掌握集合遍历方法的使用
  3. 熟悉泛型的使用
  4. 掌握Collections、Arrays工具类的使用
  5. 掌握JDK8的聚合操作功能

OVERVIEW

为了存储数目不确定&任意类型的对象,Java中提供了一系列特殊的类统称为集合。

集合按照存储结构可以分为,单列集合Collection和双列集合Map:

在这里插入图片描述

Point1(Collection):单列集合的根接口,用于存储一系列符合某种规则的元素

Collection集合有两个重要的子接口:List、Set

  • List集合的特点为元素有序、可重复,其主要的实现类有ArrayList和LinkedList
  • Set集合的特点为元素无序、并且不可重复,其主要的实现类有HashSet和TreeSet

在这里插入图片描述

Point2(Map):双列集合的根接口,用于存储具有键(Key)、值(Value)映射关系的元素

Map集合的特点为元素都包含一对键值对(Key唯一),通过Key值可以找到对应的value值

  • Map集合主要的实现类有HashMap和TreeMap

在这里插入图片描述

一、Collection接口

Collection是所有单列集合的根接口,在Collection中定义了单列集合ListSet的一些通用的方法可用于操作所有的单列集合:

在这里插入图片描述

注意:关于stream方法是JDK8增加的,用于对集合元素进行聚合操作,在文章结尾将详细讲解。

二、List接口

1.List接口概述

List接口继承自Collection接口,是单列集合的一个重要分支,List集合即为实现了List接口的对象

List集合的特点:

  1. List集合中允许出现重复的元素,即元素是一种线性的方式进行存储的(通过索引访问),与数组类似。
  2. List集合中的元素有序,即元素的存入、取出顺序一致。

List集合不但继承了Collection接口中的全部方法,还增加了一些操作集合独有的方法

List集合的常用方法:

在这里插入图片描述

注意:关于sort方法是JDK8增加的元素排序方法,该方法参数是一个接口类型的比较器Comparator,可通过Lambda表达式传入一个函数式接口作为参数,来指定集合元素的排序规则。

2.ArrayList集合

ArrayList是List接口的一个实现类(程序中常见的一种集合),可以将ArrayList集合看作一个长度可变的数组。

ArrayList描述
优点内部的数据存储结构是数组形式,允许通过索引方式访问元素(遍历与查找元素速度很快)
缺点在增加 or 删除指定位置的元素时,会创建新的数组(效率较低)

ArrayList集合的使用:

package c6p1_List_ArrayList;

import java.util.ArrayList;

public class Example01 {
	public static void main(String[] args) {
		//1.创建ArrayList集合
		ArrayList list =  new ArrayList();
		//2.向集合中添加元素
		list.add("stu1");
		list.add("stu2");
		list.add("stu3");
		list.add("stu4");
		list.add("stu4");
		list.add("stu4");
		list.add("stu5");

		System.out.println("集合的长度:" + list.size());
		System.out.println("第二个元素是" + list.get(1));
		System.out.println("第一次出现stu4的下标为是" + list.indexOf("stu4"));
		System.out.println("最后一次出现stu4的下标为是" + list.lastIndexOf("stu4"));
		System.out.println("输出下标为从2-6的List子集合:" + list.subList(2, 7));
	}
}

在这里插入图片描述

注意:如果发生数组越界,则下标返回的结果值将为-1

3.LinkedList集合

LinkedList是List接口的另一个实现类,可以将LinkedList集合看作一个双向循环列表,

该集合内部包含有两个Node类型的firstlast属性去维护这个双向列表,从而将所有元素串联起来。

注意:当插入 or 删除一个元素时,只需要修改元素之间的引用关系即可(具有高效的增删改效率

针对元素的增删操作,LinkedList在List集合常用方法基础上专门定义了一些方法,如下:

在这里插入图片描述

LinkedList集合的使用案例:

package c6p2_List_LinkedList;

import java.util.LinkedList;

public class Example02 {
	public static void main(String[] args) {
		//1.创建LinkedList集合
		LinkedList link = new LinkedList();
		//2.添加元素
		link.add("stu1");
		link.add("stu2");
		link.add("stu3");
		link.add("stu4");
		System.out.println(link);

		//2.获取元素
		Object object = link.peek();	//获取集合第一个元素
		System.out.println("使用peek方法获取第一个元素:" +object);

		//3.其他方法的元素处理
		link.offer("offer");
		link.push("push");
		System.out.println("使用offer与push方法处理后的集合:" + link);
	}
}

在这里插入图片描述

三、Set接口

1.Set接口概述

Set接口与List接口同样继承自Collection接口,是单列集合的一个重要分支,Set接口比Collection接口更加严格。

Set集合的特点:

  1. Set接口中的元素不重复(以某种规则保证存入的元素不重复)。
  2. Set接口中的元素是无序的

Set接口中主要有两个实现类:HashSet、TreeSet

  • 其中HashSet是根据对象的哈希值来确定元素在集合中存储的位置,因此具有良好的存取和查找性能。

  • 而TreeSet则是以二叉树的方式来存储元素,可以实现对集合中的元素进行排序。

2.HashSet集合

HashSet是Set接口的一个实现类,其存储的元素是不可重复且无序的。

当向HashSet集合中添加一个元素时,首先会调用该元素的hashCode()方法来确定元素的存储位置,然后再调用元素对象的equals()方法来确保该位置没有重复元素

注意:Set集合与List集合存取元素的方式都一样,在此不再赘述

(1)HashSet的基本使用:
package c6p6_Set_HashSet;

import java.util.HashSet;

public class Example09 {
	public static void main(String[] args) {
		HashSet set = new HashSet();
		set.add("Jack");
		set.add("Eve");
		set.add("Rose");
		set.add("Rose");//尝试向该Set集合中添加重复元素
		System.out.println(set);
		
		//1.使用forEach方法遍历输出Set集合中的元素
		set.forEach(obj ->System.out.println(obj));
	}
}

在这里插入图片描述

结果分析:从输出的结果可以看出

  1. 取出的元素顺序与添加元素的顺序并不一致(存储的无序性)
  2. 重复存入的字符串元素Rose被S自动去除了(元素的不重复性)
(2)HashSet对象的存储过程:

HashSet能保证存储元素的不重复性,是因为在存入元素时HashSet集合就已经做了很多工作,如下所示:

在这里插入图片描述

  • Step1:调用HashSet集合的add()方法存入元素
  • Step2:调用当前存入元素的hashCode()方法获得对象的哈希值(代表对象存储的位置)
  • Step3:然后根据对象的哈希值计算出一个存储位置
  • Step4:根据判定结果决定舍弃对象 or 调用add()方法向HashSet集合中存入元素
(3)HashSet存储自定义类型对象:

根据HashSet对象的存储流程,要保证HashSet正常工作就必须要在存入对象时重写Object类中的hashCode()equals()方法。

注意:在HashSet的基本使用案例中由于存入的String类已经默认重写了hashCode()和equals()方法,故可以正常使用HashSet存储

以下演示使用HashSet存储自定义Student类型:

case1:没有对hashCode与equals方法进行重写

package c6p6_Set_HashSet;

import java.util.HashSet;

class Student1{
	String id;
	String name;
	public Student1(String id,String name) {
		this.id = id;
		this.name = name;
	}
	public String toString() {
		return id + ":" + name;
	}
}

public class Example10_1 {
	public static void main(String[] args) {
		HashSet hashSet = new HashSet();
		Student1 stu1 = new Student1("1","luochenhao");
		Student1 stu2 = new Student1("2","lch");
		Student1 stu3 = new Student1("2","lch");
		
		hashSet.add(stu1);
		hashSet.add(stu2);
		hashSet.add(stu3);
		
		System.out.println(hashSet);
	}
}

在这里插入图片描述

结果分析:可以发现HashSet集合中出现了重复的元素。(equals()没有根据自定义类型的需求进行重写,导致创建的两个对象stu2与stu3所引用的对象地址不同,而HashSet认为这是两个不同的对象)

case2:对hashCode与equals方法进行重写后(假设id相同的学生为一个学生)

package c6p6_Set_HashSet;

import java.util.HashSet;

class Student2{
	String id;
	String name;
	public Student2(String id,String name) {
		this.id = id;
		this.name = name;
	}
	public String toString() {
		return id + ":" + name;
	}

	//1.重写hashCode()方法
	public int hashCode() {
		return id.hashCode();//返回id属性的哈希值
	}
	//2.重写equals方法()
	public boolean equals(Object obj) {
		if(this == obj) {	//判定1:判断比较对象是否相等(地址是否相同),如果是直接返回true
			return true;
		}
		if(!(obj instanceof Student2)) {	//判定2:判断对象是否为Student2类型,如果不是直接返回false
			return false;
		}
		//将obj对象强制转换为Student2类型
		Student2 obj1 = (Student2)obj;
		boolean result = this.id.equals(obj1.id);	//判定3:判定id值是否相等(对象的内容是否相同)
		return result;
	}
}

public class Example10_2 {
	public static void main(String[] args) {
		HashSet hashSet = new HashSet();
		Student2 stu1 = new Student2("1","luochenhao");
		Student2 stu2 = new Student2("2","lch");
		Student2 stu3 = new Student2("2","lch");
		
		hashSet.add(stu1);
		hashSet.add(stu2);
		hashSet.add(stu3);
		
		System.out.println(hashSet);
	}
}

在这里插入图片描述

总结:重写equals()方法判定两个对象是否相同的步骤

  1. 首先判定两个对象的地址是否相等
  2. 其次判定两个对象的类型是否相等
  3. 最后判定两个对象的内容是否相等(例为根据id的哈希值进行判定)
(4)HashSet对象保证元素唯一性码源分析:
//创建HashSet集合对象
HashSet set = new HashSet();
//向HashSet集合中添加元素
set.add("Jack");
set.add("Eve");
set.add("Rose");
set.add("Rose");

//1.HashSet.java中的add方法源码
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
//2.add方法调用了HashSet.java中的put方法
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
//3.put方法调用了HashSet.java中的hash方法与putVal方法
//4.hash方法:
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

//5.putVal方法:hash值与元素的hashCode()方法有密切联系
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //(1)如果哈希表没有初始化,则对其进行初始化操作
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //(2)根据对象的hash值计算对象的存储位置,如果该位置没有元素则直接存储元素
    if ((p = tab[i = (n - 1) & hash]) == null) {
        tab[i] = newNode(hash, key, value, null);
    } else {
        Node<K,V> e; K k;
        /**
            * 判定1:p.hash == hash存入的元素与以前的元素进行hash值的比较
            *   如果hash值不同,会继续向下执行,把元素添加到集合中
            *   如果hash值相同,会调用对象的equals()方法进行比较元素
            *       判定2:((k = p.key) == key || (key != null && key.equals(k)))
            *       如果equals()返回false,会继续向下执行,把元素添加到集合中
            *       如果equals()返回true,说明元素重复不进行存储
            * 
            * 注意&&操作的短路特性
        */
        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) {
            e = p;
        } else if (p instanceof TreeNode) {
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        } else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) {
            // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold) {
        resize();
    }
    afterNodeInsertion(evict);
    return null;
}
3.TreeSet集合

TreeSet是Set接口的另一个实现类,

其内部采用平衡二叉树来存储元素(该结构保证了TreeSet集合中没有重复的元素、并且可以对元素进行排序)。

针对TreeSet集合存储元素的特殊性,TreeSet在继承Set接口的基础上实现了一些特有的方法如下:

在这里插入图片描述

(1)TreeSet的基本使用:
package c6p7_Set_TreeSet;

import java.util.TreeSet;

public class Example11 {
	public static void main(String[] args) {
		TreeSet treeSet = new TreeSet();
        treeSet.add(36);
		treeSet.add(3);
		treeSet.add(9);
		treeSet.add(1);
		treeSet.add(21);
		treeSet.add(15);
		System.out.println("创建的TreeSet集合为:" + treeSet);
		
		//1.获取首尾元素
		System.out.println("TreeSet集合的首元素为:" + treeSet.first());
		System.out.println("TreeSet集合的尾部元素为:" + treeSet.last());
		
		//2.比较并获取元素
		System.out.println("集合中小于或等于9的最大的一个元素为:" + treeSet.floor(9));
		System.out.println("集合中大于10的最小的一个元素为:" + treeSet.higher(10));
		System.out.println("集合中大于100的最小的一个元素为:" + treeSet.higher(100));
		
		//3.删除元素
		Object first = treeSet.pollFirst();
		System.out.println("删除的第一个元素是:" + first);
		System.out.println("删除第一个元素后的TreeSet集合变为:" + treeSet);
	}
}

在这里插入图片描述

结果分析:不论向TreeSet集合中添加元素的顺序如何,最后这些元素都能按照一定的顺序进行排列

(2)TreeSet存储自定义类型排序:

集合中的元素在进行比较时,都会调用compareTo()方法(该方法是Comparable接口中定义的),

因此要想对集合中的自定义类型元素进行自定义排序,就必须实现Comparable接口(Java中的大部分类都实现了Comparable接口,并默认实现了接口中的CompareTo()方法,如Integer、Double、String)。

Java提供了两种TreeSet的排序规则,分别为:自然排序 and 定制排序

case1:自然排序

自定义排序要向TreeSet集合中存储的元素所在类必须实现Comparable接口,并重写CompareTo()方法,

然后TreeSet就会对该类型的元素使用CompareTo()方法进行比较,并默认进行升序排序:

package c6p7_Set_TreeSet;

import java.util.TreeSet;

class Teacher implements Comparable{
	int age;
	String name;
	
	public Teacher(String name, int age) {
		this.name = name;
		this.age = age;
	}
	public String toString(){
		return name + ":" + age;
	}
	//1.重写Comparable接口的compareTo()方法:先比较年龄age再比较名称name
	public int compareTo(Object obj) {
		Teacher s = (Teacher)obj;
		if (this.age - s.age > 0) {
			return 1;
		}
		if (this.age - s.age == 0) {
			return this.name.compareTo(s.name);
		}
		return -1;	//this.age - s.age < 0
	}
}

public class Example12 {
	public static void main(String[] args) {
		TreeSet treeSet = new TreeSet();
		treeSet.add(new Teacher("Jack",19));
		treeSet.add(new Teacher("Tom",19));
		treeSet.add(new Teacher("Rose",18));
		treeSet.add(new Teacher("Rose",18));
		treeSet.add(new Teacher("lch",18));
		System.out.println(treeSet);
	}
}

在这里插入图片描述

结果分析:Teacher对象会首先按照年龄升序排列,年龄相同时会根据姓名进行升序排序(同时TreeSet会进行元素去重)

case2:定制排序

如果希望类型按照自定义的方式进行排序时,可以通过在创建TreeSet集合时就自定义一个比较器来对元素进行定制排序:

package c6p7_Set_TreeSet;

import java.util.Comparator;
import java.util.TreeSet;

//定义比较器实现Comparator接口:根据字符串的长短进行排序
class MyComparator implements Comparator{
	public int compare(Object obj1, Object obj2) {
		String s1 = (String)obj1;
		String s2 = (String)obj2;
		return s1.length() - s2.length();
	}
}

public class Example13 {
	public static void main(String [] args){
		//1.在创建TreeSet集合时,传入Comparator接口实现定制排序规则
		TreeSet treeSet = new TreeSet(new MyComparator());
		
		treeSet.add("Jack");
		treeSet.add("Helena");
		treeSet.add("Eve");
		treeSet.add("luochenhao");
		System.out.println(treeSet);
		
		//2.创建集合时,使用Lambda表达式定制排序规则
		TreeSet treeSet1 = new TreeSet((obj1, obj2) ->{
			String s1 = (String)obj1;
			String s2 = (String)obj2;
			return s1.length() - s2.length();
		});

		treeSet1.add("Jack");
		treeSet1.add("Helena");
		treeSet1.add("Eve");
		treeSet1.add("luochenhao");
		System.out.println(treeSet1);
	}
}

在这里插入图片描述

程序分析:

该例中使用了TreeSet集合的public TreeSet(Comparator <? super E> comparator)有参构造方法,

分别传入了Comparable接口实现类MyComparator以及Lambda表达式两种参数方式创建了定制排序规则的TreeSet集合。

注意:在使用TreeSet集合存储数据时,TreeSet集合会对存入的数据进行比较排序(所以为了程序能够正常运行,一定要保证存入的TreeSet集合中元素类型相同)

四、Collection集合遍历

在实际开发中,针对Collection单列集合元素除了接班的增删改查操作外,还经常进行遍历操作。

下面将以List集合为例,进行几种不同的遍历方法

1.Iterator遍历集合

Iterator接口也是Java集合框架中的一员,但其与Collection、Map接口有所不同,

Iterator主要用于迭代访问(即遍历),而Collection、Map主要用于存储元素,因此Iterator对象也被称为迭代器。

(1)Iterator的基本使用:
package c6p3_Iterator;

import java.util.ArrayList;
import java.util.Iterator;

public class Example03 {
	public static void main(String[] args) {
		//1.创建ArrayList集合并向其中添加数据
		ArrayList list = new ArrayList();
		list.add("data_1");
		list.add("data_2");		
		list.add("data_3");		
		
		//2.获取Iterator对象对list集合进行遍历
		Iterator iterator = list.iterator();
		while(iterator.hasNext()) {
			Object obj = iterator.next();
			System.out.println(obj);
		}
		System.out.println(list);
	}
}

在这里插入图片描述

补充:Iterator工作原理

Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素:

  • 在调用Iterator迭代器对象的next()方法之前,迭代器的索引位于第一个元素之前不指向任何元素
  • 每调用一次next()方法便向后移动一位,直到hasNext()方法返回false
(2)Iterator的局限性:

在使用Iterator元素迭代器对集合中的元素进行迭代时,如果调用了集合对象的remove()方法去删除元素会出现异常

package c6p3_Iterator;

import java.util.ArrayList;
import java.util.Iterator;

public class Example06 {
	public static void main(String[] args) {
		ArrayList list = new ArrayList();
		list.add("Jack");
		list.add("Annie");
		list.add("Rose");
		list.add("Tom");
		
		Iterator it = list.iterator();
		while(it.hasNext()) {
			Object obj = it.next();			
			if("Annie".equals(obj)) {	
				list.remove(obj);
				//(1)直接跳出循环break;
				//(2)使用迭代器本身的删除方法it.remove()
			}
		}
		System.out.println(list);
	}
}

在这里插入图片描述

通过以下两种方法可解决在迭代器对集合中的元素进行迭代时使用remove方法出现的异常

case1:在循环体中remove()方法后书写break语句

while(it.hasNext()) {
	Object obj = it.next();
	if("Annie".equals(obj)) {
		list.remove(obj);
		break;
	}
}

case2:使用迭代器本身的删除方法it.remove()

while(it.hasNext()) {
	Object obj = it.next();
	if("Annie".equals(obj)) {
		it.remove();
	}
}

在这里插入图片描述

2.foreach遍历集合

为了简化Iterator的书写方式,从JDK5开始提供了foreach循环(一种更加简洁的for循环/增加for循环)

(1)foreach的基本使用:

foreach用于遍历数组或集合中的元素,其具体语法格式如下:

for (容器中元素的类型 临时变量temp : 容器变量) {
    //执行语句
}

注意:foreach不需要获取容器的长度、也不需要根据索引访问容器中的元素(其会自动遍历容器中的每个元素)

package c6p3_Iterator;

import java.util.ArrayList;

public class Example04 {
    public static void main(String[] args) {
        //1.创建ArrayList集合并向其中添加数据
        ArrayList list = new ArrayList();
        list.add("data_1");
        list.add("data_2");
        list.add("data_3");

        //2.使用foreach循环遍历集合
        for (Object obj : list) {
            System.out.println(obj);
        }
        System.out.println(list);
    }
}

在这里插入图片描述

总结:foreach遍历集合的语法非常的简洁,没有循环条件、没有迭代语句(都由JVM去处理了)。

(2)foreach的局限性:

虽然foreach循环书写非常简单,但也有缺点:使用foreach循环遍历集合与数组时,只能访问集合中的元素(不能进行修改)

package c6p3_Iterator;

public class Example05 {
    static String[] strs = {"aaa", "bbb", "ccc"};
    public static void main(String[] args) {
        //1.foreach循环遍历数组
        for (String str : strs) {
            str = "ddd";
        }
        System.out.println(strs[0] + "," + strs[1] + "," + strs[2]);
        //2.for循环遍历数组
        for (int i = 0; i < strs.length; ++i) {
            strs[i] = "ddd";
        }
        System.out.println(strs[0] + "," + strs[1] + "," + strs[2]);
    }
}

在这里插入图片描述

分析:foreach循环并不能修改数组中元素的值,

  • 原因是循环体中的str = "ddd"只是将临时变量str指向了一个新的字符串(与数组中的元素无关),
  • 而在for循环中是可以通过索引的方式来引用数组中的元素进行操作的。
3.JDK8的forEach遍历集合
(1)forEach(Consumer action)

在JDK8中根据Lambda表达式特性还增加了一个forEach(Consumer action)方法来遍历集合,该方法需要的参数是一个函数式接口

package c6p5_JDK8_forEach;

import java.util.ArrayList;

public class Example07 {
	public static void main(String[] args) {
		ArrayList list = new ArrayList();
		list.add("data_01");
		list.add("data_02");
		list.add("data_03");
		System.out.println(list);
		
		//1.使用JDK8增加的forEach(Consumer action)方法遍历集合
		list.forEach(obj->System.out.println("迭代集合元素:" + obj));
	}
}

补充:forEach()方法对集合中的元素进行遍历,传递的是一个Lambda表达式形式书写的函数式接口;forEach()方法在执行时会自动遍历集合元素并将元素逐个传递给Lambda表达式的形参。

在这里插入图片描述

(2)forEachRemaining(Consumer action)

除了针对所有集合类型对象增加的forEach(Consumer action)方法遍历外,还针对Iterator迭代器对象提供了一个forEachRemaining(Consumer action)方法进行遍历,该方法同样需要的一个函数式接口

package c6p5_JDK8_forEach;

import java.util.ArrayList;
import java.util.Iterator;

public class Example08 {
	public static void main(String[] args) {
		ArrayList list = new ArrayList();
		list.add("data_01");
		list.add("data_02");
		list.add("data_03");
		System.out.println(list);

		Iterator iterator = list.iterator();
		//2.使用JDK8增加的forEachRemaining来遍历迭代器对象
		iterator.forEachRemaining(obj -> System.out.println("迭代集合元素:" + obj));
	}
}

在这里插入图片描述

五、Map接口

1.Map接口概述

Map接口属于是一种双列集合(每个元素都包含一个键对象Key和值对象Value),键和值之间存在一种对应关系(映射)。

Map中的映射关系是一对一的,其中键对象&值对象可以是任意的数据类型(并且键对象Key不允许重复),以下是Map接口中定义的一些常用方法:

在这里插入图片描述

2.HashMap集合

HashMap集合是Map接口的一个实现类,用于存储键值映射关系。

HashMap集合的特点:

  1. HashMap集合允许键、值为空,但不允许键的重复
  2. HashMap集合中的元素是无序的
(1)HashMap集合内部结构及存储原理:

HashMap底层是由哈希表组成的,即数组+链表的组合体(数组是HashMap的主体结构,链表是为了解决哈希值冲突的分支结构)。

注意:HashMap集合内部结构为数组+链表,故其增删改查的效率都比较高

在这里插入图片描述

水平方向以数组结构为主体,在竖直方向以链表结构进行结合的就是HashMap中的哈希表结构。

在哈希表结构中,水平方向数组的长度称为HashMap集合的容量(capacity)、竖直方向元素位置对应的链表结构称为(bucket)。

当向HashMap集合添加元素时:

  1. 首先会调用插入键值队中键对象k的hash(k)方法—>快速定位并寻址到该元素在集合中要存储的位置(桶的位置)
  2. 当被定位的桶的位置为null时,则可以直接向该桶位置插入元素对象;若桶的位置不为空时,需要调用键对象的equals(k)方法
  3. 如果新插入的键对象与已存在元素的键对象相同,则替换原有相同的对象;若没有相同的时,会在桶的链表结构头部新增一个节点用于插入元素。(保证哈希值不冲突)
(2)HashMap的基本使用:
package c6p8_Map_HashMap;

import java.util.HashMap;
import java.util.Map;

public class Example14 {
	public static void main(String[] args) {
		Map map = new HashMap();
		map.put("1", "Jack");
		map.put("2", "Rose");
		map.put("3", "Lucy");
		map.put("4", "Lucy");
		map.put("1", "Tom");
		System.out.println(map);

		//1.查看键对象是否存在
		System.out.println(map.containsKey("1"));
		//2.获取指定键对象映射的值
		System.out.println(map.get("1"));
		//3.获取集合中的键对象和值对象集合
		System.out.println(map.keySet());
		System.out.println(map.values());
		//4.替换指定键对象映射的值
		map.replace("1", "Tom2");
		System.out.println(map);
		//5.删除指定键对象映射的键值对元素
		map.remove("1");
		System.out.println(map);
	}
}

在这里插入图片描述

注意:可以发现两次插入键元素都为1的Jack与Tom,由于键元素的唯一性先插入的键值对{1, Jack}被覆盖了(如果需要可以接受返回的旧元素)。

(3)HashMap的性能分析:

在HashMap集合中对于插入操作,其时间复杂度不大(只需要改变链表的引用链即可);而对于查找来说,就需要遍历链表通过equals(k)方法进行逐一比对,所以从性能方面考虑HashMap中的链表出现越少性能才会越好(即HashMap集合中桶的数量越多越好)。

HashMap的桶的数目就是集合中主体数组结构的长度,由于数组时内存中连续的存储单元(占用的空间代价很大、随机存取速度最高),通过增大桶的数量而减少Entry<K, V>链表的长度,从而提高HashMap中读取数据的速度(用空间换时间)。

可以根据实际情况动态分配桶的数量,从而达到最佳的时间、空间使用:

在使用new HashMap()方法创建HashMap时,会默认集合容量capacity大小为16,加载因子loadFactor为0.75(集合桶的阈值为12);根据实际开发对存取效率的需要,可以使用newHashMap(int initialCapacity, float loadFactor)构造方法自主指定集合容量与加载因子。

(4)LinkedHashMap集合实现元素顺序添加:

HashMap集合并不能保证集合元素存入和取出的顺序,但HashMap的子类LinkHashMap能够实现元素有序化

与LinkedList一致,LinkedHashMap也采用了双向链表来维护内部元素的关系,使元素迭代的顺序与存入的顺序一致,如下例:

package c6p9_Map_traverse;

import java.util.*;

public class Example18 {
	public static void main(String[] args) {
		Map map1 = new HashMap();
		map1.put("3", "Lucy");
		map1.put("1", "Jack");
		map1.put("2", "Rose");
		map1.forEach((key, value) -> System.out.println(key + ":" + value));	
		System.out.println("================================");

		Map map2 = new LinkedHashMap();
		map2.put("3", "Lucy");
		map2.put("1", "Jack");
		map2.put("2", "Rose");
		map2.forEach((key, value) -> System.out.println(key + ":" + value));
	}
}

在这里插入图片描述

3.TreeMap集合

TreeMap集合是Map接口的另一个实现类,也是用于存储键值映射关系。

HashMap集合的特点:

  1. HashMap集合允许键、值为空,但不允许键的重复(与TreeSet相似通过二叉树原理保证元素唯一性)。
  2. HashMap集合中的元素是有序的
(1)TreeMap的基本使用:
package c6p10_TreeMap;

import java.util.HashMap;
import java.util.Map;

public class Example19_TreeMap {
	public static void main(String[] args) {
		Map map = new HashMap();
		map.put("2", "Rose");
		map.put("1", "Jack");
		map.put("3", "Lucy");
		map.put("4", "lch");
		//String类型实现了Comparable接口,默认按自然顺序排序对元素进行排序
		System.out.println(map);
	}
}

在这里插入图片描述

(2)TreeMap存储自定义类型排序:

与TreeSet集合相似,在使用TreeMap集合时也可以通过自定义比较器Comparator的方式实现自定义排序:

package c6p10_TreeMap;

import java.util.*;

//1.自定义比较器针对String类型的键对象k行比较
class CustomComparator implements Comparator{
	public int compare(Object obj1,Object obj2) {
		String key1 = (String)obj1;
		String key2 = (String)obj2;
		//在实现compare()方法时,调用了String对象的compareTo()方法将比较后的值返回
		return key2.compareTo(key1);
	}
}

public class Example20_Comparator {
	public static void main(String[] args) {
		//2.在创建map集合时,传入自定义比较器CustomComparator对象并实现自定义排序
		Map map = new TreeMap(new CustomComparator());
		map.put("2", "Rose");
		map.put("1", "Jack");
		map.put("3", "Lucy");
		map.put("4", "lch");
		System.out.println(map);		
	}
}

在这里插入图片描述

总结:上述过程中的自定义比较器实现了按照键对象的k值进行比较,按照k值进行从大到小的排序

4.Map集合遍历

Map集合遍历的方式和单列集合Collection集合遍历的方式基本相同,Iterator迭代器与forEach(BiConsumer action)方法遍历。

(1)Iterator迭代器遍历Map集合:

使用Iterator迭代器遍历Map集合,必须要先将Map集合转换为Iterator接口对象然后再进行遍历。

由于Map集合中的元素是由键值对组成的,所以使用Iterator接口遍历Map集合时,会有两种将Map集合转换为Iterator接口对象再进行遍历的方法:keySet()entrySet()

case1keySet()方法

keySet()方法需要先将Map集合中所有键对象转换为Set单列集合,再将包含键对象的Set集合转换为Iterator接口对象,然后遍历Map集合中所有的键,最后根据键获取对应的值:

在这里插入图片描述

package c6p9_Map_traverse;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class Example15_1Iterator迭代器遍历Map集合keySet {
	public static void main(String[] args) {
		Map map = new HashMap();
		map.put("1", "Jack");
		map.put("2", "Rose");
		map.put("3", "Lucy");
		System.out.println(map);

		//step1.获取键的Set单列集合
		Set keySet = map.keySet();
		//step2.将包含键对象的Set集合转换为Iterator对象
		Iterator iterator = keySet.iterator();
		//step3.遍历Map中所有的键,获取每个键所对应的值
		while(iterator.hasNext()) {
			Object key = iterator.next();
			Object value = map.get(key);
			System.out.println(key + ":" + value);
		}
	}
}

在这里插入图片描述

case2entrySet()方法

entrySet()方法将原有的Map集合中的键值对作为一个整体返回为Set集合,接着将包含键值对对象的Set集合转换为Iterator接口对象,然后获取集合中所有的键值对映射关系,最后从映射关系中取出键与值:

在这里插入图片描述

package c6p9_Map_traverse;

import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
import java.util.Set;

public class Example15_2Iteraror_entrySet {
	public static void main(String[] args) {
		Map map = new HashMap();
		map.put("1", "Jack");
		map.put("2", "Rose");
		map.put("3", "Lucy");
		System.out.println(map);

		//step1.获取存储在Map中所有键值对映射关系的Set集合
		Set entrySet = map.entrySet();
		//step2.将键值对对象的Set集合转换为Iterator接口对象
		Iterator iterator = entrySet.iterator();
		//step3.获取集合中所有的键值对映射关系,最后从映射关系中取出键与值
		//注意:Entry是Map接口内部类,每个Map.Entry对象代表Map中的一个键值对
		while (iterator.hasNext()) {
			Map.Entry entry = (Map.Entry)(iterator.next());		//获取集合中每一个键值对映射对象
			Object key = entry.getKey();						//获取Entry中的键
			Object value = entry.getValue();					//获取Entry中的值
			System.out.println(key + ":" + value);
		}
	}
}

在这里插入图片描述

总结:Entry是Map接口内部类,每个Map.Entry对象代表Map中的一个键值对

(2)forEach(BiConsumer action)方法遍历Map集合:

与Collection接口类似,在JDK8中也根据Lambda表达式特性新增了一个forEach(BiConsumer action)方法来遍历Map集合。

该方法所需要的参数也是一个函数式接口,因此可以使用Lambda表达式的书写形式来进行集合遍历:

package c6p9_Map_traverse;

import java.util.HashMap;
import java.util.Map;

public class Example16_forEach {
	public static void main(String[] args) {
		Map map = new HashMap();
		map.put("1", "Jack");
		map.put("2", "Rose");
		map.put("3", "Lucy");
		System.out.println(map);
		//使用forEach(BiConsumer action)方法遍历集合
		map.forEach((key,value) -> System.out.println(key + ":" + value));
	}
}

在这里插入图片描述

注意:该方法传递的是一个Lambda表达式书写的函数式接口BiConsumer,该方法在执行时会自动遍历集合元素的键和值,并将结果逐个传递给Lambda表达式的形参。

(3)values()方法遍历Map集合:

在Map集合还提供了一个values()方法,通过这个方法可以直接获取Map中存储所有值的Collection集合,如下:

package c6p9_Map_traverse;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class Example17_values {
	public static void main(String[] args) {
		Map map = new HashMap();
		map.put("1", "Jack");
		map.put("2", "Rose");
		map.put("3", "Lucy");
		System.out.println(map);

		//1.直接获取Map集合中存储所有value值的集合对象
		Collection values = map.values();
		//2.遍历Map集合所有值的对象V
		values.forEach(v -> System.out.println(v));		
	}
}

在这里插入图片描述

5.Properties集合

Map接口还有另一个实现类Hashtable,Hashtable的效率不及HashMap(基本被HashMap取代),但Hashtable是线程安全的

Hashtable类有一个Properties子类在实际开发中非常重要,其主要用来存储字符串类型的键和值

注意:在实际开发中经常使用Properties集合类来存取应用的配置项

以下演示对一个配置文件进行写入与读取操作,如下:

step1:初始化一个配置文件

package c6p11_Properties;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.Enumeration;
import java.util.Properties;

//通过Properties进行属性文件的初始化操作
public class Example21 {
	public static void main(String[] args) throws Exception {
		//1.创建Properties对象
		Properties properties = new Properties();
		//2.指定要写入操作的文件名称和位置
		FileOutputStream out = new FileOutputStream("test.properties");
		//3.向Properties类文件进行写入键值对信息操作
		properties.setProperty("author", "lch");
		properties.setProperty("weather", "sunny");
		properties.setProperty("content", "propertie");
		//4.将此Properties集合中的键值对保存到本地配置文件test.properties中
		properties.store(out, "初始化properties信息");
	}
}

可以发现这项目文件下自动生成了一个名为test的配置文件,打开可以发现写入的配置信息

在这里插入图片描述
在这里插入图片描述

step2:对初始化的配置文件进行读取与写入操作

package c6p11_Properties;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Properties;

//通过Properties集合类对properties配置文件进行读取和写入操作
public class Example22 {
	public static void main(String[] args)throws Exception {
		//1.通过Properties进行属性文件的读取操作
		//(1)创建Properties对象
		Properties properties = new Properties();
		//(2)加载要读取的文件test.properties
		properties.load(new FileInputStream("test.properties"));
		//(3)遍历test.properties键值对元素信息,完成读取操作
		properties.forEach((k, v) -> System.out.println(k + " = " + v));

		//2.通过Properties进行属性文件的写入操作
		//(1)指定要写入操作的文件名称和位置
		FileOutputStream out = new FileOutputStream("test.properties");
		//(2)向Properties类文件进行写入键值对信息操作
		properties.setProperty("author", "luochenhao");
		properties.setProperty("tips", "goodluck");
		//(3)将此Properties集合中新增键值对信息写入配置文件test.properties
		properties.store(out, "test操作");
	}
}

在这里插入图片描述
在这里插入图片描述

总结:

  1. 输入重复的key键则其对应的value值将被覆盖(author先后被覆盖为lch)
  2. 存入的键值对是无序的(窗口输出的顺序与配置写入的先后不同)

六、泛型

1.泛型概述
(1)问题引入:集合元素类型强制转换报错

在之前的学习中知道集合中可以存储任意类型的对象元素,但是在把元素存入集合之后元素的类型就会被遗忘,(这个对象的编译类型就统一编程了Obejct类型)

从集合中取出的元素如果无法确定其类型,在进行强制类型转换时很容易出错:

package c6p12_Generics;

import java.util.ArrayList;

//程序无法确定集合元素类型,进行强制类型转换报错
public class Example23 {
	public static void main(String[] args) {
		ArrayList list = new ArrayList();
		list.add("String1");//添加字符串对象
		list.add("String2");
		list.add(1);//添加Integer对象

		//遍历ArrayList集合并强制转换成String类型进行输出
		for (Object obj : list) {
			String str = (String)obj;
			System.out.println(str);
		}
	}
}

在这里插入图片描述

Integer对象无法转换为String类型,报错ClassCastException类型转换错误

(2)解决:使用泛型指定集合存储类型

为了解决由于无法确定元素类型,而在进行强制类型转换时出现的报错,Java引入了参数化类型(parameterized type)这个概念即泛型

泛型可以限定操作数据的类型,在定义集合类时可以使用<参数化类型>的方式指定该集合中存储的数据类型

泛型的定义格式:

ArrayList<String> list1 = new ArrayList<String>(); //指定一种类型的格式
HashMap<String, Integer> list2 = new HashMap<String, Integer>(); //指定多种类型的格式,多种类型之间用逗号隔开

注意:在具体调用时候给定的类型可以看成是实参,并且实参的类型只能是应用数据类型

package c6p12_Generics;

import java.util.ArrayList;

//程序无法确定集合元素类型,进行强制类型转换报错
public class Example24 {
	public static void main(String[] args) {
		//使用泛型限定ArrayList集合只能存储String类型数据
		ArrayList<String> list = new ArrayList<String>();
		list.add("String1");//添加字符串对象
		list.add("String2");
		list.add(1);//添加Integer对象

		for (String str : list) {
			System.out.println(str);
		}
	}
}

在这里插入图片描述

总结:泛型的作用

  1. 使用泛型在编译时期就会将提示报错,明确Integer对象无法存入集合中
  2. 在使用了泛型限定集合存储类型后,在使用foreach遍历集合时可以指定元素类型为String而不是Object(避免类型转换)
2.泛型类
(1)泛型类定义:

[修饰符] class [类名]<类型> {}

public class Generic<T> {}
//此处的T可以为用任意符号标识,常见的T\E\K\V等形式的参数常用于表示泛型
(2)泛型类的使用:

在这里插入图片描述

package c6p12_Generics_itcast1;

/**
 * GenericsDemo测试类用于测试Student类与Teacher类中的内容
 */
public class GenericsDemo {
    public static void main(String[] args) {
        Student student = new Student();
        student.setName("lch");
        System.out.println("studentName:" + student.getName());

        Teacher teacher = new Teacher();
        teacher.setAge(18);
        System.out.println("teacherAge:" + teacher.getAge());

        System.out.println("=============================");
        Generics<String> generics1 = new Generics<String>();
        generics1.setT("lch");
        System.out.println("genericsName:" + generics1.getT());

        Generics<Integer> generics2 = new Generics<Integer>();
        generics2.setT(18);
        System.out.println("genericsAge:" + generics2.getT());
    }
}
package c6p12_Generics_itcast1;

public class Student {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
package c6p12_Generics_itcast1;

public class Teacher {
    private Integer age;

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}
package c6p12_Generics_itcast1;

public class Generics<T> {
    private T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
}

在这里插入图片描述

3.泛型方法:
(1)泛型方法定义:

[修饰符] <类型> [返回值类型] [方法名](类型 变量名) {...}

public <T> void show(T t) {}
(2)泛型方法的使用:

在这里插入图片描述

package c6p12_Generics_itcast2;

public class GenericDemo {
    public static void main(String[] args) {
        notGeneric notGeneric = new notGeneric();
        notGeneric.show("lch");
        notGeneric.show("18");
        notGeneric.show(false);
        //notGeneric.show(12.56);
        System.out.println("===========================");
        Generic generic = new Generic<String>();
        generic.show("lch");
        generic.show(18);
        generic.show(false);
        generic.show(12.56);
    }
}

package c6p12_Generics_itcast2;

public class notGeneric {
    public void show(String string) {
        System.out.println(string);
    }

    public void show(Integer integer) {
        System.out.println(integer);
    }

    public void show(Boolean bool) {
        System.out.println(bool);
    }
}
package c6p12_Generics_itcast2;

public class Generic<T> {
    public <T> void show(T t) {
        System.out.println(t);
    }
}

在这里插入图片描述

4.泛型接口
(1)泛型接口定义:

[修饰符] interface [接口名]<类型> {…}

public interface Generic<T> {}
(2)泛型接口的使用:

在这里插入图片描述

package c6p12_Generics_itcast3;

public class GenericDemo {
    public static void main(String[] args) {
        Generic<String> generic1 = new GenericImplement<String>();
        generic1.show("lch");
        Generic<Integer> generic2 = new GenericImplement<Integer>();
        generic2.show(18);
    }
}
package c6p12_Generics_itcast3;

public interface Generic<T> {
    void show(T t);
}
package c6p12_Generics_itcast3;

public class GenericImplement<T> implements Generic<T> {
    @Override
    public void show(T t) {
        System.out.println(t);
    }
}

在这里插入图片描述

5.类型通配符

为了表示各种泛型List的父类,可以使用类型通配符:

(1)类型通配符:<?>

List<?>:表示元素类型未知的List,其元素可以匹配任何的类型;

注意:这种带通配符的Lsit仅表示它是各种泛型List的父类,并不能把元素添加到其中

(2)类型通配符上限:<? extends 类型>

List<? extends type>:表示元素类型是Number或者其子类型;

指定通配符上限

(3)类型通配符下限:<? super 类型>

List<? super type>:表示元素类型是Number或者其父类型;

指定通配符下限

七、常用工具类(记录)

1.Collections工具类

在Java中针对集合的操作非常频繁(例如:排序、元素查找等),Java中专门提供了一个Collection工具类用来操作集合:

注意:Collection类中提供了大量的静态方法用于对集合中的元素进行排序、查找和修改等

(1)添加&排序操作:
方法声明方法描述
static boolean addAll(Collection<? super T> c, T… elements)将所有指定元素添加到指定集合c中
static void reverse(List list)反转指定List结合中元素的顺序
static void shuffle(List list)对List集合中的元素进行随机排序
static void sort(List list)根据元素的自然顺序对List集合中的元素进行排序
static void swap(List list, int i, int j)将指定List集合中角标ij处元素进行交换
package c6p13_CollectionsTools;

import java.util.ArrayList;
import java.util.Collections;

public class Example25Collection_AddandSort {
	public static void main(String[] args) {
		ArrayList<String> arrayList = new ArrayList<>();
		Collections.addAll(arrayList, "C", "Z", "B", "K", "A", "G", "I");
		System.out.println("排序前:" + arrayList);
		Collections.reverse(arrayList);
		System.out.println("反转后:" + arrayList);
		Collections.sort(arrayList);
		System.out.println("按自然排序后:" + arrayList);
		Collections.shuffle(arrayList);
		System.out.println("按随机顺序排序后:" + arrayList);
		Collections.swap(arrayList, 0, arrayList.size() - 1);
		System.out.println("集合首位元素交换后:" + arrayList);
	}
}

在这里插入图片描述

(2)查找&替换操作:
方法声明方法描述
static int binarySearch(List list, Object key)使用二分法搜索指定对象在List集合中的索引,查找的List集合中的元素必须是有序的
static Object max(Collection col)根据元素的自然顺序,返回给定集合中最大的元素
static Object min(Collection col)根据元素的自然顺序,返回给定集合中最小的元素
static boolean replaceAll(List list, Object oldVal, Object newVal)用一个新值newVal替换List集合中所有的旧值oldVal
package c6p13_CollectionsTools;

import java.util.ArrayList;
import java.util.Collections;

public class Example26Collection_QueryandReplace {
    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>();
        Collections.addAll(arrayList, -3, 2, 9, 5, 8, 8, 9, 10);
        System.out.println("集合中的元素:" + arrayList);
        System.out.println("集合中的最大元素:" + Collections.max(arrayList));
        System.out.println("集合中的最小元素:" + Collections.min(arrayList));
        //1.将集合中的8用0替换掉
        Collections.replaceAll(arrayList, 8, 0);
        System.out.println("替换后的集合:" + arrayList);
        //2.使用二分查找查找元素9所在角标位置
        Collections.sort(arrayList);
        int index = Collections.binarySearch(arrayList, 9);
        System.out.println("排序后的集合:" + arrayList);
        System.out.println("通过二分查找寻找到元素9所在的角标:" + index);
    }
}

在这里插入图片描述

注意:更多Collection类方法可以自学参考API帮助文档,不要成为API调用工程师

2.Arrays工具类

在java.util包中除了针对集合操作提供了一个集合工具类Collections,还针对数组操作提供了Arrays数组工具类:

(1)使用Sort()方法排序:
package c6p14_ArraysTools;

import java.util.Arrays;

public class Example27Arrays_sort {
	public static void main(String[] args) {
		int arr[] = {9, 8, 3, 5, 2};
		System.out.println("排序前:");
		printArray(arr);
		//调用Arrays的sort()方法进行排序
		Arrays.sort(arr);
		System.out.println("排序后:");
		printArray(arr);
	}

	public static void printArray(int[] arr) {
		System.out.print("[");
		for (int i = 0; i < arr.length; ++i) {
			if (i != arr.length - 1) {
				System.out.print(arr[i] + ", ");
			} else {
				System.out.println(arr[i] + "]");
			}
		}
	}
}

在这里插入图片描述

总结:针对数组排序,数组工具Arrays还提供了多个重载的sort()方法,既可以按照自然顺序排序,也可以比较器参数按照定制规则排序,同时还支持选择排序的元素范围。

(2)使用binarySearch(Object[] a, Object key)方法查找元素:

在数组中如果需要查找某个元素时,可以使用binarySearch方法返回数组元素的下标值index

package c6p14_ArraysTools;

import java.util.Arrays;

public class Example28Arrays_binarySearch {
    public static void main(String[] args) {
        int[] arr = {9, 8, 3, 5, 2};
        Arrays.sort(arr);
        int index = Arrays.binarySearch(arr, 3);
        System.out.println("排序后元素3的索引为:" + index);
    }
}

在这里插入图片描述

注意:binarySearch方法只能针对排序后的数组进行元素查找,这是由二分查找的特性决定的

(3)使用copyOfRange(int[] original, int from, int to)方法拷贝元素:

在程序开发中,如果需要在不破坏元素的情况下使用数组中的部分元素可以使用copyOfRange方法生成一个新的子数组:

package c6p14_ArraysTools;

import java.util.Arrays;

public class Example29Arrays_copyOfRange {
    public static void main(String[] args) {
        String[] arr1 = {"Jack", "Perk", "Jemmy", "John"};
        String[] arr2 = Arrays.copyOfRange(arr1, 1, 7);
        for (int i = 0; i < arr2.length; ++i) {
            System.out.print(arr2[i] + " ");
        }
    }
}

在这里插入图片描述

注意:由于最大索引超过了copy数组边界,另外的3个元素放入了字符串String类型数组的默认值null

(4)使用fill(Object[] a, Object val)方法替换元素:

将一个数组中的所有元素替换成同一个元素,此时可以使用Arrays工具类的fill方法:

package c6p14_ArraysTools;

import java.util.Arrays;

public class Example30Arrays_fill {
    public static void main(String[] args) {
        String[] arr = {"Jack", "Perk", "Jemmy", "John"};
        Arrays.fill(arr, "temp");
        for (int i = 0; i < arr.length; ++i) {
            System.out.print(arr[i] + " ");
        }
    }
}

在这里插入图片描述

注意:更多Arrays类方法可以自学参考API帮助文档

八、聚合操作

基于Lambda表达式可以简化集合与数组的遍历、过滤等操作的特性,在JDK8中新增了一个聚合操作:

1.聚合操作概述

为了简化集合数组、数组中对元素的查找、过滤、转换等操作,在JDK8中增加了一个Stream接口,

这个接口可以将集合、数组中的元素转换为Stream流的形式,并结合Lambda表达式的优势进一步简化上述过程(聚合操作)。

使用聚合操作的流程如下:

  1. 将原始集合 or 数组对象转换为Stream流对象
  2. 对Stream流对象中的元素进行一系列的过滤、查找等中间操作(Intermediate Operations),然后返回一个Stream流对象
  3. 对Sream流进行遍历、统计、收集等终结操作(Terminal Operation)获取想要的结果
package c6p15_Stream;

import java.util.*;
import java.util.stream.Stream;

public class Example31 {
    public static void main(String[] args) {
        //创建一个List集合对象
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("Jack");
        arrayList.add("Tom");
        arrayList.add("Jemmy");
        arrayList.add("John");
        //1.创建一个Stream流对象
        Stream<String> stream = arrayList.stream();
        //2.对Sream流对象中的元素分别进行过滤、截取操作
        Stream<String> stream2 = stream.filter(i -> i.startsWith("J"));
        Stream<String> stream3 = stream2.limit((2));
        //3.对Stream流中的元素进行终结操作,进行遍历输出
        stream3.forEach(j -> System.out.println(j));
        System.out.println("===================================");
        //通过链式表达式的形式完成聚合操作,等效果操作
        arrayList.stream().filter(i -> i.startsWith("J")).limit(2).forEach(j -> System.out.println(j));
    }
}

在这里插入图片描述

注意:关于链式表达式(有返回值不获取返回值而是调用再另一个方法)实现聚合操作,这种链式调用也称为管道流操作

2.创建Stream流对象

在Java中集合对象由对应的集合类,通过对应的集合类提供的静态方法可以创建Stream流对象,

而数组没有对应的数组类(必须通过其他方法创建Stream流对象),针对不同的源数据Java提供了多种创建Stream流对象的方式

  • case1:所有的Collections集合都可以使用steam()静态方法获取Stream流对象。
  • case2:Stream接口的of()静态方法可以获取基本类型包装类数组、引用类型数组和单个元素的Stream流对象
  • case3:Arrays数组工具类的stream()静态方法也可以获取数组元素的Stream流对象
package c6p15_Stream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class Example32 {
    public static void main(String[] args) {
        //创建一个数组
        Integer[] array = {9, 8, 3, 5, 2};
        //利用Arrays类中的asList()方法将array数组转化为List集合
        List<Integer> List = Arrays.asList(array);
        //1.使用集合对象的stream()静态方法创建Stream流对象
        Stream<Integer> stream1 = List.stream();
        stream1.forEach(i -> System.out.print(i + " "));
        System.out.println();

        //2.使用Stream接口的of()静态方法创建Stream流对象
        Stream<Integer> stream2 = Stream.of(array);
        stream2.forEach(i -> System.out.print(i + " "));
        System.out.println();

        //3.使用Arrays数组工具类的stream()静态方法创建Stream流对象
        Stream<Integer> stream3 = Arrays.stream(array);
        stream3.forEach(i -> System.out.print(i + " "));
        System.out.println();
    }
}

总结:上例中通过3种方法实现了Stream流对象的创建,并通过Stream流对象的forEach()方法结合Lambda表达式完成了遍历

在这里插入图片描述

补充:

  1. JDK8中只针对单列结合Collections接口对象提供了stream()静态方法获取Stream流对象,
  2. 而Map集合需要先调用keySet()entrySet()values()方法将Map集合转换为Set集合后再使用stream()静态方法获取Stream流对象。
3.Stream流的常用方法

(1)forEach遍历

遍历forEach()方法是JDK8中增加的Stream接口中用于遍历流元素,该方法不能保证元素的遍历过程在流中是被有序执行的

void forEach(Consumer<? super T> action);

该方法接受一个Consumer函数式作为参数(可以是一个Lambda表达式 or 方法引用)作为遍历动作:

package c6p15_Stream;

import java.util.stream.Stream;

public class Example33_forEach {
    public static void main(String[] args) {
        //1.通过字符串源数据创建一个stream流对象
        Stream<String> stream = Stream.of("Jack", "Tom", "John", "Tim");
        //2.通过forEach方法遍历Stream流对象中的元素
        stream.forEach(i -> System.out.println(i));
    }
}

在这里插入图片描述

注意:对于第2步操作也可使方法引用作为参数传入

stream.forEach(System.out::println)

(2)filter过滤

使用filter()方法可以将一个Stream流中的元素筛选成另一个子集流,方法声明如下:

Stream<T> filter(Predicate<? super T> predicate);

该方法接受一个Predicate函数式接口参数(可以是一个Lambda表达式 or 方法引用)作为筛选条件:

package c6p15_Stream;

import java.util.stream.Stream;

public class Example34_filter {
    public static void main(String[] args) {
        //1.通过字符串源数据创建了一个Stream流对象
        Stream<String> stream = Stream.of("Jack", "Tom", "Tim", "John");
        //2.对Stream流中的元素进行筛选遍历输出
        stream.filter(i -> i.startsWith("J")).filter(i -> i.length() > 2).forEach(System.out::println);
    }
}

在这里插入图片描述

注意:对于第2步筛选操作也可使用逻辑运算符进行简化,如下:

stream.filter(i -> i.startsWith("J") && i.length() > 2)

(3)map映射

Stream流对象的map()方法可以将流对象中的元素通过特定的规则进行修改,然后映射为另一个对象:

<R>Stream<R> map(Function<? super T, ? extends R>mapper);

该方法接受一个Function函数式接口参数(可以是一个Lambda表达式 or 方法引用)作为映射条件:

package c6p15_Stream;

import java.util.stream.Stream;

public class Example35_map {
    public static void main(String[] args) {
        //1.通过字符串源数据创建一个Stream流对象
        Stream<String> stream = Stream.of("a1", "a2", "b1", "c2", "c1");
        //2.使用map映射实现对将流对象中的元素通过特定规则进行修改
        stream.filter(s -> s.startsWith("c")).map(String::toUpperCase).sorted().forEach(System.out::println);
    }
}

在这里插入图片描述

(4)limit截取

Stream流对象的limit(n)方法用于对流对象中的元素进行截取,多数情况下会配合skip(n)方法使用:

package c6p15_Stream;

import java.util.stream.Stream;

public class Example36_limit {
    public static void main(String[] args) {
        //1.通过字符串源数据创建了一个Stream流对象
        Stream<String> stream = Stream.of("Jack", "Tom", "Tim", "John");
        //2.通过limit()与skip()方法实现流对象元素截取
        stream.skip(1).limit(2).forEach(System.out::println);
    }
}

在这里插入图片描述

(5)collect收集

在之前集合案例中都是使用forEach()方法对流对象进行终结操作的(显式的查看聚合操作后元素的信息),

在某些时候并不可取(无法将操作后的流元素转换为作为熟悉的对象 or 数据类型保存),对此JDK8提供了一个重要的终结操作collect

collect是一种十分有效的终结操作,可以将Stream流中的元素保存为另外一种形式,如集合、字符串等:

<R, A>R collect(Collector<? super T, A, R>collector);

collect()方法使用Collector作为参数,Collector包含四种不同的操作:supplier、accumulator、combiner、finisher

package c6p15_Stream;

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Example37_collect {
    public static void main(String[] args) {
        //1.通过字符串源数据创建了一个Stream流对象
        Stream<String> stream1 = Stream.of("Jack", "Tom", "Tim", "John", "Tony", "Mike");
        //2.通过filter()方法筛选出字符串中以J开头的元素,然后通过collect()方法进行终结操作收集到一个List集合中
        List<String> list = stream1.filter(i -> i.startsWith("J")).collect(Collectors.toList());
        System.out.println(list);
        //3.通过字符串源数据创建了一个Stream流对象
        Stream<String> stream2 = Stream.of("Jack", "Tom", "Tim", "John", "Tony", "Mike");
        //4.通过collect()方法进行终结操作,将流元素使用and连接收集到一个字符串中
        String str = stream2.filter(i -> i.startsWith("T")).collect(Collectors.joining(" and "));
        System.out.println(str);
    }
}

在这里插入图片描述

4.Parallel Stream并行流

并行流是指将源数据分为多个子流对象进行多线程操作(多个管道),然后将处理的结果再汇总为一个流对象,

Stream并行流底层会将源数据拆解为多个流对象,在多个线程中并行执行(这依赖于JDK7中新增的fork/join框架):

注意:使用Stream并行流在一定程度上可以提升程序的执行效率,但在多线程执行时可能会出现线程安全问题

在JDK8中提供了两种方式创建Stream流对象:

(1)Stream并行流的创建:

在这里插入图片描述

parallelStream()方法:通过Collection集合接口的parallelStream()方法直接将集合类型的源数据转变为Stream并行流

parallel()方法:通过BaseStream接口的parallel()方法将Stream串行流转变为Stream并行流

补充:另外在BaseStream接口中还提供了一个isParallel()方法判定是否是并行流

(2)Stream并行流的基本使用:

注意:不论是串行流还是并行流都属于Stream流对象,都拥有相同的流操作方法

package c6p15_Stream;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class Example38 {
    public static void main(String[] args) {
        //创建一个List集合数据源
        List<String> list = Arrays.asList("Jack1", "Tome1", "John1", "Wreck1");
        //1.直接使用Collection接口的parallelStream()创建并行流
        Stream<String> parallelStream1 = list.parallelStream();
        System.out.println(parallelStream1.isParallel());

        //2.使用parallel()将串行流转化为并行流
        //(1)首先创建一个串行流
        Stream<String> stream = Stream.of("Jack2", "Tome2", "John2", "Wreck2");
        //(2)将串行流转化为并行流
        Stream<String> parallelStream2 = stream.parallel();
        System.out.println(parallelStream2.isParallel());
    }
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值