Lambda表达式的基础知识(二)

一、Lambda表达式&方法引用

方法引用:是结合Lambda表达式的一种语法特性,有如下三种形式。

  • 静态方法引用

        类型名称.方法名称() ---->  类型名称 :: 静态方法名称

  • 实例方法引用

        创建类型对象的一个引用---> 对象引用 :: 实例方法名称

  • 构造方法应用

        构造方法引用必须要绑定函数式接口---->类型名称 :: new

代码示例:

public class App 
{
    public static void main( String[] args )
    {
        List<User> userList = new ArrayList<>();
        userList.add(new User("tom","男",12));
        userList.add(new User("sunny","女",23));
        userList.add(new User("tony","男",19));
        userList.add(new User("jimmy","男",15));
        userList.add(new User("kin","女",23));

        //1.匿名内部类实现排序
        Collections.sort(userList, new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                return o1.getAge()-o2.getAge();
            }
        });
        System.out.println(userList);

        //2.Lambda表达式实现排序
        Collections.sort(userList,(u1,u2)-> u1.getAge()-u2.getAge());
        System.out.println(userList);

        //3.静态方法引用
        Collections.sort(userList,User::compareByAge);
        System.out.println(userList);

        //4.实例方法的引用
        UserUtil userUtil = new UserUtil();
        Collections.sort(userList,userUtil::compareByName);
        System.out.println(userList);

        //5.构造方法引用--------构造方法引用需要绑定函数式接口
        IUser iUser = User::new;
        User user = iUser.initUser("Juby","女",34);
        System.out.println(user);
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class User{
    private String name;
    private String gender;
    private Integer age;

    public static int compareByAge(User user1,User user2){
        return user1.getAge()-user2.getAge();
    }
}

//User的工具类
class UserUtil{
    public int compareByName(User user1,User user2){
        return user1.getName().hashCode()-user2.getName().hashCode();
    }
}

@FunctionalInterface
interface IUser{
    User initUser(String name,String gender,Integer age);
}

二、Stream

(1)什么是流?

Stream(流)是一个来自数据源的元素队列并支持聚合操作的抽象接口。

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
  • 聚合操作 针对批量数据,类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。

总的来说,Stream 就是使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

示例:对List过滤的传统方式 和 Stream中使用filter的对比

public class App2 {
    public static void main( String[] args )
    {
        List<User> userList = new ArrayList<>();
        userList.add(new User("tom","男",12));
        userList.add(new User("sunny","女",23));
        userList.add(new User("tony","男",19));
        userList.add(new User("jimmy","男",15));
        userList.add(new User("kin","女",23));

        List<User> adultUserList = new ArrayList<>();           //已成年用户的List
        //问题:找出已成年的用户

        //方法1:迭代器遍历
        Iterator<User> userIterator = userList.iterator();
        while (userIterator.hasNext()){
            User user = userIterator.next();
            if (user.getAge() >= 18){
                adultUserList.add(user);
            }
        }
        System.out.println(adultUserList);
        adultUserList.clear();                                      //清空List

        //方法2:for循环遍历-------for循环遍历的本质上还是迭代器遍历
        for (User user : userList) {
            if (user.getAge() >= 18){
                adultUserList.add(user);
            }
        }
        System.out.println(adultUserList);
        adultUserList.clear();

        //方法3:Stream结合Lambda表达式过滤
        adultUserList = userList.stream().filter(user -> user.getAge() >= 18).collect(Collectors.toList());
        System.out.println(adultUserList);
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class User{
    private String name;
    private String gender;
    private Integer age;
}

(2)Stream的处理流程:    数据源->数据转换->获取结果

  • 数据源-------获取Stream对象

①  从集合或者数组中获取

      Collection.stream()、Collection.parallelStream()、Arrays.stream(T  t) 等

②  BufferReader

       BufferReader.lines()->stream()

  • 数据转换-------中间操作API

中间操作API,操作结果是一个Stream,中间操作可以有一个或者多个连续的中间操作。

需要注意的是,中间操作只记录操作方式,不做具体执行,直到结束操作发生时,才做数据的最终执行。

中间操作:就是业务逻辑处理。

中间操作过程区分:

             无状态:数据处理时,不受前置中间操作的影响。 map/filter/peek/parallel/sequential/unordered

             有状态:数据处理时,受到前置中间操作的影响。 distinct/sorted/limit/skip

  • 获取结果-------终结操作 | 结束操作

需要注意:一个Stream对象,只能有一个Terminal操作,这个操作一旦发生,就会真实处理数据,生成对应的处理结果。

终结操作区分:

              非短路操作:当前的Stream对象必须处理完集合中所有数据,才能得到处理结果。forEach/forEachOrdered/toArray/

                                 reduce/collect/min/max/count/iterator

       短路操作:当前的Stream对象在处理过程中,一旦满足某个条件,就可以得到结果。 anyMatch/allMatch/noneMatch/

                          findFirst/findAny等    Short-circuiting,无限大的Stream-> 有限大的Stream。

三、Stream操作示例

public class App3 {

    public static void main(String[] args) {

        //1.批量数据 -> Stream对象
        //多个数据
        Stream stream1 = Stream.of("tom","sunny","tony");

        //数组
        String[] stringArray = new java.lang.String[]{"tom","sunny","tony"};
        Stream stream2 = Arrays.stream(stringArray);

        //列表
        List<String> list = new ArrayList<>();
        list.add("tom");
        list.add("sunny");
        list.add("tony");
        Stream stream3 = list.stream();

        //集合
        Set<String> set = new HashSet<>();
        set.add("tom");
        set.add("sunny");
        set.add("tony");
        Stream stream4 = set.stream();

        //map
        Map<String,Integer> map = new HashMap<>();
        map.put("tom",14);
        map.put("sunny",17);
        map.put("tony",32);
        Stream stream5 = map.entrySet().stream();           //map中的数据在进行流处理的时候,是以entrySet的方式进行的。

        //2.Stream对象对于基本数据类型的功能封装(int、long、double)
        IntStream stream6 = IntStream.of(new int[]{1,2,3,4});
        IntStream stream7 = IntStream.range(1,4);               //生成1,2,3(不包括4)
        IntStream stream8 = IntStream.rangeClosed(6,10);        //生成6,7,8,9,10
        LongStream stream9 = LongStream.of(new long[]{1,2,3,4});
        DoubleStream stream10 = DoubleStream.of(new double[]{1,2,3,4});

        //3.Stream对象 -->  转换得到指定的数据类型
        //注意:在一个stream中,如果终结操作(类似于toArray()、collect()等)一旦发生,stream中的数据就会被清空,再去操作这个stream中的数据就会报错。

        //数组
        Object[] strArray = stream1.toArray(String[]::new);

        //字符串
        String str = stream2.collect(Collectors.joining()).toString();      //joining()是将所有的字符串拼接起来
        System.out.println(str);

        //列表
        List<String> listx = (List<String>) stream3.collect(Collectors.toList());    //这里的数据入口stream1没有指定数据类型,所以在转换完成后需要进行一次强制类型转换
        List<String> listy = list.stream().collect(Collectors.toList());             //这种情况下就不需要进行强制类型转换

        //集合
        Set<String> setx = (Set<String>) stream4.collect(Collectors.toSet());

        //map
        /**
         * <p>
         *     Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
         *                                     Function<? super T, ? extends U> valueMapper)
         *
         *      其中,toMap方法中的参数是两个Function函数式接口,因此我们可以直接用Lambda表达式的方式进行实现
         *      toMap(x->x,y->y)------------这是使用了Lambda表达式的精简语法。
         * </p>
         */
        Map<String,Integer> mapx = (Map<String, Integer>) stream3.collect(Collectors.toMap(x->"key:"+x, y->"value:"+y));
        System.out.println(mapx);
    }
}

四、Stream-Collection的相关操作---------知识点写在注释上

public class App4 {

    public static void main(String[] args) {
        List<User> userList = new ArrayList<>();
        userList.add(new User("tom","男",12));
        userList.add(new User("sunny","女",23));
        userList.add(new User("tony","男",19));
        userList.add(new User("jimmy","男",15));
        userList.add(new User("kin","女",23));
        userList.add(new User("kin","女",23));

        //map()中间操作,map()方法接收一个Functional<T,R>接口
        List<User> descUserList = userList.stream().map(x->{
            x.setName("我叫"+x.getName());
            x.setGender("性别:"+x.getGender());
            x.setAge(x.getAge()+1);
            return x;
        }).collect(Collectors.toList());
        System.out.println(descUserList);

        //filter()添加过滤条件,过滤符合条件的用户
        List<User> adultMaleUserList = userList.stream().filter(x->"男".equals(x.getGender())&&x.getAge()>=18).collect(Collectors.toList()); //成年男性用户list
        System.out.println(adultMaleUserList);

        //forEach增强型循环
        userList.forEach(x-> System.out.println("forEach-->>" + x));

        //peek()----可以简化对整个集合进行多次迭代的操作,属于中间操作,迭代数据完成对数据的依次处理过程
        userList.stream().peek(x->{
            if ("男".equals(x.getGender())){
                System.out.println("男用户:"+x);
            }
        }).peek(x->{
            if (x.getAge()>=18){
                System.out.println("成年用户:"+x);
            }
        }).forEach(System.out::println);

        //skip(n)----跳过前面n个元素,属于中间操作,有状态
        userList.stream().skip(2).forEach(System.out::println);

        //limit(n)----输出当前位置后的n个数据,属于中间操作,有状态
        userList.stream().skip(2).limit(2).forEach(System.out::println);     //跳过前两个数据后再输出两个数据

        //distinct()----剔除重复的数据,属于中间操作,有状态
        userList.stream().distinct().forEach(System.out::println);

        //sort()----排序,属于中间操作,有状态
        userList.stream().sorted((x,y)->x.getAge()-y.getAge()).forEach(System.out::println);
        userList.stream().sorted(User::compareByAge).forEach(System.out::println);

        //max()----获取最大值
        Optional max = userList.stream().max(User::compareByAge);
        System.out.println(max.get());

        //min()----获取最小值
        Optional min = userList.stream().min(User::compareByAge);
        System.out.println(min.get());

        //reduce()----合并处理数据
        List<Integer> ageList = userList.stream().map(x->x.getAge()).collect(Collectors.toList());
        Optional ageSum = ageList.stream().reduce((sum,x)-> sum + x);                               //等同于sum+=x  <==>  sum=sum+1
        System.out.println("所有用户的年龄总和:"+ageSum);
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class User{
    private String name;
    private String gender;
    private Integer age;

    //通过age比较
    public static int compareByAge(User user1,User user2){
        return user1.getAge()-user2.getAge();
    }
}

五、parallelStream的线程安全问题

  • 解决方法:

(1)自定义编码添加线程锁的方式

(2)Stream的API中提供的线程安全的终端操作如:collect()、reduce()等。

注:forEach()是非线程安全的。

(3)在一般的场景中,都是采用线程安全的集合去规范数据源。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值