Collections工具类和集合的流式编程

Posted dch-21

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Collections工具类和集合的流式编程相关的知识,希望对你有一定的参考价值。

Collections工具类

针对集合操作的工具类.里面定义的都是静态方法。

Collections和Collection有什么区别?

Collection是集合框架中的一个顶层接口,它里面定义了单列集合的共性方法。它有两个常用的子接口.

  • List:对元素都有定义索引。有序的。可以重复元素。
  • Set:不可以重复元素。无序。
    Collections是集合框架中的一个工具类。该类中的方法都是静态的提供的方法中有可以对list集合进行排序,二分查找等方法。>通常常用的集合都是线程不安全的。因为要提高效率。
    如果多线程操作这些集合时,可以通过该工具类中的同步方法,将线程不安全的集合,转换成安全的。

Collections中的方法

返回值 方法 描述
static <T> boolean addAll(Collection<? super T> c, T... elements) 批量的向一个集合中添加数据。
static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) 获取集合中最大的元素。
static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp) 获取集合中最大的元素。
static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll) 获取集合中最小的元素。
static <T> T min(Collection<? extends T> coll, Comparator<? super T> comp) 获取集合中最小的元素。
static void shuffle(List<?> list) 将集合中的元素随机排列。
static void swap(List<?> list, int i, int j) 交换集合中两个元素。
static void reverse(List<?> list) 将集合中的元素倒序。
static <T extends Comparable<? super T>> void sort(List<T> list) 将集合中的元素升序排序。
static <T> void sort(List<T> list, Comparator<? super T> c) 将集合中的元素升序排序。
static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) 使用二分查找法查询元素下标。
static <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> c) 使用二分查找法查询元素下标。
static <T> void copy(List<? super T> dest, List<? extends T> src) 将src集合中的元素拷贝到dest中。
static <T> void fill(List<? super T> list, T obj) 使用指定的值填充集合。
static <T> Collection<T> synchronizedCollection(Collection<T> c) 获取一个线程安全的集合。
static <T> Set<T> synchronizedSet(Set<T> s) 获取一个线程安全的集合。
static <T> List<T> synchronizedList(List<T> list) 获取一个线程安全的集合。
static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) 获取一个线程安全的集合。

集合的流式编程

简介

Stream,是Java8的新特性。 也是Java8新特性中最值得学习的两个新特性之一。 (另外一个是 lambda 表达式)
Stream,是对集合操作的增强。 流不是集合中的元素, 也不是一种数据结构, 本身不负责数据的存储。 流更像是一个迭代器, 可以单向的遍历一个集合中的每一个元素, 并且不可循环。
在程序中,可以使用Stream类来描述流。 在这个类中,有若干方法,对流中的元素进行各种处理、操作。有些操作是可以得到流对象本身,此时我们可以继续使用这个流对象进行其他处理。

为什么要使用集合的流式编程

  • 有些时候, 对集合中的元素进行操作的时候,需要使用到其他操作的结果。在这个过程中,集合的流式编程可以大幅度的简化代码量。将数据源中的数据,读取到一个流中, 可以对这个流中的数据进行各种操作(删除、 过滤、 映射...), 每次的操作结果也是一个流对象, 可以在使用这个流对象, 进行其他的数据处理。

使用集合流式编程的步骤

  • 通常情况下, 对集合中的数据使用流式编程进行处理, 需要经过以下三步:
  1. 获取数据源, 将数据源中的数据读取到流中。
  2. 对流中的数据进行各种各样的处理。
  3. 对流中的数据进行整合处理。
    在步骤2中,有若干方法,可以对流中的数据进行各种处理。这些方法,都会返回流对象本身,我们可以继续使用这个流对象进行其他的数据处理。这样的操作,被称为中间操作
    在步骤3中,有若干方法,可以将流中的数据做聚合、分析,并关闭流。此时将无法再对流中的数据进行其他的处理。这样的操作,被称为是最终操作
    在中间操作和最终操作中,基本上所有的方法参数都是函数式接口。使用集合的流式编程,来简化代码量,是需要对lambda表达式做到熟练掌握的。

数据源的获取

数据源,顾名思义,就是流中的数据的来源。是集合流式编程的第一步,将数据源中的数据读取到流中,进行处理。数据源,大部分情况下都是来自于Collection集合。 少部分情况下,可以来自于数组。
注意事项:
集合流式编程第一步,将数据源中的数据读取到流中。此时,对流中的数据进行处理,与数据源中的数据没有关系。也就是说,所有的中间操作,都可以对流中的数据进行一些处理,这些处理不会影响到数据源中的数据。其实,使用集合流式编程,更多的做的是查询操作,并不会修改集合中的数据。

将数据源中的数据读取到流中

这个是集合流式编程的第一步,将一个容器中的数据,读取到一个流中。无论是什么容器作为数据源,读取到流中的方法返回值一定是一个Stream

import java.util.*;

import java.util.stream.IntStream;
import java.util.stream.Stream;


public class Test {
    public static void main(String[] args) {
        //1、Collection集合作为数据源
        Collection<Integer> collection=new ArrayList<>();
        Collections.addAll(collection,1,2,3,4,5,6,7,8,9,0);
        //2、将Collection作为数据源读入到流中
        Stream<Integer> stream1=collection.stream();//获取的是串行流
        Stream<Integer> stream2 = collection.parallelStream();//获取的是并行流

        //1、数组作为数据源
        Integer[]array=new Integer[]{1,2,3,4,5,6,7,8,9,0};
        //2、将数组作为数据源读入流中
        Stream<Integer> stream3=Arrays.stream(array);int[] array1 = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
        // 截取数组中的一部分数据,读取到流中
        IntStream stream4 = Arrays.stream(array1, 3, 8);
        

    }
}

最终操作

最终操作的简介

最终操作,可以将流中的数据聚合起来,可以存入一个集合,也可以直接对流中的数据进行遍历、数据统计、分析... 。可以通过最终操作,可以从流中的数据中得到我们想要的信息。
注意事项
最终操作,之所以叫做最终操作,并不单单因为这些方法没有返回流对象本身。还有一个最重要的原因是,所有的最终操作,都会将一个流关闭。如果一个流被关闭了,流中的所有的数据都会被销毁。如果使用了一个已经被关闭了流,会出现 java.lang.IllegalStateException异常的。

collect

将流中的数据聚合到一起,对这些数据进行一些处理.最常见的处理,就是将流中的数据,存入一个集合中。

在这个方法中,参数是一个Collector。Collector是一个接口,而且这个接口还不是一个函数式接口。实现这个接口,可以自定义数据收集的规则。但是绝大部分情况下,我们都不要手动实现这个接口。直接使用 Collectors工具类即可。

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


import java.util.*;

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


public class Test {
    public static void main(String[] args) {
        //1、Collection集合作为数据源
        Collection<Integer> collection=new ArrayList<>();
        Collections.addAll(collection,1,2,3,4,5,6,7,8,9,0);
        //2、将Collection作为数据源读入到流中
        Stream<Integer> stream1=collection.stream();
        //List<Integer>list=stream1.collect(Collectors.toList());
        //list.forEach(System.out::println);
        
//        Set<Integer>set=stream1.collect(Collectors.toSet());
//        set.forEach(System.out::println);
        Map<String,String> map= stream1.collect(Collectors.toMap(e->"key"+e,e->"value"+e));
        map.forEach((k,v)->{
            System.out.println("key = " + k + ", value = " + v);
        });
    }
}

reduce

将流中的数据,按照一定的规则进行计算,处理。
reduce方法得到的结果是Optional,表示可空数据。如果要获取实际的数据,需要用get方法获取。

import java.util.*;

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


public class Test {
    public static void main(String[] args) {
        //1、Collection集合作为数据源
        Collection<Integer> collection=new ArrayList<>();
        Collections.addAll(collection,1,2,3,4,5,6,7,8,9,0);
        //2、将Collection作为数据源读入到流中
        Stream<Integer> stream1=collection.stream();
       //reduce方法得到的结果是Optional,表示可空数据。如果要获取实际的数据,需要用get方法获取。
        //stream1.reduce((a,b)->a+b);
        Optional<Integer> reduce = stream1.reduce(Integer::sum);
        Integer sum=reduce.get();
        System.out.println(sum);
    }
}

count

用来统计流中有多少数据。

import java.util.*;

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


public class Test {
    public static void main(String[] args) {
        //1、Collection集合作为数据源
        Collection<Integer> collection=new ArrayList<>();
        Collections.addAll(collection,1,2,3,4,5,6,7,8,9,0);
        //2、将Collection作为数据源读入到流中
        Stream<Integer> stream1=collection.stream();
        //注意返回值类型
       long count=stream1.count();
        System.out.println(count);
    }
}

forEach

用来迭代、遍历流中的数据.方法的参数是一个函数式接口,Consumer。将流中的数据依次带入到Consumer接口的方法的参数中,在方法体部分实现对流中的数据的迭代、遍历。类似于Collection接口中的forEach方法。

max & min

这是两个最终操作的方法,分别用来获取流中数据的最大值和最小值。max、min方法的参数,是Comparator接口。

import java.util.*;

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


public class Test {
    public static void main(String[] args) {
        //1、Collection集合作为数据源
        Collection<Integer> collection=new ArrayList<>();
        Collections.addAll(collection,1,2,3,4,5,6,7,8,9,0);
        //2、将Collection作为数据源读入到流中
        Stream<Integer> stream1=collection.stream();
       int min=stream1.min(Integer::compareTo).get();
       int max=stream1.max(Integer::compareTo).get();
        System.out.println(min+","+max);
    }
}

Matching

Matching操作,不是一个方法,而是若干个用来做匹配的方法的统称。

  • allMatch(Predicate) : 只有当流中,所有的数据都满足参数条件,才会返回true。
  • anyMatch(Predicate) : 只要流中有任意的数据,满足参数条件,都会返回true。
  • noneMatch(Predicate) : 只有当流中所有的数据都不满足参数条件,才会返回true。
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;


public class Test {
    public static void main(String[] args) {
        //1、Collection集合作为数据源
        Collection<Integer> collection=new ArrayList<>();
        Collections.addAll(collection,1,2,3,4,5,6,7,8,9,0);
        //2、将Collection作为数据源读入到流中
        Stream<Integer> stream1=collection.stream();
        // 验证,是否流中所有的数据都满足指定条件
        // boolean ret = stream1.allMatch(ele -> ele > 0);
        // System.out.println(ret);

        // 验证,是否流中有满足条件的数据
        // boolean ret = stream1.anyMatch(ele -> ele > 5);
        // System.out.println(ret);

        // 验证,是否流中所有的数据都不满足指定的条件
        boolean ret = stream1.noneMatch(ele -> ele > 15);
        System.out.println(ret);
    }
}

find

从流中查找一个数据。

  • findFirst(): 从流中获取一个元素。(一般情况下,是获取的开头的元素)
  • findAny() : 从流中获取一个元素。(一般情况下,是获取的开头的元素)

这两个方法,在同步流中,没有区别,获取的都是首元素。但是在并行流中,findAny和find方法返回的结果可能不一样。


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


public class Test {
    public static void main(String[] args) {
        //1、Collection集合作为数据源
        Collection<Integer> collection=new ArrayList<>();
        Collections.addAll(collection,1,2,3,4,5,6,7,8,9,0);
        //2、将Collection作为数据源读入到流中
        Stream<Integer> stream1=collection.stream();
        //findFirst
        System.out.println(collection.stream().findFirst().get());
        System.out.println(collection.parallelStream().findFirst().get());
        System.out.println();
        //findAny
        System.out.println(collection.stream().findAny().get());
        System.out.println(collection.parallelStream().findAny().get());
    }
}

技术图片

IntStream的最终操作

IntStream,也是一个流对象,类似于Stream类,和Stream类有点区别。IntStream,流中的数据,都是int类型的数据。
IntStream,比起Stream来说,方法绝大部分都是完全相同的。除了上述的最终操作之外, 还有额外的几个方法。

import java.util.*;

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


public class Test {
    public static void main(String[] args) {
        // 1. 实例化一个int[],作为数据源
        int[] array = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
        // 2. 获取到一个IntStream
        IntStream stream = Arrays.stream(array);

        // int max = stream.max().getAsInt();                   // 获取最大值
        // int min = stream.min().getAsInt();                   // 获取最小值
        // int sum = stream.sum();                              // 获取流中所有的数据的和
        // long count = stream.count();                         // 获取流中有多少数据
        // double asDouble = stream.average().getAsDouble();    // 计算流中所有的数据的平均值

        // 获取对流中数据的统计分析结果,包含了(最大值、最小值、和、平均值、数量)
        IntSummaryStatistics intSummaryStatistics = stream.summaryStatistics();
        System.out.println(intSummaryStatistics.getMax());      // 获取最大值
        System.out.println(intSummaryStatistics.getMin());      // 获取最小值
        System.out.println(intSummaryStatistics.getSum());      // 获取和
        System.out.println(intSummaryStatistics.getAverage());  // 获取平均值
        System.out.println(intSummaryStatistics.getCount());    // 获取数量
         }
}

最终操作,是会关闭流的。如果使用了一个已经关闭的流,就会出现异常!

Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed

中间操作

将数据从数据源中读取到流中,接下来,对流中的数据进行各种各样的处理。这些处理流中的数据的方法,每一个方法的返回值都是一个流对象本身,可以直接使用这个返回值再调用其他的方法,对数据进行其他的处理。这样的处理,可以一直持续下去,一直到最终操作。

filter

条件过滤,** 保留流中满足条件的数据,删除不满足条件的数据。**

// 将流中所有的年龄大于10岁的数据保留
list.stream().filter(ele -> ele.getAge() > 10).forEach(System.out::println);

distinct

去重,去重流中的重复的元素。这个方法没有参数。去重的规则与HashSet相同。

  • 比较两个对象的hashCode()
  • 如果hashCode相同,再比较equals()

sorted

对流中的数据,按照一定的规则进行大小排序。

  • sorted() : 按照流中的元素对应的类,实现的Comparable接口进行大小比较,升序排序。
  • sorted(Comparator) : 按照参数接口提供的大小比较规则,进行元素的大小比较,升序排序。

limit

限制,截取流中指定数量的元素。从第0位开始截取。

skip

跳过,跳过流中指定数量的元素。

MapToInt

将流中的数据,无论是什么类型的数据,使用一个int数据进行替代。此时方法得到的返回值是 IntStream 类型的。

Map

是一个映射关系,可以使用这个方法,对流中的数据进行映射,用新的数据替换原来的数据。

import java.util.*;

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


public class Test {
    public static void main(String[] args) {
        // 1. 获取数据源
        List<Person> list = getDataSource();
        //注:以下每次调用都使用了最终操作foeEach
        // 2. 中间操作filter
        // list.stream().filter(ele -> ele.getAge() > 10).forEach(System.out::println);

        // 3. 中间操作distinct
        // list.stream().distinct().forEach(System.out::println);

        // 4. 中间操作sorted
        //无参sorted使用类继承Comparable时实现的compareTo方法
        //list.stream().sorted().forEach(System.out::println);
        //有参sorted使用lambda表达式来指定排序方式
        // list.stream().sorted((p1, p2) -> p1.getHeight() - p2.getHeight()).forEach(System.out::println);

        // 5. 中间操作limit
        // list.stream().limit(5).forEach(System.out::println);
        // 需求: 获取身高前5名
        // list.stream().sorted((p1, p2) -> p2.getHeight() - p1.getHeight())
        //              .limit(5)
        //              .forEach(System.out::println);

        // 6. 中间操作skip
        // 需求:截取身高的 [4,7] 名
        // list.stream().sorted((p1, p2) -> p2.getHeight() - p1.getHeight())
        //         .limit(7)
        //         .skip(3)
        //         .forEach(System.out::println);

        // 7. 中间操作map
        // 需求:将一个存储了所有的Person对象的集合,将所有的名字提取出来,单独存放到一个集合中。
        // List<String> names = list.stream().map(Person::getName).collect(Collectors.toList());

        // 8. mapToInt
        // 计算一个存储了所有的Person对象的流中的年龄平均值。
        double asDouble = list.stream().mapToInt(Person::getAge).average().getAsDouble();
        System.out.println(asDouble);

    }

    /**
     * 获取数据源
     * @return 存储了若干个Person对象的数据源
     */
    private static List<Person> getDataSource() {
        List<Person> list = new ArrayList<>();
        Collections.addAll(list,
                new Person("Han Meimei", 11, 165, 45),
                new Person("Li Lei", 12, 168, 51),
                new Person("Lucy", 12, 166, 50),
                new Person("Lily", 12, 166, 49),
                new Person("Polly", 3, 92, 22),
                new Person("Tom", 13, 162, 52),
                new Person("Jerry", 2, 88, 18),
                new Person("Lin Tao", 11, 162, 53),
                new Person("Jim", 14, 167, 51),
                new Person("Jim", 14, 167, 51)
        );
        return list;
    }
}

class Person implements Comparable<Person> {
    private String name;
    private int age;
    private int height;
    private int weight;

    public Person(String name, int age, int height, int weight) {
        this.name = name;
        this.age = age;
        this.height = height;
        this.weight = weight;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age &&
                height == person.height &&
                weight == person.weight &&
                Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, height, weight);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name=‘" + name + ‘‘‘ +
                ", age=" + age +
                ", height=" + height +
                ", weight=" + weight +
                ‘}‘;
    }

    @Override
    public int compareTo(Person o) {
        return this.age - o.age;
    }
}

flatMap

类似于Map,flatMap是一个扁平化映射。

public class FMap {
    public static void main(String[] args) {
        String[] strs = {"hello", "world"};

        // 需求:输出每一个组成字符串的字符
        // 直接映射,将流中的元素(字符串),替换成了toCharArray之后的结果(字符数组)
        // 映射完成后,流中的数据依然是两个,分别是两个字符数组
        Arrays.stream(strs).map(String::toCharArray).forEach(ele -> System.out.println(Arrays.toString(ele)));

        // 常用于map直接映射完成后,流中的数据是一个个的容器,而我们需要对容器中的数据进行处理。
        // 此时,可以使用扁平化的映射,将容器中的元素直接存储于一个流中。
        Arrays.stream(strs).map(str -> str.split(""))
                .flatMap(Arrays::stream)
                .distinct()
                .forEach(System.out::println);
    }
}

以上是关于Collections工具类和集合的流式编程的主要内容,如果未能解决你的问题,请参考以下文章

Collections 工具类和 Arrays 工具类常见方法

Map集合HashMap集合LinkedHashMap集合Hashtable集合Collections工具类和模拟斗地主洗牌和发牌

Java并发编程学习6-同步工具类和并发容器

C#集合

操作系统的工具类Collections

Java多线程与并发库高级应用-同步集合