拉呱: 终于,学习jdk8的新特性了,初体验带给我的感觉真爽,代码精简的不行,可读性也很好,而且,spring5也是把jdk8的融入到血液里,总之一句话吧,说的打趣一点,学的时候自己难受,学完了写出来的代码,别人看着难受

开篇说一个问题,jdk8是如何把这些新的特性添加进来,并且兼容jdk7及以前版本的?

大家都知道,java的体系的建立,和interface有着莫大的关系,先有接口确定出一套明确的体系,再有它的实现类实现这套体系,比如,超级典型的java里面的集合体系;

新的需求来了,总不能去原有的接口里面添加抽象方法吧? 那不是开玩笑? 接口一改,所有的实现类,全部不能用了! java8是怎么做的呢? 允许接口中添加 default方法, 允许方法存在方法体

  • java的拓展接口的方法是 default方法 + 函数式接口 (更进一步说,是default方法的入参大多是该函数式接口的引用,函数体都是基于抽象方法的一套逻辑组合)

仔细想想,还真的是很精妙的,default方法虽然有方法体,但是它们的动作其实是动态传递进去的!!!

可以看一下下面的代码

list.forEach(new Consumer<String>() {
@Override
public void accept(String integer) {
System.out.println(integer);
}
}); /**
* 用lambda表达式的方法实现,得到Consumer的实现
* Consumer唯一未实现的抽象方法就是accept -- 接受一个参数,不返回任何值
* 下面的i为什么不写类型? 可以看看上面匿名内部类的实现方式, 编译器通过类型推断可以推断出 i 就是integer类型的
*/
list.forEach(i->System.out.println(i));
Consumer c1 = i->{}; /**
* 通过方法引用创建 函数式接口的实例
* 鼠标放到 :: 上,点进去, 编译器跳转到了 Consumer函数式接口 , 同样是通过类型推断,内部迭代出每个元素
*/
list.forEach(System.out::println);
Consumer c = System.out::println;
  • java的拓展类的方法是 类的话,直接添加新的方法就行

但是,相当一部分新增的方法,入参类型,依然是函数式接口


什么是函数式接口呢?

位于 java.util.function包

函数式接口本质上就是个接口,性质如下:

  1. 如果一个接口只有一个抽象方法,无论有没有FunctionInterface注解,这个接口都是一个函数式接口
  2. 如果我们在接口上加上了FunctionInterface注解,那么编译器按照函数式接口的要求,处理我们的接口

进一步,对于函数式接口,如何实现它呢?

  1. lambda表达式
  2. 方法引用
  3. 构造方法引用实现对应的实例
  4. 写个类,实现它, 不过没人这么做,傻里吧唧的

再进一步lambda表达式是什么?

  • lambda表达式其实是对象,但是这种对象必须依附于函数式接口
  • but,即便我们知道lambda是对象,它到底是什么类型的对象? 只能通过给定的特定的上下文得知
Consumer consumer = i->{};

有啥用?

  • 它解决了,在java中我们无法将函数作为参数传递给一个方法,也不能声明一个返回函数的方法这样一个问题
  • 像js这种函数编程语言,它当然可以做到,ajax向后端发送请求,得到的返回结果就是一个 回调函数 callback(){}

常见的函数式接口:

jdk8新添加的函数式接口有几十个,但是套路相似,通过下面集合常见的函数式接口,可以搞清楚它的来龙去脉

Consumer

// 接收一个参数,无返回值
void accept(T t);

Function

@FunctionalInterface
public interface Function<T, R> { /**
* 接受一个参数,返回一个值
* @param t the function argument
* @return the function result
*/
R apply(T t);

观看下面四行代码

/*
* 下面分别用 方法引用 和 lmabda表达式 实现函数式接口Function
* Function的函数式方法是apply 接受一个参数,并返回返回值, 这两部分的泛型是Function传递给它的
* 右半部分,不管使用什么方法,必须满足两件事,,编译器才会任务他是对函数式方法apply的实现
* 1. 方法返回值必须是String(第二个泛型)
* 2. 第一个String是使用方法的对象的类型,也必须是string 对比这两行代码,你就可以看到,动作是动态传递进去的!~~而不是使用预先定义的行为
* */
Function<String,String> function2 = String::toLowerCase;
Function<String,String> function3 = i->i.toUpperCase(); // 错误实例
// 无返回值
Function<String,String> function4 = i-> System.out.println(i); // 返回值是布尔类型
Function<String,String> function1 = String::contains;

Function的其他两个方法(详细记录第一个方法的使用)

  • 首先,compose()接受一个Function函数接口 before
  • 具体执行的过程是 this.apply(before.apply(v)), 也就说,先执行传递进来的这个函数式接口实例的apply方法
  • before执行apply(v),他的入参是V ==> ? super V ; 由他可知,这两个apply处理的都是V类型的数据
  • 它的返回值是 ? extends T , T就是这个Function唯一接受的参数的类型
  • 返回去看apply方法 : R apply(T t); 正好before执行完事把T类型的结果扔给this.apply(),再一步执行apply()
  • 最后看完整的看一下 return的形式: return (V v) -> apply(before.apply(v));画重点!!!,return 的这个结果,从形式上看,首先它是个lambda表达式,还可以把它理解成Function唯一的函数式接口的实现(只不过他们接受的参数比较特别),这里也可以直接把它理解成是一个Function类型的实例,或者是一套模板,apply()的嵌套;但是别忘了,apply嵌套的再多,最终也是要处理V, V具体是几? 我们要动态的传递给它,怎么传给他? 用compose()方法的返回值调用apply()方法;

其实上面的流程是java8已经搭好的架子, 我们要做的其实是apply的具体实现,apply是函数式接口,如何实现? lambda表达式,方法引用,构造方法引用随便挑

/**
* @param t the function argument
* @return the function result
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
} default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}

使用的demo

public int compute(int a, Function<Integer,Integer> function1,Function<Integer,Integer> function2){
Function<Integer,Integer> fun = function1.compose(function2);
fun.apply(a);
return function1.compose(function2).apply(a);
} public static void main(String[] args) {
FunctionText functionText = new FunctionText(); System.out.println(functionText.compute(2,i->i*4,j->j*3));
}

结果是两个24;没差

Bifurcation

  • 和Function相似,只不过,它唯一的函数式接口可以接受两个参数,返回一个值
  • 它只有andThen()这么一个默认方法,andThen()和compose正好相反,它先执行this.apply,得到一个返回值,传递非入参位置上的Function的apply() -- 因为它刚好接收一个参数,返回一个结果, 具体对谁进行apply?和我上面的分析雷同
@FunctionalInterface
public interface BiFunction<T, U, R> { /**
*接受两个参数,返回一个值
*
* @param t the first function argument
* @param u the second function argument
* @return the function result
*/
R apply(T t, U u); /** */
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t, U u) -> after.apply(apply(t, u));
}
}

使用Bifurcation的demo

public int add(Integer a, Integer b, BiFunction<Integer,Integer,Integer> biFunction){
return biFunction.apply(a,b);
}
public int compute3(Integer a, Integer b,
BiFunction<Integer,Integer,Integer> biFunction,
Function<Integer,Integer> function){
return biFunction.andThen(function).apply(a,b);
} System.out.println(functionText.add(1,2,( a , b ) -> a + b));
System.out.println(functionText.compute3(3,4,(c,d)->c*d,i->i+1));

Predicate

用于动态的判断传递给他的类型是否相等

学习过上面那几个函数式接口,再看它,应该是很容易蒙出怎么玩了

  • 套路: 到现在看,他和上面几个函数式接口的套路还是大同小异的, java8针对不同的使用情景设计出不同的函数式接口,Predicate意味,断定,判相等
  • 下面的三个默认方法,入参全部是Predicate类型的形参,目的是和当前对象的text()结合形成多重判断
  • 最后一个static静态方法,使用的是静态方法的引用
  • 关于我们: 我们能做的依旧是写出lambda表达式,作为text的真正的业务逻辑
@FunctionalInterface
public interface Predicate<T> { boolean test(T t); default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
} default Predicate<T> negate() {
return (t) -> !test(t);
} default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
} static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}

不接受参数,但是返回一个值


方法引用

方法引用其实是lambda的语法糖,当我们的lambda表达式只有一行并且恰好有一已经存在的方法作用跟他相同,我们就可以用方法引用替换lambda表达式,让代码的风格更好看

四类方法引用

一方面编译器会提示如何使用方法引用,另一方面,我们自己要根据方法的类型知道如何引用

  • 类名::静态方法名
  • 引用名(对象名)::实例方法名
  • 类名::实例方法名

lambda表示的第一个参数是作为方法的调用者传递进去的

  • 类名::new --- 构造方法引用

编译可以很智能的推断出,你在使用哪个构造方法

public class text1 {

public String getString1(String str, Function<String,String> function){
return function.apply(str);
}
public String getString2(String str, Supplier<String> function){
return function.get();
} public static void main(String[] args) { text1 text1 = new text1();
String haha = text1.getString1("haha", String::new);
System.out.println(haha); String hehe = text1.getString2("hehe", String::new);
System.out.println(hehe);
}

通过类名去找到对象的实例方法

函数式接口 & lambda表达式 & 方法引用的更多相关文章

  1. 黑马Lambda表达式学习 Stream流 函数式接口 Lambda表达式 方法引用

  2. 黑马方法引用学习 Stream流 函数式接口 Lambda表达式 方法引用

  3. 黑马函数式接口学习 Stream流 函数式接口 Lambda表达式 方法引用

  4. 黑马Stream流学习 Stream流 函数式接口 Lambda表达式 方法引用

  5. 如何优雅的将文件转换为字符串(环绕执行模式&行为参数化&函数式接口|Lambda表达式)

    首先我们讲几个概念: 环绕执行模式: 简单的讲,就是对于OI,JDBC等类似资源,在用完之后需要关闭的,资源处理时常见的一个模式是打开一个资源,做一些处理,然后关闭资源,这个设置和清理阶段类似,并且会 ...

  6. java8之lambda表达式&方法引用(一)

    本文将简单的介绍一下Lambda表达式和方法引用,这也是Java8的重要更新,Lambda表达式和方法引用最主要的功能是为流(专门负责迭代数据的集合)服务. 什么是lambda表达式 可以把lambd ...

  7. Java8函数式接口/Lambda表达式/接口默认方法/接口静态方法/接口冲突方法重写/lambda表达式指定泛型类型等

    一:函数式接口 1.函数式接口的概念就是此接口必须有且只能有一个抽象方法,可以通过@FunctionalInterface来显示规定(类似@Override),但是没有此注解的但是只有一个抽象方法的接 ...

  8. 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)

    作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-language- ...

  9. [转]深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)

    以下内容转自: 作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-l ...

随机推荐

  1. webpack学习_资源管理(loader)

    webpack 最出色的功能之一就是,除了 JavaScript,还可以通过 loader 引入任何其他类型的文件 引入资源步骤 Step1:安装你需要的loader  Step2:在 module配 ...

  2. 在MySQL中group by 是什么意思

    mysql语法中group by是什么意思? 在百度中搜索半天,最后找到一篇解释比较好的(不是博文,是百度知道,很郁闷那么多网友怎么就没人解释的清楚),链接如下: http://zhidao.baid ...

  3. 基于C# WPF框架的贪吃蛇

    游戏开始界面 游戏开始 共有两条蛇,吃到红色食物加1分,吃到绿色毒食物减1分,知道0不减: 碰到墙壁游戏结束,碰到对方游戏结束,碰到自己游戏结束 此游戏通过Canvas画布布局,通过C#代码实现 游戏 ...

  4. ORACLE spool打印

    问题描述:spool让我想起来了spooling假脱机,但是这个spool是oracle下的命令,将select查询出来的数据打印出来 1.linuxi下 spool +路径+文件名,这里的文件如果不 ...

  5. Springboot 错误处理机制

    SpringBoot默认的错误处理机制 即我们常见的白色的ErrorPage页面 浏览器发送的请求头: 如果是其他的请求方式,比如客户端,则相应一个json数据: 原理:是通过 ErrorMvcAut ...

  6. 加快C++代码的编译速度方法【转载】

    C++代码一直以其运行时的高性能高调面对世人, 但是说起编译速度,却只有低调的份了.比如我现在工作的源代码,哪怕使用Incredibuild调动近百台机子,一个完整的build也需要四个小时,恐怖!! ...

  7. 每天进步一点点----JS之比较运算符易错点

    1.字符串的比较 字符串也是可以比较的,字符串比较的asc码顺序:asc有128位,由7位二进制数表示,每个数对应的是一个字符.ASC码有ASC码1,由7位二进制1数表示:ASC2码又8位二进制数表示 ...

  8. 一起学Spring之三种注入方式及集合类型注入

    本文主要讲解Spring开发中三种不同的注入方式,以及集合数据类型的注入,仅供学习分享使用,如有不足之处,还请指正. 概述 Spring的注入方式一共有三种,如下所示: 通过set属性进行注入,即通过 ...

  9. 解决方案:从网站下载Excel,我的Office 2016,打开excel文件,显示空白

    今天在写Excel导出案例demo的时候发现,Excel下载后打开空白,要打开了看到空白后再点击一次打开后才可以显示,效果就如下图所示: 那么我就不能一次打开吗?我找了半天最终在这个博客找到了答案:h ...

  10. How to: Supply Initial Data for the Entity Framework Data Model 如何:为EF数据模型提供初始数据

    After you have introduced a data model, you may need to have the application populate the database w ...