Java函数式接口(函数式编程、常用的函数式接口)

1.函数式接口

接口中有一种特殊的接口,这类接口中只有一个抽象方法。我们把这种接口称为函数式接口(FunctionalInterface) 可以使用 @FunctionalInterface 标记函数接口 函数接口表示某个功能(能力)

函数式接口:有且只有一个抽象方法的接口,称之为函数式接口

当然接口中可以包含其他的方法(默认,静态,私有)

1.1 函数式接口的定义

package com.finterface;
/*@FunctionalInterface注解:
        作用:是检测接口是否是一个函数式接口
     注意:如果接口中没有抽象方法或者抽象方法的个数多余1个,该注解则会报错
* */
@FunctionalInterface
public interface MyFunctionalInterface {
    //定义一个抽象方法
    public abstract void method();	// 同样public abstract可以省略。。
}

由于接口当中的抽象方法 public abstract是可以省略的,所以定义一个函数式接口很简单:

@FunctionalInterface
public interface MyFunctionalInterface {
    //定义一个抽象方法
    void method();
}

1.2 函数式接口的使用

注意:这里我省略了上面4.1中的接口的实现类MyFunctionalInterfaceImpl

package com.finterface;
/*函数式接口的使用:一般可以作为方法的参数和返回值类型*/
public class Demo {
    
    //定义一个方法,参数使用函数式接口MyFunctionalInterface
    public static void show(MyFunctionalInterface inter){
        inter.method();
    }
    
    public static void main(String[] args) {
        //调用show方法,方法参数是一个接口,所以可以传递接口的实现类对象
        show(new MyFunctionalInterfaceImpl());
        
        //调用show方法,方法参数是一个接口,所以我们可以传递接口的匿名内部类
        show(new MyFunctionalInterface() {
            @Override
            public void method() {
                System.out.println("使用匿名内部类重写接口中的抽象方法");
            }
        });
        
        //调用show方法,方法参数是一个函数式接口,所以我们可以使用lambda表达式
        show(()->{
            System.out.println("使用lambda表达式重写接口中的抽象方法");
        });
        
        //如果上面的这个代码中有且仅有这一条语句,那么里面{}括号可以省略,而且输出语句后面的;也可省略
        show(()->
            System.out.println("简化后的lambda表达式。")
        );
    }
}

2.函数式编程

2.1 Lambda的延迟执行

有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式,是延迟执行的,这正好可以作为解决方案,提升性能。

性能浪费的日志案例:

注:日志可以帮助我们快速的定位问题,记录程序运行过程中的情况,以便项目的监控和优化。

一种典型的场景就是对参数进行有条件使用,例如对日志消息进行拼接后,满足条件的情况下进行打印输出:

package com.Lambda;
/*日志案例
*   通过测试发现以下代码存在一些性能浪费问题:
*       通过调用show方法,传递的第二个参数是一个拼接后的字符串,但是如果show方法中传递的日志等级不是1级
*       那么就不会打印输出拼接后的字符串,所以就感觉字符串白拼接了,存在了浪费。*/
public class DemoLogger {
    //定义一个根据日志的等级,来显示日志信息的方法
    public static void show(int level,String massage){
	//对日志等级进行判断
        if (level == 1) {
            System.out.println(massage);
        }
    }
    public static void main(String[] args) {
        //定义三个日志信息
        String msg1 = "hello  ";
        String msg2 = "world ";
        String msg3 = "java";
        show(1,msg1+msg2+msg3);
    }
}

下面我们就来使用lambda来优化上面的日志案例:

//定义一个函数式接口
@FunctionalInterface
public interface MessageBuilder {
    String buildMessage();
}
package com.Lambda;
/*使用lambda表达式进行优化日志案例:
*       lambda的特点:延迟加载
*       lambda的使用前提:必须存在函数式接口*/
public class DemoLambda {
    //定义一个显示日志的方法,方法的参数传递日志的等级和MessageBuilder接口中的builderMessage方法
    public static void showLogger(int level,MessageBuilder builder){
        if (level == 1) {
            System.out.println(builder.buildMessage());
        }
    }

    public static void main(String[] args) {
        String msg1 = "hello  ";
        String msg2 = "world ";
        String msg3 = "java";
        //调用showLogger方法,参数MessageBuilder是一个函数式接口,所以可以传递lambda表达式
        showLogger(1,()->{
            return msg1+msg2+msg3;
        });
        /*使用lambda表达式作为参数传递,仅仅是把参数传递到showLogger方法中
        * 只有满足条件,才会调用MessageBuilder中的方法buildMessage(),才会进行字符串的拼接
        * 如果条件不满足,则MessageBuilder接口中的方法buildMessage()不会执行
        *       所以拼接的字符串也不会执行,所以不存在性能的浪费。*/
    }
}

2.2 函数式接口作为方法的参数和返回值类型

作为方法的参数就和上面讲过的一样。

如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式,当需要通过一个方法来获取一个java.util.Comparator接口类型的对象作为排序器时,就可以调用该方法获取:

package com.Lambda;
import java.util.Arrays;
import java.util.Comparator;
public class DemoComparator {
    //定义一个方法,方法的返回值类型使用函数式接口Comparator
    public static Comparator<String> getComparator() {
        
        //方法的返回值类型是一个接口,那么我们可以返回这个接口的匿名内部类
        /*return new Comparator() {
            @Override
            public int compare(String o1, String o2) {
                //按照字符串的降序排序
                return o2.length()-o1.length();
            }
        };*/
        //对上面代码的优化:方法的返回值类型是一个函数式接口,所以我们可以返回一个lambda表达式
        /*return (String o1,String o2)->{
            return o2.length()-o1.length();
        };*/
        
        //继续优化lambda表达式
        return ((o1, o2) -> o2.length()-o1.length());
    }

    public static void main(String[] args) {
        //创建一个字符串数组
        String[] arr = {"aaaa","bb","cccccccc","dedsass"};
        //输出排序前的数组
        System.out.println(Arrays.toString(arr));//[aaaa, bb, cccccccc, dedsass]
        //调用Arrays数组中的sort方法,对字符串数组进行排序
        Arrays.sort(arr,getComparator())z;
        //输出排序后的数组
        System.out.println(Arrays.toString(arr));//[cccccccc, dedsass, aaaa, bb]
    }
}

3.常用的函数式接口

JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在 java.util.function包中被提供。

3.1 Supplier接口

java.util.function.Supplier接口仅包含一个无参的方法:T get().用来获取一个泛型参数指定类型的对象数据。

import java.util.function.Supplier;
/*常用函数式接口
*      java.util.function.Supplier接口仅包含一个无参构造方法get()
*   Supplier接口称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据*/
public class DemoSupplier {
    //定义一个方法,方法的参数传递Supplier接口,泛型执行String,get方法就会返回一个String类型的
    public static String getString(Supplier<String> sup){
        return sup.get();
    }

    public static void main(String[] args) {
        //调用getString方法,方法的参数Supplier是一个函数式接口,所以可以传递lambda表达式
        String s = getString(()->{
            //生产一个字符串,并返回
            return "张三";
        });
        System.out.println(s);
        //优化lambda表达式:
        String s2 = getString(()->"张三");
    }
}

3.2 Consumer接口

java.util.function.Consumer接口则正好与supplier接口相反,他不是产生一个数据,而是消费一个数据,其数据类型由泛型决定。

3.2.1 抽象方法:accept

Consumer接口中包含抽象方法 void accept(T t),意为消费一个指定泛型的数据。

Consumer接口是一个消费型接口,泛型执行什么类型,就可以使用accept方法消费什么类型的数据。

import java.util.function.Consumer;
public class DemoConsumer {
    /*定义一个方法:
    *   方法的参数传递Consumer接口,泛型使用String
    *   可以使用Consumer接口消费字符串的姓名*/
    public static void method(String name, Consumer<String> con){
        con.accept(name);
    }

    public static void main(String[] args) {
        //调用method方法,传递字符串姓名,方法的拎一个参数是Consumer接口,是一个函数式接口,所以可以传递Lambda表达式
        method("王可六",(String name)->{
            //对传递的字符串进行消费
            //消费方式:直接输出字符串
            System.out.println(name);//王可六
            //补充消费方式:把字符串进行反转输出(了解)
            String reName = new StringBuffer(name).reverse().toString();
            System.out.println(reName);//六可王
        });
    }
}
3.2.3 默认方法:andThen

如果一个方法的参数和返回值类型都是Consumer类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是Consumer接口中的default方法andThen。

备注:java.util.ObjectsrequireNonNull静态方法将会在参数为null时主动抛出 NullPointerException异常。这省去了重复编写if语句和抛出空指针异常的麻烦。

要想实现组合,需要两个或多个Lambda表达式即可,而 andThen的语义正是一步接一步操作。例如两个步骤组合的情况:

package com.Lambda;
import java.util.function.Consumer;
/*
* Consumer接口的默认方法andThen
* 作用:需要两个Consumer接口,可以把两个Consumer接口组合到一起,再对数据进行消费
* 例如:
*       Consumer con1
*       Consumer con2
*       con1.accept(s)
*       con2.accept(s)
*          连接两个Consumer接口:
*       con1.andThen(con2).accept(s)---->谁写前边,谁就先进行消费*/
public class DemoAndThen {
    //定义一个方法,方法的参数传递一个字符串和两个Consumer接口
    public static void method(String s, Consumer<String> con1,Consumer<String> con2){
//        con1.accept(s);
//        con2.accept(s);
        //使用andThen方法,把两个Consumer接口连接到一起,然后再消费数据
        con1.andThen(con2).accept(s);
    }

    public static void main(String[] args) {
        method("hello",(t)->{
            //消费方式:把字符串转换为大写
            System.out.println(t.toUpperCase());//HELLO
        },(t)->{
            System.out.println(t.toLowerCase());//hello
        });
    }
}

3.3 Predicate接口

有时候我们需要对某种类型的数据进行判断,从而达到一个Boolean值结果,这时可以使用 java.util.function.Predicate接口。

3.3.1 抽象方法:test

Predicate接口中包含一个抽象方法:boolean test(T t).用于条件判断的场景:

package com.Lambda;

import java.util.function.Predicate;
/*boolean  test(T t):用来对指定数据类型进行判断的方法
*   结果:符合条件返回true,否则返回false*/
public class DemoPredicate {
    /*定义一个方法:
    *       参数传递一个String类型的字符串,传递一个predicate接口,泛型使用String
    *       使用predicate中的方法test对字符串进行判断,并把判断的结果返回*/
    public static Boolean checkString(String s, Predicate<String> pre){
        return pre.test(s);
    }

    public static void main(String[] args) {
        String s = "asdfqwq";
        boolean b = checkString(s,(String str)->{
            //对传递的字符串进行判断,判断字符串的长度是否大于5,并把判断结果返回。
            return str.length()>5;
        });
        //优化lambda表达式
        boolean b2 = checkString(s,str->str.length()>5);
        System.out.println(b2);//true
    }
}
3.3.2 默认方法:and

既然是条件判断,就会存在与或非三种常见的逻辑关系。其中将两个Predicate条件使用“与”逻辑连接起来实现“并且”的效果,可以使用default方法and

如果要判断一个字符串既要包含大写“H”,又要包含大写“W”,那么:

package com.Lambda;
import java.util.function.Predicate;
public class DemoPredicate_and {
    /*定义一个方法,方法的参数,传递一个字符串;传递两个Predicate接口:
    *       一个用于判断字符串的长度是否大于5
    *       一个用于判断字符串中是否包含a
    *       两个条件必须同时满足,所以用与来判断。*/
    public static boolean checkString(String s, Predicate<String> pre1,Predicate<String> pre2){
        //return pre1.test(s)&& pre2.test(s);
        return pre1.and(pre2).test(s);//等同于上面的一行代码
    }

    public static void main(String[] args) {
        String s = "abcdef";
        boolean b = checkString(s,(String str)->{
            //判断字符串长度是否大于5
            return str.length()>5;
        },(String str)->{
            //判断字符串中是否包含a
            return str.contains("a");
        });
        System.out.println(b);//true
    }
}
3.3.3 默认方法:or

与and的“与”类似,默认方法or实现逻辑关系中的“或”。

比如希望实现逻辑“字符串包含大写H或者包含大写W”,那么代码只需要将“and”修改为“or"名称即可,满足一个条件即可。其他不变:

public class DemoPredicate_or {
    public static boolean checkString(String s, Predicate<String> pre1,Predicate<String> pre2){
        //return pre1.test(s)|| pre2.test(s);
        return pre1.or(pre2).test(s);
    }

    public static void main(String[] args) {
        String s = "Wang";
        boolean b = checkString(s,(String str)->{
            return str.contains("A");
        },(String str)->{
            return str.contains("L");
        });
        System.out.println(b);//false
    }
}
3.3.3 默认方法:negate

“与”“或”已经了解了,剩下的“非”(取反),默认方法 negate

从实现中容易看出,它是执行了test方法之后,对结果Boolean值进行了“!”取反而已,一定要在 test方法调用之前调用 negate方法,正如and和or方法一样:

/*需求:判断一个字符串的长度是否大于5
*       如果长度大于5,则false
*       如果长度不大于5,则true*/
public class DemoPredicate_negate {
    public static boolean checkString(String s, Predicate<String> pre){
        //return !pre.test(s);
        return pre.negate().test(s);
    }

    public static void main(String[] args) {
        String s = "Wang";
        boolean b = checkString(s,(String str)->{
            return str.length()>5;
        });
        System.out.println(b);//true
    }
}

3.4 Function接口

java.util.function.Function接口用来根据一个类型得到另一个类型的数据,前者称为前置条件,后者称为后置条件。

3.4.1 抽象方法:apply

Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。

使用的场景例如:将 String类型转换为 Integer类型。

/*将 `String`类型转换为 `Integer`类型。*/
public class DemoFunctionApply {
    /*定义一个方法:
    *   方法的参数传递一个字符串类型的整数,一个Function接口,泛型使用
        使用Function接口中的方法apply,把字符串类型的整数,转换成Integer类型的整数*/
    public static void change(String s, Function<String,Integer> fun){
        //Integer num = fun.apply(s);//这句代码也是可以的
        int num = fun.apply(s);//这句代码使用了自动拆箱
        System.out.println(num);
    }

    public static void main(String[] args) {
        String s = "1234";
        change(s,(String str)->{
            //把字符串类型的整数,转换成Integer类型的整数返回
            return Integer.parseInt(str);
        });
        //优化lambda
        change(s,str->Integer.parseInt(str));
    }
}
3.4.2 默认方法:andThen
/*需求:
*       把字符串类型的“123”转换为Integer类型,结果+10
*       再把增加之后的Integer类型的数据,转换为String类型的
* 分析:共转换了两次
*       第一次是把字符串类型的“123”转换为Integer类型,所以使用Function fun1
                Integer i = fun1.apply("123")+10;
        第二次是把Integer类型的数据,转换为String类型,可以使用Function fun2
        *       String str = fun2.apply(i);
       *我们可使用andThen方法,把两簇转换组合在一起
       *        String s = fun1.andThen(fun2).apply("123");
       *    fun1先调用apply方法,把字符串转换为Integer
       *    fun2再调用apply方法,把Integer转换为字符串。*/
public class DemoFunctionAndThen {
    public static void change(String s, Function<String,Integer> fun1,Function<Integer,String> fun2){
        String str = fun1.andThen(fun2).apply(s);
        System.out.println(str);
    }
    public static void main(String[] args){
        String s = "123";
        change(s,(String str)->{
            return Integer.parseInt(str)+10;
        },(Integer i)->{
            return i+"";
        });
        //优化lambda表达式
        change(s,str->Integer.parseInt(str)+10,i->i+"");
    }
}

4.Stream流

public class DemoStream {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("张三");
        list.add("李四");
        list.add("张三丰");
        list.add("张无忌");
        //要求:先对list集合中的元素进行过滤,找出姓张的人,
        //然后对新得到的姓张的集合中的元素进行过滤,找出姓名长度为3的人
        //最后遍历最终的集合
        list.stream().filter(name->name.startsWith("张"))
                .filter(name->name.length()>2)
                .forEach(name-> System.out.println(name));//注意forEach也是函数式接口
    }
/*这里就是使用了stream流的方式遍历集合,对集合中的数据进行过滤
* 而不再使用之前的那种采用三次forEach循环来过滤了。*/
}

和以前collection操作不同,Stream操作还有两个基础的特征:

  • **Pipelining:**中间操作都会返回流对象本身。这样多个操作都会串联成一个管道。这样做可以对操作进行优化,比如延迟执行(laziness)和短路(short-circuiting)。
  • **内部迭代:**以前对集合遍历都是通过Iterator或者增强for的方式,显式的在集合外部进行迭代,这叫做外部迭代。Stream提供了内部迭代的方法,流可以直接调用遍历方法。

当使用流的时候,通常包括三个基本步骤:获取一个数据源(source)->数据转换->执行操作获取想要的结果,每次转换,原有Stream对象不改变,而是返回一个新的Stream对象(可以有多次转换)。

你可能感兴趣的:(Java编程,java,开发语言)