《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方法,下面那個sp1
和sp
的生成過程一毛一樣,這兩個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());
}
}
andThen
是Function<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"));
}
}
上面的代碼使用了compose
和andThen
,這兩個方法的功能剛好相反
柯里化和部分求值
柯里化意爲:將一個多參數的函數,轉換爲一系列單參數函數。