Java8如何使用Lambda表達(dá)式簡化代碼詳解
系統(tǒng)環(huán)境:
- Java JDK 版本:1.8
參考地址:
一、Lambda 表達(dá)式簡介
1、什么是 Lambda 表達(dá)式
Lambda 表達(dá)式是在 JDK 8 中引入的一個(gè)新特性,可用于取代大部分的匿名內(nèi)部類。使用 Lambda 表達(dá)式可以完成用少量的代碼實(shí)現(xiàn)復(fù)雜的功能,極大的簡化代碼代碼量和代碼結(jié)構(gòu)。同時(shí),JDK 中也增加了大量的內(nèi)置函數(shù)式接口供我們使用,使得在使用 Lambda 表達(dá)式時(shí)更加簡單、高效。
2、為什么需要 Lambda 表達(dá)式
談起為什么需要 Lambda 表達(dá)式,那得從函數(shù)式編程開始說起。函數(shù)式編程可以簡單說是一種編程規(guī)范,也就是如何編寫程序的方法論。它屬于結(jié)構(gòu)化編程的一種,主要思想是把運(yùn)算過程盡量寫成一系列嵌套的函數(shù)調(diào)用。函數(shù)式編程有很多優(yōu)點(diǎn),其中包括:
- 易于并發(fā)編程;
- 代碼的熱升級;
- 更方便的代碼管理;
- 代碼簡潔,開發(fā)快速;
- 接近自然語言,易于理解;
函數(shù)式編程在 C#、Python、JavaScript中都得到充分體現(xiàn),在 Java 8 版本中也得到了支持。最明顯的就是對 Lambda 表達(dá)式的支持。很多種跡象表明未來編程語言將是逐漸融合各自的特性,而不是單純的聲明式語言函數(shù)編程語言。將來聲明式編程語言借鑒函數(shù)編程思想,函數(shù)編程語言融合聲明式編程特性,這幾乎是一種必然趨勢。
在 Java 中主要引入 Lambda 表達(dá)式的作用是對現(xiàn)有編碼語義的優(yōu)化,減少語法冗余。輕量級的將代碼封裝為數(shù)據(jù),使代碼簡潔,易于理解。
二、函數(shù)式接口和定義
1、什么是函數(shù)式接口
函數(shù)式接口(Functional Interface)是一個(gè)有且僅有一個(gè)抽象方法,但是可以有多個(gè)非抽象方法的接口。Java 中函數(shù)式接口被隱式轉(zhuǎn)換為 Lambda 表達(dá)式,只有保證接口類中有且只有一個(gè)抽象方法,Java 中的 Lambda 表達(dá)式才能對該方法進(jìn)行推導(dǎo)。
2、函數(shù)式接口格式
在 Java 函數(shù)式接口類中,需要滿足接口類中只能有一個(gè)抽象方法??偨Y(jié)如下:
- 接口有且僅有一個(gè)抽象方法;
- 允許定義靜態(tài)方法;
- 允許定義默認(rèn)方法;
- 允許 java.lang.Object 中的 public 方法;
在創(chuàng)建函數(shù)式接口時(shí),可以在接口類上面加上@FunctionalInterface注解,這時(shí)編譯器就可以對接口結(jié)構(gòu)進(jìn)行強(qiáng)制檢查是否符合函數(shù)式接口規(guī)則,如果不符合規(guī)則就顯示錯(cuò)誤。當(dāng)然,這個(gè)注解只是用于檢查,即使不加上該注解,只要符合函數(shù)式接口規(guī)則一樣也是函數(shù)式接口。
下面創(chuàng)建個(gè)演示的函數(shù)式接口,如下:
// @FunctionalInterface 注解說明:
// 使用該注解來定義接口,編譯器會強(qiáng)制檢查接口是否符合函數(shù)式接口規(guī)則(有且僅有一個(gè)抽象方法),如果不符合則會報(bào)錯(cuò)。
@FunctionalInterface
public interface MyInterface{
/**
* 抽象方法(Jdk 8 以后接口類中方法可以省去 public abstract)
*/
public abstract [返回值類型] [方法名稱](參數(shù)列表);
/**
* 其它方法(Jdk 8 以后允許接口類中添加"默認(rèn)方法"與"靜態(tài)方法" )
*/
...(略)
}
按照上面函數(shù)式接口,定義一個(gè)示例的函數(shù)式接口類,代碼如下:
@FunctionalInterface
public interface MyCollection {
void push(List list);
}
3、函數(shù)式接口和 Lambda 表達(dá)式的關(guān)系
函數(shù)式接口和 Lambda 表達(dá)式的關(guān)系可以總結(jié)為:
- 函數(shù)式接口只包含一個(gè)操作方法;
- Lambda 表達(dá)式只能操作一個(gè)方法;
- Java 中的 Lambda 表達(dá)式核心就是一個(gè)函數(shù)式編程接口的實(shí)現(xiàn)。
4、當(dāng)前 JDK 8 中存在的函數(shù)式接口類
在 JDK 1.8 之前,已經(jīng)存在部分函數(shù)式接口,如下:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.nio.file.PathMatcher
- java.lang.reflect.InvocationHandler
- java.beans.PropertyChangeListener
- java.awt.event.ActionListener
- javax.swing.event.ChangeListener
在 JDK 8 中新增了函數(shù)接口 java.util.function 包,里面包含了很多用來支持 Java 的函數(shù)式編程的接口類,如下:
| 類名稱 | 描述信息 |
|---|---|
| BiConsumer<T,U> | 代表了一個(gè)接受兩個(gè)輸入?yún)?shù)的操作,并且不返回任何結(jié)果。 |
| BiFunction<T,U,R> | 代表了一個(gè)接受兩個(gè)輸入?yún)?shù)的方法,并且返回一個(gè)結(jié)果。 |
| BinaryOperator<T> | 代表了一個(gè)作用于于兩個(gè)同類型操作符的操作,并且返回了操作符同類型的結(jié)果。 |
| BiPredicate<T,U> | 代表了一個(gè)兩個(gè)參數(shù)的 boolean 值方法。 |
| BooleanSupplier | 代表了 boolean 值結(jié)果的提供方。 |
| Consumer<T> | 代表了接受一個(gè)輸入?yún)?shù)并且無返回的操作。 |
| DoubleBinaryOperator | 代表了作用于兩個(gè) double 值操作符的操作,并且返回了一個(gè) double 值的結(jié)果。 |
| DoubleConsumer | 代表一個(gè)接受 double 值參數(shù)的操作,并且不返回結(jié)果。 |
| DoubleFunction<R> | 代表接受一個(gè) double 值參數(shù)的方法,并且返回結(jié)果。 |
| DoublePredicate | 代表一個(gè)擁有 double 值參數(shù)的 boolean 值方法。 |
| DoubleSupplier | 代表一個(gè) double 值結(jié)構(gòu)的提供方。 |
| DoubleToIntFunction | 接受一個(gè) double 類型輸入,返回一個(gè) int 類型結(jié)果。 |
| DoubleToLongFunction | 接受一個(gè) double 類型輸入,返回一個(gè) long 類型結(jié)果。 |
| DoubleUnaryOperator | 接受一個(gè)參數(shù)同為類型 double,返回值類型也為 double。 |
| Function<T,R> | 接受一個(gè)輸入?yún)?shù),返回一個(gè)結(jié)果。 |
| IntBinaryOperator | 接受兩個(gè)參數(shù)同為類型 int,返回值類型也為 int。 |
| IntConsumer | 接受一個(gè) int 類型的輸入?yún)?shù),無返回值。 |
| IntFunction<R> | 接受一個(gè) int 類型輸入?yún)?shù),返回一個(gè)結(jié)果。 |
| IntPredicate | 接受一個(gè) int 輸入?yún)?shù),返回一個(gè)布爾值的結(jié)果。 |
| IntSupplier | 無參數(shù),返回一個(gè) int 類型結(jié)果。 |
| IntToDoubleFunction | 接受一個(gè) int 類型輸入,返回一個(gè)double類型結(jié)果。 |
| IntToLongFunction | 接受一個(gè) int 類型輸入,返回一個(gè) long 類型結(jié)果。 |
| IntUnaryOperator | 接受一個(gè)參數(shù)同為類型 int,返回值類型也為 int。 |
| LongBinaryOperator | 接受兩個(gè)參數(shù)同為類型 long,返回值類型也為 long。 |
| LongConsumer | 接受一個(gè) long 類型的輸入?yún)?shù),無返回值。 |
| LongFunction<R> | 接受一個(gè) long 類型輸入?yún)?shù),返回一個(gè)結(jié)果。 |
| LongPredicate | R接受一個(gè) long 輸入?yún)?shù),返回一個(gè)布爾值類型結(jié)果。 |
| LongSupplier | 無參數(shù),返回一個(gè)結(jié)果 long 類型的值。 |
| LongToDoubleFunction | 接受一個(gè) long 類型輸入,返回一個(gè) double 類型結(jié)果。 |
| LongToIntFunction | 接受一個(gè) long 類型輸入,返回一個(gè) int 類型結(jié)果。 |
| LongUnaryOperator | 接受一個(gè)參數(shù)同為類型 long,返回值類型也為 long。 |
| ObjDoubleConsumer<T> | 接受一個(gè) object 類型和一個(gè) double 類型的輸入?yún)?shù),無返回值。 |
| ObjIntConsumer<T> | 接受一個(gè) object 類型和一個(gè) int 類型的輸入?yún)?shù),無返回值。 |
| ObjLongConsumer<T> | 接受一個(gè) object 類型和一個(gè) long 類型的輸入?yún)?shù),無返回值。 |
| Predicate<T> | 接受一個(gè)輸入?yún)?shù),返回一個(gè)布爾值結(jié)果。 |
| Supplier<T> | 無參數(shù),返回一個(gè)結(jié)果。 |
| ToDoubleBiFunction<T,U> | 接受兩個(gè)輸入?yún)?shù),返回一個(gè) double 類型結(jié)果 |
| ToDoubleFunction<T> | 接受一個(gè)輸入?yún)?shù),返回一個(gè) double 類型結(jié)果。 |
| ToIntBiFunction<T,U> | 接受兩個(gè)輸入?yún)?shù),返回一個(gè) int 類型結(jié)果。 |
| ToIntFunction<T> | 接受一個(gè)輸入?yún)?shù),返回一個(gè) int 類型結(jié)果。 |
| ToLongBiFunction<T,U> | 接受兩個(gè)輸入?yún)?shù),返回一個(gè) long 類型結(jié)果。 |
| ToLongFunction<T> | 接受一個(gè)輸入?yún)?shù),返回一個(gè) long 類型結(jié)果。 |
| UnaryOperator<T> | 接受一個(gè)參數(shù)為類型 T,返回值類型也為 T。 |
5、JDK 中常見的函數(shù)式接口類
上面 java.util.function 包提供了眾多的函數(shù)式接口,其中常用的有:
- java.util.function.Predicate<T>:接收參數(shù)對象 T,返回一個(gè) boolean 類型結(jié)果。
- java.util.function.Comsumer<T>:接收參數(shù)對象 T,不返回結(jié)果。
- java.util.function.Function<T,R>:接收參數(shù)對象 T,返回結(jié)果對象 R。
- java.util.function.Supplier<T>:不接收參數(shù),提供 T 對象的創(chuàng)建工廠。
- java.util.function.UnaryOperator<T>:接收參數(shù)對象 T,返回結(jié)果對象 T。
- java.util.function.BinaryOperator<T>:接收兩個(gè) T 對象,返回一個(gè) T 對象結(jié)果。
注:為了使易懂,下面示例中 Lambda 表達(dá)式?jīng)]有使用的最簡易寫法,而是使用比較繁瑣的寫法。
(1)、java.util.function.Predicate<T>
接口類作用: 接收參數(shù)對象T,返回一個(gè) boolean 類型結(jié)果。
接口類源碼:
@FunctionalInterface
public interface Predicate<T> {
/** abstract 方法,接收一個(gè)參數(shù), 判斷這個(gè)參數(shù)是否匹配某種規(guī)則, 然后返回布爾值結(jié)果 */
boolean test(T t);
/** default 方法,接收另外一個(gè) Predicate<T> 類型參數(shù)進(jìn)行"邏輯與"操作,返回一個(gè)新的 Predicate */
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
/** default 方法,接收另外一個(gè) Predicate<T> 類型參數(shù)進(jìn)行"邏輯或"操作,返回一個(gè)新的 Predicate */
default Predicate<T> negate() {
return (t) -> !test(t);
}
/** default 方法,返回當(dāng)前 Predicate 取反操作之后的 Predicate */
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
使用示例:
public class PredicateExample {
/** 這里創(chuàng)建一個(gè) Prodicate,設(shè)置驗(yàn)證秘鑰的一個(gè)邏輯,然后返回并輸出驗(yàn)證結(jié)果 */
public static void main(String[] args) {
// 創(chuàng)建 Predicate 及 Lambda 表達(dá)式與待實(shí)現(xiàn)的邏輯
Predicate<String> validation = (String secret) -> {
return "123456".equals(secret);
};
// 調(diào)用 Predicate 提供的 test 方法并輸出結(jié)果
System.out.println(validation.test("123"));
System.out.println(validation.test("123456"));
}
}
日常開發(fā)中,需要對某個(gè)值進(jìn)行判斷操作,并且返回判斷結(jié)果,這時(shí)可以考慮使用 Predicate 接口,以及它的 Lambda 表達(dá)式的實(shí)現(xiàn),能方便的實(shí)現(xiàn)我們業(yè)務(wù)上的一些功能。
(2)、java.util.function.Comsumer<T>
接口類作用: 接收參數(shù)對象 T,不返回結(jié)果。
接口類源碼:
@FunctionalInterface
public interface Consumer<T> {
/** abstract 方法,接收一個(gè)參數(shù),執(zhí)行消費(fèi)邏輯 */
void accept(T t);
/** default 方法,將兩個(gè) Consumer 連接到一起,再進(jìn)行消費(fèi) */
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
使用示例:
/** 這里創(chuàng)建一個(gè) Consumer,模擬發(fā)送消息并打印內(nèi)容 */
public class ConsumerExample {
public static void main(String[] args) {
// 創(chuàng)建 Consumer 及 Lambda 表達(dá)式與待實(shí)現(xiàn)的邏輯
Consumer<String> consumer = (String message) -> {
System.out.println("發(fā)送消息內(nèi)容:" + message);
};
// 調(diào)用 Consumer 提供的 accept 方法
consumer.accept("測試消息");
}
}
日常開發(fā)中,需要對某個(gè)類型進(jìn)行公共處理,并且不需要任何返回值,這時(shí)可以考慮使用 Consumer 接口及它的 Lambda 表達(dá)式的實(shí)現(xiàn),能方便的實(shí)現(xiàn)我們業(yè)務(wù)上的一些功能。
(3)、java.util.function.Function<T,R>
接口類作用: 接收參數(shù)對象 T,返回結(jié)果對象 R。
接口類源碼:
@FunctionalInterface
public interface Function<T, R> {
/**abstract 方法,接收一個(gè)參數(shù)進(jìn)行處理,然后返回處理結(jié)果 R */
R apply(T t);
/** default 方法,先執(zhí)行參數(shù)(Function)的,再執(zhí)行調(diào)用者(Function) */
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
/** default 方法,先執(zhí)行調(diào)用者,再執(zhí)行參數(shù),和compose相反 */
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
/** 返回當(dāng)前正在執(zhí)行的方法 */
static <T> Function<T, T> identity() {
return t -> t;
}
}
使用示例:
/** 這里創(chuàng)建一個(gè) Function,對傳入的參數(shù)進(jìn)行驗(yàn)證,如果包含 a 字符就返回1,否則返回0 */
public class FunctionExample {
public static void main(String[] args) {
// 創(chuàng)建 Function 及 Lambda 表達(dá)式與待實(shí)現(xiàn)的邏輯
Function<String, Integer> function = (String str) -> {
return str.contains("a") ? 1 : 0;
};
// 調(diào)用 Function 提供的 apply 方法
System.out.println(function.apply("abcd"));
System.out.println(function.apply("efgh"));
}
}
日常開發(fā)中,需要對某個(gè)類型數(shù)據(jù)進(jìn)行操作,經(jīng)過一系列邏輯后轉(zhuǎn)換為一個(gè)新的類型進(jìn)行返回,這時(shí)可以考慮使用 Function 接口及它的 Lambda 表達(dá)式的實(shí)現(xiàn),能方便的實(shí)現(xiàn)我們業(yè)務(wù)上的一些功能。
(4)、java.util.function.Supplier<T>
接口類作用: 不接收參數(shù),提供 T 對象的創(chuàng)建工廠。
接口類源碼:
@FunctionalInterface
public interface Supplier<T> {
/** abstract 方法,設(shè)置業(yè)務(wù)邏輯,獲取邏輯中創(chuàng)建的對象 */
T get();
}
使用示例:
/** 這里創(chuàng)建一個(gè) Supplier,用于生成隨機(jī)ID,通過 get 方法獲取生成的隨機(jī)ID值 */
public class SupplierExample {
public static void main(String[] args) {
// 創(chuàng)建 Supplier 及 Lambda 表達(dá)式與待實(shí)現(xiàn)的邏輯
Supplier<String> supplier = () -> {
return UUID.randomUUID().toString();
};
// 調(diào)用 Supplier 提供的 get 方法
System.out.println(supplier.get());
}
}
日常開發(fā)中,需要創(chuàng)建一個(gè)統(tǒng)一的工廠用于生成特定的產(chǎn)物完成特定的目標(biāo),這時(shí)就可以考慮使用 Supplier 接口及它的 Lambda 表達(dá)式的實(shí)現(xiàn),能方便的實(shí)現(xiàn)我們業(yè)務(wù)上的一些功能。
(5)、java.util.function.UnaryOperator<T>
接口類作用: 接收參數(shù)對象 T,返回結(jié)果對象 T。
接口類源碼:
// 可以看到 UnaryOperator 繼承了 Function 接口
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
/** static 方法,接收一個(gè)參數(shù),然后對其處理后再返回 */
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
使用示例:
/** 這里創(chuàng)建一個(gè) UnaryOperator,接收一個(gè)字符串進(jìn)行加工處理后返回新字符串 */
public class UnaryOperatorExample {
public static void main(String[] args) {
// 創(chuàng)建 UnaryOperator 及 Lambda 表達(dá)式與待實(shí)現(xiàn)的邏輯
UnaryOperator<String> unaryOperator = (String str) -> {
return "[" + str + "]";
};
// 調(diào)用 UnaryOperator 繼承的 Function 提供的 apply 方法
System.out.println(unaryOperator.apply("hello"));
}
}
日常開發(fā)中,我們經(jīng)常要對一個(gè)已有的對象進(jìn)行操作修改,然后返回修改后的對象,這時(shí)就可以考慮使用 UnaryOperator 接口及它的 Lambda 表達(dá)式的實(shí)現(xiàn),能方便的實(shí)現(xiàn)我們業(yè)務(wù)上的一些功能。
(6)、java.util.function.BinaryOperator<T>
接口類作用: 接收兩個(gè) T 對象,返回一個(gè) T 對象結(jié)果。
接口類源碼:
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
/** abstract 方法,通過比較器Comparator來比較兩個(gè)元素中較小的一個(gè)作為返回值返回 */
public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
}
/** 通過比較器Comparator來比較兩個(gè)元素中較大的一個(gè)作為返回值返回 */
public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
}
}
使用示例:
/** 這里創(chuàng)建一個(gè) BinaryOperator,比較傳入的兩個(gè)參數(shù)哪個(gè)值最大,返回最大值 */
public class BinaryOperatorExample {
public static void main(String[] args) {
// 創(chuàng)建 BinaryOperator 及 Lambda 表達(dá)式與待實(shí)現(xiàn)的邏輯
BinaryOperator<Integer> binaryOperator = (Integer t1, Integer t2) -> {
return t1 > t2 ? t1 : t2;
};
// 調(diào)用 BinaryOperator 繼承的 BiFunction 提供的 apply 方法
System.out.println(binaryOperator.apply(1, 2));
}
}
在使用這幾種基本函數(shù)接口時(shí)傳入?yún)?shù) T 不能是基本類型,如 BinaryOperator 中 T 不能設(shè)置為 int,只能使用 Integer 包裝類,這也限制了 Lambda 表達(dá)式中設(shè)置參數(shù)時(shí)候,使用包裝類替換基本類型。
日常開發(fā)中,我們有時(shí)候會對兩個(gè)對象進(jìn)行操作,執(zhí)行一些操作邏輯后返回結(jié)果,這時(shí)就可以考慮使用 BinaryOperator 接口及它的 Lambda 表達(dá)式的實(shí)現(xiàn),能方便的實(shí)現(xiàn)我們業(yè)務(wù)上的一些功能。
三、Lambda 表達(dá)式基本語法
1、Lambda 表達(dá)式的組成
Lambda 表達(dá)式的組成可以拆分為:
- 聲明: 與 Lambda 表達(dá)式綁定的接口類型。
- 參數(shù): 參數(shù)包含在一對 () 中,和綁定的接口中的抽象方法中的參數(shù)個(gè)數(shù)及順序一致。
- 操作符: ->
- 執(zhí)行代碼塊: 執(zhí)行代碼塊包含在一對 {} 中,出現(xiàn)在操作符的右側(cè)。
[接口聲明] = (參數(shù)) -> {執(zhí)行代碼塊}
2、Lambda 表達(dá)式的格式
Lambda 表達(dá)式可以分為下面幾種格式:
- 無參數(shù),無返回值;
- 有一個(gè)參數(shù),無返回值;
- 左側(cè)只有一個(gè)參數(shù),小括號可以省略不寫;
- 有兩個(gè)以上參數(shù),有返回值,并且Lambda 體中有多條語句;
- 若右側(cè)Lambda體中,只有一條語句,return 和大括號都可以省略不寫;
- Lambda 表達(dá)式的參數(shù)列表的數(shù)據(jù)類型可以省略不寫,jvm編譯器會進(jìn)行上下文推斷出,數(shù)據(jù)類型“類型推斷”;
(1)、無參數(shù),無返回值
() -> System.out.println("測試");
(2)、有一個(gè)參數(shù),無返回值
(x) -> System.out.println(x);
(3)、左側(cè)只有一個(gè)參數(shù),小括號可以省略不寫
x -> System.out.println(x);
(4)、有兩個(gè)以上參數(shù),有返回值,并且Lambda 體中有多條語句
Comparator<Integer> comparator = (x, y) -> {
System.out.println("測試");
return Integer.compare(x,y);
};
(5)、若右側(cè)Lambda體中,只有一條語句,return 和大括號都可以省略不寫
Comparator<Integer> Comparator = (x, y) -> Integer.compare(x, y);
(6)、Lambda 表達(dá)式的參數(shù)列表的數(shù)據(jù)類型可以省略不寫,JVM 在運(yùn)行時(shí),會自動根據(jù)綁定的抽象方法中的參數(shù),進(jìn)行數(shù)據(jù)類型推導(dǎo)
(Integer x, Integer y) -> Integer.compare();
四、Lambda 表達(dá)式中變量作用域
Java 中的變量捕獲與變量隱藏:
- 變量捕獲: 局部類和匿名內(nèi)部類可以訪問被 final 修飾的封閉塊內(nèi)的局部變量。
- 變量隱藏: 在一個(gè)類中,子類中的成員變量如果和父類中的成員變量同名,那么即使他們類型不一樣,只要名字一樣,父類中的成員變量都會被隱藏。
在局部類和匿名內(nèi)部類都存在 變量捕獲 與 變量隱藏,而在 Lambda 表達(dá)式中則只支持 變量捕獲。
下面是對這作用域得演示示例:
(1)、匿名內(nèi)部類:
public class VariableExample {
/** 成員變量 */
String str1 = "成員變量";
public void innerClass() {
// 方法內(nèi)部變量
String str2 = "方法內(nèi)部變量";
// 使用匿名內(nèi)部類創(chuàng)建線程
new Thread(new Runnable() {
// 匿名內(nèi)部類內(nèi)部變量
String str3 = "匿名內(nèi)部類內(nèi)部變量";
@Override
public void run() {
/* 訪問變量 */
System.out.println("匿名內(nèi)部類輸出:" + str1);
System.out.println("匿名內(nèi)部類輸出:" + str2);
System.out.println("匿名內(nèi)部類輸出:" + str3);
/* 修改變量 */
str1 = "修改訪問成員變量";
// str2 = "修改訪問方法內(nèi)部變量"; // 不能進(jìn)行修改,默認(rèn)推導(dǎo)變量的修飾符 final
str3 = "修改訪問匿名內(nèi)部類內(nèi)部變量";
/* 在匿名內(nèi)部類中定義和類外部變量一樣名稱的變量 */
String str1 = "重新命名成員變量";
String str2 = "重新命名方法內(nèi)部變量";
}
}).start();
}
/** Main 方法 */
public static void main(String[] args) {
VariableExample variableExample = new VariableExample();
// 匿名內(nèi)部類
variableExample.innerClass();
}
}
(2)、Lambda 表達(dá)式:
public class VariableExample {
/** 成員變量 */
String str1 = "成員變量";
public void lambdaExpression() {
// 方法內(nèi)部變量
String str2 = "方法內(nèi)部變量";
new Thread(()->{
// Lambda 內(nèi)部變量
String str3 = "Lambda 內(nèi)部變量";
/* 訪問變量 */
// 訪問成員變量
System.out.println("Lambda 表達(dá)式輸出:" + str1);
// 訪問方法內(nèi)部變量
System.out.println("Lambda 表達(dá)式輸出:" + str2);
// 訪問匿名內(nèi)部類內(nèi)部變量
System.out.println("Lambda 表達(dá)式輸出:" + str3);
/* 修改變量 */
str1 = "修改訪問成員變量";
// str2 = "修改訪問方法內(nèi)部變量"; // 不能進(jìn)行修改,默認(rèn)推導(dǎo)變量的修飾符 final
str3 = "修改訪問匿名內(nèi)部類內(nèi)部變量";
/* 在 Lambda 中定義和類外部變量一樣名稱的變量 */
String str1 = "重新命名成員變量";
// String str2 = "重新命名方法內(nèi)部變量"; // 不能命名,lambda 不支持變量隱藏
}).start();
}
/** Main 方法 */
public static void main(String[] args) {
VariableExample variableExample = new VariableExample();
// 匿名內(nèi)部類
variableExample.innerClass();
// Lambda 表達(dá)式
variableExample.lambdaExpression();
}
}
五、Lambda 表達(dá)式方法重載問題
當(dāng)使用 Lambda 表達(dá)式,調(diào)用一個(gè)類中的重載方法,且方法中的參數(shù)為都為函數(shù)接口,函數(shù)接口中定義的方法接收的參數(shù)類型相同,這時(shí)候 Lambda 是無法推斷出要調(diào)用哪個(gè)方法。
函數(shù)接口A:
@FunctionalInterface
interface MyInterfaceA {
void push(String param);
}
函數(shù)接口B:
@FunctionalInterface
interface MyInterfaceB {
void pull(String param);
}
示例,實(shí)現(xiàn)方法重載與測試的 Main 方法:
public class LambdaOverloadExample {
// 重載方法A
public static void method(MyInterfaceA myInterfaceA) {
myInterfaceA.push("hello 1");
}
// 重載方法B
public static void method(MyInterfaceB myInterfaceB) {
myInterfaceB.pull("Hello 2");
}
/** Main 方法*/
public static void main(String[] args) {
// 使用匿名內(nèi)部類
method(new MyInterfaceA() {
@Override
public void push(String param) {
System.out.println(param);
}
});
method(new MyInterfaceB() {
@Override
public void pull(String param) {
System.out.println(param);
}
});
// 使用 Lambda 表達(dá)式
//method(param -> System.out.println(param)); // 編譯器提示錯(cuò)誤,表示無法推斷使用哪個(gè)參數(shù)
}
}
上面注掉的那部分代碼,在編輯器中直接提示錯(cuò)誤,很顯然 Lambda 表達(dá)式無法直接推斷出使用哪個(gè)類中的重載方法。其實(shí),只要明確告訴 Lambda 表達(dá)式使用哪個(gè)參數(shù),就可以很簡單的解決問題,比如以上面的例子,在 Lambda 表達(dá)式使用 method 方法時(shí),將參數(shù)類型轉(zhuǎn)換為對應(yīng)的要使用的類型就可以解決這個(gè)問題,代碼如下:
// 轉(zhuǎn)換參數(shù)為 MyInterfaceA method((MyInterfaceA)param -> System.out.println(param)); // 轉(zhuǎn)換參數(shù)為 MyInterfaceB method((MyInterfaceB)param -> System.out.println(param));
按上面進(jìn)行修改后就可以正常使用 Lambda 表達(dá)式了,如果不習(xí)慣也可以使用匿名內(nèi)部類進(jìn)行方法調(diào)用,內(nèi)名內(nèi)部類是沒有相關(guān)問題的。
六、Lambda 表達(dá)式方法引用
方法引用本質(zhì)上就是對方法調(diào)用的簡化,方法引用和函數(shù)式接口綁定,在使用過程中會創(chuàng)建函數(shù)式接口的實(shí)例,是結(jié)合 Lambda 表達(dá)式的一種特性。在應(yīng)用過程中,方法引用常分為:
- 靜態(tài)方法引用
- 實(shí)例方法引用
- 構(gòu)造方法引用
- 特定類型的任意對象實(shí)例方法引用
注意:在使用 Lmabda 方法引用時(shí)雖然能夠簡化代碼,但是在實(shí)際開發(fā)中不可因需要簡化代碼而過度使用方法引用,因?yàn)樗麜诤艽蟪潭壬辖档痛a可讀性。
1、創(chuàng)建示例的實(shí)體類
為了下面示例方便,我們首先創(chuàng)建一個(gè) Person 實(shí)體類,如下:
public class Person {
/** 姓名 */
private String name;
/** 歲數(shù) */
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{name=" + name + ",age=" + age + "}";
}
}
2、靜態(tài)方法引用示例
靜態(tài)方法的引用的使用: 靜態(tài)方法所在類.方法名稱() –> 靜態(tài)方法所在類 :: 方法名稱
創(chuàng)建一個(gè)使用靜態(tài)方法引用的示例類:
public class StaticMethodExample {
/** 測試的靜態(tài)方法 */
public static int compareAge(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
public static void main(String[] args) {
// 創(chuàng)建 Person 集合
List<Person> personList = new ArrayList<>();
personList.add(new Person("Wangqin",26));
personList.add(new Person("Liming",22));
personList.add(new Person("Alisi",18));
personList.add(new Person("Jerry",31));
// 按歲數(shù)進(jìn)行排序,使用靜態(tài)方法引用
personList.sort(StaticMethodExample::compareAge);
}
}
3、實(shí)例方法引用示例
實(shí)例方法的引用的使用:創(chuàng)建類型對應(yīng)一個(gè)對象 –> 對應(yīng)應(yīng)用 :: 實(shí)例方法名稱
創(chuàng)建一個(gè)封裝實(shí)例方法的類:
public class PersonUtil{
/** 測試的實(shí)例方法 */
public int compareAge(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
}
創(chuàng)建一個(gè)使用實(shí)例方法引用的示例類:
public class InstanceMethodExample {
public static void main(String[] args) {
// 創(chuàng)建 Person 集合
List<Person> personList = new ArrayList<>();
personList.add(new Person("Wangqin",26));
personList.add(new Person("Liming",22));
personList.add(new Person("Alisi",18));
personList.add(new Person("Jerry",31));
// 按歲數(shù)進(jìn)行排序,
PersonUtil personUtil = new PersonUtil();
// 引用實(shí)例方法
personList.sort(personUtil::compareAge);
}
}
4、構(gòu)造方法引用示例
構(gòu)造方法的引用的使用:綁定函數(shù)式接口
創(chuàng)建一個(gè)函數(shù)式接口,且設(shè)置接收參數(shù)和 Person 的構(gòu)造方法相同,返回 Person 對象的方法定義:
@FunctionalInterface
public interface PersonConstructor{
Person initInstance(String name, int age);
}
創(chuàng)建一個(gè)使用構(gòu)造方法引用的示例類
public class LambdaConstructorMethodExample {
public static void main(String[] args) {
// 創(chuàng)建 Person 集合
List<Person> personList = new ArrayList<>();
personList.add(new Person("Wangqin", 26));
personList.add(new Person("Liming", 22));
personList.add(new Person("Alisi", 18));
personList.add(new Person("Jerry", 31));
// 構(gòu)造方法引用
PersonConstructor personConstructor = Person::new;
Person person = personConstructor.initInstance("linda", 18);
System.out.println(person);
}
}
5、特定類型的任意對象實(shí)例方法引用示例
特定類型的任意對象實(shí)例方法引用示例:特定類型 :: 特定類型的方法
以下是對特定類型的任意對象的實(shí)例方法的引用的示例:
public class LambdaExample {
public static void main(String[] args) {
// 創(chuàng)建字符串集合
List<String> strList = new ArrayList<>();
strList.add("Jerry");
strList.add("Mini");
strList.add("Kary");
strList.add("walls");
// 使用集合的sort方法,按照String的compareToIgnoreCase進(jìn)行排序(比較字符串hash值)
strList.sort(String::compareToIgnoreCase);
System.out.println(strList);
}
}
這里根據(jù)定義的集合 strList 去推導(dǎo)目標(biāo)類型參數(shù)值,如果不符合后面?zhèn)魅氲姆椒ㄒ盟鶎?yīng)的類型,將報(bào)錯(cuò)。該方法參考等效 Lambda 表達(dá)式 String::compareToIgnoreCase 的參數(shù)列表 (String a, String b),其中 a 和 b 是用于更好地描述這個(gè)例子中的任意名稱。方法引用將調(diào)用該方法 a.compareToIgnoreCase(b)。
到此這篇關(guān)于Java8如何使用Lambda表達(dá)式簡化代碼的文章就介紹到這了,更多相關(guān)Java8用Lambda表達(dá)式簡化代碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java求素?cái)?shù)和最大公約數(shù)的簡單代碼示例
這篇文章主要介紹了Java求素?cái)?shù)和最大公約數(shù)的簡單代碼示例,其中作者創(chuàng)建的Fraction類可以用來進(jìn)行各種分?jǐn)?shù)運(yùn)算,需要的朋友可以參考下2015-09-09
Java實(shí)現(xiàn)Android拼圖游戲設(shè)計(jì)過程解析
這篇文章主要介紹了Java實(shí)現(xiàn)Android拼圖游戲設(shè)計(jì)過程解析,下面文章要接受的這是一款基于 Java 開發(fā)的移動端安卓小游戲,可以作為大家在學(xué)習(xí)期間的一個(gè)小練習(xí),接下來和小編一起進(jìn)入文章學(xué)習(xí)具體內(nèi)容吧2022-02-02
Java Date類常用示例_動力節(jié)點(diǎn)Java學(xué)院整理
在JDK1.0中,Date類是唯一的一個(gè)代表時(shí)間的類,但是由于Date類不便于實(shí)現(xiàn)國際化,所以從JDK1.1版本開始,推薦使用Calendar類進(jìn)行時(shí)間和日期處理。這里簡單介紹一下Date類的使用,需要的朋友可以參考下2017-05-05
Java實(shí)現(xiàn)讀取SFTP服務(wù)器指定目錄文件的方法
SFTP是一種在安全通道上傳輸文件的協(xié)議,它是基于SSH(Secure Shell)協(xié)議的擴(kuò)展,用于在客戶端和服務(wù)器之間進(jìn)行加密的文件傳輸,這篇文章主要介紹了Java實(shí)現(xiàn)讀取SFTP服務(wù)器指定目錄文件,感興趣的朋友跟隨小編一起看看吧2023-08-08
簡單操作實(shí)現(xiàn)Java jsp servlet文件上傳過程解析
這篇文章主要介紹了簡單操作實(shí)現(xiàn)Java jsp servlet文件上傳過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10
SpringBoot+Jpa項(xiàng)目配置雙數(shù)據(jù)源的實(shí)現(xiàn)
本文主要介紹了SpringBoot+Jpa項(xiàng)目配置雙數(shù)據(jù)庫源的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12
mybatis的insert語句插入數(shù)據(jù)時(shí)的返回值的實(shí)現(xiàn)
這篇文章主要介紹了mybatis的insert語句插入數(shù)據(jù)時(shí)的返回值的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
JavaWeb項(xiàng)目中dll文件動態(tài)加載方法解析(詳細(xì)步驟)
這篇文章主要介紹了JavaWeb項(xiàng)目中dll文件動態(tài)加載方法,步驟詳細(xì),在這里分享給大家,需要的朋友可以了解下。2017-09-09
Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(14)
下面小編就為大家?guī)硪黄狫ava基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧,希望可以幫到你2021-07-07
java實(shí)現(xiàn)統(tǒng)計(jì)字符串中大寫字母,小寫字母及數(shù)字出現(xiàn)次數(shù)的方法示例
這篇文章主要介紹了java實(shí)現(xiàn)統(tǒng)計(jì)字符串中大寫字母,小寫字母及數(shù)字出現(xiàn)次數(shù)的方法,涉及java針對字符串的遍歷、判斷、運(yùn)算相關(guān)操作技巧,需要的朋友可以參考下2019-06-06

