文章目錄
一、Lambda 表達式
1.1 基本概念
Lambda 表達式,也可稱爲閉包,它是推動 Java 8 發佈的最重要新特性。
Lambda 允許把函數作爲一個方法的參數(函數作爲參數傳遞進方法中)。
使用 Lambda 表達式可以使代碼變的更加簡潔緊湊。
1.2 語法
(parameters) -> expression
或
(parameters) ->{ statements; }
1.3 重要特徵
- **可選類型聲明:**不需要聲明參數類型,編譯器可以統一識別參數值。
- **可選的參數圓括號:**一個參數無需定義圓括號,但多個參數需要定義圓括號。
- **可選的大括號:**如果主體包含了一個語句,就不需要使用大括號。
- **可選的返回關鍵字:**如果主體只有一個表達式返回值則編譯器會自動返回值,大括號需要指定明表達式返回了一個數值。
使用 Lambda 表達式需要注意以下兩點:
- Lambda 表達式主要用來定義行內執行的方法類型接口。
- Lambda 表達式免去了使用匿名方法的麻煩,並且給予Java簡單但是強大的函數化的編程能力。
1.4 變量作用域
lambda 表達式只能引用標記了 final 的外層局部變量,這就是說不能在 lambda 內部修改定義在域外的局部變量,否則會編譯錯誤。
lambda 表達式的局部變量可以不用聲明爲 final,但是必須不可被後面的代碼修改(即隱性的具有 final 的語義)。
在 Lambda 表達式當中不允許聲明一個與局部變量同名的參數或者局部變量。
二、方法引用
2.1 基本概念
方法引用通過方法的名字來指向一個方法。、
方法引用可以使語言的構造更緊湊簡潔,減少冗餘代碼。
方法引用使用一對冒號 :: ;
2.2 引用方法
- 構造器引用
Class::new ,或者更一般的Class::new - 靜態方法引用
Class::static_method - 特定類的任意對象的方法引用
Class::method - 特定對象的方法引用
instance::method
三、函數式接口
3.1 基本概念
函數式接口就是一個有且僅有一個抽象方法,但是可以有多個非抽象方法的的接口。
函數式接口可以被隱式轉換爲lambda表達式。
例:
@FunctionalInterface
interface GreetingService {
void sayMesage(String message);
}
如果使用lambda表達式來創建一個函數式接口實例,那這個lambda表達式的入參和返回必須符合這個函數式接口中唯一的抽象方法的定義。
3.2 默認方法
簡單說,默認方法就是接口可以有實現方法,而且不需要實現類去實現其方法。
我麼只需在方法名前面加個 default 關鍵字即可實現默認方法。
爲什麼要有這個特性?
首先,之前的接口是個雙刃劍,好處是面向抽象而不是面向具體編程,缺陷是,當需要修改接口時候,需要修改全部實現該接口的類,目前的 java 8 之前的集合框架沒有 foreach 方法,通常能想到的解決辦法是在JDK裏給相關的接口添加新的方法及實現。然而,對於已經發布的版本,是沒法在給接口添加新方法的同時不影響已有的實現。所以引進的默認方法。他們的目的是爲了解決接口的修改與現有的實現不兼容的問題。
3.3 Supplier
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
3.4 Function
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
}
3.5 Predicate
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
}
3.6 Consumer
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}
四、Optional
4.1 基本概念
爲了解決NullPointerException問題,減少代碼中的判空,實現函數式編程,給工程師們提供函數式的API。
Optional 類是一個可以爲null的容器對象。如果值存在則isPresent()方法會返回true,調用get()方法會返回該對象。
Optional 是個容器:它可以保存類型T的值,或者僅僅保存null。Optional提供很多有用的方法,這樣我們就不用顯式進行空值檢測。
Optional 類的引入很好的解決空指針異常。
4.2 類方法
修飾符和類型 | 方法 | 描述 |
---|---|---|
static Optional | empty() | 返回空的Optional實例 |
boolean | equals(Object obj) | 判斷其他對象是否等於Optional |
int | hashCode() | 返回存在值的哈希碼,如果值不存在 返回 0 |
static Optional | of(T value) | 返回一個指定非null值的Optional |
static Optional | ofNullable(T value) | 如果爲非空,返回 Optional 描述的指定值,否則返回空的 Optional |
T | orElse(T other) | 如果存在該值,返回值, 否則返回 other |
T | orElseGet(Supplier<? extends T> other) | 如果存在該值,返回值, 否則觸發 other,並返回 other 調用的結果 |
boolean | isPresent() | 如果值存在則方法會返回true,否則返回 false |
T | get() | 如果在這個Optional中包含這個值,返回值,否則拋出異常:NoSuchElementException |
String | toString() | 返回一個Optional的非空字符串,用來調試 |
Optional | filter(Predicate<? super predicate) | 如果值存在,並且這個值匹配給定的 predicate,返回一個Optional用以描述這個值,否則返回一個空的Optional |
Optional | flatMap(Function<? super T, Optional> mapper) | 如果值存在,返回基於Optional包含的映射方法的值,否則返回一個空的Optional |
void | ifPresent(Consumer<? super T> consumer) | 如果值存在則使用該值調用 consumer , 否則不做任何事情 |
Optional | map(Function<? super T,? extends U> mapper) | 如果有值,則對其執行調用映射函數得到返回值。如果返回值不爲 null,則創建包含映射返回值的Optional作爲map方法返回值,否則返回空Optional |
T | orElseThrow(Supplier<? extends X> exceptionSupplier) | 如果存在該值,返回包含的值,否則拋出由 Supplier 繼承的異常 |
五、Stream
5.1 基本概念
Stream 使用一種類似用 SQL 語句從數據庫查詢數據的直觀方式來提供一種對 Java 集合運算和表達的高階抽象。
Stream API可以極大提高Java程序員的生產力,讓程序員寫出高效率、乾淨、簡潔的代碼。
這種風格將要處理的元素集合看作一種流, 流在管道中傳輸, 並且可以在管道的節點上進行處理, 比如篩選, 排序,聚合等。
元素流在管道中經過中間操作(intermediate operation)的處理,最後由最終操作(terminal operation)得到前面處理的結果。
流操作由3部分組成:
- 創建流
- 零個或多箇中間操作
- 終止操作(到這一步纔會執行整個stream pipeline計算)
5.2 什麼是stream
Stream(流)是一個來自數據源的元素隊列並支持聚合操作
- 元素是特定類型的對象,形成一個隊列。 Java中的Stream並不會存儲元素,而是按需計算。
- 數據源 流的來源。 可以是集合,數組,I/O channel, 產生器generator 等。
- 聚合操作 類似SQL語句一樣的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作還有兩個基礎的特徵:
- Pipelining: 中間操作都會返回流對象本身。 這樣多個操作可以串聯成一個管道, 如同流式風格(fluent style)。 這樣做可以對操作進行優化, 比如延遲執行(laziness)和短路( short-circuiting)。
- 內部迭代:以前對集合遍歷都是通過Iterator或者For-Each的方式, 顯式的在集合外部進行迭代, 這叫做外部迭代。 Stream提供了內部迭代的方式, 通過訪問者模式(Visitor)實現。
5.3 創建流的方式:
-
通過Stream接口的of靜態方法創建一個流
Stream<String> stream1 = Stream.of("a", "b", "c");
-
創建一個空的流
Stream<Object> empty = Stream.empty();
-
通過builder創建
Stream<Object> build = Stream.builder().add("a").add("b").add("c").build();
-
通過Arrays類的stream方法,實際上第一種of方法底層也是調用的Arrays.stream(values)
String[] array = new String[]{"hello","world","helloworld"}; Stream<String> stream3 = Arrays.stream(array);
-
通過集合的stream方法,該方法是Collection接口的默認方法,所有集合都繼承了該方法
Stream<String> stream2 = Arrays.asList("hello","world","helloworld").stream();
-
合併多個Stream
Stream stream = Stream.concat(stream1,stream2);
-
generate()和iterate()
兩個都是生成一個無限的流,通常跟
limit()
一起使用,限制流中元素的個數。不同的是前者可以根據任何計算方式來生成,後者只能根據給定的
seed
來生成。Stream<T> generate(Supplier<T> s): Stream.generate(UUID.randomUUID()::toString).limit(10).forEach(System.out::println); Stream<T> iterate(final T seed, final UnaryOperator<T> f): //從1開始,每個元素比前一個元素大2,最多生成10個元素 Stream.iterate(1,item -> item + 2).limit(10).forEach(System.out::println);
集合接口有兩個方法來生成流:
- stream() − 爲集合創建串行流。
- parallelStream() − 爲集合創建並行流。
5.4 基本方法
5.4.1 中間處理
-
篩選
接收一個lambda表達式,過濾掉某些元素,僅留下符合要求的元素。
filter(d -> true)
Stream<Integer> stream = Stream.of(1, 2, -1, 0, 3, -2); stream.filter(value -> value > 0).forEach(System.out::print);
-
截斷
僅保留流中的前n個元素。由於中間處理是惰性的,所以limit在某些情況下可以很大的提升處理速度。
limit(5)
Stream<Integer> stream = Stream.iterate(0, x -> x + 2); stream.limit(5).forEach(System.out::print);
-
捨棄
捨棄流中的前n個元素,僅保留第n+1個及其之後的元素。skip(5)
Stream<Integer> stream = Stream.iterate(1, 2, 3, 4, 5, 6, 7); stream.skip(3).forEach(System.out::print);
-
去重
去掉Stream中重複的元素,它使用hashCode和equals方法來判斷元素是否相等。distinct()
Stream<String> stream = Stream.of("a", "b", "c", "b", "c"); stream.distinct().forEach(System.out::print);
-
排序
對Stream中的元素進行排序。sorted()
sorted(Comparator.comparingInt(v -> v))
、sorted((v1, v2) -> v2 - v1)
Stream<Integer> stream = Stream.of(2, 4, 1, 5, 3); stream.sorted().forEach(System.out::print); stream.sorted(Comparator.reverseOrder()).forEach(System.out::print);
-
映射
通過Lambda表達式,將每一個元素一一映射爲一個新的元素。map(v -> v + 55)
Stream<Integer> stream = Stream.of(2, 4, 1, 5, 3); stream.map(v -> v + 55).forEach(System.out::println);
-
扁平化映射
通過Lambda表達式,將每一個元素一一映射爲一個新的Stream後,將新的Stream全部連起來。
flatMap(theList -> theList.stream())
Stream<String> stream = Stream.of("abc", "def", "ghi"); stream.flatMap(str -> str.chars().boxed()).forEach(System.out::print);
5.4.2 結束處理
-
迭代
對Stream內的每一個元素進行循環處理。forEach(System.out::println)
Stream<String> stream = Stream.of("a", "b", "c"); stream.forEach(System.out::print);
匹配
判斷Stream中的元素是否匹配某條件,返回boolean結果。allMatch:Stream中是否所有元素都匹配
allMatch(v -> true)
anyMatch:Stream中是否有任一元素匹配
noneMatch(v -> true)
noneMatch:Stream中是否所有元素都不匹配
anyMatch(v -> true)
Stream<String> stream = Stream.of(1, 2, 4, 0, -3, -5); stream.allMatch(value -> value > 0); // 返回false stream.anyMatch(value -> value > 0); // 返回true stream.noneMatch(value -> value > 0); // 返回false
-
查找
查找Stream中的一個元素,返回Optional類型。一般與filter等一起使用。
findFirst:查找第一個元素
findAny:查找任一個元素。在並行流(parallelStream)中性能提升比較明顯。findFirst().orElse(0)
findAny().orElse(0)
Stream<String> stream = Stream.of(1, 2, 4, 0, -3, -5); System.out.print(stream.findFirst().orElse(0)); System.out.print(stream.findAny().orElse(0));
-
統計
count:統計Stream中元素的個數。min:獲取Stream中的最小元素。
max:獲取Stream中的最大元素。
count()
min(Comparator.comparing(v -> v))
、mapToInt(v -> v + 3).min().orElse(0)
max(Comparator.comparing(v -> v))
、mapToInt(v -> v + 3).max().orElse(0)
System.out.println(Stream.of(1, 2, 4, 0, -3, -5).count()); System.out.println(Stream.of(1, 2, 4, 0, -3, -5).min(Comparator.comparing(v -> v)).orElse(0)); System.out.println(Stream.of(1, 2, 4, 0, -3, -5).max((v1, v2) -> v1.compareTo(v2)).orElse(0));
-
規約
將Stream中的每一個元素進行指定的疊加處理,最終生成一個值。
reduce(BigDecimal.ZERO, BigDecimal::add).get()
Stream<String> stream = Stream.of("abc", "def", "ghi"); System.out.println(stream.reduce((s, s2) -> s + ", " + s2).get());
-
收集
將Stream收集成各種形式。主要利用Collectors中的靜態方法來實現。
collect(Collectors.toList())
List<String>list = Stream.of("abc", "def", "ghi").collect(Collectors.toList()); Set<String>set = Stream.of("abc", "def", "ghi").collect(Collectors.toSet()); LinkedList<String>list = Stream.of("abc", "def", "ghi").collect(Collectors.toCollection(LinkedList::new));
collect(Collectors.toMap(v -> v, v -> v.toUpperCase())
Map<String, String> map = Stream.of("abc", "def", "ghi").collect(Collectors.toMap(v -> v, v -> v.toUpperCase()));
-
計數
collect(Collectors.counting())
long count = Stream.of(1, 2, 4, 0, -3, -5).collect(Collectors.counting());
-
平均
collect(Collectors.averagingDouble(v -> v))
collect(Collectors.averagingInt(v -> v))
collect(Collectors.averagingLong(v -> v))
double average = Stream.of(1, 2, 4, 0, -3, -5).collect(Collectors.averagingInt(v -> v));
-
最小值
collect(Collectors.minBy(Integer::compare)))
Optional<Integer> min = Stream.of(1, 2, 4, 0, -3, -5).collect(Collectors.minBy(Integer::compare));
-
最大值
collect(Collectors.maxBy(Integer::compare)))
Optional<Integer> max = Stream.of(1, 2, 4, 0, -3, -5).collect(Collectors.maxBy(Integer::compare));
-
合計
collect(Collectors.summingInt(v -> v))
-
分組
collect(Collectors.groupingBy(v -> v.equals("11")))
Stream<Goods> goodsStream = Stream.of(new Goods("A", 18), new Goods("A", 15), new Goods("B", 5), new Goods("B", 20)); Map<String, List<Goods>> groupedGoods = goodsStream.collect(Collectors.groupingBy(Goods::getGoodsName)); Map<String, Map<String, List<Goods>>> groupedGoods = goodsStream.collect(Collectors.groupingBy(Goods::getGoodsType, Collectors.groupingBy(Goods::getGoodsName)));
-
分組合計
collect(Collectors.groupingBy(v -> v, Collectors.summarizingInt(v -> v)))
Map<String, Double> groupedGoods = Stream.of(new Goods("A", 18), new Goods("A", 15), new Goods("B", 5), new Goods("B", 20)).collect(Collectors.groupingBy(Goods::getGoodsType, Collectors.summingDouble(Goods::getPrice)));
-
分區
collect(Collectors.partitioningBy(g -> g.getPrice() > 15))
Map<Boolean, List<Goods>> groupedGoods = Stream.of(new Goods("A", 18), new Goods("A", 15), new Goods("B", 5), new Goods("B", 20)).collect(Collectors.partitioningBy(g -> g.getPrice() > 15));
-
分區合計
collect(Collectors.partitioningBy(g -> g.getPrice() > 15, Collectors.summingDouble(d -> d.get("quantity")))
Map<Boolean, Double> groupedGoods = Stream.of(new Goods("A", 18), new Goods("A", 15), new Goods("B", 5), new Goods("B", 20)).collect(Collectors.partitioningBy(g -> g.getPrice() > 15, , Collectors.summingDouble(Goods::getPrice)));
六、日期時間API
6.1 基本概念
舊版Java中,日期時間API存在的問題:
- 非線程安全
java.util.Date是非線程安全的,所有的日期類都是可變的 - 設計很差
Java的日期/時間類的定義並不一致,在java.util和java.sql的包中都有日期類,此外用於格式化和解析的類在java.text包中定義。
java.util.Date同時包含日期和時間,而java.sql.Date僅包含日期,將其納入java.sql包並不合理。
另外這兩個類都有相同的名字,這本身就是一個非常糟糕的設計。 - 時區處理麻煩
Java8在java.time中提供的API:
- Local(本地)
簡化了日期時間的處理,沒有時區的問題 - Zoned(時區)
通過制定的時區處理日期時間
新的java.time包涵蓋了所有處理日期,時間,日期/時間,時區,時刻(instants),過程(during)與時鐘(clock)的操作。
6.2 關鍵類
java.time
包裏有許多可以代表時間和日期的類。
Instant
類,提供了一個機器視角的時間線。LocalDate
,LocalTime
和LocalDateTime
類提供了人類視角的日期和時間,不涉及到時區。ZoneId
,ZoneRules
和ZoneOffset
類描述了時區,時區偏移量和時區規則。ZonedDateTime
類,代表了與時區關聯的時間和日期。OffsetDateTime
和OffsetTime
分別代表了日期和時間和時間。這些類描述了時區偏移。Duration
類在秒和毫秒尺度丈量一段時間。Period
類在年、月和日尺度上丈量一段時間。
6.3 常用的API
-
獲取當前日期時間
LocalDate date = LocalDate.now(); LocalTime time = LocalTime.now(); LocalDateTime dateTime = LocalDateTime.now().withNano(0);
-
獲取指定日期時間
LocalDate specDatefromString = LocalDate.parse(“2014-12-12”); LocalDate specDate = LocalDate.of(2014, 2, 20); specDate = LocalDate.ofYearDay(2015, 100); specDate = LocalDate.ofEpochDay(200);//自1970年1月1日起200天后的日期
-
獲取今天是今年的第幾天
int dayOfYear = LocalDate.now().getDayOfYear();
-
當前月的最後一天
LocalDate date = LocalDate.now(); // 不用考慮是28、29、30還是31天 LocalDate lastDayOfMonth = date.with(TemporalAdjusters.lastDayOfMonth());
-
2015年11月第一個週一
LocalDate firstMondayInOneMonth = LocalDate.parse("2015-11-11") .with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
-
日期加減與時間間隔
LocalTime time = LocalTime.now(); // 當前時間加20分鐘 LocalTime timeAfterPlus = time.plusMinutes(20); // 當前時間減2小時 LocalTime timeAfterMinus = time.minusHours(2); // 兩個時間間隔(單位:分鐘),如第二個參數比第一個大,結果爲負數 long duration = ChronoUnit.MINUTES.between(time, timeAfterPlus);
-
獲取兩個日期間的距離
LocalDate date = LocalDate.now(); Period period = Period.between(LocalDate.of(2014, 2, 10), date);
-
日期判斷
LocalDate date = LocalDate.now(); LocalDate date1 = LocalDate.now(); // 判斷是否相等 boolean isEqual = date.equals(date1); // 判斷是否在另一個日期之前 boolean isBefore = date.isBefore(date1); // 判斷是否爲閏年 boolean isLeapYear = date.isLeapYear();
-
查看時區
// 獲得所有時區 Set<String> allZone = ZoneId.getAvailableZoneIds(); ZoneId zone = ZoneId.systemDefault(); // 獲得美國時間 ZoneId zoneInUSA = ZoneId.of("America/New_York"); LocalTime timeInUSA = LocalTime.now(zoneInUSA);
-
時間戳轉換爲日期
Instant second = Instant.ofEpochSecond(1234567890L); DateTimeFormatter formatter = DateTimeFormatter.ofPattern(“yyyy-MM-dd HH:mm:ss”); // 等同於 formatter.format(LocalDateTime.ofInstant(second, ZoneId.systemDefault())); String time = LocalDateTime.ofInstant(second, ZoneId.systemDefault()).format(formatter); // 另一種方法 LocalDateTime now = LocalDateTime.ofEpochSecond(12468312, 0, ZoneOffset.of("+8"));
-
日期轉換爲時間戳
// 第一種方式 Instant instant1 = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant(); // 第二種方式 OffsetDateTime offsetTime = OffsetDateTime.now(ZoneId.systemDefault()); ZoneOffset offset = offsetTime.getOffset(); Instant instant2 = LocalDateTime.now().toInstant(offset); // 獲得絕對秒 long millisecond = instant1.getEpochSecond();