Java8 常用的lambda表达式

本文深入探讨Java8中Lambda表达式的应用场景,包括常用函数接口如Supplier、Predicate、Consumer、Function及BinaryOperator的实例演示。同时,展示了如何利用Stream简化集合操作,提高代码效率,并介绍Optional类型在避免空指针异常方面的优势。

Java8 常用的几个lambda函数接口

  1. Supplier
  2. Predicate
  3. Consumer
  4. Function
//使用Lambda表达式提供Supplier接口实现,返回OK字符串
Supplier<String> stringSupplier = ()->"OK";
//使用方法引用提供Supplier接口实现,返回空字符串
Supplier<String> supplier = String::new;

//Predicate接口是输入一个参数,返回布尔值。我们通过and方法组合两个Predicate条件,判断是否值大于0并且是偶数
Predicate<Integer> positiveNumber = i -> i > 0;
Predicate<Integer> evenNumber = i -> i % 2 == 0;
assertTrue(positiveNumber.and(evenNumber).test(2));

//Consumer接口是消费一个数据。我们通过andThen方法组合调用两个Consumer,输出两行abcdefg
Consumer<String> println = System.out::println;
println.andThen(println).accept("abcdefg");

//Function接口是输入一个数据,计算后输出一个数据。我们先把字符串转换为大写,然后通过andThen组合另一个Function实现字符串拼接
Function<String, String> upperCase = String::toUpperCase;
Function<String, String> duplicate = s -> s.concat(s);
assertThat(upperCase.andThen(duplicate).apply("test"), is("TESTTEST"));

//Supplier是提供一个数据的接口。这里我们实现获取一个随机数
Supplier<Integer> random = ()->ThreadLocalRandom.current().nextInt();
System.out.println(random.get());

//BinaryOperator是输入两个同类型参数,输出一个同类型参数的接口。这里我们通过方法引用获得一个整数加法操作,通过Lambda表达式定义一个减法操作,然后依次调用
BinaryOperator<Integer> add = Integer::sum;
BinaryOperator<Integer> subtraction = (a, b) -> a - b;
assertThat(subtraction.apply(add.apply(1, 2), 3), is(0));

使用 Stream 简化集合操作

示例:


private static double calc(List<Integer> ints) {
    //临时中间集合
    List<Point2D> point2DList = new ArrayList<>();
    for (Integer i : ints) {
        point2DList.add(new Point2D.Double((double) i % 3, (double) i / 3));
    }
    //临时变量,纯粹是为了获得最后结果需要的中间变量
    double total = 0;
    int count = 0;

    for (Point2D point2D : point2DList) {
        //过滤
        if (point2D.getY() > 1) {
            //算距离
            double distance = point2D.distance(0, 0);
            total += distance;
            count++;
        }
    }
    //注意count可能为0的可能
    return count >0 ? total / count : 0;
}


================================================================
stream 方式

List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
double average = calc(ints);
double streamResult = ints.stream()
        .map(i -> new Point2D.Double((double) i % 3, (double) i / 3))
        .filter(point -> point.getY() > 1)
        .mapToDouble(point -> point.distance(0, 0))
        .average()
        .orElse(0);
//如何用一行代码来实现,比较一下可读性
assertThat(average, is(streamResult));

Optional 可空类型

其实,类似 OptionalDouble、OptionalInt、OptionalLong 等,是服务于基本类型的可空对象。此外,Java8 还定义了用于引用类型的 Optional 类。使用 Optional,不仅可以避免使用 Stream 进行级联调用的空指针问题;更重要的是,它提供了一些实用的方法帮我们避免判空逻辑。

@Test(expected = IllegalArgumentException.class)
public void optional() {
    //通过get方法获取Optional中的实际值
    assertThat(Optional.of(1).get(), is(1));
    //通过ofNullable来初始化一个null,通过orElse方法实现Optional中无数据的时候返回一个默认值
    assertThat(Optional.ofNullable(null).orElse("A"), is("A"));
    //OptionalDouble是基本类型double的Optional对象,isPresent判断有无数据
    assertFalse(OptionalDouble.empty().isPresent());
    //通过map方法可以对Optional对象进行级联转换,不会出现空指针,转换后还是一个Optional
    assertThat(Optional.of(1).map(Math::incrementExact).get(), is(2));
    //通过filter实现Optional中数据的过滤,得到一个Optional,然后级联使用orElse提供默认值
    assertThat(Optional.of(1).filter(integer -> integer % 2 == 0).orElse(null), is(nullValue()));
    //通过orElseThrow实现无数据时抛出异常
    Optional.empty().orElseThrow(IllegalArgumentException::new);
}

在这里插入图片描述

Java 8 类对于函数式 API 的增强


private Map<Long, Product> cache = new ConcurrentHashMap<>();

private Product getProductAndCache(Long id) {
    Product product = null;
    //Key存在,返回Value
    if (cache.containsKey(id)) {
        product = cache.get(id);
    } else {
        //不存在,则获取Value
        //需要遍历数据源查询获得Product
        for (Product p : Product.getData()) {
            if (p.getId().equals(id)) {
                product = p;
                break;
            }
        }
        //加入ConcurrentHashMap
        if (product != null)
            cache.put(id, product);
    }
    return product;
}

@Test
public void notcoolCache() {
    getProductAndCache(1L);
    getProductAndCache(100L);

    System.out.println(cache);
    assertThat(cache.size(), is(1));
    assertTrue(cache.containsKey(1L));
}

而在 Java 8 中,我们利用 ConcurrentHashMap 的 computeIfAbsent 方法,用一行代码就可以实现这样的繁琐操作:


private Product getProductAndCacheCool(Long id) {
    return cache.computeIfAbsent(id, i -> //当Key不存在的时候提供一个Function来代表根据Key获取Value的过程
            Product.getData().stream()
                    .filter(p -> p.getId().equals(i)) //过滤
                    .findFirst() //找第一个,得到Optional<Product>
                    .orElse(null)); //如果找不到Product,则使用null
}

@Test
public void coolCache()
{
    getProductAndCacheCool(1L);
    getProductAndCacheCool(100L);

    System.out.println(cache);
    assertThat(cache.size(), is(1));
    assertTrue(cache.containsKey(1L));
}

并行流


IntStream.rangeClosed(1,100).parallel().forEach(i->{
    System.out.println(LocalDateTime.now() + " : " + i);
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) { }
});

五种方式实现多线程:

  1. 直接把任务按照线程数均匀分割,分配到不同的线程执行,使用 CountDownLatch 来阻塞主线程,直到所有线程都完成操作。
    private int thread(int taskCount, int threadCount) throws InterruptedException {
        //总操作次数计数器
        AtomicInteger atomicInteger = new AtomicInteger();
        //使用CountDownLatch来等待所有线程执行完成
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        //使用IntStream把数字直接转为Thread
        IntStream.rangeClosed(1, threadCount).mapToObj(i -> new Thread(() -> {
            //手动把taskCount分成taskCount份,每一份有一个线程执行
            IntStream.rangeClosed(1, taskCount / threadCount).forEach(j -> increment(atomicInteger));
            //每一个线程处理完成自己那部分数据之后,countDown一次
            countDownLatch.countDown();
        })).forEach(Thread::start);
        //等到所有线程执行完成
        countDownLatch.await();
        //查询计数器当前值
        return atomicInteger.get();
    }
    
  2. Executors.newFixedThreadPool 来获得固定线程数的线程池,使用 execute 提交所有任务到线程池执行,最后关闭线程池等待所有任务执行完成.
    private int threadpool(int taskCount, int threadCount) throws InterruptedException {
        //总操作次数计数器
        AtomicInteger atomicInteger = new AtomicInteger();
        //初始化一个线程数量=threadCount的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
        //所有任务直接提交到线程池处理
        IntStream.rangeClosed(1, taskCount).forEach(i -> executorService.execute(() -> increment(atomicInteger)));
        //提交关闭线程池申请,等待之前所有任务执行完成
        executorService.shutdown();
        executorService.awaitTermination(1, TimeUnit.HOURS);
        //查询计数器当前值
        return atomicInteger.get();
    }
    
  3. 使用 ForkJoinPool 而不是普通线程池执行任务。
    ForkJoinPool 和传统的 ThreadPoolExecutor 区别在于,前者对于 n 并行度有 n 个独立队列,后者是共享队列。如果有大量执行耗时比较短的任务,ThreadPoolExecutor 的单队列就可能会成为瓶颈。这时,使用 ForkJoinPool 性能会更好。
    因此,ForkJoinPool 更适合大任务分割成许多小任务并行执行的场景,而 ThreadPoolExecutor 适合许多独立任务并发执行的场景。
    private int forkjoin(int taskCount, int threadCount) throws InterruptedException {
        //总操作次数计数器
        AtomicInteger atomicInteger = new AtomicInteger();
        //自定义一个并行度=threadCount的ForkJoinPool
        ForkJoinPool forkJoinPool = new ForkJoinPool(threadCount);
        //所有任务直接提交到线程池处理
        forkJoinPool.execute(() -> IntStream.rangeClosed(1, taskCount).parallel().forEach(i -> increment(atomicInteger)));
        //提交关闭线程池申请,等待之前所有任务执行完成
        forkJoinPool.shutdown();
        forkJoinPool.awaitTermination(1, TimeUnit.HOURS);
        //查询计数器当前值
        return atomicInteger.get();
    }
    
  4. 直接使用并行流,并行流使用公共的 ForkJoinPool,也就是 ForkJoinPool.commonPool()。
    公共的 ForkJoinPool 默认的并行度是 CPU 核心数 -1,原因是对于 CPU 绑定的任务分配超过 CPU 个数的线程没有意义。由于并行流还会使用主线程执行任务,也会占用一个 CPU 核心,所以公共 ForkJoinPool 的并行度即使 -1 也能用满所有 CPU 核心。
    private int stream(int taskCount, int threadCount) {
        //设置公共ForkJoinPool的并行度
    System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", String.valueOf(threadCount));
        //总操作次数计数器
        AtomicInteger atomicInteger = new AtomicInteger();
        //由于我们设置了公共ForkJoinPool的并行度,直接使用parallel提交任务即可
        IntStream.rangeClosed(1, taskCount).parallel().forEach(i -> increment(atomicInteger));
        //查询计数器当前值
        return atomicInteger.get();
    }
    
  5. 使用 CompletableFuture 来实现。CompletableFuture.runAsync 方法可以指定一个线程池,一般会在使用 CompletableFuture 的时候用到.
    private int completableFuture(int taskCount, int threadCount) throws InterruptedException, ExecutionException {
        //总操作次数计数器
        AtomicInteger atomicInteger = new AtomicInteger();
        //自定义一个并行度=threadCount的ForkJoinPool
        ForkJoinPool forkJoinPool = new ForkJoinPool(threadCount);
        //使用CompletableFuture.runAsync通过指定线程池异步执行任务
        CompletableFuture.runAsync(() -> IntStream.rangeClosed(1, taskCount).parallel().forEach(i -> increment(atomicInteger)), forkJoinPool).get();
        //查询计数器当前值
        return atomicInteger.get();
    }
    

另外需要注意的是,在上面的例子中我们一定是先运行 stream 方法再运行 forkjoin 方法,对公共 ForkJoinPool 默认并行度的修改才能生效。

ForkJoinPool 类初始化公共线程池是在静态代码块里,加载类时就会进行的,如果 forkjoin 方法中先使用了 ForkJoinPool,即便 stream 方法中设置了系统属性也不会起作用。因此我的建议是,设置 ForkJoinPool 公共线程池默认并行度的操作,应该放在应用启动时设置。

JAVA 业务开发常见错误100例笔记。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值