java8新特性之lambda表达式

  • 1.什么是lambda表达式?为什么用它?
  • 2. 为什么Java需要lambda表达式?
  • 3. lambda表达式的语法
  • 4.函数式接口
    • 4.1 什么是函数式接口
    • 4.2 自定义函数式接口
    • 4.3 内置函数式接口
  • 5. 方法引用
    • 5.1. 对象::实例方法名
    • 5.2. 类::静态方法名
    • 5.3. 类::实例方法名
    • 5.4. 类::new
    • 5.5 数组引用
  • 6 lambda表达式的作用域
    • 6.1 访问局部变量
    • 6.2 访问局部引用,静态变量,实例变量
    • 6.3 Lambda表达式访问局部变量做限制的原因。

1.什么是lambda表达式?为什么用它?

  lambda表达式又称闭包。它是推动java8发布的最重要新特性。lambda允许把函数作为方法的参数(函数作为参数传递进方法中)。使用lambda可以使代码更加简洁。
例如:我们要创建一个线程输出一句话
不用lambda:
我们需要写一个类实现Runnable接口,然后实现他的run方法

//实现Runnable接口的类
public class RunTemp implements Runnable{@Overridepublic void run() {System.out.println("我是实现的Runnable方法");}
}//使用
RunTemp runTemp = new RunTemp();
new Thread(runTemp).start();

结果:

使用lambda:

new Thread(()-> System.out.println("lambdaYYDS")).start();

结果:
一对比我们就可以看出,lambda为我们节省了好多代码。但实现的功能却是相同的。

2. 为什么Java需要lambda表达式?

  lambda表达式为java提供了缺失的函数式编程特点。使我们能将函数当作一等公民来看待。在一个支持函数的语言中,lambda表达式的类型应该是函数,但是在Java中它是一种对象,它必须依附于一种特殊的对象类型:函数式接口(functional interface)

3. lambda表达式的语法

  lambda表达式在Java语言中引入了一个新的操作符“->”,该操作符被称为lambda操作符或者箭头操作符。它将lambda表达式分为了两部分:
左侧:lambda所需参数
右侧:lambda要执行的操作。

(type param1,type param2,...) -> {body}
(param1,param2,...) -> {body}
//例如:
(String param1,Interge param2,...) -> {return param1+param2;}

以下是lambda表达式的重要特性:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但无参数或者多参数需要定义圆括号
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指明表达式返回了一个数值。

举例:

  1. 无参,无返回值,lambda体只有一句话:
public class Test1 {public static void main(String[] args) {Runnable runnable = ()->{ System.out.println("无参,无返回值,一句话"); };runnable.run();}
}
//一语句可以省略大括号:
public static void main(String[] args) {Runnable runnable = ()-> System.out.println("无参,无返回值,一句话。可以省略大括号");runnable.run();}
  1. 一参,无返回值,lambda体只有一句话:
public static void main(String[] args) {Consumer<String> consumer = (t)->{System.out.println("一参:"+t+",无返回值,一句话"); };consumer.accept("我是参数");
}
//一参可以省略小括号:
public static void main(String[] args) {Consumer<String> consumer = t->{ System.out.println("一参:"+t+",无返回值,一句话。一参可以省略小括号"); };consumer.accept("我是参数");
}
  1. 两个参数,一条语句,有返回值
public static void main(String[] args) {Comparator<Integer> comparable = (a, b)->{return Integer.compare(a,b);};System.out.println(comparable.compare(3,4));
}
/**
* 一条语句有返回值时,return和大括号都可不写
**/
public static void main(String[] args) {Comparator<Integer> comparable = (a, b)-> Integer.compare(a,b);System.out.println(comparable.compare(3,4));
}
  1. 两个参数,多条语句,有返回值
public static void main(String[] args) {Comparator<Integer> comparable = (a, b)->{System.out.println("我是另一条语句");return Integer.compare(a,b);};System.out.println(comparable.compare(3,4));
}

4.函数式接口

4.1 什么是函数式接口

  只包含一个抽象方法的接口就是函数式接口。我们可以通过lambda表达式来创建该接口的实现对象。我们可以在任意函数式接口上使用@Functionallnterface注解,这样做可以用于检测它是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。

4.2 自定义函数式接口

/*** 自定义函数式接口* @author wangdawei*/
@FunctionalInterface
public interface WorkerInterface {/*** 一个抽象方法*/public void doSomeWork();
}//使用
public class Test {public static void main(String[] args) {Test test = new Test();test.show(()-> System.out.println("我是最简单的lambda表达式"),"你号");}//自定义一个方法,将函数式接口作为参数private void show(WorkerInterface worker,String str) {System.out.println(str);worker.doSomeWork();}
}

4.3 内置函数式接口

四大内置函数式接口:

使用示例:

1 Consumer:
 //使用public class Test {public static void main(String[] args) {Test test = new Test();test.markMoney(10,(x)-> System.out.println("今天花费了"+x+"元钱"));}/*** Consumer<T>*/private void markMoney(Integer money, Consumer<Integer> consumer){consumer.accept(money);}}
//结果:今天花费了10元钱
2 Supplier:
public class Test {public static void main(String[] args) {Test test = new Test();List<Integer> list = test.addNumInList(10,()->(int)(Math.random()*100));list.forEach(t-> System.out.print(t+","));}/*** Supplier<T>*/private List<Integer> addNumInList(int size, Supplier<Integer> supplier){List<Integer> list=new ArrayList<>();for (int i = 0; i < size; i++) {list.add(supplier.get());}return list;}}//结果:92,40,77,48,95,86,40,51,52,27,
3 Function<T,R>:
public class Test {public static void main(String[] args) {Test test = new Test();Function<Integer,String > function = (b)->{System.out.println("Function");int data = b*b+b;return "b*b+b的结果是:"+data;};function.apply(11);}
}
//结果:
//Function
//b*b+b的结果是:132
4 Predicate<T>:
//判断数字大小
public class Test {public static void main(String[] args) {int data = 11;Predicate<Integer> predicate = (a)->{if (a>10){return true;}return false;};if (predicate.test(data)){System.out.println("data大于10");}else{System.out.println("data小于等于10");}}
}
//结果:data大于10

其它接口:

5. 方法引用

  当要传递给Lambda体的操作,已经有实现的方法了,就可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用的参数列表一致,方法的返回值也必须一致,即方法的签名一致)。可以理解为方法引用是Lambda表达式的另外一种表现形式。
语法:使用操作符"::"将对象或类与方法名分隔开。
使用方法:

  • 对象::实例方法名
  • 类::静态方法名
  • 类::实例方法名
  • 类::new
  • type[]::new

5.1. 对象::实例方法名

public class Test {public static void main(String[] args) {PrintStream stream = System.out;Consumer<String> consumer = stream::println;consumer.accept("对象::实例方法名");}
}
//结果:对象::实例方法名

5.2. 类::静态方法名

public class Test {public static void main(String[] args) {PrintStream stream = System.out;Consumer<String> consumer = Test::show;consumer.accept("类::静态方法名");}/*** 方法引用*/static void show(String s){System.out.println(s);}
}
//结果:类::静态方法名

5.3. 类::实例方法名

public class Test {public static void main(String[] args) {BiPredicate<String,String> biPredicate = String::equals;biPredicate.test("t","t");}
}

注意:此方式有要求。

   第一点:接口方法的参数比引用方法的参数多一个第二点:接口方法的第一个参数恰巧是调用引用方法的对象(其引用方法所在类或其父类的实例)

以上面的例子为例:

BiPredicate<String,String> biPredicate = String::equals;
//首先,BigPredicate接口中的test方法规定要传两个参数:第一个参数规定第一个参数必须是String类型的实例,第二个参数是String类型参数。
biPredicate.test("t","t");
//第一个参数”t“是String类型的实例,流程可以理解为:"第一个参数".(test/equals)(第二个参数)
//这也是为什么第一个参数必须是String类型的实例的原因。如果不是那就无法调用.equals方法。

例二:

public class Test {public static void main(String[] args) {BiPredicate<MyFinalClass,String> biPredicate = MyFinalClass::showString;biPredicate.test(new MyFinalClass(),"例二结果输出");}
}public class MyFinalClass{public Boolean showString(Object s){System.out.println("给你展示:"+s);return true;}
}
//BiPredicate<MyFinalClass,String> 规定:第一个参数必须是“MyFinalClass”类型的实例。第二个参数是String类型的实例。

5.4. 类::new

public class Test {public static void main(String[] args) {//无参Supplier<Entity> supplier = Entity::new;supplier.get();//一参Function<String,Entity> function = Entity::new;System.out.println(function.apply("王大伟").getName());//两参BiFunction<String,String,Entity> biPredicate = Entity::new;Entity entity = biPredicate.apply("1234232","王大伟");System.out.println("id:"+entity.getId()+";name:"+entity.getName());//三参NewEntity newEntity = Entity::new;Entity entity1 = newEntity.newEntity("372929","王大伟",20);System.out.println("id:"+entity.getId()+";name:"+entity.getName()+";age:"+entity1.getAge());}
}/**
*   自定义一个函数式接口
**/
@FunctionalInterface
public interface NewEntity {/*** 三个参数的初始化* @param id* @param name* @param age* @return*/public Entity newEntity(String id,String name,Integer age);
}public class Entity {private String id;private String name;private Integer age;public Entity() {}public Entity(String name) {this.name = name;}public Entity(String id, String name, Integer age) {this.id = id;this.name = name;this.age = age;}public Entity(String id, String name) {this.id = id;this.name = name;}public String getId() {return id;}public void setId(String id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}
}

5.5 数组引用

数组引用格式:type[]:new
示例:

  @Testpublic void test02(){Function<Integer,String[]> function=String[]::new;String[] apply = function.apply(10);System.out.println(apply.length);//结果:10}

6 lambda表达式的作用域

  Lambda表达式可以看作式匿名内部类实例化的对象,Lambda表达式对变量的访问限制和匿名内部类一样,因此Lambda表达式可以访问局部变量,局部引用,静态变量,实例变量。

6.1 访问局部变量

  在Lambda表达式中规定只能引用标记了final的外层局部变量。我们不能在lambda内部修改定义在域外的局部变量,否则会编译错误。
例如:

public class Test {public static void main(String[] args) {//声明局部变量int t = 0;WorkerInterface workerInterface = (a,b)->{System.out.println("a:"+a+"b:"+b);//修改局部变量t = t+1;System.out.println("t:"+t);};workerInterface.doSomeWork(3,4);}
}
/*** 自定义函数式接口* @author wangdawei*/
@FunctionalInterface
public interface WorkerInterface {/*** 一个抽象方法*/public void doSomeWork(int a,int b);
}


特殊情况下,局部变量也可以不用声明为final,但是必须保证它不可被后面的代码修改,(即隐形的具有final语义)
例:以上面代码举例:

public class Test {public static void main(String[] args) {//声明局部变量//final int t = 0;int t = 0;WorkerInterface workerInterface = (a,b)->{System.out.println("a:"+a+"b:"+b);//修改局部变量int c = t+1;System.out.println("c:"+c);};workerInterface.doSomeWork(3,4);System.out.println("我这里可不敢改被lambda用过的局部变量");}
}

上面代码就可以成功了。

6.2 访问局部引用,静态变量,实例变量

  Lambda表达式不限制访问局部引用变量,静态变量,实例变量。
例如:

//访问局部引用变量。
public class Test {public static void main(String[] args) {int t = 0;//创建一个局部引用变量List<String> list = new ArrayList<>();list.add("增加了一个数据。");WorkerInterface workerInterface = (a,b)->{//获取了一个局部引用变量,System.out.println(list.get(0));//我往里面加一个数据list.add("我往里面加一个数据");};workerInterface.doSomeWork(3,4);list.add("我再往里面加一个数据,也不会报错");}
}//访问静态变量
public class Test {static String staticStr = "静态变量";public static void main(String[] args) {WorkerInterface staticWorker = (a,b)->{System.out.println("看好了,我要改静态变量:"+staticStr);staticStr = staticStr+";;我改了,看见了吗?";System.out.println(staticStr);};staticWorker.doSomeWork(1,1);}
}
//结果:看好了,我要改静态变量:静态变量
//静态变量;;我改了,看见了吗?//访问实例变量
public class Test {static String staticStr = "静态变量";String instanceStr = "实例变量";public static void main(String[] args) {Test test = new Test();WorkerInterface instanceWorker = (a,b)->{System.out.println("看我改实例变量:"+test.instanceStr);test.instanceStr = test.instanceStr+",我改了";System.out.println(test.instanceStr);};}
}
//结果:
//看我改实例变量:实例变量
//实例变量,我改了

6.3 Lambda表达式访问局部变量做限制的原因。

  因为实例变量存在堆中,而局部变量是在栈上分配,存在于虚拟机栈的局部变量表中,Lambda表达式(匿名类)有可能会在另一个线程中执行。如果在线程中要直接访问一个局部变量,可能线程执行时该局部变量已经被销毁了,而final类型的局部变量在Lambda表达式(匿名类)中其实是局部变量的拷贝。
  基于上述,对于引用类型的局部变量,因为Java是值传递,又因为引用类型的指向内容是保存在堆中,是线程共享的,因此Lambda表达式中可以修改引用类型的局部变量的内容,而不能修改该变量的引用。
  对于基本数据类型的变量,在Lambda表达式中只是获取到该变量的副本,且局部变量是线程私有的。因此无法知道其它线程对该变量的修改。如果该变量不做final修饰,会造成数据不同步的问题。
  但是实例变量,静态变量不做限制,因为他两个保存在堆中(Java8之后),而堆是线程共享的。在Lambda表达式内部是可以知道实例变量,静态变量的变化。

参考:
Lambda表达式超详细总结
【Java 8系列】Lambda 表达式,一看就废
Lambda表达式使用局部变量的限制
lambda表达式——类名::实例方法

java8新特性之lambda表达式--超级详细版本相关推荐

  1. 【java8新特性】——lambda表达式与函数式接口详解(一)

    一.简介 java8于2014年发布,相比于java7,java8新增了非常多的特性,如lambda表达式.函数式接口.方法引用.默认方法.新工具(编译工具).Stream API.Date Time ...

  2. java8新特性: lambda表达式:直接获得某个list/array/对象里面的字段集合

    java8新特性: lambda表达式:直接获得某个list/array/对象里面的字段集合 比如,我有一张表: entity Category.java service CategoryServic ...

  3. Java8 新特性:Lambda 表达式、方法和构造器引用、Stream API、新时间与日期API、注解

    Java8新特性:Lambda 表达式.方法和构造器引用.Stream API.新时间与日期API.注解 1.Java8新特性 1.1.主要的新特性: 1.2.编程风格 2.Lambda 表达式 2. ...

  4. java8新特性总结——lambda表达式

    最近看尚硅谷java8新特性视屏,总结一下学习知识. Lambda表达式:是一个匿名函数,我们可以把Lambda理解为一段可以传递的代码(将代码像数据一样传递),可以写出更简洁更灵活的代码.作为一种更 ...

  5. lambda 对象去重_最火的java8新特性:Lambda 表达式

    主要内容 1. Lambda 表达式 2. 函数式接口 3. 方法引用与构造器引用 4. Stream API 5. 其他新特性 Java 8新特性简介 速度更快 代码更少(增加了新的语法 Lambd ...

  6. java:java8新特性(Lambda 表达式、方法引用、构造器引用、数组引用、Stream API)

    速度更快 对 HashMap .ConcurrentHashMap低层的数据结构(数组+链表+二叉树) 低层的内存结构(将永久区更新为元空间,元空间使用的是物理内存) 代码更少(增加了新的语法 Lam ...

  7. 夯实Java基础(二十二)——Java8新特性之Lambda表达式

    1.前言 Java 8于14年发布到现在已经有5年时间了,经过时间的磨练,毫无疑问,Java 8是继Java 5(发布于2004年)之后的又一个非常最重要的版本.因为Java 8里面出现了非常多新的特 ...

  8. java8新特性之Lambda表达式入门

    一 什么是Lambda表达式 Lambda表达式:可以让你的代码更加的简洁.ambda无法单独出现,需要一个函数式接口来盛放,可以说lambda表达式方法体是函数式接口的实现,lambda实例化函数式 ...

  9. Java8新特性之- Lambda表达式和函数式接口

    Lambda表达式和函数式接口 1. 背景 Java是一门面向对象编程语言.面向对象编程语言和函数式编程语言中的基本元素(Basic Values)都可以动态封装程序行为:面向对象编程语言使用带有方法 ...

最新文章

  1. ACL 2020 | 腾讯AI Lab解读三大前沿方向及入选的20篇论文
  2. 网页怎么在图片上添加文字_想给图片添加文字,留白,添加小印章?用手机三步搞定...
  3. java.lang.Enum
  4. 正确处理 Azure OnStop 事件
  5. 解决2次查询User的问题(ThreadLocal)
  6. oracle 绑定变量模糊查询,求助-ACTIVE DG 异常shutdown
  7. 【Android 修炼手册】常用技术篇 -- 聊聊 Android 的打包
  8. superhot预告片下载_预告片:裸指关节SOA
  9. sql注入查找注入点_基本的EJB参考,注入和查找
  10. go语言 函数相关1:实参到形参的传递永远是值拷贝
  11. 清除error.log、access.log并限制Apache日志文件大小的方法
  12. 写笔记插件_Java程序员笔记(知识)管理的一点经验
  13. HTML - 文本及其格式化
  14. Subversion代码提交中的org.apache.subversion.javahl.ClientException: svn: E200007: Commit failed异常解决...
  15. js去空格的其他方法
  16. 仅需一行代码,小白也可以制作自己的专属二维码!
  17. 电脑如何进入bios
  18. 2021爱智先行者—精灵1号的体验分享
  19. 蓝牙Ibeacon室内定位和微信摇一摇周边原理分析
  20. 手把手教你配置苹果APNS推送服务

热门文章

  1. echarts自适应窗口(父盒子)大小
  2. 25道经典大企Python面试题总结(附答案)
  3. Playwright + Python爬虫
  4. osgEarth测高程方法
  5. 人脸识别嵌入式Linux芯片瑞芯微RV1109参数介绍
  6. 数据库课程设计----学生信息与选课、成绩评价管理系统
  7. Eclips运行时概述4
  8. python补充超级鹰代码
  9. STM32H743使用PA0,PA1作为ADC输入的坑!!
  10. Gem5模拟器,详解官网教程的statistics and output(三)