您好,登錄后才能下訂單哦!
如何正確的在Java8中使用lambda表達式?針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
1.基本介紹
lambda表達式,即帶有參數的表達式,為了更清晰地理解lambda表達式,先上代碼:
1.1 兩種方式的對比
1.1.1 方式1-匿名內部類
class Student{ private String name; private Double score; public Student(String name, Double score) { this.name = name; this.score = score; } public String getName() { return name; } public Double getScore() { return score; } public void setName(String name) { this.name = name; } public void setScore(Double score) { this.score = score; } @Override public String toString() { return "{" + "\"name\":\"" + name + "\"" + ", \"score\":\"" + score + "\"" + "}"; } }: @Test public void test1(){ List<Student> studentList = new ArrayList<Student>(){ { add(new Student("stu1",100.0)); add(new Student("stu2",97.0)); add(new Student("stu3",96.0)); add(new Student("stu4",95.0)); } }; Collections.sort(studentList, new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return Double.compare(o1.getScore(),o2.getScore()); } }); System.out.println(studentList); }
代碼調用Collections.sort方法對集合進行排序,其中第二個參數是一個匿名內部類,sort方法調用內部類中的compare方法對list進行位置交換,因為java中的參數類型只能是類或者基本數據類型,所以雖然傳入的是一個Comparator類,但是實際上可以理解成為了傳遞compare方法而不得不傳遞一個Comparator類 ,這種方式顯得比較笨拙,而且大量使用的話代碼嚴重冗余,這種情況在java8中通過使用lambda表達式來解決。
lambda表達式專門針對只有一個方法的接口(即函數式接口),Comparator就是一個函數式接口
@FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); }
@FunctionalInterface的作用就是標識一個接口為函數式接口,此時Comparator里只能有一個抽象方法,由編譯器進行判定。
使用lambda表達式之后方式1 中的代碼改造如下
1.1.2 方式2-lambda表達式
public void test1_(){ List<Student> studentList = new ArrayList<Student>(){ { add(new Student("stu1",100.0)); add(new Student("stu2",97.0)); add(new Student("stu3",96.0)); add(new Student("stu4",95.0)); } }; Collections.sort(studentList,(s1,s2)-> Double.compare(s1.getScore(),s2.getScore())); System.out.println(studentList); }
1.2 lambda語法
1.2.1 多參數
(1). lambda表達式的基本格式為(x1,x2)->{表達式...};
(2). 在上式中,lambda表達式帶有兩個參數,此時參數類型可以省略,但兩邊的括號不能省略
(3). 如果表達式只有一行,那么表達式兩邊的花括號可以省略
1.2.2 無參數
一個常見的例子是新建一個線程,不使用lambda表達式的寫法為
public void testThread(){ new Thread(new Runnable() { @Override public void run() { System.out.println("hello, i am thread!"); } }).start(); }
其中Runnable接口也是一個函數式接口,源碼如下
@FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }
將其轉換為lambda表達式的寫法為
public void testThread_(){ new Thread(()-> System.out.println("hello, i am thread!")).start(); }
對于沒有參數的情況 :
(1).參數的括號不能省略,
(2).其他語法同多參數
1.2.3 一個參數
我們構造一個只有一個參數的函數式接口
@FunctionalInterface public interface MyFunctionalInterface { public void single(String msg); } /** * 需要單個參數 */ public static void testOnePar(MyFunctionalInterface myFunctionalInterface){ myFunctionalInterface.single("msg"); } /** * 一個參數,可以省略參數的括號 */ @Test public void testOneParameter(){ testOnePar(x-> System.out.println(x)); }
對于一個參數的情況:
(1).可以省略參數的括號和類型
(2).其他語法同多參數
1.3 jdk提供的常用函數式接口
在這里我們為了演示只有一個參數的情況自己創建了一個函數式接口,其實java8中已經為我們提供了很多常見的函數式接口,截圖如下:
常見的有
Function:提供任意一種類型的參數,返回另外一個任意類型返回值。 R apply(T t);
Consumer:提供任意一種類型的參數,返回空值。 void accept(T t);
Supplier:參數為空,得到任意一種類型的返回值。T get();
Predicate:提供任意一種類型的參數,返回boolean返回值。boolean test(T t);
因此針對上面的情況,我們可以直接使用Consumer類,
/** * 需要單個參數 */ public static void testOnePar1(Consumer unaryOperator){ unaryOperator.accept("msg"); }
2.方法引用
lambda表達式用于替換函數式接口,方法引用也是如此,方法引用可以使代碼更加簡單和便捷
2.1 小試牛刀
上代碼,根據List中字符串長度排序:
public static void test1_() { List<String> strLst = new ArrayList<String>() { { add("adfkjsdkfjdskjfkds"); add("asdfasdfafgfgf"); add("public static void main"); } }; Collections.sort(strLst, String::compareToIgnoreCase); System.out.println(strLst); }
只要方法的參數和返回值類型與函數式接口中抽象方法的參數和返回值類型一致,就可以使用方法引用。
2.2 使用方式
方法引用主要有如下三種使用情況
(1). 類::實例方法
(2). 類::靜態方法
(3). 對象::實例方法
其中后兩種情況等同于提供方法參數的lambda表達式,
如System.out::println 等同于(x)->System.out.println(x),
Math::pow 等同于(x,y)->Math.pow(x,y).
第一種中,第一個參數會成為執行方法的對象,String::compareToIgnoreCase)等同于(x,y)->x.compareToIgnoreCase(y)
此外,方法引用還可以使用this::methodName及super::methodName表示該對象或者其父類對象中的方法
class Father { public void greet() { System.out.println("Hello, i am function in father!"); } } class Child extends Father { @Override public void greet() { Runnable runnable = super::greet; new Thread(runnable).start(); } } public static void main(String[] args){ new Child().greet(); }
最后打印的結果為:Hello, i am function in father!
3.構造器引用
構造器引用同方法引用類似,同樣作用于函數式接口
構造器引用的語法為 ClassName::new
啥也不說,線上代碼
List<String> labels = Arrays.asList("aaa","bbb","ccc","ddd"); Stream<Button> buttonStream = labels.stream().map(Button::new);
如上代碼所示,map方法內需要一個Function對象
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
調用Button的構造器,接收一個String類型的參數,返回一個Button類型的對象
public class Button extends ButtonBase { /** * Creates a button with the specified text as its label. * * @param text A text string for its label. */ public Button(String text) { super(text); initialize(); } }
另外一個例子如下
Button[] buttons1 = buttonStream.toArray(Button[]::new);
toArray方法的申明如下
<A> A[] toArray(IntFunction<A[]> generator);
接收一個IntFunction類型的接口R apply(int value);該接口接收一個int型參數,返回指定類型
調用數組的初始化方法剛好適合。
有一個簡單的構造器引用的例子如下:
public class LambdaTest3 { @Test public void test1_(){ List<Integer> list = this.asList(ArrayList::new ,1,2,3,4,5); list.forEach(System.out::println); } public <T> List<T> asList(MyCrator<List<T>> creator,T... a){ List<T> list = creator.create(); for (T t : a) list.add(t); return list; } } interface MyCrator<T extends List<?>>{ T create(); }
我們在項目中經常使用asList來創建一個ArrayList,但是也只能是ArrayList,
public static <T> List<T> asList(T... a) { return new ArrayList<>(a); }
我們如何在asList中指定創建哪種類型的List的實例呢,使用構造器引用使得asList方法可以指定生成的List類型。
4.自由變量的作用范圍
啥都不說,上代碼先:
public class LambdaTest4 { public void doWork1(){ Runnable runnable = ()->{ System.out.println(this.toString()); System.out.println("lambda express run..."); }; new Thread(runnable).start(); } public void doWork2(){ Runnable runnable = new Runnable() { @Override public void run() { System.out.println(this.toString()); System.out.println("anony function run..."); } }; new Thread(runnable).start(); } public static void main(String[] args) { new LambdaTest4().doWork1(); new LambdaTest4().doWork2(); } }
代碼中doWork1和doWork2分別使用lambda表達式和匿名內部類的方式實現了Runnable接口,最后打印的結果如下
com.java8.lambda.LambdaTest4@74f84cf
lambda express run...
com.java8.lambda.LambdaTest4$1@4295c176
anony function run...
可見使用lambda表達式的方式,表達式中的this指的是包含lambda表達式的類,而使用匿名內部類的方式,this指的是匿名內部類本身。
4.1 自由變量和閉包
lambda達式中的變量有幾類,1.參數內的變量,2.lambda表達式中的內部變量,3.自由變量,自由變量指的是在lambda表達式之外定義的變量。
包含自由變量的代碼則稱為閉包,如果理解了lambda表達式會在編譯階段被轉換為匿名內部類,那么可以很容易理解自由變量在lambda表達式中的作用范圍,在lambda表達式中會捕獲所有的自由變量,并且將變量定義為final類型,所以不能改變lambda表達式中自由變量的值,如果改變,那么首先就無法編譯通過。
對于引用類型(如ArrayList),final指的是引用指向的類始終不變,進行add操作是允許的,但是應該保證變量的線程安全。
代碼如下所示:
public class Outer { public AnnoInner getAnnoInner(int x) { int y = 100; return new AnnoInner() { int z = 100; @Override public int add() { return x + y + z; } }; } public AnnoInner AnnoInnergetAnnoInner1(List<Integer> list1) { List<Integer> list2 = new ArrayList<>(Arrays.asList(1, 2, 3)); return ()->{ list2.add(123); int count = 0; Iterator<Integer> it = list1.iterator(); while (it.hasNext()){ count+=it.next(); } Iterator<Integer> it1 = list2.iterator(); while (it1.hasNext()){ count+=it1.next(); } return count; }; } @Test public void test(){ AnnoInner res = new Outer().AnnoInnergetAnnoInner1(new ArrayList<>(Arrays.asList(1,2,3))); System.out.println(res.add()); } } interface AnnoInner { int add(); }
最后返回135
5.接口的靜態方法和默認方法
java8對于接口做出了種種改進,使得我們可以在接口中實現默認方法和靜態方法,見Comparator接口完整定義
@FunctionalInterface public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); default Comparator<T> reversed() { return Collections.reverseOrder(this); } default Comparator<T> thenComparing(Comparator<? super T> other) { Objects.requireNonNull(other); return (Comparator<T> & Serializable) (c1, c2) -> { int res = compare(c1, c2); return (res != 0) ? res : other.compare(c1, c2); }; } default <U> Comparator<T> thenComparing( Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator) { return thenComparing(comparing(keyExtractor, keyComparator)); } default <U extends Comparable<? super U>> Comparator<T> thenComparing( Function<? super T, ? extends U> keyExtractor) { return thenComparing(comparing(keyExtractor)); } default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) { return thenComparing(comparingInt(keyExtractor)); } default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) { return thenComparing(comparingLong(keyExtractor)); } default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor) { return thenComparing(comparingDouble(keyExtractor)); } public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() { return Collections.reverseOrder(); } @SuppressWarnings("unchecked") public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() { return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE; } public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) { return new Comparators.NullComparator<>(true, comparator); } public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) { return new Comparators.NullComparator<>(false, comparator); } public static <T, U> Comparator<T> comparing( Function<? super T, ? extends U> keyExtractor, Comparator<? super U> keyComparator) { Objects.requireNonNull(keyExtractor); Objects.requireNonNull(keyComparator); return (Comparator<T> & Serializable) (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1), keyExtractor.apply(c2)); } public static <T, U extends Comparable<? super U>> Comparator<T> comparing( Function<? super T, ? extends U> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2)); } public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2)); } public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2)); } public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) { Objects.requireNonNull(keyExtractor); return (Comparator<T> & Serializable) (c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2)); } }
在比較器接口中定義了若干用于比較和鍵提取的靜態方法和默認方法,默認方法的使用使得方法引用更加方便,例如使用java.util.Objects類中的靜態方法isNull和nonNull可以在Stream中很方便的進行null的判定(之后會有對于stream的介紹)。但是在接口中引入默認方法設計到一個問題,即
(1).接口中的默認方法和父類中方法的沖突問題
(2).接口之間引用的沖突問題
對于第一個沖突,java8規定類中的方法優先級要高于接口中的默認方法,所以接口中默認方法復寫Object類中的方法是沒有意義的,因為所有的接口都默認繼承自Object類使得默認方法一定會被覆蓋。
對于第二個沖突,java8強制要求子類必須復寫接口中沖突的方法。如下所示:
public class LambdaTest5 implements myInterface1, myInterface2 { @Override public void getName() { myInterface1.super.getName(); } public static void main(String[] args) { new LambdaTest5().getName(); } } interface myInterface1 { default void getName() { System.out.println("myInterface1 getName"); } ; } interface myInterface2 { default void getName() { System.out.println("myInterface2 getName"); } }
強制使用myInterface1中的getName方法
關于如何正確的在Java8中使用lambda表達式問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。