java8 stream的这些开发技巧,你值得好好收藏

简介: java8 stream的开发技巧

前言


  • 如果有些朋友以前没有使用过java8 stream这种链式编程方式做开发,想学习一下。
  • 如果有些朋友只学习了一部分用法,想学习更多。
  • 如果有些朋友想看看有没有好的示例适用于实际工作当中。

那么恭喜你,这篇文章非常适合你。


正文


首先,我们一起看看stream的继承关系:

2.jpg

stream的继承关系

Stream、IntStream、LongStream、DoubleStream的父接口都是BaseStream。BaseStream的四个子接口方法都差不多,只是IntStream、LongStream、DoubleStream直接存储基本类型,可以避免自动装/拆箱,效率会更高一些。但是,我们实际上使用Stream更多一些。

我们再看看stream的工作流程图:


4.jpg

stream的工作流程图


为什么要学stream的链式编程方式


业务需求1:指定一个字符串数组,找出里面相同的元素,并且统计重复的次数。

我们以前大概是这样做的:

public class CountTest {
    @Test
      public void testCount1() {
        List<String> list = Lists.newArrayList("a", "b", "ab", "abc", "a", "ab", "a", "abcd", "bd", "abc");
        Map<String, Long> countMap = new HashMap<>();
        for (String data : list) {
            Long aLong = countMap.get(data);
            if (Objects.isNull(aLong)) {
                countMap.put(data, 1L);
            } else {
                countMap.put(data, ++aLong);
            }
        }
        countMap.forEach((key, value) -> System.out.println("key:" + key + " value:" + value));
    }
}

执行结果:

key:a value:3
key:ab value:2
key:b value:1
key:bd value:1
key:abc value:2
key:abcd value:1

我们再看看如果用java8的stream可以怎么做:

public class CountTest {
    @Test
    public void testCount2() {
        List<String> list = Lists.newArrayList("a", "b", "ab", "abc", "a", "ab", "a", "abcd", "bd", "abc");
        Map<String, Long> countMap = list.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
        countMap.forEach((key, value) -> System.out.println("key:" + key + " value:" + value));
    }
}

执行结果:

key:a value:3
key:ab value:2
key:b value:1
key:bd value:1
key:abc value:2
key:abcd value:1

我们可以看到testCount1和testCount2执行结果相同,仅仅一行代码:Map<String, Long> countMap = list.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); 就可以实现上面testCount1中多行代码的逻辑。


业务需求2:从一个指定的字符串数组中,查找指定的字符串是否存在

我们以前大概是这样做的:

public class FindTest {
    @Test
    public void testFind1() {
        String findStr = "bd";
        List<String> list = Lists.newArrayList("a", "b", "ab", "abc", "a", "ab", "a", "abcd", "bd", "abc");
        boolean match = false;
        for (String data : list) {
            if (data.equals(findStr)) {
                match = true;
                break;
            }
        }
        //结果:match:true
        System.out.println("match:" + match);
    }
}

我们再看看如果用java8的stream可以怎么做:

public class MatchTest {
    @Test
    public void testFind2() {
        String findStr = "bd";
        List<String> list = Lists.newArrayList("a", "b", "ab", "abc", "a", "ab", "a", "abcd", "bd", "abc");
        boolean match = list.stream().anyMatch(x -> x.equals(findStr));
        //结果:match:true
        System.out.println("match:" + match);
    }
}


我们可以看到调用testFind1和testFind2方法执行结果也是一样的。但是,用java8 stream的语法,又只用一行代码就完成功能了,真棒。


java8 stream超详细用法指南


stream的操作符大体上分为两种:中间操作符终止操作符

中间操作:

1.filter(T-> boolean)

过滤数据,保留 boolean 为 true 的元素,返回一个集合

public class FilterTest {
    @Test
    public void testFilter() {
        List<Integer> list = Lists.newArrayList(20, 23, 25, 28, 30, 33, 37, 40);
        //从指定数据集合中过滤出大于等于30的数据集合
        List<Integer> collect = list.stream().filter(x -> x >= 30).collect(Collectors.toList());
        //结果:[33, 37, 40]
        System.out.println(collect);
    }
}

     

collect(Collectors.toList())可以把流转换为 List 类型,collect实际上是一个终止操作。


2.map(T -> R)

转换操作符,可以做数据转换,比如:把字符串转换成int、long、double,或者把一个实体转换成另外一个实体。包含:map,mapToInt、mapToLong、mapToDouble

public class MapTest {
    @Test
    public void testMap() {
        List<String> list = Lists.newArrayList("1", "2", "3", "4", "5", "6");
        List<Long> collect1 = list.stream().map(x -> Long.parseLong(x)).collect(Collectors.toList());
        //结果:[1, 2, 3, 4, 5, 6]
        System.out.println(collect1);
        //结果:111111
        list.stream().mapToInt(x -> x.length()).forEach(System.out::print);
        System.out.println("");
        //结果:111111
        list.stream().mapToLong(x -> x.length()).forEach(System.out::print);
        System.out.println("");
        //结果:1.01.01.01.01.01.0
        list.stream().mapToDouble(x -> x.length()).forEach(System.out::print);
    }
}

 

3.flatMap(T -> Stream)


将流中的每一个元素 T 映射为一个流,再把每一个流连接成为一个流

我们可以看到flatMap可以轻松把字符串的二维数据变成一位数组。

public class FlatMapTest {
    @Test
    public void testFlatMap() {
        List<List<String>>  list = new ArrayList<List<String>>(){{
            add(Lists.newArrayList("a","b","c"));
            add(Lists.newArrayList("d","e","f"));
            add(Lists.newArrayList("j","k","y"));
        }};
        //结果:[[a, b, c], [d, e, f], [j, k, y]]
        System.out.println(list);
        List<String> collect = list.stream().flatMap(List::stream).collect(Collectors.toList());
        //结果:[a, b, c, d, e, f, j, k, y]
        System.out.println(collect);
    }
}

4.distinct


去重,类似于msql中的distinct的作用,底层使用了equals方法做比较。

public class DistinctTest {
    @Test
    public void testDistinct() {
        List<String> list = Lists.newArrayList("a", "b", "ab", "abc", "a", "ab", "a", "abcd", "bd", "abc");
        List<String> collect = list.stream().distinct().collect(Collectors.toList());
        //结果:[a, b, ab, abc, abcd, bd]
        System.out.println(collect);
    }
}

其实,去重还有另外一种办法,可以用Collectors.toSet(),后面会讲到。


5.sorted

对元素进行排序,前提是实现Comparable接口,当然也可以自定义比较器。

public class SortTest {
    @Test
    public void testSort() {
        List<Integer> list = Lists.newArrayList(5, 3, 7, 1, 4, 6);
        List<Integer> collect = list.stream().sorted((a, b) -> a.compareTo(b)).collect(Collectors.toList());
        //结果:[1, 3, 4, 5, 6, 7]
        System.out.println(collect);
    }
}


6.limit


限流操作,有点类似于mysql中的limit功能,比如:有10个元素,只取前面3个元素

public class LimitTest {
    @Test
    public void testLimit() {
        List<String> list = Lists.newArrayList("a", "b", "ab", "abc", "a", "ab", "a", "abcd", "bd", "abc");
        List<String> collect = list.stream().limit(3).collect(Collectors.toList());
        //结果:[a, b, ab]
        System.out.println(collect);
    }
}

7.skip


跳过操作,比如:有个10个元素,从第5个元素开始去后面的元素

public class SkipTest {
    @Test
    public void testSkip() {
        List<String> list = Lists.newArrayList("a", "b", "ab", "abc", "a", "ab", "a", "abcd", "bd", "abc");
        List<String> collect = list.stream().skip(5).collect(Collectors.toList());
        //结果:[ab, a, abcd, bd, abc]
        System.out.println(collect);
    }
}


8.peek


挑出操作,

public class PeekTest {
    @Test
    public void testPeek() {
        List<String> list = Lists.newArrayList("a", "b", "ab", "abc", "a", "ab", "a", "abcd", "bd", "abc");
        //结果:abababcaabaabcdbdabc
        list.stream().peek(x -> x.toUpperCase()).forEach(System.out::print);
    }
}

眼尖的朋友会发现,进行x.toUpperCase()转换为大写功能,但是实际上没有生效。把peek改成map方法试试:

public class PeekTest {
    @Test
    public void testPeek() {
        List<String> list = Lists.newArrayList("a", "b", "ab", "abc", "a", "ab", "a", "abcd", "bd", "abc");
        //结果:ABABABCAABAABCDBDABC
        list.stream().map(x -> x.toUpperCase()).forEach(System.out::print);
    }
}

我们可以看到,用map操作转换成大写功能生效了,但是用peek操作却没有生效。peek只是对Stream中的元素进行某些操作,但是操作之后的数据并不返回到Stream中,所以Stream中的元素还是原来的元素。


终止操作:


1.forEach


遍历操作,包含:forEach 和 forEachOrdered

forEach:支持并行处理

forEachOrdered:是按顺序处理的,遍历速度较慢

public class ForEachTest {
    @Test
    public void testForEach() {
        List<String> list = Lists.newArrayList("a", "b", "ab");
        //结果:a b ab
        list.stream().forEach(x-> System.out.print(x+' '));
        System.out.println("");
        //可以简化
        //结果:a b ab
        list.forEach(x-> System.out.print(x+' '));
        System.out.println("");
        //结果:a b ab
        list.stream().forEachOrdered(x-> System.out.print(x+' '));
    }
}


2.collect


收集操作,将所有的元素收集起来,Collectors 提供了非常多收集器。包含:toMap、toSet、toList、joining,groupingBy,maxBy,minBy等操作。

toMap:将数据流转换为map,里面包含的元素是用key/value的形式的

toSet:将数据流转换为set,里面包含的元素不可重复

toList:将数据流转出为list,里面包含的元素是有序的

joining:拼接字符串

groupingBy:分组,可以将list转换map

couting:统计元素数量

maxBy:获取最大元素

minBy:获取最小元素

summarizingInt: 汇总int类型的元素,返回IntSummaryStatistics,再调用具体的方法对元素进行统计:getCount(统计数量),getSum(求和),getMin(获取最小值),getMax(获取最大值),getAverage(获取平均值)


summarizingLong:汇总long类型的元素,用法同summarizingInt

summarizingDouble:汇总double类型的元素,用法同summarizingInt

averagingInt:获取int类型的元素的平均值,返回一个double类型的数据

averagingLong:获取long类型的元素的平均值,用法同averagingInt

averagingDouble:获取double类型的元素的平均值,用法同averagingInt

mapping:获取映射,可以将原始元素的一部分内容作为一个新元素返回

public class CollectTest {
    @Data
    @AllArgsConstructor
    class User {
        private String name;
        private Integer age;
    }
    @Test
    public void testCollect() {
        List<String> list0 = Lists.newArrayList("a", "b", "ab");
        Map<String, String> collect0 = list0.stream().collect(Collectors.toMap(String::new, Function.identity()));
        //结果:{ab=ab, a=a, b=b}
        System.out.println(collect0);
        List<String> list = Lists.newArrayList("a", "b", "ab", "a", "b", "ab");
        List<String> collect1 = list.stream().collect(Collectors.toList());
        //结果:[a, b, ab, a, b, ab]
        System.out.println(collect1);
        //结果:[a, ab, b]
        Set<String> collect2 = list.stream().collect(Collectors.toSet());
        System.out.println(collect2);
        String collect3 = list.stream().collect(Collectors.joining(","));
        //结果:a,b,ab,a,b,ab
        System.out.println(collect3);
        Map<String, List<String>> collect4 = list.stream().collect(Collectors.groupingBy(Function.identity()));
        //结果:{ab=[ab, ab], a=[a, a], b=[b, b]}
        System.out.println(collect4);
        Long collect = list.stream().collect(Collectors.counting());
        //结果:6
        System.out.println(collect);
        String collect5 = list.stream().collect(Collectors.maxBy((a, b) -> a.compareTo(b))).orElse(null);
        //结果:b
        System.out.println(collect5);
        String collect6 = list.stream().collect(Collectors.minBy((a, b) -> a.compareTo(b))).orElse(null);
        //结果:a
        System.out.println(collect6);
        List<String> list2 = Lists.newArrayList("2", "3", "5");
        IntSummaryStatistics summaryStatistics = list2.stream().collect(Collectors.summarizingInt(x -> Integer.parseInt(x)));
        long sum = summaryStatistics.getSum();
        //结果:10
        System.out.println(sum);
        Double collect7 = list2.stream().collect(Collectors.averagingInt(x -> Integer.parseInt(x)));
        //结果:3.3333333333333335
        System.out.println(collect7);
        List<User> userList = new ArrayList<User>() {{
            add(new User("jack",23));
            add(new User("james",30));
            add(new User("curry",28));
        }};
        List<String> collect8 = userList.stream().collect(Collectors.mapping(User::getName, Collectors.toList()));
        //[jack, james, curry]
        System.out.println(collect8);
    }
}

3.find


查找操作,包含:findFirst、findAny

findFirst:找到第一个,返回的类型为Optional

findAny:使用 stream() 时找到的是第一个元素,使用 parallelStream() 并行时找到的是其中一个元素,返回的类型为Optional

public class FindOpTest {
    @Test
    public void testFindOp() {
        List<String> list = Lists.newArrayList("a", "b", "ab", "abc", "bc", "ab");
        //查找第一匹配的元素
        String data1 = list.stream().findFirst().orElse(null);
        //结果: a
        System.out.println(data1);
        String data2 = list.stream().findAny().orElse(null);
        //结果: a
        System.out.println(data2);
    }
}

4.match


匹配操作,包含:allMatch、anyMatch、noneMatch

allMatch:所有元素都满足条件,返回boolean类型

anyMatch:任意一个元素满足条件,返回boolean类型

noneMatch:所有元素都不满足条件,返回boolean类型


public class MatchTest {
    @Test
    public void testMatch() {
        List<Integer> list = Lists.newArrayList(2, 3, 5, 7);
        boolean allMatch = list.stream().allMatch(x -> x > 1);
        //结果:true
        System.out.println(allMatch);
        boolean allMatch2 = list.stream().allMatch(x -> x > 2);
        //结果:false
        System.out.println(allMatch2);
        boolean anyMatch = list.stream().anyMatch(x -> x > 2);
        //结果:true
        System.out.println(anyMatch);
        boolean noneMatch1 = list.stream().noneMatch(x -> x > 5);
        //结果:false
        System.out.println(noneMatch1);
        boolean noneMatch2 = list.stream().noneMatch(x -> x > 7);
        //结果:true
        System.out.println(noneMatch2);
    }
}

5.count


统计操作,效果跟调用集合的size()方法类似

public class CountOpTest {
    @Test
    public void testCountOp() {
        List<String> list = Lists.newArrayList("a", "b", "ab");
        long count = list.stream().count();
        //结果:3
        System.out.println(count);
    }
}

6.min、max


min:获取最小值,返回Optional类型的数据

max:获取最大值,返回Optional类型的数据

public class MaxMinTest {
    @Test
    public void testMaxMin() {
        List<Integer> list = Lists.newArrayList(2, 3, 5, 7);
        Optional<Integer> max = list.stream().max((a, b) -> a.compareTo(b));
        //结果:7
        System.out.println(max.get());
        Optional<Integer> min = list.stream().min((a, b) -> a.compareTo(b));
        //结果:2
        System.out.println(min.get());
    }
}

7.reduce


规约操作,将整个数据流的值规约为一个值,count、min、max底层就是使用reduce。

reduce 操作可以实现从Stream中生成一个值,其生成的值不是随意的,而是根据指定的计算模型。

public class ReduceTest {
    @Test
    public void testReduce() {
        List<Integer> list = Lists.newArrayList(2, 3, 5, 7);
        Integer sum1 = list.stream().reduce(0, Integer::sum);
        //结果:17
        System.out.println(sum1);
        Optional<Integer> reduce = list.stream().reduce((a, b) -> a + b);
        //结果:17
        System.out.println(reduce.get());
        Integer max = list.stream().reduce(0, Integer::max);
        //结果:7
        System.out.println(max);
        Integer min = list.stream().reduce(0, Integer::min);
        //结果:0
        System.out.println(min);
        Optional<Integer> reduce1 = list.stream().reduce((a, b) -> a > b ? b : a);
        //2
        System.out.println(reduce1.get());
    }
}


8.toArray


数组操作,将数据流的元素转换成数组。

public class ArrayTest {
    @Test
    public void testArray() {
        List<String> list = Lists.newArrayList("a", "b", "ab");
        String[] strings = list.stream().toArray(String[]::new);
        //结果:a b ab
        for (int i = 0; i < strings.length; i++) {
            System.out.print(strings[i]+" ");
        }
    }
}


stream和parallelStream的区别


stream:是单管道称其为流,其主要用于集合的逻辑处理。

parallelStream:是多管道,提供了流的并行处理,它是Stream的另一重要特性,其底层使用Fork/Join框架实现


public class StreamTest {
    @Test
    public void testStream() {
        List<Integer> list = Lists.newArrayList(1,2, 3,4, 5,6, 7);
        //结果:1234567
        list.stream().forEach(System.out::print);
    }
}
public class ParallelStreamTest {
    @Test
    public void testParallelStream() {
        List<Integer> list = Lists.newArrayList(1,2, 3,4, 5,6, 7);
        //结果:5726134
        list.parallelStream().forEach(System.out::print);
    }
}

我们可以看到直接使用parallelStream的forEach遍历数据,是没有顺序的。

如果要让parallelStream遍历时有顺序怎么办呢?

public class ParallelStreamTest {
    @Test
    public void testParallelStream() {
        List<Integer> list = Lists.newArrayList(1,2, 3,4, 5,6, 7);
        //结果:1234567
        list.parallelStream().forEachOrdered(System.out::print);
    }
}


parallelStream的工作原理:

6.jpg


实际工作中的案例


1.从两个集合中找相同的元素。一般用于批量数据导入的场景,先查询出数据,再批量新增或修改。

public class WorkTest {
    @Test
    public void testWork1() {
        List<String> list1 = Lists.newArrayList("a", "b", "ab");
        List<String> list2 = Lists.newArrayList("a", "c", "ab");
        List<String> collect = list1.stream()
                .filter(x -> list2.stream().anyMatch(e -> e.equals(x)))
                .collect(Collectors.toList());
        //结果:[a, ab]
        System.out.println(collect);
    }
}


2.有两个集合a和b,过滤出集合a中有,但是集合b中没有的元素。这种情况可以使用在假如指定一个id集合,根据id集合从数据库中查询出数据集合,再根据id集合过滤出数据集合中不存在的id,这些id就是需要新增的。


  @Test
    public void testWork2() {
        List<String> list1 = Lists.newArrayList("a", "b", "ab");
        List<String> list2 = Lists.newArrayList("a", "c", "ab");
        List<String> collect = list1.stream()
          .filter(x -> list2.stream().noneMatch(e -> e.equals(x)))
          .collect(Collectors.toList());
        //结果:[b]
        System.out.println(collect);
    }


3.根据条件过滤数据,并且去重做数据转换


 @AllArgsConstructor
    @Data
    class User {
        private String name;
        private Integer age;
    }
    @Test
    public void testWork3() {
        List<User> userList = new ArrayList<User>() {{
            add(new User("jack",23));
            add(new User("james",30));
            add(new User("curry",28));
            add(new User("tom",27));
            add(new User("sue",29));
        }};
        List<String> collect = userList.stream()
                .filter(x -> x.getAge() > 27)
                .sorted((a, b) -> a.getAge().compareTo(b.getAge()))
                .limit(2)
                .map(User::getName)
                .collect(Collectors.toList());
        //结果:[curry, sue]
        System.out.println(collect);
    }


4.统计指定集合中,姓名相同的人中年龄最小的年龄


 @Test
    public void testWork4() {
        List<User> userList = new ArrayList<User>() {{
            add(new User("tom", 23));
            add(new User("james", 30));
            add(new User("james", 28));
            add(new User("tom", 27));
            add(new User("sue", 29));
        }};
        userList.stream().collect(Collectors.groupingBy(User::getName))
                .forEach((name, list) -> {
                    User user = list.stream().sorted((a, b) -> a.getAge().compareTo(b.getAge())).findFirst().orElse(null);
                    //结果:name:sue,age:29
                    //     name:tom,age:23
                    //     name:james,age:28
                    System.out.println("name:" + name + ",age:" + user.getAge());
                });
    }
相关文章
|
1月前
|
Java API Maven
如何使用Java开发抖音API接口?
在数字化时代,社交媒体平台如抖音成为生活的重要部分。本文详细介绍了如何用Java开发抖音API接口,从创建开发者账号、申请API权限、准备开发环境,到编写代码、测试运行及注意事项,全面覆盖了整个开发流程。
126 10
|
1月前
|
监控 Java API
如何使用Java语言快速开发一套智慧工地系统
使用Java开发智慧工地系统,采用Spring Cloud微服务架构和前后端分离设计,结合MySQL、MongoDB数据库及RESTful API,集成人脸识别、视频监控、设备与环境监测等功能模块,运用Spark/Flink处理大数据,ECharts/AntV G2实现数据可视化,确保系统安全与性能,采用敏捷开发模式,提供详尽文档与用户培训,支持云部署与容器化管理,快速构建高效、灵活的智慧工地解决方案。
|
22天前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
40 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
9天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
60 13
|
14天前
|
算法 Java API
如何使用Java开发获得淘宝商品描述API接口?
本文详细介绍如何使用Java开发调用淘宝商品描述API接口,涵盖从注册淘宝开放平台账号、阅读平台规则、创建应用并申请接口权限,到安装开发工具、配置开发环境、获取访问令牌,以及具体的Java代码实现和注意事项。通过遵循这些步骤,开发者可以高效地获取商品详情、描述及图片等信息,为项目和业务增添价值。
48 10
|
8天前
|
前端开发 Java 测试技术
java日常开发中如何写出优雅的好维护的代码
代码可读性太差,实际是给团队后续开发中埋坑,优化在平时,没有那个团队会说我专门给你一个月来优化之前的代码,所以在日常开发中就要多注意可读性问题,不要写出几天之后自己都看不懂的代码。
45 2
|
17天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
22天前
|
存储 Java 数据挖掘
Java 8 新特性之 Stream API:函数式编程风格的数据处理范式
Java 8 引入的 Stream API 提供了一种新的数据处理方式,支持函数式编程风格,能够高效、简洁地处理集合数据,实现过滤、映射、聚合等操作。
39 6
|
22天前
|
Java API 开发者
Java中的Lambda表达式与Stream API的协同作用
在本文中,我们将探讨Java 8引入的Lambda表达式和Stream API如何改变我们处理集合和数组的方式。Lambda表达式提供了一种简洁的方法来表达代码块,而Stream API则允许我们对数据流进行高级操作,如过滤、映射和归约。通过结合使用这两种技术,我们可以以声明式的方式编写更简洁、更易于理解和维护的代码。本文将介绍Lambda表达式和Stream API的基本概念,并通过示例展示它们在实际项目中的应用。
|
1月前
|
开发框架 Java 关系型数据库
Java哪个框架适合开发API接口?
在快速发展的软件开发领域,API接口连接了不同的系统和服务。Java作为成熟的编程语言,其生态系统中出现了许多API开发框架。Magic-API因其独特优势和强大功能,成为Java开发者优选的API开发框架。本文将从核心优势、实际应用价值及未来展望等方面,深入探讨Magic-API为何值得选择。
44 2