Java 8 Lambda 、MethodReference、function包

多年前,学校讲述C#时,就已经知道有Lambda,也惊喜于它的方便,将函数式编程方式和面向对象式编程基于一身。此外在使用OGNL库时,也是知道它可以支持Lambda。但是OGNL中的lambda毕竟不是java语言本身支持,操作有诸多不便,使用Lambda不就是为了方便吗。但是Java中迟迟没有支持Lambda。直到Java 8的来临,总算给Java注入了这个新鲜血液。

  • 1、default method or static method interface

    • 1.1 default method
    • 1.2 static method
  • 2、Lambda
    • 2.1 Lambda 用在什么地方
    • 2.2 语法格式
    • 2.3 demo
    • 2.4 Lamdba几点注意事项(FunctionalInterface)
    • 2.5 Lambda 与匿名类的区别
    • 2.6 Lambda 的单例与多例
  • 3、Method Reference
    • 3.1 什么是Method Reference
    • 3.2 何时使用Method Reference
    • 3.3 demo
  • 4、java.util.function包
  • 5、综合demo
  • 6、Lambda的翻译、运行、性能

1、default Method or static method in interface

1.1 default method

Java 8 之前,为一个已有的类库增加功能是非常困难的。具体的说,接口在发布之后就已经被定型,除非我们能够一次性更新所有该接口的实现,否则向接口添加方法就会破坏现有的接口实现。Default method的目标即是解决这个问题,使得接口在发布之后仍能被逐步演化。

Default method,即接口中声明的方法是有方法体的方法,并且需要使用default关键字来修饰。

举个例子:java.util.Collection是线性表数据结构的统一接口,java 8里为它加上4个方法: removeIf(Predicate p)、spliterator()、stream()、parallelStream()。如果没有default method,

就得对java.util.Collection、java.util.AbstractCollection、java.util.Set 等,还有很多用户自定义的集合添加这4个方法,如果不添加,这些代码在jdk8上运行就会失败。

而使用default method,就可以完美解决这个问题。只要在java.util.Collection中将这4个新加的方法设置为default即可。

在引入default方法后,可能会带来如下问题:

1)一个类ImplementClass直接实现(中间没有父类)了两个接口 InterfaceA, InterfaceB,这两个接口中有同一个方法: void hello()。那么ImplementClass必须重写方法hello,不然不知道到底继承哪个,这里不会去管接口中的default

2) 一个类ImplementClassA 直接实现了接口InterfaceA,InterfaceA中定义了一个非default的void hello()方法。有另外一个接口InterfaceB,定义了一个default方法void hello();现在有一个实现类ImplementClassAB,extends了ImplementClassA,implements了InterfaceB, 且ImplementClassAB没有重写void hello()方法。那么在调用ImplementClassAB#hello()时,到底是调用的是ImplementClassA#hello(),还是调用的是InterfaceB#hello()呢?

为了解决这个问题,有这么一项原则:类的方法优先调用。所以应该是调用ImplementClassA#hello()。

1.2 static method

与此同时,java8在接口中也引入了static method,它也是有方法体的,需要使用static关键字修饰。另外要特别注意的是:如果一个类中定义static方法,那么访问这个方法可以使用ClassName.staticMethodName、instance.staticMethodName两种方式来访问static方法,但是对于接口中定义的静态方法,只能通过InterfaceName.staticMethodName方式来访问。

2、Lambda

Lambda官方教程地址:

https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

2.1 Lambda用在什么地方?

Anonymous Classes, shows you how to implement a base class without giving it a name.Although this is often more concise than a named class, for classes with only one method, even an anonymous class seems a bit excessive and cumbersome. Lambda expressions let you express instances of single-method classes more compactly.

从这段话可以看出Lambda主要用于替代匿名类,并以简约而清晰的方式展现业务处理逻辑。

2.2 语法格式

将匿名类的写法转换成一行代码的格式:

(arguments) -> {method body}

2.3 demo

下面用一个例子来说明:

    public static interface Computer{public double compute(double a, int b);}public static void executeCompute(List<Integer> list, Computer computer){double result = 1D;if(list!=null){for (Integer i : list) {if(i==0){continue;}result = computer.compute(result, i);}}System.out.println(result);}

我们可以在Computer的实现类中完成各种各样的操作,例如求和、乘积等。如果使用匿名类来实现的话,代码量会有不少,即便是我们代码写的很规范,变量名命名也可以做到见名知意,但是代码看起来仍旧是比较凌乱的:

    @Testpublic void test0(){Computer getSum = new Computer() {@Overridepublic double compute(double a, int b) {if(b!=0) {return a + b;}return a;}};executeCompute(nums, getSum);Computer getMult = new Computer() {@Overridepublic double compute(double a, int b) {return a * b;}};executeCompute(nums, getMult);    }

如果使用Lambda来编写的话,看起来就简明清晰了:

@Test
public void test0(){executeCompute(nums, (a,b)->{ return (b!=0) ? (a+b) : a;});executeCompute(nums, (a,b)->{ return a * b;} );
}

从上面的demo看出,我们甚至不需要去设置参数的类型,只需要一个参数列表即可。

如果要处理的参数是复杂类型怎么办呢?

    /*** Operate an Object use an Lambda*/static class Person{private int age;private String name;Person(String name, int age){this.name = name;this.age = age;}@Overridepublic String toString() {return "name: "+name + " age: "+age;}}private static interface InfoGetter {public String get(Person p);public default String get2(int a, Person p){return "";};public default String get3(Person p){return "";};}static void show(List<Person> persons, InfoGetter infoGetter){persons.forEach((person)->{System.out.println(infoGetter.get(person));});}

测试代码:

    static List<Person> persons = new ArrayList<>();static{for(int i = 0 ; i< 10; i++) {persons.add(new Person("hello_"+i, i));}}@Testpublic void test1(){LambdaTests ctx = this;InfoGetter getter1= (Person person)->{System.out.println(ctx == this);Method[] methods = this.getClass().getMethods();return person.toString();};
}

更多demo,参见:

http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html

2.4 Lambda几点注意事项(FunctionalInterface)

1、lambda用于替代匿名内部类的实例,它的本质是运行是由JVM产生匿名内部类,并生成一个实例。

2、一个接口(只能是接口),想要使用Lambda来替代,最多只能有一个抽象方法。

3、一个lambda通常用作某个方法的参数

4、lambda内部的this,是产生lambda时的对象。

5、lambda表达式的参数,可以有类型,也可以不指定。

2.5 Lambda 与匿名类的区别

1)一个是对象,一个是类。Lamdba表达式,可以看做是一个匿名类的实例。

2)this关键字含义不同。在匿名类中访问外部类的实例,需要使用outterclass.this来引用,而在lambda中可以直接使用this来引用。

2.6 Lambda 的单例与多例

上面知道lamdba是一个实例,那么我在一个方法内部,写上几个完全一样的lambda,他们是否是同一个对象呢?什么情况下,你写的lambda永远是同一个实例呢?

为了找到答案,改造测试用例如下:

    @Testpublic void test1(){LambdaTests ctx = this;InfoGetter getter1= (Person person)->{System.out.println(ctx == this);Method[] methods = this.getClass().getMethods();return person.toString();};InfoGetter getter2= (Person person)->{System.out.println(ctx == this);return person.toString();};show(persons, getter1);System.out.println(getter1==getter2);InfoGetter getter3 = getInfoGetter();InfoGetter getter4 = getInfoGetter();System.out.println(getter3==getter4);InfoGetter getter5 = getInfoGetter("a");InfoGetter getter6 = getInfoGetter("b");System.out.println(getter3==getter5);System.out.println(getter5==getter6);InfoGetter getter7 = getInfoGetter("a",1);InfoGetter getter8 = getInfoGetter("b",2);System.out.println(getter3==getter5);System.out.println(getter7==getter8);}static InfoGetter getInfoGetter(){return (p)->"";}static InfoGetter getInfoGetter(String str){System.out.println(str);return (p)->{int a = 1; return "" + a;};}static InfoGetter getInfoGetter(String str, int i){System.out.println(str);return (p)->str + i;}

调试截图:

从上面的几个测试用例上,可以得出如下结论:

1)每写一次lambda表达式,就代表创建一个实例(不管表达式里,会引用什么内容)。

2)想要得到一个单例的lambda实例,可以在static方法中声明该lambda,并且该lambda方法体中,除了lambda代表的方法的参数外,不能用其他的变量。

3、MethodReference

3.1 什么是MethodReference ?

Person p = new Person(); 这行代码里, 我们称p为对象的引用。那么什么是method 引用呢?故名思议,一个变量指向了一个方法,就称为方法引用。

官方文档:https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

java中的method分为static,非static的,构造器本身也是一个方法。要想引用到这些方法,该如何做呢?

kind example
引用到一个static方法

::static方法

例如:Math::abs

引用到一个对象的某个方法

对象::方法

例如:System.out::print

引用到一个类的某个方法

类:方法

例如:PrintWriter::print

引用到一个构造函数

类:new

例如:String::new

3.2 何时可以使用MethodReference?

Lambda用于简化匿名类的写法。有时候,我们写的表达式中,只是调用了另外一个类的某个方法。此时我们连lamdba表达式都可以省略了,使用MethodReference来表示即可。

3.3 demo

/**** Lambda 的本质是在运行时(编译时不会产生的)产生一个匿名内部类* MethodReference 的本质是产生一个Lambda,并在lambda里调用你指定的方法。* 所以如果你要写的Lambda只是用于调用另外一个方法时,你完全可以用MethodReference来替代的。*/
public class MethodReferenceTest {private static interface MyPrinter {public void print(Serializable o);}private static void printArray(Integer[] arr, MyPrinter printer){if(arr==null){return;}for (int i = 0; i < arr.length; i++) {printer.print(arr[i]);printer.print(" ");}printer.print("\n");}@Testpublic void test0(){// 原始写法Integer[] arr1 = new Integer[]{12,23,545,2,345,0, -1};Comparator<Integer> comparator1 = new Comparator<Integer>() {@Overridepublic int compare(Integer t1, Integer t2) {return t1.compareTo(t2);}};Arrays.sort(arr1, comparator1);printArray(arr1, new MyPrinter() {@Overridepublic void print(Serializable o) {System.out.print(o);}});// 使用lambda的写法Integer[] arr2 = new Integer[]{12,23,545,2,345,0, -1};Arrays.sort(arr2, (Integer x, Integer y)->{return x.compareTo(y);});printArray(arr2, (x)->System.out.print(x));// 使用MethodReference的写法Comparator<Integer> comparator3 = Integer::compareTo;Integer[] arr3 = new Integer[]{12,23,545,2,345,0, -1};Arrays.sort(arr3, comparator3);MyPrinter printer = System.out::print;printArray(arr3, printer);printer.print("test finish\n");Comparator<Integer> comparator4 = (Integer x,Integer y)->{return x.compareTo(y);};}
}

上面的例子中,声明了一个接口MyPrinter,然后在printArray方法中使用该接口。那么此后在调用printArray方法时,就可以使用lambda了。当lambda的方法体只是调用某个方法是,可以直接使用method refence来替代,所以就可以直接使用System.out::print来执行。此外,Arrays.sort的第二个参数是一个Comparator接口,此时我们又可以使用lambda来实现一个Comparator了,在我们要实现的Comparator里,只需要调用comparTo方法,所以我们又可以使用Method reference来替代lambda了。

4、java.util.function包

通过上面的学习知道,只有接口才可能被lambda替代,抽象类是不行的。很多时候,要使用的接口里的方法,也就那一两个。如果每一次我们想要使用lambda时,都去声明一个接口岂不很麻烦吗?好在JDK里内置了可能常用的接口,在java.util.function包下。

来看看JDK doc里如何描述这个包的:

Functional interfaces (java.util.function包下的这些接口) provide target types (函数的参数,被称为target) for lambda expressions and method references. Each functional interface has a single abstract method, called the functional method for that functional interface, to which the lambda expression's parameter and return types are matched or adapted.  这个意思再明白不过了。

   在学习这些接口之前,先要知道几个英文单词的含义:Nilary 零元,Unary 一元,Binary 二元,Ternary 三元,Quaternary 四元。对于一个算子来说,一个参数,就是一元运算;2个参数就是二元运算。

在java.util.function包下提供了很多接口(我们可以直接理解为函数),主要分为下面几类:

1)Predicate 为target type提供断言。参数 T,返回 boolean。

2)Consumer 消费target type。参数 T,无返回值(void)。

3)Function 对target type做转换。参数T,返回R。

4)Supplier 供应target,可以理解为target的factory。无参,返回T。

5)UnaryOperator 一元运算。继承Function接口。参数T,返回T。

6)BinaryOperator 二元运算。参数 T、U,返回R。

java.util.function下的接口最多支持到二元运算。有了这些接口,我们就可以省去创建接口的功夫,而直接使用lambda了。

如果自定义functional interface呢?其实很简单,定义一个可以用作lambda的接口,然后使用@FunctionalInterface 注解标注即可,当然这个注解并不是必须用的,只是使用了注解后,编译器会帮你检查一个FunctionInterface的必要条件。

5、综合Demo

下面就综合上面这些内容,用一个demo演示一下:

提供一个基础数据库表:

    private static class Person {String id;String name;int age;Gender gender;Person(String id, String name, int age, Gender gender){this.id = id;this.name = name;this.age = age;this.gender =gender;}@Overridepublic String toString() {return "id: "+id+"\tname: "+name + "\tage: "+age+"\tgender: "+gender;}}enum Gender{man,woman}static Collection<Person> persons = new ArrayList<>();static{for (int i = 0; i < 100; i++) {persons.add(new Person("id_"+i, "name_"+i, i, i%3==0 ? Gender.woman : Gender.man));}}

筛选出满足条件的行:

    public static <Row> Collection<Row> doSelection(Collection<Row> table, Predicate<Row> rowWhere) {Predicate isNull = (c) -> c == null;if(isNull.test(table)){return Collections.EMPTY_LIST;}Collection<Row> result = new ArrayList<>();table.forEach(row -> {boolean rowAvailable = row !=null && (rowWhere !=null ? rowWhere.test(row) : true);if (rowAvailable) {result.add(row);}});return result;}

投映出满足条件的列:

    public static <Row, Column> Collection<Column> doProjection(Collection<Row> table, Predicate<Row> rowWhere, String columnName, BiFunction<Row, String, Column> columnGetter, Predicate<Column> columnPredicate) {Predicate isNull = (c) -> c == null;if(isNull.test(table)){return Collections.EMPTY_LIST;}Collection<Column> result = new ArrayList<>();table.forEach(row -> {boolean rowAvailable = row !=null && (rowWhere !=null ? rowWhere.test(row) : true);if (rowAvailable) {Column column = columnGetter.apply(row, columnName);boolean columnAvailable =column!=null && (columnPredicate!=null?columnPredicate.test(column) : true);if(columnAvailable) {result.add(column);}}});return result;}

测试:

    @Testpublic void test0() {Collection<Person> selection = doSelection(persons, (person ->person.age>84 && person.gender==Gender.man));Consumer<Person> printer =System.out::println;selection.forEach(printer);}@Testpublic void test1() {Collection<String> projection = doProjection(persons,(Person person) -> {return person.age>84 && person.gender==Gender.man;},"name",(Person person, String filedName)->{return "name".equals(filedName) ? person.name : "";},null);Consumer<String> printer = System.out::println;projection.forEach(printer);}

结果:

id: id_85    name: name_85    age: 85    gender: man
id: id_86    name: name_86    age: 86    gender: man
id: id_88    name: name_88    age: 88    gender: man
id: id_89    name: name_89    age: 89    gender: man
id: id_91    name: name_91    age: 91    gender: man
id: id_92    name: name_92    age: 92    gender: man
id: id_94    name: name_94    age: 94    gender: man
id: id_95    name: name_95    age: 95    gender: man
id: id_97    name: name_97    age: 97    gender: man
id: id_98    name: name_98    age: 98    gender: man
name_85
name_86
name_88
name_89
name_91
name_92
name_94
name_95
name_97
name_98Process finished with exit code 0

6、Lambda 翻译与运行、性能

Lambad到底是怎样翻译的,又是如何保证this执行的是创建lambda的那个上下问题的。翻译的工作整个程序运行性能有多大的影响?这些问题都将在后续文章补充。

如果来不及等待,可以先参考:http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html

深入学习Java8 Lambda (default method, lambda, function reference, java.util.function 包)相关推荐

  1. Function接口 – Java8中java.util.function包下的函数式接口

    作者:   Mohamed Sanaulla  译者: 李璟(jlee381344197@gmail.com) 早先我写了一篇<函数式接口>,探讨了Java8中函数式接口的用法.如果你正在 ...

  2. java.util.function.Function的用法

    JDK 1.8 API包含了很多内建的函数式接口,在老Java中常用到的比如Comparator或者Runnable接口,这些接口都增加了@FunctionalInterface注解以便能用在lamb ...

  3. Java高并发编程学习(三)java.util.concurrent包

    简介 我们已经学习了形成Java并发程序设计基础的底层构建块,但对于实际编程来说,应该尽可能远离底层结构.使用由并发处理的专业人士实现的较高层次的结构要方便得多.要安全得多.例如,对于许多线程问题,可 ...

  4. java.util接口_Java 8中java.util.function包中的谓词和使用者接口

    java.util接口 在上一篇文章中,我写了关于Function接口的内容 ,它是java.util.package的一部分. 我还提到了Predicate接口,它是同一包的一部分,在这篇文章中,我 ...

  5. java.util接口_函数接口– Java 8中java.util.function包中的函数接口

    java.util接口 我以前写过有关功能接口及其用法的文章. 如果您正在探索要成为Java 8一部分的API,尤其是那些支持lambda表达式的API,您会发现很少的接口,例如Function,Su ...

  6. Java 8中java.util.function包中的谓词和使用者接口

    在我以前的文章中,我写了关于Function接口的内容 ,它是java.util.package的一部分. 我还提到了Predicate接口,它是同一包的一部分,在这篇文章中,我将向您展示如何使用Pr ...

  7. 函数接口– Java 8中java.util.function包中的函数接口

    我以前写过有关功能接口及其用法的文章. 如果您正在探索要成为Java 8一部分的API,尤其是那些支持lambda表达式的API,您会发现很少的接口,例如Function,Supplier,Consu ...

  8. java.util.function包

    目录 Supplier 参数个数扩展 参数类型扩展 特殊变形 Function,r> 参数个数扩展 参数类型扩展 特殊变形 Consumer 参数个数扩展 参数类型扩展 特殊变形 Predica ...

  9. java.lang.IllegalStateException: No primary or default constructor found for interface java.util

    java.lang.IllegalStateException: No primary or default constructor found for interface java.util.Lis ...

最新文章

  1. 我去,你写的 switch 语句也太老土了吧
  2. Javascript实现复选框(全选反选功能)
  3. Android listview 中嵌套 listview
  4. SAP UI5 数据绑定之高级技巧 - Type System
  5. 【VirtualBox】VirtualBox的桥接网络模式,为啥网络不稳定?
  6. 解决 error: Your local changes to the following files would be overwritten by merge:XXXX
  7. jdk卸载不干净怎么办_【实用】流氓软件卸载不干净?
  8. 无重复字符串的最长子串
  9. 字符串 kmp算法解析
  10. 浅谈C#中virtual和abstract的区别
  11. 四4层电梯三菱PLC程序带io表接线图
  12. K8s问题【flannel一直重启问题,CrashLoopBackOff】
  13. XCode11上传ipa到AppStoreConnect
  14. 矩阵分解实现个性化推荐算法实践
  15. 仪器计量校准机构的CNAS和CMA有哪些区别?分别具有什么作用?
  16. JavaScript - 正则(RegExp)判断文本框中是否包含特殊符号
  17. [声纹识别]语音识别系统框架[1]
  18. 没学历,当程序员还有机会吗?
  19. html 限制每行字数,毕业论文每行字数的设置
  20. pychar调试报错:Cython extension speeds up Python debugging

热门文章

  1. Access中字段类型及修改字段类型的SQL语句
  2. 微信小程序 没有找到可以构建的npm包
  3. Hadoop记录-JMX参数
  4. 定时任务--mysql数据库备份
  5. mysqldump: command not found
  6. Spark-1.6.0之Application运行信息记录器JobProgressListener
  7. 深入理解Openstack自动化部署
  8. Bypass WAF Cookbook
  9. python读写mysql总结
  10. 【Open Search产品评测】- 来往,7天轻松定制属于自己的搜索引擎