java.util.Map中的putIfAbsent、computeIfAbsent、computeIfPresent、compute的区别

探索Java8:(三)Predicate接口的使用

HashMap

putIfAbsent

default V putIfAbsent(K key,V value)

If the specified key is not already associated with a value (or is mapped to null) associates it with the given value and returns null, else returns the current value

上面是官方的解释,意思是如果给定的key不存在(或者key对应的value为null),关联给定的key和给定的value,并返回null;

如果存在,返回当前值(不会把value放进去);

  1.      Map<Integer, List<Integer>> map = new HashMap<>();
  2. map.put(2, Lists.newArrayList(22));
  3. List<Integer> xx = map.putIfAbsent(2, Lists.newArrayList(1));
  4. System.out.println(map + " " + xx);//返回:{2=[22]} [22]
  5.  
  6. map.clear();
  7. xx = map.putIfAbsent(1, Lists.newArrayList(1));
  8. System.out.println(map + " " + xx);//返回:{1=[1]} null

computeIfAbsent

default V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)
If the specified key is not already associated with a value (or is mapped to null), attempts to compute its value using the given mapping function and enters it into this map unless null.

If the function returns null no mapping is recorded. If the function itself throws an (unchecked) exception, the exception is rethrown, and no mapping is recorded. The most common usage is to construct a new object serving as an initial mapped value or memoized result, as in:

map.computeIfAbsent(key, k -> new Value(f(k)));
Or to implement a multi-value map, Map<K,Collection<V>>, supporting multiple values per key:

map.computeIfAbsent(key, k -> new HashSet<V>()).add(v);

官方文档的解释:如果给定的key不存在(或者key对应的value为null),就去计算mappingFunction的值;

如果mappingFunction的值不为null,就把key=value放进去;
如果mappingFunction的值为null,就不会记录该映射关系,返回值为null;
如果计算mappingFunction的值的过程出现异常,再次抛出异常,不记录映射关系,返回null;
如果存在该key,并且key对应的value不为null,返回null;(HashMap返回的是旧value)

  1.      Map<Integer, List<Integer>> map = new HashMap<>();
  2. map.put(2, Lists.newArrayList(22));
  3. List<Integer> xx = map.computeIfAbsent(2, k -> new ArrayList<>());
  4. System.out.println(map + " " + xx);//返回:{2=[22]} [22] key存在,得到旧值并返回
  5. xx.add(222);
  6. System.out.println(map + " " + xx);//返回:{2=[22, 222]} [22, 222]
  7.  
  8. map.clear();
  9. xx = map.computeIfAbsent(1, k -> new ArrayList<>());
  10. System.out.println(map + " " + xx);//返回:{1=[]} [] key不存在,计算后面的表达式并返回
  11. xx.add(1);
  12. System.out.println(map + " " + xx);//返回:{1=[1]} [1]

computeIfPresent

default V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)
If the value for the specified key is present and non-null, attempts to compute a new mapping given the key and its current mapped value.

If the function returns null, the mapping is removed. If the function itself throws an (unchecked) exception, the exception is rethrown, and the current mapping is left unchanged.

官方文档的解释:key存在并且不为空,计算remappingFunction的值value;

如果value不为空,保存指定key和value的映射关系;
如果value为null,remove(key);
如果计算value的过程抛出了异常,computeIfPresent方法中会再次抛出,key和其对应的值不会改变

  1.      Map<Integer, List<Integer>> map = new HashMap<>();
  2. map.put(2, Lists.newArrayList(22));
  3. List<Integer> xx = map.computeIfPresent(2, (k, v) -> {
  4. v.add(222);
  5. return v;
  6. });
  7. System.out.println(map + " " + xx);//返回:{2=[22, 222]} [22, 222] 当key存在,插入新value并返回
  8.  
  9. xx = map.computeIfPresent(2, (k, v) -> {
  10. return null;
  11. });
  12. System.out.println(map + " " + xx);//返回:{} null 当key存在,插入value=null,remove key并返回null
  13.  
  14. map.clear();
  15. xx = map.computeIfPresent(2, (k, v) -> {
  16. v.add(222);
  17. return v;
  18. });
  19. System.out.println(map + " " + xx);//返回:{} null 当key不存在,不插入新value,返回null

compute

  1. V oldValue = map.get(key);
  2. V newValue = remappingFunction.apply(key, oldValue);
  3. if (oldValue != null ) {
  4. if (newValue != null)
  5. map.put(key, newValue);
  6. else
  7. map.remove(key);
  8. } else {
  9. if (newValue != null)
  10. map.put(key, newValue);
  11. else
  12. return null;
  13. }

和这段代码意思一样。

官方文档:如果lambda表达式的值不为空,不论key是否已经存在,建立一种映射关系key=newValue;否则,不建立映射并返回null。

  1.      Map<Integer, List<Integer>> map = new HashMap<>();
  2. map.put(2, Lists.newArrayList(22));
  3. List<Integer> xx = map.compute(2, (k, v) -> {
  4. return Lists.newArrayList(22, 222);
  5. });
  6. System.out.println(map + " " + xx);//返回:{2=[22, 222]} [22, 222] key存在,插入新value并返回
  7.  
  8. xx = map.compute(2, (k, v) -> {
  9. return null;
  10. });
  11. System.out.println(map + " " + xx);//返回:{} null 表达式返回null,remove key并返回null
  12.  
  13. xx = map.compute(2, (k, v) -> {
  14. return Lists.newArrayList(22, 222);
  15. });
  16. System.out.println(map + " " + xx);//返回:{2=[22, 222]} [22, 222] key不存在,插入新value并返回

小结

putIfAbsent和computeIfAbsent

都是在key不存在的时候才会建立key和value的映射关系;
putIfAbset不论传入的value是否为空,都会建立映射(并不适合所有子类,例如HashTable),而computeIfAbsent方法,当存入value为空时,不做任何操作
当key不存在时,返回的都是新的value(为什么不说新插入的value),即使computeIfAbsent在传入的value为null时,不会新建映射关系,但返回的也是null;

computeIfPresent和computeIfAbsent

这两个方法正好相反,前者是在key存在时,才会用新的value替换oldValue
当传入的key存在,并且传入的value为null时,前者会remove(key),把传入的key对应的映射关系移除;而后者不论何时都不会remove();
前者只有在key存在,并且传入的value不为空的时候,返回值是value,其他情况都是返回null;后者只有在key不存在,并且传入的value不为null的时候才会返回value,其他情况都返回null;

compute
新传入的value不为null就建立映射关系(也就是说不论key是否为null,具体子类再具体分析)
新传入的value为null时:key已存在,且老的对应value不为null,移除改映射关系,返回null;否则,直接返回null

Predicate

Predicate

Predicate是个断言式接口其参数是<T,boolean>,也就是给一个参数T,返回boolean类型的结果。Predicate的具体实现也是根据传入的lambda表达式来决定的。

基本示例:

  1. Predicate<Integer> aa= xx-> xx== 2;
  2. aa.test(xx);

Predicate默认实现的三个重要方法and,or和negate。这三个方法对应了java的三个连接符号&&、|| 和 !。

举例:

  1. int[] numbers= {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
  2. List<Integer> list=new ArrayList<>();
  3. for(int i:numbers) {
  4. list.add(i);
  5. }
  6. Predicate<Integer> p1=i->i>5;
  7. Predicate<Integer> p2=i->i<20;
  8. Predicate<Integer> p3=i->i%2==0;
  9. List test=list.stream().filter(p1.and(p2).and(p3)).collect(Collectors.toList());
  10. System.out.println(test.toString());
  11. /** print:[6, 8, 10, 12, 14]*/
    List test=list.stream().filter(p1.and(p2).and(p3.negate())).collect(Collectors.toList());
    /** print:[7, 9, 11, 13, 15]*/

isEqual这个方法的返回类型也是Predicate,所以我们也可以把它作为函数式接口进行使用。我们可以当做==操作符来使用。

  1. List test=list.stream()
  2. .filter(p1.and(p2).and(p3.negate()).and(Predicate.isEqual(7)))
  3. .collect(Collectors.toList());
  4. /** print:[7] */

BiPredicate

相比Predicate,其实就是由1个入参变为2个。

  1. BiPredicate<Integer, Integer> xx = (type, subType) -> type == 20 && (subType == 15);

Function

Function

Function是一个泛型类,其中定义了两个泛型参数T和R,在Function中,T代表输入参数,R代表返回的结果。

其作用类似于数学中函数的定义 ,(x,y)跟<T,R>的作用几乎一致。y = f(x)

Function中没有具体的操作,具体的操作需要我们去为它指定,因此apply具体返回的结果取决于传入的lambda表达式。

  1. R apply(T t);

举例:

  1. public void test(){
  2. Function<Integer,Integer> test=i->i+1;
  3. test.apply(5);
  4. }
  5. /** print:6*/

用于逻辑复用:

  1. public void test(){
  2. Function<Integer,Integer> test1=i->i+1;
  3. Function<Integer,Integer> test2=i->i*i;
  4. System.out.println(calculate(test1,5));
  5. System.out.println(calculate(test2,5));
  6. }
  7. public static Integer calculate(Function<Integer,Integer> test,Integer number){
  8. return test.apply(number);
  9. }
  10. /** print:6*/
  11. /** print:25*/

实现复杂逻辑:compose、andThen

compose接收一个Function参数,返回时先用传入的逻辑执行apply,然后使用当前Function的apply。

andThen跟compose正相反,先执行当前的逻辑,再执行传入的逻辑。

compose等价于B.apply(A.apply(5)),而andThen等价于A.apply(B.apply(5))。

  1. public void test(){
  2. Function<Integer,Integer> A=i->i+1;
  3. Function<Integer,Integer> B=i->i*i;
  4. System.out.println("F1:"+B.apply(A.apply(5)));
  5. System.out.println("F1:"+B.compose(A).apply(5));
  6. System.out.println("F2:"+A.apply(B.apply(5)));
  7. System.out.println("F2:"+B.andThen(A).apply(5));
  8. }
  9. /** F1:36 */
  10. /** F1:36 */
  11. /** F2:26 */
  12. /** F2:26 */

可以看到上述两个方法的返回值都是一个Function,这样我们就可以使用建造者模式的操作来使用。

  1. B.compose(A).compose(A).andThen(A).apply(5);

BiFunction

相比Function,其实就是由1个入参变为2个。

  1. BiFunction<String, Function<String, String>, Boolean> xx =
  2. (stat, mapper) -> Optional.ofNullable(stat)
  3. .map(mapper)
  4. .map(Double::parseDouble)
  5. .map(comp -> comp >= 0)
  6. .orElse(false);

Optional

empty Optional

  1. Optional<String> empty = Optional.empty();
  2. System.out.println(empty.isPresent());//false

Optional.of

传递给of()的值不可以为空,否则会抛出空指针异常。

  1. Optional<String> xx = Optional.of("kk");
  2. System.out.println(xx.isPresent() + " " + xx.get());//true kk

Optional.ofNullable

使用ofNullable API,则当传递进去一个空值时,不会抛出异常,而只是返回一个空的Optional对象,如同我们用Optional.empty。

  1. Optional<String> xx = Optional.ofNullable("kk");
  2. System.out.println(xx.isPresent() + " " + xx.get());//true xx
  3. xx = Optional.ofNullable(null);
  4. System.out.println(xx.isPresent());//false get()不返回值,因为不存在值

isPresent

判断Optional对象中是否有值,只有值非空才返回true。

ifPresent

传统写法检查空值:

  1. if(name != null){
  2. System.out.println(name.length);
  3. }

Optional写法:

  1. Optional<String> opt = Optional.ofNullable(name);
  2. opt.ifPresent(na -> {
  3. System.out.println(na);
  4. });

orElse orElseGet

orElse用来检索Optional对象中的值,它被传入一个“默认参数‘。如果对象中存在一个值,则返回它,否则返回传入的“默认参数”。

  1. String name = null;
  2. String name = Optional.ofNullable(name).orElse("xx");

orElseGet与orElse类似,但是这个函数不接收一个“默认参数”,而是一个函数接口。

  1. String nullName = null;
  2. String name = Optional.ofNullable(nullName).orElseGet(() -> "xx");

两者区别:

  1. public void test() {
  2. String text = "name";
  3. String defaultText =
  4. Optional.ofNullable(text).orElseGet(this::aa);
  5. System.out.println(defaultText);
  6. System.out.println("---");
  7.  
  8. defaultText =
  9. Optional.ofNullable(text).orElse(aa());
  10. System.out.println(defaultText);
  11. }
  12.  
  13. public String aa() {
  14. System.out.println("aa method");//orElse,当入参为null,会调用aa()方法
  15. return "aa";
  16. }

当text为null,二者一致。

当text不为null,orElse会计算后面的aa()。

orElseThrow

orElseThrow当遇到一个不存在的值的时候,并不返回一个默认值,而是抛出异常。

  1. String nullName = null;
  2. String name = Optional.ofNullable(nullName).orElseThrow(
  3. IllegalArgumentException::new);

get

当Optional.ofNullable(nullName)存在值时,才可以get,否则报错。

filter map flatmap

  1. Optional.ofNullable(String str)
  2. .map(Integer::parseInt)
  3. .filter(p -> p >= 10)
  4. .filter(p -> p <= 15)
  5. .isPresent();

有时我们可以使用flatmap()替换map(),二者不同之处在于,map()只有当值不被包裹时才进行转换,而flatmap()接受一个被包裹着的值并且在转换之前对其解包。

  1. public class Person {
  2. public String name;
  3.  
  4. public Optional<String> getName() {
  5. return Optional.ofNullable(name);
  6. }
  7. }
  8.  
  9. Person person = new Person();
    person.name = "xx";
  10. Optional<Person> personOptional = Optional.of(person);
  11. String name = personOptional
  12. .flatMap(Person::getName)
  13. .orElse("");

方法getName返回的是一个Optional对象,而不是像trim那样。这样就生成了一个嵌套的Optional对象。

Supplier

Supplier 接口是一个供给型的接口,其实,说白了就是一个容器,可以用来存储数据,然后可以供其他方法使用的一个接口。

  1.      Supplier<Integer> supplier = new Supplier<Integer>() {
  2. @Override
  3. public Integer get() {
  4. //返回一个随机值
  5. return new Random().nextInt();
  6. }
  7. };
  8.  
  9. System.out.println(supplier.get());//每次调用都会走到get()方法内部
  10. System.out.println(supplier.get());
  11.  
  12. System.out.println("********************");
  13.  
  14. //使用lambda表达式,
  15. supplier = () -> new Random().nextInt();
  16. System.out.println(supplier.get());
  17. System.out.println("********************");
  18.  
  19. //使用方法引用
  20. Supplier<Double> supplier2 = Math::random;
  21. System.out.println(supplier2.get());

在 Optional 对象中orElseGet 是需要一个 Supplier 接口。

Consumer

consumer接口就是一个消费型的接口,通过传入参数,然后输出值。

  1. //使用consumer接口实现方法
  2. Consumer<String> consumer = new Consumer<String>() {
  3.  
  4. @Override
  5. public void accept(String s) {
  6. System.out.println(s);
  7. }
  8. };
  9. Stream<String> stream = Stream.of("aaa", "bbb", "ddd", "ccc", "fff");
  10. stream.forEach(consumer);
  11.  
  12. System.out.println("********************");
  13.  
  14. //使用lambda表达式,forEach方法需要的就是一个Consumer接口
  15. stream = Stream.of("aaa", "bbb", "ddd", "ccc", "fff");
  16. Consumer<String> consumer1 = (s) -> System.out.println(s);//lambda表达式返回的就是一个Consumer接口
  17. stream.forEach(consumer1);
  18. //更直接的方式
  19. //stream.forEach((s) -> System.out.println(s));
  20. System.out.println("********************");
  21.  
  22. //使用方法引用,方法引用也是一个consumer
  23. stream = Stream.of("aaa", "bbb", "ddd", "ccc", "fff");
  24. Consumer consumer2 = System.out::println;
  25. stream.forEach(consumer);
  26. //更直接的方式
  27. //stream.forEach(System.out::println);

Java8常用示例的更多相关文章

  1. Java8常用的内置函数式接口(一)Predicate、Consumer、Supplier、Function

    Java8常用的内置函数式接口(一) 简介 JDK 1.8 API中包含了很多内置的函数式接口.有些是在以前版本的Java中大家耳熟能详的,例如Comparator接口,或者Runnable接口.对这 ...

  2. Java8常用新特性实践

    前言: 时下Oracle开速迭代的Java社区以即将推出Java10,但尴尬的是不少小中企业仍使用JDK7甚至JDK6开发. 从上面列出的JDK8特性中我们可以发现Java8的部分特性很明显的是从Sc ...

  3. js之checkbox判断常用示例

    checkbox常用示例可参考: 关于checkbox自动选中 checkbox选中并通过ajax传数组到后台接收 MP实战系列(十三)之批量修改操作(前后台异步交互) 本次说的是,还是关于智能门锁开 ...

  4. Linux curl 常用示例

    本篇文章包含了curl的常用案例使用. 如果想了解curl选项的详细说明,请参考前一篇文章「Linux curl 命令详解」. 常见网页访问示例 基本用法 访问一个网页 curl https://ww ...

  5. Linux ar命令介绍 和常用示例

    制作静态库要用到ar命令,命令格式: ar [-]{dmpqrtx}[abcfilNoPsSuvV] [membername] [count] archive files... {dmpqrtx}中的 ...

  6. jQuery ajax常用示例

    总结一下jQuery ajax常用示例 $.ajax({ type: "post", //类型get,post url: urls, //链接地址 data:{"id&q ...

  7. c++中stl容器的常用示例

    1. set(集合)——包含了经过排序了的数据,这些数据的值(value)必须是唯一的. 也就是说输入set容器后得到数据,会去重并排序.    s.insert()插入一个元素    s.begin ...

  8. 25个iptables常用示例

    本文将给出25个iptables常用规则示例,这些例子为您提供了些基本的模板,您可以根据特定需求对其进行修改调整以达到期望. 格式 iptables [-t 表名] 选项 [链名] [条件] [-j ...

  9. iOS开发之Runtime常用示例总结

    经常有小伙伴私下在Q上问一些关于Runtime的东西,问我有没有Runtime的相关博客,之前还真没正儿八经的总结过.之前只是在解析第三方框架源码时,聊过一些用法,也就是这些第三方框架中用到的Runt ...

随机推荐

  1. Windows下MariaDB数据库安装图文教程

    MariaDB是基于MySQL的开源数据库,兼容MySQL,现有的MySQL数据库可以迁移到MariaDB中使用   说明: MariaDB是基于MySQL的开源数据库,兼容MySQL,现有的MySQ ...

  2. Qt编写气体安全管理系统4-通信协议

    一.前言 通信协议解析是整个系统的核心灵魂,绝大部分人做软硬件通信开发,第一步估计就是写demo将协议解析好,然后再慢慢写整个界面和操作流程等,在工业控制领域,modbus协议应用还是非常广泛的,这个 ...

  3. django 中单独执行py文件修改用户名

    Python文件代码 import os import django # 在environ字典里设置默认Django环境,'xxxx.settings'指Django项目的配置文件 os.enviro ...

  4. 【Leetcode_easy】883. Projection Area of 3D Shapes

    problem 883. Projection Area of 3D Shapes 参考 1. Leetcode_easy_883. Projection Area of 3D Shapes; 完

  5. Dockerfile指令的使用

    关于Dockerfile Dockerfile实际上就是一系列创建Docker镜像的脚本, 虽然可以通过命令行来执行, 但是那样繁琐而且容易出错. Dockerfile指令 FROM 他的意思是在创建 ...

  6. Kylin系列(一)—— 入门

          因为平常只会使用kylin而不知其原理,故写下此篇文章.文章不是自己原创,是看过很多资料,查过很多博客,有自己的理解,觉得精华的部分的一个集合.算是自己对Kylin学习完的一个总结和概括吧 ...

  7. [转帖]Linux下主机间文件传输命令

    Linux下主机间文件传输命令 https://yq.aliyun.com/articles/53631?spm=a2c4e.11155435.0.0.580ce8ef4Q9uzs   SCP命令: ...

  8. SQL 先固定特殊的几行数据之外再按照某一字段排序方法(CASE 字段排序(CASE WHEN THEN)

    查询用户表的数据,管理员用户始终在最前面,然后再按照CreateTime排序: SELECT TOP * FROM [dbo].[User] WHERE ParentID = '**' ORDER B ...

  9. MATLAB:一个K×M的矩阵,第一列是1,其它都是0,从最后一行开始,每循环一次,最后一行的1往右边移一位,移动到末尾后溢出,重新回到最左边,同时上一行的1往右边移一位

    问题:一个K×M的矩阵,第一列是1,其它都是0,从最后一行开始,每循环一次,最后一行的1往右边移一位,移动到末尾后溢出,重新回到最左边,同时上一行的1往右边移一位.上一行溢出时,上上一行的1移动一位, ...

  10. k8s 证书反解

    k8s证书反解 1.将k8s配置文件(kubelet.kubeconfig)中client-certificate-data:内容拷贝 2.echo "client-certificate- ...