java8新特性--集合、Optional 类

Lambda表达式----集合

一、集合

例一:主要操作集合  

传统的循环:

java8流式处理的时候,我的第一感觉是流式处理让集合操作变得简洁了许多,通常我们需要多行代码才能完成的操作,借助于流式处理可以在一行中实现。比如我们希望对一个包含整数的集合中筛选出所有的偶数,并将其封装成为一个新的List返回。

实际上不光是集合,包括数组、文件等,只要是可以转换成流,我们都可以借助流式处理,类似于我们写SQL语句一样对其进行操作。java8通过内部迭代来实现对流的处理,一个流式处理可以分为三个部分:转换成流、中间操作、终端操作。如下图:

26192908_IwPY.png

   
//删除其中某一个      
          Iterator<Lecture> lectureIterator = lectures.iterator();
            while (lectureIterator.hasNext()) {
                Lecture lecture = lectureIterator.next();
                if (lectureId.equals(lecture.getLectureId())) {
                    lectureIterator.remove();
                }
            }
     //简化:
          lectures.removeIf(lecture -> lectureId.equals(lecture.getLectureId()));  

//保留交集
strings.retainAll(strings2);

//去除交集
       Set<String> strings = new HashSet<>();
        strings.add("11");
        strings.add("22");
        strings.add("33");
        Set<String> strings2=new HashSet<>();
        strings2.add("11");

        strings.removeAll(strings2); //重要

        strings.forEach(s1 -> System.out.println(s1.toString()));

//是否包含
System.out.println(strings.contains("11"));  //true  false


//按条件过滤
strings= strings.stream().filter(s3 -> s3.length()>2).collect(Collectors.toSet()) ;


//对列表的每个元素应用函数
       // 将字符串换成大写并用逗号链接起来
List<String> G7 = Arrays.asList("USA", "Japan", "France", "Germany", "Italy", "U.K.","Canada");
String G7Countries = G7.stream().map(x -> x.toUpperCase()).collect(Collectors.joining(", "));
System.out.println(G7Countries); 
//USA, JAPAN, FRANCE, GERMANY, ITALY, U.K., CANADA


//去重复
    List<Integer> numbers = Arrays.asList(9, 10, 3, 4, 7, 3, 4);
        List<Integer> distinct = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
        System.out.printf("Original List : %s,  Square Without duplicates : %s %n", numbers, distinct);
 //Original List : [9, 10, 3, 4, 7, 3, 4],  Square Without duplicates : [81, 100, 9, 16, 49]


//计算集合元素的最大值、最小值、总和以及平均值
//获取数字的个数、最小值、最大值、总和以及平均值
List<Integer> primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29);
IntSummaryStatistics stats = primes.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("Highest prime number in List : " + stats.getMax());
System.out.println("Lowest prime number in List : " + stats.getMin());
System.out.println("Sum of all prime numbers : " + stats.getSum());
System.out.println("Average of all prime numbers : " + stats.getAverage());
/*
Highest prime number in List : 29
Lowest prime number in List : 2
Sum of all prime numbers : 129
Average of all prime numbers : 12.9
*/

//复制集合
usersCopy.clear();
usersCopy.addAll(users);

//排序
List<Integer> dd=numbers.stream().sorted((s1,s2) -> s1-s2).collect(Collectors.toList());

通过Alt + Enter

简化:

lectures.removeIf(lecture -> lectureId.equals(lecture.getLectureId()));

 

例二: 循环

// Java 8之前:
List features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API");
for (String feature : features) {
    System.out.println(feature);
}

// Java 8之后:
List features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API");
features.forEach(n -> System.out.println(n));

 

例三  Map和Reduce示例

List<Integer> costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
        //传统
        for (Integer cost : costBeforeTax) {
            double price = cost + .12*cost;
            System.out.println(price);
        }
        // 使用lambda表达式
        costBeforeTax.stream().map((cost) -> cost + .12 * cost).forEach(c -> System.out.println(c));
        costBeforeTax.stream().map((cost) -> cost + .12 * cost).forEach(System.out::println);  //等价于上面的

 

2.1 过滤

过滤,顾名思义就是按照给定的要求对集合进行筛选满足条件的元素,java8提供的筛选操作包括:filter、distinct、limit、skip。

filter
在前面的例子中我们已经演示了如何使用filter,其定义为:Stream<T> filter(Predicate<? super T> predicate),filter接受一个谓词Predicate,我们可以通过这个谓词定义筛选条件,在介绍lambda表达式时我们介绍过Predicate是一个函数式接口,其包含一个test(T t)方法,该方法返回boolean。现在我们希望从集合students中筛选出所有武汉大学的学生,那么我们可以通过filter来实现,并将筛选操作作为参数传递给filter:

 List<Student> whuStudents = students.stream()
                .filter(student -> "武汉大学".equals(student.getSchool()))
                .collect(Collectors.toList());

 

distinct
distinct操作类似于我们在写SQL语句时,添加的DISTINCT关键字,用于去重处理,distinct基于Object.equals(Object)实现,回到最开始的例子,假设我们希望筛选出所有不重复的偶数,那么可以添加distinct操作:

List<Integer> evens = nums.stream()
                        .filter(num -> num % 2 == 0).distinct()
                        .collect(Collectors.toList());

 

limit
limit操作也类似于SQL语句中的LIMIT关键字,不过相对功能较弱,limit返回包含前n个元素的流,当集合大小小于n时,则返回实际长度,比如下面的例子返回前两个专业为土木工程专业的学生:

说到limit,不得不提及一下另外一个流操作:sorted。该操作用于对流中元素进行排序,sorted要求待比较的元素必须实现Comparable接口,如果没有实现也不要紧,我们可以将比较器作为参数传递给sorted(Comparator<? super T> comparator),比如我们希望筛选出专业为土木工程的学生,并按年龄从小到大排序,筛选出年龄最小的两个学生,那么可以实现为:

List<Student> sortedCivilStudents = students.stream()
                 .filter(student -> "土木工程".equals(student.getMajor())).sorted((s1, s2) -> s1.getAge() - s2.getAge())
                 .limit(2)
                 .collect(Collectors.toList());

 

skip
skip操作与limit操作相反,如同其字面意思一样,是跳过前n个元素,比如我们希望找出排序在2之后的土木工程专业的学生,那么可以实现为:

List<Student> civilStudents = students.stream()
                 .filter(student -> "土木工程".equals(student.getMajor()))
                 .skip(2)
                 .collect(Collectors.toList());

通过skip,就会跳过前面两个元素,返回由后面所有元素构造的流,如果n大于满足条件的集合的长度,则会返回一个空的集合。

2.2 映射

在SQL中,借助SELECT关键字后面添加需要的字段名称,可以仅输出我们需要的字段数据,而流式处理的映射操作也是实现这一目的,在java8的流式处理中,主要包含两类映射操作:map和flatMap。

map
举例说明,假设我们希望筛选出所有专业为计算机科学的学生姓名,那么我们可以在filter筛选的基础之上,通过map将学生实体映射成为学生姓名字符串,具体实现如下:

List<String> names = students.stream()
                   .filter(student -> "计算机科学".equals(student.getMajor()))
                   .map(Student::getName).collect(Collectors.toList());

除了上面这类基础的map,java8还提供了mapToDouble(ToDoubleFunction<? super T> mapper)mapToInt(ToIntFunction<? super T> mapper)mapToLong(ToLongFunction<? super T> mapper),这些映射分别返回对应类型的流,java8为这些流设定了一些特殊的操作,比如我们希望计算所有专业为计算机科学学生的年龄之和,那么我们可以实现如下:

int totalAge = students.stream()
                    .filter(student -> "计算机科学".equals(student.getMajor()))
                    .mapToInt(Student::getAge).sum();

通过将Student按照年龄直接映射为IntStream,我们可以直接调用提供的sum()方法来达到目的,此外使用这些数值流的好处还在于可以避免jvm装箱操作所带来的性能消耗。

flatMap
flatMap与map的区别在于 flatMap是将一个流中的每个值都转成一个个流,然后再将这些流扁平化成为一个流 。举例说明,假设我们有一个字符串数组String[] strs = {"java8", "is", "easy", "to", "use"};,我们希望输出构成这一数组的所有非重复字符,那么我们可能首先会想到如下实现:

List<String[]> distinctStrs = Arrays.stream(strs)
                                .map(str -> str.split(""))  // 映射成为Stream<String[]>
                                .distinct()
                                .collect(Collectors.toList());

在执行map操作以后,我们得到是一个包含多个字符串(构成一个字符串的字符数组)的流,此时执行distinct操作是基于在这些字符串数组之间的对比,所以达不到我们希望的目的,此时的输出为:

[j, a, v, a, 8]
[i, s]
[e, a, s, y]
[t, o]
[u, s, e]

distinct只有对于一个包含多个字符的流进行操作才能达到我们的目的,即对Stream<String>进行操作。此时flatMap就可以达到我们的目的:

List<String> distinctStrs = Arrays.stream(strs)
                                .map(str -> str.split(""))  // 映射成为Stream<String[]>
                                .flatMap(Arrays::stream)  // 扁平化为Stream<String>
                                .distinct()
                                .collect(Collectors.toList());

flatMap将由map映射得到的Stream<String[]>,转换成由各个字符串数组映射成的流Stream<String>,再将这些小的流扁平化成为一个由所有字符串构成的大流Steam<String>,从而能够达到我们的目的。
与map类似,flatMap也提供了针对特定类型的映射操作:flatMapToDouble(Function<? super T,? extends DoubleStream> mapper)flatMapToInt(Function<? super T,? extends IntStream> mapper)flatMapToLong(Function<? super T,? extends LongStream> mapper)

二. 终端操作

终端操作是流式处理的最后一步,我们可以在终端操作中实现对流查找、归约等操作。

3.1 查找

allMatch
allMatch用于检测是否全部都满足指定的参数行为,如果全部满足则返回true,例如我们希望检测是否所有的学生都已满18周岁,那么可以实现为:

boolean isAdult = students.stream().allMatch(student -> student.getAge() >= 18);

anyMatch
anyMatch则是检测是否存在一个或多个满足指定的参数行为,如果满足则返回true,例如我们希望检测是否有来自武汉大学的学生,那么可以实现为:

boolean hasWhu = students.stream().anyMatch(student -> "武汉大学".equals(student.getSchool()));

noneMathch
noneMatch用于检测是否不存在满足指定行为的元素,如果不存在则返回true,例如我们希望检测是否不存在专业为计算机科学的学生,可以实现如下:

boolean noneCs = students.stream().noneMatch(student -> "计算机科学".equals(student.getMajor()));

findFirst
findFirst用于返回满足条件的第一个元素,比如我们希望选出专业为土木工程的排在第一个学生,那么可以实现如下:

Optional<Student> optStu = students.stream().filter(student -> "土木工程".equals(student.getMajor())).findFirst();

findFirst不携带参数,具体的查找条件可以通过filter设置,此外我们可以发现findFirst返回的是一个Optional类型,关于该类型的具体讲解可以参考上一篇:Java8新特性 – Optional类

findAny
findAny相对于findFirst的区别在于,findAny不一定返回第一个,而是返回任意一个,比如我们希望返回任意一个专业为土木工程的学生,可以实现如下:

Optional<Student> optStu = students.stream().filter(student -> "土木工程".equals(student.getMajor())).findAny();

实际上对于顺序流式处理而言,findFirst和findAny返回的结果是一样的,至于为什么会这样设计,是因为在下一篇我们介绍的并行流式处理,当我们启用并行流式处理的时候,查找第一个元素往往会有很多限制,如果不是特别需求,在并行流式处理中使用findAny的性能要比findFirst好。

3.2 归约

前面的例子中我们大部分都是通过collect(Collectors.toList())对数据封装返回,如我的目标不是返回一个新的集合,而是希望对经过参数化操作后的集合进行进一步的运算,那么我们可用对集合实施归约操作。java8的流式处理提供了reduce方法来达到这一目的。

前面我们通过mapToInt将Stream<Student>映射成为IntStream,并通过IntStream的sum方法求得所有学生的年龄之和,实际上我们通过归约操作,也可以达到这一目的,实现如下:

// 前面例子中的方法
int totalAge = students.stream()
                .filter(student -> "计算机科学".equals(student.getMajor()))
                .mapToInt(Student::getAge).sum();
// 归约操作
int totalAge = students.stream()
                .filter(student -> "计算机科学".equals(student.getMajor()))
                .map(Student::getAge)
                .reduce(0, (a, b) -> a + b);

// 进一步简化
int totalAge2 = students.stream()
                .filter(student -> "计算机科学".equals(student.getMajor()))
                .map(Student::getAge)
                .reduce(0, Integer::sum);

// 采用无初始值的重载版本,需要注意返回Optional
Optional<Integer> totalAge = students.stream()
                .filter(student -> "计算机科学".equals(student.getMajor()))
                .map(Student::getAge)
                .reduce(Integer::sum);  // 去掉初始值

3.3 收集

前面利用collect(Collectors.toList())是一个简单的收集操作,是对处理结果的封装,对应的还有toSettoMap,以满足我们对于结果组织的需求。这些方法均来自于java.util.stream.Collectors,我们可以称之为收集器。

3.3.1 归约

收集器也提供了相应的归约操作,但是与reduce在内部实现上是有区别的,收集器更加适用于可变容器上的归约操作,这些收集器广义上均基于Collectors.reducing()实现。

例1:求学生的总人数

long count = students.stream().collect(Collectors.counting());

// 进一步简化
long count = students.stream().count();

例2:求年龄的最大值和最小值

// 求最大年龄
Optional<Student> olderStudent = students.stream().collect(Collectors.maxBy((s1, s2) -> s1.getAge() - s2.getAge()));

// 进一步简化
Optional<Student> olderStudent2 = students.stream().collect(Collectors.maxBy(Comparator.comparing(Student::getAge)));

// 求最小年龄
Optional<Student> olderStudent3 = students.stream().collect(Collectors.minBy(Comparator.comparing(Student::getAge)));

例3:求年龄总和

int totalAge4 = students.stream().collect(Collectors.summingInt(Student::getAge));

对应的还有summingLongsummingDouble

例4:求年龄的平均值

double avgAge = students.stream().collect(Collectors.averagingInt(Student::getAge));

对应的还有averagingLongaveragingDouble

例5:一次性得到元素个数、总和、均值、最大值、最小值

IntSummaryStatistics statistics = students.stream().collect(Collectors.summarizingInt(Student::getAge));

//输出:

IntSummaryStatistics{count=10, sum=220, min=20, average=22.000000, max=24}

对应的还有summarizingLongsummarizingDouble

例6:字符串拼接

String names = students.stream().map(Student::getName).collect(Collectors.joining());
// 输出:孔明伯约玄德云长翼德元直奉孝仲谋鲁肃丁奉
String names = students.stream().map(Student::getName).collect(Collectors.joining(", "));
// 输出:孔明, 伯约, 玄德, 云长, 翼德, 元直, 奉孝, 仲谋, 鲁肃, 丁奉

3.3.2 分组

在数据库操作中,我们可以通过GROUP BY关键字对查询到的数据进行分组,java8的流式处理也为我们提供了这样的功能Collectors.groupingBy来操作集合。比如我们可以按学校对上面的学生进行分组:

Map<String, List<Student>> groups = students.stream().collect(Collectors.groupingBy(Student::getSchool));

groupingBy接收一个分类器Function<? super T, ? extends K> classifier,我们可以自定义分类器来实现需要的分类效果。

上面演示的是一级分组,我们还可以定义多个分类器实现 多级分组,比如我们希望在按学校分组的基础之上再按照专业进行分组,实现如下:

Map<String, Map<String, List<Student>>> groups2 = students.stream().collect(
                Collectors.groupingBy(Student::getSchool,  // 一级分组,按学校
                Collectors.groupingBy(Student::getMajor)));  // 二级分组,按专业

实际上在groupingBy的第二个参数不是只能传递groupingBy,还可以传递任意Collector类型,比如我们可以传递一个Collector.counting,用以统计每个组的个数:

Map<String, Long> groups = students.stream().collect(Collectors.groupingBy(Student::getSchool, Collectors.counting()));

如果我们不添加第二个参数,则编译器会默认帮我们添加一个Collectors.toList()

3.3.3 分区

分区可以看做是分组的一种特殊情况,在分区中key只有两种情况:true或false,目的是将待分区集合按照条件一分为二,java8的流式处理利用ollectors.partitioningBy()方法实现分区,该方法接收一个谓词,例如我们希望将学生分为武大学生和非武大学生,那么可以实现如下:

Map<Boolean, List<Student>> partition = students.stream().collect(Collectors.partitioningBy(student -> "武汉大学".equals(student.getSchool())));

分区相对分组的优势在于,我们可以同时得到两类结果,在一些应用场景下可以一步得到我们需要的所有结果,比如将数组分为奇数和偶数。

以上介绍的所有收集器均实现自接口java.util.stream.Collector,该接口的定义如下:

public interface Collector<T, A, R> {
    /**
     * A function that creates and returns a new mutable result container.
     *
     * @return a function which returns a new, mutable result container
     */
    Supplier<A> supplier();

    /**
     * A function that folds a value into a mutable result container.
     *
     * @return a function which folds a value into a mutable result container
     */
    BiConsumer<A, T> accumulator();

    /**
     * A function that accepts two partial results and merges them.  The
     * combiner function may fold state from one argument into the other and
     * return that, or may return a new result container.
     *
     * @return a function which combines two partial results into a combined
     * result
     */
    BinaryOperator<A> combiner();

    /**
     * Perform the final transformation from the intermediate accumulation type
     * {@code A} to the final result type {@code R}.
     *
     * <p>If the characteristic {@code IDENTITY_TRANSFORM} is
     * set, this function may be presumed to be an identity transform with an
     * unchecked cast from {@code A} to {@code R}.
     *
     * @return a function which transforms the intermediate result to the final
     * result
     */
    Function<A, R> finisher();

    /**
     * Returns a {@code Set} of {@code Collector.Characteristics} indicating
     * the characteristics of this Collector.  This set should be immutable.
     *
     * @return an immutable set of collector characteristics
     */
    Set<Characteristics> characteristics();

}

我们也可以实现该接口来定义自己的收集器,此处不再展开。

三、Multimap--for循环嵌套

   在开发当中难免list存在嵌套的情况,通常的实现方式是使用两个for循环嵌套,这样使得执行步骤增加,大大降低了系统的性能。

引入pom.xml

<!-- https://mvnrepository.com/artifact/com.google.guava/guava-collections -->
		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava-collections</artifactId>
			<version>r03</version>
		</dependency>

 

实例:

import java.util.Collection;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;

public class MutliMapTest {
	public static void main(String... args) {
		Multimap<String, String> myMultimap = ArrayListMultimap.create();

		// Adding some key/value
		myMultimap.put("Fruits", "Bannana");
		myMultimap.put("Fruits", "Apple");
		myMultimap.put("Fruits", "Pear");
		myMultimap.put("Fruits", "Pear");
		myMultimap.put("Vegetables", "Carrot");

		// Getting the size
		int size = myMultimap.size();
		System.out.println(size); // 5

		// Getting values
		Collection<String> fruits = myMultimap.get("Fruits");
		System.out.println(fruits); //  [Bannana, Apple, Pear, Pear]
		System.out.println(ImmutableSet.copyOf(fruits));// [Bannana, Apple, Pear]
		// Set<Foo> set = Sets.newHashSet(list);
		// Set<Foo> foo = new HashSet<Foo>(myList);

		Collection<String> vegetables = myMultimap.get("Vegetables");
		System.out.println(vegetables); // [Carrot]

		// Iterating over entire Mutlimap
		for (String value : myMultimap.values()) {
			System.out.println(value);
		}

		// Removing a single value
		myMultimap.remove("Fruits", "Pear");
		System.out.println(myMultimap.get("Fruits")); // [Bannana, Apple, Pear]

		// Remove all values for a key
		myMultimap.removeAll("Fruits");
		System.out.println(myMultimap.get("Fruits")); // [] (Empty Collection!)
	}
}

这里有一点你可能会疑惑,就是为何get方法返回的是一个collection而不是list,这是因为前者会更加有用。如果你需要基于multimap直接操作list或者set,那么可以使用在定义类型的时候使用子类名称:ListMultimap,SetMultimap和SortedSetMultimap。例如: 

ListMutlimap<String,String> myMutlimap = ArrayListMultimap.create();

List<string> myValues = myMutlimap.get("myKey");  // Returns a List, not a Collection.

自己写了一个demo

List<User> list =new ArrayList<>;
  Multimap<String, User> multimap = ArrayListMultimap.create();
        if (CollectionUtils.isNotEmpty(list)) {
            for (User user : list){
                multimap.put(user.getQuestionId(),user);
            }
        }
        for (Topic topic :topics){
            List<User> user=(List<User>) multimap.get(topic.getTopicId());
            if (CollectionUtils.isNotEmpty(user)){
                topic.setLectureState(user.get(0).getExistsType());
            }else {
                topic.setLectureState(2);  //当题库未给出时  默认为无微课
            }

 

四、 并行流式数据处理

流式处理中的很多都适合采用 分而治之 的思想,从而在处理集合较大时,极大的提高代码的性能,java8的设计者也看到了这一点,所以提供了 并行流式处理。上面的例子中我们都是调用stream()方法来启动流式处理,java8还提供了parallelStream()来启动并行流式处理,parallelStream()本质上基于java7的Fork-Join框架实现,其默认的线程数为宿主机的内核数。

启动并行流式处理虽然简单,只需要将stream()替换成parallelStream()即可,但既然是并行,就会涉及到多线程安全问题,所以在启用之前要先确认并行是否值得(并行的效率不一定高于顺序执行),另外就是要保证线程安全。此两项无法保证,那么并行毫无意义,毕竟结果比速度更加重要,以后有时间再来详细分析一下并行流式数据处理的具体实现和最佳实践。

Optional 类

Optional不是对null关键字的一种替代,而是对于null判定提供了一种更加优雅的实现。

NullPointException可以说是所有Java程序员都遇到过的一个异常,虽然java从设计之初就力图让程序员脱离指针的苦海,但是指针确实是实际存在的,而java设计者也只能是让指针在java语言中变得更加简单、易用,而不能完全的将其剔除,所以才有了我们日常所见到的关键字null。

空指针异常是一个运行时异常,对于这一类异常,如果没有明确的处理策略,那么最佳实践在于让程序早点挂掉,但是很多场景下,不是开发人员没有具体的处理策略,而是根本没有意识到空指针异常的存在。当异常真的发生的时候,处理策略也很简单,在存在异常的地方添加一个if语句判定即可,但是这样的应对策略会让我们的程序出现越来越多的null判定,我们知道一个良好的程序设计,应该让代码中尽量少出现null关键字,而java8所提供的Optional类则在减少NullPointException的同时,也提升了代码的美观度。但首先我们需要明确的是,它并 不是对null关键字的一种替代,而是对于null判定提供了一种更加优雅的实现,从而避免NullPointException。

一. 直观感受

假设我们需要返回一个字符串的长度,如果不借助第三方工具类,我们需要调用str.length()方法:

if(null == str) { // 空指针判定
    return 0;
}
return str.length();


//如果采用Optional类,实现如下:

return Optional.ofNullable(str).map(String::length).orElse(0);

Optional的代码相对更加简洁,当代码量较大时,我们很容易忘记进行null判定,但是使用Optional类则会避免这类问题。

二. 基本使用

1.对象创建

创建空对象

Optional<String> optStr = Optional.empty();

上面的示例代码调用empty()方法创建了一个空的Optional<String>对象型。

创建对象:不允许为空 
Optional提供了方法of()用于创建非空对象,该方法要求传入的参数不能为空,否则抛NullPointException,示例如下:

Optional<String> optStr = Optional.of(str);  // 当str为null的时候,将抛出NullPointException

创建对象:允许为空 
如果不能确定传入的参数是否存在null值的可能性,则可以用Optional的ofNullable()方法创建对象,如果入参为null,则创建一个空对象。示例如下:

Optional<String> optStr = Optional.ofNullable(str);  // 如果str是null,则创建一个空对象

2.流式处理

流式处理也是java8给我们带来的一个重量级新特性,让我们对集合的操作变得更加简洁和高效,后面的文章将对流失处理进行全面的讲解。这里Optional也提供了两个基本的流失处理:映射和过滤

对于如下一个User类:

public class User {

    /** 用户编号 */
    private long id;

    private String name;

    private int age;

    private Optional<Long> phone;

    private Optional<String> email;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 省略setter和getter
}

手机和邮箱不是一个人的必须有的,所以我们利用Optional定义。

映射:map与flatMap 
映射是将输入转换成另外一种形式的输出的操作,比如前面例子中,我们输入字符串,而输出的是字符串的长度,这就是一种隐射,我们利用方法map()得以实现。假设我们希望获得一个人的姓名,那么我们可以如下实现:

String name = Optional.ofNullable(user).map(User::getName).orElse("no name");

这样当入参user不为空的时候则返回其name,否则返回no name 如我我们希望通过上面方式得到phone或email,利用上面的方式则行不通了,因为map之后返回的是Optional,我们把这种称为Optional嵌套,我们必须在map一次才能拿到我们想要的结果

long phone = optUser.flatMap(User::getPhone).orElse(-1L);

flapMap可以将方法返回的各个流扁平化成为一个流,后面的文章中细说。

过滤:fliter 
filiter,顾名思义是过滤的操作,我们可以将过滤操作做为参数传递给该方法,从而实现过滤目的,加入我们希望筛选18周岁以上的成年人,则可以实现如下:

optUser.filter(u -> u.getAge() >= 18).ifPresent(u -> System.out.println("Adult:" + u));

3.默认行为

默认行为是当Optional为不满足条件时所执行的操作,比如在上面的例子中我们使用的orElse()就是一个默认操作,用于在Optional对象为空时执行特定操作,当然也有一些默认操作是当满足条件的对象存在时执行的操作。

get() 
get用于获取变量的值,但是当变量不存在时则会抛出NoSuchElementException,所以如果不确定变量是否存在,则不建议使用

orElse(T other) 
当Optional的变量不满足给定条件时,则执行orElse,比如前面当str为null时,返回0。

orElseGet(Supplier<? extends X> expectionSupplier)

如果条件不成立时,需要执行相对复杂的逻辑,而不是简单的返回操作,则可以使用orElseGet实现:

long phone = optUser.map(User::getPhone).map(Optional::get).orElseGet(() -> {
    // do something here
    return -1L;
});

orElseThrow(Supplier<? extends X> expectionSupplier)

与get()方法类似,都是在不满足条件时返回异常,不过这里我们可以指定返回的异常类型。

ifPresent(Consumer<? super T>) 
当满足条件时执行传入的参数化操作。

三. 注意事项 
Optional是一个final类,未实现任何接口,所以当我们在利用该类包装定义类的属性的时候,如果我们定义的类有序列化的需求,那么因为Optional没有实现Serializable接口,这个时候执行序列化操作就会有问题:

public class User implements Serializable{

    /** 用户编号 */
    private long id;

    private String name;

    private int age;

    private Optional<Long> phone;  // 不能序列化

    private Optional<String> email;  // 不能序列化

不过我们可以采用如下替换策略:

private long phone;

public Optional<Long> getPhone() {
    return Optional.ofNullable(this.phone);
}

Optional在设计的时候就没有考虑将它作为类的字段使用~

http://www.codeceo.com/article/optional-class-of-java-8.html

 

五、类的修改----创建 与 内部修改

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ClassTest {
    private int id;

    @Singular
    private List<User> users;

    public static void main(String[] args) {
        ClassTest classTest =new ClassTest ();
        List<User> users =new ArrayList<> ();
        users.add (new User (1,"a",11));
        users.add (new User (2,"b",22));
        users.add (new User (3,"c",33));
        classTest.setId (1);
        classTest.setUsers (users);
        for (User user : classTest.getUsers ()){
            user.setId (4);
        }
        System.out.println (classTest);
        //ClassTest(id=1, users=[User{id=4, name='a', age=11}, User{id=4, name='b', age=22}, User{id=4, name='c', age=33}])

        List<User> list =classTest.getUsers ();
        for (User user :list){
            user.setName ("quit");
        }
        System.out.println (classTest);
        //ClassTest(id=1, users=[User{id=4, name='quit', age=11}, User{id=4, name='quit', age=22}, User{id=4, name='quit', age=33}])
    }
}

 

 

转载于:https://my.oschina.net/orange666/blog/1488735

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值