λ表達(dá)式
什么是λ表達(dá)式
λ表達(dá)式有三部分組成:參數(shù)列表蜜托,箭頭(->)抄囚,以及一個(gè)表達(dá)式或者語(yǔ)句塊霉赡。
public int add(int x, int y) {
return x + y;
}
轉(zhuǎn)換為λ表達(dá)式
(int x, int y) -> x + y;
去除參數(shù)類型
(x, y) -> x + y;
無(wú)參 以及 只有一個(gè)參數(shù)
() -> { System.out.println("Hello Lambda!");
c -> { return c.size(); }
λ表達(dá)式的類型
λ表達(dá)式可以被當(dāng)做是一個(gè)Object(注意措辭)。λ表達(dá)式的類型幔托,叫做“目標(biāo)類型(target type)”穴亏。λ表達(dá)式的目標(biāo)類型是“函數(shù)接口(functional interface)”,這是Java8新引入的概念重挑。它的定義是:一個(gè)接口嗓化,如果只有一個(gè)顯式聲明的抽象方法,那么它就是一個(gè)函數(shù)接口谬哀。一般用@FunctionalInterface標(biāo)注出來(lái)(也可以不標(biāo))刺覆。舉例如下:
@FunctionalInterface
public interface Runnable{
void run();
}
public interface Callable<V>{
V call() throws Exception;
}
public interface Comparator<T>{
int compare(T o1, T o2); boolean equals(Object obj);
}
所以 可以定義
Runnable r1 = () -> {System.out.println("Hello Lambda!");};
思考:可否定義 Object o = () -> {System.out.println("Hello Lambda!");};
JDK常用的預(yù)定義接口函數(shù)
//入?yún)門(mén),返回R
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
//入?yún)門(mén)史煎,無(wú)返回值
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
//無(wú)入?yún)⑶迹祷豑(通常配合構(gòu)造方法)
@FunctionalInterface
public interface Supplier<T> {
T get();
}
//入?yún)門(mén),返回值為boolean
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
//入?yún)門(mén),U篇梭,返回值為R 類似還有BiConsumer等
@FunctionalInterface
public interface BiFunction<T, U> {
R accept(T t, U u);
}
方法引用
方法引用讓開(kāi)發(fā)者可以直接引用現(xiàn)存的方法氢橙、Java類的構(gòu)造方法或者實(shí)例對(duì)象。方法引用和Lambda表達(dá)式配合使用恬偷,使得java類的構(gòu)造方法看起來(lái)緊湊而簡(jiǎn)潔悍手,沒(méi)有很多復(fù)雜的模板代碼。
public static class Car {
public static Car create( final Supplier<Car> supplier ) {
return supplier.get();
}
public static void collide( final Car car ) {
System.out.println( "Collided " + car.toString() );
}
public void follow( final Car another ) {
System.out.println( "Following the " + another.toString() );
}
public void repair() {
System.out.println( "Repaired " + this.toString() );
}
}
第一種方法引用的類型是構(gòu)造器引用,語(yǔ)法是Class::new坦康,或者更一般的形式:Class<T>::new
竣付。注意:這個(gè)構(gòu)造器沒(méi)有參數(shù)。
Car car = Car.create(Car::new);
List<Car> cars = Arrays.asList(car);
第二種方法引用的類型是靜態(tài)方法引用滞欠,語(yǔ)法是Class::static_method卑笨。注意:這個(gè)方法接受一個(gè)Car類型的參數(shù)。
cars.forEach(Car::collide);
第三種方法引用的類型是某個(gè)類的成員方法的引用仑撞,語(yǔ)法是Class::method赤兴,注意,這個(gè)方法沒(méi)有定義入?yún)ⅲ?/p>
cars.forEach(Car::repair);
第四種方法引用的類型是某個(gè)實(shí)例對(duì)象的成員方法的引用隧哮,語(yǔ)法是instance::method桶良。注意:這個(gè)方法接受一個(gè)Car類型的參數(shù):
final Car police = Car.create(Car::new);
cars.forEach(police::follow);
流
什么是流
Stream 不是數(shù)據(jù)結(jié)構(gòu),不保存數(shù)據(jù)沮翔,它是有關(guān)算法和計(jì)算的陨帆,就如同一個(gè)高級(jí)版本的迭代器(Iterator),單向采蚀,不可往復(fù)疲牵,數(shù)據(jù)只能遍歷一次,遍歷過(guò)一次后即用盡了榆鼠,就好比流水從面前流過(guò)纲爸,一去不復(fù)返。同時(shí)又與迭代器不同妆够,迭代器只能串行操作识啦,Stream可以并行化操作。
流的構(gòu)成
當(dāng)我們使用一個(gè)流的時(shí)候神妹,通常包括三個(gè)基本步驟:
獲取一個(gè)數(shù)據(jù)源(source)→數(shù)據(jù)轉(zhuǎn)換→執(zhí)行操作獲取想要的結(jié)果颓哮,每次轉(zhuǎn)換原有 Stream 對(duì)象不改變,返回一個(gè)新的 Stream 對(duì)象(可以有多次轉(zhuǎn)換)鸵荠,這就允許對(duì)其操作可以像鏈條一樣排列冕茅,變成一個(gè)管道
,如下圖所示蛹找。
常用的構(gòu)建流的方式:
- 集合Collection
Collection.stream()
Collection.parallelStream()
- 數(shù)組
Stream.of(T[] tArray)
- 多個(gè)相同類型對(duì)象
Stream.of("chaimm","peter","john");
流的基本使用
常見(jiàn)操作
中間操作 | 無(wú)狀態(tài) | map (mapToInt, flatMap 等)姨伤、filter、peek |
有狀態(tài) | distinct熄赡、sorted姜挺、limit、skip | |
終結(jié)操作 | 非短路 | forEach彼硫、forEachOrdered炊豪、toArray凌箕、reduce、collect、min、 max仰税、 count |
短路操作 | anyMatch、allMatch芜壁、noneMatch、findFirst高氮、findAny |
Stream中的操作可以分為兩大類:中間操作與終結(jié)操作
中間操作(Intermediate):一個(gè)流可以后面跟隨零個(gè)或多個(gè) intermediate 操作慧妄。其目的主要是打開(kāi)流,做出某種程度的數(shù)據(jù)映射/過(guò)濾剪芍,然后返回一個(gè)新的流塞淹,交給下一個(gè)操作使用。這類操作都是惰性化的(lazy)罪裹,就是說(shuō)饱普,僅僅調(diào)用到這類方法,并沒(méi)有真正開(kāi)始流的遍歷状共。中間操作又可以分為無(wú)狀態(tài)(Stateless)操作與有狀態(tài)(Stateful)操作套耕,前者是指元素的處理不受之前元素的影響;后者是指該操作只有拿到所有元素之后才能繼續(xù)下去峡继。
終結(jié)操作(Terminal):一個(gè)流只能有一個(gè) terminal 操作冯袍,當(dāng)這個(gè)操作執(zhí)行后,流就被使用“光”了鬓椭,無(wú)法再被操作颠猴。所以這必定是流的最后一個(gè)操作关划。Terminal 操作的執(zhí)行小染,才會(huì)真正開(kāi)始流的遍歷,并且會(huì)生成一個(gè)結(jié)果贮折。終結(jié)操作又可以分為短路與非短路操作裤翩,短路是指遇到某些符合條件的元素就可以得到最終結(jié)果,比如找到第一個(gè)滿足條件的元素。而非短路是指必須處理所有元素才能得到最終結(jié)果调榄。
map/flatMap
對(duì)流中的每個(gè)元素執(zhí)行一個(gè)函數(shù)踊赠,使得元素轉(zhuǎn)換成另一種類型輸出。
map 一對(duì)一 (入?yún)?Function<T, R>)
List<Person> persons = new ArrayLisy<>();
List<Long> result = persons.stream().map(x -> x.getId())
.collect(Collectors.toList());
List<Student> result = persons.stream().map(x -> {
Student s = new Student();
s.setName(x.getName());
return s;
}).collect(Collectors.toList());
flatMap 一對(duì)多 (入?yún)?Function<T, ? extends Stream>)
List<List<Person>> listAll = new ArrayList<>();
List<Person> result = listAll.stream().flatMap(x -> x.stream())
.collect(Collectors.toList());
List<Student> result = listAll.stream().flatMap(x -> x.stream())
.map(x -> {
Student s = new Student();
s.setName(x.getName());
return s;
}).collect(Collectors.toList());
filter
filter 對(duì)原始 Stream 進(jìn)行某項(xiàng)測(cè)試每庆,通過(guò)測(cè)試的元素被留下來(lái)生成一個(gè)新 Stream筐带。 (入?yún)?Predicate)
Integer[] nums = new Integer[]{1, 2, 3, 4, 5, 6};
Integer[] evens = Stream.of(nums).filter(n -> n%2 == 0)
.toArray(Integer[]::new);
List<Student> result = persons.stream().filter(x -> x.getAge() < 20)
.map(x -> {
Student s = new Student();
s.setName(x.getName());
return s;
}).collect(Collectors.toList());
peek
peek 方法我們可以拿到元素,然后做一些其他事情缤灵。(入?yún)?Consumer)
List<Long> result = persons.stream().map(x -> x.getId())
.peek(x -> System.out.println(x))
.collect(Collectors.toList());
Map<Long, Person> map = new HashMap<>();
List<Student> result = persons.stream()
.filter(x -> x.getAge() < 20 )
.peek(x -> map.put(x.getId(), x) )
.map(x -> {
Student s = new Student();
s.setName(x.getName());
return s;
}).collect(Collectors.toList());
limit/skip
limit 返回 Stream 的前面 n 個(gè)元素伦籍,skip 則是扔掉前 n 個(gè)元素
List<Long> result = persons.stream().map(x -> x.getId())
.limit(10).skip(3).collect(Collectors.toList());
sorted
對(duì)元素進(jìn)行排序 (入?yún)?Comparator)
List<Person> personList2 = persons.stream()
.sorted((p1, p2) -> p1.getName().compareTo(p2.getName()))
.collect(Collectors.toList());
forEach
對(duì)元素進(jìn)行遍歷消費(fèi) (入?yún)?Consumer)
persons.stream().sorted((p1, p2) -> p1.getName().compareTo(p2.getName()))
.forEach(peron -> {
System.out.println(persion.getName());
});
collect
對(duì)元素進(jìn)行收集
List<Person> personList2 = persons.stream()
.sorted((p1, p2) -> p1.getName().compareTo(p2.getName()))
.collect(Collectors.toList());
List<Person> personList2 = persons.stream()
.sorted((p1, p2) -> p1.getName().compareTo(p2.getName()))
.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
Collectors.toMap
轉(zhuǎn)換為MAP
Map<Long, String> personIdNameMap = persons.stream()
.collect(Collectors.toMap(Person::getId, Person::getName);
Map<Long, String> personIdNameMap = persons.stream()
.collect(Collectors.toMap(Person::getId, Person::getName, (v1,v2)->v1);
Collectors.groupingBy
分組
Map<Integer, List<Person>> personAgeMap = persons.stream()
.collect(Collectors.groupingBy(Person::getAge));
Map<Integer, Map<Long, Person>> personAgeMap = persons.stream()
.collect(Collectors.groupingBy(Person::getAge,
Collectors.toMap(Person::getId, Function.identity())));
Collectors.collectingAndThen
收集然后處理
Map<Integer, Integer> personAgeMap = persons.stream()
.collect(Collectors.groupingBy(Person::getAge,
Collectors.collectingAndThen(
Collectors.toList(),
list->list.size()
)));
接口方法
接口default方法
默認(rèn)方法使得開(kāi)發(fā)者可以在不破壞兼容性的前提下蓝晒,往現(xiàn)存接口中添加新的方法,即不強(qiáng)制那些實(shí)現(xiàn)了該接口的類也同時(shí)實(shí)現(xiàn)這個(gè)新加的方法帖鸦。
默認(rèn)方法和抽象方法之間的區(qū)別在于抽象方法需要實(shí)現(xiàn)芝薇,而默認(rèn)方法不需要。接口提供的默認(rèn)方法會(huì)被接口的實(shí)現(xiàn)類繼承或者覆寫(xiě)
private interface HelloService {
default String sayHello() {
return "hello";
}
}
private static class HelloImpl implements HelloService {
}
private static class HelloWorldImpl implements HelloService {
@Override
public String sayHello() {
return "hello world";
}
}
思考:為啥要加入default方法作儿?
接口static方法
private interface HelloService {
static boolean testHello(String s) {
return Objects.equals(s,"hello");
}
}
Stream流水線解決方案
Stage(Pipeline)
java8用Stage來(lái)記錄Stream的中間操作洛二,很多Stream操作會(huì)需要一個(gè)回調(diào)函數(shù)(Lambda表達(dá)式),因此一個(gè)完整的操作是<數(shù)據(jù)來(lái)源攻锰,操作晾嘶,回調(diào)函數(shù)>構(gòu)成的三元組。Stream中使用Stage的概念來(lái)描述一個(gè)完整的操作娶吞,并用某種實(shí)例化后的Pipeline來(lái)代表Stage变擒,然后將具有先后順序的各個(gè)Stage連到一起,就構(gòu)成了整個(gè)流水線寝志。
Sink
有了操作娇斑,我們需要將所有操作疊加起來(lái),讓流水線起到應(yīng)有的作用材部,java用Sink來(lái)協(xié)調(diào)相鄰Stage之間的調(diào)用關(guān)系毫缆。每個(gè)Stage必須實(shí)現(xiàn)opWrapSink方法。Sink接口的主要方法如下
方法名 | 作用 |
---|---|
void begin(long size) | 開(kāi)始遍歷元素之前調(diào)用該方法乐导,通知Sink做好準(zhǔn)備 |
void end() | 所有元素遍歷完成之后調(diào)用苦丁,通知Sink沒(méi)有更多的元素了 |
boolean cancellationRequested() | 是否可以結(jié)束操作,可以讓短路操作盡早結(jié)束 |
void accept(T t) | 遍歷元素時(shí)調(diào)用物臂,接受一個(gè)待處理元素旺拉,并對(duì)元素進(jìn)行處理 |
每個(gè)Stage都會(huì)將自己的操作封裝到一個(gè)Sink里,前一個(gè)Stage只需調(diào)用后一個(gè)方法即可棵磷,并不需要知道其內(nèi)部是如何處理的蛾狗。當(dāng)然對(duì)于有狀態(tài)的操作,Sink的begin()和end()方法也是必須實(shí)現(xiàn)的仪媒。比如Stream.sorted()是一個(gè)有狀態(tài)的中間操作沉桌,其對(duì)應(yīng)的Sink.begin()方法可能創(chuàng)建一個(gè)乘放結(jié)果的容器,而accept()方法負(fù)責(zé)將元素添加到該容器算吩,最后end()負(fù)責(zé)對(duì)容器進(jìn)行排序留凭。對(duì)于短路操作,Sink.cancellationRequested()也是必須實(shí)現(xiàn)的偎巢,比如Stream.findFirst()是短路操作蔼夜,只要找到一個(gè)元素,cancellationRequested()就應(yīng)該返回true压昼,以便調(diào)用者盡快結(jié)束查找求冷。Sink的四個(gè)接口方法常常相互協(xié)作翠订,共同完成計(jì)算任務(wù)。實(shí)際上Stream API內(nèi)部實(shí)現(xiàn)的的本質(zhì)遵倦,就是如何重載Sink的這四個(gè)接口方法尽超。
map方法的主要實(shí)現(xiàn)
public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
Objects.requireNonNull(mapper);
return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
@Override
Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
return new Sink.ChainedReference<P_OUT, R>(sink) {
@Override
public void accept(P_OUT u) {
downstream.accept(mapper.apply(u));
}
};
}
};
}
static abstract class ChainedReference<T, E_OUT> implements Sink<T> {
protected final Sink<? super E_OUT> downstream;
public ChainedReference(Sink<? super E_OUT> downstream) {
this.downstream = Objects.requireNonNull(downstream);
}
@Override
public void begin(long size) {
downstream.begin(size);
}
@Override
public void end() {
downstream.end();
}
@Override
public boolean cancellationRequested() {
return downstream.cancellationRequested();
}
}
// Stream.sort()方法用到的Sink實(shí)現(xiàn)
class RefSortingSink<T> extends AbstractRefSortingSink<T> {
private ArrayList<T> list;// 存放用于排序的元素
RefSortingSink(Sink<? super T> downstream, Comparator<? super T> comparator) {
super(downstream, comparator);
}
@Override
public void begin(long size) {
...
// 創(chuàng)建一個(gè)存放排序元素的列表
list = (size >= 0) ? new ArrayList<T>((int) size) : new ArrayList<T>();
}
@Override
public void end() {
list.sort(comparator);// 只有元素全部接收之后才能開(kāi)始排序
downstream.begin(list.size());
if (!cancellationWasRequested) {// 下游Sink不包含短路操作
list.forEach(downstream::accept);// 2. 將處理結(jié)果傳遞給流水線下游的Sink
}
else {// 下游Sink包含短路操作
for (T t : list) {// 每次都調(diào)用cancellationRequested()詢問(wèn)是否可以結(jié)束處理。
if (downstream.cancellationRequested()) break;
downstream.accept(t);// 2. 將處理結(jié)果傳遞給流水線下游的Sink
}
}
downstream.end();
list = null;
}
@Override
public void accept(T t) {
list.add(t);// 1. 使用當(dāng)前Sink包裝動(dòng)作處理t梧躺,只是簡(jiǎn)單的將元素添加到中間列表當(dāng)中
}
}
多個(gè)Sink疊加
多個(gè)Stage組成的鏈路如圖所示似谁,那么什么時(shí)候出發(fā)執(zhí)行結(jié)束操作(Terminal Operation),一旦調(diào)用某個(gè)結(jié)束操作掠哥,就會(huì)觸發(fā)整個(gè)流水線的執(zhí)行巩踏。
final <P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator){
...
if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {
wrappedSink.begin(spliterator.getExactSizeIfKnown());// 通知開(kāi)始遍歷
spliterator.forEachRemaining(wrappedSink);// 迭代
wrappedSink.end();// 通知遍歷結(jié)束
}
...
}
元空間
永久代的消除
從JDK1.7開(kāi)始,貯存在永久代的一部分?jǐn)?shù)據(jù)已經(jīng)轉(zhuǎn)移到了Java Heap或者是Native Heap续搀。符號(hào)引用(Symbols)轉(zhuǎn)移到了native heap;字面量(interned strings)轉(zhuǎn)移到了java heap;類的靜態(tài)變量(class statics)轉(zhuǎn)移到了java heap塞琼。但永久代仍然存在于JDK7,并沒(méi)有完全的移除禁舷。在JDK1.8版本中永久帶被徹底移除彪杉。永久代的參數(shù)-XX:PermSize和-XX:MaxPermSize也被移除。該參數(shù)在JDK1.8使用會(huì)有警告
元空間
JDK1.8將類信息存儲(chǔ)在元空間中牵咙,元空間并不在虛擬機(jī)中派近,而是使用本地內(nèi)存。因此洁桌,默認(rèn)情況下渴丸,元空間的大小僅受本地內(nèi)存限制,但可以通過(guò)以下參數(shù)來(lái)指定元空間的大辛砹琛:
-XX:MetaspaceSize谱轨,初始空間大小,達(dá)到該值就會(huì)觸發(fā)垃圾收集進(jìn)行類型卸載吠谢,同時(shí)GC會(huì)對(duì)該值進(jìn)行調(diào)整:如果釋放了大量的空間土童,就適當(dāng)降低該值;如果釋放了很少的空間囊卜,那么在不超過(guò)MaxMetaspaceSize時(shí)娜扇,適當(dāng)提高該值。
-XX:MaxMetaspaceSize栅组,最大空間,默認(rèn)是沒(méi)有限制的(取決于內(nèi)存)枢析。