Lambda表达式(Java8系列2)

Lambda

  • 你甚至不能将一个lambda赋值给一个Object

  • 为了好地支持Lambda。最终采取的方法是:增加函数式接口的概念

    • @FunctionalInterface

      • 非必要

  • 方法引用(::)

    • 对象::实例方法

    • 类::静态方法

    • 类::实例方法

  • 构造器引用

    • 类名::new

  • Lambda作用域

    • 含有自由变量的代码称为闭包(Closure)

    • 可以直接在 lambda 表达式中访问外部的局部变量

      • final int num = 1;
        Converter<Integer, String> stringConverter =
                (from) -> String.valueOf(from + num);
        ​
        stringConverter.convert(2);     // 3
    • 访问成员变量和静态变量

    • class Lambda4 {
          static int outerStaticNum;
          int outerNum;
      ​
          void testScopes() {
              Converter<Integer, String> stringConverter1 = (from) -> {
                  outerNum = 23;
                  return String.valueOf(from);
              };
      ​
              Converter<Integer, String> stringConverter2 = (from) -> {
                  outerStaticNum = 72;
                  return String.valueOf(from);
              };
          }
      }

  • 默认方法

    • 函数式接口的支持

      • 一个接口如果需要多个方法,也要是函数式接口,那可将其他接口都声明为default

    • Collection中的forEach

      • 不能接受所有集合子类都实现一些新的方法

  • 类型推断

    • 改善心情

    • java7

      • Map<String, String> myMap = new HashMap<>();
    • java8

      • JEP101

      1.支持通过方法上下文推断泛型目标类型

      2.支持在方法调用链路当中,泛型类型推断传递到最后一个方法

class List<E> {
   static <Z> List<Z> nil() { ... };
   static <Z> List<Z> cons(Z head, List<Z> tail) { ... };
   E head() { ... }
}
//通过方法赋值的目标参数来自动推断泛型的类型
List<String> l = List.nil();
//而不是显示的指定类型
//List<String> l = List.<String>nil();
//通过前面方法参数类型推断泛型的类型
List.cons(42, List.nil());
//而不是显示的指定类型
//List.cons(42, List.<Integer>nil());

一、forEach

本文由 JavaGuide 翻译,原文地址:https://www.baeldung.com/foreach-java

1 概述

在Java 8中引入的forEach循环为程序员提供了一种新的,简洁而有趣的迭代集合的方式。

在本文中,我们将看到如何将forEach与集合一起使用,它采用何种参数以及此循环与增强的for循环的不同之处。

2 基础知识

public interface Collection<E> extends Iterable<E>

Collection 接口实现了 Iterable 接口,而 Iterable 接口在 Java 8开始具有一个新的 API:

void forEach(Consumer<? super T> action)//对 Iterable的每个元素执行给定的操作,直到所有元素都被处理或动作引发异常。

使用forEach,我们可以迭代一个集合并对每个元素执行给定的操作,就像任何其他迭代器一样。

例如,迭代和打印字符串集合for循环版本:

for (String name : names) {
    System.out.println(name);
}

我们可以使用forEach写这个 :

names.forEach(name -> {
    System.out.println(name);
});

3.使用forEach方法

3.1 匿名类

我们使用 forEach迭代集合并对每个元素执行特定操作。要执行的操作包含在实现Consumer接口的类中,并作为参数传递给forEach 。

所述消费者接口是一个功能接口(具有单个抽象方法的接口)。它接受输入并且不返回任何结果。

Consumer 接口定义如下:

@FunctionalInterface
public interface Consumer {
    void accept(T t);
}

任何实现,例如,只是打印字符串的消费者:

Consumer<String> printConsumer = new Consumer<String>() {
    public void accept(String name) {
        System.out.println(name);
    };
};

可以作为参数传递给forEach

names.forEach(printConsumer);

但这不是通过消费者和使用forEach API 创建操作的唯一方法。让我们看看我们将使用forEach方法的另外2种最流行的方式:

3.2 Lambda表达式

Java 8功能接口的主要优点是我们可以使用Lambda表达式来实例化它们,并避免使用庞大的匿名类实现。

由于 Consumer 接口属于函数式接口,我们可以通过以下形式在Lambda中表达它:

(argument) -> { body }
name -> System.out.println(name)
names.forEach(name -> System.out.println(name));

3.3 方法参考

我们可以使用方法引用语法而不是普通的Lambda语法,其中已存在一个方法来对类执行操作:

names.forEach(System.out::println);

4.forEach在集合中的使用

4.1.迭代集合

任何类型Collection的可迭代 - 列表,集合,队列 等都具有使用forEach的相同语法。

因此,正如我们已经看到的,迭代列表的元素:

List<String> names = Arrays.asList("Larry", "Steve", "James");
 
names.forEach(System.out::println);

同样对于一组:

Set<String> uniqueNames = new HashSet<>(Arrays.asList("Larry", "Steve", "James"));
 
uniqueNames.forEach(System.out::println);

或者让我们说一个队列也是一个集合

Queue<String> namesQueue = new ArrayDeque<>(Arrays.asList("Larry", "Steve", "James"));
 
namesQueue.forEach(System.out::println);

4.2.迭代Map - 使用Map的forEach

Map没有实现Iterable接口,但它提供了自己的forEach 变体,它接受BiConsumer。*

Map<Integer, String> namesMap = new HashMap<>();
namesMap.put(1, "Larry");
namesMap.put(2, "Steve");
namesMap.put(3, "James");
namesMap.forEach((key, value) -> System.out.println(key + " " + value));

4.3.迭代一个Map - 通过迭代entrySet

namesMap.entrySet().forEach(entry -> System.out.println(entry.getKey() + " " + entry.getValue()));

二、 内置函数式接口(Built-in Functional Interfaces)

函数式接口函数描述符
Runnable()->void
Predicate<T>T->boolean
Consumer<T>T->void
Function<T,R>T->R
Supplier<T>() -> T
UnaryOperator<T>T -> T
BinaryOperator<T>(T,T)->T
BiPredicate<L,R>(L,R)->boolean
BiConsumer<T,U>(T,U)->void
BiFunction<T,U,R>(T,U)->R

JDK 1.8 API包含许多内置函数式接口。 其中一些借口在老版本的 Java 中是比较常见的比如: ComparatorRunnable,这些接口都增加了@FunctionalInterface注解以便能用在 lambda 表达式上。

但是 Java 8 API 同样还提供了很多全新的函数式接口来让你的编程工作更加方便,有一些接口是来自 Google Guava 库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到lambda上使用的。

Predicates

Predicate 接口是只有一个参数的返回布尔类型值的 断言型 接口。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非):

译者注: Predicate 接口源码如下

package java.util.function;
import java.util.Objects;
​
@FunctionalInterface
public interface Predicate<T> {
    
    // 该方法是接受一个传入类型,返回一个布尔值.此方法应用于判断.
    boolean test(T t);
​
    //and方法与关系型运算符"&&"相似,两边都成立才返回true
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
    // 与关系运算符"!"相似,对判断进行取反
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }
    //or方法与关系型运算符"||"相似,两边只要有一个成立就返回true
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
   // 该方法接收一个Object对象,返回一个Predicate类型.此方法用于判断第一个test的方法与第二个test方法相同(equal).
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }

示例:

Predicate<String> predicate = (s) -> s.length() > 0;
​
predicate.test("foo");              // true
predicate.negate().test("foo");     // false
​
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
​
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();

Functions

Function 接口接受一个参数并生成结果。默认方法可用于将多个函数链接在一起(compose, andThen):

译者注: Function 接口源码如下

package java.util.function;
 
import java.util.Objects;
 
@FunctionalInterface
public interface Function<T, R> {
    
    //将Function对象应用到输入的参数上,然后返回计算结果。
    R apply(T t);
    //将两个Function整合,并返回一个能够执行两个Function对象功能的Function对象。
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    // 
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
 
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
backToString.apply("123");     // "123"

Suppliers

Supplier 接口产生给定泛型类型的结果。 与 Function 接口不同,Supplier 接口不接受参数。

Supplier<Person> personSupplier = Person::new;
personSupplier.get();   // new Person

Consumers

Consumer 接口表示要对单个输入参数执行的操作。

Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));

Comparators

Comparator 是老Java中的经典接口, Java 8在此之上添加了多种默认方法:

Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
​
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
​
comparator.compare(p1, p2);             // > 0
comparator.reversed().compare(p1, p2);  // < 0

三、Optionals

Optionals不是函数式接口,而是用于防止 NullPointerException 的漂亮工具。这是下一节的一个重要概念,让我们快速了解一下Optionals的工作原理。

Optional 是一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是有时却什么也没有返回,而在Java 8中,你应该返回 Optional 而不是 null。

译者注:示例中每个方法的作用已经添加。

//of():为非null的值创建一个Optional
Optional<String> optional = Optional.of("bam");
// isPresent(): 如果值存在返回true,否则返回false
optional.isPresent();           // true
//get():如果Optional有值则将其返回,否则抛出NoSuchElementException
optional.get();                 // "bam"
//orElse():如果有值则将其返回,否则返回指定的其它值
optional.orElse("fallback");    // "bam"
//ifPresent():如果Optional实例有值则为其调用consumer,否则不做处理
optional.ifPresent((s) -> System.out.println(s.charAt(0)));     // "b"

推荐阅读:[[Java8]如何正确使用Optional]

四、方法引用

ambda表达式方法引用
(args) -> ClassName.staticMethod(args)ClassName::staticMethod静态方法方法引用
(arg0, params) -> arg0.instanceMethod(params)ClassName::instanceMethod内部实例方法引用
arg0 (params) -> arg0.instanceMethod(params)arg0.instanceMethod外部实例方法引用

五、 lambda表达式的写法

六、 重构重复代码
 

Java8 由Oracle在2014年发布,是继Java5之后最具革命性的版本。 Java8吸收其他语言的精髓带来了函数式编程,lambda表达式,Stream流等一系列新特性,学会了这些新特性,可以让你实现高效编码优雅编码。

烂代码登场

首先引入一个实际的例子,我们常常会写一个dao类来操作数据库,比如查询记录,插入记录等。

下面的代码中实现了查询和插入功能(引入Mybatis三方件):

public class StudentDao {
    /**
     * 根据学生id查询记录
     * @param id 学生id
     * @return 返回学生对象
     */
    public Student queryOne(int id) {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession session = null;
        try {
            session = sqlSessionFactory.openSession();
            // 根据id查询指定的student对象
            return session.selectOne("com.coderspace.mapper.student.queryOne", id);
        } finally {
            if (session != null) {
                session.close();
            }
        }
    }
    /**
     * 插入一条学生记录
     * @param student 待插入对象
     * @return true if success, else return false
     */
    public boolean insert(Student student) {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession session = null;
        try {
            session = sqlSessionFactory.openSession();
            // 向数据库插入student对象
            int rows = session.insert("com.coderspace.mapper.student.insert", student);
            return rows > 0;
        } finally {
            if (session != null) {
                session.close();
            }
        }
    }
  }

睁大眼睛观察上面的代码可以发现,这两个方法有很多重复的代码。

除了下面这两行,其他的代码都是一样的,都是先获取session,然后执行核心操作,最后关闭session。

// 方法1中核心代码
return session.selectOne("com.coderspace.mapper.student.queryOne", id);
// 方法2中核心代码
int rows = session.insert("com.coderspace.mapper.student.insert", student);

作为一个有追求的程序员,不,应该叫代码艺术家,是不是应该考虑重构一下。

获取session和关闭session这段代码围绕着具体的核心操作代码,我们可以称这段代码为模板代码。

假如又来了一个需求,需要实现删除student方法,那么你肯定会copy上面的获取session和关闭session代码,这样做有太多重复的代码,作为一名优秀的工程师肯定不会容忍这种事情的发生。

开始重构烂代码

怎么重构呢?现在请出我们的主角登场:环绕执行模式使行为参数化

名字是不是很高大上,啥叫行为参数化?上面例子中我们已经观察到了,除了核心操作代码其他代码都是一模一样,那我们是不是可以将核心操作代码作为入参传入模板方法中,根据不同的行为分别执行。

变量对象很容易作为参数传入,行为可以作为参数传入吗?

答案是:当然可以,可以采用lambda表达式传入。

下面开始重构之前的例子,主要可以分为三步:

(1)定义函数式接口;

(2)定义模板方法;

(3)传递lambda表达式

所有的环绕执行模式都可以套用上面这三步公式。

第一步:定义函数式接口

@FunctionalInterface
public interface DbOperation<R> {
    /**
     * 通用操作数据库接口
     * @param session 数据库连接session
     * @param mapperId 关联mapper文件id操作
     * @param params 操作参数
     * @return 返回值,R泛型
     */
    R operate(SqlSession session, String mapperId, Object params);
}

定义了一个operate抽象方法,接收三个参数,返回泛型R。

第二步:定义模板方法

DbOperation是一个函数式接口,作为入参传入:

public class CommonDao<R> {
    
    public R proccess(DbOperation<R> dbOperation, String mappperId, Object params) {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession session = null;
        try {
            session = sqlSessionFactory.openSession();
            // 核心操作
            return dbOperation.operate(session, mappperId, params);
        } finally {
            if (session != null) {
                session.close();
            }
        }
    }
  }

第三步:传递lambda表达式

// 根据id查询学生
String mapperId = "com.coderspace.mapper.student.queryOne";
int studentNo = 123;
CommonDao<Student> commonDao = new CommonDao<>();
// 使用lambda传递具体的行为
Student studentObj = commonDao.proccess(
        (session, mappperId, params) -> session.selectOne(mappperId, params),
        mapperId, studentNo);
// 插入学生记录
String mapperId2 = "com.coderspace.mapper.student.insert";
Student student = new Student("coderspace", 1, 100);
CommonDao<Boolean> commonDao2 = new CommonDao<>();
// 使用lambda传递具体的行为
Boolean successInsert = commonDao2.proccess(
        (session, mappperId, params) -> session.selectOne(mappperId, params),
        mapperId2, student);

实现了上面三步,假如要实现删除方法,CommonDao里面一行代码都不用改,只用在调用方传入不同的参数即可实现。

总结

环绕执行模式在项目实战中大有用途,如果你发现几行易变的代码外面围绕着一堆固定的代码,这个时候你应该考虑使用lambda环绕执行模式了。

环绕执行模式固有套路请跟我一起大声读三遍:

第一步:定义函数式接口

第二步:定义模板方法

第三步:传递lambda表达式

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

拦路雨g

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值