Java8新特性lambda和stream系列

目录

    • 前言
      • 一. Java8新特性之lambda表达式
      • 二. Java8新特性之四大函数式接口
        • 1. default关键字介绍
        • 2. 函数式接口介绍
        • 3. 函数型接口 Function
        • 4. 断定型接口 Predicate
        • 5. 消费型接口 Consumer
        • 6. 供给型接口 Supplier
      • 三. Java8新特性之Stream流
      • 四. Java8新特性之 :: 表达式
        • 1. :: 表达式介绍
        • 2. Oracle官方翻译讲解
        • 3. 个人代码示例讲解

前言

Java8是Java的一个核心版本,添加了许多新的功能和操作,也是Java中使用最高的一个版本即使现在Java16都已经出了。不少人依然对Java8这些特性不了解,甚至看不懂代码。就让本篇文章带你去了解下这些新特性

  • Lambda 表达式
  • 函数式接口
  • stream流系列操作
  • :: 表达式

在写这个文章之前,我对此只是略有了解,甚至写不出一个简单的lambda语句,现在基本上lambda语句都能写出来了,sream系列操作因为涉及到很多方法,写不出来所有的,但是常用的几种流的生成,中间操作和最终操作还是能写出来的。::表达式也基本能看懂,下面就跟着我来了解下吧

一. Java8新特性之lambda表达式

lambda表达式的表现形式

()->{};

lambda表达式的格式

() 用来写参数的, 对应抽象方法中()的参数类型及声明直接copy过来

-> 用来进行参数传递

{} 重写方法的代码都写到大括号中,对应匿名内部类{}重写方法体直接copy过来

lambda表达式要求

  1. 必须是一个接口
  2. 必须只有一个抽象方法(只有一个抽象方法的接口,称之为函数式接口)
  3. 必须有上下文来推断哪个接口哪个方法,否则编译都不会通过(一般都是表达式作为参数)

lambda表达式的省略:

  1. () 中的参数类型声明可以省略掉,可以推断出来
  2. () 中如果只有一个参数,小括号可以省略
  3. {} 中如果只有一行不是返回值的代码,大括号可以省略,分号也可以省略(必须同时省略)
  4. 当只有一行返回值的时候,在大括号和分号同时省略的基础上,return 也必须省略

代码演示循序渐进

 				//0lambda的标准样式
        TestLambdaOmit testLambdaOmit0 = (String aa, String bb) -> {
            System.out.println("0lambda的标准样式");
            return aa + bb;
        };
        //1lambda参数类型声省略掉(常用)
        TestLambdaOmit testLambdaOmit1 = (aa, bb) -> {
            System.out.println("1lambda参数类型声省略掉");
            return aa + bb;
        };
        //2()中如果只有一个参数,小括号可以省略
        TestLambda testLambda2 = (aa -> {
            System.out.println("2()中如果只有一个参数,小括号可以省略");
        });
        //3{} 号中如果只有一行代码,大括号可以省略,分号也可以省略
        TestLambda testLambda3 = (aa -> System.out.println("3{} 号中如果只有一行代码,大括号可以省略,分号也可以省略"));
        //4{}当只有一行返回值的时候,在大括号和分号同时省略的基础上,return 也可以省略
        TestLambdaOmit testLambdaOmit4 = (aa, bb) ->  aa + bb;
           // System.out.println("4{}当只有一行返回值的时候在大括号和分号同时省略的基础上,return 也可以省略");

完整版代码

package com.concurrent.demo15Lambda;

/**
 * lambda
 * @author lane
 * @date 2021年05月26日 下午11:21
 */
public class TestLambdaDemo {

    public static void main(String[] args) {

        //会出错,没有上下文,推断不出来
        // ()->{ System.out.println("aa");};
        //匿名内部类
        TestLambda testLambda = new TestLambda() {
            @Override
            public void test(String aa) {
                System.out.println("test方法执行了");
            }
        };

        testLambda.test("hello");
        //必须只有一个抽象方法才行,接口中如果有两个方法就会编译出错了
        //lambda
        TestLambda testLambda1 = ((aa) -> {
            System.out.println("lambda的test方法执行了");
        });
        testLambda1.test("hello");

/*      lambda表达式的省略(不建议看)
        1. () 中的参数类型声明可以省略掉,可以推断出来
        2. () 中如果只有一个参数,小括号可以省略
        3. {} 号中如果只有一行不是返回值的代码,大括号可以省略,分号也可以省略(必须分号和大括号同时省略)
        4. {} 当只有一行返回值的时候,在大括号和分号同时省略的基础上,return 也可以省略
*/
        //0lambda的标准样式
        TestLambdaOmit testLambdaOmit0 = (String aa, String bb) -> {
            System.out.println("0lambda的标准样式");
            return aa + bb;
        };
        //1lambda参数类型声省略掉
        TestLambdaOmit testLambdaOmit1 = (aa, bb) -> {
            System.out.println("1lambda参数类型声省略掉");
            return aa + bb;
        };
        //2()中如果只有一个参数,小括号可以省略
        TestLambda testLambda2 = (aa -> {
            System.out.println("2()中如果只有一个参数,小括号可以省略");
        });
        //3{} 号中如果只有一行代码,大括号可以省略,分号也可以省略
        TestLambda testLambda3 = (aa -> System.out.println("3{} 号中如果只有一行代码,大括号可以省略,分号也可以省略"));
        //4{}当只有一行返回值的时候,在大括号和分号同时省略的基础上,return 也可以省略
        TestLambdaOmit testLambdaOmit4 = (aa, bb) ->  aa + bb;
           // System.out.println("4{}当只有一行返回值的时候在大括号和分号同时省略的基础上,return 也可以省略");

        //0
        System.out.println("========下面是测试省略============");
        System.out.println(testLambdaOmit0.testOmit("hello", "world"));
        //1
        System.out.println(testLambdaOmit1.testOmit("hello", "world"));
        //2
        testLambda2.test("hello");
        //3
        testLambda3.test("hello");
        //4
        System.out.println(testLambdaOmit4.testOmit("hello", "world"));
    }
}

/*
test方法执行了
lambda的test方法执行了
========下面是测试省略============
0lambda的标准样式
helloworld
1lambda参数类型声省略掉
helloworld
2()中如果只有一个参数,小括号可以省略
3{} 号中如果只有一行代码,大括号可以省略,分号也可以省略
helloworld
*/

二. Java8新特性之四大函数式接口

只有一个抽象方法的接口,称之为函数式接口,天生就是为了适合lambda,default关键字也是为了lambda的

一般用@FunctionalInterface注解标注

1. default关键字介绍

接口中default关键字修饰方法

1.8 新引入的Lambda表达式,有明显的局限性只能有一个抽象方法很不好,如果想实现多个方法呢,lambda就不可以使用了,为了让lambda可以使用,某种程度也可以重写多个方法,所有就有了default方法(Virtual extension methods)。

default方法是指,在接口内部包含了一些默认的方法实现(也就是接口中可以包含方法体,这打破了Java之前版本对接口的语法限制),从而使得接口在进行扩展的时候,直接就重写了自己的default方法了

简而言之就是接口中的方法既可以有抽象方法也可以有default修饰的完整方法

/**
 * @author lane
 * @date 2021年05月26日 下午11:20
 */
public interface TestLambda {
	//抽象方法
    public abstract void test(String aa);

    //public abstract void testTwo();
	//含有方法体的方法
    default void testDefault(){
        System.out.println("接口中default的方法执行了");
    }

}


/** 
 * 既可以使用lambda又可以有多个方法
 * @author lane
 * @date 2021年05月27日 上午1:04
 */
public class DefaultDemo {

    public static void main(String[] args) {
        TestLambda testLambda = (s)->{
            System.out.println("lambda执行接口test方法");
        };
        
        testLambda.test("helloworld");
        testLambda.testDefault();
    }

}

/*
lambda执行接口test方法
接口中default定于的方法执行了
*/
2. 函数式接口介绍
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
// jdk以前的核心特性: 泛型、枚举、反射等
// 新的特性:lambda表达式、链式编程、函数式接口、Stream流式计算
// 超级多FunctionalInterface
// 简化编程模型,在新版本的框架底层大量应用!
// foreach(消费者类的函数式接口)

Java.util.function下的四大函数式接口

Java8新特性lambda和stream系列_第1张图片

3. 函数型接口 Function

有一个输入参数,有一个输出

Java8新特性lambda和stream系列_第2张图片

代码实现Function

package com.concurrent.demo13Function;

import java.util.function.Function;

/**
 * Function 函数型接口, 有一个输入参数,有一个输出
 * 只要是 函数型接口 可以 用 lambda表达式简化
 * @author lane
 * @date 2021年05月26日 下午4:40
 */
public class FunctionDemo {

    public static void main(String[] args) {
        //匿名内部类实现
        Function<Integer,String> function = new Function<Integer,String> () {
            @Override
            public String apply(Integer i) {
                System.out.println("执行了apply方法"+i);
                return "re:"+i;
            }
        };

        //lambda实现
        Function<Integer,String> function2 = (integer)->{
            System.out.println("执行了apply方法"+integer);
            return "re"+integer;
        };
        function.apply(1024);
        System.out.println("===========================");
        function2.apply(2048);
    }
}
/*
执行了apply方法1024
===========================
执行了apply方法2048
*/
4. 断定型接口 Predicate

断定型接口:有一个输入参数,返回值只能是 布尔值!

Java8新特性lambda和stream系列_第3张图片

代码实现

package com.concurrent.demo13Function;

import java.util.function.Predicate;

/**
 * 有一个参数,返回值只能是布尔
 * @author lane
 * @date 2021年05月26日 下午7:10
 */
public class PredicateDemo {

    public static void main(String[] args) {
        //匿名内部类方式
        Predicate<String>  predicate = new Predicate<String>() {
            @Override
            public boolean test(String s) {
                System.out.println("执行了predicate方法");
                return s.isEmpty();
            }
        };

        System.out.println(predicate.test("1024"));

        //lambda方式实现

        Predicate<String> predicate2 = (string)->{
            System.out.println("lambda执行了predicate方法");
            return string.isEmpty();};

        System.out.println(predicate2.test(""));


    }

}
/*
执行了predicate方法
false
lambda执行了predicate方法
true
*/
5. 消费型接口 Consumer

只有一个输入,没有返回值的接口

Java8新特性lambda和stream系列_第4张图片

代码实现

package com.concurrent.demo13Function;

import java.util.function.Consumer;

/**
 * 消费型接口只有输入没有返回值
 * @author lane
 * @date 2021年05月26日 下午7:17
 */
public class ConsumerDemo {

    public static void main(String[] args) {

        Consumer<String> consumer = new Consumer<String>(){
            @Override
            public void accept(String s) {
                System.out.println("内部类执行了accept方法"+s);
            }

            @Override
            public Consumer<String> andThen(Consumer<? super String> after) {
                return null;
            }
        };
        consumer.accept("1024");

        Consumer<String> consumer1 = (s->{
            System.out.println("lambda执行了consumer方法"+s);
        });

        consumer1.accept("1024");


    }


}
/*
内部类执行了accept方法1024
lambda执行了consumer方法1024
*/
6. 供给型接口 Supplier

没有参数值,只有一个返回值

Java8新特性lambda和stream系列_第5张图片

代码实现

package com.concurrent.demo13Function;

import java.util.function.Supplier;

/**
 * Supplier 供给型接口 没有参数,只有返回值
 * @author lane
 * @date 2021年05月26日 下午7:26
 */
public class SupplierDemo {

    public static void main(String[] args) {
        Supplier<String> supplier = new Supplier<String>(){
            @Override
            public String get() {
                System.out.println("匿名内部类执行get方法");
                return "1024";
            }
        };
    supplier.get();
        Supplier<String> supplier1 =()->{
            System.out.println("lambda执行get方法");
            return "1024";
        };

        supplier1.get();

    }
}
/*匿名内部类执行get方法
lambda执行get方法
 */

看到这里,你可能有疑问,这都是什么啊。 实际这就是lambda表达式和stream的核心。这类接口就是Java 特意为lambda来创建的函数式接口,凡是这类接口都可以写成lambda的形式,这类接口也是stream流操作的核心参数

三. Java8新特性之Stream流

Stream介绍
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等

Stream特性

  • 不是数据结构它没有内部存储 它只是用操作管道从 source(数据结构、数组、generator function、IO channel)抓取数据。它也绝不修改自己所封装的底层数据结构的数据。例如 Stream 的 filter 操作会产生一个不包含被过滤元素的新 Stream,而不是从 source 删除那些元素。
  • 不支持索引访问 但是很容易生成数组或者 List 。
  • 惰性化很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始。Intermediate 操作永远是惰性化的。
  • 并行能力当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的。
  • 可以是无限的集合有固定大小,Stream 则不必。limit(n) 和 findFirst() 这类的 short-circuiting 操作可以对无限的 Stream 进行运算并很快完成。

注意事项: Stream 的操作基本是函数式接口一般以 lambda 表达式或者::表达式为参数。
Stream 流操作类型

  • Intermediate:一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。 这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
  • Terminal:一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。 所以这必定是流的最后一个操作。 Terminal操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。

Stream使用

在开发中,我们有时需要对一些数据进行过滤,如果是传统的方式,我们需要对这批数据进行遍历过滤,会显得比较繁琐,如果使用steam流方式的话,那么可以很方便的进行处理。

个人理解

Java8新特性lambda和stream系列_第6张图片

stream流式表达式的结构

  1. 生成流的方法,一般如list.stream()

  2. 中间操作方法,如fileter(Predicate) 可以多个

  3. 终结方法,如foreach(Consumer) 只能有一个

Predicate和Consumer是否很眼熟呢

stream流的执行顺序

  1. stream流的创建和过滤,只是拼接条件并没有执行具体方法
  2. stream流的终结方法执行的时候,这时候才会执行所有的方法

可以理解为 产品原材料---->流水线工位—>流水线开关 当工位准备完成后,流水线才会打开,才会在具体工位执行操作

Java8新特性lambda和stream系列_第7张图片

stream流的执行原理

匿名内部类的延时执行现象

Java8新特性lambda和stream系列_第8张图片

代码实现流式原理

package com.concurrent.demo14Stream;

import java.util.Objects;
import java.util.function.Predicate;

/**
 * 匿名内部类的延时执行特性
 * @author lane
 * @date 2021年05月27日 上午12:47
 */
public class StreamDemo2 {

    public static void main(String[] args) {

        Predicate<String> predicate1 = new Predicate<String>() {
            @Override
            public boolean test(String s) {
                System.out.println("1执行了test方法了,参数为"+s);
                return s.endsWith("d");
            }
        };
        Predicate<String> predicate2 = new Predicate<String>() {
            @Override
            public boolean test(String s) {
                System.out.println("2执行了test方法了,参数为"+s);
                return s.startsWith("h");
            }
        };

        mymethod(predicate1,predicate2);


    }

    public static void mymethod(Predicate<String> pre1,Predicate<String> pre2){
       //源码 返回的是函数时接口
        /*default Predicate and(Predicate other) {
            Objects.requireNonNull(other);
            return (t) -> test(t) && other.test(t);
        }*/
        //1
        pre1.and(pre2);
        //2
        new Predicate<String>() {
            @Override
            public boolean test(String s) {
                System.out.println("执行了test方法了,参数为"+s);
                return s.startsWith("h") && s.endsWith("d");
            }
        };
        //看源码得知1等价于2,这样执行并不会打印任何信息
        //而stream流的创建和过滤,只是拼接条件并没有执行具体方法 依据匿名内部类的延时执行现象
        //多个拼接的方法并没有执行,类似于stream流式多个过滤方法
        //调用test方法就类似于stream流的终结方法,这时候才会执行所有的方法
        //list.stream().filter(s->s.startsWith("h")).filter(s->s.endsWith("d")).foreach((s)->System.out.println("执行了test方法了,参数为"+s);)
        pre1.and(pre2).test("helloworld");//这就是流的原理一种表现形式
        //打印内容为
        //1执行了test方法了,参数为helloworld
        //2执行了test方法了,参数为helloworld
 
        //源码
        /*default Predicate and(Predicate other) {
            Objects.requireNonNull(other);
            return (t) -> test(t) && other.test(t);
        }*/
    }

}

代码实现流式操作

package com.concurrent.demo14Stream;

import java.util.Arrays;
import java.util.List;

/**
 * 题目要求:一分钟内完成此题,只能用一行代码实现!
 * 现在有6个用户!筛选:
 * 1、ID 必须是偶数
 * 2、年龄必须大于23岁
 * 3、用户名转为大写字母
 * 4、用户名字母倒着排序
 * 5、只输出两个用户!
 * @author lane
 * @date 2021年05月26日 下午7:42
 */
public class StreamDemo {

    public static void main(String[] args) {
        User user1 = new User(1,"zhangsan",25);
        User user2 = new User(2,"lisi",27);
        User user3 = new User(3,"wangwu",26);
        User user11 = new User(4,"zhangsan",28);
        User user22 = new User(5,"lisi",15);
        User user33 = new User(6,"wangwu",23);
        List<User> userList = Arrays.asList(user1, user2, user3,user11,user22,user33);

        userList.stream()
                //参数为predicate有参数,返回布尔
                .filter((user)->{return user.getId()%2==0;})
                .filter((user)->{return user.getAge()>23;})
                //参数为function有参数和返回值
                .map(user -> {return user.getName().toUpperCase();})
                .sorted((u1,u2)->{return u2.compareToIgnoreCase(u1);})
                .limit(2)
                //返回值是consumer有个参数,无返回值
                .forEach((user)->{
                    System.out.println(user);
                });
    }

}


/*
ZHANGSAN
LISI
*/

可以看到strem常用的参数都是我们上一部分的函数式接口predicate``function``consumer,实际上stream的操作方法有很多很多,自己慢慢学,这里只是简单入门方法。可以参考JDK1.8的Lambda、Stream和日期的使用详解来学习更多的stream使用

四. Java8新特性之 :: 表达式

1. :: 表达式介绍

英文:double colon,双冒号(::)运算符在Java 8中被用作方法引用(method reference),方法引用是与lambda表达式相关的一个重要特性。它提供了一种不执行方法的方法。为此,方法引用需要由兼容的函数接口组成的目标类型上下文

方法引用
您使用lambda表达式创建匿名方法。 但是,有时lambda表达式除了调用现有方法外什么也不做。 在这种情况下,通常更容易按名称引用现有方法。 方法引用使您可以执行此操作; 它们是紧凑,易于阅读的lambda表达式,用于已经具有名称的方法。
摘自oracle官网

::关键字提供了四种语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,::关键字可以使语言更简洁,减少冗余代码。

语法种类 示例
引用静态方法 ContainingClass::staticMethodName
引用特定对象的实例方法 containingObject::instanceMethodName
引用特定类型的任意对象的实例方法 ContainingType::methodName
引用构造函数 ClassName::new

常用的一种场景是

把方法当做参数传到stream内部,使stream的每个元素都传入到该方法里面执行一下。

个人理解

  1. 必须是函数式接口才行
  2. 必须四种符合规范要求
  3. 只要我能推断出来,那么方法就是你的,不是也是(下面举例,知识不举例子就不是好的传授)
  4. 通过JRE进行推断方法是否可以执行,简化代码
  5. 不推荐使用,只是为了简化代码,毕竟改BUG也是程序员工作的一部分,还要猜
  6. JRE乐意猜,那么你猜我猜不猜
  7. 要能看懂,不然就被别人装逼得逞了
  8. 常用于stream流操作中
  9. 完全可以不使用,替代方式有很多
2. Oracle官方翻译讲解

官方的例子是伪代码,代码我修改一下后的

代码实例

package com.concurrent.demo16;

import java.time.LocalDate;
import java.util.Calendar;

/**
 * @author lane
 * @date 2021年05月27日 下午7:37
 */

public class Person {

    public enum Sex {
        MALE, FEMALE
    }

    String name;
    Integer birthday;
    Sex gender;
    String emailAddress;

    public int getAge() {
        // ...
        return 1024;
    }

    public Integer getBirthday() {

        return birthday;
    }

    public static int compareByAge(Person a, Person b) {
        return a.birthday.compareTo(b.birthday);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setBirthday(Integer birthday) {
        this.birthday = birthday;
    }

    public Sex getGender() {
        return gender;
    }

    public void setGender(Sex gender) {
        this.gender = gender;
    }

    public String getEmailAddress() {
        return emailAddress;
    }

    public void setEmailAddress(String emailAddress) {
        this.emailAddress = emailAddress;
    }

}


测试类

package com.concurrent.demo16;

import java.util.*;

/**
 * @author lane
 * @date 2021年05月27日 下午7:03
 */
public class PersonTest {

    public static void main(String[] args) {

        Person p1 = new Person();
        Person p2 = new Person();
        Person p3 = new Person();
        List<Person> list = new ArrayList<>();
        p1.setBirthday(20);p2.setBirthday(12);p3.setBirthday(22);
        list.add(p1);
        list.add(p2);
        list.add(p3);

        Person[] rosterAsArray = list.toArray(new Person[list.size()]);

        class PersonAgeComparator implements Comparator<Person> {
            public int compare(Person a, Person b) {
                return a.getBirthday().compareTo(b.getBirthday());
            }
        }
        //工具类排序方法
//        static  void sort(T[] a, Comparator c)


        //方式一:创建类PersonAgeComparator重写接口
        Arrays.sort(rosterAsArray, new PersonAgeComparator());
        //方式二:lambda
        Arrays.sort(rosterAsArray,
                (Person a, Person b) -> {
                    return a.getBirthday().compareTo(b.getBirthday());
                }
        );
        //方式三:最简化版lambda,参数类型可以略,只有一行的时候可以省略掉 return 和 {}
        Arrays.sort(rosterAsArray, (a,b) -> a.getBirthday().compareTo(b.getBirthday()));

        //方式四:可以是随便一个类的类似方法,只要推断没错
        Arrays.sort(rosterAsArray,  (a, b) -> Person.compareByAge(a, b));
        //可以是随便一个类的类似方法,只要推断没错
        Arrays.sort(rosterAsArray,  (a, b) -> Human.compareByAge(a, b));
        //方式五:::表达式版 你是不是一脸懵逼(其实就是推断出来的)
        Arrays.sort(rosterAsArray, Person::compareByAge);
      
        Arrays.sort(rosterAsArray,Human::compareByAge);
      
        for (int i = 0; i <rosterAsArray.length ; i++) {
            System.out.println(rosterAsArray[i]);
        }
      // Arrays.stream(rosterAsArray).forEach((ro)->System.out.println(ro));
       //Arrays.stream(rosterAsArray).forEach(System.out::println);

    }
}


1. 引用静态方法

请注意,该接口Comparator是功能接口。因此,您可以使用lambda表达式,而不是定义并创建一个新类的实例,该实例实现Comparator

Arrays.sort(rosterAsArray,
    (Person a, Person b) -> {
        return a.getBirthday().compareTo(b.getBirthday());
    }
);

但是,这种用于比较两个Person实例的出生日期的方法已经存在Person.compareByAge。您可以改为在lambda表达式的主体中调用此方法:

Arrays.sort(rosterAsArray,
    (a, b) -> Person.compareByAge(a, b)
);

由于此lambda表达式会调用现有方法,因此您可以使用方法引用代替lambda表达式:

Arrays.sort(rosterAsArray, Person::compareByAge);

方法引用Person::compareByAge在语义上与lambda表达式相同(a, b) -> Person.compareByAge(a, b)。每个都有以下特征:

  • 它的形参列表是从复制Comparator.compare,这是(Person, Person)
  • 它的主体调用该方法Person.compareByAge

2. 引用特定对象的实例方法

class ComparisonProvider {
    public int compareByName(Person a, Person b) {
        return a.getName().compareTo(b.getName());
    }
        
    public int compareByAge(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);

方法引用myComparisonProvider::compareByName调用compareByName作为对象一部分的方法myComparisonProviderJRE推断方法类型参数,在这种情况下为(Person, Person)

3. 引用特定类型的任意对象的实例方法

String[] stringArray = { "Barbara", "James", "Mary", "John",
    "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);

方法参考的等效lambda表达式String::compareToIgnoreCase将具有形式参数列表(String a, String b),其中ab是用于更好地描述此示例的任意名称。方法引用将调用该方法a.compareToIgnoreCase(b)

4. 引用构造函数

功能接口Supplier包含一个get不带任何参数并返回一个对象的方法。因此,您可以transferElements使用lambda表达式调用该方法,如下所示:

Set<Person> rosterSetLambda =
    transferElements(roster, () -> { return new HashSet<>(); });

您可以使用构造函数引用代替lambda表达式,如下所示:

Set<Person> rosterSet = transferElements(roster, HashSet::new);

Java编译器推断您要创建一个HashSet包含type元素的集合Person。或者,您可以指定以下内容:

Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);
3. 个人代码示例讲解
/**
 * @author lane
 * @date 2021年05月27日 下午6:29
 */
@FunctionalInterface
public interface MyColon {
    int fiveTwoSeven();

}


/**
 * @author lane
 * @date 2021年05月28日 上午11:55
 */
public class MyColonImpl implements MyColon {
    @Override
    public int fiveTwoSeven() {
        System.out.println("我是方式零:创建一个类MyColonImpl实现MyColon接口,昨天是5月27日");
        return 0;
    }
}

/**
 * @author lane
 * @date 2021年05月27日 下午6:34
 */
public class MyColonDemo {

    public int fiveTwoSevenDemo() {
        System.out.println("我是方式三:lambda推断类型II,也可以是方式四或者五");
        return 0;
    }

 public static int staticFiveTwoSevenDemo(){
     System.out.println("我是方式六:lambda推断类型II ::静态方法");
     return 0;
 }

}

真正的测试类

/**
 * 举例子讲解::表达式的及其四种类型
 * @author lane
 * @date 2021年05月27日 下午6:24
 */
public class MyColonTest {

    public static void main(String[] args) {
        test1();

    }

    // 多种方式循序渐进方便理解
    // test1引用特定对象的实例方法
    public static void test1(){

        //方式零:创建一个类MyColonImpl实现MyColon接口
        System.out.println("=============方式零================");
        MyColon myColon0 = new MyColonImpl();
        myColon0.fiveTwoSeven();
        //方式一:匿名内部类方式
        System.out.println("=============方式一================");
        MyColon myColon1 = new MyColon() {
            @Override
            public int fiveTwoSeven() {
                System.out.println("我是方式一:匿名内容类方式,昨天是5月27");
                return 0;
            }
        };
        myColon1.fiveTwoSeven();
        //方式二:lambda标准类型I
        System.out.println("=============方式二================");
        MyColon myColon2 = ()->{
        System.out.println("我是方式二:lambda标准类型I,昨天是5月27");
        return 0;};
        myColon2.fiveTwoSeven();
        //方式三:lambda推断类型II  方法都是返回int类型,可以推断出来
        //注意:类MyColonDemo和接口MyColon 没有任何关系 方法名字不一样 但是参数和返回值类型一样
        System.out.println("=============方式三================");
        MyColonDemo myColonDemo = new MyColonDemo();
        MyColon myColon3 = ()->{return myColonDemo.fiveTwoSevenDemo();};
        myColon3.fiveTwoSeven();
        //方式四:lambda推断类型II简化版
        System.out.println("=============方式四================");
        MyColon myColon4 = ()->myColonDemo.fiveTwoSevenDemo();
        myColon4.fiveTwoSeven();
        //方式五:lambda推断类型II ::表达式版 对象实例方法
        System.out.println("=============方式五================");
        MyColon myColon5= myColonDemo::fiveTwoSevenDemo;
        myColon5.fiveTwoSeven();
        //方式六: ::表达式版 类型静态方法
        System.out.println("=============方式六================");
        MyColon myColon6= MyColonDemo::staticFiveTwoSevenDemo;
        myColon6.fiveTwoSeven();
        //方式七: ::表达式版 引用特定类型的任意对象的实例方法
        System.out.println("=============方式七================");
        LongBinaryOperator longBinaryOperator = (long a, long b) -> Long.sum(a, b);
        LongBinaryOperator longBinaryOperator2 = Long::sum;
        System.out.println("方式七:引用特定类型如Long的sum方法"+longBinaryOperator2.applyAsLong(1, 2));
        BiFunction<String, String, Boolean> endsWith = String::endsWith;
        System.out.println("方式七:引用特定类型如String的endswith方法"+endsWith.apply("hello", "o"));
       //引用String的排序方法
        String[] stringArray = { "Barbara", "James", "Mary", "John",
                "Patricia", "Robert", "Michael", "Linda" };
        Arrays.sort(stringArray, String::compareToIgnoreCase);
        Arrays.stream(stringArray).map(String::toUpperCase).toArray();
        //方式八: ::表达式版 引用构造函数方法,一般还是那种特定类型
        System.out.println("=============方式七================");
        //无返回值的一般不用
       /* public interface MyVoid {
            void test();
        }
        MyVoid myvoid = MyColonDemo::new;*/
        //返回值为该对象的
        Callable<MyColonDemo> myColonDemoCall = MyColonDemo::new;
        //返回值为hashSet
        HashSet<String> hashSet = new HashSet<>();
        Supplier<HashSet<String>> supHash = HashSet<String>::new;
        //返回值为String的
        Supplier<String> supplier = String::new;

    }

}

测试结果

=============方式零================
我是方式零:创建一个类MyColonImpl实现MyColon接口,昨天是5月27日
=============方式一================
我是方式一:匿名内容类方式,昨天是5月27
=============方式二================
我是方式二:lambda标准类型I,昨天是5月27
=============方式三================
我是方式三:lambda推断类型II,也可以是方式四或者五
=============方式四================
我是方式三:lambda推断类型II,也可以是方式四或者五
=============方式五================
我是方式三:lambda推断类型II,也可以是方式四或者五
=============方式六================
我是方式六:lambda推断类型II ::静态方法
=============方式七================
方式七:引用特定类型如Long的sum方法3
方式七:引用特定类型如String的endswith方法true
=============方式七================

重点看下这段代码,类MyColonDemo和接口MyColon

				 //方式三:lambda推断类型II  方法都是返回int类型,可以推断出来
        //注意:类MyColonDemo和接口MyColon 没有任何关系 方法名字不一样 但是参数和返回值类型一样
        System.out.println("=============方式三================");
        MyColonDemo myColonDemo = new MyColonDemo();
        MyColon myColon3 = ()->{return myColonDemo.fiveTwoSevenDemo();};
        myColon3.fiveTwoSeven();
			//方式五:lambda推断类型II ::表达式版 对象实例方法
        System.out.println("=============方式五================");
        MyColon myColon5= myColonDemo::fiveTwoSevenDemo;

注意:

  1. 类和接口没有任何关系比如实现
  2. 方法名字不一样
  3. 但是参数和返回值类型一样
  4. 由此可以根据3假定推断这个类实现了这个方法,重写了方法 所以可以写成

MyColon myColon3 = ()->{return myColonDemo.fiveTwoSevenDemo();};

简化后就是

MyColon myColon5 = myColonDemo::fiveTwoSevenDemo;

这种推断式写法,A接口 = B类::方法 的形式

个人看法

一般不建议使用,我们在写代码过程中难免会出现错误,这种形式对于看懂代码和寻找错误信息都是十分费劲的。毕竟能一眼看出来,谁也不喜欢推断,当然JRE是可以推断的啦!:: 表达式官方介绍翻译版可以看这篇文章JAVA 8 ‘::’ 关键字官方翻译版

你可能感兴趣的:(Java新特性,lambda,stream)