欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

从零开始的java学习Day16----------基础篇(函数式编程思想、函数式接口)

程序员文章站 2022-05-28 15:01:34
...

函数式编程思想

在编程中,除了面向对象思想,还有函数式编程思想。面向对象强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。
Java中Lambda就是一种函数式编程思想的语法,例如我们之前使用的匿名内部类,就可以使用Lambda来替代。

Lambda标准格式:

(参数类型 参数名称) -> { 代码语句 }
格式说明:
  • 小括号内的语法与传统方法(抽象方法)参数列表一致:无参数则留空;多个参数则用逗号分隔
  • ->是新引入的语法格式,代表指向动作
  • 大括号内的语法与传统方法体要求基本一致
Lambda省略格式(在标准格式的基础上):
  • 参数类型可以省略
  • 如果小括号内的参数有且只有一个,小括号可以省略
  • 如果大括号内只有一个语句,无论是否有返回值,都可以省略(大括号、retunrn、分号)
    示例1(无参):
    //先定义一个接口,里面只有一个抽象方法
public interface JieK {
  void a();
}
//在主方法中调用
public class Demo2 {
  public static void main(String[] args) {
//这里a就是下面定义的方法,因为a需要传入JieK接口的时限类对象,所以可以使用Lambda
  	a(()->System.out.println("呵呵"));  //运行程序会输入"呵呵"
  }

//这里需要新建一个方法,这个方法需要传入的就上上面的接口
  static void a(JieK j){
  	j.a();
  }
}

示例2(有参):
用Lambda写comparator的比较器实现类方法(降序)

Integer[] arr ={2,5,7,8,9,1,3};
Arrays.sort((o1,o2)->o2-o1);
此时,arr数组里的数字,已经按照降序排序了

小结

  • Lambda只能作为代替要传入的一个接口的实现对象时使用,且该接口只有一个需要重写的方法
  • 当方法的形参,需要传入的是一个接口,且该接口有且只有一个抽象方法,才可以使用Lambda

函数式接口

函数式接口在java中指的就是有且仅有一个抽象方法的接口。(必须有一个抽象方法,其他方法或变量可以随意)
函数式接口,可以适用于函数式编程的接口,在Java中函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda的接口。

如何判断

判断一个接口是不是函数式接口,可以用@FunctionalInterface标识,能被它标识的接口就是函数式接口

使用场景

函数式接口的典型应用场景就是作为方法的参数和返回值

Lambda的延迟执行

我们在传递参数时,如果参数类型是函数式接口,等实际调用该方法时,我们用Lambda表达式当作这个接口对象传入进去,穿进去的时候,Lambda表达式不会立即执行,需要等到该方法调用函数式接口的方法时才会执行,这样就有避免掉了一些资源浪费(比如该参数实际不会被用到)

方法引用

在使用Lambda表达式的时候,我们实际上传进去的代码就是一种解决方案。那么考虑到一种情况,我们在Lambda中所指定的操作方案,已经有地方存在相同方案,那就没有必要再写重复逻辑了

方法引用符

双冒号::为引用运算符,而他所在的表达式被称为方法引用,是一种Lambda表达式的改进写法。条件是,Lambda表达式的函数方案已经存在于某个方法中,那么可以通过双冒号来引用该方法为Lambda的替代者
下面是几种使用格式

通过对象名引用成员方法

这是最常见的一种用法,当一个类中有某个方法是我们需要的函数方案,可以通过这个类的对象来引用
格式:

对象名::方法名
通过类名引用静态方法

常用于一些系统里已经有的类的静态方法,可以直接通过类名来引用
格式:

类名::方法名
例:Math::abs               //abs是Math类的静态方法
通过super引用父类方法

当我们操作的类存在继承关系,我们在使用Lambda时,需要引用父类中的方法,可以使用super调用
格式:

super::方法名
通过this引用成员方法

当我们操作的时候,想要引用自己类中的方法,可以使用this调用
格式:

this::方法名
类的构造器的引用

当我们使用Lambda时,需要实现的功能是要创建某个类的对象,就可以直接引用该类的构造方法来创建
格式:

类名::new
数组构造器的引用

同样的,当我们想要使用Lambda完成一个创建数组的功能时,也可以直接引用数组的构造器,注意,数组的构造器是需要传入长度(所以需要原使用的Lambda有int参数)
格式:

数组类型[]::new

常用函数接式口

JDK提供了大量的常用函数式接口以丰富Lambda的典型使用场景,他们主要在java.util.function包中被提供。下面是最简单的几个接口及使用示例

Supplier接口

该接口负责生产数据(无参数,有返回值),又叫做生产接口
Supplier该接口仅包含一个无参的方法:T get(),通过作为形参时指定数据类型,执行方法,返回一个对应的数据类型对象。
示例(求数组的最大值)

public static void main(String[] args) {
    int[] intArr =new int[]{1,3,4,5,6,22,3,4,19,-9};
    maxarr(() -> {
        int max = intArr[0];
        for (int i : intArr)
            if (i > max)
                max = i;
        return max;
    });
}
static public int maxarr(Supplier<Integer> max){
    return max.get();
}

该接口的方法只负责返回一个对应类型的对象,内部的方法还是要自己去书写

Consumer接口

Consumer此接口与Supplier 接口刚好相反,它负责消费指定类型数据(有参数,无返回值)
接口中包含一个抽象方法void accept(T t),意为消费一个指定泛型的数据
示例(输出打印传入的对象):

public static void main(String[] args) {
    XiaoFei((str)->{
        System.out.println(str);
    },"呵呵");
}
public static void XiaoFei(Consumer<String> consu,String str){
    consu.accept(str);
}
Consumer接口中还包含一个默认方法andThen,该方法可以实现一个数据消费两次

示例(输出打印传入的对象两次):

public static void main(String[] args) {
    XiaoFei("呵呵",(str)->{
        System.out.println(str);
    },(str)->{
        System.out.println(str+"1");
    });
}
public static void XiaoFei(String str,Consumer<String> con1,Consumer<String> con2){
    con1.andThen(con2).accept(str);     //这里哪个con对象写在前面,就会先执行哪个对象对应的Lambda内的方法
}

Predicate接口

Predicate该接口内的方法是能一个能得到boolean值的结果,我们叫它判断接口,当我们需要对某种类型的数据进行判断就可以使用
boolean test(T t)方法,判断传入的数据,返回一个布尔值
示例:

  public static void main(String[] args) {
  String st = "cdcda";
// 判断字符串是否包含"dd"
  boolean res = pand1(st, xx -> xx.contains("dd"));
  System.out.println(res);
  }

//对一个字符串进行判断
  static boolean pand1(String str, Predicate<String> a) {
  return a.test(str);
  }
Predicate接口内包含三个默认方法,分别是and,or和negate

and和or,逻辑上的"与"和"或"
当我们要对同一个数据进行多次比较时可以使用,可以链式编程,执行流程是从左往右
and等价&&符号
or等价||符号
格式示例:

    public static void main(String[] args) {
//        判断字符串是否包含"a"和"cn",或者包含"c"
        String st = "ab";//用它执行下列方法为true
        String st1 = "c";//用它执行下列方法也为true
        boolean res = pand1(st, xx -> xx.contains("a"),xx -> xx.contains("b"),
                xx -> xx.contains("c"));
        System.out.println(res);
    }

//对一个字符串进行3次判断
    static boolean pand1(String str, Predicate<String> a,Predicate<String> b,
                         Predicate<String> c) {
//先执行a.test(str)&&b.test(str)的结果,然后拿其值 ||c.test(str)的结果
        return a.and(b).or(c).test(str);
    }

nagate,逻辑取反,可以取反执行了test()后的值,注意,一定要在调用test方法调用它
格式:

nagate.test();等价于!test();
链式编程示例:
//设p1的lambda是判读数是否是偶数,p2的Lambda是判断数是否小于0
p1.nagate.or(p2).test(-1)      //该语句等价于:p1.nagate.test(-1)||p2.test(-1),结果为true

Function接口
Function<T,R>该接口的功能就是通过T,得到一个R类型的数据,我们通常称为转换接口
抽象方法:R apply(T t)
使用示例:

  public static void main(String[] args) {
  Integer i = 100;
  boolean b =true;
  System.out.println(deao(i, a -> a + ""));//输出100字符串
  System.out.println(deao(b, a -> a + ""));//输出ture字符串

  }
// 将任意数据转换成String
  static <T> String deao(T a ,Function<T,String> c){
  String a1 = c.apply(a);
  return a1;
  }

Function接口也内含默认方法andThen,和Consumer内的差不多,都是先执行前面的,再执行后面的,只是它里面的andThen,第二次操作的数是第一次返回的值
使用示例:

public static void main(String[] args) {
    boolean flag =true;
    char[] chars = deao2(flag, a -> a + "", b -> b.toCharArray());
    for (char c : chars) {
        System.out.println(c);
    }
}
//    将任意数据转换成String,再转为字符数组
static <T,R> char[] deao2(T a ,Function<T,R> c,Function<R,char[]> d){
    return c.andThen(d).apply(a);
}
相关标签: 从零开始学java