程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 深刻懂得Java中的Lambda表達式

深刻懂得Java中的Lambda表達式

編輯:關於JAVA

深刻懂得Java中的Lambda表達式。本站提示廣大學習愛好者:(深刻懂得Java中的Lambda表達式)文章只能為提供參考,不一定能成為您想要的結果。以下是深刻懂得Java中的Lambda表達式正文


 Java 8 開端湧現,帶來一個全新特征:應用 Lambda 表達式 (JSR-335) 停止函數式編程。明天我們要評論辯論的是 Lambda 的個中一部門:虛擬擴大辦法,也叫做公共辯解(defender)辦法。該特征可讓你在接口界說中供給辦法的默許完成。例如你可認為已有的接口(如 List 和 Map)聲明一個辦法界說,如許其他開辟者就無需從新完成這些辦法,有點像籠統類,但現實倒是接口。固然,Java 8 實際上照樣兼容已有的庫。

虛擬擴大辦法為 Java 帶來了多重繼續的特征,雖然該團隊宣稱與多重繼續分歧,虛擬擴大辦法被限制用於行動繼續。也許經由過程這個特征你可以看到了多重繼續的影子。但你照樣可以模仿實例狀況的繼續。我將在接上去的文章具體描寫 Java 8 中經由過程 mixin 混入完成狀況的繼續。

甚麼是混入 mixin?

混入是一種組合的籠統類,重要用於多繼續高低文中為一個類添加多個辦事,多重繼續將多個 mixin 組分解你的類。例如,假如你有一個類表現“馬”,你可以實例化這個類來創立一個“馬”的實例,然後經由過程繼續像“車庫”和“花圃”來擴大它,應用 Scala 的寫法就是:
 
val myHouse = new House with Garage with Garden

從 mixin 繼續其實不是一個特定的標准,這只是用來將各類功效添加到已有類的辦法。在 OOP 中,有了 mixin,你就有經由過程它來晉升類的可讀性。

例如在 Python 的  socketserver 模塊中就有應用 mixin 的辦法,在這裡,mixin 贊助 4 個基於分歧 Socket 的 辦事,包含支撐多過程的 UDP 和 TCP 辦事和支撐多線程的 UDP 和 TCP 辦事。
 

class ForkingUDPServer(ForkingMixIn, UDPServer): pass
class ForkingTCPServer(ForkingMixIn, TCPServer): pass
 
class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

甚麼是虛擬擴大辦法?


Java 8 將引入虛擬擴大辦法的概念,也叫 public defender method. 讓我們權且把這個概念簡化為 VEM。

VEM 旨在為 Java 接口供給默許的辦法界說,你可以用它在已有的接口中添加新的辦法界說,例如 Java 裡的聚集 API。如許相似 Hibernate 如許的第三方庫無需反復完成這些聚集 API 的一切辦法,由於曾經供給了一些默許辦法。

上面是若何在接口中界說辦法的示例:
 

public interface Collection<T> extends Iterable<T> {
 
  <R> Collection<R> filter(Predicate<T> p)
    default { return Collections.<T>filter(this, p); }
 
}

Java 8 對混入的模仿

如今我們來經由過程 VEM 完成一個混入後果,不外事前正告的是:請不要在任務中應用!

上面的完成不是線程平安的,並且還能夠存在內存洩漏成績,這取決於你在類中界說的 hashCode 和 equals 辦法,這也是別的一個缺陷,我將在前面評論辯論這個成績。

起首我們界說一個接口(模仿狀況Bean)並供給辦法的默許界說:
 

public interface SwitchableMixin {
  boolean isActivated() default { return Switchables.isActivated(this); }
  void setActivated(boolean activated) default { Switchables.setActivated(this, activated); }
}

然後我們界說一個對象類,包括一個 Map 實例來保留實例和狀況的聯系關系,狀況經由過程對象類中的公有的嵌套類代表:
 

public final class Switchables {
 
  private static final Map<SwitchableMixin, SwitchableDeviceState> SWITCH_STATES = new HashMap<>();
 
  public static boolean isActivated(SwitchableMixin device) {
    SwitchableDeviceState state = SWITCH_STATES.get(device);
    return state != null && state.activated;
  }
 
  public static void setActivated(SwitchableMixin device, boolean activated) {
    SwitchableDeviceState state = SWITCH_STATES.get(device);
    if (state == null) {
      state = new SwitchableDeviceState();
      SWITCH_STATES.put(device, state);
    }
    state.activated = activated;
  }
 
  private static class SwitchableDeviceState {
    private boolean activated;
  }
 
}

這裡是一個應用用例,凸起了狀況的繼續:
 

private static class Device {}
 
private static class DeviceA extends Device implements SwitchableMixin {}
 
private static class DeviceB extends Device implements SwitchableMixin {}

“完整分歧的器械”

下面的完成跑起來仿佛挺正常的,但 Oracle 的 Java 說話架構師 Brian Goetz 向我提出一個疑問說以後完成是沒法任務的(假定線程平安和內存洩漏成績已處理)
 

interface FakeBrokenMixin {
  static Map<FakeBrokenMixin, String> backingMap
    = Collections.synchronizedMap(new WeakHashMap<FakeBrokenMixin, String>());
 
  String getName() default { return backingMap.get(this); }
  void setName(String name) default { backingMap.put(this, name); }
}
 
interface X extends Runnable, FakeBrokenMixin {}
 
X makeX() { return () -> { System.out.println("X"); }; }
 
  X x1 = makeX();
  X x2 = makeX();
  x1.setName("x1");
  x2.setName("x2");
 
  System.out.println(x1.getName());
  System.out.println(x2.getName());


你猜這段代碼履行後會顯示甚麼成果呢?
疑問的處理

第一眼看去,這個完成的代碼沒有成績。X 是一個只包括一個辦法的接口,由於 getName 和 setName 曾經有了默許的界說,但 Runable 接口的 run 辦法沒有界說,是以我們可經由過程 lambda 表達式來生成 X 的實例,然後供給 run 辦法的完成,就像 makeX 那樣。是以,你願望這個法式履行後顯示的成果是:
 

x1
x2

假如你刪失落 getName 辦法的挪用,那末履行成果釀成:
 

MyTest$1@30ae8764
MyTest$1@123acf34

這兩行顯示出 makeX 辦法的履行來自兩個分歧的實例,而這時候以後 OpenJDK 8 生成的(這裡我應用的是 OpenJDK 8 24.0-b07).

不論如何,以後的 OpenJDK 8 其實不能反應終究的 Java 8 的行動,為懂得決這個成績,你須要應用特別參數 -XDlambdaToMethod 來運轉 javac 敕令,在應用了這個參數後,運轉成果釀成:

 

x2
x2

假如不挪用 getName 辦法,則顯示:
 

MyTest$$Lambda$1@5506d4ea
MyTest$$Lambda$1@5506d4ea

每一個挪用 makeX 辦法仿佛都是來自雷同匿名外部類的一個單例實例,假如不雅察包括編譯後的 java class 文件的目次,會發明並沒有一個名為 MyTestClass$$Lambda$1.class 的文件。

由於在編譯時,lambda 表達式並沒有經由完全的翻譯,現實上這個翻譯進程是在編譯和運轉時完成的,javac 編譯器將 lambda 表達式釀成 JVM 新增的指令 invokedynamic (JSR292)。這個指令包括一切必需的關於在運轉時履行 lambda 表達式的元信息。包含要挪用的辦法名、輸出輸入類型和一個名為 bootstrap 的辦法。bootstrap 辦法用於界說吸收此辦法挪用的實例,一旦 JVM 履行了 invokedynamic 指令,JVM 就會在特定的 bootstrap 上挪用 lambda 元工場辦法 (lambda metafactory method)。

再回到適才誰人疑問中,lambda 表達式轉成了一個公有的靜態辦法,() -> { System.out.println("X"); } 被轉到了 MyTest:
 

private static void lambda$0() {
  System.out.println("X");
}

假如你用 javap 反編譯器並應用 -private 參數便可以看到這個辦法,你也能夠應用 -c 參數來檢查加倍完全的轉換。

當你運轉法式時,JVM 會挪用 lambda metafactory method 來測驗考試闡釋 invokedynamic 指令。在我們的例子中,初次挪用 makeX 時,lambda metafactory method 生成一個 X 的實例並靜態鏈接 run 辦法到 lambda$0 辦法. X 的實例接上去被存儲在內存中,當第二次挪用 makeX 時就直接從內存中讀取這個實例,是以你第二次挪用的實例跟第一次是一樣的。
修復了嗎?有處理方法嗎?

今朝還沒有這個成績直接的修復或許是處理方法。雖然 Oracle 的 Java 8 籌劃默許激活-XDlambdaToMethod 參數,由於這個參數其實不是 JVM 標准的一部門,是以分歧供給商和 JVM 的完成是分歧的。對一個 lambda 表達式而言,你獨一能希冀的就是在類中完成你的接口辦法。


其他的辦法

到此為止,雖然我們對 mixin 的模擬其實不能兼容 Java 8,但照樣能夠經由過程多繼續和委派為已有的類添加多個辦事。這個辦法就是 virtual field pattern (虛擬字段形式).

所以來看看我們的 Switchable.
 

interface Switchable {  boolean isActive();
  void setActive(boolean active);
}

我們須要一個基於 Switchable 的接口,並供給一個附加的籠統辦法前往 Switchable 的完成。集成的辦法包括默許的界說,它們應用 getter 來轉換到 Switchable 完成的挪用:

 

public interface SwitchableView extends Switchable {
  Switchable getSwitchable();
 
 
  boolean isActive() default { return getSwitchable().isActive(); }
  void setActive(boolean active) default { getSwitchable().setActive(active); }
}

接上去,我們創立一個完全的 Switchable 完成:
 

public class SwitchableImpl implements Switchable {
 
 
  private boolean active;
 
 
  @Override
  public boolean isActive() {
    return active;
  }
 
 
  @Override
  public void setActive(boolean active) {
    this.active = active;
  }
}

這裡是我們應用虛擬字段形式的例子:
 

public class Device {}
 
 
public class DeviceA extends Device implements SwitchableView {
  private Switchable switchable = new SwitchableImpl();
 
 
  @Override
  public Switchable getSwitchable() {
    return switchable;
  }
}
 
 
public class DeviceB extends Device implements SwitchableView {
  private Switchable switchable = new SwitchableImpl();
 
 
  @Override
  public Switchable getSwitchable() {
    return switchable;
  }
}

結論

在這篇文章中,我們應用了兩種辦法經由過程 Java 8 的虛擬擴大辦法為類增長多個辦事。第一個辦法應用一個 Map 來存儲實例狀況,這個辦法很風險,由於不是線程平安並且存在內存洩漏成績,這完整依附於分歧的 JVM 對 Java 說話的完成。別的一個辦法是應用虛擬字段形式,經由過程一個籠統的 getter 來前往終究的完成實例。第二種辦法加倍自力並且加倍平安。

虛擬擴大辦法是 Java 的新特征,本文重要引見的是多重繼續的完成,具體你會有更深刻的研討和運用於其他方面,別忘了跟年夜家分享。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved