Lambda-让人又爱又恨的“->"
写在前边
- 聊到Java8新特性,我们第一反应想到的肯定是Lambda表达式和函数式接口的出现。要说ta到底有没有在一定程度上“优化”了代码的简洁性呢?抑或是ta在一定程度上给程序员增加了阅读和debug的难度,让不少程序员头疼。这期来接着“聊聊Java”,新特性篇只又爱又恨的Lambda。
Lambda表达式
实质属于函数式编程的概念,可返回一个接口的实现
线程中的应用
传统方式
创建一个一次性的类
//一次性的类,用在new Thread中充当Runnable对的实现类
class runnable implements Runnable{
@Override
public void run() {
System.out.println("我在路上");
}
}
public class lambdaTest {
public static void main(String[] args) {
runnable runnable = new runnable();
Thread thread1 = new Thread(runnable);
}
}
(稍微优化)匿名内部类
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我在路上");
}
});
作用
- 避免匿名内部类定义过多
- 使代码看起来简洁
- 简化代码,只留下核心逻辑
函数式接口
定义:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
public interface Runnable{
void run();
}
为了避免后来人给这个接口添加函数后,导致该接口有多个函数,不再是函数式接口,我们可以在接口类的上方声明@FunctionalInterface
所有的Lambda的类型都是一个接口
- 而Lambda表达式本身,就是这个接口的实现
如果定义成实现类,就会报错
- 我们在程序编译的时候并不知道我们最终创建出来的对象具体是哪个子类,直到运行时期才能得知,随之我们可以让这个like指向指向各种不同的类上,可以调用各种不同的子类方法,大大提高了程序的可扩展性
而这里我们用lambda实际上是等价于匿名内部类(没有类名),实际创建出来的类是什么,我们不知道,所以我们会定义成接口,利用多态的向上转型特性
关于多态的更多特性,在我的另一篇博客中 : 传送门->多态
方法引用
Demo
//接口定义
interface parseIntNum{
//定义一个String转化成Integer的方法
int pass(String s);
}
public static void main(String[] args) {
parseIntNum parseIntNum1;
parseIntNum parseIntNum2;
//原始lambda
parseIntNum1 = (str)-> Integer.parseInt(str);
System.out.println(parseIntNum1.pass("1"));
//方法引用改进版本
parseIntNum2 = Integer::parseInt;
System.out.println(parseIntNum2.pass("1"));
}
所谓方法引用,是指如果某个已经存在的方法,他的签名和接口里边定义的函数恰好一致,就可以直接传入方法引用。
因为parseIntNum接口定义的方法是int pass(String s),和Integer中的静态方法int parseInt(String s)相比,除了方法名外,方法参数一致,返回类型相同,这就是我们说的方法签名一致,可以直接传入方法引用
方法引用的写法
- 其实很简单,只需要使用操作符双冒号** "::"**
常见的方法引用
常见的引用形式
类名::静态方法名
调用类的静态方法
- 其实我们上边使用Integer::parseInt 就等价于调用 Integer的静态方法 parseInt
对象:实例方法
此处小小中二了一点hhhh
class Naruto{
public static void Rasengan(){
System.out.println("螺旋丸");
}
}
//接口定义
interface Ninja{
//定义一个奥义方法
void aoyi();
}
public class methodQuote {
//通过引用Naurto的螺旋丸
Ninja ninja=Naruto::Rasengan;
//再发动奥义
ninjia.aoyi();
}
数据类型:new
public static void main(String[] args) {
//方式一
IntFunction<int []> arr1 = new IntFunction<int[]>() {
@Override
public int[] apply(int num) {
return new int[num];
}
};
arr1.apply(10);
//方式二(方法引用)
IntFunction<int []> arr2 = int[]::new;
arr2.apply(10);
}
- 除此之外还有很多种形式,这里就不过多赘述了
开发常用
常用小技巧:遍历数组打印
对比三种方法,可以看出简洁程度
ArrayList<Integer> arrayList=new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(3);
//匿名内部类
arrayList.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
//lambda优化
arrayList.forEach((integer)-> System.out.println(integer));
//方法引用打印,用方法引用替代了我们的匿名内部类(相当于替代了lambda)
arrayList.forEach(System.out::println);
遍历Map
Map<Integer, String> map = new HashMap<>();
map.forEach((k,v)->System.out.println(v));
Lambda表达式作用域
访问局部普通变量
- 只能引用标记了final的外层局部变量
即 : 不能在lambda 内部修改定义在域外的局部变量,否则会编译错误。
- 特殊情况下,局部变量也可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
破坏隐式final(再次修改)
package com.melo.notes.lambdaTest;
public class TestFinal {
interface MeiYan{
Integer change(String str);
}
public static void main(String[] args) {
//定义局部变量
String temp = "222";
//写成一行可以不用return
MeiYan meiYan = (str -> Integer.valueOf(str+temp));
//再次修改,不符合隐式final定义
temp = "333";
Integer str =meiYan.change("111") ;
System.out.println(str);
}
}
- 不允许声明一个与局部变量同名的参数或者局部变量。
访问局部引用变量
import java.util.ArrayList;
public class TestArray {
interface MeiYan{
Integer change();
}
void testArrayList(){
ArrayList<String> list = new ArrayList<>();
list.add("111");
//访问外部引用局部引用变量
MeiYan meiYan = (() -> Integer.valueOf(list.get(0)));
//修改局部引用变量
list.set(0,"222");
Integer str =meiYan.change();
System.out.println(str);
}
public static void main(String[] args) {
new TestArray().testArrayList();
}
}
- 访问引用变量的话,是没有问题的。因为lambda可以感知到外部对该引用变量的改变,不会出现数据不同步问题
具体可以看下边的"理解"有更详细的解释
访问静态变量和实例变量
都是可以的,再次修改也不会报错
代码
public class TestFinal {
//静态变量
static String StaticTemp;
//实例变量
String instanceTemp;
interface MeiYan{
Integer change(String str);
}
void testStatic(){
StaticTemp="222";
MeiYan meiYan = (str -> Integer.valueOf(str+StaticTemp));
StaticTemp="333";
Integer str =meiYan.change("111") ;
System.out.println(str);
}
public static void main(String[] args) {
new TestFinal().testStatic();
}
理解
实例变量和局部变量的区别
这里的关键问题转换到: 实例变量和局部变量的区别是什么?
- 实例变量存储在堆上
堆是在线程之间共享的。
- 而局部变量存储在栈上
有可能会有相应的线程问题(见下)
线程问题
比如A线程分配了一个变量temp
- 有可能Lambda是在另一个线程B中使用的,使用Lambda的线程B,可能会在分配该变量的线程A将temp变量收回之后,还去访问temp变量。
数据不同步问题
联想一下我们普通的方法,方法的参数只是存活在方法栈这个空间里,而我们lambda的{ }里实际上也相当于一个方法块。
- 如果我们这里的方法块访问了外部的变量,而这个变量只是一个普通数据类型的话,相当于只是访问到了一份副本。当外部对这个变量进行修改时,lambda内部(只有副本)是无法感知到这个变量的修改的。
因此为了防止出现数据不同步的问题,java8就限制了:lambda访问局部普通数据类型变量时,需要用final修饰或使用隐式final的方法!
扩展--值传递还是引用传递?
- 这里只是提到了,对于普通数据类型的局部变量会有限制,而对于引用类型的局部变量呢?这就涉及到了Java的参数传值究竟是值传递还是引用传递了。
先占个坑位,以后还会在本专栏中更新一篇关于这方面的博客!!!
疑问解决
一开始有个疑问,看起来方法引用省略了参数,那我们Intger.parseInt是去对谁操作?
- 其实是自己混淆了lambda,lambda定义的时候那个参数,根本不是实际的参数
可以说那个参数,只是为方法体服务的,只是方法体里边会用到.
而我们都用了方法引用了,前提就是参数和返回值一样,方法体也是我们想要实现的内容,这时自然而然都不用我们写方法体了,那方法体所依赖的参数也自然不用派上用场了
写在最后
- 最近要开始忙活项目了,对于这些基础知识的更深入掌握,也许会放在以后准备面试的时候,目前理解不深,如有错误之处还望指出!
本专栏还会断断续续更新一些Java基础知识和面经,提前为以后面试打下基础!
Lambda-让人又爱又恨的“->"的更多相关文章
- 谈谈Nancy中让人又爱又恨的Diagnostics【上篇】
前言 在Nancy中有个十分不错的功能-Diagnostics,可以说这个功能让人又爱又恨. 或许我们都做过下面这样的一些尝试: 记录某一个功能用到的相关技术信息 记录下网站的访问记录 全局配置某些框 ...
- 让人又爱又恨的Lombok,到底该不该用
1 简介 Lombok,印尼的一个岛屿,龙目岛.但在Java的世界里,它是一个方便的类库,能提供很多便利,因此得到许多人的青睐.但也有不少反对声音.这是为什么呢? 之前去龙目岛拍的日落. 2 Lomb ...
- 让人又爱又恨的char(字符型)
今天来总结一下char型,平常写算法的时候对这个东西感觉都有一点绕着走,说到底还是对这部分的知识不熟悉所以有点怕他,不过以后不要怕,今天来总结一下 首先,说到字符型数据类型,char型,恩它是一种数据 ...
- 让人又爱又恨的this
this是个神奇的东西, 既可以帮助我们把模拟的类实例化. 又可以在事件绑定里准确指向触发元素. 还可以帮助我们在对象方法中操作对象的其他属性或方法. 甚至可以在使用apply.call.bing.f ...
- MySQL让人又爱又恨的多表查询
1. 前言 在SQL开发当中,多表联查是绝对绕不开的一种技能.同样的查询结果不同的写法其运行效率也是千差万别. 在实际开发当中,我见过(好像还写过~)不少又长又臭的查询SQL,数据量一上来查个十几分钟 ...
- 又爱又恨系列之枚举enum
其实枚举挺简单的,只不过以前没好好学,所以不知道这个东西,恩,现在梳理一下 整体而言,首先枚举是一个数据类型,这个数据类型和结构体有点像 可以分为三个层次 1.枚举数据类型定义 第一种:enum 枚举 ...
- 爱与恨的抉择:ASP.NET 5+EntityFramework 7
EF7 的纠缠 ASP.NET 5 的无助 忘不了你的好 一开始列出的这个博文大纲,让我想到了很久之前的一篇博文:恋爱虽易,相处不易:当EntityFramework爱上AutoMapper,只不过这 ...
- 搞不懂为什么开发人员爱iOS恨Android?
导读:很多网站发表文章大同小异.唯有这个不同点,给大家分享. Android和iOS的较量一直都是人们津津乐道的话题.两个平台各有各的优势所在,同时也都力图能在各个方面赶超对手.对于用户来说,青菜萝卜 ...
- 又爱又恨的BOOTSTRAP
搞本书,看了一天,确实,,UIKIT比它好用... 但,艺多不压身吧. 今天自己抄了个大概的,不用其它插件,,但那手风琴,真的找了很多,没有中意的... <!DOCTYPE html> & ...
随机推荐
- Groovy系列(2)- Groovy与Java的不同之处
Groovy与Java的不同之处 默认 imports 所有这些包和类都是默认导入的,不必使用显式import语句来使用它们 java.io.* java.lang.* java.math.BigDe ...
- jmeter之命令行执行jmx脚本
使用界面执行不稳定,且保存报告非常麻烦 https://www.jb51.net/article/191367.htm 作者:Anthony_tester 来源:CSDN 原文:https://blo ...
- SVN与LDAP服务器整合验证
说明:svn的访问是以svn://协议访问的,一般都是用http协议访问,所以要使用apache的httpd服务器apache已经添加了对ldap服务器的支持,所以svn的认证过程是使用apache代 ...
- [转载]用redis实现跨服务器session
地址:http://blog.chinaunix.net/uid-11121450-id-3284875.html 这个月我们新开发了一个项目,由于使用到了4台机器做web,使用dns做负载均衡, 上 ...
- ARC106E-Medals【hall定理,高维前缀和】
正题 题目链接:https://atcoder.jp/contests/arc106/tasks/arc106_e 题目大意 \(n\)个员工,第\(i\)个在\([1,A_i]\)工作,\([A_i ...
- strategy策略模式个人理解
首先了解策略模式的主要作用:能够把算法进行封装和动态传递: 可能听上去很抽象,我们引入一个方便理解的案例来解释: 给定一个数组 int[] array = {32,12,42,26,-23,0,-2, ...
- CefSharp请求资源拦截及自定义处理
CefSharp请求资源拦截及自定义处理 前言 在CefSharp中,我们不仅可以使用Chromium浏览器内核,还可以通过Cef暴露出来的各种Handler来实现我们自己的资源请求处理. 什么是资源 ...
- Android QMUI实战:实现APP换肤功能,并自动适配手机深色模式
Android换肤功能已不是什么新鲜事了,市面上有很多第三方的换肤库和实现方案. 之所以选择腾讯的QMUI库来演示APP的换肤功能,主要原因: 1.换肤功能的实现过程较简单.容易理解: 2.能轻松适配 ...
- ❤️【Python从入门到精通】(二十七)更进一步的了解Pillow吧!
您好,我是码农飞哥,感谢您阅读本文,欢迎一键三连哦. 进一步介绍Pillow库的使用,详细了解 干货满满,建议收藏,需要用到时常看看. 小伙伴们如有问题及需要,欢迎踊跃留言哦~ ~ ~. 前言 本文是 ...
- MyBatis的框架设计
1.MyBatis的框架设计 2.整体设计 2.1 总体流程 (1)加载配置并初始化 触发条件:加载配置文件 配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信 ...