Java8用法總結


一、新特性

Java8帶來了很多的新特性,本篇就以下幾個方面,從實際用法的角度進行介紹。

  • Lambda 表達式
  • 函數式接口
  • Stream
  • 默認方法
  • Optional 類

 

二、Lambda表達式

2.1 引例

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    private String id;
    private Long num;
    private Double price;
}
為了以后排序,我們定義一種比較器,按價格排序:
Comparator<Product> byPrice = new Comparator<Product>() {
   @Override 
   public int compare(Product o1, Product o2) {
      return o1.getPrice().compareTo(o2.getPrice());
   }
};    
byPrice的作用是按價格比較2種產品,它是一種行為,可以用Lambda表達:
Comparator<Product> byPrice = (Product o1, Product o2) -> o1.getPrice().compareTo(o2.getPrice();
這里只有一種類型Product,可根據Comparator<Product>判斷,因此進一步簡化:
Comparator<Product> byPrice = (o1, o2) -> o1.getPrice().compareTo(o2.getPrice();

 

2.2 概念

Lambda表示一種行為,通過Lambda表達式將行為參數化,這樣,行為可以和對象一樣傳遞;從第三章可以了解,Lambda表達式可以用函數式接口表示,Comparator就是一種函數式接口;

 

2.3 表達式

Lambda表達式有三部分,參數列表、"->"、Lambda主體,實際中有以下2種形式:

(parameters) -> expression
(parameters) ->{ statements; }
(List<Product> list) -> list.isEmpty;    // 判斷隊列為空
() -> new Product();  // 新建一個對象
(String s) -> s.length;  // 求字符串長度
(Product p) -> System.out.println(p);  // 輸出對象

 

三、函數式接口

3.1 相關概念

函數式接口:只定義一個抽象方法的接口;它可能還會有很多默認方法,但有且僅有一個抽象方法;常見的函數式接口如Comparator, Runnable;函數式接口可以用來

表達Lamdba表達式;如將一個Lamdba表達式傳遞給一個函數式接口,即Lamdba表達式以內聯的方式實現了函數式接口;

函數描述符:函數式接口的抽象方法;

 

3.2 引例

如果我們想寫一個用於2個數計算的計算器,可能需要實現如下幾個函數,根據運算符調用對應函數計算;

public <T> T add(T a, T b);
public <T> T add(T a, T b);
public <T> T multiply(T a, T b);
public <T> T divide(T a, T b);

換一種思路,如果有這樣一個函數 public double func(double a, double b, Function f); f是一個函數式接口,它表示具體運算,具體代碼實現如下:

@Log4j2
public class T19 {
    public static void main(String[] args) {
        log.info(myFunction(1, 2, (a, b) -> a + b));
        log.info(myFunction(1.0, 2.0, (a, b) -> a - b));
        log.info(myFunction(BigDecimal.ZERO, BigDecimal.valueOf(2), (a, b) -> a.multiply(b)));
    }

    public static <T> T myFunction(T a, T b, MyBiFunctionInterface<T> f) {
        return f.apply(a, b);
    }
}

@FunctionalInterface
public interface MyBiFunctionInterface<T> {
    T apply(T a, T b);
}

輸出如下:

2018-09-01 19:39:11 - test.other.T19 INFO test.other.T19.main(T19.java:20) : 3
2018-09-01 19:39:11 - test.other.T19 INFO test.other.T19.main(T19.java:21) : -1.0
2018-09-01 19:39:11 - test.other.T19 INFO test.other.T19.main(T19.java:22) : 0

Java8提供了很多函數式接口,一般情況下不用去定義函數式接口,比如例子中MyBiFunctionInterface,可用BinaryOperator代替,BinaryOperator這個函數式接口,接收2個類型為T的參數,返回一個類型為T的結果,即(T, T) -> T,修改后如下:

    public static <T> T myFunction(T a, T b, BinaryOperator<T> f) {
        return f.apply(a, b);
    }


3.3 常見函數式接口

Function<T, R> T-> R
Predict<T> T -> boolean
Consumer<T> T -> void
Supplier<T> () -> T
UnaryOperator<T> T -> T
BinaryOperator<T> (T, T) -> T
BiFunction<T, U> (T, U) -> R
BiPredicate<L, R> (L, R) -> boolean
BiConsumer<T, U> (T, U) -> void

 

 

 

 

 

 

 

 

 

3.4 方法引用

Lamdba表達式的快捷寫法,它更直觀,可讀性更好,比如:(Product p) -> p.getPrice  ==  Product::getPrice

方法引用主要有二類:

(1)指向靜態方法;如 Integer::parseInt;

(2)指向實例方法:如 String::length;

(3)構造函數的方法引用:如Supplier<Product> p = Product::new;

例:第二章引例中還可以如下表達:

Comparator<Product> c = Comparator.comparing(Product::getPrice);

 

3.5 復合

復合,就是將多個Lamdba表達式連接起來,表示更加復雜的功能;主要有以下三種

(1)函數復合:將Function代表的Lamdba復合起來,有andThen, compose;其中

f.andThen(g) = g(f(x)),先計算f表達式,將結果再計算g表達式;

f.compose(g) = f(g(x)),先計算g表達式,將結果再計算f表達式;

        Function<Integer, Integer> f = x -> x + 1;
        Function<Integer, Integer> g = x -> x * 2;
        Function<Integer, Integer> h1 = f.andThen(g);  // (1 + 1) * 2 = 4
        Function<Integer, Integer> h2 = f.compose(g);  // (1 * 2) + 1 = 3

(2)Predicate的復合,有negate, and, or,分別表示非、且、或,按從左到右的順序

        Predicate<Product> p1 = a -> a.getPrice() > 100;  // 大於100
        Predicate<Product> p2 = p1.negate(); // 小於等於100
        Predicate<Product> p3 = p1.negate().and(a -> a.getNum() > 10); // 價格小於等於100,且數量大於10

(3)比較器復合,如

        Comparator<Product> c = Comparator.comparing(Product::getPrice).reversed().thenComparing(Product::getNum);

 

四、流

4.1 概念

流用來處理數據集合,它具有如下特點:

(1)流強調的是計算,它是 源+數據處理,流將外部迭代(如for/while)轉化為對我們透明的內部迭代;

(2)只能遍歷一次,遍歷完就關閉;

流具有如下優點:

(1)內置了很多常用方法(如排序、分類、統計);

(2)能透明的並行處理;

(3)聲明式的,只需關注我要怎么樣,不用關注我該如何實現,通過內置的方法與復合很容易實現;

 

4.2 流的操作

流的操作分為:

(1)中間操作:filter(Predicate<T>), map(Function(T, R), limit, sorted(Comparator<T>), distinct,flatMap;

(2)終端操作:只有終端操作才能產生輸出,包括:allMatch, anyMatch, noneMatch, findAny, findFirst, forEach, collect, reduce, count

 

4.3 流的用法

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    private String id;
    private Long num;
    private Double price;
    private Boolean isUse;
}
List<Product> list = Lists.newArrayList(
Product.builder().id("11").num(20l).price(100d).isUse(true).build(),
Product.builder().id("12").num(25L).price(120d).isUse(true).build(),
Product.builder().id("13").num(25L).price(100d).isUse(true).build(),
Product.builder().id("14").num(20L).price(110d).isUse(false).build()
);
 

(1)filter,  找出價格大於100的產品:

List<Product> list1 = list.stream().filter(p -> p.getPrice() > 100).collect(Collectors.toList());

(2)distinct,去重

Arrays.asList(1, 2, 3, 1).stream().distinct().forEach(System.out::print); // 輸出123

(3)limit,輸出前n個

Arrays.asList(1, 2, 3, 1).stream().limit(2).forEach(System.out::print);  //輸出12

(4)skip,跳過前n個

Arrays.asList(1, 2, 3, 1).stream().skip(2).forEach(System.out::print); // 輸出31

(5)map, 映射,T -> R

<R> Stream<R> map(Function<? super T, ? extends R> mapper);
list.stream().map(Product::getPrice).distinct().forEach(System.out::println);
輸出:
100.0
120.0
110.0

(6)flatMap,扁平化,將每個元素產生的中間集合合並成一個大集合;接收的Function將T->Stream<R>

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
        Arrays.asList(new String[]{"hello", "world"}).stream().map(p -> p.split(""))
                .flatMap(Arrays::stream)  //.flatMap(p -> Arrays.stream(p))
                .distinct().forEach(System.out::print);
// 輸出:helowrd

(7)匹配

boolean anyMatch(Predicate<? super T> predicate);
  • allMatch: 都滿足條件才返回true;
  • anyMatch: 有一個滿足就返回true;
  • noneMatch: 都不滿足才返回true;
boolean b = Arrays.asList(1, 2, 3, 1).stream().anyMatch(p -> p > 2); //返回true

(8)查找,與其它操作結合使用

findAny:     Optional<T> findAny()

findFirst:     Optional<T> findFirst()

Arrays.asList(1, 2, 3, 4, 1).stream().filter(p -> p > 2).findAny() //輸出Optional[3]
Arrays.asList(1, 2, 3, 4, 1).stream().filter(p -> p > 2).findFirst() //輸出Optional[3]

 

4.4 reduce歸約

歸約操作是很常用的操作,它將流中的值反復的結合起來,最終得到一個值,它是一種終端操作;

(1)Optional<T> reduce(BinaryOperator<T> accumulator);
(2)T reduce(T identity, BinaryOperator<T> accumulator);
(3)<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);

(1) 給定歸約算法,最終歸約成一個值,考慮到流可能為空,所以返回類型為Option,例:

Optional<Integer> op1 = Arrays.asList(1, 2, 3, 4, 1).stream().reduce(Integer::sum); //輸出Optional[11]

(2)給定了初值,歸約算法,返回結果;

Arrays.asList(1, 2, 3, 4, 1).stream().reduce(0, Integer::sum); //輸出11
// Steam<T>中T為包裝類型,沒有sum,但Java8為流的原始類型提供了一些方法,如下 Arrays.asList(1, 2, 3, 4, 1).stream().mapToInt(a -> a).sum(); list.stream().mapToLong(Product::getNum).sum();

(3)第三個參數表示合並方式,當是並行流時,各線程獨立計算結果,最后將各線程的結果合並;

BiFunction<Double, Product, Double> f1 = (Double a, Product b) -> a + b.getNum();
        BinaryOperator<Double> f2 = (a, b) -> a + b;
        double b2 = list.parallelStream().reduce(0d, f1, f2);
        log.info(b2); //輸出90

 

4.5 數值流

數值流除了具有流的方法外,還有一些特殊的統計方法,例

DoubleStream doubleStream = list.stream().mapToDouble(Product::getPrice);
double average = doubleStream.average().getAsDouble();
//數值流->對象流
Stream<Double> sd = doubleStream.boxed();
// 生成n以內的勾股數
Stream<double[]> stream = IntStream.rangeClosed(1, 30).boxed().flatMap(a -> IntStream.rangeClosed(a, 30).mapToObj(
b -> new double[]{a, b, Math.sqrt(a * a + b * b)}).filter(t -> t[2] % 1 == 0));
stream.limit(3).forEach(t -> System.out.println(t[0] + ", " + t[1] + ", " + t[2]));
輸出:
3.0, 4.0, 5.0
5.0, 12.0, 13.0
6.0, 8.0, 10.0

 

4.6 構建流

Stream.iterate(0, n -> n + 2).limit(10);
Stream.generate(Math::random).limit(10);

 

五、收集器(collect歸約)

5.1 常見用法

Map<Double, List<Product>> map = list.stream().collect(groupingBy(Product::getPrice));
Long allNum = list.stream().collect(summingLong(Product::getNum));
double average = list.stream().collect(averagingDouble(Product::getPrice));
LongSummaryStatistics statistics = list.stream().collect(summarizingLong(Product::getNum));
String ids = list.stream().map(Product::getId).collect(joining(", "));

 

5.2 reducing歸約

Optional<Product> opp = list.stream().collect(reducing((a, b) -> a.getPrice() > b.getPrice() ? a : b));
long allNum2 = list.stream().collect(reducing(0L, Product::getNum, Long::sum));
long allNum3 = list.stream().collect(reducing(0L, Product::getNum, (i, j) -> i + j));

collect中reducing歸約三要素,初值,提取值,歸約方法,若無初值返回Optional,若提取值即是對象本身,可省略;

 

5.3 多重分組

Map<Double, Map<Long, List<Product>>> map = list.stream().collect(groupingBy(Product::getPrice, groupingBy(Product::getNum)));
Map<Double, Map<String, List<Product>>> map2 = list.stream().collect(groupingBy(Product::getPrice, groupingBy(p -> {
    if (p.getNum() <= 80L)
        return "little";
    else if (p.getNum() >= 120L)
        return "many";
    else
        return "normal";
})));
System.out.println(JacksonUtil.toJson(map));
System.out.println(JacksonUtil.toJson(map2));

輸出如下:

{
  "100.0" : {
    "20" : [ {
      "id" : "11",
      "num" : 20,
      "price" : 100.0,
      "isUse" : true
    } ],
    "25" : [ {
      "id" : "13",
      "num" : 25,
      "price" : 100.0,
      "isUse" : true
    } ]
  },
  "110.0" : {
    "20" : [ {
      "id" : "14",
      "num" : 20,
      "price" : 110.0,
      "isUse" : false
    } ]
  },
  "120.0" : {
    "25" : [ {
      "id" : "12",
      "num" : 25,
      "price" : 120.0,
      "isUse" : true
    } ]
  }
}
{
  "100.0" : {
    "little" : [ {
      "id" : "11",
      "num" : 20,
      "price" : 100.0,
      "isUse" : true
    }, {
      "id" : "13",
      "num" : 25,
      "price" : 100.0,
      "isUse" : true
    } ]
  },
  "110.0" : {
    "little" : [ {
      "id" : "14",
      "num" : 20,
      "price" : 110.0,
      "isUse" : false
    } ]
  },
  "120.0" : {
    "little" : [ {
      "id" : "12",
      "num" : 25,
      "price" : 120.0,
      "isUse" : true
    } ]
  }
}

在一次分組的子集合中處理數據

Map<Double, Long> map = list.stream().collect(groupingBy(Product::getPrice, counting()));
Map<Double, Optional<Product>> map2 = list.stream().collect(groupingBy(Product::getPrice, maxBy(comparingLong(Product::getNum))));
Comparator<Product> c = ((p1, p2) -> p1.getNum().compareTo(p2.getNum()));
Map<Double, Optional<Product>> map3 = list.stream().collect(groupingBy(Product::getPrice, maxBy(c)));
Map<Double, Product> map4 = list.stream().collect(groupingBy(Product::getPrice, 
collectingAndThen(maxBy(comparing(Product::getNum)), Optional::get)));

 

5.4 分區

由一個謂詞作為分類,分為2類,true與false,用法與groupingBy完全一樣

Map<Boolean, List<Product>> map = list.stream().collect(partitioningBy(Product::getIsUse));
Map<Boolean, Map<Double, List<Product>>> map2 = list.stream().collect(partitioningBy(Product::getIsUse, 
groupingBy(Product::getPrice))); Map
<Boolean, LongSummaryStatistics> map3 = list.stream().collect(partitioningBy(Product::getIsUse,
summarizingLong(Product::getNum))); Map
<Boolean, Double> map4 = list.stream().collect(partitioningBy(Product::getIsUse, averagingLong(Product::getNum)));

 

六、optional

6.1 使用

(1)單級包裝用法:我們會見到如下代碼, 

String name = null;
if (product != null) {
    name = product.getId();
}

利用optional可轉化為

Optional<Product> optProduct = Optional.ofNullable(product);
Optional<String> optName = optProduct.map(Product::getId);

(2)多級包裝用法

public String getName(Person person) {
    return person.getCar().getInsurance().getName();
}

經過包裝如下,注意為防止Optional<Optional<T>>這種中間結果造成編譯不通過,需要使用flatMap

public String getName(Person person) {
    Optional<Person> optPerson = Optional.ofNullable(person);
    return optPerson.flatMap(Person::getCar).flatMap(Car::getInsurance).map(Insurance::getName).orElse("Unknown");
}

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM