《Java8實戰》學習:lambda---面向函數編程和流

面向函數編程

定義:行爲參數化就是可以幫助你處理頻繁變更的需求的一種軟件開發模式。一言以蔽之,它意味着拿出一個代碼塊,把它準備好卻不去執行它。這個代碼塊以後可以被你程序的其他部分調用,這意味着你可以推遲這塊代碼的執行。

匿名函數

遇到參數是一個只有一個抽象方法接口的場景,可以使用匿名的方式傳遞其實現過程
普通的匿名函數

Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("線程的匿名函數");
            }
        });

lambda改寫成

Thread thread1=new Thread(()-> System.out.println("線程的匿名函數"));
Runnable runnable=()-> System.out.println("線程的匿名函數");
Thread thread1=new Thread(runnable)
面向函數及@FunctionalInterface

通過@FunctionalInterface標註面向函數編程的接口,這類接口的特徵就是一個接口只有一個抽象方法,只要滿足一個接口只有一個抽象方法就算沒有@FunctionalInterface也可以。這裏接口是作爲參數時,我們定義的行爲必須是和接口的抽象方法的返回類型,參數類型,參數個數一致,函數式接口主要起到了模板的作用

Callable和Runnable就是這類型的類

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

線程池中submit方法傳遞的參數是Callable接口,我們可以定義Callable的行爲

<T> Future<T> submit(Callable<T> task);

通過匿名或者顯示的先定義好行爲在進行傳遞

 ExecutorService executorService = Executors.newCachedThreadPool();
//顯示定義好行爲再傳遞
 Callable<String> callable=()-> UUID.randomUUID().toString();
 executorService.submit(callable);
//匿名的方式傳遞
 executorService.submit(()-> UUID.randomUUID().toString());

排序接口也是這類型的

@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
}
List<Cat> cats=new ArrayList<>();
//常規的定義
cats.sort(new Comparator<Cat>() {
            @Override
            public int compare(Cat o1, Cat o2) {
                return o1.getAge()-o2.getAge()*2;
            }
        });
//lambda 
 cats.sort((Cat o1, Cat o2)->o1.getAge()-o2.getAge()*2);
//lambda可以隱藏參數類型
 cats.sort((o1,o2)->o1.getAge()-o2.getAge()*2);
自定義@FunctionalInterface

JDK提供了很多@FunctionalInterface類型的接口,比如Predicate<T>、Consumer<T>、Function<T,R>、Supplier<T>其實我們也可以自定義
JDK提供的函數式接口,我們可以直接使用他們,也可以自己定義

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

自定義函數式接口

@FunctionalInterface
public interface DoSamething {
    String returnString(String str);
}
@FunctionalInterface
public interface DoTest<T> {
    boolean test(T a);
}
@FunctionalInterface
public interface DoSameField<T,R> {
    R test(T a);
}

將他們作爲參數傳遞

public String todoSameThing(DoSamething a,String string) {
        return a.returnString(string);
    }

    public static <T>List<T> TestFunc(List<T> ls, DoTest<T> t){
        List<T> result=new ArrayList<>();
        ls.forEach(l->{
            if (t.test(l)){
                result.add(l);
            }
        });
        return result;
    }

    public static <T, R> List<R> TestFuncRT(List<T> list,
                                     DoSameField<T,R> f) {
        List<R> result = new ArrayList<>();
        for(T s: list){
            result.add(f.test(s));
        }
        return result;
    }

定義具體的行爲

public static void main(String[] args) {
        final String TEST = "TEST";
        DoSameThingImpl doSameThing = new DoSameThingImpl();
        String s = doSameThing.todoSameThing(str -> str.toLowerCase(),TEST);
        ArrayList<String> lists = Lists.newArrayList("蘋果", "香蕉");

        List<String> result = TestFunc(lists, str -> {
            if (StringUtils.equals(str, "蘋果")) {
                return true;
            }
            return false;
        });

        List<String> result2 = TestFuncRT(lists, str -> {
            if (StringUtils.equals(str, "蘋果")) {
                return str;
            }
            return "null";
        });
}

方法引用

方法引用主要有三類。

(1) 指向靜態方法的方法引用(例如Integer的parseInt方法,寫作Integer::parseInt)
(2) 參數類型指向方法的方法引用(例如 String 的 length 方法,寫作String::length)
(3) 指向現有對象的實例方法的方法引用(假設你有一個局部變量dog用於存放Cat類型的對象,它支持實例方法getValue,那麼你就可以寫dog::getName)

第二種和第三種方法引用可能乍看起來有點兒暈。類似於String::length的第二種方法引
用的思想就是你在引用一個對象的方法,而這個對象本身是Lambda的一個參數。(String s) -> s.toUppeCase()可以寫作String::toUpperCase。但第三種方法引用指的是,你在Lambda中調用一個已經存在的外部對象中的方法。例如()->dog.getName()可以寫作dog::getName!

(1) 指向靜態方法的方法引用

自定義一個函數式接口

@FunctionalInterface
public interface SetName<T> {
    T setName(T a);
}

在Cat類的定義一個otherName方法,參數包含了SetName函數式接口

@Getter
@Setter
@ToString
public class Cat {
    private String name;
    private int age;
    public void otherName(String name,SetName<String> n){
        this.name=n.setName(name);
    }
}

測試時,使用測試類中定義的靜態方法setName,就可以使用Test::setName調用進行傳值,這裏setName_static行爲的返回類型和參數類型及參數個數是和函數式接口SetName的抽象方法setName()一致的,所以可以將這個行爲傳遞進去,讓使用方法引用靜態方法可以寫成Test::setName

public class Test {
    public static String setName_static(String name){
        return name.toLowerCase();
    }
    public static void main(String[] args) {
        Cat cat=new Cat();
        cat.otherName("otherName",Test::setName_static);
        System.out.println(cat.getName());
    }
(2) 參數類型指向方法的方法引用

在引用一個對象的方法,而這個對象本身是Lambda的一個參數
我們在Cat類裏定義一個otherName2,這次是使用JDK提供的Function<T, R> 函數式接口

@Getter
@Setter
@ToString
public class Cat {
    private String name;
    private int age;
    public void otherName(String name,SetName<String> n){
        this.name=n.setName(name);
    }
    public void otherName2(Function<String,String> n){
        this.name=n.apply(this.name);
    }
}

jdk提供的函數式接口

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

在上次測試的基礎上,使用cat.otherName2(String::toUpperCase);給cat的name轉化爲大寫,由於只是調用String類型參數的toUpperCase方法,所以可以寫成String::toUpperCase,String的toUpperCase行爲的返回值,參數類型,參數個數和我們的Function<String,String> 中的R apply(T t)(相當於 String apply(String t))方法是一樣的

public class Test {
    public static String setName(String name){
        return name.toLowerCase();
    }
    public static void main(String[] args) {
        Cat cat=new Cat();
        cat.otherName("otherName",Test::setName);
        System.out.println(cat.getName());

        cat.otherName2(String::toUpperCase);
        System.out.println(cat.getName());
    }
(3) 指向現有對象的實例方法的方法引用

基於上面的例子增加setName_test方法,然後測試時把Test類new出來當做外部對象,然後使用test::setName_test使用setName_test方法的行爲傳入otherName2中

public class Test {
    public static String setName_static(String name){
        return name.toLowerCase();
    }
    public  String setName_test(String name){
        return name.toLowerCase()+"外部對象";
    }
 public static void main(String[] args) {
       Test test=new Test();
        Cat cat=new Cat();
        cat.otherName("otherName",Test::setName_static);
        System.out.println(cat.getName());
        cat.otherName2(String::toUpperCase);
        System.out.println(cat.getName());

        //外部對象的表示
        cat.otherName2(test::setName_test);
        System.out.println(cat.getName());
    }
}

構造函數引用

對於一個現有構造函數,你可以利用它的名稱和關鍵字new來創建它的一個引用:ClassName::new。它的功能與指向靜態方法的引用類似。例如,假設有一個構造函數沒有參數。它適合Supplier的簽名() -> Apple。你可以這樣做:

我們在Cat類中使用JDK提供的Supplier<T>創建對象

@Getter
@Setter
@ToString
public class Cat {
    private String name;
    private int age;
    public static<T> T build(Supplier<T> t){
        return t.get();
    }
}

創建對象可以使用Cat::new,這裏是創建的是空參的構造函數

Cat cat=Cat.build(Cat::new);

我們需要創建一個多個多參構造函數,可以使用jdk提供的BiFunction,如果參數不夠用,我還可以自定義函數式接口

@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u);
}

在Cat我們必須提供多個參數的構造函數

@Getter
@Setter
@ToString
public class Cat {
    private String name;
    private int age;
    public Cat(int age,String name) {
        this.name = name;
        this.age = age;
    }
    public static <R> R build(Integer age,String name,BiFunction<Integer,String,R> t){
        return t.apply(age,name);
    }
}

使用Cat::new構造多參數的構造函數

Cat cat2=Cat.build(2,"吉吉",Cat::new);
需求:現在我們要創建的Cat是有條件的,需要年齡大於15歲並且體重要小於10,如果這個Cat的名字是吉吉的話就不用體重限制

我們先要創建一個函數式的接口,這個接口有三個範形的參數,對應了年齡,姓名,體重

public interface ThreeArgsConsturct<Q,W,E,R>{
    R get(Q q,W w,E e);
}

在Cat中創建好對象的創建方法build2

@Getter
@Setter
@ToString
public class Cat {
    private String name;
    private int age;
    private int weight;

    public Cat(int age,String name, int weight) {
        this.name = name;
        this.age = age;
        this.weight = weight;
    }
    public static <R> R build(Integer age, String name,Integer weight, ThreeArgsConsturct<Integer,String,Integer,R> t){
        return t.get(age,name,weight);
    }
    public static Cat build2(Integer a,String n,Integer w,Predicate<Cat> t){
        Cat build = Cat.build(a, n, w, Cat::new);
        if (t.test(build)){
            return build;
        }
        return null;
    }
}
public class Test {
    public static void main(String[] args) {
      Predicate<Cat> catPredicate=a->a.getAge()<15;
        //negate()是非的意思,也就是>15
        Predicate<Cat> catPredicate1=catPredicate
                .negate()
                .and(a->a.getWeight()<10)
                .or(a->a.getName().equals("吉吉"));
        List<Cat> cats2=new ArrayList<>();
        cats2.add(Cat.build2(30,"吉吉",15,catPredicate1));
        cats2.add(Cat.build2(14,"吉吉2",9,catPredicate1));
        cats2.add(Cat.build2(16,"吉吉3",8,catPredicate1));
        cats2.add(Cat.build2(17,"吉吉4",15,catPredicate1));
        System.out.println(cats2);
    }
}
現在我要給貓的名字進行一系列的加工

在原來的基礎改成這樣

public class Cat {
    private String name;
    private int age;
    private int weight;

    public Cat(int age,String name, int weight) {
        this.name = name;
        this.age = age;
        this.weight = weight;
    }
    public static <R> R build(Integer age, String name,Integer weight, ThreeArgsConsturct<Integer,String,Integer,R> t){
        return t.get(age,name,weight);
    }
    public static Cat build2(Integer a,String n,Integer w,Predicate<Cat> t
            ,Function<String,String> f){
        Cat build = Cat.build(a, n, w, Cat::new);
        if (t.test(build)){
            build.setName(f.apply(build.getName()));
            return build;
        }
        return null;
    }
}
public class Test {
        public static void main(String[] args) {
        Predicate<Cat> catPredicate = a -> a.getAge() < 15;
        //negate()是非的意思,也就是>15
        Predicate<Cat> catPredicate1 = catPredicate
                .negate()
                .and(a -> a.getWeight() < 10)
                .or(a -> a.getName().equals("吉吉"));
        Function<String, String> function = s -> "琪琪的貓:" + s;
        Function<String, String> function2 = function.andThen(s -> "魔女宅急便的:" + s).andThen(s -> "宮崎駿創作的:" + s);
        List<Cat> cats2 = new ArrayList<>();
        cats2.add(Cat.build2(30, "吉吉", 15, catPredicate1, function2));
        cats2.add(Cat.build2(14, "吉吉2", 9, catPredicate1, function2));
        cats2.add(Cat.build2(16, "吉吉3", 8, catPredicate1, function2));
        cats2.add(Cat.build2(17, "吉吉4", 15, catPredicate1, function2));
        System.out.println(cats2);
    }
}

更多示例

Comparator

List<Cat> cats=new ArrayList<>();
cats.sort(new Comparator<Cat>() {
            @Override
            public int compare(Cat o1, Cat o2) {
                return o1.getAge()-o2.getAge();
            }
        });
cats.sort((o1,o2)->o1.getAge()-o2.getAge());
//使用方法引用,這裏的從小到大排序
cats.sort(comparing(Cat::getAge));
cats.sort(comparing(Cat::getName));
//也可以逆序
cats.sort(comparing(Cat::getAge).reversed());
//還可以進行鏈式的,有Age一樣的就比較Weight,Weight一樣的就比較Name 
cats.sort(comparing(Cat::getAge).thenComparing(Cat::getWeight).thenComparing(Cat::getName));

函數複合

Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g);
int result = h.apply(1);//結果是4

Stream

  • filter——接受Lambda,從流中排除某些元素。
  • map——接受一個Lambda,將元素轉換成其他形式或提取信息。
  • limit——截斷流,使其元素不超過給定數量。
  • collect——將流轉換爲其他形式。
@Getter
@Setter
@ToString
public class Dish { 
 private final String name; 
 private final boolean vegetarian; 
 private final int calories; 
 private final Type type; 
 public Dish(String name, boolean vegetarian, int calories, Type type) { 
 this.name = name; 
 this.vegetarian = vegetarian; 
 this.calories = calories; 
 this.type = type; 
   }
}
static final List<Dish> menu = Arrays.asList(
            new Dish("pork", false, 800, "肉"),
            new Dish("beef", false, 700, "肉"),
            new Dish("chicken", false, 400, "肉"),
            new Dish("french fries", true, 530, "其他"),
            new Dish("rice", true, 350, "其他"),
            new Dish("season fruit", true, 120, "其他"),
            new Dish("pizza", true, 550, "其他"),
            new Dish("prawns", false, 300, "魚"),
            new Dish("salmon", false, 450, "魚"));

     List<String> names = menu.stream()
                .filter(d -> d.getCalories() > 300)//過濾
                .sorted(comparing(Dish::getCalories))//按照Calories排序
                .distinct()//去重
                .map(Dish::getName)//只提取name
                .limit(3) //只拿前三條
                .collect(toList());//把數據裝到list中
//遍歷
names.forEach(s-> System.out.println(s));
long count = names.stream().count();//集合個數
boolean isHealthy =menu.stream().anyMatch(Dish::isVegetarian);//匹配集合對象裏的字段Vegetarian至少有一個是true
boolean isHealthy = menu.stream().allMatch(d -> d.getCalories() < 1000);//匹配集合對象裏的字段Calories全部小於1000
//匹配的集合中對象的字段Calories沒有大於等於1000的
boolean isHealthy = menu.stream().noneMatch(d -> d.getCalories() >= 1000);
Optional.ofNullable(strs2).orElse(new ArrayList<>());

Optional.ofNullable(strs2).orElseThrow(() -> new RuntimeException("集合爲空"));

Optional.ofNullable(strs2).filter(c->c.size()>0).orElse(new ArrayList<>());   

String teststr="Test";

 ofNullable(teststr).filter(s->s.length()>0).orElse("字符串爲空");

 ofNullable(teststr).filter(s -> s.length() > 0)
                .map(s->ofNullable(strs.get(s.length()))
                        .orElse("字符串爲空"))
                .orElse("字符串爲空");

List<Integer> ints=Arrays.asList(1,2,3,4,5,6,7,8,9,10);//遍歷的數相加相加
 Optional<Integer> max = ints.stream().reduce(Integer::max);
Optional<Integer> min = ints.stream().reduce(Integer::min);
System.out.println(max.get());
System.out.println(min.get());
int calories = menu.stream()
                .mapToInt(Dish::getCalories)//映射成一個特殊流就可以求和
                .sum();//求和

OptionalInt calories1 = menu.stream()
                .mapToInt(Dish::getCalories)
                .max();//最大值
        OptionalInt calories2 = menu.stream()
                .mapToInt(Dish::getCalories)
                .min();//最小值

IntStream.range(0, 3).forEach(i-> System.out.println(i));//範圍,這裏只是輸出0到2
IntStream.rangeClosed(0, 3).forEach(i-> System.out.println(i));//這裏輸出0到3
flatMap

給定單詞列表["Hello","World"],你想要返回列表["H","e","l", "o","W","r","d"]。

(1) 給定一個數字列表,如何返回一個由每個數的平方構成的列表呢?例如,給定[1, 2, 3, 4,
5],應該返回[1, 4, 9, 16, 25]。

 List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
 List<Integer> squares = numbers.stream().map(n -> n * n).collect(toList());

(2) 給定兩個數字列表,如何返回所有的數對呢?例如,給定列表[1, 2, 3]和列表[3, 4],應
該返回[(1, 3), (1, 4), (2, 3), (2, 4), (3, 3), (3, 4)]。爲簡單起見,你可以用有兩個元素的數組來代
表數對。

List<Integer> numbers1 = Arrays.asList(1, 2, 3);
        List<Integer> numbers2 = Arrays.asList(3, 4);
        List<int[]> pairs =numbers1.stream()
                        .flatMap(i -> numbers2.stream()
                                .map(j -> new int[]{i, j})
                        )
                        .collect(toList());
Optional

Optional<T>類(java.util.Optional)是一個容器類,代表一個值存在或不存在。

中間操作和終端操作

中間操作

諸如filter或sorted等中間操作會返回另一個流。這讓多個操作可以連接起來形成一個查詢。重要的是,除非流水線上觸發一個終端操作,否則中間操作不會執行任何處理——它們很懶。這是因爲中間操作一般都可以合併起來,在終端操作時一次性全部處理。

終端操作

終端操作會從流的流水線生成結果。其結果是任何不是流的值,比如List、Integer,甚至void。例如,在下面的流水線中,forEach是一個返回void的終端操作,它會對源中的每道菜應用一個Lambda。把System.out.println傳遞給forEach,並要求它打印出由menu生成的流中的每一個Dish:
menu.stream().forEach(System.out::println);


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