雖然從java8就開始支持lambda特性,如今java更是已經(jīng)升級(jí)了11等更高級(jí)的版本,但是本人一直使用的還是java8以下的語法,對(duì)于lambda語法更是甚少了解,剛開始接觸這個(gè)lambda還是十分不習(xí)慣的,感覺有點(diǎn)繞;怎么看怎么不順眼,在我看來,lambada特性單純只是為了簡(jiǎn)化java代碼的書寫的,所以實(shí)質(zhì)上并沒有什么用,甚至自己一度覺得很討厭,但作為Android開發(fā)者,為了更好的學(xué)習(xí)kotlin,也只能迎著頭皮接受這個(gè)自己討厭的東西;(獲取將來某一天會(huì)覺得他很香吧)開始學(xué)習(xí)java 8的lambda表達(dá)式。以下是針對(duì)一些教程以及本身對(duì)lambda的一些理解所作的作結(jié),寫的不好,還請(qǐng)大家指正;
學(xué)習(xí)的時(shí)候參考了很多資料,學(xué)著學(xué)著就越來越懵逼了,可能是看的資料太多反倒產(chǎn)生了影響。學(xué)完又忘記了。額,感覺自己在寫日記了。丟~~~
lambda表達(dá)式是什么?有什么作用?什么時(shí)候可以使用lambda表達(dá)式?
按照官方說法:lambda表達(dá)式就是一個(gè)匿名函數(shù),也就是沒有函數(shù)名的函數(shù)。什么叫沒有函數(shù)名?
//定義接口
public interface IUser {
String getUserName(String name);
}
public void test() {
//正常情況下的接口實(shí)現(xiàn)方法
IUser user = new IUser() {
@Override
public String getUserName(String name) {
return name;
}
};
system.out.print(user.getUserName("達(dá)文西"))
}
上面的new IUser{...}就是一個(gè)匿名函數(shù),所謂匿名,就是我完全不需要知道你的名字,我也不關(guān)心,我真正關(guān)心的只是你的內(nèi)容,你的實(shí)現(xiàn)方法;故此,如果按照以往的寫法,如果每一個(gè)匿名函數(shù)我都需要new xxx那這些沒用的代碼就現(xiàn)的很多余了,而用lambda表達(dá)式我便可以省略這些沒有用的內(nèi)容,就一句IUser user=name->name,簡(jiǎn)單?。?!。所以簡(jiǎn)單來說lambda表示是為了簡(jiǎn)化java書寫冗余的。如果你覺得沒有必要,那是真的就沒有必要-_-!!。
那什么情況下才能使用lambda表達(dá)式呢?那就是只有當(dāng)你的接口符合函數(shù)式接口的時(shí)候?什么是函數(shù)式接口?只有一個(gè)抽象方法的接口就是函數(shù)式接口。
上面的 IUser接口便是函數(shù)式接口;
再舉個(gè)例子
@FunctionalInterface
public interface Comparator<T>{
...
int compare(T o1,T o2);
}
其中的@FunctionalInterface是用來修飾函數(shù)式接口的,也就是說只要有這個(gè)修飾符,你的接口就必須有且僅有一個(gè)接口。如果你用了這個(gè)修飾符,還沒有來得及寫方法,那他會(huì)報(bào)No target method found的錯(cuò)誤,如果你的抽象方法不止一個(gè),那就會(huì)報(bào)Multiple non-overriding abstract methods found in interface xxx
lambda表達(dá)式語法
(參數(shù))->{實(shí)現(xiàn)方法}
小括號(hào)內(nèi)的語法與傳統(tǒng)方法參數(shù)列表一致:無參數(shù)則留空;多個(gè)參數(shù)則用逗號(hào)分隔。
-> 是新引入的語法格式,是運(yùn)算符,代表指向動(dòng)作。
大括號(hào)內(nèi)的語法與傳統(tǒng)方法體要求基本一致。
有時(shí)候()和{}甚至可以省略,比如上面提到的IUser user=name->name接口的實(shí)現(xiàn);
//完整的lambda的語法
IUser user0 = (String name) -> {
return name;
};
//省略{}
IUser user2 = (String uname) -> uname;
//省略()和{}
IUser user = name -> null;
總結(jié)表達(dá)式一些總結(jié):
- 當(dāng)方法體只有一行的時(shí)候,花括號(hào){}可以省略;
- 當(dāng)接口方法有返回值,且方法體只有一行的時(shí)候,省略花括號(hào){}的同時(shí)可省略return,直接寫返回的表達(dá)式/值即可,單行代碼的執(zhí)行結(jié)果會(huì)自動(dòng)返回;
- 當(dāng)無參數(shù)的時(shí)候,()不可以省略;
- 當(dāng)參數(shù)只有一個(gè)的時(shí)候,()可以省略;
- 當(dāng)參數(shù)的類型確定的時(shí)候,可以省略參數(shù)的類型;jvm在運(yùn)行時(shí),會(huì)自動(dòng)根據(jù)綁定的抽象方法中的參數(shù)進(jìn)行推導(dǎo);
//通常使用
Supplier<String> sup1 = new Supplier<String>() {
@Override
public String get() {
return "達(dá)文西";
}
};
//lambda表達(dá)式
Supplier<String> sup2 = ()-> "達(dá)文西";
JAVA內(nèi)置的4大核心函數(shù)式接口
- 消費(fèi)型接口
ConSumer void accept(T t)
典型的代表就是list集合的使用
private static void test4() {
String[] strs = new String[]{"1", "2", "3", "4", "5"};
List<String> list = Arrays.asList(strs);
//正常情況遍歷
for (String str : list) {
System.out.print(str + ";");
}
//使用lambda表達(dá)式進(jìn)行遍歷,其中forEach方法接收的就是一個(gè)消費(fèi)型接口;
list.forEach(str -> System.out.println(str + ";"));
}
- 供給型接口
Supplier T get() - 函數(shù)型接口
Function<T,R> R apply(T t)
public static void test5() {
//傳統(tǒng)方式
Runnable run1 = new Runnable() {
@Override
public void run() {
System.out.println("傳統(tǒng)方式的Runnable");
}
};
Thread t1 = new Thread(run1);
t1.start();
//使用lambda表達(dá)式
Thread t2 = new Thread(() -> System.out.println("----------lambda方式----------------"));
t2.start();
}
- 斷定型接口
Predicate boolean test(T t)
下面展示的是Java類型系統(tǒng)內(nèi)部的函數(shù)式接口
public static void test6() {
/* Java8中的java.util.function包中提供了一些常用的函數(shù)式功能接口*/
// 1.java.util.function.Predicate<T>
//接受參數(shù)T,返回一個(gè)Boolean類型的結(jié)果
Predicate<String> predicate = String::isEmpty;
boolean flag = predicate.test("");
System.out.println(flag);
// 2.java.util.function.Consumer<T>
// 接收一個(gè)參數(shù)T,不返回結(jié)果
Consumer<String> consumer = System.out::println;
consumer.accept("哈哈哈");
// 3.java.util.function.Function<T,R>
// 接收參數(shù)對(duì)象T,返回結(jié)果對(duì)象R
Function<String, Integer> function = (String userName) -> {
return userName.equals("admin") ? 1 : 0;
};
int funflag = function.apply("admin");
// 4.java.util.function.Supplier<T>
// 不接收參數(shù),返回T對(duì)象創(chuàng)建的工廠
Supplier<String> supplier = () -> UUID.randomUUID().toString();
String s = supplier.get();
// 5.java.util.UnaryOperator<T>
// 接收參數(shù)對(duì)象T,返回結(jié)果參數(shù)對(duì)象T
UnaryOperator<String> operator = (String userName) -> "Hello " + userName;
//6.java.util.BinaryOperator<T>
// 接收兩個(gè)T對(duì)象,返回一個(gè)T對(duì)象結(jié)果
BinaryOperator<String> binary = (String userName, String userId) -> {
return userName + ":" + userId;
};
}
靜態(tài)方法的引用
這個(gè)怎么解釋呢?我的理解就是,使用一個(gè)靜態(tài)的方法去替代你需要實(shí)現(xiàn)的接口方法,當(dāng)然前提是這個(gè)靜態(tài)方法必須跟你的接口方法是一樣的(即請(qǐng)求參數(shù)以及返回值一致)
例如:
//工具類Utils中含有一個(gè)靜態(tài)類
public class Utils {
public static final int compar(Integer p1, Integer p2) {
return p1 - p2;
}
}
//調(diào)用
List<Integer> list = Arrays.asList(33, 44, 55, 33, 22);
Collections.sort(list, Utils::compar2);
其實(shí)就相當(dāng)于你在你的實(shí)現(xiàn)類中,調(diào)用了外部的靜態(tài)方法類,且把參數(shù)傳遞過去之后獲取到返回值進(jìn)行返回(即沒有對(duì)參數(shù)進(jìn)行任何加工就給第三方去處理了。外包出去了....):
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Utils.compar(o1, o2);
}
});
上述的調(diào)用方法有一個(gè)專業(yè)的名字叫【方法應(yīng)用】,直接使用符號(hào) :: (兩個(gè)冒號(hào))。
除了上述的靜態(tài)方法的引用之外,還有以下常見的幾種引用方式:
1. 對(duì)象引用成員方法
對(duì)象名::方法名
2. 類名引用靜態(tài)方法
類名::靜態(tài)方法
3. 類的構(gòu)造器引用
類的構(gòu)造器(類似于構(gòu)造方法)引用要在創(chuàng)建對(duì)象的時(shí)候使用。
類名::new
4. 數(shù)組的構(gòu)造器引用
數(shù)組的構(gòu)造器引用在創(chuàng)建數(shù)組的時(shí)候使用。
數(shù)據(jù)類型[]::new
Stream概念的引入(要去做核酸了,今天先到這里)
在Java 8中,得益于Lambda所帶來的函數(shù)式編程,引入了一個(gè)全新的Stream概念,用于解決已有集合類庫既有的弊端。
讓我先來看兩個(gè)例子:
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("黃毛");list1.add("黃文名");list1.add("黃文雄");list1.add("黃文離");list1.add("黃文金");list1.add("葉小平");list1.add("黃狗");
System.out.println("------------方式1-------------");
List<String> list2 = new ArrayList<>();
list1.forEach(name -> {
if (name.startsWith("黃")) {
list2.add(name);
}
});
ArrayList<String> list3 = new ArrayList<>();
list2.forEach(name -> {
if (name.length() == 3) {
list3.add(name);
}
});
list3.forEach(name -> {
System.out.println("符合條件的姓名:" + name);
});
System.out.println("------------方式2-------------");
list1.stream().filter(name -> name.startsWith("黃")).filter(name -> name.length() == 3).forEach(name -> System.out.println("符合條件的姓名:" + name));
}
乍一看方式1跟方式2的結(jié)果是一樣的,都是想要遍歷出帶有三個(gè)字的姓黃的名字。但是很顯然第2種方式更為簡(jiǎn)潔,就一句話到底。這就是流式思想.
獲取流的方式
java.util.stream.Stream 是Java 8新加入的最常用的流接口。(這并不是一個(gè)函數(shù)式接口。)在 Java 8 中, 集合接口有兩個(gè)方法來生成流: ① stream() ? 為集合創(chuàng)建串行流。 ② parallelStream() ? 為集合創(chuàng)建并行流。
- 通過Collection(單列)集合獲取流
Stream stream(): 獲取一個(gè)流對(duì)象 - 通過Map(雙列)集合獲取流
通過Map集合獲取流(了解),Map集合不能直接獲取流對(duì)象, 只能間接獲取, 有三種間接獲取方式。
①. 先獲取Map集合中的所有的key, 然后獲取所有key的stream流
②. 先獲取Map集合中的所有的value,然后獲取所有value的stream流
③. 先獲取Map集合中的所有的Entry(鍵值對(duì)), 獲取所有entry的stream流。 - 通過數(shù)組獲取流
方式一:使用Stream的靜態(tài)方法of完成(推薦, 因?yàn)閰?shù)不僅可以傳遞數(shù)組, 也可以傳遞任意個(gè)數(shù)據(jù))
static Stream of(T… values): 根據(jù)一個(gè)數(shù)組獲取一個(gè)stream流
方式二: 使用Arrays的靜態(tài)方法stream完成
static Stream stream(T[] array) : 根據(jù)一個(gè)數(shù)組獲取對(duì)應(yīng)的stream流
流式布局常見用法:
| 方法名稱 | 方法作用 | 方法種類 | 是否支持鏈?zhǔn)秸{(diào)用 |
|---|---|---|---|
| forEach | 逐一處理 | 終結(jié)方法 | NO |
| count | 獲取個(gè)數(shù) | 終結(jié)方法 | NO |
| filter | 過濾 | 非終結(jié)方法 | YES |
| limit | 獲取前幾個(gè) | 非終結(jié)方法 | YES |
| skip | 跳過前幾個(gè) | 非終結(jié)方法 | YES |
| concat | 合并 | 非終結(jié)方法 | YES |
| map | 映射 | 非終結(jié)方法 | YES |
Stream中用于收集的方法:
1. 收集到集合中:
R collect(Collector collector):可以將流中的數(shù)據(jù)收集到集合。 參數(shù)Collector表示收集到哪種集合。Collector是一個(gè)接口,如果要傳遞需要傳遞Collector實(shí)現(xiàn)類對(duì)象, 這個(gè)實(shí)現(xiàn)類對(duì)象不能由我們自己去new,要通過Collectors工具類的靜態(tài)方法進(jìn)行獲取, 使用不同方法獲取的Collector,表示收集到了不同的集合中。
static Collector toList():如果使用toList獲取的Collector對(duì)象,表示將數(shù)據(jù)收集到List集合中。
static Collector toSet()::如果使用toSet獲取的Collector對(duì)象,表示將數(shù)據(jù)收集到Set集合中。
2. stream收集到數(shù)組
我們可以使用Stream中的toArray方法進(jìn)行收集
Object[] toArray(): 將流中的數(shù)據(jù)收集到數(shù)組, 返回Object[]
注意:
1. stream流不會(huì)改變?cè)磳?duì)象.Stream流中的非終結(jié)方法返回的都是Stream對(duì)象, 返回的Stream是一個(gè)新的Stream
2. Stream流只能一次性使用,不能多次操作,否則會(huì)報(bào)錯(cuò)。
3. stream流本身不保存任何元素.便于理解可以把流看成容器,但是它不是容器.流本身不保存任何元素,他保存的是一些的中間操作步驟.
4. stream流操作是延遲執(zhí)行的.當(dāng)調(diào)用終結(jié)方法時(shí),會(huì)按順序?qū)⒉僮鞑襟E執(zhí)行.
參考資料:
- https://www.runoob.com/java/java8-lambda-expressions.html
- https://blog.csdn.net/qq_29660549/article/details/106564964
- https://blog.csdn.net/weixin_45735355/article/details/119911204
- https://blog.csdn.net/fengkuagn123/article/details/106315317
- 關(guān)于stream的用法
- https://blog.csdn.net/weixin_45590174/article/details/103542176