2016年12月10日 星期六

方法引用、泛型 (java8 三)

lambda、方法引用都無法使用泛型方法,可以定義(因為可以覆寫),但無法用 lambda 和方法引用的寫法去調用

方法引用(Method Reference)的符號是「::」,如果方法體已經寫好了,就可以使用
它的功能和上一章的 lambda類似,但沒辦法加工,而 lambda 裡面還可以寫引用

※方法引用

import java.util.Arrays;
import java.io.PrintStream;
    
@FunctionalInterface
interface I1 {
    void fi(String s);
}
    
@FunctionalInterface
interface I2 {
    void fi(int... i);
}
    
@FunctionalInterface
interface I3 {
    public String fi(int p);
}
    
@FunctionalInterface
interface I4 {
    void fi(PrintStream out, String s);
}
    
public class Java8Test {
    public static void main(String[] args) {
        I1 i = (c) -> System.out.println(c);
        i.fi("lambda");
    
        I1 i1 = System.out::println;
        i1.fi("引用1");
    
        ((I1) System.out::println).fi("引用2");
    
        int[] iArray = new int[] { 3, 1, 52, 4 };
    
        System.out.println("before:" + Arrays.toString(iArray));
        // java7(含)之前的做法
        // Arrays.sort(iArray);
    
        // 使用標準的lambda
        I2 i2a = (x) -> Arrays.sort(x);
        i2a.fi(iArray);
    
        // 使用引用
        // I2 i2b = Arrays::sort;
        // i2b.fi(iArray);
        System.out.println("after:" + Arrays.toString(iArray));
    
        // 普通方法(和上面的System.out::println一樣的意思)
        String str = "12345";
        I3 i3a = (x) -> {
            return str.substring(x);
        };
        System.out.println(i3a.fi(2));
    
        I3 i3b = new String("12345")::substring;
        System.out.println(i3b.fi(2));
    
        // 參數是物件
        I4 i4a = (x, o) -> x.println(o);
        i4a.fi(System.out, "i4a");
    
        I4 i4b = PrintStream::println;
        i4b.fi(System.out, "i4b");
    }
}

※使用引用也是可以強制轉換後寫成一行

※靜態方法可以將「.」變成「::」,然後後面的括號都不寫

※普通方法先宣告好或用建構子寫在::的左邊

※要試無參數和無回傳值,可用 Map 的 clear


※引用加泛型

P表示參數;R表示回傳值

import java.util.Arrays;
import java.io.PrintStream;
    
@FunctionalInterface
interface I1<P> {
    void fi(P s);
}
    
@FunctionalInterface
interface I2<P> {
    void fi(P i);
}
    
@FunctionalInterface
interface I3<P, R> {
    public R fi(P p);
}
    
@FunctionalInterface
interface I4<P1, P2> {
    void fi(P1 p1, P2 p2);
}
    
public class Java8Test {
    public static void main(String[] args) {
        I1<String> i = (c) -> System.out.println(c);
        i.fi("lambda");
    
        I1<String> i1 = System.out::println;
        i1.fi("引用1");
    
        ((I1<String>) System.out::println).fi("引用2");
    
        int[] iArray = new int[] { 3, 1, 52, 4 };
    
        System.out.println("before:" + Arrays.toString(iArray));
        // java7(含)之前的做法
        // Arrays.sort(iArray);
    
        // 使用標準的lambda
        // I2<int[]> i2a = (x) -> Arrays.sort(x);
        // i2a.fi(iArray);
    
        // 使用引用
        I2<int[]> i2b = Arrays::sort;
        i2b.fi(iArray);
        System.out.println("after:" + Arrays.toString(iArray));
    
        // 普通方法(和上面的System.out::println一樣的意思)
        String str = "12345";
        I3<Integer, String> i3a = (x) -> {
            return new String("12345").substring(x);
        };
        System.out.println(i3a.fi(2));
    
        I3<Integer, String> i3b = str::substring;
        System.out.println(i3b.fi(2));
    
        // 參數是物件
        I4<PrintStream, String> i4a = (x, o) -> x.println(o);
        i4a.fi(System.out, "i4a");
    
        I4<PrintStream, String> i4b = PrintStream::println;
        i4b.fi(System.out, "i4b");
    }
}




※建構子

@FunctionalInterface
interface I1 {
    ConstructorTest fi();
}
    
@FunctionalInterface
interface I2 {
    ConstructorTest fi(String s);
}
    
class ConstructorTest {
    ConstructorTest() {
        System.out.println("無參");
    }
    
    ConstructorTest(String s) {
        System.out.println("有參" + s);
    }
}
    
public class Java8Test {
    public static void main(String[] args) {
        I1 i1a = () -> new ConstructorTest();
        i1a.fi();
    
        I1 i1b = ConstructorTest::new;
        i1b.fi();
    
        I2 i2a = (x) -> new ConstructorTest(x);
        i2a.fi("xxx");
    
        I2 i2b = ConstructorTest::new;
        i2b.fi("xxx");
    }
}




※建構子加泛型

@FunctionalInterface
interface I1<R extends ConstructorTest> {
    R fi();
}
    
@FunctionalInterface
interface I2<R extends ConstructorTest> {
    R fi(String s);
}
    
class ConstructorTest {
    ConstructorTest() {
        System.out.println("無參");
    }
    
    ConstructorTest(String s) {
        System.out.println("有參" + s);
    }
}
    
public class Java8Test {
    public static void main(String[] args) {
        I1<ConstructorTest> i1a = () -> new ConstructorTest();
        System.out.println(i1a.fi());
    
        I1<ConstructorTest> i1b = ConstructorTest::new;
        System.out.println(i1b.fi());
    
        I2<ConstructorTest> i2a = (x) -> new ConstructorTest(x);
        System.out.println(i2a.fi("xxx"));
    
        I2<ConstructorTest> i2b = ConstructorTest::new;
        System.out.println(i2b.fi("xxx"));
    }
}

※塞值可以參考 反射(一) 塞值的部分


※加泛型注意事項

interface 裡的 方法,不可以用泛型方法,否則使用時會編譯錯誤 Target method is generic,就不能用 lambda 了,只能用匿名類別了

例:
@FunctionalInterface
interface Animal {
    int get(Monkey m); // 如果在此宣告泛型方法,這裡可以編譯,但 main 方法裡的 a -> 1 會報錯
}

@Data
@AllArgsConstructor
class Monkey {
    private int id;
    private String name;

    public static void main(String[] args) {
        Animal b = a -> 1;
        b.get(new Monkey(999, "monkey"));
    }
}


※包含::方法名

總共有四種引用方式
1.建構子,很簡單,看了就會
2.靜態,一樣看了就會
3.因為靜態可以直接去「.」,不是靜態就只好用 new 的方式,然後一樣接「::」
4.不是靜態,但看起來像靜態的方式,這種方式,第一個參數一定要傳「::」左邊的物件才行


Function<Integer, String> i3b = new String("0123456789")::substring;
System.out.println(i3b.apply(4));
    
Function<char[], String> i3ba = String::copyValueOf;
System.out.println(i3ba.apply(new char[] { 'a', 'b', 'c', 'd', 'e' }));
    
BiFunction<String, Integer, String> i4 = String::substring;
System.out.println(i4.apply("0123456789", 4));
    
Supplier<J8Test> con1 = J8Test::new;
con1.get();
    
Function<String, J8Test> con2 = J8Test::new;
con2.apply("oo");

※想法要先寫引用再想方法名怎麼寫,會比較好理解

官方網站的例子比較好理解了吧!

※範例
@FunctionalInterface
public interface Ixxx {
    int method(Xxx x, String s, int i);
    
    public static void main(String[] args) {
        // 引用
        Ixxx x = Xxx::xxxMethod;
        System.out.println(x.method(new Xxx(9), "5", 1));
    
        // Lambda
        Ixxx o = (xxx, y, z) -> xxx.xxxMethod(y, z);
        System.out.println(o.method(new Xxx(9), "5", 1));
    }
}
--------------------
public class Xxx {
    private int a;
    public Xxx(int a){
        this.a = a;
    }
    
    public int xxxMethod(String s, int i){
        return this.a + Integer.parseInt(s) + i;
    }
}

※注意 Ixxx 的 method 方法和  Xxx 的 xxxMethod 裡的參數有何不同

※另外一種記法是以 Lambda 寫出來後,如上的 (xxx, y, z) -> xxx.xxxMethod(y, z)
第一個參數去點方法,而這個方法的參數是第二個之後的參數,且是有照順序的,此時可用方法引用

※1.8 有在 Integer、Long、Float、Double 增加 靜態的 sum、max、min,這些用在方法引用還不錯

沒有留言:

張貼留言