純Java中的函數式編程:Functor和Monad示例

2020-12-18 Java從零開始

絕大多數程式設計師,特別是那些沒有功能編程背景的程式設計師,都傾向於認為monad是某種神秘的計算機科學概念,因此從理論上講,它對他們的編程事業沒有幫助。這種消極的觀點可以歸因於數十篇文章或博客文章過於抽象或過於狹窄。但是事實證明,即使在標準Java庫中,monad也無處不在,尤其是從Java Development Kit(JDK)8開始(以後會有更多介紹)。絕對妙不可言的是,一旦您第一次了解monad,突然之間就會有幾個完全不相同的目的無關的類和抽象變得熟悉。

Monad概括了各種看似獨立的概念,因此學習Monad的另一種化身只需很少的時間。例如,您不必學習CompletableFuture在Java 8中的工作方式-一旦意識到它是monad,就可以精確地知道它的工作方式,以及從其語義中可以期待什麼。然後您會聽說RxJava聽起來有很大不同,但是由於Observable是monad,因此沒有太多要添加的內容。您已經不知不覺中已經遇到過許多其他的Monads示例。因此,即使您實際上沒有使用RxJava,本節也將是有用的複習。

Functors

在解釋什麼是monad之前,讓我們研究一個稱為functor的簡單結構。Functors是封裝某些值的類型化數據結構。從語法的角度來看,Functors是具有以下API的容器:

import java.util.function.Function;

interface Functor<T> {

<R> Functor<R> map(Function<T, R> f);

}

但是僅語法是不足以了解什麼是Functors。functor提供的唯一操作是帶有函數f的map()。此函數接收框內的任何內容,對其進行轉換並將結果按原樣包裝到另一個Functors中。請仔細閱讀。Functor <T>始終是一個不可變的容器,因此map不會使執行該操作的原始對象發生突變。相反,它將返回包裝在全新Functors中的結果(或結果-請耐心等待),Functors可能是類型R。另外,Functors在應用標識函數(即map(x-> x))時不應執行任何操作。這種模式應始終返回相同的Functors或相等的實例。

通常將Functor <T>與保存T的實例進行比較,其中與該值交互的唯一方法是對其進行轉換。但是,沒有從Functors解開或逃逸的慣用方法。值始終位於Functors的上下文內。Functors為什麼有用?它們使用一個統一的,適用於所有集合的API概括了集合,promise,Optionals等多個常見習語。讓我介紹幾個Functors,以使您更流暢地使用此API:

interface Functor<T,F extends Functor<?,?>> {

<R> F map(Function<T,R> f);

}

class Identity<T> implements Functor<T,Identity<?>> {

private final T value;

Identity(T value) { this.value = value; }

public <R> Identity<R> map(Function<T,R> f) {

final R result = f.apply(value);

return new Identity<>(result);

}

}

需要額外的F類型參數來進行Identity編譯。在前面的示例中,您看到的是最簡單的Functors,僅包含一個值。您只能在map方法內部對其進行轉換,但是無法提取它。這被認為超出了純Functors的範圍。與Functors進行交互的唯一方法是應用類型安全的轉換序列:

Identity<String> idString = new Identity<>("abc");

Identity<Integer> idInt = idString.map(String::length);

或流利地,就像您編寫函數一樣:

Identity<byte[]> idBytes = new Identity<>(customer)

.map(Customer::getAddress)

.map(Address::street)

.map((String s) -> s.substring(0, 3))

.map(String::toLowerCase)

.map(String::getBytes);

從這個角度來看,在Functors上的映射與調用鏈式函數沒有太大不同:

byte[] bytes = customer

.getAddress()

.street()

.substring(0, 3)

.toLowerCase()

.getBytes();

您為什麼還要打擾這樣冗長的包裝,不僅不提供任何附加值,而且也不能將內容提取回去?好了,事實證明您可以使用此原始Functors抽象對其他幾個概念建模。例如,從Java 8開始,可選的是帶有map()方法的Functors。讓我們從頭開始實現它:

class FOptional<T> implements Functor<T,FOptional<?>> {

private final T valueOrNull;

private FOptional(T valueOrNull) {

this.valueOrNull = valueOrNull;

}

public <R> FOptional<R> map(Function<T,R> f) {

if (valueOrNull == null)

return empty();

else

return of(f.apply(valueOrNull));

}

public static <T> FOptional<T> of(T a) {

return new FOptional<T>(a);

}

public static <T> FOptional<T> empty() {

return new FOptional<T>(null);

}

}

現在變得有趣了。一個FOptional<T>仿函數可以持有價值,但同樣也可能是空的。這是一種類型安全的編碼方式null。有兩種構造方法FOptional-通過提供值或創建 empty()實例。在這兩種情況下,就像with一樣Identity,FOptional都是不可變的,我們只能與內部的值進行交互。不同之處FOptional在於,如果轉換函數f為空,則可能不會將其應用於任何值。這意味著Functors可能未必必須完全封裝type的一個值T。它也可以包裝任意數量的值,就像List... functor:

import com.google.common.collect.ImmutableList;

class FList<T> implements Functor<T, FList<?>> {

private final ImmutableList<T> list;

FList(Iterable<T> value) {

this.list = ImmutableList.copyOf(value);

}

@Override

public <R> FList<?> map(Function<T, R> f) {

ArrayList<R> result = new ArrayList<R>(list.size());

for (T t : list) {

result.add(f.apply(t));

}

return new FList<>(result);

}

}

API保持不變:您可以在轉換中使用Functors-但行為卻大不相同。現在,我們對FList中的每個項目進行轉換,以聲明方式轉換整個列表。因此,如果您有客戶列表,並且想要他們的街道列表,則非常簡單:

import static java.util.Arrays.asList;

FList<Customer> customers = new FList<>(asList(cust1, cust2));

FList<String> streets = customers

.map(Customer::getAddress)

.map(Address::street);

這不再像說那麼簡單customers.getAddress().street(),您不能getAddress()在一個客戶集合上調用,您必須getAddress()在每個單獨的客戶上調用,然後將其放回一個集合中。順便說一句,Groovy發現這種模式是如此普遍,以至於實際上它有一個語法糖:customer*.getAddress()*.street()。該運算符稱為散點,實際上是一種map偽裝。也許您想知道為什麼我要在list內部手動迭代map而不是使用StreamJava 8中的s list.stream().map(f).collect(toList())?這會響嗎?如果我java.util.stream.Stream<T>用Java 告訴您也是Functors怎麼辦?順便說一句,一個Monads?

現在,您應該看到Functors的第一個好處-它們抽象了內部表示形式,並為各種數據結構提供了一致且易於使用的API。作為最後一個示例,讓我介紹類似於的 promise函數Future。Promise「承諾」有一天將提供一個值。它尚未出現,可能是因為產生了一些後臺計算,或者我們正在等待外部事件。但是它將在將來的某個時間出現。完成a Promise<T>的機制並不有趣,但是Functors的性質是:

Promise<Customer> customer = //...

Promise<byte[]> bytes = customer

.map(Customer::getAddress)

.map(Address::street)

.map((String s) -> s.substring(0, 3))

.map(String::toLowerCase)

.map(String::getBytes);

看起來很熟悉?這就是我想說的!

Functors的實現超出了本文的範圍,甚至不重要。不用說,我們非常接近從Java 8實現CompletableFuture,並且幾乎從RxJava中發現了Observable。但是回到Functors。Promise <客戶>尚未持有客戶的值。它有望在將來具有這種價值。但是,我們仍然可以像使用FOptional和FList一樣映射此類Functors-語法和語義完全相同。行為遵循Functors表示的內容。調用customer.map(Customer :: getAddress)會產生Promise <Address>,這意味著地圖是非阻塞的。customer.map()將客戶承諾完成。相反,它將返回另一個不同類型的promise。當上遊承諾完成後,下遊承諾應用傳遞給map()的函數並將結果傳遞給下遊。突然,我們的Functors使我們能夠以非阻塞方式流水線進行異步計算。但是您不必了解或學習-因為Promise是Functors,所以它必須遵循語法和法則。

Functors還有許多其他很好的例子,例如以組合方式表示值或錯誤。但是現在是時候看看Monads了。

從 Functors到Monads

我假設您了解Functors是如何工作的,為什麼它們是有用的抽象。但是Functors並不像人們期望的那樣普遍。如果您的轉換函數(作為map()的一個參數傳遞)返回Functors實例而不是簡單值,會發生什麼情況?好吧,Functors也是一個值,因此不會發生任何不良情況。將返回的所有內容放回Functors中,以便所有行為都保持一致。但是,假設您有以下方便的方法來解析字符串:

FOptional<Integer> tryParse(String s) {

try {

final int i = Integer.parseInt(s);

return FOptional.of(i);

} catch (NumberFormatException e) {

return FOptional.empty();

}

}

例外是會影響類型系統和功能純度的副作用。在純函數式語言中,沒有例外的地方。畢竟,我們從未聽說過在數學課上拋出異常,對嗎?錯誤和非法條件使用值和包裝器明確表示。例如,tryParse()接受一個String,而不是簡單地返回一個int或在運行時靜默引發異常。通過類型系統,我們明確地告訴了tryParse()可能失敗,字符串格式錯誤沒有任何異常或錯誤。此半故障由可選結果表示。有趣的是,Java已經檢查了必須聲明和處理的異常,因此從某種意義上講,Java在這方面更純淨,它沒有隱藏副作用。但是對於Java中通常不建議檢查的異常情況,因此,讓我們回到tryParse()。用已經包裝在FOptional中的String組成tryParse似乎很有用:

FOptional<String> str = FOptional.of("42");

FOptional<FOptional<Integer>> num = str.map(this::tryParse);

這不足為奇。如果tryParse()返回a,int您將得到FOptional<Integer> num,但是由於map()函數FOptional<Integer>本身返回,因此將其包裝兩次成尷尬FOptional<FOptional<Integer>>。請仔細查看類型,您必須了解為什麼我們在這裡得到這種雙重包裝。除了看上去很恐怖之外,在Functors中放一個Functors會破壞構圖和流暢的連結:

FOptional<Integer> num1 = //...

FOptional<FOptional<Integer>> num2 = //...

FOptional<Date> date1 = num1.map(t -> new Date(t));

//doesn't compile!

FOptional<Date> date2 = num2.map(t -> new Date(t));

在這裡,我們嘗試FOptional通過轉換int為+ Date + 映射內容。有了int -> Date我們可以輕鬆地從轉換Functor<Integer>為的功能Functor<Date>,我們知道它是如何工作的。但是在 num2 情況變得複雜的情況下。什麼num2.map()接收輸入的不再是一個int,但一個FOoption<Integer>顯然java.util.Date不具備這樣的構造。我們通過雙重包裹打破了Functors。但是,擁有返回Functors而不是簡單值的函數非常普遍(如tryParse()),我們不能簡單地忽略這種要求。一種方法是引入一種特殊的無參數join()方法,以「展平」嵌套Functors:

FOptional<Integer> num3 = num2.join()

它可以工作,但是因為這種模式太普遍了,所以flatMap()引入了名為的特殊方法。flatMap()與以下內容非常相似,map但希望作為參數接收的函數返回Functors-或準確地說是monad:

interface Monad<T,M extends Monad<?,?>> extends Functor<T,M> {

M flatMap(Function<T,M> f);

}

我們簡單地得出結論,這flatMap只是一種語法糖,可以使成分更好。但是flatMap方法(通常稱為Haskell bind或>>=從Haskell 調用)具有所有不同,因為它允許以純淨的功能樣式構成複雜的轉換。如果FOptional是monad的實例,則解析突然可以按預期進行:

FOptional<String> num = FOptional.of("42");

FOptional<Integer> answer = num.flatMap(this::tryParse);

Monads不需要實現map,它可以flatMap()很容易地實現。事實上flatMap,必不可少的運算符可實現全新的轉換領域。顯然,就像Functors一樣,句法順從性不足以將某類稱為Monads,flatMap()操作員必須遵守Monads法則,但是它們非常直觀,就像flatMap()與身份的結合一樣。後者要求m(x).flatMap(f)與f(x)持有值x和函數的任何monad 相同f。我們不會深入研究monad理論,而讓我們關注實際含義。例如,當單聲道內部結構不重要時,它們會發光Promise未來將具有價值的monad。您可以從類型系統中猜出Promise在以下程序中將如何運行嗎?首先,所有可能花費一些時間才能完成的方法都返回a Promise:

import java.time.DayOfWeek;

Promise<Customer> loadCustomer(int id) {

//...

}

Promise<Basket> readBasket(Customer customer) {

//...

}

Promise<BigDecimal> calculateDiscount(Basket basket, DayOfWeek dow) {

//...

}

現在,我們可以像使用monadic運算符一樣阻止所有這些函數的方式編寫這些函數:

Promise<BigDecimal> discount =

loadCustomer(42)

.flatMap(this::readBasket)

.flatMap(b -> calculateDiscount(b, DayOfWeek.FRIDAY));

這變得很有趣。flatMap()必須保留Monads類型,因此所有中間對象均為Promises。這不僅僅是保持類型有序-前一個程序突然完全異步!loadCustomer()返回一個,Promise因此它不會阻塞。readBasket()接受Promise具有(將具有)的任何東西,並應用返回另一個函數的函數Promise,依此類推。基本上,我們建立了一個異步計算管道,其中後臺完成一個步驟會自動觸發下一步。

探索 flatMap()

有兩個Monads並將它們包含的值組合在一起是很常見的。但是,Functors和monad都不允許直接訪問其內部,這是不純的。相反,我們必須謹慎地應用轉換,而不能逃脫monad。假設您有兩個Monads,並且想要將它們合併:

import java.time.LocalDate;

import java.time.Month;

Monad<Month> month = //...

Monad<Integer> dayOfMonth = //...

Monad<LocalDate> date = month.flatMap((Month m) ->

dayOfMonth

.map((int d) -> LocalDate.of(2016, m, d)));

請花點時間研究前面的偽代碼。我不使用任何真正的monad實現方式,Promise也不List強調核心概念。我們有兩個獨立的Monads,一個是type Month,另一個是type Integer。為了構建LocalDate它們,我們必須構建一個嵌套的轉換,該轉換可以訪問兩個monad的內部。仔細研究這些類型,尤其要確保您了解為什麼我們flatMap在一個地方和另一個地方使用map()。想想如果您也有三分之一的話,將如何構造該代碼Monad<Year>。應用的兩個參數的函數(的這種模式m,並d在我們的例子)是很常見的,在Haskell有一個名為特殊輔助函數liftM2正是在map和之上實現的轉換flatMap。在Java偽語法中,它看起來像這樣:

Monad<R> liftM2(Monad<T1> t1, Monad<T2> t2, BiFunction<T1, T2, R> fun) {

return t1.flatMap((T1 tv1) ->

t2.map((T2 tv2) -> fun.apply(tv1, tv2))

);

}

您不必為每個monad都實現此方法,這flatMap()已經足夠了,而且,它對所有monad都一致地起作用。liftM2當您考慮如何將其與各種monad結合使用時,它非常有用。例如,listM2(list1, list2, function)將應用於和(笛卡爾積)function上的所有可能的項目對。另一方面,對於可選選項,僅當兩個可選選項均為非空時,它將應用功能。更好的是,對於 monad,當兩個都完成時,函數將異步執行。這意味著我們只是發明了一個簡單的同步機制(在fork-join算法中),該機制包含兩個異步步驟。list1list2Promise Promisejoin()

我們可以輕鬆構建的另一個有用的運算符flatMap()是filter(Predicate<T>),它接受monad中的所有內容,如果不符合某些謂詞,則將其完全丟棄。在某種程度上,它類似於map1-to-1映射,而不是1-to-1映射。同樣filter(),每個monad具有相同的語義,但取決於我們實際使用的monad,其功能卻非常出色。顯然,它允許從列表中過濾掉某些元素:

FList<Customer> vips =

customers.filter(c -> c.totalOrders > 1_000);

但是它也可以很好地工作,例如對於可選項目。在這種情況下,如果可選內容不符合某些條件,我們可以將非空可選轉換為空。空的可選部分保持不變。

從Monads列表到Monads列表

源自flatMap()的另一個有用的運算符是sequence()。您只需查看類型籤名即可輕鬆猜測其作用:

Monad<Iterable<T>> sequence(Iterable<Monad<T>> monads)

通常,我們有一堆相同類型的monad,而我們想要一個具有該類型列表的monad。這對您來說可能聽起來很抽象,但卻非常有用。想像一下,您想通過ID同時從資料庫中加載一些客戶,因此您loadCustomer(id)多次對不同的ID 使用方法,每次調用都返回Promise<Customer>。現在,您有一個的列表,Promise但您真正想要的是一個客戶列表,例如要在Web瀏覽器中顯示的客戶列表。將 sequence()(在RxJava sequence()被稱為concat()或merge()根據使用情況)運算符剛建成為:

FList<Promise<Customer>> custPromises = FList

.of(1, 2, 3)

.map(database::loadCustomer);

Promise<FList<Customer>> customers = custPromises.sequence();

customers.map((FList<Customer> c) -> ...);

通過調用每個ID,FList<Integer>我們擁有一個具有代表性的客戶ID map(您知道它對FList仿函數有何幫助?)database.loadCustomer(id)。這導致Promises的列表非常不便。sequence()節省了一天的時間,但這再次不僅僅是語法糖。前面的代碼是完全非阻塞的。對於不同種類的Monadssequence()還是有意義的,但是在不同的計算環境中。例如,它可以更改FList<FOptional<T>>為FOptional<FList<T>>。順便說一句,您可以在之上實現sequence()(就像map())flatMap()。

flatMap()一般而言,這只是冰山一角。儘管源於晦澀的類別理論,但即使在Java等面向對象的程式語言中,monad也被證明是極其有用的抽象。能夠組成返回Monads函數的函數非常有用,以至於數十個無關的類遵循Monads行為。

而且,一旦將數據封裝在monad中,通常很難顯式地將其取出。這種操作不是monad行為的一部分,並且經常導致非慣用語代碼。例如,Promise.get()on Promise<T>可以從技術上返回T,但只能通過阻塞返回,而所有基於的運算符flatMap()都是非阻塞的。另一個示例是FOptional.get(),但是可能失敗,因為FOptional可能為空。即使FList.get(idx)從列表中偷看特定元素也聽起來很尷尬,因為您可以經常替換for循環map()。

我希望您現在了解為什麼現在這些Monads如此流行。即使在像Java這樣的面向對象的語言中,它們也是非常有用的抽象。

最後,開發這麼多年我也總結了一套學習Java的資料與面試題,如果你在技術上面想提升自己的話,可以關注我,私信發送領取資料或者在評論區留下自己的聯繫方式,有時間記得幫我點下轉發讓跟多的人看到哦。

相關焦點

  • 從Python到Haskell:程式設計師為何與函數式編程「墜入愛河」?
    現在,當查看函數聲明時,用戶能確切地知道發生了什麼。因此,如果程序運行不正常,用戶也可以輕而易舉地單獨測試每個功能,並查明哪個功能有問題函數式編程正在編寫純函數沒有副作用的函數是指其輸入和輸出都具有明確的聲明,而沒有副作用的功能就是純函數。
  • 函數式編程那些事兒
    函數式編程是一種編程範式,在其中它試圖將每個函數都綁定到純數學函數中。這是一種聲明式的編程風格,著重於解決什麼而不是如何解決。Clojure,Common Lisp,Erlang,Haskell和Scala是遵循函數式編程方法的一些著名程式語言。
  • 輕鬆玩轉函數式編程
    於是我抽時間捋了捋,將平時工作中用到的函數式編程案例和思想整理了出來,相信閱讀本文後,大家都能快速上手函數式編程。 函數式編程目前使用範圍非常廣,常用的框架,語言幾乎都能看到它的身影。 前端框架:react、vue 的 hooks 用法。
  • java8的函數式編程解析
    其實在java8就已經有java的函數式編程寫法,只是難度較大,大家都習慣了對象式用法,但在其它語言中都有函數式的用法,如js,scala,函數式其實是抽象到極致的思想。什麼是函數式編程 函數式編程並不是Java新提出的概念,其與指令編程相比,強調函數的計算比指令的計算更重要;與過程化編程相比,其中函數的計算可以隨時調用。
  • 寫Python 代碼不可不知的函數式編程技術
    選自 Medium作者:Raivat Shah參與:魔王、Jamin本文對 Python 中的函數式編程技術進行了簡單的入門介紹。近來,越來越多人使用函數式編程(functional programming)。因此,很多傳統的命令式語言(如 Java 和 Python)開始支持函數式編程技術。本文對 Python 中的函數式編程技術進行了簡單的入門介紹。本文適合對函數式編程有基本了解的讀者。
  • 大數據入門:Java和Scala編程對比
    在學習大數據之初,很多人都會對程式語言的學習有疑問,比如說大數據編程主要用什麼語言,在實際運用當中,大數據主流編程是Java,但是涉及到Spark、Kafka框架,還需要懂Scala。今天的大數據入門分享,我們就來對Java和Scala這兩門語言的編程做個對比。
  • 你好, Java !
    2017年夏天,團隊發起了一個新的微服務項目,和往常一樣,我們需要對程式語言和技術進行選型。部分團隊成員是 Kotlin 的擁護者,再加上我們都想嘗試一下新的東西,於是我們決定用 Kotlin 來開發這個項目。由於 Spock 測試框架不支持 Kotlin,因此我們決定堅持使用 Groovy 來測試。
  • 知識分享之函數式編程的簡單介紹
    狹義地說,函數式編程沒有可變的變量、循環等這些命令式編程方式中的元素,像數學裡的函數一樣,對於給定的輸入,不管你調用該函數多少次,永遠返回同樣的結果。而在我們常用的命令式編程方式中,變量用來描述事物的狀態,整個程序,不過是根據不斷變化的條件來維護這些變量。
  • 大數據入門:Scala函數式編程
    提到Scala,首先會提到的一個概念,就是函數式編程,這也是Scala語言區別與其他程式語言的典型特徵。Scala是一門多範式(multi-paradigm)的程式語言,設計初衷是要集成面向對象編程和函數式編程的各種特性。
  • 函數式編程很難懂?其實真心很簡單
    二、函數式編程函數,這個概念我們在數學裡面我們就接觸過。y=f(x)(y=x+1)這就是函數的格式,其中f是函數名,x是變量,y是函數值,還有定義域,值域什麼的。你發現沒有,Java中的方法其實就是一個函數:方法名不就是函數名麼?參數也就是函數中的變量,返回值也就是函數值?它們本質上是一樣的的,只不過叫法不一樣,並且在有的程式語言中方法也就叫函數。
  • 一起學JAVA——數組和函數
    之前我們介紹了java的數據類型、變量、流程控制等內容。今天我們高級數據類型——數組以及函數的作用。函數(方法)函數的定義函數就是一段有名字的代碼,可以完成某一特定功能。方法(函數)是java的最小代碼重用單位,方法(函數)是為了重用代碼。方法不能嵌套方法,不能在一個方法內部定義另外一個方法。可以在一個方法內部調用另外一個方法。
  • 讓你徹底明白yield語法糖的用法和原理及在C 函數式編程中的作用
    通過使用 yield 定義迭代器,可在實現自定義集合類型的 IEnumerator 和 IEnumerable 模式時無需其他顯式類(保留枚舉狀態的類,有關示例,請參閱 IEnumerator)。沒用過yield之前,看這句話肯定是一頭霧水,只有在業務開發中踩過坑,才能體會到yield所帶來的快感。
  • 新手編程:Java多線程中Thread與Runnable的區別
    Java多線程中Thread與Runnable的區別定義extends Thread子類繼承Thread具備多線程能力,可以實現多線程;啟動線程的方法:①創建子類對象所以不管是繼承Thread類還是實現Runnable接口來實現多線程,最終都是通過Thread對象的API來控制線程的,因此熟悉Thread類的API是進行多線程編程的基礎。(
  • java如何快速入門?
    在Java中使用'new'關鍵字進行內存分配是必要的,因為Java是一種動態程式語言。C,C ++沒有顯式具有此功能,因此在Java中處理數組和對象聲明時必須謹慎。不使用'new'關鍵字將在代碼中顯示空指針異常。
  • Java的新未來:JVM、Kotlin和Java將在2020年後走向何方?
    必須從Java開始,因為Java是JVM世界中最古老、最流行的語言。Java是1996年1月正式發布的,已經存在了24年。最初,Java是一種純粹的命令式語言,遵循純粹面向對象的編程風格;它也是一種強類型語言。Java的語法在某些方面與C++和C語言相似,但它被認為是一個改進版,因為用它編寫代碼比C++或C++更容易。
  • Java 10 大裝 B 寫法,看完可以出去吹牛逼了!
    想不想學習裝 B 式的 Java 騷操作花式寫法?沒錯,本文棧長來教你!1、集合初始化集合的創建、賦值一步到位,想不想學?- 不確定類型上面的泛型我們應該有常見到吧,邊界和通配符不懂的可以看下這篇文章吧:困擾我多年的Java泛型 和 ,終於搞清楚了。泛型要學會用,學好能裝B。6、LambdaLambda 表達式這是 Java 8 裡面添加的新特性,用來簡化匿名內部類以及結合函數式接口編程用的。
  • 開發崗位這麼多,為什麼選Java?你學Java了嗎-開課吧
    TIOBE編程排行榜根據全球工程師、課程和搜尋引擎數量為指數得出,在一定程度上反映了程式語言的發展趨勢。程式語言排行榜目前很多軟體都是用Java寫的,新出的系統和函數庫為了市場,也會儘量和Java兼容或者提供Java的接口。
  • Spark程式語言之scala
    scala能夠繼續使用java的部分語法。scala具有自己特有的語法:增強,函數式編程,偏函數,函數的柯裡化高階函數,將函數作為參數傳遞等。3.Scala語言的特點:Scala是以一門以java虛擬機JVM為運行環境的將面向對象,函數式編程結合在一起的靜態類型程式語言。Scala原始碼.scala會被編譯成Java字節碼.class,然後運行於JVM上。
  • 「詳細」MySQL資料庫與JDBC編程
    MySQL單行函數分組和組函數group by分組多表連接查詢交叉連接自然連接using子句連接SELECT * FROM table ORDER BY name DESC, id ASC;資料庫函數多用在select和where後面。
  • JAVA8 新特性詳解
    一、接口的默認方法在接口中新增了default方法和static方法,這兩種方法可以有方法體1、static方法示例代碼,我們知道此時父接口和子接口中存在同名同參數的default方法,這會怎麼樣?基本語法:<函數式接口> <變量名> = (參數1,參數2...)