Java8深入学习系列(一)lambda表达式介绍
前言
最近在学习java8,所以接下来会给大家介绍一系列的java8学习内容,那么让我们先从lambda表达式开始。
众所周知从java8出现以来lambda是最重要的特性之一,它可以让我们用简洁流畅的代码完成一个功能。 很长一段时间java被吐槽是冗余和缺乏函数式编程能力的语言,随着函数式编程的流行java8种也引入了 这种编程风格。在此之前我们都在写匿名内部类干这些事,但有时候这不是好的做法,本文中将介绍和使用lambda, 带你体验函数式编程的魔力。
什么是lambda?
lambda表达式是一段可以传递的代码,它的核心思想是将面向对象中的传递数据变成传递行为。 我们回顾一下在使用java8之前要做的事,之前我们编写一个线程时是这样的:
runnable r = new runnable() { @override public void run() { system.out.println("do something."); } }
也有人会写一个类去实现runnable接口,这样做没有问题,我们注意这个接口中只有一个run方法, 当把runnable对象给thread对象作为构造参数时创建一个线程,运行后将输出do something.
。 我们使用匿名内部类的方式实现了该方法。
这实际上是一个代码即数据的例子,在run方法中是线程要执行的一个任务,但上面的代码中任务内容已经被规定死了。 当我们有多个不同的任务时,需要重复编写如上代码。
设计匿名内部类的目的,就是为了方便 java 程序员将代码作为数据传递。不过,匿名内部 类还是不够简便。 为了执行一个简单的任务逻辑,不得不加上 6 行冗繁的样板代码。那如果是lambda该怎么做?
runnable r = () -> system.out.println("do something.");
嗯,这代码看起来很酷,你可以看到我们用()和->的方式完成了这件事,这是一个没有名字的函数,也没有人和参数,再简单不过了。 使用->将参数和实现逻辑分离,当运行这个线程的时候执行的是->之后的代码片段,且编译器帮助我们做了类型推导; 这个代码片段可以是用{}包含的一段逻辑。下面一起来学习一下lambda的语法。
基础语法
在lambda中我们遵循如下的表达式来编写:
expression = (variable) -> action
- variable: 这是一个变量,一个占位符。像x,y,z,可以是多个变量。
- action: 这里我称它为action, 这是我们实现的代码逻辑部分,它可以是一行代码也可以是一个代码片段
可以看到java中lambda表达式的格式:参数、箭头、以及动作实现,当一个动作实现无法用一行代码完成,可以编写 一段代码用{}包裹起来。
lambda表达式可以包含多个参数,例如:
int sum = (x, y) -> x + y;
这时候我们应该思考这段代码不是之前的x和y数字相加,而是创建了一个函数,用来计算两个操作数的和。 后面用int类型进行接收,在lambda中为我们省略去了return。
函数式接口
函数式接口是只有一个方法的接口,用作lambda表达式的类型。前面写的例子就是一个函数式接口,来看看jdk中的runnable源码
@functionalinterface public interface runnable { /** * when an object implementing interface <code>runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * the general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.thread#run() */ public abstract void run(); }
这里只有一个抽象方法run,实际上你不写public abstract
也是可以的,在接口中定义的方法都是public abstract的。 同时也使用注解@functionalinterface
告诉编译器这是一个函数式接口,当然你不这么写也可以,标识后明确了这个函数中 只有一个抽象方法,当你尝试在接口中编写多个方法的时候编译器将不允许这么干。
尝试函数式接口
我们来编写一个函数式接口,输入一个年龄,判断这个人是否是成人。
public class functioninterfacedemo { @functionalinterface interface predicate<t> { boolean test(t t); } /** * 执行predicate判断 * * @param age 年龄 * @param predicate predicate函数式接口 * @return 返回布尔类型结果 */ public static boolean dopredicate(int age, predicate<integer> predicate) { return predicate.test(age); } public static void main(string[] args) { boolean isadult = dopredicate(20, x -> x >= 18); system.out.println(isadult); } }
从这个例子我们很轻松的完成 是否是成人 的动作,其次判断是否是成人,在此之前我们的做法一般是编写一个 判断是否是成人的方法,是无法将 判断 共用的。而在本例只,你要做的是将 行为 (判断是否是成人,或者是判断是否大于30岁) 传递进去,函数式接口告诉你结果是什么。
实际上诸如上述例子中的接口,伟大的jdk设计者为我们准备了java.util.function包
我们前面写的predicate函数式接口也是jdk种的一个实现,他们大致分为以下几类:
接口 | 参数 | 返回值 | 类别 | 示例 |
---|---|---|---|---|
consumer |
t | void | 消费型接口 | 输出一个值 |
supplier |
none | t | 供给型接口 | 工厂方法 |
function | t | r | 函数型接口 | 获得 artist 对象的名字 |
predicate |
t | boolean | 断言型接口 | 这张唱片已经发行了吗 |
消费型接口示例
public static void donation(integer money, consumer<integer> consumer){ consumer.accept(money); } public static void main(string[] args) { donation(1000, money -> system.out.println("好心的麦乐迪为blade捐赠了"+money+"元")) ; }
供给型接口示例
public static list<integer> supply(integer num, supplier<integer> supplier){ list<integer> resultlist = new arraylist<integer>() ; for(int x=0;x<num;x++) resultlist.add(supplier.get()); return resultlist ; } public static void main(string[] args) { list<integer> list = supply(10,() -> (int)(math.random()*100)); list.foreach(system.out::println); }
函数型接口示例
转换字符串为integer
public static integer convert(string str, function<string, integer> function) { return function.apply(str); } public static void main(string[] args) { integer value = convert("28", x -> integer.parseint(x)); }
断言型接口示例
筛选出只有2个字的水果
public static list<string> filter(list<string> fruit, predicate<string> predicate){ list<string> f = new arraylist<>(); for (string s : fruit) { if(predicate.test(s)){ f.add(s); } } return f; } public static void main(string[] args) { list<string> fruit = arrays.aslist("香蕉", "哈密瓜", "榴莲", "火龙果", "水蜜桃"); list<string> newfruit = filter(fruit, (f) -> f.length() == 2); system.out.println(newfruit); }
默认方法
在java语言中,一个接口中定义的方法必须由实现类提供实现。但是当接口中加入新的api时, 实现类按照约定也要修改实现,而java8的api对现有接口也添加了很多方法,比如list接口中添加了sort方法。 如果按照之前的做法,那么所有的实现类都要实现sort方法,jdk的编写者们一定非常抓狂。
幸运的是我们使用了java8,这一问题将得到很好的解决,在java8种引入新的机制,支持在接口中声明方法同时提供实现。 这令人激动不已,你有两种方式完成 1.在接口内声明静态方法 2.指定一个默认方法。
我们来看看在jdk8中上述list接口添加方法的问题是如何解决的
default void sort(comparator<? super e> c) { object[] a = this.toarray(); arrays.sort(a, (comparator) c); listiterator<e> i = this.listiterator(); for (object e : a) { i.next(); i.set((e) e); } }
翻阅list接口的源码,其中加入一个默认方法default void sort(comparator<? super e> c)
。 在返回值之前加入default关键字,有了这个方法我们可以直接调用sort方法进行排序。
list<integer> list = arrays.aslist(2, 7, 3, 1, 8, 6, 4); list.sort(comparator.naturalorder()); system.out.println(list);
comparator.naturalorder()
是一个自然排序的实现,这里可以自定义排序方案。 你经常看到使用java8操作集合的时候可以直接foreach的原因也是在iterable接口中也新增了一个默认方法:foreach , 该方法功能和 for 循环类似,但是允许 用户使用一个lambda表达式作为循环体。
在后面的章节中我们再次通过案例来展示函数式编程的魅力 :)
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。