JDK8特性與使用手記

新特性總覽

  • lambda表達式
  • Stream操作數(shù)組
  • Optional取代null
  • 簡潔的并發(fā)編程
  • LocalDateTime新的時間API

Lambda表達式

  • 概念:Lambda表達式是一個匿名函數(shù)搁凸,Lambda表達式基于數(shù)學中的λ演算得名答渔,直接對應其中的Lambda抽象(lambda abstraction),是一個匿名函數(shù)系任,既沒有函數(shù)名的函數(shù)。Lambda表達式可以表示閉包(注意和數(shù)學傳統(tǒng)意義的不同)。你也可以理解為,簡潔的表示可傳遞的匿名函數(shù)的一種方式:它沒有名稱盈滴,但它有參數(shù)列表、函數(shù)主體轿钠、返回類型,可能還有一個可以拋出異常的列表病苗。

  • 作用:既然是匿名函數(shù)疗垛,那就類比于匿名內(nèi)部類的用法咯,哪些地方硫朦,會用到一些代碼量大但實際邏輯不算復雜的方法調用贷腕,就可以用到它。

    什么是函數(shù)式接口咬展?

    答:僅僅只有一個抽象方法的接口泽裳。

  • 語法:

    • () -> 表達式
    • () -> {語句;}
    • () -> 對象
    • (Class)() -> {語句;}【指定對象類型】
      public static void doSomething(Runnable r) { r.run(); }
      public static void doSomething(Task a) { a.execute(); }
      ....
      doSomething(() -> System.out.println("DDDD"));
      // 為了避免隱晦的方法調用,嘗試顯式地類型轉換
      doSomething((Runnable)() -> System.out.println("DDDD"));
      
  • 使用場景(很多特殊場景都包含在內(nèi))
    總的來說破婆,只有在接受函數(shù)式接口的地方才可以使用Lambda表達式涮总。


    image.png
  • 一些Jdk8的lambda語法糖:

    Lambda:(Apple a) -> a.getWeight() 
    方法引用:Apple::getWeight
    
    Lambda:() -> Thread.currentThread().dumpStack() 
    方法引用:Thread.currentThread()::dumpStack
      
    Lambda:(str, i) -> str.substring(i)
    方法引用:String::substring
      
    Lambda:(String s) -> System.out.println(s)
    方法引用:System.out::println
    
  • 構造函數(shù)引用

    • 無參構造器
      Supplier<Apple> c1 = Apple::new;
      Apple apple = c1.get();
      // 等價于
      Supplier<Apple> c2 = () -> new Apple();
      
    • 一參構造器
      Function<Integer, Apple> c1 = Apple::new;
      Apple apple = c1.apply(123);
      
    • 兩參構造器
      BiFunction<Integer, String, Apple> c1 = Apple::new;
      Apple apple = c2.apply(120, "red");
      
  • 簡化的數(shù)組排序

    apples.sort(comparing(Apple::getWeight));
    // 其中:
    // ArrayList.sort() since:1.2
    // Comparator.comparing(Function<Apple, Integer>) since: 1.8
    
  • 更復雜的數(shù)組排序

    • 倒序
      apples.sort(Comparator.comparing(Apple::getWeight).reversed());
      
    • 多條件排序
      apples.sort(Comparator.comparing(Apple::getWeight).reversed()
                  .thenComparing(Apple::getCountry));
      
  • Predicate的復合

    以下三個基本謂詞,可以配合已有的謂詞(Predicate)祷舀,來制造出更加復雜的謂詞瀑梗。

    • negate:“非”
      private static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
          List<T> result = new ArrayList<>();
          for (T t : list) {
              if (predicate.test(t)) {
                  result.add(t);
              }
          }
          return result;
      }
      // 紅蘋果
      Predicate<Apple> redApplePd = a -> "red".equals(a.getColor());
      // 不是紅蘋果
      Predicate<Apple> notRedApplePd = a -> redApplePd.negate();
      // 過濾
      List<Apple> redApple = filter(rawApples, redApplePd);
      List<Apple> notRedApple = filter(rawApples, notRedApplePd);
      
    • and:“與”
      Predicate<Apple> redHeavyApplePd = ((Predicate<Apple>) apple -> apple.color.equals("red")).and(apple -> false);
      
    • or:“或”(同理)
  • Function的復合

    • andThen
      f.andThen(g)相當于 g(f()),先執(zhí)行f()裳扯,后執(zhí)行g()
      Function<Integer, Integer> dbSelf = x -> x * 2;
      Function<Integer, Integer> oppositeNum = x -> -1 * x;
      Function<Integer, String> toStr = String::valueOf;
      String result = dbSelf.andThen(oppositeNum).andThen(toStr).apply(1); // "-2"
      
    • compose
      f.compose(g)相當于 f(g())抛丽,先執(zhí)行g(),后執(zhí)行f()
      Function<Integer, String> toStr = x -> "ToString:" + x;
      Function<String, String> split = x -> x.split(":")[1];
      Function<String, Integer> toInt = Integer::valueOf;
      int result = toInt.compose(split.compose(toStr)).apply(123); // 123
      

Stream

  • 概念:流是Java API新成員饰豺,它允許你以聲明性方式處理數(shù)據(jù)集合亿鲜。
  • 特點:
    • 流水線:類似“鏈式調用”,一步接一步冤吨。
    • 內(nèi)部迭代:與使用迭代器顯式迭代的集合不同蒿柳,流的迭代操作是在背后進行的。
  • 作用:
    • 減少for循環(huán)
    • 減少數(shù)組操作中可能聲明的垃圾變量的數(shù)量
    • 直觀锅很、提高可讀性
  • 流和集合的區(qū)別:
    • 類似于看視頻其馏,無論點到視頻的哪一段,它都能很快加載出來爆安,這就是流叛复。而集合相當于我要把整部電影down下來,才能點哪看哪。
    • 集合是內(nèi)存中的數(shù)據(jù)結構褐奥,它包含數(shù)據(jù)結構中目前所有的值咖耘,集合中每個元素都需要事先計算好,才被放入集合撬码。
    • 流是在概念上固定的數(shù)據(jù)結構儿倒,其元素時按需計算的(懶加載)。需要多少就給多少呜笑。換一個角度夫否,流像是一個延遲創(chuàng)建的集合:只有在消費者要求的時候才會生成值。
  • 看一個例子
    public List<Apple> filterApple(List<Apple> apples, Predicate<Apple> criteria) {
          List<Apple> result = new ArrayList<>();
          apples.forEach(apple -> {
              if (criteria.test(apple)) {
                  result.add(apple);
              }
          });
          return result;
      }
    
    如果只用Lambda表達式叫胁,那操作數(shù)組起來凰慈,也還是需要一些for循環(huán)的加持。
    而有了Stream驼鹅,寫起code就簡單很多了微谓。
    List<Apple> redHeavyApples = apples.stream()
      .filter(apple -> "red".equals(apple.color))
      .filter(apple -> apple.weight > 120)
      .collect(Collectors.toList());
    
  • 相關包、類输钩、方法
    • 包:java.util.stream
    • 接口:
      • BaseStream<T, S extends BaseStream<T, S>> extends AutoCloseable
      • Stream<T> extends BaseStream<T, Stream<T>>
      • DoubleStream extends BaseStream<Double, DoubleStream>
      • IntStream extends BaseStream<Integer, IntStream>
      • LongStream extends BaseStream<Long, LongStream>
    • 方法:
      • filter(Predicate<? super T>):Stream<T>
      • map(Function<? super T, ? extends R>):Stream<R>
      • mapToInt(ToIntFunction<? super T>):LongStream
      • mapToDouble(ToDoubleFunction<? super T>):DoubleStream
      • mapToLong
      • flatMap(Function<? super T, ? super Stream<? extends R>>):Stream<R>
      • flatMapToInt()
      • flatMapToLong()
      • flatMapToDouble()
      • distinct():Stream<T>
      • sorted():Stream<T>
      • sorted(Comparator<? super T>):Stream<T>
      • peek(Consumer<? super T>):Stream<T>
      • limit(long):Stream<T>
      • skip(long):Stream<T>
      • forEach(Consumer<? super T>):void
      • forEachOrdered()
      • toArray():Object[]
      • toArray(IntFunction<A[]>):Object[]
      • reduce(T, BinaryOperator<T>):T
      • reduce(BinaryOperator<T>):Optional<T>
      • reduce(U, BiFunction<U, ? super T, U>, BinaryOperator<U>):U
      • collect(Supplier<R>, BiConsumer<R, ? super T>, BiConsumer<R,R>):R
      • collect(Collector<? super T,A,R>):R
      • min(Comparator<? super T>):Optional<T>
      • max
      • count():long
      • anyMatch(Predicate<? super T>):boolean
      • allMatch
      • noneMatch
      • findFirst():Optional<T>
      • findAny
      • builder():Builder<T>
      • empty():Stream<T>
      • of(T...):Stream<T>
      • iterate(T, UnaryOperator<T>):Steram<T>
      • generate(Supplier<T>):Stream<T>
      • concat(Stream<? extends T>, Stream<? extends T>):Stream<T>
  • 例子:取重的綠蘋果豺型,然后升序排序,取它的重量
    List<Integer> greenHeavyAppleWeight = apples.stream()
          .filter(apple -> apple.weight > 120)
          .filter(apple -> "green".equals(apple.color))
          .sorted(comparing(Apple::getWeight)) // Comparator.comparing()
          .map(apple -> apple.weight)
          .collect(toList()); // Collectors.toList()
    
  • 流只能被消費一次
    List<String> names = Arrays.asList("Java8", "Lambdas", "In", "Action");
      Stream<String> s = names.stream();
      s.forEach(System.out::println);
      // 再繼續(xù)執(zhí)行一次买乃,則會拋出異常
      s.forEach(System.out::println);
    
  • flatMap()實現(xiàn)流的扁平化
    String[] words = {"Hello", "World"};
    Stream<String> streamOfWords = Arrays.stream(words);
    
    // 沒有打平姻氨,是兩個 String[],我需要兩個嵌套for 循環(huán)來打印內(nèi)容
    List<String[]> a = streamOfWords.map(w -> w.split("")).collect(toList());
    for (String[] itemStrings: a) {
        System.out.println("item.length: " + itemStrings.length);
        for (String item: itemStrings) {
            System.out.print(item);
        }
        System.out.println();
    }
    
    沒有打平剪验,出來的是兩個String[]哼绑,我需要兩個嵌套for 循環(huán)來打印內(nèi)容。而如果用flatMap:
    String[] words = {"Hello", "World"};
    Stream<String> streamOfWords = Arrays.stream(words);
    
    // 打平碉咆,一個for循環(huán)就搞定
    List<String> chars = streamOfWords
            .map(w -> w.split(""))
            .flatMap(Arrays::stream)
            .collect(toList());
    for (String item: chars) {
        System.out.print(item + "-");
    }
    

打平之后抖韩,直接操作一個數(shù)組就好。

  • 例子:求最大最小值
    List<Integer> numbers = Arrays.asList(2, 5, 3, 4, 1, 6, 3, 5);
    // way 1
    Integer max = numbers.stream().max(Integer::compareTo).orElse(null);
    // way 2
    max = numbers.stream().reduce(Integer::max).orElse(null);
    
  • 原始類型流的特化(Stream轉IntStream/LongStream/DoubleStream)

    作用:直接特化為原始類型:int疫铜、long茂浮、double,避免暗含的裝箱成本壳咕。以及席揽,有了一些額外的計算方法。

    • 映射到數(shù)值流:mapToInt/mapToLong/mapToDouble
    • 轉回對象流:boxed
  • 特化流的一下額外方法
    // 獲得 1到100 的所有偶數(shù)
    IntStream.rangeClosed(1, 100).filter(num -> num%2 == 0).forEach(System.out::println);
    
  • 構建流
    • 方式一:由值創(chuàng)建流
      Stream<String> stream = Stream.of("Java8", "Lambda", "In");
      stream.map(String::toUpperCase).forEach(System.out::println);
      
    • 方式二:由數(shù)組創(chuàng)建流
      int[] nums = {2,4,6,7,8,12};
      int sum = Arrays.stream(nums).sum();
      
    • 方式三:由集合創(chuàng)建流
      List<Integer> nums = Arrays.asList(1,2,3,4,5);
      Stream<Integer> numStream = nums.stream();
      Stream<Integer> parallelStream = nums.parallelStream();
      
    • 方式四:文件 + NIO 創(chuàng)建流

      利用 java.nio.file.Files中的一些靜態(tài)方法(靜態(tài)方法 since JDK1.8)都返回一個流谓厘。Filessince JDK1.7

      一個很有用的方法是 Files.lines 幌羞,它會返回一個由指定文件中的各行構成的字符串流。
      long uniqueWords; try (Stream<String> lines = Files.lines(Paths.get(ClassLoader.getSystemResource("data.txt").toURI()), Charset.defaultCharset())) { uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" "))) .distinct() .count(); System.out.println("uniqueWords:" + uniqueWords); } catch (IOException e) { e.fillInStackTrace(); } catch (URISyntaxException e) { e.printStackTrace(); }

    • 方式五:由函數(shù)生成流
      以下兩個操作竟稳,可以創(chuàng)建“無限流”属桦,一般配合limit 使用
      Stream.iterate(<初始值>, <值的變化函數(shù)>)
      Stream.generate(Supplier<? extends Object> s)
      
      不像從固定集合創(chuàng)建的流那樣有固定大小的流熊痴。由 iterate和 generate 產(chǎn)生的流會用給定的函數(shù)按需創(chuàng)建值,因此可以無窮無盡地計算下去聂宾。
      // 迭代:每次返回前一個元素加2的值
      Stream.iterate(0, n -> n + 2)
          .limit(10)
          .forEach(System.out::println);
      
      // 生成:接收一個Supplier類型的函數(shù)(有出無入的函數(shù))
      Stream.generate(Math::random)
          .limit(5)
          .forEach(System.out::println);
      
      看下面這個例子果善,對比Lambda表達式(匿名函數(shù))和匿名內(nèi)部類:
      // lambda
      IntStream twos = IntStream.generate(() -> 2);
      // 匿名內(nèi)部類
      IntStream twos = IntStream.generate(new IntSupplier() {
          @Override
          public int getAsInt() {
              return 2;
          }
      });
      
      如果這個IntSupplier中不存在成員變量,那么系谐,兩者等價巾陕。總的來說纪他,匿名內(nèi)部類更加靈活鄙煤,而且其output值不一定唯一不變,較為靈活。
  • 收集器的用法
    • 接口:java.util.stream.Collector
    • 作用:對Stream的處理結果做收集。
    • 例子:
      • 分組

        List<Apple> apples = Arrays.asList(
              new Apple(130, "red"),
              new Apple(22, "red"),
              new Apple(60, "green"),
              new Apple(162, "green"),
              new Apple(126, "green"),
              new Apple(142, "green"),
              new Apple(117, "green")
        );
        // 根據(jù)顏色分組
        Map<String, List<Apple>> colorAppleMap = apples.stream().collect(groupingBy(apple -> apple.color));
        
      • 多級分組

        Map<Dish.Type, Map<Dish.CaloricLevel, List<Dish>>> dishesByTypeCaloricLevel =
        menu.stream().collect(
                groupingBy(Dish::getType,
                        groupingBy(dish -> {
                            if (dish.getCalories() <= 400) {
                                return Dish.CaloricLevel.DIET;
                            } else if (dish.getCalories() <= 700) {
                                return Dish.CaloricLevel.NORMAL;
                            } else {
                                return Dish.CaloricLevel.FAT;
                            }
                        })
                )
        );
        
      • 分組的一些配合操作

        Stream.collect(groupingBy(Dish::getType, summingInt(Dish::getCalories)));
        Stream.collect(groupingBy(Dish::getType, mapping(...)));
        // 如:
        Map<Dish.Type, Set<Dish.CaloricLevel>> caloricLevelsByType =
        menu.stream().collect(
             groupingBy(Dish::getType, mapping(
                  dish -> {
                      if (dish.getCalories() <= 400) {
                          return Dish.CaloricLevel.DIET;
                      } else if (dish.getCalories() <= 700) {
                          return Dish.CaloricLevel.NORMAL;
                      } else {
                          return Dish.CaloricLevel.FAT;
                      }
                  },
                  toSet())));
        
      • 計數(shù)

        long count = apples.size();
        long count = apples.stream().collect(Collectors.counting());
        long count = apples.stream().count();
        
      • 匯總

        // 求和
        int totalWeight = apples.stream().collect(summingInt(Apple::getWeight));
        totalWeight = apples.stream().mapToInt(Apple::getWeight).sum();
        
        // 平均數(shù)
        double avgWeight = apples.stream().collect(averagingDouble(Apple::getWeight));
        avgWeight = apples.stream().mapToDouble(Apple::getWeight).average().orElse(-1);
        
        // 匯總
        IntSummaryStatistics appleStatistics = apples.stream().collect(summarizingInt(Apple::getWeight));
        System.out.println(appleStatistics.getMax());
        System.out.println(appleStatistics.getMin());
        System.out.println(appleStatistics.getAverage());
        System.out.println(appleStatistics.getCount());
        System.out.println(appleStatistics.getSum());
        
      • 連接字符串

        String[] strs = {"Hello", "World"};
        String result = Arrays.stream(strs).collect(joining([分隔符]));
        
      • 分區(qū)

        根據(jù)Predicate條件亩码,分成true和false兩部分集合。

        Map<Boolean, List<Apple>> partitionApples = apples.stream().collect(partitioningBy(apple -> "green".equals(apple.color)));
        

        看似沒有什么特點,但是其實和grouping類似句喜,有一個downStream:Collector的一個第二參數(shù)预愤,這就厲害了,擴展性很強咳胃。

        // 先劃分了素食和非素食植康,然后,每一類里面销睁,去熱量最高的一個冻记。
        Map<Boolean, Dish> mostCaloricPartitionedByVegetarian = menu.stream().collect(
          partitioningBy(Dish::isVegetarian, collectingAndThen(
                  maxBy(comparingInt(Dish::getCalories)),
                  Optional::get
          )));
        
  • 自定義流(這個就再說咯)
  • 并行流

    相對于stream()来惧,用parallelStream()就能把集合轉換為并行流冗栗。

    • 概念:并行流就是一個把內(nèi)容分成多個數(shù)據(jù)塊,并用不同線程分別處理每個數(shù)據(jù)塊的流供搀。
    • 方法:
      切換為并行流:Stream.parallel()
      切換為順序流:Stream.sequential()
      // 注意隅居,誰最后調用,流就apply誰葛虐。
      
    • 用并行流之前胎源,要測試性能(如果遇到iterator裝包解包等的情況,實際上并行鎖會更加慢屿脐,能用特化流就盡量用特化流)
    • Spliterator 定義了并行流如何拆分它要遍歷的數(shù)據(jù)

Optional

  • class:java.util.Optional
  • 作用:解決和避免NPE異常
  • Optional對象的方法
    • get():不推薦使用涕蚤。如果變量存在宪卿,它直接返回封裝的變量值,否則就拋出一個NPE或者NoSuchElementException異常赞季。
      Optional方法 調用get()后拋出異常
      Optional.of(null) java.lang.NullPointerException
      Optional.ofNullable(null) java.util.NoSuchElementException
    • orElse(T other)
    • orElseGet(Supplier<? extends T> other)orElse的延遲調用版愧捕,Supplier方法只有在Optional對象不含值時才執(zhí)行。
      • 適用場景:
        • 創(chuàng)建默認值是耗時的工作申钩。
        • 或者需要十分確定某個方法僅在Optional為空時才調用次绘。
      Person p1 = null;
      Optional<Person> optP1 = Optional.ofNullable(p1);
      Person resultP = optP1.orElseGet(() -> {
          Person p = new Person();
          p.firstName = "Fang";
          return p;
      });
      System.out.println("resultP.firstName: " + resultP.firstName);
      
    • orElseThrow(Supplier<? extends X> exceptionSupplier):定制拋出的異常邮偎。
    • ifPresent(Consumer<? super T>):當變量值存在時執(zhí)行一個作為參數(shù)傳入的方法,否則不做任何操作泻云。
      Person p1 = new Person();
      Optional<Person> optP1 = Optional.ofNullable(p1);
      optP1.ifPresent(person -> System.out.println("Haha"));
      
    • filter
      Person p1 = new Person();
      p1.firstName="Fang";
      p1.lastName="Hua";
      Person resP = Optional.ofNullable(p1).filter(person -> "Hua".equals(person.lastName)).orElseGet(() -> {
          Person newP = new Person();
          newP.firstName= "Ming";
          return newP;
      });
      System.out.println(resP.firstName); // Ming
      
    • 當然层释,還有一些方法與Stream接口相似廉白,如mapflatMap
  • 例子:
    • 對象嵌套取值
      • old
        @Test
        public void test_optional_1() {
            Person person = new Person();
            // 當然從重構角度來看楣嘁,這里是不對的瓢省,我們知道太多這個類內(nèi)部的東西,是需要重構的
            String name = person.getCar().getInsurance().getName();
        }
        public class Person {
            private Car car;
            public Car getCar() { return car; }
        }
        public class Car {
            private Insurance insurance;
            public Insurance getInsurance() { return insurance; }
        }
        public class Insurance {
            private String name;
            public String getName() { return name; }
        }
        
      • new
        public String getCarInsuranceName(Person person) {
            return Optional.ofNullable(person).flatMap(Person::getCar)
                    .flatMap(Car::getInsurance)
                    .map(Insurance::getName)
                    .orElse("Unknown");
        }
        
        public class Person {
            private String sex;
            private String firstName;
            private String lastName;
            private Optional<Car> car = Optional.empty();
        
            public Optional<Car> getCar() {
                return car;
            }
        }
        
        public class Car {
            private Optional<Insurance> insurance = Optional.empty();
        
            public Optional<Insurance> getInsurance() {
                return insurance;
            }
        }
        
        public class Insurance {
            private String name;
        
            public String getName() {
                return name;
            }
        }
        
    • 封裝可能為空的值
      Object value = map.get("key");
      // 加上 Optional
      Optional<Object> value = Optional.ofNullable(map.get("key"));
      
      // 如: Map<String, Person> map
      String valA = Optional.ofNullable(map.get("A")).orElse(new Person()).firstName;
      
    • 異常與Optional 去替代 if-else判斷
      public static Optional<Integer> stringToInt(String s) {
          try {
              return Optional.of(Integer.parseInt(s));
          } catch (NumberFormatException e) {
              return Optional.empty();
          }
      }
      
    • 兩個Optional對象的組合
      public Insurance findBestInsurance(Person person, Car car) {
          Insurance insurance = new Insurance();
          insurance.name = person.firstName + person.lastName + " --insurance 01";
          return insurance;
      }
      
      public Optional<Insurance> nullSafeFindBestInsurance(Optional<Person> person) {
          if (person.isPresent() && person.get().getCar().isPresent()) {
              Car car = person.get().getCar().get();
              return Optional.of(findBestInsurance(person.get(), car));
          } else {
              return Optional.empty();
          }
      }
      
  • 原理:
    • 變量存在時,Optional類只是對類簡單封裝睦尽。
    • 變量不存在時,缺失的值會被建模成一個“空”的Optional對象,由方法Optional.empty()返回朴则。
    • Optional.empty()是一個靜態(tài)工廠方法,

函數(shù)式編程(Stream+Lambda)

函數(shù)式編程 VS 命令式編程

  • 命令式編程關注怎么做,而函數(shù)式編程關注做什么
  • 函數(shù)式編程 可讀性強,但運行速度不見得更快席镀。

例子

1. for循環(huán)取數(shù)組最小值

int[] nums = {1,3,-1,6,-20};
int min = Integer.MAX_VALUE;
for (int i:nums) {
    if(i < min) {
        min = i;
    }
}

變成

int min2 = IntStream.of(nums).parallel().min().getAsInt();

2. 接口的實現(xiàn)/匿名內(nèi)部類轉Lambda

/// 接口實現(xiàn)
Object target = new Runnable() {
    @Override
    public void run() {
        System.out.println("新建一個線程");
    }
};
new Thread((Runnable) target).start();

/// 匿名內(nèi)部類
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("BBB");
    }
}).start();

變成

Object target2 = (Runnable)() -> System.out.println("新建一個線程2");
Runnable target3 = () -> System.out.println("新建一個線程3");
System.out.println("target2 == target3 :" + (target2 == target3)); // false
new Thread((Runnable) target2).start();

new Thread(() -> System.out.println("BBB")).start()

3. Lambda創(chuàng)建自定義接口的實例對象

必備條件:

  1. 該接口中只能有一個抽象方法
  2. 在接口上加上@FunctionalInterface注解(可選:為了編譯器的校驗屎篱,有這個注解的接口,當存在多個抽象方法時秦士,是會編譯報錯的提针。)

JDK8 中,接口中可以定義靜態(tài)方法和默認方法。

@FunctionalInterface
interface Interface1 {
    int doubleNum(int i);

    default int add(int x, int y) {
        return x + y;
    }

    static int sub(int x, int y) {
        return x - y;
    }
}

@FunctionalInterface
interface Interface2 {
    int doubleNum(int i);

    default int add(int x, int y) {
        return x + y;
    }
}

@FunctionalInterface
interface Interface3 extends Interface1, Interface2 {
    @Override
    default int add(int x, int y) {
        return Interface1.super.add(x, y);
    }
}


@Test
public void test_lambda_1() {
    Interface1 i1 = (i) -> i * 2;
    System.out.println("Interface1.sub(10, 3): " + Interface1.sub(10, 3));
    System.out.println("i1.add(3炭剪,7):" + i1.add(3, 7));
    System.out.println("i1.doubleNum(20):" + i1.doubleNum(20));

    Interface2 i2 = i -> i * 2;
    Interface3 i3 = (int i) -> i * 2;
    Interface3 i4 = (int i) -> {
        System.out.println(".....");
        return i * 2;
    };
}

4. Lambda與Function

String cityName= "HongKong";
int stateCode=237;
String street = "東岸村黃皮樹下街1號";

String locationID = "";

Function<String, String> locationIDBuilder = locId -> locId + cityName; // Step 1
locationID = locationIDBuilder
        .andThen(locId -> locId + "错妖,區(qū)號:" + stateCode) // Step 2
        .andThen(locId -> locId+"暂氯,街道:" + street).apply(locationID); // Step 3
System.out.println("locationID:" + locationID);

JDK 1.8 API包含了很多內(nèi)建的函數(shù)式接口究流,在老Java中常用到的比如Comparator或者Runnable接口神得,這些接口都增加了@FunctionalInterface注解以便能用在lambda上

name type description
Consumer Consumer< T > 接收T對象,不返回值
Predicate Predicate< T > 接收T對象并返回boolean
Function Function< T, R > 接收T對象,返回R對象
Supplier Supplier< T > 提供T對象(例如工廠)全跨,不接收值
UnaryOperator UnaryOperator 接收T對象渺杉,返回T對象
BinaryOperator BinaryOperator 接收兩個T對象,返回T對象

Lambda 與 設計模式

策略模式

interface ValidationStrategy {
    boolean execute(String s);
}

static class IsAllLowerCase implements ValidationStrategy {
    @Override
    public boolean execute(String s) {
        return s.matches("[a-z]+");
    }
}

static class IsNumeric implements ValidationStrategy {
    @Override
    public boolean execute(String s) {
        return s.matches("\\d+");
    }
}

static class Validator {
    private final ValidationStrategy validationStrategy;

    public Validator(ValidationStrategy validationStrategy) {
        this.validationStrategy = validationStrategy;
    }

    public boolean validate(String s) {
        return validationStrategy.execute(s);
    }
}

正常來說,要new一些策略來當參數(shù)傳天梧。

IsNumeric isNumeric = new IsNumeric();
IsAllLowerCase isAllLowerCase = new IsAllLowerCase();

Validator validatorA = new Validator(isNumeric);
Validator validatorB = new Validator(isAllLowerCase);

使用lambda蛹尝,讓new盡量少出現(xiàn)在code中挫酿。

Validator validatorA = new Validator(s -> s.matches("\\d+"));
Validator validatorB = new Validator(s -> s.matches("[a-z]+"));

模板模式(抽象類的應用)

public abstract class AbstractOnlineBank {
    public void processCustomer(int id) {
        Customer customer = Database.getCustomerWithId(id);
        makeCustomerHappy(customer);
    }

    abstract void makeCustomerHappy(Customer customer);

    static class Customer {}

    static class Database {
        static Customer getCustomerWithId(int id) {
            return new Customer();
        }
    }
}
...
AbstractOnlineBank bank = new AbstractOnlineBank() {
    @Override
    void makeCustomerHappy(Customer customer) {
        System.out.println("Hello!");
    }
};
bank.processCustomer(1);
bank.processCustomer(2);

用了Lambda葱弟,抽象方法都用不著了

public class AbstractOnlineBank {
    public void processCustomer(int id, Consumer<Customer> makeCustomerHappy) {
        Customer customer = Database.getCustomerWithId(id);
        makeCustomerHappy.accept(customer);
    }

    static class Customer {}

    static class Database {
        static Customer getCustomerWithId(int id) {
            return new Customer();
        }
    }
}
...
AbstractOnlineBank bank = new AbstractOnlineBank();
bank.processCustomer(1, customer -> System.out.println("Hello"));
bank.processCustomer(2, customer -> System.out.println("Hi"));

觀察者模式

interface Observer{
        void inform(String tweet);
    }

private static class NYTimes implements Observer {

    @Override
    public void inform(String tweet) {
        if (tweet != null && tweet.contains("money")) {
            System.out.println("Breaking news in NY!" + tweet);
        }
    }
}

private static class Guardian implements Observer {

    @Override
    public void inform(String tweet) {
        if (tweet != null && tweet.contains("queen")) {
            System.out.println("Yet another news in London... " + tweet);
        }
    }
}

private static class LeMonde implements Observer {

    @Override
    public void inform(String tweet) {
        if(tweet != null && tweet.contains("wine")){
            System.out.println("Today cheese, wine and news! " + tweet);
        }
    }
}

interface Subject {
    void registerObserver(Observer o);

    void notifyObserver(String tweet);
}

private static class Feed implements Subject {
    private final List<Observer> observers = new ArrayList<>();

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void notifyObserver(String tweet) {
        observers.forEach(o -> o.inform(tweet));
    }
}

好的居触,看到了有一個觀察者接口轮洋,并且只有一個方法inform开财,那么,我們是不是就可以少聲明這幾個實現(xiàn)類呢?

Feed feedLambda = new Feed();
feedLambda.registerObserver((String tweet) -> {
    if (tweet != null && tweet.contains("money")) {
        System.out.println("Breaking news in NY!" + tweet);
    }
});

feedLambda.registerObserver((String tweet) -> {
    if (tweet != null && tweet.contains("queen")) {
        System.out.println("Yet another news in London... " + tweet);
    }
});

feedLambda.notifyObserver("Money money money, give me money!");

責任鏈模式

private static abstract class AbstractProcessingObject<T> {
    protected AbstractProcessingObject<T> successor;

    public void setSuccessor(AbstractProcessingObject<T> successor) {
        this.successor = successor;
    }

    public T handle(T input) {
        T r = handleWork(input);
        if (successor != null) {
            return successor.handle(r);
        }
        return r;
    }

    protected abstract T handleWork(T input);
}

一看到,又是抽象類加不同的實現(xiàn)帜羊,那就想到是不是可以用匿名函數(shù)實現(xiàn),而這里窥淆,我們使用UnaryOperator

// 流程A
UnaryOperator<String> headerProcessing = (String text) -> "From Raoul, Mario and Alan: " + text;
// 流程B
UnaryOperator<String> spellCheckerProcessing = (String text) -> text.replaceAll("labda", "lambda");
// A --> B
Function<String, String> pipeline = headerProcessing.andThen(spellCheckerProcessing);
String result2 = pipeline.apply("Aren't labdas really sexy?!!");

工廠模式

private interface Product {
}

private static class ProductFactory {
    public static Product createProduct(String name) {
        switch (name) {
            case "loan":
                return new Loan();
            case "stock":
                return new Stock();
            case "bond":
                return new Bond();
            default:
                throw new RuntimeException("No such product " + name);
        }
    }
}

static private class Loan implements Product {
}

static private class Stock implements Product {
}

static private class Bond implements Product {
}

簡單情況下筷畦,可以用Supplier去實現(xiàn)引用方法式的構造器調用吼砂,并且減少switch。

private static class ProductFactory {
    private static final Map<String, Supplier<Product>> map = new HashMap<>();

    static {
        map.put("loan", Loan::new);
        map.put("stock", Stock::new);
        map.put("bond", Bond::new);
    }

    public static Product createProduct(String name) {
        Supplier<Product> productSupplier = map.get(name);
        if (productSupplier != null) {
            return productSupplier.get();
        }

        throw new RuntimeException("No such product " + name);
    }
}

當然,如果工廠方法 createProduct 需要接收多個傳遞給產(chǎn)品構造方法的參數(shù)蓉坎,這種方式的擴展性不是很好。

默認方法

Jdk8開始支持的東西

Jdk8中的接口支持在聲明方法的同時提供實現(xiàn)拓瞪。

  • Jdk8支持接口中有靜態(tài)方法弓柱。
    interface IBoy {
          static String getCompany() {
              return "OOL";
          }
      }
    
  • Jdk8支持接口中有默認方法航罗≈嘌【jdk8 新功能】
    // List.sort()
    default void sort(Comparator<? super E> c) {
          Object[] a = this.toArray();
          Arrays.sort(a, (Comparator) c);
          ListIterator<E> i = this.listIterator();
          for (Object e : a) {
              i.next();
              i.set((E) e);
          }
      }
    // Collection.stream()
    default Stream<E> stream() {
          return StreamSupport.stream(spliterator(), false);
      }
    

作用

  • 為接口定義默認的方法實現(xiàn):默認方法提供了接口的這個方法的默認實現(xiàn)缭嫡,那么,在使用Lambda匿名地構造接口實現(xiàn)時眷茁,就不需要顯示重寫接口的這個方法,默認方法自動就會繼承過來登刺。
  • 新增的接口晤锥,只要加上default修飾符,就可以不被顯式繼承蛉迹,因此,別的地方的代碼是不用改動的!攘宙!

    看個例子:jdk8以前的Iterator接口线召,實際上用戶是不在乎remove()方法的祟蚀,而且當時是沒有forEachRemaining()方法的,那么,當時的做法丙挽,就是每次實現(xiàn)這個接口的時候肺孵,都需要顯式繼承并重寫remove()方法,很煩颜阐。

    到了jdk8平窘, 有了default修飾符,那么凳怨,我們不想顯式重寫的remove()方法就可以不用重寫了均蜜,然后匪蟀,jdk8新增了forEachRemaining()方法穗泵,也不需要其他實現(xiàn)類再去修改code了,因為它壓根不需要你顯式重寫。

解決繼承鏈上的沖突的規(guī)則

public interface A {
    default void hello() {
        System.out.println("Hello from A");
    }
}
public interface B extends A {
    default void hello() {
        System.out.println("Hello from B");
    }
}
public class C implements A, B {
    public static void main(String[] args) {
        // 猜猜打印的是什么蕴潦?
        new C().hello();
    }
}

以上問題秀菱,就是菱形繼承問題琼锋,如果父子接口有同名的default方法,那么董栽,以上代碼編譯不通過歹撒。
我們需要重寫這個方法:

public class C implements A, B {
    public static void main(String[] args) {
        new C().hello();
    }

    @Override
    public void hello() {
       A.super.hello();
    }

    OR

    @Override
    public void hello() {
       B.super.hello();
    }

    OR

    @Override
    public void hello() {
       System.out.println("Hello from C!");
    }
}

組合式異步編程

  • 術語: CompletableFuture
  • 背景:
    • Jdk 7 中引入了“并行/合并框架”

      注意:并行和并發(fā)的區(qū)別:

      • 并行(parallellism):
        • 并行指的是同一個時刻闭专,多個任務確實真的在同時運行雀扶。多個任務不搶對方資源迫吐。
        • 例子:兩個人,各自一邊吃水果诞吱,吃完就吃pizza命黔〗骼【有多個CPU內(nèi)核時,每個內(nèi)核各自不占對方的資源,各做各的事,是可以達到真正意義上的“同時”的∑蟛洌】
      • 并發(fā)(concurrency):
        • 并發(fā)是指在一段時間內(nèi)宏觀上多個程序同時運行它呀。多個任務互相搶資源。
        • 例子:一個人叉跛,同時做多件事情赫冬∨ň担【單核計算器中,是不可能“同時”做兩件事的劲厌,只是時間片在進程間的切換很快膛薛,我們感覺不到而已〔贡牵】
    • Jdk 8 中引入了并行流
    • Jdk 8 中改進了Future接口哄啄,并且,新增了CompletableFuture接口风范。
    • 如果我們現(xiàn)在有一個大的耗時任務要處理咨跌,我們可以將其拆分為多個小任務,讓其并行處理硼婿,最終再將處理的結果統(tǒng)計合并起來锌半, 那么,我們可以結合 并行/合并框架 + 并行流 來快速實現(xiàn)寇漫。

RecursiveTask(JDK 1.7)

例子:實現(xiàn)一個100000個自然數(shù)的求和刊殉。

class SumTask extends RecursiveTask<Long> {
public static final int Flag = 50;
long[] arr;
int start;
int end;

public SumTask(long[] arr, int start, int end) {
    this.arr = arr;
    this.start = start;
    this.end = end;
}

public SumTask(long[] arr) {
    this.arr = arr;
    this.start = 0;
    this.end = arr.length;
}

@Override
protected Long compute() {
    // 如果不能進行更小粒度的任務分配
    int length = end - start;
    if (length <= Flag) {
        return processSequentially();
    }
    //分治
    int middle = (start + end) / 2;
    SumTask sumTaskOne = new SumTask(arr, start, middle);
    SumTask sumTaskTwo = new SumTask(arr, middle, end);
    invokeAll(sumTaskOne, sumTaskTwo);
    Long join1 = sumTaskOne.join();
    Long join2 = sumTaskTwo.join();
    return join1 + join2;
}

// 小任務具體是做什么
private long processSequentially() {
    long sum = 0;
    for (int i = start; i < end; i++) {
        sum += arr[i];
    }
    return sum;
}
}

@Test
public void test() {
  long[] arr = new long[1000];
  for (int i = 0; i < arr.length; i++) {
      arr[i] = (long) (Math.random() * 10 + 1);
  }
  // 線程池(since 1.7)
  ForkJoinPool forkJoinPool = new ForkJoinPool(5);
  ForkJoinTask<Long> forkJoinTask = new SumTask(arr);
  long result = forkJoinPool.invoke(forkJoinTask);
  System.out.println(result);
}
  • 總結: 分支/合并框架讓你得以用遞歸方式將可以并行的任務拆分成更小的任務,在不同的線程上執(zhí)行猪腕,然后將各個子任務的結果合并起來生成整體結果冗澈。

Spliterator(可分迭代器 JDK 1.8)

(一)背景

  • jdk 1.8 加入,和Iterator一樣陋葡,也用于遍歷數(shù)據(jù)源元素亚亲,但它是為了并行執(zhí)行而設計的。
  • jdk8已經(jīng)為集合框架中的所有數(shù)據(jù)結構提供了一個默認的Spliterator實現(xiàn)。
  • 目的:
    • 為了優(yōu)化在并行流做任務處理時的數(shù)據(jù)源拆分遍歷時捌归,使用Iterator的裝包和解包的性能開銷肛响。
    • 配合并行流更快地遍歷和處理元素。
  • 內(nèi)部方法:
    public interface Spliterator<T> {
      // 如果還有元素要遍歷惜索, 返回true
      boolean tryAdvance(Consumer<? super T> action);
      // 把一些元素拆分給第二個Spliterator,不斷對 Spliterator 調用 trySplit直到它返回 null 特笋,表明它處理的數(shù)據(jù)結構不能再分割
      Spliterator<T> trySplit();
      // 估計剩下多少元素要遍歷
      long estimateSize();
      // 用于影響開分過程的配置參數(shù)
      int characteristics();
    }
    
  • 延遲綁定的Spliterator:Spliterator可以在第一次遍歷、第一次拆分或第一次查詢估計大小時綁定元素的數(shù)據(jù)源巾兆,而不是在創(chuàng)建時就綁定猎物。這種情況下,它稱為延遲綁定(late-binding)的 Spliterator 角塑。

(二)例子

例子:一個自定義的并行迭代器蔫磨,用于處理單詞數(shù)量統(tǒng)計

// 首先,我們有一個英文句子圃伶,我們要統(tǒng)計它的單詞數(shù)量
public static final String SENTENCE =
        " Nel   mezzo del cammin  di nostra  vita " +
                "mi  ritrovai in una  selva oscura" +
                " che la  dritta via era   smarrita ";
  • 方法一:通常手段堤如,寫一個方法,直接實現(xiàn)統(tǒng)計邏輯
    public static int countWords1(String s) {
          int counter = 0;
          boolean lastSpace = true;
          for (char c : s.toCharArray()) {
              if (Character.isWhitespace(c)) {
                  lastSpace = true;
              } else {
                  if (lastSpace) {
                      counter ++;
                  }
                  lastSpace = Character.isWhitespace(c);
              }
          }
          return counter;
      }
    
  • 方法二:利用jdk8 stream的函數(shù)聲明式來簡化代碼窒朋,但是要實現(xiàn)輔助對象了
    private static class WordCounter {
          private final int counter;
          private final boolean lastSpace;
    
          public WordCounter(int counter, boolean lastSpace) {
              this.counter = counter;
              this.lastSpace = lastSpace;
          }
    
          // 如何改變WordCounter的屬性狀態(tài)
          public WordCounter accumulate(Character c) {
              if (Character.isWhitespace(c)) {
                  return lastSpace ? this : new WordCounter(counter, true);
              } else {
                  return lastSpace ? new WordCounter(counter + 1, false):this;
              }
          }
    
          // 調用此方法時搀罢,會把兩個子counter的部分結果進行匯總。
          // 其實就是 把內(nèi)部計數(shù)器相加
          public WordCounter combine(WordCounter wordCounter) {
              return new WordCounter(counter + wordCounter.counter, wordCounter.lastSpace);
          }
    
          public int getCounter() {
              return counter;
          }
      }
    
    實現(xiàn)了輔助對象后侥猩,實現(xiàn)一個傳stream處理的方法榔至,去調用
    public static int countWords2(Stream<Character> stream) {
          WordCounter wordCounter = stream.reduce(new WordCounter(0, true),WordCounter::accumulate, WordCounter::combine);
          return wordCounter.getCounter();
      }
    
    用以上這種方式,就只能實現(xiàn)串行的處理
    @Test
    public void test_2() {
         Stream<Character> stream = IntStream.range(0, SENTENCE.length())
                .mapToObj(SENTENCE::charAt);
        System.out.println("Found " + countWords2(stream) + " words");
    }
    
  • 方法三:使用jdk8中的可分迭代器拭宁,模擬jdk7的拆分/合并模式去實現(xiàn)并行迭代處理過程:
    public class WordCounterSpliterator implements Spliterator<Character> {
          private final String string;
          private int currentChar = 0;
      
          public WordCounterSpliterator(String string) {
              this.string = string;
          }
      
          /**
           * 把String中當前位置的char 傳給 Consumer洛退,并讓其位置+1瓣俯,
           * 作為參數(shù)傳遞的Consumer是一個java內(nèi)部類杰标,在遍歷流時將要處理的char傳給一系列要對其執(zhí)行的函數(shù)。
           * 這里只有一個歸約函數(shù)彩匕,即 WordCounter 類的 accumulate方法腔剂。
           * 如果新的指針位置小于 String 的總長,且還有要遍歷的 Character 驼仪,則tryAdvance 返回 true 掸犬。
           */
          @Override
          public boolean tryAdvance(Consumer<? super Character> action) {
              action.accept(string.charAt(currentChar++));
              return currentChar < string.length();
          }
      
          @Override
          public Spliterator<Character> trySplit() {
              // 像 RecursiveTask 的 compute 方法一樣(分支/合并框架的使用方式)
      
              int currentSize = string.length() - currentChar;
      
              // 定義不再拆分的界限(不斷拆分,直到返回null)
              if (currentSize < 10) {
                  return null;
              }
              for (int splitPos = currentSize / 2 + currentChar;
                   splitPos < string.length(); splitPos++) {
                  if (Character.isWhitespace(string.charAt(splitPos))) {
                      // 類似RecursiveTask那樣绪爸,遞歸拆分
                      Spliterator<Character> spliterator =
                              new WordCounterSpliterator(string.substring(currentChar,
                                      splitPos));
                      currentChar = splitPos;
                      return spliterator;
                  }
              }
              return null;
          }
      
          @Override
          public long estimateSize() {
              return string.length() - currentChar;
          }
      
          @Override
          public int characteristics() {
              return ORDERED + SIZED + SUBSIZED + NONNULL + IMMUTABLE;
          }
    }
    
    有了自定義的可分迭代器湾碎,我們就可以用并行的處理方式了:
    @Test
    public void test_3() {
          Spliterator<Character> spliterator = new WordCounterSpliterator(SENTENCE);
          Stream<Character> stream = StreamSupport.stream(spliterator, true);
          System.out.println("Found " + countWords2(stream.parallel()) + " words");
    }
    

CompletableFuture(Jdk 1.8)

  • 翻譯:可完備的Future
  • 簡單來說,就是寫法更靈活奠货、code可讀性更好的Future介褥。
  • 是除了并行流之外的另一種并行方式,只是使用場景不同。

(一)一個商店商品報價的例子

普通的方法去寫一個同步的計算報價的方法:

private static class Shop {
    private final String name;
    private final Random random;

    public Shop(String name) {
        this.name = name;
        random = new Random(name.charAt(0) * name.charAt(1) * name.charAt(2));
    }

    public double getPrice(String product) {
        return calculatePrice(product);
    }
    
    private double calculatePrice(String product) {
        delay(1000);
        return random.nextDouble()*product.charAt(0) + product.charAt(1);
    }
}
......
@Test
public void test_1() {
    Shop nike = new Shop("nike");
    Shop adidas = new Shop("adidas");
    System.out.println("nike Kobe1 price : "+ nike.getPrice("Kobe1"));
    System.out.println("nike Rose3 price : "+ adidas.getPrice("Rose3"));
}

我們先用CompletableFuture來讓計算變成異步柔滔。

  • 首先溢陪,用一個線程去執(zhí)行對應邏輯,并且返回一個CompletableFuture實例:
    private static class Shop {
         ............
          public Future<Double> getPriceAsync(String product) {
              CompletableFuture<Double> futurePrice = new CompletableFuture<>();
              new Thread(() -> {
                  double price = calculatePrice(product);
                  futurePrice.complete(price);
              }).start();
              return futurePrice;
          }
          ............
    }
    
  • 好睛廊,然后我們在執(zhí)行此方法時形真,就實現(xiàn)了異步。
    @Test
    public void test_2() throws ExecutionException, InterruptedException {
          Shop nike = new Shop("nike");
          Shop adidas = new Shop("adidas");
          Future<Double> price1 = nike.getPriceAsync("Kobe1");
          Future<Double> price2 = adidas.getPriceAsync("Kobe1");
          System.out.printf("nike Kobe1 price :%.2f%n",price1.get());
          System.out.printf("nike Kobe1 price :%.2f%n", price2.get());
    }
    

(二)Future的局限性

看到上面超全,我們發(fā)現(xiàn)咆霜,其實,和用Future去handle結果返回嘶朱,好像差不多裕便。

  • 我們可以用Future.get(timeout, TimeUnit) 來防止一直拿不到值而等待的情況。
  • 可以用Future.isDone()來判斷當前任務是否跑完见咒,然后做不同的handle邏輯偿衰。
@Test
public void test_1() throws InterruptedException, ExecutionException {
    ExecutorService executor = Executors.newCachedThreadPool();
    Future<Double> future = executor.submit(this::doSthAsync);
    System.out.println("好,任務起來了改览,我去干別的先了");
    for (int i = 0; i < 2; i++) {
        System.out.println("主線程正在干活下翎。。");
        Thread.sleep(500L);
    }
    try {
        System.out.println("異步任務返回了: " + future.get(2, TimeUnit.SECONDS));
    } catch (TimeoutException e) {
        System.out.println("異步任務出了異常1Φ薄J邮隆!");
        e.printStackTrace();
    }
}

這里庆揩,就要說一下Future局限性了俐东。

  1. 我們很難表述Future結果之間的依賴性。比如這樣一個案例:“當長時間計算任務完成時订晌,請將該計算的結果通知到另一個長時間運行的計算任務虏辫,這兩個計算任務都完成后,將計算的結果與另一個查詢操作結果合并”锈拨。
  2. 以下場景砌庄,Future都難以表述:
    • 將兩個異步計算合并為一個——這兩個異步計算之間相互獨立,同時第二個又依賴于第一個的結果奕枢。
    • 等待Future集合中的所有任務都完成娄昆。
    • 僅等待Future集合中最快結束的任務完成(有可能因為它們試圖通過不同的方式計算同一個值),并返回它的結果缝彬。
    • 通過編程方式完成一個Future任務的執(zhí)行(即以手工設定異步操作結果的方式)萌焰。
    • 應對Future的完成事件(即當Future的完成事件發(fā)生時會收到通知,并能使用Future計算的結果進行下一步的操作谷浅,不只是簡單地阻塞等待操作的結果)扒俯。

(三)CompetableFuture與Future間的關系

CompetableFuture之于Future族购,相當于Stream之于Collection

(四)CompetableFuture的一些用法

  • CompetableFuture的一些靜態(tài)方法陵珍,直接簡化創(chuàng)建Thread的邏輯:

    public Future<Double> getPriceAsync(String product) {
          CompletableFuture<Double> futurePrice = new CompletableFuture<>();
          new Thread(() -> {
              double price = calculatePrice(product);
              futurePrice.complete(price);
          }).start();
          return futurePrice;
    }
    

    直接變成一句話

    public Future<Double> getPriceAsync(String product) {
        return CompletableFuture.supplyAsync(() -> calculatePrice(product));
    }
    

    supplyAsync方法接受一個生產(chǎn)者(Supplier)作為參數(shù)寝杖,返回一個CompletableFuture對象,該對象完成異步執(zhí)行后會讀取調用生產(chǎn)者方法的返回值互纯。生產(chǎn)者方法會交由ForkJoinPool池中的某個執(zhí)行線程(Executor)運行瑟幕,但是你也可以使用supplyAsync方法的重載版本,傳遞第二個參數(shù)指定不同的執(zhí)行線程執(zhí)行生產(chǎn)者方法留潦。

  • 再來一個例子只盹,之前是一家Shop做異步處理,這還不能發(fā)揮此接口的最大效用兔院,所以殖卑,這次,來一打Shops坊萝,比較最佳售價孵稽。

    private final List<Shop> shops = Lists.newArrayList(new Shop("BestPrice"),
          new Shop("LetsSaveBig"),
          new Shop("MyFavoriteShop"),
          new Shop("BuyItAll"));
    

    一般情況下,用并行流和CompletableFuture的異步效果是半斤八兩的十偶。

    /// 并行流的方式
    public List<String> findPricesParallel(String product) {
          return shops.parallelStream()
                  .map(shop -> String.format("%s 價格 %.2f", shop.getName() , shop.getPrice(product)))
                  .collect(toList());
    }
    
    // CompletableFuture的方式
    public List<String> findPricesFuture(String product) {
          List<CompletableFuture<String>> completableFutures = shops.stream()
                      .map(shop -> CompletableFuture.supplyAsync(() -> String.format("%s 價格 %.2f", shop.getName(), shop.getPrice(product))))
                      .collect(toList());
          return completableFutures
                  .stream()
                  .map(CompletableFuture::join)
                  .collect(toList());
    }
    

    默認CompletableFuture.supplyAsyn()內(nèi)部使用的線程池和ParallelStream使用的是同一個線程池菩鲜,是默認的固定線程數(shù)量的線程池,這個線程數(shù)由CPU惦积、JVM配置等決定接校。

    具體線程數(shù)取決于Runtime.getRuntime().availableProcessors()的返回值

    但是,CompletableFuture是可以配置supplyAsyn()中使用的ThreadFactory的狮崩,而ParallelStream是不能的蛛勉。

    // 使用自定義的線程池
      private final Executor executor = Executors.newFixedThreadPool(100, new ThreadFactory() {
          @Override
          public Thread newThread(Runnable runnable) {
              Thread t = new Thread(runnable);
              t.setDaemon(true);
              return t;
          }
      });
    ...
    ...
    public List<String> findPricesFuture(String product) {
          return shops.stream()
                  .map(shop -> CompletableFuture.supplyAsync(() -> "" + shop.getPrice(), executor))
                  .collect(toList())
                  .stream()
                  .map(CompletableFuture::join)
                  .collect(toList());
      }
    

并發(fā),用并行流還是CompletableFuture睦柴?

情況 推薦 原因
如果你進行的是計算密集型的操作诽凌,并且沒有I/O Stream 實現(xiàn)簡單
如果你并行的工作單元還涉及等待I/O的操作(包括網(wǎng)絡連接等待) CompletableFuture 高靈活性

新的日期和時間API

背景

  • Java 1.0
    • 特點:只有java.util.Date類
    • 缺點:這個類無法表示日期,只能以毫秒的精度表示時間爱只。而且皿淋,易用性差,如:Date date = new Date(114, 2, 18);居然表示2014年3月18日恬试。
  • Java 1.1
    • 更新:Date類中的很多方法被廢棄了,取而代之的是java.util.Calendar類
    • 缺點:Calendar類同樣很難用疯暑,比如:
      • 月份依舊是從0開始計算(不過训柴,至少Calendar類拿掉了由1900年開始計算年份這一設計)
      • DateFormat方法也有它自己的問題,它不是線程安全的妇拯。

LocalDate和LocalTime

相關類:

  • java.time.LocalDate
  • java.time.LocalTime
  • java.time.LocalDateTime

三個類都實現(xiàn)了各種基本計算方法幻馁、parse方法洗鸵、比較方法, 以及各種靜態(tài)方法仗嗦。

LocalDate localDate = LocalDate.of(2018, 11, 25);
int year = localDate.getYear();// 2018
Month month = localDate.getMonth(); // 11
int day = localDate.getDayOfMonth(); // 25
DayOfWeek dow = localDate.getDayOfWeek(); // SUNDAY
int len = localDate.lengthOfMonth(); // 本月總天數(shù): 30
boolean leap = localDate.isLeapYear(); // 是不是閏年: false

LocalDate localDate = LocalDate.now();
int year = localDate.get(ChronoField.YEAR);
int month = localDate.get(ChronoField.MONTH_OF_YEAR);
int day = localDate.get(ChronoField.DAY_OF_MONTH);

LocalTime localTime = LocalTime.now();
int hour = localTime.get(ChronoField.HOUR_OF_DAY);
int minute = localTime.get(ChronoField.MINUTE_OF_HOUR);
int second = localTime.get(ChronoField.SECOND_OF_MINUTE);

LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime); // 2018-11-25T22:10:08.721
System.out.println(localDateTime.atZone(ZoneId.of("GMT"))); // 2018-11-25T22:11:08.778Z[GMT]
System.out.println(localDateTime.atOffset(ZoneOffset.UTC)); // 2018-11-25T22:11:44.362Z

機器的日期和時間格式

從計算機的角度來看膘滨,建模時間最自然的格式是表示一個持續(xù)時間段上某個點的單一大整型數(shù)。

  • 相關類:

    • java.time.Instant
  • 建模方式:以Unix元年時間(傳統(tǒng)的設定為UTC時區(qū)1970年1月1日午夜時分)開始所經(jīng)歷的秒數(shù)進行計算稀拐。

  • 作用:適用于計算機做高精度運算火邓。

  • 用法實例

    • Instant.ofEpochSecond(秒/long, 納秒/long)
      Instant.ofEpochSecond(3); // 1970-01-01T00:00:03Z
      Instant.ofEpochSecond(3, 0);
      Instant.ofEpochSecond(2, 1_000_000_000); // 2 秒之后再加上100萬納秒(1秒)
      Instant.ofEpochSecond(4, -1_000_000_000); // 4秒之前的100萬納秒(1秒)
      

Duration/Period

Duration

  • 作用:Duration類主要用于以秒和納秒衡量時間的長短
  • 注意:不要用機器時間相關API來計算Duration,你看不懂
    LocalTime time1 = LocalTime.of(21, 50, 10);
    LocalTime time2 = LocalTime.of(22, 50, 10);
    LocalDateTime dateTime1 = LocalDateTime.of(2018, 11, 17, 21, 50, 10);
    LocalDateTime dateTime2 = LocalDateTime.of(2018, 11, 17, 23, 50, 10);
    Instant instant1 = Instant.ofEpochSecond(1000 * 60 * 2);
    Instant instant2 = Instant.ofEpochSecond(1000 * 60 * 3);
    
    // 可用工廠方法定義
    Duration threeMinutes = Duration.ofMinutes(3);
    Duration fourMinutes = Duration.of(4, ChronoUnit.MINUTES);
    
    Duration d1 = Duration.between(time1, time2);
    Duration d2 = Duration.between(dateTime1, dateTime2);
    Duration d3 = Duration.between(instant1, instant2);
    // PT1H 相差1小時
    System.out.println("d1:" + d1);
    // PT2H 相差2小時
    System.out.println("d2:" + d2);
    // PT16H40M 相差16小時40分鐘
    System.out.println("d3:" + d3);
    
    而且德撬,不要試圖在這兩類對象之間創(chuàng)建duration铲咨,會觸發(fā)一個DateTimeException異常。而且蜓洪,不要放一個LocalDate對象作為參數(shù)纤勒,不合適。

Period

  • 作用:以年隆檀、月或者日的方式對多個時間單位建模
  • 用法:
    // 可用工廠方法定義
    Period tenDay = Period.ofDays(10);
    Period threeWeeks = Period.ofWeeks(3);
    Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1);
    
    Period period = Period.between(LocalDate.of(2018, 11, 7), LocalDate.of(2018, 11, 17));
    System.out.println("Period between:" + period); // P10D 相差10天
    

修改摇天、構造時間

簡單來說,API給我們劃分了讀取和修改兩類方法:

  • 讀:get
  • 修改:with

下面這些方法都會生成一個新的時間對象恐仑,不會修改源對象:

// 2018-11-17
LocalDate date1 = LocalDate.of(2018, 11, 17);
// 2019-11-17
LocalDate date2 = date1.withYear(2019);
// 2019-11-25
LocalDate date3 = date2.withDayOfMonth(25);
// 2019-09-25
LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9);

特別:用TemporalAdjuster實現(xiàn)復雜操作

利用重寫各種withXX方法闸翅,并自定義TemporalAdjuster參數(shù),就能實現(xiàn)復雜的時間操作:

// 2018-11-17
LocalDate date1 = LocalDate.of(2018, 11, 17);
// 2018-11-19
LocalDate date2 = date1.with(TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY));
// 2018-11-30
LocalDate date3 = date2.with(TemporalAdjusters.lastDayOfMonth());

我們看看這個用TemporalAdjuster接口菊霜,其實要自定義很簡單坚冀,因為它只有一個接口:

@FunctionalInterface
public interface TemporalAdjuster {
    Temporal adjustInto(Temporal temporal);
}

Format

  • 相關類:
    • java.time.format.DateTimeFormatter
    • java.time.format.DateTimeFormatterBuilder:用于實現(xiàn)更加復雜的格式化
  • 特點:
    • 所有的DateTimeFormatter實例都是線程安全的。(所以鉴逞,你能夠以單例模式創(chuàng)建格式器實例记某,就像DateTimeFormatter所定義的那些常量,并能在多個線程間共享這些實例构捡。)
// Date 轉 String
LocalDate date1 = LocalDate.of(2018, 11, 17);
String s1 = date1.format(DateTimeFormatter.BASIC_ISO_DATE); // 20181117
String s2 = date1.format(DateTimeFormatter.ISO_LOCAL_DATE); // 2018-11-17
// String 轉 Date
LocalDate date2 = LocalDate.parse("20181117", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate date3 = LocalDate.parse("2018-11-17", DateTimeFormatter.ISO_LOCAL_DATE);

DateTimeFormatter italianFormatter = DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.ITALIAN);
LocalDate date5 = LocalDate.of(2018, 11, 16);
// 16. novembre 2018
String formattedDate2 = date5.format(italianFormatter);
// 2018-11-16
LocalDate date6 = LocalDate.parse(formattedDate2, italianFormatter);

DateTimeFormatterBuilder實現(xiàn)細粒度格式化控制:

DateTimeFormatter italianFormatter = new DateTimeFormatterBuilder()
                .appendText(ChronoField.DAY_OF_MONTH)
                .appendLiteral(". ")
                .appendText(ChronoField.MONTH_OF_YEAR)
                .appendLiteral(" ")
                .appendText(ChronoField.YEAR)
                .parseCaseInsensitive()
                .toFormatter(Locale.ITALIAN);

LocalDate now = LocalDate.now();
// 17. novembre 2018
String s1 = now.format(italianFormatter);

處理不同的時區(qū)和歷法

  • 相關類:
    • java.time.ZoneId
    • java.time.ZoneOffset
  • 特點:
    • 新的java.time.ZoneId類是老版java.util.TimeZone的替代品液南。
    • 更容易處理日光時(Daylight Saving Time,DST)這種問題勾徽。
 // 地區(qū)ID都為“{區(qū)域}/{城市}”的格式
ZoneId shanghaiZone = ZoneId.of("Asia/Shanghai");

LocalDate date = LocalDate.of(2018, 11, 17);
ZonedDateTime zdt1 = date.atStartOfDay(shanghaiZone);

LocalDateTime dateTime = LocalDateTime.of(2018, 11, 27, 18, 13, 15);
ZonedDateTime zdt2 = dateTime.atZone(shanghaiZone);

Instant instant = Instant.now();
ZonedDateTime zdt3 = instant.atZone(shanghaiZone);


// LocalDateTime 轉 Instant
LocalDateTime dateTime2 = LocalDateTime.of(2018, 11, 17, 18, 45);
ZoneOffset newYorkOffset = ZoneOffset.of("-05:00");
Instant instantFromDateTime = dateTime2.toInstant(newYorkOffset);

// 通過反向的方式得到LocalDateTime對象
Instant instant2 = Instant.now();
LocalDateTime timeFromInstant = LocalDateTime.ofInstant(instant2, shanghaiZone);

// OffsetDateTime滑凉,它使用ISO-8601的歷法系統(tǒng),以相對于UTC/格林尼治時間的偏差方式表示日期時間喘帚。
LocalDateTime dateTime3 = LocalDateTime.of(2018, 11, 17, 18, 45);
OffsetDateTime offsetDateTime = OffsetDateTime.of(dateTime3, newYorkOffset);
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末畅姊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子吹由,更是在濱河造成了極大的恐慌若未,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件倾鲫,死亡現(xiàn)場離奇詭異粗合,居然都是意外死亡萍嬉,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門隙疚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來壤追,“玉大人,你說我怎么就攤上這事供屉⌒斜” “怎么了倦逐?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵荷并,是天一觀的道長硝全。 經(jīng)常有香客問我氢烘,道長堵第,這世上最難降的妖魔是什么弄诲? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任包颁,我火速辦了婚禮铆帽,結果婚禮上啡彬,老公的妹妹穿的比我還像新娘羹与。我一直安慰自己,他們只是感情好庶灿,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布纵搁。 她就那樣靜靜地躺著,像睡著了一般往踢。 火紅的嫁衣襯著肌膚如雪腾誉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天峻呕,我揣著相機與錄音利职,去河邊找鬼。 笑死瘦癌,一個胖子當著我的面吹牛猪贪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播讯私,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼热押,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了斤寇?” 一聲冷哼從身側響起桶癣,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎抡驼,沒想到半個月后鬼廓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡致盟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年碎税,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片馏锡。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡雷蹂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出杯道,到底是詐尸還是另有隱情匪煌,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布党巾,位于F島的核電站萎庭,受9級特大地震影響,放射性物質發(fā)生泄漏齿拂。R本人自食惡果不足惜驳规,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望署海。 院中可真熱鬧吗购,春花似錦、人聲如沸砸狞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刀森。三九已至踱启,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間研底,已是汗流浹背埠偿。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留飘哨,地道東北人胚想。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像芽隆,于是被迫代替她去往敵國和親浊服。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容