java8 函数式编程、接口、lambda、stream

函数式编程

函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。
和命令式编程相比,函数式编程强调函数的计算比指令的执行重要。
和声明式编程相比,函数式编程里函数的计算可随时调用。

函数式编程与命令式编程不同

命令式编程的代码由一系列改变全局状态的语句构成;
函数式编程将计算过程抽象成表达式求值。表达式由纯数学函数构成,而这些数学函数是第一类对象且没有副作用。由于没有副作用,函数式编程可以更容易做到线程安全,因此特别适合于并发编程。
函数式编程是可以直接支持并行的模型。

    private static void programmatically() {
        //命令式编程     
        //关注计算机执行的步骤
        //一步一步告诉计算机怎么做
        List nums = new ArrayList(Arrays.asList(1,2,3,4,5,6,7,8,9,10));
        //第一步 创建一个存储结果的集合
        List results = new ArrayList();
        //第二步 遍历集合
        for (Integer num : nums) {
            //第三步 一个一个判断是否小于7,如果小于7就加入到结果集合中
            if(num < 7) {
                results.add(num);
            }
        }
        
        //声明书编程     
        //以数据结构的形式来表达程序执行的逻辑
        //只告诉计算机应该做什么,而不具体指定怎么做。
        //select num from nums where num < 7;
        
        
        //函数式编程  
        //和声明书编程 编程一样值关注做什么,而不去关心怎么做
        //函数式编程最重要的特点是“函数第一位”,即函数可以出现在任何地方,比如你可以把函数作为参数传递给另一个函数,还可以将函数作为返回值
        results = nums.stream().filter(num -> num < 7).collect(Collectors.toList());
        
        Predicate predicate = new Predicate() {
            @Override
            public boolean test(Integer num) { 
                return num < 7;
            }
        };
        
        results = nums.stream().filter(predicate).collect(Collectors.toList());
        
        results.forEach(System.out::println);
    }

函数式接口

函数式接口只能有一个抽象方法,而不是只能有一个方法。
Java8新增了接口的默认方法,接口也可以包含若干个实例方法。在Java8中,使用default关键字,可以在接口内定义实例方法,这个方法并非抽象方法,而是拥有特定逻辑的具体实例方法。

java8内置的四大核心函数式接口
java8 函数式编程、接口、lambda、stream_第1张图片
image.png

java8 函数式编程、接口、lambda、stream_第2张图片
image.png
java8 函数式编程、接口、lambda、stream_第3张图片
image.png

BiXX类型接口
BiConsumer、BiFunction、BiPrediate 是 Consumer、Function、Predicate 的扩展,可以传入多个参数。

XXYY类型接口
用 XX来表示 int、long、double、boolean,用 YY来表示 Consumer、Function、Predicate、Supplier 。JDK8就内置了此四种基本数据类型的消费,构建,操作,断言以及他们因此而产生的一系列其他接口 。

UnaryOperator与BinaryOperator
UnaryOperator:代表了一个作用于一个类型的操作,并且返回了同类型的结果
BinaryOperator:代表了一个作用于于两个同类型的操作,并且返回了同类型的结果
XXUnaryOperator:代表了一个作用于一个XX数据类型的操作,并且返回了同类型数据的结果
XXBinaryOperator:代表了一个作用于两个XX数据类型的操作,并且返回了同类型数据的结果
ToXXFunction与ToXXBiFunction类型接口
ToXXFunction:接受一个输入参数,返回一个XX类型结果
ToXXBiFunction:接受两个输入参数,返回一个XX类型结果

Consumer消费数据函数式接口

源码如下,有注解FunctionalInterface。提供了一个accept抽象方法(传入一个参数,无返回值),另有一个由default修饰的实列方法andThen,该方法可传入一个Consumer函数,并返回一个Consumer函数(先执行原函数的accept,再执行传入函数的accept)。

@FunctionalInterface
public interface Consumer {
    void accept(T t);
    default Consumer andThen(Consumer after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

演示

private static void consumerDetail() {
        Consumer c = new Consumer() {
            @Override
            public void accept(String t) {
                System.out.println(t);
            }
        };
        c.accept("samir");
    }

可以用lambda表达式优化(详细lambda语法在本文中会专门讲解)

private static void consumer() {
        /**
         * 输入: -> 前面部分 
         * 函数体 : -> 后面部分
         */
        Consumer c = (x) -> {
            System.out.println(x);
        };
        //只有一个输入参数, 可以去掉() , 当函数体中只有一个语句时,可以去掉{}进一步简化
        c = x -> System.out.println(x);
        //然而这还不是最简的,由于此处只是进行打印,调用了System.out中的println实列方法对输入参数直接进行打印,因此可以简化成以下写法
        c = System.out::println;
        
        c.accept("samir");
    }

输出

samir

注:default修饰的实列方法,是可以重写的。如下

private static void consumerDetail() {
        Consumer c = new Consumer() {
            @Override
            public void accept(String t) {
                System.out.println("C1-"+t);
            }

            @Override
            public Consumer andThen(Consumer after) {
                return (String t) -> {
                    accept(t);
                    System.out.println("todo");
                    after.accept(t);
                };
            }
        };
        
        Consumer c2 = new Consumer() {
            @Override
            public void accept(Object t) {
                System.out.println("C2-" + t);
            }
        };
        c.andThen(c2).accept("samir");
    }
 
 

输出
注:c.andThen(c2)返回的是一个新的函数(此函数就是c的andThen方法return之后的函数,先执行C的accept,再打印todo,再执行C2的accept)

C1-samir
todo
C2-samir

supplier生产数据函数式接口

源码如下,有注解FunctionalInterface。只有一个抽象方法get(无入参,有返回值)。

@FunctionalInterface
public interface Supplier {
    T get();
}

演示

private static void supplierDetail() {
        Supplier supplier = new Supplier() {
            @Override
            public String get() {
                return "samir";
            }
        };
        System.out.println(supplier.get());
    }

lambda优化

private static void supplier() {
        Supplier supplier = () -> "samir";
        System.out.println(supplier.get());
    }

输出

samir

Predicate判断函数式接口

源码如下,有注解FunctionalInterface。提供一个抽象方法test(一个入参,返回一个boolean),三个default修饰的实例方法,and(与)、negate(非)、or(或),一个静态方法isEqual(返回一个函数,判断是否相等的函数)。

@FunctionalInterface
public interface Predicate {

    boolean test(T t);

    default Predicate and(Predicate other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

    default Predicate negate() {
        return (t) -> !test(t);
    }

    default Predicate or(Predicate other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }

    static  Predicate isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

演示

private static void predicateDetail() {
        Predicate p = new Predicate() {
            @Override
            public boolean test(String t) {
                return t.equals("samir");
            }
        };
        System.out.println(p.test("samir"));
    }

lambda优化

private static void predicate() {
        Predicate p = o -> o.equals("samir");
        Predicate p2 = o -> o.equals("wu");
        //p
        System.out.println("test=" + p.test("samir"));
        //p和p2都执行,结果and
        System.out.println("and=" + p.and(p2).test("samir"));
        //p和p2都执行,结果or
        System.out.println("or=" + p.or(p2).test("samir"));
        //p,结果非
        System.out.println("negate=" + p.negate().test("samir"));
        
        Predicate s = Predicate.isEqual("samir");
        System.out.println("isEqual=" + s.test("samir"));
    }
 
 

输出

test=true
and=false
or=true
negate=false
isEqual=false

Function函数式接口

源码如下,有注解FunctionalInterface。提供一个抽象方法apply(一个入参,返回一个参数),两个default修饰的实例方法,compose(先执行传进来的函数,再执行原来的函数)、andThen(先执行原来的函数,再执行传入的函数),一个静态方法identity(返回一个输出跟输入一样的Lambda表达式对象)。

@FunctionalInterface
public interface Function {

    R apply(T t);

    default  Function compose(Function before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default  Function andThen(Function after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static  Function identity() {
        return t -> t;
    }
}

演示

private static void functionDetail() {
        Function f = new Function() {
            @Override
            public Object apply(Object t) {
                return t + "----";
            }
        };
        System.out.println(f.apply("samir"));
    }

lambda优化

private static void function() {
        Function f = o -> o + 10;
        Function f2 = o -> o * 10;
        //f  13 + 10 = 23
        System.out.println(f.apply(13));
        //先执行f2 13 * 10 =  130 ,然后把结果130传给f2,f2再执行 , 130 + 10 = 140
        System.out.println(f.compose(f2).apply(13));
        //先执行f 13 + 10 = 23 , 然后把结果23传给f2,f2再执行 , 23 * 10 = 230
        System.out.println(f.andThen(f2).apply(13));
        //不进行任何处理
        System.out.println(Function.identity().apply(13));
    }

输出

23
140
230
13

lambda表达式

Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像参数一样进行传递,称为行为参数化)。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。

Lambda重要特征

可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

Lambda方法引用

一、是什么?
方法引用是用来直接访问类或者实例已经存在的方法或者构造方法。
二、哪里能用?
当Lambda表达式中只是执行一个方法调用时。
类::静态方法
类::实列方法
对象引用::实例方法名
引用构造器
引用数组

private static void lambda() {
        //不需要参数,返回值3
        Supplier s = () -> 3;
        //接受一个参数,返回其两倍值
        Function f = x -> 2 * x;
        //接受一个参数,返回其两倍值
        IntFunction i = x -> 2 * x;
        
        //接受两个参数,返回其之和
        BiFunction b = (x,y) -> x + y;
        IntBinaryOperator i2 = (x, y) -> x + y;
        BinaryOperator b2 = (x,y) -> x + y;
        
        //如加上{},有返回值,需加return
        IntBinaryOperator i3 = (x,y) -> {return x + y;};
        
        
        //类::静态方法
        Function stringToInt1 = x -> Integer.parseInt(x);
        Function stringToInt2 = Integer::parseInt;
        
        //类::实列方法
        BiPredicate b3 = (x,y) -> x.equals(y);
        BiPredicate b4 = String::equals;
        
        //对象引用::实例方法名
        Consumer c1 = x -> System.out.println(x);
        Consumer c2 = System.out::println;
        
        //引用构造器
        Function f1 = n -> new StringBuffer(n);
        Function f2 = StringBuffer::new;
        StringBuffer sbf = f2.apply(10);
        
        //引用数组
        Function f3 = n -> new int[n];
        Function f4 = int[]::new;
        int[] i4 = f4.apply(10);
        
        //属于什么?
        BiPredicate, String> contains1 = (list, element) -> list.contains(element);
        BiPredicate, String> contains2 = List::contains;
        List strs = new ArrayList<>();
        contains2.test(strs, "samir");
    }
疑问: (list, element) -> list.contains(element); 这个属于什么呢?
java8 函数式编程、接口、lambda、stream_第4张图片
image.png

类::静态方法
类::实列方法
对象引用::实例方法名
引用构造器
引用数组

contains是接口List的抽象方法,不属于上面任何一种,那为什么不报错。
我们再来看下面一段代码,点contains进去,这里的contains是类ArrayList的实列方法。
很明显,这里是属于 类::实列方法。也就是上面List::contains;这种写法,其实在调用时,最终还是通过contains的实现方法来的。

        ArrayList strs2 = new ArrayList<>();
        BiPredicate, String> contains3 = ArrayList::contains;
        contains3.test(strs2, "samir");

Stream

java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

什么是Stream

Stream(流)是一个来自数据源的元素队列并支持聚合操作
元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。

java8 函数式编程、接口、lambda、stream_第5张图片
image.png

java8 函数式编程、接口、lambda、stream_第6张图片
image.png
image.png

如上所说,Stream就如同查询sql一样来操作java。

流的创建

演示:

private static void streamCreate() {
        //创建空的Stream
        Stream empty = Stream.empty();
        
        //通过集合中的stream()和parallelStream()创建
        List list = Arrays.asList("a","b","c","d","e");
        //串行
        Stream stream = list.stream();
        //并行
        Stream pstream = list.parallelStream();
        stream.forEach(System.out::print);
        pstream.forEach(System.out::print);
        
        //通过Stream.of创建
        Stream s = Stream.of("samir");
        Stream s2 = Stream.of("s","a","m","i","r");
        s.forEach(System.out::println);
        s2.forEach(System.out::println);
        
        //通过Stream.iterate创建  无限有序值  一般通过limit限制
        Stream s3 = Stream.iterate(1, x -> x+2).limit(10);
        s3.forEach(System.out::println);
        
        //通过Stream.generate创建 无限无序值  一般通过limit限制
        Stream s4 = Stream.generate(() -> Math.random()).limit(10);
        s4.forEach(System.out::println);
    }
 
 

输出

abcdecaedbsamir
s
a
m
i
r
1
3
5
7
9
11
13
15
17
19
0.6039120906429292
0.3088897418801203
0.03897174513445234
0.78025515188639
0.7347193387953834
0.27960273810401304
0.09422833272405962
0.8460545152461284
0.31310439370164334
0.8181407076324397

准备数据

private static List users = new ArrayList();
    
    static {
        User user = new User(1,"张三",1,18,new BigDecimal(5000));
        User user2 = new User(2,"李四",2,22,new BigDecimal(7000));
        User user3 = new User(3,"王五",1,20,new BigDecimal(10000));
        users.add(user);
        users.add(user2);
        users.add(user3);
    }
filter 方法用于通过设置的条件过滤出元素
private static void filter() {
        List nusers = users.stream().filter(user -> user.getGender() == 1).collect(Collectors.toList());
        nusers.forEach(System.out::println);
        System.out.println();
    }

输出

User [id=1, name=张三, gender=1, age=18, salary=5000]
User [id=3, name=王五, gender=1, age=20, salary=10000]
sorted 方法用于对流进行排序
private static void sort() {
        //java8之前排序
        users.sort(new Comparator() {
            @Override
            public int compare(User o1, User o2) {
                return o2.getAge() - o1.getAge();
            }
        });
        
        //java8之后排序
        List nusers = users.stream().sorted(new Comparator() {
            @Override
            public int compare(User o1, User o2) {
                return o2.getAge() - o1.getAge();
            }
        }).collect(Collectors.toList());
        nusers.forEach(System.out::println);
        System.out.println();
        //java8 lambda优化
        //不改变原集合
        List nusers2 = users.stream().sorted((o1,o2) -> o1.getAge() - o2.getAge()).collect(Collectors.toList());
        nusers2.forEach(System.out::println);
        System.out.println();
        //原集合已排序
        users.sort((o1,o2) -> o1.getAge() - o2.getAge());
        users.forEach(System.out::println);
    }

输出

User [id=2, name=李四, gender=2, age=22, salary=7000]
User [id=3, name=王五, gender=1, age=20, salary=10000]
User [id=1, name=张三, gender=1, age=18, salary=5000]

User [id=1, name=张三, gender=1, age=18, salary=5000]
User [id=3, name=王五, gender=1, age=20, salary=10000]
User [id=2, name=李四, gender=2, age=22, salary=7000]

User [id=1, name=张三, gender=1, age=18, salary=5000]
User [id=3, name=王五, gender=1, age=20, salary=10000]
User [id=2, name=李四, gender=2, age=22, salary=7000]

map、mapToXXX 、flatMap、reduce

map 方法用于映射每个元素到对应的结果
mapToXXX 流转换为数值流
flatMap流的扁平化
reduce 归约 中文含义为:减少、缩小;而Stream中的Reduce方法干的正是这样的活:根据一定的规则将Stream中的元素进行计算后返回一个唯一的值。

演示

private static void map() {
        //抽取对象属性
        List names = users.stream().map(user -> "samir " + user.getName()).distinct().collect(Collectors.toList());
        names.forEach(System.out::println);
        
        //流转换为数值流
        //求和
        int sum = users.stream().mapToInt(user -> user.getAge()).sum();
        //最大值
        int max = users.stream().mapToInt(user -> user.getAge()).max().getAsInt();
        //最小值
        int min = users.stream().mapToInt(user -> user.getAge()).min().getAsInt();
        
        System.out.println("sum="+sum);
        System.out.println("max="+max);
        System.out.println("min="+min);
        
        //flatMap 和map类似,不同的是其每个元素转换得到的是Stream对象,会把子Stream中的元素压缩到父集合中;
        List nnames = users.stream().flatMap(user -> Arrays.stream(user.getName().split(""))).collect(Collectors.toList());
        nnames.forEach(System.out::println);
        
        //归约 
        int sum2 = users.stream().map(user -> user.getAge()).reduce(Integer::sum).get();
        int max2 = users.stream().map(user -> user.getAge()).reduce(Integer::max).get();
        int min2 = users.stream().map(user -> user.getAge()).reduce(Integer::min).get();
        System.out.println("sum2="+sum2);
        System.out.println("max2="+max2);
        System.out.println("min2="+min2);
        
        BigDecimal salarySum = users.stream().map(user -> user.getSalary()).reduce(BigDecimal::add).get();
        System.out.println("salarySum="+salarySum);
        
    }

输出

samir 张三
samir 李四
samir 王五
sum=60
max=22
min=18
张
三
李
四
王
五
sum2=60
max2=22
min2=18
salarySum=22000
collect 负责收集流

演示

private static void collect() {
        //List转Map
        //k:属性 v:属性
        Map umap = users.stream().collect(Collectors.toMap(User::getId, User::getName));
        umap.forEach((k,v) -> System.out.println("k="+k + "---v="+v));
        //k:属性 v:类
        Map amap = users.stream().collect(Collectors.toMap(User::getId, user -> user));
        amap.forEach((k,v) -> System.out.println("k="+k + "---v="+v));      
        //分组
        Map> gmap = users.stream().collect(Collectors.groupingBy(User::getGender));
        gmap.forEach((k,v) -> System.out.println("k="+k + "---v="+v.toString()));
    }

输出

k1=1---v1=张三
k1=2---v1=李四
k1=3---v1=王五
k2=1---v2=User [id=1, name=张三, gender=1, age=18, salary=5000]
k2=2---v2=User [id=2, name=李四, gender=2, age=22, salary=7000]
k2=3---v2=User [id=3, name=王五, gender=1, age=20, salary=10000]
k3=1---v3=[User [id=1, name=张三, gender=1, age=18, salary=5000], User [id=3, name=王五, gender=1, age=20, salary=10000]]
k3=2---v3=[User [id=2, name=李四, gender=2, age=22, salary=7000]]

你可能感兴趣的:(java8 函数式编程、接口、lambda、stream)