深入学习Java8 Lambda (default method, lambda, function reference, java.util.function 包)
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
- 2、Lambda
- 3、Method Reference
- 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的实现类中完成各种各样的操作,例如求和、乘积等。如果使用匿名类来实现的话,代码量会有不少,即便是我们代码写的很规范,变量名命名也可以做到见名知意,但是代码看起来仍旧是比较凌乱的:
@Test
public void test0(){
Computer getSum = new Computer() {
@Override
public double compute(double a, int b) {
if(b!=0) {
return a + b;
}
return a;
}
};
executeCompute(nums, getSum); Computer getMult = new Computer() {
@Override
public 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;
}
@Override
public 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));
}
} @Test
public 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永远是同一个实例呢?
为了找到答案,改造测试用例如下:
@Test
public 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方法 | 
 例如:Math::abs | 
| 引用到一个对象的某个方法 | 
 例如:System.out::print | 
| 引用到一个类的某个方法 | 
 例如:PrintWriter::print | 
| 引用到一个构造函数 | 
 例如: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");
} @Test
public void test0(){
// 原始写法
Integer[] arr1 = new Integer[]{12,23,545,2,345,0, -1};
Comparator<Integer> comparator1 = new Comparator<Integer>() {
@Override
public int compare(Integer t1, Integer t2) {
return t1.compareTo(t2);
}
};
Arrays.sort(arr1, comparator1);
printArray(arr1, new MyPrinter() {
@Override
public 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;
        }
        @Override
        public 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;
    }
测试:
@Test
public void test0() {
Collection<Person> selection = doSelection(persons, (person ->person.age>84 && person.gender==Gender.man));
Consumer<Person> printer =System.out::println;
selection.forEach(printer);
} @Test
public 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_98 Process 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 包)的更多相关文章
- Function接口 – Java8中java.util.function包下的函数式接口
		Introduction to Functional Interfaces – A concept recreated in Java 8 Any java developer around the ... 
- JAVA8的java.util.function包 @FunctionalInterface
		1 函数式接口java.util.function https://www.cnblogs.com/CobwebSong/p/9593313.html 2 JAVA8的java.util.functi ... 
- 函数式接口java.util.function
		什么是函数式接口 为什么要用函数式接口 java.util.function和其他的函数式接口 lamdba表达式 方法引用 流 Stream 1 什么是函数式接口 用@FunctionInterfa ... 
- The main method caused an error: java.util.concurrent.ExecutionException: org.apache.flink.runtime.client.JobSubmissionException: Failed to submit JobGraph.
		在使用flink run命令提交任务可能会遇到如下错误: The program finished with the following exception: org.apache.flink.cli ... 
- SpringMVC开发中遇到的异常1:No primary or default constructor found for interface java.util.List
		Request processing failed; nested exception is java.lang.IllegalStateException: No primary or defaul ... 
- JAVA8的java.util.function包
		一 概述 name type description Consumer Consumer< T > 接收T对象,不返回值 Predicate Predicate< T > 接收 ... 
- java.util.function 中的 Function、Predicate、Consumer
		函数式接口: 函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但可以有多个非抽象方法的接口. 函数式接口可以被隐式转换为 Lambda 表达式. Function ... 
- java.util.concurrent包学习笔记(一)Executor框架
		类图: 其实从类图我们能发现concurrent包(除去java.util.concurrent.atomic 和 java.util.concurrent.locks)中的内容并没有特别多,大概分为 ... 
- Java.util.concurrent包学习(一) BlockingQueue接口
		JDK1.7 BlockingQueue<E>接口 (extends Queue<E>) 所有父接口:Collection<E>,Iterable<E> ... 
随机推荐
- 漂亮CSS样式用户留言表单
			基本样式 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF- ... 
- 基于Gulp + Browserify构建es6环境下的自动化前端项目
			随着React.Angular2.Redux等前沿的前端框架越来越流行,使用webpack.gulp等工具构建前端自动化项目也随之变得越来越重要.鉴于目前业界普遍更流行使用webpack来构建es6( ... 
- win10 系统输入法与 idea的 ctr+shift+f 快捷键冲突,解决办法
			我认为首先是输入法简繁热键的冲突,(当然也有人认为是qq的热键冲突,) 解决办法: 1.首先打开搜狗输入法的设置(当然有的可能不是搜狗输入法,其他的输入法设置步骤都是大同小异) 看到了吗,就是这个热键 ... 
- 《深入理解Java虚拟机:JVM高级特性与最佳实践》【PDF】下载
			<深入理解Java虚拟机:JVM高级特性与最佳实践>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230062566 内容简介 作为一位 ... 
- 【CSS3】浏览器内核、私有前缀
			浏览器内核 私有前缀 浏览器 webkit -webkit- chrome.safari.安卓.ios trident -ms- IE gecko -moz- firefox presto -o- o ... 
- asp.net mvc 下拉列表
			第一步:新建一个格式化下拉列表的公共类文件 using System; using System.Collections; using System.Collections.Generic; usin ... 
- 你的Excel表格颜色搭配的对么?
			在昨天的文章中,我们讨论了<Excel表格制作的基本九大原则>,今天我们还要继续聊聊,Excel表格的颜色搭配规则. 一个表格的美丑与否,除了基本的格式之外,如何配色也是非常关键的,如果只 ... 
- 关于PLC
			学电气的一方面是单片机,一方面是PLC,,,,常常看到说选择比努力更重要,,单片机都很熟悉了,我就来介绍一下PLC..... 然后呢我先吹吹牛,,,目的是让大家相信我介绍的PLC绝对是亲身体验.... ... 
- [Upper case conversion ] 每个单词的首小写字母转换为对应的大写字母
			Given a string , write a program to title case every first letter of words in string. Input:The firs ... 
- bzoj 4345: [POI2016]Korale
			Description 有n个带标号的珠子,第i个珠子的价值为a[i].现在你可以选择若干个珠子组成项链(也可以一个都不选),项链的价值为所有珠子的价值和.现在给所有可能的项链排序,先按权值从小到大排 ... 
