Java函数式编程

本文详细介绍了Java 8中的Lambda表达式及其四种省略模式,强调了Lambda与匿名内部类的区别,并展示了Stream流的创建、中间操作和终结操作,包括过滤、映射、去重、排序、聚合等操作。此外,还探讨了Optional类的使用,以避免空指针异常,并介绍了元组的概念和应用。文章通过实例展示了如何在实际编程中提升代码的简洁性和效率。
摘要由CSDN通过智能技术生成

引入pom依赖

目前最新的稳定版是0.10.2

<!-- https://mvnrepository.com/artifact/io.vavr/vavr -->
<dependency>
    <groupId>io.vavr</groupId>
    <artifactId>vavr</artifactId>
    <version>0.10.2</version>
</dependency>

1.Lambda表达式

1.1Lambda表达式省略模式

  1. 参数类型可以省略,如果有多个参数,不能只省略一个
/*Lambda表达式的省略模式1:
* 参数的类型可以省略,但注意,如果有多个参数,不能只省略一个*/
//getScore((int x,int y,int z) ->{//之前的写法
//getScore((x,int y, int z) ->{//会报错
getScore((x,y,z) ->{//现在省略了所有参数的类型
   return x+y+z;//scoreCount()正常返回方法的返回值,交给下方的score变量来保存
});
  1. 如果方法的参数只有一个,参数的小括号可以省略
/*Lambda表达式的省略模式2:
 * 如果方法的参数有且只有一个,参数的小括号可以省略*/
//getFruit( (String s) -> {//之前的写法,现在省略的参数的类型与小括号
  getFruit( s -> {
     System.out.println("参数s是:"+s);
     System.out.println("我是一颗大草莓");
  });
  1. 如果大括号里的方法体只有一句,可以省略大括号
/*Lambda表达式的省略模式3:
     * 如果大括号里的方法体只有一句,可以省略大括号和分号*/
     getAnimal( () -> 
        System.out.println("Lambda表达式的方式,小动物们都惊呆了~")
     );
  1. 如果方法体里有return返回值,return需要一起省略
/*Lambda表达式的省略模式4:
 - 如果方法体只有一句,大括号与分号可以省略
 - 除此之外,如果方法用关键字return返回返回值,return也需要一起省略掉*/
 getScore((x,y,z) -> x+y+z );

1.2Lambda表达式注意事项

  • 必须有一个接口,而且接口中只有一个抽象方法(函数式接口)
  • 使用表达式时必须有上下文环境推导出Lambda实际代表的接口
  • 匿名内部类与Lambda表达式的区别/使用场景:
    1)不论被调用方法的参数是接口/抽象类/实体类,匿名内部类的形式均可以使用
    但是Lambda表达式只能是被调用方法参数为接口的时候使用
    2)不论接口里有几个抽象方法,都可以使用匿名内部类的形式
    但是Lambda表达式只支持接口中有且仅有一个抽象方法的情况
    3)实现原理不同:匿名内部类生效后会多产生一个额外单独的内部类字节码文件
    而Lambda表达式只有一个字节码文件

2.Stream流

2.1创建流(需要加终结方法)

  • 单列集合:集合对象.stream()
List<Author> authors = getAuthors();
Stream<Author> stream = authors.stream;
  • 数组:Arrays.stream(数组)或者使用Stream.of 来创建
Integer[arr = [1,2,3,4,5];
Stream<Integer> stream = Arrays.stream(arr); //数组转换为流对象
Stream<Integer> stream2 = stream.of(arr);//数组转换为流对象
  • 双列集合:转换成单列集合后再创建
Map<string,Integer> map = new HashMap() ;
map.put("蜡笔小新",19);
map .put("热子",17);
map. put("日向翔阳",16);
Stream<Map.Entry<string,Integer>> stream = map.entryset().stream();

2.2中间操作

  • filter
    可以对流中的元素进行条件过滤,符合过滤条件的才能继续留在流中。
    例:打印所有姓名长度大于1的作家的姓名
List<Author> authors = getAuthors();
authors.stream()
.filter(author -> author.getName().length()>1)
.forEach(author -> System.out.print1n(author.getName()));
  • map
    可以把对流中的元素进行计算或转换。
    例:打印所有作家的姓名1
List<Author> authors = getAuthors();
authors
.stream()
.map(author-> author.getName())
.forEach(name->System.out.print1n(name));

打印所有作家的姓名2

List<Author> authors = getAuthors();
//authors.stream()
//.map(author->author.getName())
//.forEach(s->System.out.println(s));
authors.stream()
.map(author->author.getAge())
.map(age->age+10)
.forEach(age->system.out.println(age));
  • distinct
    可以去除流中的重复元素。

注意: distinct方法是依赖Object的equals方法来判断是否是相同对象的。所以需要注意重新equals方法。
例如:
打印所有作家的姓名,并且要求其中不能有重复元素。

List<Author> authors = getAuthors();
authors.stream()
.distinct()
.forEach(author->System.out.println(author.getName()));
  • sorted
    可以对流中的元素进行排序。
    注意:如果调用空参sorted(),需要流中元素实现Comparable<>接口
    例如:
    对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素。
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted() //实体类需要实现Comparable<>接口
.forEach(author->System.out.println(author.getAge()));
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted((o1,o2)->o2.getAge()-o1.getAge())
.forEach(author->System.out.println(author.getAge()));
  • limit
    可以设置流的最大长度,超出的部分将被抛弃。
    例如:
    对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素,然后打印其中年龄最大的两个作家的姓名。
List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted((o1,o2)->o2.getAge()-o1.getAge())
.limit(2)
.forEach(author->System.out.println(author.getAge()));
  • skip

打印除了年龄最大的作家外的其他作家,要求不能有重复元素,并且按照年龄降序排序。

List<Author> authors = getAuthors();
authors.stream()
.distinct()
.sorted((o1,o2)->o2.getAge()-o1.getAge())
.skip(1)
.forEach(author->System.out.println(author.getName()));
  • flatMap
    map只能把一个对象转换成另一个对象来作为流中的元素。而flatMap可以把一个对象转换成多个对象作为流中的元素.
    例一:
    打印所有书籍的名字。要求对重复的元素进行去重。
List<Author> authors = getAuthors();
authors.stream()
.flatMap(author->author.getBooks().stream())
.distinct()
.forEach(book->System.out.println(book.getName()));

例二:
打印现有数据的所有分类。要求对分类进行去重。不能出现这种格式:哲学,爱情(需要进行分割)

List<Author> authors = getAuthors();
authors.stream()
.flatMap(author->author.getBooks().stream())
.distinct() //对书籍去重
.flatMap(book->Arrays.stream(book.getCategory().split(",")))
.distinct() //对分类去重
.forEach(book->System.out.println(book.getName()));

2.3终结操作

  • forEach
    对流中的元素进行遍历操作。我们通过传入的参数去指定对遍历到的元素进行什么具体操作。
    例子:
    输出所有作家的名字
List<Author> authors = getAuthors();
authors.stream()
.map(author->author.getName())
.distinct()
.forEach(name->System.out.println(name))
  • count
    可以用来获取当前流中元素的个数。
    例子:
    打印这些作家的所出书籍的数目,注意删除重复元素。
List<Author> authors = getAuthors();
long count = authors.stream()
.flatMap(author->author.getBooks().stream())
.distinct()
.count()
  • max&min
    可以用来或者流中的最值。
    例子:
    分别获取这些作家的所出书籍的最高分和最低分并打印。
List<Author> authors = getAuthors();
Optional<Interger> max = authors.stream()
.flatMap(author->author.getBooks().stream())
.map(book->book.getScore())
.max((s1-s2)->s1-s2)
Optional<Interger> min= authors.stream()
.flatMap(author->author.getBooks().stream())
.map(book->book.getScore())
.max((s1-s2)->s1-s2)
System.out.println(max.get());
  • collect
    把当前流转换成一个集合。
    例子;
    获取一个存放所有作者名字的List集合。
List<Author> authors = getAuthors();
List<String> nameList = authors.stream()
.map(author->author.getName())
.distinct()
.collect(Collectors.toList())
System.out.println(nameList);

获取一个所有书名的Set集合。

List<Author> authors = getAuthors();
Set<Book> books = authors.stream()
.flatMap(author->author.getBooks().stream())
.distinct()
.collect(Collectors.toSet())
System.out.println(books);

获取一个map集合,map的key为作者名,value为List

List<Author> authors = getAuthors();
Map<String,List<Book>> map = authors.stream()
.distinct()
.collect(Collectors.toMap(new Function<Author,String>(){
@override
public String apply(Author author){
return author.getName();
}, new Function<Author,List<Book>>(){
@override
public List<Book> apply(Author author){
return author.getBooks();
}
System.out.println(map);
List<Author> authors = getAuthors();
Map<String,List<Book>> map = authors.stream()
.distinct()
.collect(Collectors.toMap(author -> author.getName()),author -> author.getBooks()));
System.out.println(map);
  • reduce归并
    对流中的数据按照你指定的计算方式计算出一个结果。(缩减操作)
    reduce的作用是把stream中的元素给组合起来,我们可以传入一个初始值,它会按照我们的计算方式依次拿流中的元素和在初始化值的基础上进行计算,计算结果再和后面的元素计算。
    reduce两个参数的重载形式内部的计算方式如下:
T result = identity;
for (T element : this stream)
result = accumulator.app1y(resu1t, element)
return result;

其中identity就是我们可以通过方法参数传入的初始值,accumulator的apply具体进行什么计算也是我们通过方法参数来确定的。
例子:
使用reduce求所有作者年龄的和

List<Author> authors = getAuthors();
Integer sum=authors.stream()
.map(author -> author .getAge())
.reduce(0,(resu1t, element -> result + element);
system.out.println(sum);

使用reduce求所有作者中年龄的最大值

List<Author> authors = getAuthors();
Integer max=authors.stream()
.map(author -> author .getAge())
.reduce(Integer.MIN_VALUE,(resu1t, element -> result + element);
system.out.println(max);

使用reduce求所有作者中年龄的最小值
两个参数:

List<Author> authors = getAuthors();
Integer min=authors.stream()
.map(author -> author .getAge())
.reduce(Integer.MAX_VALUE,(resu1t, element -> result + element);
system.out.println(min);

一个参数:

boolean foundAny = false;
T result = null;
for (T element : this stream) i
if (!foundAny) i
foundAny = true;
result = element;
}
e1se
result = accumulator . apply(result,element);
}
return foundAny ? optional.of(result) : optional.empty();

2.4注意事项

  • 惰性求值(如果没有终结操作,没有中间操作是不会得到执行的)
  • 流是一次性的(一旦一个流对象经过一个终结操作后。这个流就不能再被使用)
  • 不会影响原数据(我们在流中可以多数据做很多处理。但是正常情况下是不会影响原来集合中的元素的。这往往也是我们期望的)

3.Optional

3.1概述

我们在编写代码的时候出现最多的就是空指针异常。所以在很多情况下我们需要做各种非空的判断。例如:

Author author = getAuthor();
if(author !=nu11){
System.out.print1n(author.getName);
}

尤其是对象中的属性还是一个对象的情况下。这种判断会更多。而过多的判断语句会让我们的代码显得臃肿不堪。
所以在JDK8中引入了Optional,养成使用Optional的习惯后你可以写出更优雅的代码来避免空指针异常。
并且在很多函数式编程相关的API中也都用到了Optional,如果不会使用ptional也会对函数式编程的学习造成影响。

3.2创建对象

optional就好像是包装类,可以把我们的具体数据封装Optional对象内部。然后我们去使用Optional中封装好的方法操作封装进去的数据就可以非常优雅的避免空指针异常。

我们一般使用Optional的静态方法ofNullable来把数据封装成一个Optional对象。无论传入的参数是否为null都不会出现问题。

Author author = getAuthor();
Optional<Author> authoroptional = Optional.ofNullable(author);

你可能会觉得还要加一行代码来封装数据比较麻烦。但是如果改造下getAuthor方法,让其的返回值就是封装好的Optiona的话,我们在使用时就会方便很多。
而且在实际开发中我们的数据很多是从数据库获取的。Mybatis从3.5版本可以也已经支持Optional了。我们可以直接把dao方法的返回值类型定义成Optional类型,MyBastis会自己把数据封装成Optional对象返回。封装的过程也不需要我们自己操作。

ifPresent表值是否存在
public class OptionalDemo {
public static void main(String[] args){
//Author author = getAuthor();
//Optional<Author> authorOptional = Optional.ofNullable(author) :
//authorOptional.ifPresent(author1 -> System.out.println(author1.getName()));

Optional<Author> authorOptional = getAuthorOptional();
authorOptional.ifPresent(author -> System.out.println(author.getName()));
}


public static Optional<Author> getAuthorOptional(){
Author author = new Author(1L,"蒙多",33,"一个从菜刀中明悟哲理的祖安人",null);
return Optional.ofNummable(author);
}

public static Author getAuthor(){
Author author = new Author(1L,"蒙多",33,"一个从菜刀中明悟哲理的祖安人",null);
return author;
}

如果你确定一个对象不是空的则可以使用optional的静态方法of来把数据封装成Optional对象。

Author author = new Author();
Optional<Author> authorOptional = Optional.of(author);
authorOptional.ifPresent(author -> System.out.println(author.getName()));

但是一定要注意,如果使用of的时候传入的参数必须不为null
如果一个方法的返回值类型是Optional类型。而如果我们经判断发现某次计算得到的返回值为null,这个时候就需要把null封装成Optional对象返回。这时则可以使用Optional的静态方法empty来进行封装。

Optional.empty()

3.3安全消费值

我们获取到一个Optional对象后肯定需要对其中的数据进行使用。这时候我们可以使用其ifPresent方法对来消费其中的值。这个方法会判断其内封装的数据是否为空,不为空时才会执行具体的消费代码。这样使用起来就更加安全了。
例如,以下写法就优雅的避免了空指针异常。|

Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
authorOptional.ifPresent(author -> system.out.println(author.getName()));

3.4获取值

  • get()不推荐,当Optional内部的数据为空的时候会出现异常。

3.5安全获取值

如果我们期望安全的获取值。我们不推荐使用get方法,而是使用Optional提供的以下方法。

  • orElseGet
    获取数据并且设置数据为空时的默认值。如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建对象作为默认值返回。
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
Author authorl = authorOptional.orElseget(() -> new Author());
  • orEseThrow
    获取数据,如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建异常抛出。
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
try {
Author author = authorOptional.orElseThrow((supplier<Throwable>)() -> new
RuntimeException("author为空"));
System.out.println(author.getName();
catch (Throwable throwable) {
throwab1e.printstackTrace();
}

3.6过滤

我们可以使用filter方法对数据进行过滤。如果原本是有数据的,但是不符合判断,也会变成一个无数据的Optional对象。

Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
authorOptional.filter(author -> author.getAge()>100).ifPresent(author >
System.out.println(author.getName();

3.7判断

我们可以使用isPresent方法进行是否存在数据的判断。如果为空返回值为false,如果不为空,返回值为true。但是这种方式并不能体现ptional的好处,更推荐使用ifPresent方法

Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
if (authorOptional.isPresent( {
system.out.println(authorOptional.get().getName();
}

3.8数据转换

optionali还提供了map可以让我们的对数据进行转换,并且转换得到的数据也还是被Optional包装好的,保证了我们的使用安全。例如我们想获取作家的书籍集合。

Optional<Author> authorOptional = Optional.ofNullab1e(getAuthor());
Optional<List<Book>> books = authorOptional.map(author -> author.getBooks());
books.ifPresent(new consumer<List<Book>>(){
@override
public void accept(List<Book> books {
books.forEach(book -> system.out.println (book.getName()));
}
});

4.函数式接口

4.1概述

只有一个抽象方法的接口我们称之为函数接口。

JDK的函数式接口都加上了@FunctionalInterface 注解进行标识。但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式接口。

4.2常见函数式接口

  • Consumer 消费接口
    根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数进行消费。
    在这里插入图片描述

  • Funcion 计算转换接口
    根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数计算或转换,把结果返回。
    在这里插入图片描述

  • Predicate 判断接口
    根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数条件判断,返回判断结果。
    在这里插入图片描述

  • Supplier 生产型接口
    根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中创建对象,把创建好的对象返回
    在这里插入图片描述

4.3常用的默认方法

  • and
    我们在使用Predicate接口时候可能需要进行判断条件的拼接。而and方法相当于是使用&&来拼接两个判断条件。
    例如:
    打印作家中年龄大于17并且姓名的长度大于1的作家。
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.filter(new Predicate<Author> {
@override
public boolean test(Author author) {
return author.getAge()>17;
}
}.and(new Predicate<Author> {
@override
public boolean test(Author author) {
return author.getName().1ength>1;
}).forEach(author -> System.out.println(author));
  • or
    我们在使用Predicate接口时候可能需要进行判断条件的拼接。而or方法相当于是使用||来拼接两个判断条件。例如:
    打印作家中年龄大于17或者姓名的长度小于2的作家。
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.filter(new Predicate<Author> {
@override
public boolean test(Author author) {
return author.getAge()>17;
}
}.or(new Predicate<Author> {
@override
public boolean test(Author author) {
return author.getName().1ength<2;
}).forEach(author -> System.out.println(author));
  • negate
    Predicate接口中的方法。negate方法相当于是在判断添加前面加了个!表示取反。

5.方法引用

5.1基本格式

类名或对象名::方法名

5.2语法详解

5.2.1引用类的静态方法

  • 格式
    类名::方法名
  • 使用前提
    如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类的静态方法。

5.2.2引用对象的实例方法

  • 格式
    对象名::方法名
  • 使用前提
    如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且我们把要重写的抽象方法中剩余的所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用类的实例方法。|

5.2.3引用类的实例方法

  • 格式
    类名::方法名
  • 使用前提
    如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且我们把要重写的抽象方法中剩余的所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用类的实例方法。

5.2.4构造器引用

如果方法体中的一行代码是构造器的话就可以使用构造器引用。

  • 格式
    类名: :new
  • 使用前提
    如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的构造方法,并且我们把要重写的抽象方法中的所有的参数都按照顺序传入了这个构造方法中,这个时候我们就可以引用构造器。

6.并行流

当流中有大量元素时,我们可以使用并行流去提高操作的效率。其实并行流就是把任务分配给多个线程去完成。如果我们自己去用代码实现的话其实会非常的复杂,并且要求你对并发编程有足够的理解和认识。而如果我们使用Stream的话,我们只需要修改一个方法的调用就可以使用并行流来帮我们实现,从而提高效率。
parallel()(Stream流调用)、parallelStream()(集合可以直接调用)转为并行流对象
ParallelStreams默认使用ForkJoinPool.commonPool()线程池,多线程操作。

7.元组(Tuple)

7.1概述

元组是固定数量的不同类型的元素的组合。元组与集合的不同之处在于,元组中的元素类型可以是不同的,而且数量固定。元组的好处在于可以把多个元素作为一个单元传递。如果一个方法需要返回多个值,可以把这多个值作为元组返回,而不需要创建额外的类来表示。根据元素数量的不同,Vavr总共提供了Tuple0、Tuple1…Tuple8等9个类。**每个元组类都要声明其元素类型。**如 Tuple2<String, Integer>表示的是两个元素的元组,第一个元素的类型为 String,第二个元素的类型为 Integer。**对于元组对象,可以使用 _1、_2 到 _8 来访问其中的元素。**所有元组对象都是不可变的,在创建之后不能更改。

元组通过Tuple的静态方法of来创建。 **由于元组是不可变的,所有相关的操作都返回一个新的元组对象。**在清单1中,使用Tuple.of创建了一个Tuple2对象。**Tuple2的map方法用来转换元组中的每一个元素,返回新的元组对象。而apply方法则把元组转换成单个值。**其他元组类也有类似的方法。除了map方法外,还有map1、map2、map3等方法来转换第N个元素;update1、update2 和 update3 等方法用来更新单个元素。

7.2创建对象、更新内容

通过update1-n方法,可以实现元组内容的更新,更新结果

public class TupleDemo {
    public static void main(String[] args) {
        Tuple3<String, LocalDateTime,Long> tuple3 = Tuple.of("TupleTest", LocalDateTime.now(),Long.MAX_VALUE);
        System.out.println(tuple3);//(TupleTest, 2022-06-24T16:07:07.124, 9223372036854775807)
        tuple3 = tuple3.update3(Long.MIN_VALUE);
        System.out.println(tuple3);//(TupleTest, 2022-06-24T16:11:50.164, -9223372036854775808)
    }
}

7.3方法多个返回值使用元组接收

比如我们需要封装一个方法获取今天的最小时间和最大时间,那么这里就需要两个返回值,虽然我们可以用Object[]或者Map之类的容器达到我们的效果,但是如果我返回值得类型都不尽想同,那么在获取的时候就要进行类型转换,加到了转换出错的概率,但是使用元组就不会有这个问题。
我们可以看到getNowRange同时有两个放回值,通过Tuple2进行保存,通过 _n 或者 _n() 获取每一个元组中的一列数据。

public static void main(String[] args) {
    Tuple2<LocalDateTime,LocalDateTime> tuple2 = getNowRange();
    System.out.println(tuple2);//(2022-06-24T00:00, 2022-06-24T23:59:59.999999999)
    System.out.println(tuple2._1);//2022-06-24T00:00
    System.out.println(tuple2._2());//2022-06-24T23:59:59.999999999
}
public static Tuple2<LocalDateTime,LocalDateTime> getNowRange(){
    LocalDate now = LocalDate.now();
    LocalDateTime startTime = now.atTime(LocalTime.MIN);
    LocalDateTime enTime = now.atTime(LocalTime.MAX);
    return Tuple.of(startTime, enTime);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值