【Java】《OnJava8》筆記——第13章函數式編程

《On Java 8》中文版,又名《Java編程思想》 第5版
接下來的都是個人學習過程的筆記,不是總結,沒有參考價值,但是這本書很棒

方法引用

未綁定方法

package chapter13;

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) {
        TransformX sp=X::f;
//        簡單說,就是告訴sp在調用transform的時候,當你接受了一個X對象的時候,調用其f方法
        X x=new X();
//        MakeString ms=x::f;//報錯
        TransformX sp1=(a)->a.f();//do the same
        System.out.println(sp.transform(x));
        System.out.println(x.f());
    }
}

剛開始沒有理解上面這段代碼的運作流程,實際上就是就是告訴sp在調用transform的時候,當你接受了一個X對象的時候,調用其f方法,下面那個sp1sp的生成過程一毛一樣,這兩個TransformX的功能完全相同。只不過f方法是個非靜態方法,沒有跟對象綁定。這也就是說,X::f會生成一個TransformX類型的對象,這個對象的String transform(X x)方法長成下面的這個樣子

String transform(X x){
	return x.f();
}

更進一步,看下面代碼

class X{
    String f(){
        return "X::f()";
    }
    String f2(String s){
        return s+"!";
    }
}

interface MyTransformX{
    String transform(X x,String s);
}

public class UnboundMethodReference {
    public static void main(String[] args) {
        MyTransformX mySp=X::f2;
        System.out.println(mySp.transform(x,"hi"));
    }
}

X::f2會生成一個MyTransformX類型的對象,這個對象的String transform(X x)方法長成下面的這個樣子

String transform(X x,String s){
	return x.f2(s);
}

函數式接口

// functional/FunctionVariants.java

import java.util.function.*;

class Foo {}

class Bar {
  Foo f;
  Bar(Foo f) { this.f = f; }
}

class IBaz {
  int i;
  IBaz(int i) {
    this.i = i;
  }
}

class LBaz {
  long l;
  LBaz(long l) {
    this.l = l;
  }
}

class DBaz {
  double d;
  DBaz(double d) {
    this.d = d;
  }
}

public class FunctionVariants {
  static Function<Foo,Bar> f1 = f -> new Bar(f);
  static IntFunction<IBaz> f2 = i -> new IBaz(i);
  static LongFunction<LBaz> f3 = l -> new LBaz(l);
  static DoubleFunction<DBaz> f4 = d -> new DBaz(d);
  static ToIntFunction<IBaz> f5 = ib -> ib.i;
  static ToLongFunction<LBaz> f6 = lb -> lb.l;
  static ToDoubleFunction<DBaz> f7 = db -> db.d;
  static IntToLongFunction f8 = i -> i;
  static IntToDoubleFunction f9 = i -> i;
  static LongToIntFunction f10 = l -> (int)l;
  static LongToDoubleFunction f11 = l -> l;
  static DoubleToIntFunction f12 = d -> (int)d;
  static DoubleToLongFunction f13 = d -> (long)d;

  public static void main(String[] args) {
    Bar b = f1.apply(new Foo());
    IBaz ib = f2.apply(11);
    LBaz lb = f3.apply(11);
    DBaz db = f4.apply(11);
    int i = f5.applyAsInt(ib);
    long l = f6.applyAsLong(lb);
    double d = f7.applyAsDouble(db);
    l = f8.applyAsLong(12);
    d = f9.applyAsDouble(12);
    i = f10.applyAsInt(12);
    d = f11.applyAsDouble(12);
    i = f12.applyAsInt(13.0);
    l = f13.applyAsLong(13.0);
  }
}

上面的代碼在構造的時候,其實可以用構造函數引用的,例如static Function<Foo,Bar> f1 = Bar::new;

package chapter13;

import java.util.function.*;

class In1 {
}

class In2 {
}

public class MethodConversion {
    static void accept(In1 i1, In2 i2) {
        System.out.println("accept()");
    }

    void boundAccept(In1 i1, In2 i2) {
        System.out.println("boundAccept()");
    }

    static void someOtherName(In1 i1, In2 i2) {
        System.out.println("someOtherName()");
    }

    public static void main(String[] args) {
        BiConsumer<In1, In2> bic;
        bic = MethodConversion::accept;
        bic.accept(new In1(), new In2());

        bic = MethodConversion::someOtherName;
        bic.accept(new In1(), new In2());

        MethodConversion m = new MethodConversion();
        bic=m::boundAccept;
        bic.accept(new In1(), new In2());
    }
}

這段代碼我增加了一點,注意main函數裏的第一個bic,生成的時候用的是MethodConversion::accept,但這個時候應該不算是非綁定引用吧,因爲這個accept是個靜態方法,可以由類直接調用的。跟最後面那個m::boundAccept其實差別不大。

高階函數

package chapter13;

import java.util.function.*;

interface FuncSS extends Function<String,String>{}

public class ProduceFunction {
    static FuncSS produce(){
        return String::toUpperCase;
//        return s->s.toUpperCase();
    }


    public static void main(String[] args) {
        FuncSS f=produce();
        System.out.println(f.apply("YELLING"));
    }
}

第一段代碼也可以直接用String::toUpperCase的非綁定方法,因爲Function接口需要實現一個R apply(T t)功能

// functional/TransformFunction.java

import java.util.function.*;

class I {
  @Override
  public String toString() { return "I"; }
}

class O {
  @Override
  public String toString() { return "O"; }
}

public class TransformFunction {
  static Function<I,O> transform(Function<I,O> in) {
    return in.andThen(o -> {
      System.out.println(o);
      return o;
    });
  }
  public static void main(String[] args) {
    Function<I,O> f2 = transform(i -> {
      System.out.println(i);
      return new O();
    });
    O o = f2.apply(new I());
  }
}

andThenFunction<T, R>接口裏的一個default方法,具體實現如下,顧名思義,接受一個Function<? super R, ? extends V>返回一個Function<T, V>,當調用返回的對象的apply(T t)方法的時候:先會執行Function<T, R>apply返回一個R類型對象,然後這個對象作爲參數調用Function<? super R, ? extends V>apply(R r)返回一個V類型的對象。所以上面的代碼就容易理解了,唯一需要說的就是,in.andThen方法傳入的是一個Function<O, O>對象

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
    Objects.requireNonNull(after);
    return (T t) -> after.apply(apply(t));
}

閉包

從 Lambda 表達式引用的局部變量必須是 final 或者是等同 final 效果的。
這就叫做等同 final 效果(Effectively Final)。這個術語是在 Java 8 纔開始出現的,表示雖然沒有明確地聲明變量是 final 的,但是因變量值沒被改變過而實際有了 final 同等的效果。 如果局部變量的初始值永遠不會改變,那麼它實際上就是 final 的。
等同 final 效果意味着可以在變量聲明前加上 final 關鍵字而不用更改任何其餘代碼。 實際上它就是具備 final 效果的,只是沒有明確說明。

package chapter13;

import java.util.function.IntSupplier;

public class Closure1 {
    int i;
    IntSupplier makeFun(int x){
        return ()->x+i++;
    }
}

makeFun被調用的時候,這個i是會被jvm搜索的,這也就是說,jvm一直要持有一個i的對象,書上是這麼解釋的

實際上,垃圾收集器幾乎肯定會保留一個對象,並將現有的函數以這種方式綁定到該對象上

函數組合

// functional/FunctionComposition.java

import java.util.function.*;

public class FunctionComposition {
  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"));
  }
}

上面的代碼使用了composeandThen,這兩個方法的功能剛好相反

柯里化和部分求值

柯里化意爲:將一個多參數的函數,轉換爲一系列單參數函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章