【Java学习总结】chapter06:集合

2023-10-07 09:59

本文主要是介绍【Java学习总结】chapter06:集合,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

第6章:集合


在这里插入图片描述

本章学习目标:

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

OVERVIEW

  • 第6章:集合
      • 一、Collection接口
      • 二、List接口
        • 1.List接口概述
        • 2.ArrayList集合
        • 3.LinkedList集合
      • 三、Set接口
        • 1.Set接口概述
        • 2.HashSet集合
          • (1)HashSet的基本使用:
          • (2)HashSet对象的存储过程:
          • (3)HashSet存储自定义类型对象:
          • (4)HashSet对象保证元素唯一性码源分析:
        • 3.TreeSet集合
          • (1)TreeSet的基本使用:
          • (2)TreeSet存储自定义类型排序:
            • case1:自然排序
            • case2:定制排序
      • 四、Collection集合遍历
        • 1.Iterator遍历集合
          • (1)Iterator的基本使用:
          • (2)Iterator的局限性:
        • 2.foreach遍历集合
          • (1)foreach的基本使用:
          • (2)foreach的局限性:
        • 3.JDK8的forEach遍历集合
          • (1)forEach(Consumer action)
          • (2)forEachRemaining(Consumer action)
      • 五、Map接口
        • 1.Map接口概述
        • 2.HashMap集合
          • (1)HashMap集合内部结构及存储原理:
          • (2)HashMap的基本使用:
          • (3)HashMap的性能分析:
          • (4)LinkedHashMap集合实现元素顺序添加:
        • 3.TreeMap集合
          • (1)TreeMap的基本使用:
          • (2)TreeMap存储自定义类型排序:
        • 4.Map集合遍历
          • (1)Iterator迭代器遍历Map集合:
          • (2)forEach(BiConsumer action)方法遍历Map集合:
          • (3)values()方法遍历Map集合:
        • 5.Properties集合
      • 六、泛型
        • 1.泛型概述
          • (1)问题引入:集合元素类型强制转换报错
          • (2)解决:使用泛型指定集合存储类型
        • 2.泛型类
          • (1)泛型类定义:
          • (2)泛型类的使用:
        • 3.泛型方法:
          • (1)泛型方法定义:
          • (2)泛型方法的使用:
        • 4.泛型接口
          • (1)泛型接口定义:
          • (2)泛型接口的使用:
        • 5.类型通配符
          • (1)类型通配符:`<?>`
          • (2)类型通配符上限:`<? extends 类型>`
          • (3)类型通配符下限:`<? super 类型>`
      • 七、常用工具类(记录)
        • 1.Collections工具类
          • (1)添加&排序操作:
          • (2)查找&替换操作:
        • 2.Arrays工具类
          • (1)使用Sort()方法排序:
          • (2)使用binarySearch(Object[] a, Object key)方法查找元素:
          • (3)使用copyOfRange(int[] original, int from, int to)方法拷贝元素:
          • (4)使用fill(Object[] a, Object val)方法替换元素:
      • 八、聚合操作
        • 1.聚合操作概述
        • 2.创建Stream流对象
        • 3.Stream流的常用方法
        • 4.Parallel Stream并行流
          • (1)Stream并行流的创建:
          • (2)Stream并行流的基本使用:

为了存储数目不确定&任意类型的对象,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:判断比较对象是否相等(地址是否相同),如果是直接返回truereturn true;}if(!(obj instanceof Student2)) {	//判定2:判断对象是否为Student2类型,如果不是直接返回falsereturn 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 1sttreeifyBin(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 keyV 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再比较名称namepublic 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集合所有值的对象Vvalues.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.propertiesproperties.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.propertiesproperties.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> {@Overridepublic 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());}
}

在这里插入图片描述

这篇关于【Java学习总结】chapter06:集合的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/157596

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06