java8函数式编程实战

本文介绍了函数式编程的核心概念,包括不可变性如何解决并发问题,Lambda表达式的使用方式,以及函数式接口的特性。通过示例展示了Lambda的简洁语法,并解释了方法引用和未绑定方法引用的用法。此外,还探讨了函数组合,如`andThen`和`compose`等方法,以及如何在实际编程中应用这些概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

函数式编程

函数式编程(Functional Programming)

  • OO(object oriented,面向对象)是抽象数据
  • FP(Functional programming,函数式编程)是抽象行为

约束

函数式编程是安全的,它增加了一些约束,即所有的数据(变量、对象)必须是不可变的,设置一次,永不改变。不修改自身外部的任何东西(函数范围之外的元素)

优点

不可变对象范式解决了并发编程中棘手问题(当程序的某些部分在多处理器同时运行时),意味着不同的处理器可以尝试同时修改同一块内存。

如果函数永远不会修改现有值而是产生新值,则不会对内存产生争用,这是函数式语言天生的优点。

函数式语言作为并行编程的其中之一的解决方案。

Lambda 表达式

Lambda 表达式使用最小可能的语法编写的函数定义

  • Lambda 表达式产生函数,而不是类。
  • Lambda 语法尽可能少,这使 Lambda 易于编写和使用
interface Description {
  String brief();
}

interface Body {
  String detailed(String head);
}

interface Multi {
  String twoArg(String head, Double d);
}

public class LambdaExpressions {

  static Body bod = h -> h + " No Parens!";      // [1]

  static Body bod2 = (h) -> h + " More details"; // [2]

  static Description desc = () -> "Short info";  // [3]

  static Multi mult = (h, n) -> h + n;           // [4]

  static Description moreLines = () -> {         // [5]
    System.out.println("moreLines()");
    return "from moreLines()";
  };

  public static void main(String[] args) {
    System.out.println(bod.detailed("Oh!"));
    System.out.println(bod2.detailed("Hi!"));
    System.out.println(desc.brief());
    System.out.println(mult.twoArg("Pi! ", 3.14159));
    System.out.println(moreLines.brief());
  }
}

output:

Oh! No Parens!
Hi! More details
Short info
Pi! 3.14159
moreLines()
from moreLines()

语法

a -> c

a :参数

-> : 表示为产出

c : 方法体

上例中展示了 Lambda 的几种使用方法

[1] 当使用一个参数时,可以不需要括号()

[2] 使用括号 () 包裹参数

[3] 如果没有参数则必须使用括号 () 表示空参数列表

[4] 当使用多个参数,将参数列表包裹在括号 () 中

[5] 如果方法体一行写不完,需要使用花括号 {} 将方法体包裹其中,这种情况下需要使用return 来返回

方法引用

方法引用组成:类名或对象名 :: 方法名称

interface Callable {                        // [1]
    void call(String s);
}

class Describe {
    // show() 的签名(参数和返回类型)符合 Callable 的 call()签名
    void show(String msg) {                   // [2]
        System.out.println(msg);
    }
}

public class MethodReferences {
	// hello() 符合call() 签名
    static void hello(String name) {          // [3]
        System.out.println("Hello, " + name);
    }

    static class Description {
        String about;

        Description(String desc) {
            about = desc;
        }
		// help() 符合call() 签名,它是静态内部类中的非静态方法
        void help(String msg) {                 // [4]
            System.out.println(about + " " + msg);
        }
    }

    static class Helper {
        // assist() 是静态内部类中的静态方法
        static void assist(String msg) {        // [5]
            System.out.println(msg);
        }
    }

    public static void main(String[] args) {
        Describe d = new Describe();
        // 简单理解 映射,将describe.show() 映射给 Callable.call() 方法上
        Callable c = d::show;                   // [6]
        // 通过调用 call() 来调用 show(), java 将 call() 映射到show()
        c.call("call()");                       // [7]
		// 静态方法引用
        c = MethodReferences::hello;            // [8]
        c.call("Bob");
        // 映射:将Description.help()方法映射到 Callable.call() 上
        c = new Description("valuable")::help;  // [9]
        c.call("information");

        c = Helper::assist;                     // [10]
        c.call("Help!");
    }
}

output:

call()
Hello, Bob
valuable information
Help!

未绑定的方法引用

未绑定的方法引用是指没有关联对象的普通(非静态)方法。

使用未绑定的引用时,我们必须先提供对象

class X {
    String f() {
        return "X::f()";
    }
}

interface MakeString {
    String make();
}

interface TransformX {
    String transform(X x);
}

public class UnboundMethodReference {

    public static void main(String[] args) {
        // MakeString ms = X::f;  不可以使用,因为需要绑定方法引用       // [1]
        MakeString makeString = new MakeString() {
            @Override
            public String make() {
                return "hah";
            }
        };
        System.out.println(makeString.make());

        //  TransformX sp1 = X::f; 和下面表达的意思是一样的
        TransformX sp = new TransformX() {
            @Override
            public String transform(X x) {
                return x.f();
            }
        };

        X x = new X();
        System.out.println(sp.transform(x));    // [2]
        System.out.println(x.f()); // Same effect
    }
}
/* Output:
X::f()
X::f()
*/
  • [1] 我们尝试把 X 的 f() 方法引用赋值给 MakeString。
    • 虽然 make() 和 f() 具有相同的签名,但是编译不通过,会报无效引用(invalid method reference)错误。
    • 这是因为实际上还有一个隐藏的参数:this 。你不能在没有 X 对象的前提下调用 f() 。因此 X::f 表示未绑定的方法引用,因为它尚未绑定到对象。
    • 要解决这个问题,我们需要一个 X 对象,索引我们接口中需要一个额外的参数,如上例的 TransformX
    • 如果将 X :: f 赋值给 TransformX,在 Java 中是允许的。
  • 使用未绑定的引用时,函数式方法的签名(接口中的单个方法)不再与方法引用的签名完全匹配:原因是:需要一个对象来调用方法

未绑定方法与多参数的结合使用

class This {
    void two(int i, double d) {
    }

    void three(int i, double d, String s) {
    }

    void four(int i, double d, String s, char c) {
    }
}

interface TwoArgs {
    void call2(This athis, int i, double d);
}

interface ThreeArgs {
    void call3(This athis, int i, double d, String s);
}

interface FourArgs {
    void call4(This athis, int i, double d, String s, char c);
}

public class MultiUnbound {

    public static void main(String[] args) {
        TwoArgs twoargs = This::two;
        ThreeArgs threeargs = This::three;
        FourArgs fourargs = This::four;

        This athis = new This();
        twoargs.call2(athis, 11, 3.14);
        threeargs.call3(athis, 11, 3.14, "Three");
        fourargs.call4(athis, 11, 3.14, "Four", 'Z');
    }
}

构造函数引用
class Dog {
    String name;
    int age = -1; // For "unknown"

    Dog() {
        name = "stray";
    }

    Dog(String nm) {
        name = nm;
    }

    Dog(String nm, int yrs) {
        name = nm;
        age = yrs;
    }
}

interface MakeNoArgs {
    Dog make();
}

interface Make1Arg {
    Dog make(String nm);
}

interface Make2Args {
    Dog make(String nm, int age);
}

public class CtorReference {

    public static void main(String[] args) {
       MakeNoArgs makeNoArgs = Dog::new;
       Make1Arg make1Arg = Dog::new;
       Make2Args make2Args = Dog::new;

       Dog noArgs = makeNoArgs.make();
       Dog args1 = make1Arg.make("ha");
       Dog args2= make2Args.make("11",2);

    }
}

函数式接口

  • @FunctionalInterface 函数式接口:限制接口中只能存在一个抽象方法

  • java 8黑魔法:自动适配函数式接口(适配你的值到目标接口),编译器会在后台把方法引用或lambda表达式包装进实现目标接口的类中

@FunctionalInterface
interface Functional {
    String goodbye(String arg);
}

interface FunctionalNoAnn {
    String goodbye(String arg);
}

// @FunctionalInterface 作用:接口中如果有多个抽象方法则会产生编译期错误
/*
@FunctionalInterface
interface NotFunctional {
  String goodbye(String arg);
  String hello(String arg);
}
Produces error message:
NotFunctional is not a functional interface
multiple non-overriding abstract methods
found in interface NotFunctional
*/

public class FunctionalAnnotation {

    public String goodbye(String arg) {
        return "Goodbye, " + arg;
    }

    public static void main(String[] args) {

        FunctionalAnnotation fa = new FunctionalAnnotation();
// TODO: 2021/8/30 java 8黑魔法:自动适配函数式接口(适配你的值到目标接口),编译器会在后台把方法引用或lambda表达式包装进实现目标接口的类中
        Functional f = fa::goodbye;
        FunctionalNoAnn fna = fa::goodbye;

        // Functional fac = fa; // Incompatible
        Functional fl = a -> "Goodbye, " + a;
        FunctionalNoAnn fnal = a -> "Goodbye, " + a;
    }
}

函数式接口命名准则
  1. 如果只处理对象而非基本类型,名称则为 Function,Consumer,Predicate 等。参数类型通过泛型添加

  2. 如果接收的参数是基本类型,则由名称的第一部分表示,如 LongConsumer,DoubleFunction,IntPredicate 等,但返回基本类型的 Supplier 接口例外。

  3. 如果返回值为基本类型,则用 To 表示,如 ToLongFunction 和 IntToLongFunction。

  4. 如果返回值类型与参数类型一致,则是一个运算符:单个参数使用 UnaryOperator,两个参数使用 BinaryOperator。

  5. 如果接收两个参数且返回值为布尔值,则是一个谓词(Predicate)。

  6. 如果接收的两个参数类型不同,则名称中有一个 Bi。

特征函数式方法名示例
无参数;无返回值Runnable (java.lang) run()Runnable
无参数;返回类型任意Supplier get() getAs 类型 ()Supplier
BooleanSupplier
IntSupplier
LongSupplier
DoubleSupplier
无参数;返回类型任意Callable(java.util.concurrent)call()Callable
1 参数;无返回值Consumer accept()Consumer
IntConsumer
LongConsumer
DoubleConsumer
2 参数 ConsumerBiConsumer accept()BiConsumer<T,U>
2 参数 Consumer;1引用;1 基本类型Obj 类型 Consumer accept()ObjIntConsumer
ObjLongConsumer
ObjDoubleConsumer
1 参数;返回类型不同Function apply() To 类型和 类型 To 类型<applyAs 类型 ()Function<T,R>
IntFunction
LongFunction
DoubleFunction
ToIntFunction
ToLongFunction
ToDoubleFunction
IntToLongFunction
IntToDoubleFunction
LongToIntFunction
LongToDoubleFunction
DoubleToIntFunction
DoubleToLongFunction
1 参数;返回类型相同UnaryOperator apply()UnaryOperator
IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator
2 参数类型相同;返回类型相同BinaryOperator apply()BinaryOperator
IntBinaryOperator
LongBinaryOperator
DoubleBinaryOperator
2 参数类型相同; 返回整型Comparator (java.util)compare()Comparator
2 参数;返回布尔型Predicate test()Predicate
BiPredicate<T,U>
IntPredicate
LongPredicate
DoublePredicate
参数基本类型;返回基本类型类型 To 类型 Function applyAs 类型 ()IntToLongFunction
IntToDoubleFunction
LongToIntFunction
LongToDoubleFunction
DoubleToIntFunction
DoubleToLongFunction
2 参数类型不同Bi 操作 (不同方法名)BiFunction<T,U,R>
BiConsumer<T,U>
BiPredicate<T,U>
ToIntBiFunction<T,U>
ToLongBiFunction<T,U>
ToDoubleBiFunction

使用函数式接口时,名称无关紧要,只要参数类型和返回类型相同即可,Java会将你的方法映射到接口方法。

等同 final 效果

// 如果局部变量的初始值永远不会改变,那么它实际上就是 final 的。
class Closure2 {
    // TODO: 2021/8/30  x等同 final 效果
    IntSupplier makeFun(int x) {
        int i = 0;
        return () -> x + i;
    }
}

class Closure3 {
    IntSupplier makeFun(int x) {
        int i = 0;
        // TODO: 2021/8/30 被 Lambda 表达式引用的局部变量必须是 final 或者是等同 final 效果的
        //return () -> x++ + i++; error
        return () -> x;
    }
}

class Closure4 {
    IntSupplier makeFun(final int x) {
        final int i = 0;
        return () -> x + i;
    }
}

函数组合

组合方法支持接口
andThen(argument) 根据参数执行原始操作Function BiFunction Consumer
BiConsumer IntConsumer
LongConsumer DoubleConsumer
UnaryOperator IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator
BinaryOperator
compose(argument) 根据参数执行原始操作Function UnaryOperator
IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator
and(argument) 短路逻辑与原始谓词和参数谓词Predicate BiPredicate IntPredicate
LongPredicate DoublePredicate
or(argument) 短路逻辑或原始谓词和参数谓词Predicate BiPredicate IntPredicate
LongPredicate DoublePredicate
negate() 该谓词的逻辑否谓词Predicate BiPredicate IntPredicate
LongPredicate DoublePredicate
public class FunctionComposition {

    // TODO: 2021/8/30 函数组合
    static Function<String, String> f1 = s -> {
        System.out.println(s);
        return s.replace('A', '_'); },
            f2 = s -> s.substring(3),
            f3 = s -> s.toLowerCase(),
            f4 = f1.compose(f2).andThen(f3);

    public static void main(String[] args) {
        System.out.println(
                f4.apply("GO AFTER ALL AMBULANCES"));
    }
}
/* Output:
AFTER ALL AMBULANCES
_fter _ll _mbul_nces
*/

public class PredicateComposition {
    // TODO: 2021/8/30 短路 与 或 非
    static Predicate<String>
            p1 = s -> s.contains("bar"),
            p2 = s -> s.length() < 5,
            p3 = s -> s.contains("foo"),
            p4 = p1.negate().and(p2).or(p3);

    public static void main(String[] args) {
        Stream.of("bar", "foobar", "foobaz", "fongopuckey")
                .filter(p4)
                .forEach(System.out::println);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值