一、Lambda 表達式
1.1 基本概念
? Lambda 表達式,也可稱為閉包,它是推動 Java 8 發(fā)布的最重要新特性砸狞。
? Lambda 允許把函數(shù)作為一個方法的參數(shù)(函數(shù)作為參數(shù)傳遞進方法中)捻勉。
? 使用 Lambda 表達式可以使代碼變的更加簡潔緊湊。
1.2 語法
(parameters) -> expression
或
(parameters) ->{ statements; }
1.3 重要特征
- 可選類型聲明:不需要聲明參數(shù)類型刀森,編譯器可以統(tǒng)一識別參數(shù)值踱启。
- 可選的參數(shù)圓括號:一個參數(shù)無需定義圓括號,但多個參數(shù)需要定義圓括號研底。
- 可選的大括號:如果主體包含了一個語句埠偿,就不需要使用大括號。
- 可選的返回關(guān)鍵字:如果主體只有一個表達式返回值則編譯器會自動返回值榜晦,大括號需要指定明表達式返回了一個數(shù)值冠蒋。
使用 Lambda 表達式需要注意以下兩點:
- Lambda 表達式主要用來定義行內(nèi)執(zhí)行的方法類型接口。
- Lambda 表達式免去了使用匿名方法的麻煩乾胶,并且給予Java簡單但是強大的函數(shù)化的編程能力抖剿。
1.4 變量作用域
? lambda 表達式只能引用標(biāo)記了 final 的外層局部變量,這就是說不能在 lambda 內(nèi)部修改定義在域外的局部變量识窿,否則會編譯錯誤斩郎。
? lambda 表達式的局部變量可以不用聲明為 final,但是必須不可被后面的代碼修改(即隱性的具有 final 的語義)喻频。
? 在 Lambda 表達式當(dāng)中不允許聲明一個與局部變量同名的參數(shù)或者局部變量缩宜。
二、方法引用
2.1 基本概念
? 方法引用通過方法的名字來指向一個方法甥温。锻煌、
? 方法引用可以使語言的構(gòu)造更緊湊簡潔,減少冗余代碼姻蚓。
? 方法引用使用一對冒號 :: ;
2.2 引用方法
- 構(gòu)造器引用
Class::new 炼幔,或者更一般的Class<T>::new - 靜態(tài)方法引用
Class::static_method - 特定類的任意對象的方法引用
Class::method - 特定對象的方法引用
instance::method
三、函數(shù)式接口
3.1 基本概念
? 函數(shù)式接口就是一個有且僅有一個抽象方法史简,但是可以有多個非抽象方法的的接口乃秀。
? 函數(shù)式接口可以被隱式轉(zhuǎn)換為lambda表達式。
例:
@FunctionalInterface
interface GreetingService {
void sayMesage(String message);
}
? 如果使用lambda表達式來創(chuàng)建一個函數(shù)式接口實例圆兵,那這個lambda表達式的入?yún)⒑头祷乇仨毞线@個函數(shù)式接口中唯一的抽象方法的定義跺讯。
3.2 默認(rèn)方法
? 簡單說,默認(rèn)方法就是接口可以有實現(xiàn)方法殉农,而且不需要實現(xiàn)類去實現(xiàn)其方法刀脏。
? 我么只需在方法名前面加個 default 關(guān)鍵字即可實現(xiàn)默認(rèn)方法。
為什么要有這個特性超凳?
? 首先愈污,之前的接口是個雙刃劍耀态,好處是面向抽象而不是面向具體編程,缺陷是暂雹,當(dāng)需要修改接口時候首装,需要修改全部實現(xiàn)該接口的類,目前的 java 8 之前的集合框架沒有 foreach 方法杭跪,通常能想到的解決辦法是在JDK里給相關(guān)的接口添加新的方法及實現(xiàn)仙逻。然而,對于已經(jīng)發(fā)布的版本涧尿,是沒法在給接口添加新方法的同時不影響已有的實現(xiàn)系奉。所以引進的默認(rèn)方法。他們的目的是為了解決接口的修改與現(xiàn)有的實現(xiàn)不兼容的問題姑廉。
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問題,減少代碼中的判空,實現(xiàn)函數(shù)式編程桥言,給工程師們提供函數(shù)式的API瞬内。
? Optional 類是一個可以為null的容器對象。如果值存在則isPresent()方法會返回true限书,調(diào)用get()方法會返回該對象虫蝶。
? Optional 是個容器:它可以保存類型T的值,或者僅僅保存null倦西。Optional提供很多有用的方法能真,這樣我們就不用顯式進行空值檢測。
? Optional 類的引入很好的解決空指針異常扰柠。
4.2 類方法
修飾符和類型 | 方法 | 描述 |
---|---|---|
static<T> Optional<T> | empty() | 返回空的Optional實例 |
boolean | equals(Object obj) | 判斷其他對象是否等于Optional |
int | hashCode() | 返回存在值的哈希碼粉铐,如果值不存在 返回 0 |
static <T> Optional<T> | of(T value) | 返回一個指定非null值的Optional |
static <T> Optional<T> | ofNullable(T value) | 如果為非空,返回 Optional 描述的指定值卤档,否則返回空的 Optional |
T | orElse(T other) | 如果存在該值蝙泼,返回值, 否則返回 other |
T | orElseGet(Supplier<? extends T> other) | 如果存在該值劝枣,返回值汤踏, 否則觸發(fā) other,并返回 other 調(diào)用的結(jié)果 |
boolean | isPresent() | 如果值存在則方法會返回true舔腾,否則返回 false |
T | get() | 如果在這個Optional中包含這個值溪胶,返回值,否則拋出異常:NoSuchElementException |
String | toString() | 返回一個Optional的非空字符串稳诚,用來調(diào)試 |
Optional<T> | filter(Predicate<? super <T> predicate) | 如果值存在哗脖,并且這個值匹配給定的 predicate,返回一個Optional用以描述這個值,否則返回一個空的Optional |
Optional<U> | flatMap(Function<? super T, Optional<U>> mapper) | 如果值存在才避,返回基于Optional包含的映射方法的值橱夭,否則返回一個空的Optional |
void | ifPresent(Consumer<? super T> consumer) | 如果值存在則使用該值調(diào)用 consumer , 否則不做任何事情 |
Optional<U> | map(Function<? super T,? extends U> mapper) | 如果有值,則對其執(zhí)行調(diào)用映射函數(shù)得到返回值桑逝。如果返回值不為 null棘劣,則創(chuàng)建包含映射返回值的Optional作為map方法返回值,否則返回空Optional |
<X extends Throwable> T | orElseThrow(Supplier<? extends X> exceptionSupplier) | 如果存在該值肢娘,返回包含的值呈础,否則拋出由 Supplier 繼承的異常 |
五舆驶、Stream
5.1 基本概念
? Stream 使用一種類似用 SQL 語句從數(shù)據(jù)庫查詢數(shù)據(jù)的直觀方式來提供一種對 Java 集合運算和表達的高階抽象橱健。
? Stream API可以極大提高Java程序員的生產(chǎn)力,讓程序員寫出高效率沙廉、干凈拘荡、簡潔的代碼。
? 這種風(fēng)格將要處理的元素集合看作一種流撬陵, 流在管道中傳輸珊皿, 并且可以在管道的節(jié)點上進行處理, 比如篩選巨税, 排序蟋定,聚合等。
? 元素流在管道中經(jīng)過中間操作(intermediate operation)的處理草添,最后由最終操作(terminal operation)得到前面處理的結(jié)果驶兜。
流操作由3部分組成:
- 創(chuàng)建流
- 零個或多個中間操作
- 終止操作(到這一步才會執(zhí)行整個stream pipeline計算)
5.2 什么是stream
Stream(流)是一個來自數(shù)據(jù)源的元素隊列并支持聚合操作
- 元素是特定類型的對象,形成一個隊列远寸。 Java中的Stream并不會存儲元素抄淑,而是按需計算。
- 數(shù)據(jù)源 流的來源驰后。 可以是集合肆资,數(shù)組,I/O channel灶芝, 產(chǎn)生器generator 等郑原。
- 聚合操作 類似SQL語句一樣的操作, 比如filter, map, reduce, find, match, sorted等夜涕。
和以前的Collection操作不同颤专, Stream操作還有兩個基礎(chǔ)的特征:
- Pipelining: 中間操作都會返回流對象本身。 這樣多個操作可以串聯(lián)成一個管道钠乏, 如同流式風(fēng)格(fluent style)栖秕。 這樣做可以對操作進行優(yōu)化, 比如延遲執(zhí)行(laziness)和短路( short-circuiting)晓避。
- 內(nèi)部迭代:以前對集合遍歷都是通過Iterator或者For-Each的方式, 顯式的在集合外部進行迭代簇捍, 這叫做外部迭代只壳。 Stream提供了內(nèi)部迭代的方式, 通過訪問者模式(Visitor)實現(xiàn)暑塑。
5.3 創(chuàng)建流的方式:
-
通過Stream接口的of靜態(tài)方法創(chuàng)建一個流
Stream<String> stream1 = Stream.of("a", "b", "c");
-
創(chuàng)建一個空的流
Stream<Object> empty = Stream.empty();
-
通過builder創(chuàng)建
Stream<Object> build = Stream.builder().add("a").add("b").add("c").build();
-
通過Arrays類的stream方法吼句,實際上第一種of方法底層也是調(diào)用的Arrays.stream(values)
String[] array = new String[]{"hello","world","helloworld"}; Stream<String> stream3 = Arrays.stream(array);
-
通過集合的stream方法,該方法是Collection接口的默認(rèn)方法事格,所有集合都繼承了該方法
Stream<String> stream2 = Arrays.asList("hello","world","helloworld").stream();
-
合并多個Stream
Stream stream = Stream.concat(stream1,stream2);
-
generate()和iterate()
兩個都是生成一個無限的流惕艳,通常跟
limit()
一起使用,限制流中元素的個數(shù)驹愚。不同的是前者可以根據(jù)任何計算方式來生成远搪,后者只能根據(jù)給定的
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() ? 為集合創(chuàng)建串行流。
- parallelStream() ? 為集合創(chuàng)建并行流劫瞳。
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中重復(fù)的元素憔恳,它使用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 結(jié)束處理
-
迭代
對Stream內(nèi)的每一個元素進行循環(huán)處理屿附。forEach(System.out::println)
Stream<String> stream = Stream.of("a", "b", "c"); stream.forEach(System.out::print);
匹配
判斷Stream中的元素是否匹配某條件郎逃,返回boolean結(jié)果。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));
-
統(tǒng)計
count:統(tǒng)計Stream中元素的個數(shù)朵你。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));
-
規(guī)約
將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中的靜態(tài)方法來實現(xiàn)搞监。
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()));
-
計數(shù)
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)));
-
分區(qū)
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));
-
分區(qū)合計
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是非線程安全的腺逛,所有的日期類都是可變的 - 設(shè)計很差
Java的日期/時間類的定義并不一致荷愕,在java.util和java.sql的包中都有日期類衡怀,此外用于格式化和解析的類在java.text包中定義棍矛。
java.util.Date同時包含日期和時間,而java.sql.Date僅包含日期抛杨,將其納入java.sql包并不合理够委。
另外這兩個類都有相同的名字,這本身就是一個非常糟糕的設(shè)計怖现。 - 時區(qū)處理麻煩
Java8在java.time中提供的API:
- Local(本地)
簡化了日期時間的處理茁帽,沒有時區(qū)的問題 - Zoned(時區(qū))
通過制定的時區(qū)處理日期時間
新的java.time包涵蓋了所有處理日期,時間屈嗤,日期/時間潘拨,時區(qū),時刻(instants)饶号,過程(during)與時鐘(clock)的操作铁追。
6.2 關(guān)鍵類
java.time
包里有許多可以代表時間和日期的類。
-
Instant
類茫船,提供了一個機器視角的時間線琅束。 -
LocalDate
,LocalTime
和LocalDateTime
類提供了人類視角的日期和時間,不涉及到時區(qū)算谈。 -
ZoneId
,ZoneRules
和ZoneOffset
類描述了時區(qū)涩禀,時區(qū)偏移量和時區(qū)規(guī)則。 -
ZonedDateTime
類然眼,代表了與時區(qū)關(guān)聯(lián)的時間和日期艾船。OffsetDateTime
和OffsetTime
分別代表了日期和時間和時間。這些類描述了時區(qū)偏移。 -
Duration
類在秒和毫秒尺度丈量一段時間屿岂。 -
Period
類在年礁蔗、月和日尺度上丈量一段時間。
6.3 常用的API
-
獲取當(dāng)前日期時間
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();
-
當(dāng)前月的最后一天
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(); // 當(dāng)前時間加20分鐘 LocalTime timeAfterPlus = time.plusMinutes(20); // 當(dāng)前時間減2小時 LocalTime timeAfterMinus = time.minusHours(2); // 兩個時間間隔(單位:分鐘),如第二個參數(shù)比第一個大霉撵,結(jié)果為負(fù)數(shù) 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();
-
查看時區(qū)
// 獲得所有時區(qū) Set<String> allZone = ZoneId.getAvailableZoneIds(); ZoneId zone = ZoneId.systemDefault(); // 獲得美國時間 ZoneId zoneInUSA = ZoneId.of("America/New_York"); LocalTime timeInUSA = LocalTime.now(zoneInUSA);
-
時間戳轉(zhuǎn)換為日期
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"));
-
日期轉(zhuǎn)換為時間戳
// 第一種方式 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();