Java8 特性小结

还在使用 Java6 Java7 吗,还不了解的 Java8 的可以看看

本文已参加 GitChat「我的技术实践」有奖征文活动,活动链接: GitChat「我的技术实践」有奖征文活动

Java8 Optional

  • 例一

以前写法

public String getCity(User user)  throws Exception{        if(user!=null){            if(user.getAddress()!=null){                Address address = user.getAddress();                if(address.getCity()!=null){                    return address.getCity();                }            }        }        throw new Excpetion("取值错误");     }

Java8 写法

public String getCity(User user) throws Exception{    return Optional.ofNullable(user)                   .map(u-> u.getAddress())                   .map(a->a.getCity())                   .orElseThrow(()->new Exception("取指错误"));}
  • 例二以前写法
if(user!=null){    dosomething(user);}

Java8 写法

Optional.ofNullable(user)         .ifPresent(u->{            dosomething(u);         });
  • 例三以前写法
public User getUser(User user) throws Exception{    if(user!=null){        String name = user.getName();        if("zhangsan".equals(name)){            return user;        }    }else{        user = new User();        user.setName("zhangsan");        return user;    }}

Java8 写法

public User getUser(User user) {    return Optional.ofNullable(user)                   .filter(u->"zhangsan".equals(u.getName()))                   .orElseGet(()-> {                        User user1 = new User();                        user1.setName("zhangsan");                        return user1;                   });}

Java8 妙用 Optional

Java8 Lambda 表达式

  • java 自己带的函数式接口都在 java.util.function 包里面,这里面有一些事正常的函数式接口,java 还为我们提供了一些不用自动装箱和拆箱的方法,因为正常的情况下泛型不支持基础类型,比如:int、double 之类的,只支持它们的包装类,所以如果我们传进去的是基础类型就会自动装箱,浪费时间和空间,所以 java 提供了一些基础类型的函数式接口

  • java 自带的函数式接口的那个函数不带抛出异常的,要想带,只能自己声明函数式接口

  • 通用套路:

// 先定义一个函数式接口,自己定义要加上@FunctionalInterface@FunctionalInterfacepublic interface BufferedReaderProcessor {String process(BufferedReader b) throws IOException;}// 使用函数式接口定义一个通用处理函数public static String processFile(BufferedReaderProcessor p) throwsIOException {    try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {        return p.process(br);    }}// 使用通用处理函数,传递 lambda 表达式String result = processFile((BufferedReader br) ->                            br.readLine() + br.readLine());
  • Lambda 表达式可以类型推倒,但是吧,有时候感觉还是带着类型,清晰些

  • Lambda 表达式可以使用局部变量,就像匿名类一样,==但是有一点需要注意,就是 Lambda 表达式里面使用的局部变量必须是 final 的==,上例子:

// 这种是报错的,因为 Lambda 引用的 portNumber 被赋值两次,不是 final 的int portNumber = 1337;Runnable r = () -> System.out.println(portNumber);portNumber = 31337;

之所以有这个 final 的限制,是因为局部变量是保存在栈上的,而实例变量是保存在堆上,保存在栈上的,就是每个线程独自的。如果我们的 Lambda 表达式是在另一个线程中运行的,比如 Runnable,那么就可能会有线程不安全的问题,因为这个时候相当于是两个线程访问同一个局部变量。但是堆上的东西就没事,本来也是所有线程共享的。==出于这个目的,Lambda 可以随意访问实例变量和静态变量,但是局部变量只能为 final。==Lambda 表达式能实现一部分闭包了,就是能让一个函数作为参数传递给另一个函数。

  • 方法引用

主要是分成三种情况:

  1. 类的静态方法,直接使用 类名::方法名
  2. 实例方法,使用 类名::方法名
String::length   <===>  (String str) -> str.length()
  1. 针对现有对象的实力方法,使用 对象名::方法名
expensiveTransaction::getValue <===> ()->expensiveTransaction.getValue()

4.构造函数的方法引用

// 如果是无参的构造函数,正好和 Supplier 接口符合Supplier<Apple> c1 = Apple::new;  <===> Supplier<Apple> c1 = () -> new Apple();Apple apple = c1.get();// 如果带一个参数,就符合 Function 接口  Apple(Integer weight)Function<Integer, Apple> c2 = Apple::new;   Function<Integer, Apple> c2 = (weight) -> new Apple(weight);// 如果带两个参数,就符合 BiFunction 接口 Apple(String color, Integer weight)BiFunction<String, Integer, Apple> c3 = Apple::new;Apple c3 = c3.apply("green", 110);// 如果带三个参数的话,就需要自己定义函数式接口了public interface TriFunction<T, U, V, R>{    R apply(T t, U u, V v);}TriFunction<Integer, Integer, Integer, Color> colorFactory = Color::new;

java8 in action 中介绍的终极简化版本

inventory.sort(comparing(Apple::getWeight));
  • Lambda 复合
  1. 比较器复合
// Comparator 的静态方法 comparing 是为了将一个 Function 接口转换成一个 ComparatorComparator<Apple> c = Comparator.comparing(Apple::getWeight);// Comparator 中实现了很多默认接口,这都是 java8 的东西嘛,如下:reversed() // 逆序Comparator<T> thenComparing(Comparator<? super T> other) // 比较链
  1. 谓语符合
//  negate、and 和 or Predicate<Apple> notRedApple = redApple.negate();redicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150);Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(a -> a.getWeight() > 150)                                        .or(a -> "green".equals(a.getColor()));
  1. 函数复合
//  andThen 和 composef.andThen(g) // 是把 f 得到的结果作为 g 的输入f.compose(g) // 先做 g,然后把 g 的输出作为输入,输入到 f 中// 主要是为了实现流水线功能Function<String, String> addHeader = Letter::addHeader;Function<String, String> transformationPipeline = addHeader.andThen(Letter::checkSpelling).andThen(Letter::addFooter);
  • 匿名类和 Lambda 表达式的区别
  1. 匿名类和 Lambda 表达式中的 this 和 super 的含义是不同的。在匿名类中, this 代表的是类自身,但是在 Lambda 中,它代表的是包含类。
  2. 匿名类可以屏蔽包含类的变量,而 Lambda 表达式不能(它们会导致编译错误),因为局部变量只能是 final 的
  3. 如果 Lambda 表达式符合多个函数式接口的签名,可能产生歧义,这就会出现强制转换,使得原本优雅的 Lambda 变得不优雅

==Lambda 表达式现阶段的缺点在于调试困难==,你无法一看看出是啥问题,有一种打印的方法,使用 peek 函数,把流执行完的每步都打印出来

// 打印每一步的元素List<Integer> result = numbers.stream()                            .peek(x -> System.out.println("from stream: " + x))                            .map(x -> x + 17)                            .peek(x -> System.out.println("after map: " + x))                            .filter(x -> x % 2 == 0)                            .peek(x -> System.out.println("after filter: " + x))                            .limit(3)                            .peek(x -> System.out.println("after limit: " + x))                            .collect(toList());
  • 使用 andThen 可以轻松实现责任链模式
naryOperator<String> headerProcessing = (String text) -> "From Raoul, Mario and Alan: " + text;UnaryOperator<String> spellCheckerProcessing = (String text) -> text.replaceAll("labda", "lambda");Function<String, String> pipeline = headerProcessing.andThen(spellCheckerProcessing);String result = pipeline.apply("Aren't labdas really sexy?!!")

  • 流只能被消费一次,不能重复消费,只能重新获取一个新的流,然后去重新消费
  • 流的中间操作是什么都不干的,因为懒,都是在最终操作时遍历一次,然后执行中间操作的,而且还夹杂着各种优化,比如 limit,就意味着不用遍历那么多
  • 所谓的中间操作就是返回值是 Stream 的函数,终止操作就是返回值不是 Stream 的函数
  • 流的编写感觉有点类似于数据库查询
  • ==流常用操作==:
  1. filter 接受一个谓语判断
  2. distinct()没参数,去掉重复元素
  3. limit(int n) 截取前 n 个元素
  4. skip(int n) 丢弃掉前 n 个元素
  5. map 接受一个 Lambda 函数
  6. flatMap 扁平化流 其实是它既有 map 的效果又有 flat 的效果,map 的效果就是针对流各个元素进行操作,flat 是将所有的流连接到一起成为一个流,相当于是去掉一层 Stream
List<Integer> numbers1 = Arrays.asList(1, 2, 3);List<Integer> numbers2 = Arrays.asList(3, 4);List<int[]> pairs = numbers1.stream().flatMap(i -> numbers2.stream()                                                .map(j -> new int[]{i, j})                                             ).collect(toList());

使用 flatMap 能使 Stream>转变成 Stream,就是去掉了一层 Stream

  1. anyMatch 接受一个谓语,流中是否有一个元素满足谓语,返回 boolean
  2. allMatch 同 anyMatch 返回 boolean
  3. noneMatch 没有元素满足谓语
  4. findAny 返回 Optional,返回符合条件的任意值,一般配合 filter 使用,再结合上 Optional 的方法
menu.stream()    .filter(Dish::isVegetarian)    .findAny()   <--- 返回一个 Optional<Dish>    .ifPresent(d -> System.out.println(d.getName()); <--- 如果包含值的话执行 Lambda
  1. findFirst 返回 Optional,一般配合 filter 使用,和 findAny 类似,主要是针对有序的流
  2. reduce
// 求和int sum = numbers.stream().reduce(0, Integer::sum); // 求和,主要是怕流中没有任何元素Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));// 求最大值、最小值Optional<Integer> max = numbers.stream().reduce(Integer::max);Optional<Integer> min = numbers.stream().reduce(Integer::min);// 结合 map,构建 map-reduceint count = menu.stream()                .map(d -> 1)                .reduce(0, (a, b) -> a + b);// 当然这个也可以使用 count()
  • 数值流

数值流就是所谓的 IntStream、DoubleStream、LongStream,主要就是解决拆箱的问题,因为流是泛型,必须是 Integer,如果后续操作是用 int 的话,就得拆箱,而且这些数值流还定义了普通流没有的 sum、max、min

int calories = menu.stream().mapToInt(Dish::getCalories).sum();
// 数值流向对象流的转化IntStream intStream = menu.stream().mapToInt(Dish::getCalories);Stream<Integer> stream = intStream.boxed();

IntStream、LongStream 有两个静态方法获取范围数值流:range、rangeClosed,前一个左闭右开,后一个左闭右闭

  • 构建流
  1. 使用 stream 方法
  2. Stream.of
  3. Arrays.stream
  4. Files.lines,java nio 新加入的,为了迎合 stream api
// Stream 实现了 AutoCloseable,可以使用 java7 的 try resourcelong uniqueWords = 0;try(    Stream<String> lines =Files.lines(Paths.get("data.txt"),Charset.defaultCharset())    ){    uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))                        .distinct()                        .count();    }catch(IOException e){    }

5.通过函数生成流,创建无限流

主要通过 Stream.iterate 和 Stream.generate

// 生成斐波那契的无限流Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0]+t[1]})       .limit(20)       .map(t -> t[0])       .forEach(t -> System.out.println("(" + t[0] + "," + t[1] +")"));// 生成一个全是 1 的无限流IntStream ones = IntStream.generate(() -> 1); 
  • 几点建议
  1. 尽量使用方法方法引用,减少代码长度,把复杂的逻辑封装成函数,然后使用方法引用
  2. 尽量考虑静态辅助方法,比如 comparing 、 maxBy

CompletableFuture

为了解决 Future 的问题,我们可以像 Future 那样使用,就是自己起线程,自己往 future 中赋值,自己处理异常。

public Future<Double> getPriceAsync(String product) {    CompletableFuture<Double> futurePrice = new CompletableFuture<>();    // 自己起线程    try {        double price = calculatePrice(product);        // 自己往 Future 中设置结果        futurePrice.complete(price);    } catch (Exception ex) {        // 自己往 Future 中设置异常        futurePrice.completeExceptionally(ex);    }    return futurePrice;}

简单一点的是通过 CompletableFuture 中的静态函数,比如 supplyAsync

public Future<Double> getPriceAsync(String product) {    return CompletableFuture.supplyAsync(() -> calculatePrice(product));}
  • CompletableFuture 和并行流

    对集合进行并行计算有两种方式:要么将其转化为并行流,利用 map 这样的操作开展工作,要么枚举出集合中的每一个元素,创建新的线程,在 CompletableFuture 内对其进行操作。后者提供了更多的灵活性,你可以调整线程池的大小,而这能帮助你确保整体的计算不会因为线程都在等待 I/O 而发生阻塞。

    并行流的缺点在于它使用内置的线程池,但是内置的线程池的线程个数是 cpu 的个数,比如 cpu 是 4,就是 4 个线程,但是如果你有 5 个任务就得有一个等待了。而 CompletableFuture 的好处就在于线程池可以自定义,你可以创建多个线程

线程池的个数: N threads = N CPU * U CPU * (1 + W/C)

其中: N CPU 是处理器的核的数目,可以通过 Runtime.getRuntime().availableProce- ssors() 得到 U CPU 是期望的 CPU 利用率(该值应该介于 0 和 1 之间) W/C 是等待时间与计算时间的比率

这里的 W/C 是指阻塞的时长占总时长的多少,基本总时长是阻塞时间 + 计算时间,但是这个公式计算出来的值只是一个上限,一般任务几个就有几个线程

总结:如果是计算密集型的程序,不涉及 io,那就用 stream,连并行流都不用,如果你是 io 密集型的,那就使用 CompletableFuture,更灵活,而且出了错能定位,并行流不好定位

CompletableFuture 的使用:

// 这里一定要注意使用两次 map,如果合成一个的话,就变成串行的了,执行第一个得到 Future,然后执行 join,就相当是等执行完了,然后执行第二个得到 future,然后执行 joinpublic List<String> findPrices(String product) {    List<CompletableFuture<String>> priceFutures = shops.stream()                                                        .map(shop -> CompletableFuture.supplyAsync(                                                        () -> shop.getName() + " price is " + shop.getPrice(product)))                                                    .collect(Collectors.toList());    return priceFutures.stream().map(CompletableFuture::join).collect(toList());}// 应该改成下面这样,效率更好private final Executor executor = Executors.newFixedThreadPool(Math.min(shops.size(), 100), new ThreadFactory() {    public Thread newThread(Runnable r) {        Thread t = new Thread(r);        t.setDaemon(true);        return t;    }})public List<String> findPrices(String product) {    List<CompletableFuture<String>> priceFutures = shops.stream()                                                        .map(shop -> CompletableFuture.supplyAsync(                                                        () -> shop.getName() + " price is " + shop.getPrice(product), executor))                                                    .collect(Collectors.toList());    return priceFutures.stream().map(CompletableFuture::join).collect(toList();}

CompletableFuture 的静态方法中,没有 Asycn 的就代表是同步的,不要在这种函数中执行阻塞方法,thenApply 将第一步的结果作为输入,执行函数。thenCompose 对第一个 CompletableFuture 对象调用 thenCompose ,并向其传递一个函数。当第一个CompletableFuture 执行完毕后,它的结果将作为该函数的参数,这个函数的返回值是以第一个 CompletableFuture 的返回做输入计算出的第二个 CompletableFuture 对象。

public List<String> findPrices(String product) {    List<CompletableFuture<String>> priceFutures =     shops.stream().map(shop -> CompletableFuture.supplyAsync(    () -> shop.getPrice(product), executor)    ).map(future -> future.thenApply(Quote::parse))    .map(future -> future.thenCompose(quote ->    CompletableFuture.supplyAsync(() -> Discount.applyDiscount(quote),executor))    ).collect(toList());    return priceFutures.stream().map(CompletableFuture::join).collect(toList());}

本文首发于 GitChat,未经授权不得转载,转载需与 GitChat 联系。

阅读全文: http://gitbook.cn/gitchat/activity/5d7869d6b5b7df27c661e94d

您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

FtooAtPSkEJwnW-9xkCLqSTRpBKX

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值