怒学Java8系列一:Lambda表达式
PDF文档已上传Github
Github:https://github.com/zwjlpeng/Angrily_Learn_Java_8
第一章 Lambda
1.1 引言
课本上说编程有两种模式,面向过程的编程以及面向对象的编程,其实在面向对象编程之前还出现了面向函数的编程(函数式编程) ,以前一直被忽略、不被重视,现在从学术界已经走向了商业界,对函数编程语言的支持目前有Scala、Erlang、F#、Python、Php、Java、Javascript等,有人说他将会是编程语言中的下一个主流...
1.2 Lambda表达式
为什么需要Lambda表达式?
1.使用Lambda表达式可以使代码变的更加紧凑,例如在Java中实现一个线程,只输出一个字符串Hello World!,我们的代码如下所示:
public static void main(String[] args) throws Exception {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello World!");
}
}).start();
TimeUnit.SECONDS.sleep(1000);
}
使用Lambda表达式之后代码变成如下形式:
public static void main(String[] args) throws Exception {
new Thread(() -> System.out.println("Hello World!")).start();
TimeUnit.SECONDS.sleep(1000);
}
是不是代码变的更紧凑了~,其他的例如各种监听器,以及事件处理器等都可以用这种方式进行简化。
2.修改方法的能力,其实说白了,就是函数中可以接受以函数为单元的参数,在C/C++中就是函数指针,在Java中就是Lambda表达式,例如在Java中使用集合类对一个字符串按字典序列进行排序,代码如下所示:
public static void main(String[] args) {
String []datas = new String[] {"peng","zhao","li"};
Arrays.sort(datas);
Stream.of(datas).forEach(param -> System.out.println(param));
}
在上面代码中用了Arrays里的sort方法,现在我们不需要按字典排序,而是按字符串的长度进行排序,代码如下所示:
public static void main(String[] args) {
String []datas = new String[] {"peng","zhao","li"};
Arrays.sort(datas,(v1 , v2) -> Integer.compare(v1.length(), v2.length()));
Stream.of(datas).forEach(param -> System.out.println(param));
}
是不是很方便,我们不需要实现Comparable接口,使用一个Lambda表达式就可以改变一个函数的形为~
1.3 Syntax
1.Lambda表达式的形式化表示如下所示
Parameters -> an expression
2.如果Lambda表达式中要执行多个语句块,需要将多个语句块以{}进行包装,如果有返回值,需要显示指定return语句,如下所示:
Parameters -> {expressions;};
3.如果Lambda表达式不需要参数,可以使用一个空括号表示,如下示例所示
() -> {for (int i = 0; i < 1000; i++) doSomething();};
4.Java是一个强类型的语言,因此参数必须要有类型,如果编译器能够推测出Lambda表达式的参数类型,则不需要我们显示的进行指定,如下所示,在Java中推测Lambda表达式的参数类型与推测泛型类型的方法基本类似,至于Java是如何处理泛型的,此处略去
String []datas = new String[] {"peng","zhao","li"};
Arrays.sort(datas,(String v1, String v2) -> Integer.compare(v1.length(), v2.length()));
上述代码中 显示指定了参数类型Stirng,其实不指定,如下代码所示,也是可以的,因为编译器会根据Lambda表达式对应的函数式接口Comparator<String>进行自动推断
String []datas = new String[] {"peng","zhao","li"};;
Arrays.sort(datas,(v1, v2) -> Integer.compare(v1.length(), v2.length()));
5.如果Lambda表达式只有一个参数,并且参数的类型是可以由编译器推断出来的,则可以如下所示使用Lambda表达式,即可以省略参数的类型及括号
Stream.of(datas).forEach(param -> {System.out.println(param.length());});
6.Lambda表达式的返回类型,无需指定,编译器会自行推断,说是自行推断
7.Lambda表达式的参数可以使用修饰符及注解,如final、@NonNull等
1.4函数式接口
函数式接口是Java 8为支持Lambda表达式新发明的,在上面讲述的Lambda Syntax时提到的sort排序方法就是一个样例,在这个排序方法中就使用了一个函数式接口,函数的原型声明如下所示
public static <T> void sort(T[] a, Comparator<? super T> c)
上面代码中Comparator<? Super T>就是一个函数式接口,? Super T or ? entends T从Java 5支持泛型时开始引入,得理解清楚,在此忽略讲述
什么是函数式接口
1.函数式接口具有两个主要特征,是一个接口,这个接口具有唯一的一个抽像方法,我们将满足这两个特性的接口称为函数式接口,说到这,就不得不说一下接口中是有具体实现这个问题啦~
2.Lambda表达式不能脱离目标类型存在,这个目录类型就是函数式接口,所下所示是一个样例
String []datas = new String[] {"peng","zhao","li"};
Comparator<String> comp = (v1,v2) -> Integer.compare(v1.length(), v2.length());
Arrays.sort(datas,comp);
Stream.of(datas).forEach(param -> {System.out.println(param);});
Lambda表达式被赋值给了comp函数接口变量
3.函数式接口可以使用@FunctionalInterface进行标注,使用这个标注后,主要有两个优势,编译器知道这是一个函数式接口,符合函数式的要求,另一个就是生成Java Doc时会进行显式标注
4.异常,如果Lambda表达式会抛出非运行时异常,则函数式接口也需要抛出异常,说白了,还是一句话,函数式接口是Lambda表达式的目标类型
5.函数式接口中可以定义public static方法,想想在Java中我们提供了Collection接口,同时还提供了一个Collections工具类等等,在Java中将这种Collections的实现转移到了接口里面,但是为了保证向后兼容性,以前的这种Collection/Collections等逻辑均未改变
6.函数式接口可以提供多个抽像方法,纳尼!上面不是说只能有一个嘛?是的,在函数式接口中可以提供多个抽像方法,但这些抽像方法限制了范围,只能是Object类型里的已有方法,为什么要这样做呢?此处忽略,大家可以自已研究
7.函数式接口里面可以定义方法的默认实现,如下所示是Predicate类的代码,不仅可以提供一个default实现,而且可以提供多个default实现呢,Java 8以前可以嘛?我和我的小伙伴们都惊呆了,这也就导致出现了多继承下的问题,想知道Java 8是如何对其进行处理的嘛,其实很Easy,后面我会再讲~
8.为什么要提供default接口的实现?如下就是一个默认实现
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
Java 8中在接口中增加了默认实现这种函数,其实在很大程序上违背了接口具有抽象这种特征的,增加default实现主要原因是因为考虑兼容及代码的更改成本,例如,在Java 8中向iterator这种接口增加一个方法,那么实现这个接口的所有类都要需实现一遍这个方法,那么Java 8需要更改的类就太多的,因此在Iterator接口里增加一个default实现,那么实现这个接口的所有类就都具有了这种实现,说白了,就是一个模板设计模式吧
1.5方法引用
有时,我们需要执行的代码在某些类中已经存在,这时我们没必要再去写Lambda表达式,可以直接使用该方法,这种情况我们称之为方法引用,如下所示,未采用方法引用前的代码
如下所示
Stream.of(datas).forEach(param -> {System.out.println(param);});
使用方法引用后的代码如下所示
Stream.of(datas).forEach(System.out::println);
以上示例使用的是out对象,下面示例使用的是类的静态方法引用对字符串数组里的元素忽略大小写进行排序
String []datas = new String[] {"peng","Zhao","li"};
Arrays.sort(datas,String::compareToIgnoreCase);
Stream.of(datas).forEach(System.out::println);
上面就是方法引用的一些典型示例
方法引用的具体分类
Object:instanceMethod
Class:staticMethod
Class:instanceMethod
上面分类中前两种在Lambda表达式的意义上等同,都是将参数传递给方法,如上示例
System.out::println == x -> System.out.println(x)
最后一种分类,第一个参数是方法执行的目标,如下示例
String::compareToIgnoreCase == (x,y) -> x.compareToIgnoreCase(y)
还有类似于super::instanceMethod这种方法引用本质上与Object::instanceMethod类似
1.6构造方法引用
构造方法引用与方法引用类似,除了一点,就是构造方法引用的方法是new!以下是两个示例
示例一:
String str = "test";
Stream.of(str).map(String::new).peek(System.out::println).findFirst();
示例二:
String []copyDatas = Stream.of(datas).toArray(String[]::new);
Stream.of(copyDatas).forEach(x -> System.out.println(x));
总结一下,构造方法引用有两种形式
Class::new
Class[]::new
1.7 Lambda表达式作用域
总体来说,Lambda表达式的变量作用域与内部类非常相似,只是条件相对来说,放宽了些以前内部类要想引用外部类的变量,必须像下面这样
final String[] datas = new String[] { "peng", "Zhao", "li" };
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(datas);
}
}).start();
将变量声明为final类型的,现在在Java 8中可以这样写代码
String []datas = new String[] {"peng","Zhao","li"};
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(datas);
}
}).start();
也可以这样写
new Thread(() -> System.out.println(datas)).start();
总之你爱怎么写,就怎么写吧,I don’t Care it!
看了上面的两段代码,能够发现一个显著的不同,就是Java 8中内部类或者Lambda表达式对外部类变量的引用条件放松了,不要求强制的加上final关键字了,但是Java 8中要求这个变量是effectively final
What is effectively final?
Effectively final就是有效只读变量,意思是这个变量可以不加final关键字,但是这个变量必须是只读变量,即一旦定义后,在后面就不能再随意修改,如下代码会编译出错
String []datas = new String[] {"peng","Zhao","li"};
datas = null;
new Thread(() -> System.out.println(datas)).start();
Java中内部类以及Lambda表达式中也不允许修改外部类中的变量,这是为了避免多线程情况下的race condition
Lambda中变量以及this关键字
Lambda中定义的变量与外部类中的变量作用域相同,即外部类中定义了,Lambda就不能再重复定义了,同时在Lambda表达式使用的this关键字,指向的是外部类,大家可以自行实践下,此处略
未完待写....
怒学Java8系列一:Lambda表达式的更多相关文章
- Java8系列 (一) Lambda表达式
函数式编程 在介绍Lambda表达式之前, 首先需要引入另一个概念, 函数式编程. 函数式编程是一种编程范式, 也就是如何编写程序的方法论.它的核心思想是将运算过程尽量写成一系列嵌套的函数调用,关注的 ...
- Java8学习笔记----Lambda表达式 (转)
Java8学习笔记----Lambda表达式 天锦 2014-03-24 16:43:30 发表于:ATA之家 本文主要记录自己学习Java8的历程,方便大家一起探讨和自己的备忘.因为本人 ...
- Java8新特性-Lambda表达式是什么?
目录 前言 匿名内部类 函数式接口 和 Lambda表达式语法 实现函数式接口并使用Lambda表达式: 所以Lambda表达式是什么? 实战应用 总结 前言 Java8新特性-Lambda表达式,好 ...
- 乐字节-Java8新特性-Lambda表达式
上一篇文章我们了解了Java8新特性-接口默认方法,接下来我们聊一聊Java8新特性之Lambda表达式. Lambda表达式(也称为闭包),它允许我们将函数当成参数传递给某个方法,或者把代码本身当作 ...
- Java8新特性 - Lambda表达式 - 基本知识
A lambda expression is an unnamed block of code (or an unnamed function) with a list of formal param ...
- JAVA8学习——深入浅出Lambda表达式(学习过程)
JAVA8学习--深入浅出Lambda表达式(学习过程) lambda表达式: 我们为什么要用lambda表达式 在JAVA中,我们无法将函数作为参数传递给一个方法,也无法声明返回一个函数的方法. 在 ...
- java8新特性——Lambda表达式
上文中简单介绍了一下java8得一些新特性,与优点,也是为本次学习java8新特性制定一个学习的方向,后面几篇会根据上文中得新特性一一展开学习.本文就从java8新特性中比较重要的Lambda表达式开 ...
- java8学习之Lambda表达式深入与流初步
Lambda表达式深入: 在上一次[http://www.cnblogs.com/webor2006/p/8135873.html]中介绍Lambda表达式的作用时,其中说到这点: 如标红处所说,既然 ...
- java8学习之Lambda表达式初步与函数式接口
对于Java8其实相比之前的的版本增加的内容是相当多的,其中有相当一大块的内容是关于Lambda表达式与Stream API,而这两部分是紧密结合而不能将其拆开来对待的,但是是可以单独使用的,所以从学 ...
随机推荐
- 岭回归(Ridge Regression)
一.一般线性回归遇到的问题 在处理复杂的数据的回归问题时,普通的线性回归会遇到一些问题,主要表现在: 预测精度:这里要处理好这样一对为题,即样本的数量和特征的数量 时,最小二乘回归会有较小的方差 时, ...
- JavaScript实现功能全集
JavaScript就这么回事1:基础知识 1 创建脚本块 <script language="JavaScript">JavaScript code goes her ...
- 710 Random Pick with Blacklist
1. 问题 给定一个黑名单,包含[0, N)的一些数,从[0, N)之间的非黑名单数中随机采样一个值. 2. 思路 字典映射 (1)计算黑名单数的长度,记作B,因为已经排除掉了B个元素,所以最后是从N ...
- Python: ValueError: too many values to unpack
eg1: >>>a,b=(1,2,3) Traceback (most recent call last): File "<stdin>",line ...
- MyBatis学习笔记(六)——调用存储过程
转自孤傲苍狼的博客:http://www.cnblogs.com/xdp-gacl/p/4270352.html 一.提出需求 查询得到男性或女性的数量, 如果传入的是0就女性否则是男性 二.准备数据 ...
- html5设置全屏模式--开发游戏必备
<!-- uc强制竖屏 --> <meta name="screen-orientation" content="portrait"> ...
- linux基础命令---tmpwatch
tmpwatch 删除最近一段时间没有访问的文件,时间以小时为单位,节省磁盘空间.tmpwatch递归删除给定时间未被访问的文件.通常,它用于清理用于临时保存空间(如/tmp)的目录.当更改目录时,t ...
- [转载]onclientclick和onclick区别
OnClientClick是客户端脚本,一般使用javascript,在客户端,也就是IE中运行,点击后马上执行OnClick是服务器端事件处理函数,使用C#或者vb.net,在服务器端,也就是IIS ...
- django 加载静态文件(图片,js,css)
昨天写过一个项目通过django上传展示图片,但是今天写项目的时候发现出现了问题,静态文件加载不出来了,尴尬的一笔~ 记录一下静态文件的使用方法,基础~ ----------------------- ...
- Android Socket 知识点汇总
版权声明:这篇博客资料来源 https://blog.csdn.net/carson_ho/article/details/53366856 , 未经授权,严禁转发! 一.网络基础 1.1 计算机网络 ...