啥是函数式编程?

概述:
是相较于 命令式编程 的一种编程范式;
是一种如何 搭建应用程序 的方法论,不是一种具体的技术;

具体的,能够熟练应用Stream流相关的API ,lamdba 表达式 到工作中,就说明你会函数式编程

函数式编程:关注做什么,说白了,内置函数库已经将需要的功能实现好了,直接用
命令式编程:关注怎么做,具体实现细节需要我们自己代码编写

为什么要使用函数式编程:
使用 lambda 能够使得代码更加的简洁

 Lambda 表达式

其实也是一个匿名函数,它不去关心接口名,方法名,参数名,而是关心参数类型,参数个数,返回值信息
形式上: (i) -> i *2
即:参数列表 -> 函数体

 本质:返回了一个 实现指定接口的对象实例

那么,是不是任意接口都是可以的呢?当然不是这样哈,这个接口必须是函数接口(java8新引入的接口特性)。

总结

Lambda 表达式在Java 语言中引入了一个新的语法元 素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符或剪头操作符。

它将 Lambda 分为 两个部分: 

左侧:指定了 Lambda 表达式需要的所有参数 

右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能。

 Jdk8 接口新特性

1.函数接口:@FunctionalInterface ,这个接口有且仅有一个 要实现的非静态方法,函数接口包含了多个未实现的方法的话,就会报错。

现在回头想想:箭头左边是函数接口的参数 ,箭头右边是这个函数接口的具体实现


2. 默认实现的方法
需要注意,函数接口并不是只能有一个方法,可以是
多个默认实现了方法 + 1个未实现的非静态方法 的形式 ,如下:

@FunctionalInterface
interface Interface1{
     
	// 1个未实现的非静态方法
    int doubleNum(int i);
	// 默认实现的方法
    default int add(int x ,int y){

        return x + y;
    }
}
  1. 在 函数接口多重继承的时候,如果出现 同名的默认实现的方法,这个时候需要指定具体是哪个类的默认方法。
    注意学习:接口的默认方法,如List

Java8 内置的函数接口

java已经帮我们定义好了一些常用的函数接口,就不用我们自己去定义了,我们仅关注自己的实现逻辑即可;同时函数接口也支持链式编程

定义更少的接口,可以更加灵活的操作

那么常见内置的函数接口有哪些呢?

Java8之Predicate函数

 

Predicate 一个函数式接口,属于java.util.function包,主要用来对输入的对象按照指定的条件进行过滤

大家不要对这种函数式接口抱有太大的畏惧心理,要我说,如果没有对应的场景,他就只是一堆代码而已,都是纸老虎!

Predicate就是一个接口,里面有且仅有一个抽象方法test方法:

boolean test(T t);

T是你传进去的参数,可以是任意类型,返回的是一个布尔值。意思就是:你给我一个值,我告诉你对不对?

这是一种操作,一种回调。它不是一种有方法体的函数,也就没有逻辑,逻辑是要你自己补充的。

我们先来一个简单的例子,有一个Person类,有name和sex字段,如果sex=男,我就输出“男孩”,简单吧!

class Person{
     public Person(String name, String sex) {
         this.name = name;
         this.sex = sex;
     }

     String name;
     String sex;
 }

然后去判断是不是男的

Person jack = new Person("jack","男");
if("男".equals(jack.sex)){
    System.out.println(jack.name + "是男的!");
}

答案:jack是男的!

那么,我们如何用Predicate来实现这个场景呢?

public void test2(){

    Person jack = new Person("jack","男");

    /** 新建一个判断是否为Boy的匿名子类对象,别忘了全部加泛型否则会报错 * */
    Predicate<Person> PredicateBody = new Predicate<Person>() {
        @Override
        public boolean test(Person o) {
            return "男".equals(o.sex);
        }
    };

    if(PredicateBody.test(jack)){
        System.out.println(jack.name + "是男的!");
    }
}

PredicateBody的生成方式可以换成Lamda,看起来更简洁一些。

IDEA还有贴心的提示,这也太牛了吧!

 Predicate<Person> PredicateBody = o -> "男".equals(o.sex);

什么,你还觉得不够爽?之前学了匿名子类对象的初始化,忍不住要试试?

行啊,试试就试试!

看代码:

if(new Predicate<Person>(){
    @Override
    public boolean test(Person person) {
        return "男".equals(person.sex);
    }
}.test(jack)){
    System.out.println(jack.name + "是男的!");
}

如果这种Predicate对象其他方法也需要用怎么办?有一个技巧就是单独做一个方法,返回这种独有的Predicate对象。

/** 好耶!做成static任何地方想用就用啦! * */
public static Predicate<Person> PredicateBody (){
    return new Predicate<Person>(){
        @Override
        public boolean test(Person person) {
            return "男".equals(person.sex);
        }
    };
}

然后是用的地方:

Person jack = new Person("jack","男");

if(PredicateBody().test(jack)){
    System.out.println(jack.name + "是男的!");
}

最后附上Predicate源码:

/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package java.util.function;

import java.util.Objects;

/**
 * Represents a predicate (boolean-valued function) of one argument.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #test(Object)}.
 *
 * @param <T> the type of the input to the predicate
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);

    /**
     * Returns a composed predicate that represents a short-circuiting logical
     * AND of this predicate and another.  When evaluating the composed
     * predicate, if this predicate is {@code false}, then the {@code other}
     * predicate is not evaluated.
     *
     * <p>Any exceptions thrown during evaluation of either predicate are relayed
     * to the caller; if evaluation of this predicate throws an exception, the
     * {@code other} predicate will not be evaluated.
     *
     * @param other a predicate that will be logically-ANDed with this
     *              predicate
     * @return a composed predicate that represents the short-circuiting logical
     * AND of this predicate and the {@code other} predicate
     * @throws NullPointerException if other is null
     */
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    /**
     * Returns a predicate that represents the logical negation of this
     * predicate.
     *
     * @return a predicate that represents the logical negation of this
     * predicate
     */
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }

    /**
     * Returns a composed predicate that represents a short-circuiting logical
     * OR of this predicate and another.  When evaluating the composed
     * predicate, if this predicate is {@code true}, then the {@code other}
     * predicate is not evaluated.
     *
     * <p>Any exceptions thrown during evaluation of either predicate are relayed
     * to the caller; if evaluation of this predicate throws an exception, the
     * {@code other} predicate will not be evaluated.
     *
     * @param other a predicate that will be logically-ORed with this
     *              predicate
     * @return a composed predicate that represents the short-circuiting logical
     * OR of this predicate and the {@code other} predicate
     * @throws NullPointerException if other is null
     */
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    /**
     * Returns a predicate that tests if two arguments are equal according
     * to {@link Objects#equals(Object, Object)}.
     *
     * @param <T> the type of arguments to the predicate
     * @param targetRef the object reference with which to compare for equality,
     *               which may be {@code null}
     * @return a predicate that tests if two arguments are equal according
     * to {@link Objects#equals(Object, Object)}
     */
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

Predicate自带的and方法怎么用?

 

看下源码:

default Predicate<T> and(Predicate<? super T> other) {
     Objects.requireNonNull(other);
     return (t) -> test(t) && other.test(t);
}

意思就是加一个other的断言规则,返回的是自己(当前规则)和other规则判定的最终值(已经是一个布尔值了)之间的并集。

举个例子,一个person对象,我需要去判断他的name是否是jack,并且sex是否是男,代码如下。

Person jack = new Person("jack","男");
if(new Predicate<Person>(){
    @Override
    public boolean test(Person person) {
        return "jack".equals(person.name);
    }
}.and(p -> "男".equals(p.sex)).test(jack)){
    System.out.println(jack.name + "是男的!");
}else{
    System.out.println("判断出错!");
}

以上代码可以得到预期的结果。

但是,相信很多人对and方法的源码已经看糊涂了,我这边再来点一点,其实刚才发生的事情就是这样:

Predicate<Person> p1 = new Predicate<Person>() {
    @Override
    public boolean test(Person person) {
        return "jack".equals(person.name);
    }
};

Predicate<Person> p2 = t -> true && true;

&& 左右两边的条件必须都为true,结果才是true。

至于为什么能这样写,站长也觉得挺奇怪的,姑且就认为是Java8对lamda内部做的特别处理吧,有点像lamda的扩展语法。也就是说,lamda支持 “参数平移”,下面的这个例子,&&右边的逻辑相当于左边lamda的扩展,是一段用于参数 t 的 新逻辑。不得不说, 这真的很巧妙。

Person rose = new Person("rose","女");
Predicate<Person> isGirlAndRose = t -> "女".equals(t.sex) && "rose".equals(t.name);
if(isGirlAndRose.test(rose)){
    System.out.println("是rose!");
}

函数式接口总结

1.如果一个接口里面有且仅有一个未实现的方法,它就是函数式接口,你可以直接用lamda来完成new的操作。

2.如果一个方法的返回值是函数式接口,return的时候直接写个lamda即可。

3.如果参数是函数式接口,你可以直接写一个lamda,相当于传了一个匿名子类对象进去。

Lamda方法引用 ::(双冒号)

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使 Java的语言表达能力得到了提升。

方法引用 ::

方法引用是什么 ?

方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一

种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。

当Lambda表达式中只是执行一个方法调用时,不用Lambda表达式,直接通过方法引用的形式可读性更高一些。

  • 作用
    方法引用的唯一用途是支持Lambda的简写。
    方法引用提高了代码的可读性,也使逻辑更加清晰。
  • 组成
    使用::操作符将方法名和对象或类的名字分隔开。
    “::” 是域操作符(也可以称作定界符、分隔符)。

静态方法引用

ClassName::staticMethod

实例方法引用

objectName::instanceMethod

构造方法引用(构造器引用)

ClassName::new

举例:

//构建一个接收T a,T b并返回T的函数接口
@FunctionalInterface
public interface LambdaFunction<T> {
 
    T add(T a,T b);
}
 
 
 
public class Test {
 
    public static int add(int a,int b){
        return a+b;
    }
 
    public int add(int a,int b,LambdaFunction<Integer> lambdaFunction){
        return lambdaFunction.add(a,b);
    }
 
    public static void main(String[] args) {
        Test test = new Test();
        //使用lambda实现
        System.out.println(test.add(1,2,( a, b) ->a+b));
       //使用方法引用实现
        System.out.println(test.add(1,2,Test::add));
    }
}

 

posted on 2022-01-22 14:40  剽悍一小兔  阅读(18)  评论(0编辑  收藏  举报  来源