Java-Lambda表达式、方法引用与函数式接口

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

一、函数式编程的概述

函数式编程是一种编程范式,我们常见的编程范式有命令式编程(Imperative programming),函数式编程,逻辑式编程,常见的面向对象编程是也是一种命令式编程。
命令式编程是面向计算机硬件的抽象,有变量(对应着存储单元),赋值语句(获取,存储指令),表达式(内存引用和算术运算)和控制语句(跳转指令)。
而对于函数式编程,它是将事务与事物之间的运算过程进行抽象,形成了数学中函数的映射关系,即,输入值能对应输出值的y=f(x),用来描述数据、函数之间的映射关系。

二、Lambda表达式

事实上,Lambda表达式就是函数式编程思想的一种实现,也是匿名内部类的简化(关于内部类,请见本专栏《Java基础》一文)。
Lambda表达式是JAVA8中提供的一种新的特性,是一个匿名函数方法。可以把Lambda表达式理解为一段可以传递的代码(允许将函数作为一个方法的参数,使得代码像数据一样传递),可以写出更简洁、更灵活的代码。

(一)使用步骤

1.使用条件

Lambda表达式可认为是对匿名内部类的一种简化,但并非所有的匿名内部类都可简化为Lambda表达式,只有函数式接口(需要加上@Functionalinterface进行声明,后面会写)的匿名内部类才可使用Lambda表达式进行简化。
函数式接口不同于普通接口,其接口当中只有一个抽象方法需要去实现,Lambda就可完成这一实现。

2.基本语法

(参数列表)->{函数体}

其中,"->"作为分隔符来分隔参数和Lambda表达式的主体。
重要特征:

  • 不需要声明参数类型,编译器会自动识别。即:
int(x,int y)->{return x+y;}
//等价于
(x,y)->{return x+y;}
  • 一个参数无需定义圆括号,但无参数、多个参数需要定义。
//单个参数可不用圆括号
msg->{System.out.println(msg);}
//多个参数、无参数需要使用圆括号
(msg1,msg2)->System.out.println(""+msg1+msg2);
  • 若函数体中只有一个语句,就可不写花括号。
(msg)->System.out.println(msg)
  • 若函数体只有一个语句,且该语句的结果就是函数返回值,则可不使用return语句。
(x,y)->x+y;

3.作用域

Lambda表达式可以看做是匿名内部类实例化的对象,其访问权限和匿名内部类一样,可以访问外层函数局部变量、局部引用、静态变量、实例变量。
其中,外层函数局部变量有着特殊的要求,即,只能引用标记了final的外层局部变量,或是不在Lambda的内部对外层的局部变量进行修改(隐形的final),否则会编译错误,且,不允许在Lambda表达式内部声明一个与外层同名的参数或变量例:

public class TestFinalVariable {

    interface VarTestInterface{
        Integer change(String str);
    }

    public static void main(String[] args) {
       //局部变量不使用final修饰
        Integer tempInt = 1;
        VarTestInterface var = (str -> Integer.valueOf(str+tempInt));
        //再次修改,不符合隐式final定义
        tempInt =2;
        Integer str =var.change("111") ;
        System.out.println(str);
    }
}

以上代码会出现编译错误,应当改成:

public class TestFinalVariable {

    interface VarTestInterface{
        Integer change(String str);
    }

    public static void main(String[] args) {
       //局部变量不使用final修饰
        Integer tempInt = 1;
        VarTestInterface var = (str -> Integer.valueOf(str+tempInt));
        Integer str =var.change("111") ;
        System.out.println(str);
    }
}

原因
局部变量是保存在栈帧中的。而在Java的线程模型中,栈帧中的局部变量是线程私有的,如果允许Lambda表达式访问到栈帧中的变量地址(可改变的局部变量),则会可能导致线程私有的数据被并发访问,造成线程不安全问题。

对于引用类型的局部变量,因为Java是值传递,又因为引用类型的指向内容是保存在堆中,是线程共享的,因此Lambda表达式中可以修改引用类型的局部变量的内容,而不能修改该变量的引用。
对于基本数据类型的变量,在 Lambda表达式中只是获取到该变量的副本,且局部变量是线程私有的,因此无法知道其他线程对该变量的修改,如果该变量不做final修饰,会造成数据不同步的问题。

但是实例变量,静态变量不作限制,因为实例变量,静态变量是保存在堆中(Java8之后),而堆是线程共享的。在Lambda表达式内部是可以知道实例变量,静态变量的变化。

4.Lambda表达式和匿名内部类的对比

类型不同:

  • 匿名内部类:可以实现接口、抽象类,还可是具体的类。
  • Lambda表达式:只能实现接口。
    使用限制不同:
  • Lambda表达式实现的接口需要使用@Functionalinterface注解进行声明。
  • 匿名内部类实现接口并无限制。
    实现原理不同:
  • Lambda表达式编译后并无单独的字节码文件,它对应的字节码文件在运行时自动生成。
  • 匿名内部类:编译后会产生对应的字节码文件。

(二)案例

案例1

//Man.java函数式接口
package org.example.polo;

@FunctionalInterface
public interface Man {
    void say(String words);
}

//测试类Demo
package org.example.SimpleCode;

import org.example.polo.Man;
import org.junit.Test;

public class Demo {
    @Test
    public void test(){
        //使用匿名内部类直接实现函数式接口
        Man man=new Man() {
            @Override
            public void say(String words) {
                System.out.println(words);
            }
        };
        //使用Lambda表达式简化实现函数式接口
        Man man1=(words)->{
            System.out.println(words);
        };
        man.say("Hello World");
        man1.say("Hello World");
    }
}

案例2

//精简之前:
Arrays.sort(grade,new Comparator<Integer>(){
    @Override
    public int compare(Integer o1,Integer o2){
        return o2 -o1;
}
});
//精简之后:
Arrays.sort(grade,(Integer o1,Integer o2) -> {
        return o2 -o1;
});

三、函数式接口

函数式接口是一类特殊类型的接口,这类接口中只定义了唯一的抽象方法,但可有多个静态方法和默认方法,故而,称为函数类型接口(即,接口的功能和其中的唯一抽象函数是对应的)。
且,函数式接口可作为函数参数进行传递(使用时该方法时就需要实现该接口),这也是函数式编程的特点。
可在任意函数式接口上使用@FunctionalInterface注解,来检查是否是一个函数式接口。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

Java常用的函数式接口一共有五种:

  • 自定义函数式接口
  • Consumer:消费型接口,包含方法accept。
  • Supplier:供给型接口 ,包含方法get。
  • Function<T,R>:函数型接口,对类型为T的对象应用操作并返回R类型的结果,包含方法apply。
  • Predicate:断定型接口,确定类型为T的对象是否满足条件约束,包含方法test。

(一)自定义函数式接口

使用注解可自定义一个函数式接口,其可作为参数传入方法。
函数式接口SingleAbstractMethodInterface.java

@FunctionalInterface
public interface SingleAbstractMethodInterface {
    public abstract void singleMethod();
}

定义test类,并将函数式接口实现后作为参数传入方法:

public class Test {
    public void testMethod(SingleAbstraMethodInterface single){
        System.out.println("即将执行函数式接口外部定义方法");
        single.singleMethod();
    }
    public static void main(String[] args) {
        Test test = new Test();
        test.testMethod(new SingleAbstraMethodInterface() {
            @Override
            public void singleMethod() {
                System.out.println("执行函数式接口定义方法");
            }
        });
    }
}

事实上,亦可使用Lambda表达式:

    @Test
    public void test() {
        SingleAbstractMethodInterface singleAbstractMethodInterface=()-> System.out.println("执行函数式接口定义方法");
        singleAbstractMethodInterface.singleMethod();
    }

(二)Consumer函数式接口

源码

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

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

1.函数方法accept

void accept(T t);

作用:消费一个数据,并执行相关操作
例:

    public void reverse(String Str, Consumer<String>consumer){
        //声明一个方法,需要传入函数式接口的实现作为参数,来完成相关操作
        consumer.accept(Str);
    }
    @Test
    public void test() {
        String str="ABCD";
        reverse(str,s -> {
            String res="";
            for(int i=s.length()-1;i>=0;i--)res+=s.charAt(i);
            System.out.println("字符串反转后为:"+res);
        });
    }

2.默认方法andThen

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }

作用:默认方法andThen,需要两个Consumer接口,返回值是Consumer接口类型,可将两个Consumer接口组合到一起再消费数据,且,谁先写前面,谁先被消费。
例:

    public void reverse(String Str1, Consumer<String>consumer1,Consumer<String>consumer2){
        //andThen先执行本consumer中的accept方法,再调用第二个consumer中的方法,且,两个consumer均是对同一数据进行操作
        //先执行consumer1,再执行consumer2,且均对Str1进行操作
        consumer1.andThen(consumer2).accept(Str1);
    }
    @Test
    public void test() {
        String str="ABCD";
        reverse(str,s -> {
        	//实现consumer1中的方法函数
            String res="";
            for(int i=s.length()-1;i>=0;i--)res+=s.charAt(i);
            System.out.println("Consumer1中是字符串反转:"+res);
        },t->{
        	//实现consumer2中的方法函数
            System.out.println("Consumer2中是直接输出"+t);
        });
    }

(三)Supplier函数式接口

@FunctionalInterface
public interface Supplier<T> {
    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

1.函数方法accept

只包含一个无参的方法,称为生产型接口,指定接口的泛型,get方法就会产生相应类型的数据。
例:

    //定义一个方法,用于获取int类型数组中元素的最大值,方法的参数传递Supplier接口,泛型使用Integer
    public int getMax(Supplier<Integer> sup){
        return sup.get();
    }
    @Test
    public void test1() {
        int arr[] = {100,23,456,-23,-90,0,-678,14};
        //调用getMax方法,方法的参数Supplier是一个函数式接口,所以可以传递Lambda表达式
        int maxValue = getMax(() -> {
            int max = arr[0];
            for (int i : arr) {
                if (i > max) {
                    max = i;
                }
            }
            //返回最大值
            return max;
        });
        System.out.println("数组中元素的最大值:"+maxValue);
    }

(四)Predicate函数式接口

public interface Predicate<T> {

    boolean test(T t);

    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);
    }

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

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }

    @SuppressWarnings("unchecked")
    static <T> Predicate<T> not(Predicate<? super T> target) {
        Objects.requireNonNull(target);
        return (Predicate<T>)target.negate();
    }
}

对某种数据类型的数据进行判断,并得到一个Boolean值结果。

1.函数方法test

boolean test(T t);

用于得到一个数据并进行判断。

    public boolean judge(String str,Predicate<String>predicate){
        return predicate.test(str);
    }
    @Test
    public void test1() {
        String str="ABC";
        if(judge(str,s->s.equals("ABD"))){
            System.out.println("str的值为ABD");
        }else {
            System.out.println("str的值不为ABD");
        }
    }

2.默认方法or

    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
     ```
参数为函数式接口Predicate,用于对结果进行与操作,例:

```java
    public boolean judge(String str,Predicate<String>predicate1,Predicate<String>predicate2){
        return predicate1.or(predicate2).test(str);
    }
    @Test
    public void test1() {
        String str="ABC";
        if(judge(str,s->"ABD".equals(str),t->"ABE".equals(str))){
            System.out.println("str即不为ABD,亦不为ABE");
        }else {
            System.out.println("str为ABD或ABE");
        }
    }

(五)Function函数式接口

用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
源码

@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

    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;
    }
}

1.抽象方法apply

import java.util.function.Function;

public class Demo01Function {
    public static void change(String s, Function<String,Integer> fun) {
        Integer in = fun.apply(s);
        System.out.println(in);
    }

    public static void main(String[] args) {
        String s = "1234";
        change(s,(String str)->{
            return Integer.parseInt(str);   //1234
        });
        change(s,str->Integer.parseInt(str));   //1234
    }
}

2.默认方法andThen

用来进行组合操作。

import java.util.function.Function;

public class Demo02AndThen {
    public static void change(String s,Function<String,Integer> fun1,Function<Integer,String> fun2) {
        String ss = fun1.andThen(fun2).apply(s);
        System.out.println(ss);
    }
    public static void main(String[] args) {
        String s = "123";
        change(s,(String str)->{
            return Integer.parseInt(s)+10;
        },(Integer i)->{
            return i+"";    //133
        });
        //优化
        change(s,str->Integer.parseInt(s)+10,i->i+"");  //133
    }
}

四、方法引用

方法引用可认为是Lambda表达式的一种特殊形式,Lambda表达式可让开发者自定义抽象方法的实现代码,而方法引用则可让开发者直接引用已存在的实现方法,作为Lambda表达式的Lambda体。

使用条件

  • 被引用方法必须已经存在。
  • 被引用处必须是函数式接口。
  • 被引用方法的形参和返回值必须和抽象方法保持一致。
  • 被引用的方法的功能需要满足当前条件。
  • 被引用方法不能是静态方法。

语法格式

类名::方法名
//其中,"::"称为方法引用符

例:

package org.example.SimpleCode;
import java.util.*;
import java.util.function.*;
import java.util.stream.Stream;

public class Demo {
    public static void main(String[] args) {
        Integer[] arr={3,5,4,1,6,2};
        //匿名内部类写法
        Arrays.sort(arr, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2-o1;
            }
        });
        //Lambda表达式写法
        Arrays.sort(arr,(Integer o1,Integer o2)->{
            return o2-o1;
        });
        //注:函数式接口会转化为Lambda表达式,此处参数列表提示了使用函数式接口,自然可使用Lambda表达式
        //Lambda表达式简化格式
        Arrays.sort(arr,(o1,o2)->o2-o1);
        //方法引用
        Arrays.sort(arr,Demo::subtraction);
    }
    public static int subtraction(int num1,int num2){
        return num2-num1;
    }
}

方法引用的分类

引用静态方法

格式

类名::静态方法
//例:Integer::parseInt

练习:集合中有以下数字:“1”、“2”、“3”、“4”、“5”,要求将它们变为int类型。

package org.example.SimpleCode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Function;

public class Demo {
    public static void main(String[] args) {
        ArrayList<String>arrayList=new ArrayList<>();
        Collections.addAll(arrayList,"1","2","3","4","5");
        //若不使用函数式接口
        arrayList.stream().map(new Function<String, Integer>() {
            //此处Function接口是函数式接口,故可使用方法引用
            @Override
            public Integer apply(String s){
                return Integer.parseInt(s);
            }
        }).forEach(s-> System.out.println(s));
        //若使用函数式接口
        arrayList.stream().map(Integer::parseInt).forEach(s-> System.out.println(s));
    }
}

引用成员方法

格式:

对象::成员方法

①引用其他类
其他类对象::方法名
例:只要以"张"开头,且名字是3个字的字符串。

StringOperation.java

public class StringOperation{
    public boolean stringJudge(String s){
        return s.startsWith("张")&&s.length()==3;
    }
}

测试类

package org.example.SimpleCode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Function;
import java.util.function.Predicate;

public class Demo {
    public static void main(String[] args) {
        ArrayList<String>arrayList=new ArrayList<>();
        Collections.addAll(arrayList,"张无忌","张强","张三丰","周芷若");
        //链式编程写法
        arrayList.stream().filter(s->s.startsWith("张")).filter(s->s.length()==3).forEach(s-> System.out.println(s));
        //使用函数式接口
        arrayList.stream().filter(new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.startsWith("张")&&s.length()==3;
            }
        }).forEach(s-> System.out.println(s));
        //使用对象成员方法
        arrayList.stream().filter(new StringOperation()::stringJudge).forEach(s-> System.out.println(s));
    }
}

②引用本类

this::方法名

注:静态方法中是无this的,故而只能通过创建本类对象来引用。即,引用处不能是静态方法。
例:

package org.example.SimpleCode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Predicate;

public class Demo {
    public boolean stringJudge(String s){
        return s.startsWith("张")&&s.length()==3;
    }
    public static void main(String[] args) {
        ArrayList<String>arrayList=new ArrayList<>();
        Collections.addAll(arrayList,"张无忌","张强","张三丰","周芷若");
        //链式编程写法
        arrayList.stream().filter(s->s.startsWith("张")).filter(s->s.length()==3).forEach(s-> System.out.println(s));
        //使用函数式接口
        arrayList.stream().filter(new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.startsWith("张")&&s.length()==3;
            }
        }).forEach(s-> System.out.println(s));
        //使用对象成员方法
        arrayList.stream().filter(new Demo()::stringJudge).forEach(s-> System.out.println(s));
    }
}

③引用父类

super::方法名

引用构造方法

格式:

类名::new

练习:集合中存储姓名和年龄的字符串,如,“张无忌,15”,要求将数据封装成Student对象并收集到List集合中。

package org.example.SimpleCode;
import org.example.polo.Student;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

public class Demo {
    public static void main(String[] args) {
        ArrayList<String>arrayList=new ArrayList<>();
        Collections.addAll(arrayList,"张无忌,15","张强,16","张三丰,20","周芷若,18");
        //常规写法
        List<Student>list=arrayList.stream().map(new Function<String, Student>() {
            @Override
            public Student apply(String s){
                String[] arr=s.split(",");
                String name=arr[0];
                int age=Integer.parseInt(arr[1]);
                return new Student(name,age);
            }
        }).collect(Collectors.toList());
        //方法引用,由于此时Student类中并无参数只有一个String的构造方法,故需要添加:public Student(String s);
        /*
        *     public Student(String s){
                String[] arr=s.split(",");
                this.Name=arr[0];
                this.age=Integer.parseInt(arr[1]);
              }
        * */
        arrayList.stream().map(Student::new).forEach(s-> System.out.println(s));
    }
}

注,本文部分参考:https://blog.csdn.net/huangjhai/article/details/107110182

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值