本文主要是介绍【Java学习总结】chapter06:集合,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
第6章:集合
本章学习目标:
- 掌握List集合、Set集合、Map集合的使用
- 掌握集合遍历方法的使用
- 熟悉泛型的使用
- 掌握Collections、Arrays工具类的使用
- 掌握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中定义了单列集合List
与Set
的一些通用的方法可用于操作所有的单列集合:
注意:关于stream方法是JDK8增加的,用于对集合元素进行聚合操作,在文章结尾将详细讲解。
二、List接口
1.List接口概述
List
接口继承自Collection接口,是单列集合的一个重要分支,List集合即为实现了List接口的对象。
List集合的特点:
- List集合中允许出现重复的元素,即元素是一种线性的方式进行存储的(通过索引访问),与数组类似。
- 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类型的first
和last
属性去维护这个双向列表,从而将所有元素串联起来。
注意:当插入 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集合的特点:
- Set接口中的元素不重复(以某种规则保证存入的元素不重复)。
- 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));}
}
结果分析:从输出的结果可以看出
- 取出的元素顺序与添加元素的顺序并不一致(存储的无序性)
- 重复存入的字符串元素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()
方法判定两个对象是否相同的步骤
- 首先判定两个对象的地址是否相等
- 其次判定两个对象的类型是否相等
- 最后判定两个对象的内容是否相等(例为根据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集合的特点:
- HashMap集合允许键、值为空,但不允许键的重复。
- HashMap集合中的元素是无序的。
(1)HashMap集合内部结构及存储原理:
HashMap底层是由哈希表组成的,即数组+链表的组合体(数组是HashMap的主体结构,链表是为了解决哈希值冲突的分支结构)。
注意:HashMap集合内部结构为数组+链表,故其增删改查的效率都比较高
水平方向以数组结构为主体,在竖直方向以链表结构进行结合的就是HashMap中的哈希表结构。
在哈希表结构中,水平方向数组的长度称为HashMap集合的容量(capacity)、竖直方向元素位置对应的链表结构称为桶(bucket)。
当向HashMap集合添加元素时:
- 首先会调用插入键值队中键对象k的
hash(k)
方法—>快速定位并寻址到该元素在集合中要存储的位置(桶的位置) - 当被定位的桶的位置为null时,则可以直接向该桶位置插入元素对象;若桶的位置不为空时,需要调用键对象的
equals(k)
方法 - 如果新插入的键对象与已存在元素的键对象相同,则替换原有相同的对象;若没有相同的时,会在桶的链表结构头部新增一个节点用于插入元素。(保证哈希值不冲突)
(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集合的特点:
- HashMap集合允许键、值为空,但不允许键的重复(与TreeSet相似通过二叉树原理保证元素唯一性)。
- 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()
case1:keySet()
方法
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);}}
}
case2:entrySet()
方法
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操作");}
}
总结:
- 输入重复的key键则其对应的value值将被覆盖(author先后被覆盖为lch)
- 存入的键值对是无序的(窗口输出的顺序与配置写入的先后不同)
六、泛型
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);}}
}
总结:泛型的作用
- 使用泛型在编译时期就会将提示报错,明确
Integer
对象无法存入集合中- 在使用了泛型限定集合存储类型后,在使用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集合中角标i 与j 处元素进行交换 |
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表达式的优势进一步简化上述过程(聚合操作)。
使用聚合操作的流程如下:
- 将原始集合 or 数组对象转换为Stream流对象
- 对Stream流对象中的元素进行一系列的过滤、查找等中间操作(Intermediate Operations),然后返回一个Stream流对象
- 对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表达式完成了遍历
补充:
- JDK8中只针对单列结合Collections接口对象提供了
stream()
静态方法获取Stream流对象,- 而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:集合的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!