JAVA8-lambda表达式7:重要的函数接口

目录

从什么是好代码讲起

重申函数式接口定义

重要的函数接口

Java中重要的函数接口

Predicate

Consumer

Function

Supplier

自定义函数


从什么是好代码讲起

最近又在看《clean code》,回顾了一下里面提到的整洁代码的标准。

然后审视了一下现在的项目代码,里面还有很多if,for循环。比如:

// 查询用户列表
List userList = userService.list();
// 打印用户信息
for (int i = 0; i < userList.size(); i++) {
            System.out.println(userList.get(i));
        }

并不是这种写法不好(用《clean code》的话说就是,它不并是一个坏味道),非常多优秀的框架,类库也是这样写的。但现在项目用是jdk1.8啊,已经出来多久了!隔壁有的已经到17了,要求使用1.8的新特性不过分吧?

不仅是现在(也不仅是现在的公司),再把时间也往前推到17年(往前推服务过的2个公司),都存在一样的现象:jdk版本是1.8,而写代码的风格却还停留在1.4!而其中的原因也很简单甚至不值得一提:并不是lambda表达式不好,或者抗拒这种使用方法,就是不熟悉而已。

没有看错,就是有的程序员不熟悉而已,当他愿意花时间去熟悉lambda表达式,就很快应用并爱不释手。所以,我认为整洁代码还有一个评判因素就是,使用jdk新的代码风格编写代码!

重申函数式接口定义

3年前在JAVA8-lambda表达式1:什么是lambda表达式,一文里面认识了什么是lambda,这里再简单回顾一下:只有一个抽象方法的接口,就是函数式接口!

JAVA8-lambda表达式7:重要的函数接口_第1张图片

 这里一再强调lambda表达式的定义,多数时候可以直接使用内置的函数来解决问题,比如JAVA8-lambda表达式2:常用的集合类api,甚至自定义函数JAVA8-lambda表达式6:重构和定制收集器

重要的函数接口

上面例子里面的代码,如果要改写成lambda表达式,可以如下:

userList.stream().forEach(user -> System.out.println(user));

而forEach方法里面的参数其实就是一个函数:Consumer函数式接口:

JAVA8-lambda表达式7:重要的函数接口_第2张图片

 Consumer是一个比较重要的函数,表示接受一个参数用于执行代码且没有返回值。

Java中重要的函数接口

其中Java中重要的函数接口,有以下几个:

Java中重要的函数接口
接口
参数
返回类型
示例
Predicate
T
boolean
条件判断:接受一个参数并判断给定条件是否成立,比如过滤出age>60的用户
Consumer
T
void
立即执行:接受一个参数并立即执行给定代码,比如例子中的打印用户信息
Function
T
R
立即执行:接受一个参数立即执行给定代码并转换成指定的类型返回,比如将Predicate过滤出age>60的用户设置为老年客户
Supplier
None
T 获取结果:立即执行给定代码并获取指定结果,比如收集上面的过滤结果
UnaryOperator
T T 立即执行:对单个操作数的操作,产生与其操作数相同类型的结果,比如要将userList转成Map集合就可以用到
BinaryOperator
(T, T)
T 二元操作:接受2个相同类型参数立即执行给定代码,并返回相同类型的结果,比如统计所有用户的年龄之和

Predict

Predicate

Predicate接口是比较好理解的,目的就是执行一个判断条件,返回boolean值。比如过滤出age>60的用户,用传统代码如下,方式1:

// 添加2个用户:张3跟李四
        User user1 = new User();
        user1.setId(1L);
        user1.setName("张3");
        user1.setAge(61);
        user1.setEmail("[email protected]");
        userList.add(user1);
        
        User user2 = new User();
        user2.setId(2L);
        user2.setName("李四");
        user2.setAge(31);
        user2.setEmail("[email protected]");
        userList.add(user2);
        // 打印age>60的用户
        for (int i = 0; i < userList.size(); i++) {
            User user = userList.get(i);
            if (user.getAge() > 60) {
                System.out.println(userList.get(i));
            }
        }

如果要用lambda函数来改写,可能就是下面这种,方式2:

// lambda表达式改写:打印age>60的用户
        userList.stream().filter(user -> user.getAge() > 60).forEach(user -> System.out.println(user));

 方式1与方式2其实是等价的,效果相同,但是方式2明显代码更简洁,意图也理清晰!重点在于filter这个高阶函数上面,它要求参数是一个Predicate函数。据此,上面的方式2其实可以写成如下此种方式,方便理解:

// 条件函数
        Predicate predicate = user -> user.getAge() > 60;
        // lambda表达式改写:打印age>60的用户
        userList.stream().filter(predicate).forEach(user -> System.out.println(user));
  • 先声明一个函数predicate,目的是过滤age>60的用户
  • 传递给集合处理函数filter,执行函数过滤出用户列表 

 因为Predicate是泛型,所以这里是需要声明为Predicate,由lambda自动做类型推导。可以再举一个例子,比如过滤过所以姓"张"且age>60的用户,如果还是按照刚才的方式,代码如下:

 // 年龄条件函数
        Predicate agePredicate = user -> user.getAge() > 60;
        // 姓名条件函数
        Predicate namePredicate = user -> user.getName().startsWith("张");
        // lambda表达式改写:打印age>60的用户
        userList.stream().filter(agePredicate).filter(namePredicate).forEach(user -> System.out.println(user));

Consumer

Consumer接口在开篇的时候已经提到,比如集合的forEach参数。从Predicate的分析来看,上面的例子也可以改写成如下:

// 年龄条件函数
        Predicate agePredicate = user -> user.getAge() > 60;
        // lambda表达式改写:打印age>60的用户
        Consumer printConsumer = user -> System.out.println(user);
        userList.stream().filter(agePredicate).forEach(printConsumer);
  • 先声明一个函数printConsumer,目的是打印age>60的用户
  • 传递给集合处理函数forEach,执行函数打印出户列表

这里要重点注意一点的是,上面的例子并不是指Consumer只能用在集合类处理,而是在强调代码即函数,lambda函数甚至可以做为参数传递,比如可以将打印的提出一个方法,由上面的集合处理过程中来调用:

// 年龄条件函数
        Predicate agePredicate = user -> user.getAge() > 60;
        // lambda表达式改写:打印age>60的用户
        Consumer printConsumer = user -> System.out.println(user);
        userList.stream().filter(agePredicate).forEach(user -> print(printConsumer, user));


/**
     * 打印用户信息
     *
     * @param printConsumer
     * @param u
     */
    private void print(Consumer printConsumer, User u) {
        printConsumer.accept(u);
    }

Function

继续以上面的例子来看,如果要获取用户的名称列表,用lambda表达式可以这么写:

// 获取用户名称列表
List userNames = userList.stream().map(user -> {
            return user.getName();
        }).collect(Collectors.toList());

 按照上面的函数式写法,也可以写成如下形式:

        // 用户姓名函数
        Function userNameFunction = user -> user.getName();
        List userNames = userList.stream().map(userNameFunction).collect(Collectors.toList());

 注意Function函数,接受一个参数立即执行给定代码并转换成指定的类型返回。这里要重点强调的是,里面的返回类型,这个在很多场景是非常有用的,比如上面的类型转换,从User对象转换成String类型。如果是有多个参数,比如2个参数的情况,java8也提供了BiFunction的函数来支持!

看到这里,会不会有人疑问,Predicate固定返回boolean值,所以是Function函数的特例呢?毫无疑问,Function是可以返回boolean的,比如:

Function isOldUserFunction = user -> user.getAge() > 60;
        List userNames = userList.stream().map(isOldUserFunction).collect(Collectors.toList());
        System.out.println(userNames);

其实从函数的作用来说,这是不对的,原因在于对于Function函数来说,它作用的元素是类型会变化,个数是不变的,即T->R是1:1的

JAVA8-lambda表达式7:重要的函数接口_第3张图片

 Predicate是过滤函数,它作用的元素会代入条件去执行,得到是否满足的结果。所以它们在集合流中的表现才各不相同

JAVA8-lambda表达式7:重要的函数接口_第4张图片

Supplier

 Supplier这个接口比较特殊一点,它没有参数并且可以返回指定的泛型对象,比如下面的new一个User的实例:

 Supplier nameSupplier = User::new;
        User user = nameSupplier.get();

所以它通常用在类似工厂的方法调用中,获取一个特定的对象。比如以前提到Optional用法里面的一个例子:

CardPromotionPublishResponse response = new CardPromotionPublishResponse();
        CardPromotionPrize backUp = new CardPromotionPrize();
 
        CardPromotionPrize cardPromotionPrize = Optional.ofNullable(response.getCardPromotionPrize()).orElseGet(CardPromotionPrize::new);
        cardPromotionPrize = Optional.ofNullable(response.getCardPromotionPrize()).orElse(backUp);

如果查询的优惠为空,则默认生成一个CardPromotionPrize::new,这个也是Supplier函数!

这里可以看下Optional的源码,这个方法orElseGet的参数就是一个Supplier函数:

    /**
     * Return the value if present, otherwise invoke {@code other} and return
     * the result of that invocation.
     *
     * @param other a {@code Supplier} whose result is returned if no value
     * is present
     * @return the value if present otherwise the result of {@code other.get()}
     * @throws NullPointerException if value is not present and {@code other} is
     * null
     */
    public T orElseGet(Supplier other) {
        return value != null ? value : other.get();
    }

要强调一点的是,Supplier可以返回任务值,不一定是new一个对象,甚至可以是一个固定的字符串,比如:

public class SampleTest {
  public static void main(String[] args) {
    Supplier strSupplier = SampleTest::getDefault;
        System.out.println(strSupplier.get());
    }

    private static String getDefault() {
        return "aaaa";
    }
}

 总之,这里始终强调的一点就是lambda代码即函数的思想

自定义lambda函数

上面表格中的六个函数,你会发现并没有一一讲完,因为它们表达的都是一个思想,代码即函数。除了这些之外,还有更多细分专业场景下的比函数,比如针对包装类优化的

  • DoubleConsumer
  • BooleanSupplier
  • IntFunction
  • LongPredicate

等函数。这些jdk提供的默认函数,其它就是把一般场景中做了抽象,用于判断的Predicate,用于获得值的Supplier,用过转换的Function。如果有特殊需求,完全可以自定义lambda函数,只要符合lambda的定义即可!比如,也来定义一个Supplier实现一个相同的功能:

@FunctionalInterface
public interface SupplierV2

{ P getV2(); } public class SampleTest { public static void main(String[] args) { Supplier strSupplier = SampleTest::getDefault; System.out.println(strSupplier.get()); // 自定义lambda函数 SupplierV2 strSupplierV2 = SampleTest::getDefault; System.out.println(strSupplierV2.getV2()); } private static String getDefault() { return "aaaa"; } }

 后面会给出一个,更加复杂的模板方法中使用lambda的例子!

你可能感兴趣的:(#,lambda,java)