Lambda 表达式详解~Lambda与集合

我们先从最熟悉的*Java集合框架(Java Collections Framework, JCF)*开始说起。

为引入Lambda表达式,Java8新增了java.util.funcion包,里面包含常用的函数接口,这是Lambda表达式的基础,Java集合框架也新增部分接口,以便与Lambda表达式对接。

首先回顾一下Java集合框架的接口继承结构:

 上图中绿色标注的接口类,表示在Java8中加入了新的接口方法,当然由于继承关系,他们相应的子类也都会继承这些新方法。下表详细列举了这些方法。

口名Java8新加入的方法
CollectionremoveIf() spliterator() stream() parallelStream() forEach()
ListreplaceAll() sort()
MapgetOrDefault() forEach() replaceAll() putIfAbsent() remove() replace() computeIfAbsent() computeIfPresent() compute() merge()

这些新加入的方法大部分要用到java.util.function包下的接口,这意味着这些方法大部分都跟Lambda表达式相关。我们将逐一学习这些方法。

Collection中的新方法

如上所示,接口CollectionList新加入了一些方法,我们以是List的子类ArrayList为例来说明。了解Java7ArrayList实现原理,将有助于理解下文。

forEach()

该方法的签名为void forEach(Consumer<? super E> action),作用是对容器中的每个元素执行action指定的动作,其中Consumer是个函数接口,里面只有一个待实现方法void accept(T t)(后面我们会看到,这个方法叫什么根本不重要,你甚至不需要记忆它的名字)。

需求:假设有一个字符串列表,需要打印出其中所有长度大于3的字符串.

Java7及以前我们可以用增强的for循环实现:

// 使用曾强for循环迭代
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
for(String str : list){if(str.length()>3)System.out.println(str);
}

现在使用forEach()方法结合匿名内部类,可以这样实现:

// 使用forEach()结合匿名内部类迭代
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.forEach(new Consumer<String>(){@Overridepublic void accept(String str){if(str.length()>3)System.out.println(str);}
});

上述代码调用forEach()方法,并使用匿名内部类实现Comsumer接口。到目前为止我们没看到这种设计有什么好处,但是不要忘记Lambda表达式,使用Lambda表达式实现如下:

// 使用forEach()结合Lambda表达式迭代
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.forEach( str -> {if(str.length()>3)System.out.println(str);});

上述代码给forEach()方法传入一个Lambda表达式,我们不需要知道accept()方法,也不需要知道Consumer接口,类型推导帮我们做了一切。

removeIf()

该方法签名为boolean removeIf(Predicate<? super E> filter),作用是删除容器中所有满足filter指定条件的元素,其中Predicate是一个函数接口,里面只有一个待实现方法boolean test(T t),同样的这个方法的名字根本不重要,因为用的时候不需要书写这个名字。

需求:假设有一个字符串列表,需要删除其中所有长度大于3的字符串。

我们知道如果需要在迭代过程冲对容器进行删除操作必须使用迭代器,否则会抛出ConcurrentModificationException,所以上述任务传统的写法是:

// 使用迭代器删除列表元素
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
Iterator<String> it = list.iterator();
while(it.hasNext()){if(it.next().length()>3) // 删除长度大于3的元素it.remove();
}

现在使用removeIf()方法结合匿名内部类,我们可是这样实现:

// 使用removeIf()结合匿名名内部类实现
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.removeIf(new Predicate<String>(){ // 删除长度大于3的元素@Overridepublic boolean test(String str){return str.length()>3;}
});

上述代码使用removeIf()方法,并使用匿名内部类实现Precicate接口。相信你已经想到用Lambda表达式该怎么写了:

// 使用removeIf()结合Lambda表达式实现
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.removeIf(str -> str.length()>3); // 删除长度大于3的元素

使用Lambda表达式不需要记忆Predicate接口名,也不需要记忆test()方法名,只需要知道此处需要一个返回布尔类型的Lambda表达式就行了。

replaceAll()

该方法签名为void replaceAll(UnaryOperator<E> operator),作用是对每个元素执行operator指定的操作,并用操作结果来替换原来的元素。其中UnaryOperator是一个函数接口,里面只有一个待实现函数T apply(T t)

需求:假设有一个字符串列表,将其中所有长度大于3的元素转换成大写,其余元素不变。

Java7及之前似乎没有优雅的办法:

// 使用下标实现元素替换
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
for(int i=0; i<list.size(); i++){String str = list.get(i);if(str.length()>3)list.set(i, str.toUpperCase());
}

使用replaceAll()方法结合匿名内部类可以实现如下:

// 使用匿名内部类实现
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.replaceAll(new UnaryOperator<String>(){@Overridepublic String apply(String str){if(str.length()>3)return str.toUpperCase();return str;}
});

上述代码调用replaceAll()方法,并使用匿名内部类实现UnaryOperator接口。我们知道可以用更为简洁的Lambda表达式实现:

// 使用Lambda表达式实现
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.replaceAll(str -> {if(str.length()>3)return str.toUpperCase();return str;
});

sort()

该方法定义在List接口中,方法签名为void sort(Comparator<? super E> c),该方法根据c指定的比较规则对容器元素进行排序Comparator接口我们并不陌生,其中有一个方法int compare(T o1, T o2)需要实现,显然该接口是个函数接口。

需求:假设有一个字符串列表,按照字符串长度增序对元素排序。

由于Java7以及之前sort()方法在Collections工具类中,所以代码要这样写:

// Collections.sort()方法
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
Collections.sort(list, new Comparator<String>(){@Overridepublic int compare(String str1, String str2){return str1.length()-str2.length();}
});

现在可以直接使用List.sort()方法,结合Lambda表达式,可以这样写:

// List.sort()方法结合Lambda表达式
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.sort((str1, str2) -> str1.length()-str2.length());

spliterator()

方法签名为Spliterator<E> spliterator(),该方法返回容器的可拆分迭代器。从名字来看该方法跟iterator()方法有点像,我们知道Iterator是用来迭代容器的,Spliterator也有类似作用,但二者有如下不同:

  1. Spliterator既可以像Iterator那样逐个迭代,也可以批量迭代。批量迭代可以降低迭代的开销。

  2. Spliterator是可拆分的,一个Spliterator可以通过调用Spliterator<T> trySplit()方法来尝试分成两个。一个是this,另一个是新返回的那个,这两个迭代器代表的元素没有重叠。

可通过(多次)调用Spliterator.trySplit()方法来分解负载,以便多线程处理。

stream()和parallelStream()

stream()parallelStream()分别返回该容器的Stream视图表示,不同之处在于parallelStream()返回并行的StreamStream是Java函数式编程的核心类,我们会在后面章节中学习。

Map中的新方法

相比CollectionMap中加入了更多的方法,我们以HashMap为例来逐一探秘。了解Java7HashMap实现原理,将有助于理解下文。

forEach()

该方法签名为void forEach(BiConsumer<? super K,? super V> action),作用是Map中的每个映射执行action指定的操作,其中BiConsumer是一个函数接口,里面有一个待实现方法void accept(T t, U u)BinConsumer接口名字和accept()方法名字都不重要,请不要记忆他们。

需求:假设有一个数字到对应英文单词的Map,请输出Map中的所有映射关系.

Java7以及之前经典的代码如下:

// Java7以及之前迭代Map
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
for(Map.Entry<Integer, String> entry : map.entrySet()){System.out.println(entry.getKey() + "=" + entry.getValue());
}

使用Map.forEach()方法,结合匿名内部类,代码如下:

// 使用forEach()结合匿名内部类迭代Map
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.forEach(new BiConsumer<Integer, String>(){@Overridepublic void accept(Integer k, String v){System.out.println(k + "=" + v);}
});

上述代码调用forEach()方法,并使用匿名内部类实现BiConsumer接口。当然,实际场景中没人使用匿名内部类写法,因为有Lambda表达式:

// 使用forEach()结合Lambda表达式迭代Map
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.forEach((k, v) -> System.out.println(k + "=" + v));
}

getOrDefault()

该方法跟Lambda表达式没关系,但是很有用。方法签名为V getOrDefault(Object key, V defaultValue),作用是按照给定的key查询Map中对应的value,如果没有找到则返回defaultValue。使用该方法程序员可以省去查询指定键值是否存在的麻烦.

需求;假设有一个数字到对应英文单词的Map,输出4对应的英文单词,如果不存在则输出NoValue

// 查询Map中指定的值,不存在时使用默认值
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
// Java7以及之前做法
if(map.containsKey(4)){ // 1System.out.println(map.get(4));
}else{System.out.println("NoValue");
}
// Java8使用Map.getOrDefault()
System.out.println(map.getOrDefault(4, "NoValue")); // 2

putIfAbsent()

该方法跟Lambda表达式没关系,但是很有用。方法签名为V putIfAbsent(K key, V value),作用是只有在不存在key值的映射或映射值为null,才将value指定的值放入到Map中,否则不对Map做更改.该方法将条件判断和赋值合二为一,使用起来更加方便.

remove()

我们都知道Map中有一个remove(Object key)方法,来根据指定key值删除Map中的映射关系;Java8新增了remove(Object key, Object value)方法,只有在当前Map中**key正好映射到value时**才删除该映射,否则什么也不做.

replace()

在Java7及以前,要想替换Map中的映射关系可通过put(K key, V value)方法实现,该方法总是会用新值替换原来的值.为了更精确的控制替换行为,Java8在Map中加入了两个replace()方法,分别如下:

  • replace(K key, V value),只有在当前Map中**key的映射存在时**才用value去替换原来的值,否则什么也不做.

  • replace(K key, V oldValue, V newValue),只有在当前Map中**key的映射存在且等于oldValue时**才用newValue去替换原来的值,否则什么也不做.

replaceAll()

该方法签名为replaceAll(BiFunction<? super K,? super V,? extends V> function),作用是对Map中的每个映射执行function指定的操作,并用function的执行结果替换原来的value,其中BiFunction是一个函数接口,里面有一个待实现方法R apply(T t, U u).不要被如此多的函数接口吓到,因为使用的时候根本不需要知道他们的名字.

需求:假设有一个数字到对应英文单词的Map,请将原来映射关系中的单词都转换成大写.

Java7以及之前经典的代码如下:

// Java7以及之前替换所有Map中所有映射关系
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
for(Map.Entry<Integer, String> entry : map.entrySet()){entry.setValue(entry.getValue().toUpperCase());
}

使用replaceAll()方法结合匿名内部类,实现如下:

// 使用replaceAll()结合匿名内部类实现
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.replaceAll(new BiFunction<Integer, String, String>(){@Overridepublic String apply(Integer k, String v){return v.toUpperCase();}
});

上述代码调用replaceAll()方法,并使用匿名内部类实现BiFunction接口。更进一步的,使用Lambda表达式实现如下:

// 使用replaceAll()结合Lambda表达式实现
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
map.replaceAll((k, v) -> v.toUpperCase());

merge()

该方法签名为merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction),作用是:

  1. 如果Mapkey对应的映射不存在或者为null,则将value(不能是null)关联到key上;

  2. 否则执行remappingFunction,如果执行结果非null则用该结果跟key关联,否则在Map中删除key的映射.

参数中BiFunction函数接口前面已经介绍过,里面有一个待实现方法R apply(T t, U u)

merge()方法虽然语义有些复杂,但该方法的用方式很明确,一个比较常见的场景是将新的错误信息拼接到原来的信息上,比如:

map.merge(key, newMsg, (v1, v2) -> v1+v2);

compute()

该方法签名为compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction),作用是把remappingFunction的计算结果关联到key上,如果计算结果为null,则在Map中删除key的映射.

要实现上述merge()方法中错误信息拼接的例子,使用compute()代码如下:

map.compute(key, (k,v) -> v==null ? newMsg : v.concat(newMsg));

computeIfAbsent()

该方法签名为V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction),作用是:只有在当前Map不存在key值的映射或映射值为null,才调用mappingFunction,并在mappingFunction执行结果非null时,将结果跟key关联.

Function是一个函数接口,里面有一个待实现方法R apply(T t)

computeIfAbsent()常用来对Map的某个key值建立初始化映射.比如我们要实现一个多值映射,Map的定义可能是Map<K,Set<V>>,要向Map中放入新值,可通过如下代码实现:

Map<Integer, Set<String>> map = new HashMap<>();
// Java7及以前的实现方式
if(map.containsKey(1)){map.get(1).add("one");
}else{Set<String> valueSet = new HashSet<String>();valueSet.add("one");map.put(1, valueSet);
}
// Java8的实现方式
map.computeIfAbsent(1, v -> new HashSet<String>()).add("yi");

使用computeIfAbsent()将条件判断和添加操作合二为一,使代码更加简洁.

computeIfPresent()

该方法签名为V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction),作用跟computeIfAbsent()相反,即,只有在当前Map存在key值的映射且非null,才调用remappingFunction,如果remappingFunction执行结果为null,则删除key的映射,否则使用该结果替换key原来的映射.

这个函数的功能跟如下代码是等效的:

// Java7及以前跟computeIfPresent()等效的代码
if (map.get(key) != null) {V oldValue = map.get(key);V newValue = remappingFunction.apply(key, oldValue);if (newValue != null)map.put(key, newValue);elsemap.remove(key);return newValue;
}
return null;
  1. Java8为容器新增一些有用的方法,这些方法有些是为完善原有功能,有些是为引入函数式编程,学习和使用这些方法有助于我们写出更加简洁有效的代码.

  2. 函数接口虽然很多,但绝大多数时候我们根本不需要知道它们的名字,书写Lambda表达式时类型推断帮我们做了一切.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/495959.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

京东AI战略宏图展现 不枉挖来这么多AI大牛

来源&#xff1a;网易科技4月15日下午&#xff0c;京东人工智能创新峰会在北京举行。这次会议虽然规模不大&#xff0c;但是堪称重磅&#xff0c;一是在这次会议上京东AI带头人周伯文首次向外界展示京东在AI领域的战略布局与发展方向&#xff1b;二是AI领域重量级人物周志华等大…

Lambda 表达式详解~Streams API~Stream常见接口方法

你可能没意识到Java对函数式编程的重视程度&#xff0c;看看Java 8加入函数式编程扩充多少功能就清楚了。Java 8之所以费这么大功夫引入函数式编程&#xff0c;原因有二&#xff1a; 代码简洁函数式编程写出的代码简洁且意图明确&#xff0c;使用stream接口让你从此告别for循环…

Scrapy源码阅读分析_1_整体框架和流程介绍

From&#xff1a;https://blog.csdn.net/weixin_37947156/article/details/74435304 Scrapy github 下载地址&#xff1a;https://github.com/scrapy/scrapy 介绍 Scrapy是一个基于Python编写的一个开源爬虫框架&#xff0c;它可以帮你快速、简单的方式构建爬虫&#xff0c;并…

Waymo正式向真正“无人车”迈进,申请DMV远程监控许可证

作者 &#xff1a; DudeWaymo 又向前迈出一大步&#xff0c;真正迈向“无人车”&#xff0c;测试的自动驾驶车辆将不配备安全驾驶员。据报道&#xff1a;Waymo已经向加州车管局提出了申请&#xff0c;Waymo官方也证实了媒体报道&#xff0c;而DMV方面称&#xff0c;在申请提交后…

或者是修改服务器时间,修改云服务器时间设置

修改云服务器时间设置 内容精选换一换云服务器的系统盘在创建云服务器时自动创建并挂载&#xff0c;无需单独购买。数据盘可以在购买云服务器的时候一同购买&#xff0c;由系统自动挂载给云服务器。也可以在购买了云服务器之后&#xff0c;单独购买云硬盘并挂载给云服务器。本节…

Lambda 表达式详解~Streams API~规约操作

上一节介绍了部分Stream常见接口方法&#xff0c;理解起来并不困难&#xff0c;但Stream的用法不止于此&#xff0c;本节我们将仍然以Stream为例&#xff0c;介绍流的规约操作。 规约操作&#xff08;reduction operation&#xff09;又被称作折叠操作&#xff08;fold&#x…

Scrapy源码阅读分析_2_启动流程

From&#xff1a;https://blog.csdn.net/weixin_37947156/article/details/74436333 使用 PyCharm 打开下载好的 Scrapy 源码&#xff08;github&#xff1a;https://github.com/scrapy/scrapy&#xff09; scrapy命令 当用 scrapy 写好一个爬虫后&#xff0c;使用 scrapy craw…

重磅!这可能是史上最全的AI产业链地图了

来源&#xff1a;智东西摘要&#xff1a;信通院最新发布全球人工智能产业地图&#xff0c;从底层技术到垂直应用&#xff0c;盘点人工智能发展态势。这年头&#xff0c;没挂上AI的名号&#xff0c;都不好意思说自己是旗舰机。德勤也预测到&#xff1a;2023年&#xff0c;人工智…

2018年医疗人工智能技术与应用白皮书

来源&#xff1a;互联网医疗健康产业联盟【导读】2017 年医疗人工智能发展迅速&#xff0c;产业格局风起云涌。人工智能在医疗领域中的应用已非常广泛&#xff0c;包括医学影像、临床决策支持、语音识别、药物挖掘、健康管理、病理学等众多领域。本白皮书梳理和研究国际、国内医…

Scrapy源码阅读分析_3_核心组件

From&#xff1a;https://blog.csdn.net/weixin_37947156/article/details/74481758 这篇这要是关于核心组件&#xff0c;讲解这些核心组件初始化都做了哪些工作。包括&#xff1a;引擎、下载器、调度器、爬虫类、输出处理器 等的初始化。每个核心组件下其实都包含一些小的组件…

微信服务器向公众号推送消息或事件后,微信服务器向公众号推送消息或事件后,得到的回应不合法?...

呼啦08-04加粗标红插入代码插入链接插入图片上传视频请 登录 后发表内容关闭新增或编辑超链接链接地址关闭插入视频视频链接Appid: wxd4170daab0213d6a昵称: 大都会官微UAT时间: 2021-08-04 11:03:44内容: 微信服务器向公众号推送消息或事件后&#xff0c;得到的回应不合法次数…

Lambda 表达式详解~Stream Pipelines

前面我们已经学会如何使用Stream API&#xff0c;用起来真的很爽&#xff0c;但简洁的方法下面似乎隐藏着无尽的秘密&#xff0c;如此强大的API是如何实现的呢&#xff1f;比如Pipeline是怎么执行的&#xff0c;每次方法调用都会导致一次迭代吗&#xff1f;自动并行又是怎么做到…

报告:采用人工智能并不意味着成功

来源&#xff1a;199IT互联网数据中心毕马威发布了新报告“采用人工智能并不意味着成功”&#xff0c;分析了2018年的主要趋势。采用数字化劳动&#xff08;Digital labor&#xff09;、机器人流程自动化、人工智能、机器学习和其他创新技术解决方案是2018年和未来几年的流行趋…

Scrapy源码阅读分析_4_请求处理流程

From&#xff1a;https://blog.csdn.net/weixin_37947156/article/details/74533108 运行入口 还是回到最初的入口&#xff0c;在Scrapy源码分析&#xff08;二&#xff09;运行入口这篇文章中已经讲解到&#xff0c;在执行scrapy命令时&#xff0c;调用流程如下&#xff1a; …

普通电阻触摸屏多点触摸低成本解决方 转载

苹果公司iPhone的成功将多点触摸技术推到了一个前所未有的高度&#xff0c;经典的弹钢琴应用程序可以支持超过5点的同时触摸&#xff0c;虽然这一性能并不见得有太多的实用价值&#xff0c;但绝对带给了用户技术无限领先的震撼感。苹果公司的iPhone采用电容屏和他们的专利技术来…

Gartner:2018年前沿技术预测

本文转载自科技中国&#xff0c;作者&#xff1a;孟海华(上海市科学学研究所)&#xff0c;首发刊载于《科技中国》杂志2018年3月 第3期 预测。一、人工智能全球领先的信息技术研究与顾问公司Gartner认为&#xff0c;2018年将是人工智能大众化应用的开始&#xff0c;将影响到企业…

@font-face

问题描述&#xff1a; 产品展示的界面上有个产品编号&#xff0c;由后台程序动态生成&#xff0c;如图 而"875"的字体是特殊字体&#xff0c;如果客户端系统上没有安装该特殊字体&#xff0c;就达不到原设计效果。 解决办法(我认为的三种)&#xff1a;1。通过FLASH实…

扎克伯格|在美国国会数据门听证会上的证词-中英文全文

来源&#xff1a;网络法前哨美国时间2018年4月10日至11日&#xff0c;Facebook公司CEO马克-扎克伯格&#xff08;Mark Zuckerberg&#xff09;将在美国国会就“剑桥分析丑闻”作证。4月10日&#xff0c;扎克伯格已经参加了美国参议院司法与商业委员会举行的听证会。4月11日&…

Scrapy-Link Extractors(链接提取器)

Link Extractors 中文文档&#xff1a;https://scrapy-chs.readthedocs.io/zh_CN/1.0/topics/link-extractors.html Link Extractors 英文文档&#xff1a;http://doc.scrapy.org/en/latest/topics/link-extractors.html 利用爬虫Scrapy中的LinkExtractor&#xff08;链接提取器…

Java8 Stream详解~Stream 创建

Stream可以通过集合数组创建。 1、通过 java.util.Collection.stream() 方法用集合创建流 List<String> list Arrays.asList("a", "b", "c"); // 创建一个顺序流 Stream<String> stream list.stream(); // 创建一个并行流 Strea…