《Java8实战》学习:lambda---面向函数编程和流

2021-01-03  本文已影响0人  笔记本一号

面向函数编程

定义:行为参数化就是可以帮助你处理频繁变更的需求的一种软件开发模式。一言以蔽之,它意味着拿出一个代码块,把它准备好却不去执行它。这个代码块以后可以被你程序的其他部分调用,这意味着你可以推迟这块代码的执行。

匿名函数

遇到参数是一个只有一个抽象方法接口的场景,可以使用匿名的方式传递其实现过程
普通的匿名函数

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

@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
//检查集合中对象的Type是否存在肉的
 System.out.println(menu.stream().anyMatch(s->StringUtils.equals(s.getType(),"肉")));
//检查集合中对象的Type是否全是肉
 System.out.println(menu.stream().allMatch(s->StringUtils.equals(s.getType(),"肉")));
//检查集合中对象的Type是否全不存在肉
 System.out.println(menu.stream().noneMatch(s->StringUtils.equals(s.getType(),"肉")));
//findAny方法将返回当前流中的任意元素。它可以与其他流操作结合使用
 System.out.println(menu.stream().filter(s->StringUtils.equals(s.getType(),"肉")).findAny());
//findAny方法将返回当前流中的第一个元素。它可以与其他流操作结合使用
 System.out.println(menu.stream().filter(s->StringUtils.equals(s.getType(),"肉")).findFirst());
//创建一个String类型的流
Stream<String> stream = Stream.of("java8", "python", "shell");
stream.map(String::toUpperCase).forEach(System.out::println);
//无限流,这里会一直加下去,0+2,2+2,4+2.....        
Stream.iterate(0, n -> n + 2)
                .limit(10)
                .forEach(System.out::println);
//按照类型分组
Map<String, List<Dish>> collect=menu.stream().collect(Collectors.groupingBy(Dish::getType));

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)是一个容器类,代表一个值存在或不存在。

数据收集collect

//按照属性分组
Map<String, List<Dish>> collect = menu.stream().collect(groupingBy(Dish::getType));
//转set
Set<Dish> collect1 = menu.stream().collect(Collectors.toSet());
//求平均数
Double collect2 = menu.stream().collect(Collectors.averagingInt(Dish::getCalories));
//字符串连接
 String shortMenu = menu.stream().map(Dish::getName).collect(Collectors.joining("---->"));
这个Collectors有很多的方法,可以可以进行数据的收集

中间操作和终端操作

中间操作

诸如filter或sorted等中间操作会返回另一个流。这让多个操作可以连接起来形成一个查询。重要的是,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理——它们很懒。这是因为中间操作一般都可以合并起来,在终端操作时一次性全部处理。

终端操作

终端操作会从流的流水线生成结果。其结果是任何不是流的值,比如List、Integer,甚至void。例如,在下面的流水线中,forEach是一个返回void的终端操作,它会对源中的每道菜应用一个Lambda。把System.out.println传递给forEach,并要求它打印出由menu生成的流中的每一个Dish:
menu.stream().forEach(System.out::println);


并行流 ParallelStream

并行流内部使用了默认的ForkJoinPool,它默认的线程数量就是你的处理器数量,这个值是由 Runtime.getRuntime().availableProcessors()得到的。但是你可以通过系统属性 java.util.concurrent.ForkJoinPool.common. parallelism来改变线程池大小,如下所示:
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12"); 这是一个全局设置,因此它将影响代码中所有的并行流。反过来说,目前还无法专为某个并行流指定这个值。一般而言,让ForkJoinPool的大小等于处理器数量是个不错的默认值,除非你有很好的理由,否则我们强烈建议你不要修改它。

sequential

用parallel之后进行的所有操作都并行执行。类似地,你只需要对并行流调用sequential方法就可以把它变成顺序流

stream.parallel() 
 .filter(...) 
 .sequential() 
 .map(...) 
 .parallel() 
 .reduce();
上一篇 下一篇

猜你喜欢

热点阅读