java8新特性源碼解析

1 Lambda表達式與Functional接口
Lambda表達式(也稱爲閉包)是整個Java 8發行版中最受期待的在Java語言層面上的改變,Lambda允許把函數作爲一個方法的參數(函數作爲參數傳遞進方法中),或者把代碼看成數據:函數式程序員對這一概念非常熟悉。在JVM平臺上的很多語言(Groovy,Scala,……)從一開始就有Lambda,但是Java程序員不得不使用毫無新意的匿名類來代替lambda。
關於Lambda設計的討論佔用了大量的時間與社區的努力。可喜的是,最終找到了一個平衡點,使得可以使用一種即簡潔又緊湊的新方式來構造Lambdas。在最簡單的形式中,一個lambda可以由用逗號分隔的參數列表、–>符號與函數體三部分表示。例如:
Arrays.asList("a","b","d").forEach( e -> System.out.println( e ) );
Arrays.asList( "a", "b", "d" ).forEach( e -> {
    System.out.print( e );
    System.out.print( e );
} );
Lambda可能會返回一個值。返回值的類型也是由編譯器推測出來的。如果lambda的函數體只有一行的話,那麼沒有必要顯式使用return語句。下面兩個代碼片段是等價的:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
    return result;
} );
語言設計者投入了大量精力來思考如何使現有的函數友好地支持lambda。最終採取的方法是:增加函數式接口的概念。函數式接口就是一個具有一個方法的普通接口。像這樣的接口,可以被隱式轉換爲lambda表達式。java.lang.Runnable與java.util.concurrent.Callable是函數式接口最典型的兩個例子。在實際使用過程中,函數式接口是容易出錯的:如有某個人在接口定義中增加了另一個方法,這時,這個接口就不再是函數式的了,並且編譯過程也會失敗。爲了克服函數式接口的這種脆弱性並且能夠明確聲明接口作爲函數式接口的意圖,Java 8增加了一種特殊的註解@FunctionalInterface(Java 8中所有類庫的已有接口都添加了@FunctionalInterface註解)。讓我們看一下這種函數式接口的定義:
@FunctionalInterface
public interface Functional {
    void method();
}
需要記住的一件事是:默認方法與靜態方法並不影響函數式接口的契約,可以任意使用:
@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();
         
    default void defaultMethod() {           
    }       
}
2. Default關鍵字
在Java 8中,接口可以包含帶有實現代碼的方法,這些方法稱爲default方法。
3. Optional(見5.函數式接口詳述)
到目前爲止,臭名昭著的空指針異常是導致Java應用程序失敗的最常見原因。以前,爲了解決空指針異常,Google公司著名的Guava項目引入了Optional類,Guava通過使用檢查空值的方式來防止代碼污染,它鼓勵程序員寫更乾淨的代碼。受到Google Guava的啓發,Optional類已經成爲Java 8類庫的一部分。
Optional實際上是個容器:它可以保存類型T的值,或者僅僅保存null。Optional提供很多有用的方法,這樣我們就不用顯式進行空值檢測。更多詳情請參考官方文檔
我們下面用兩個小例子來演示如何使用Optional類:一個允許爲空值,一個不允許爲空值。
1
2
3
4
Optional< String > fullName = Optional.ofNullable(null );
System.out.println("Full Name is set? " + fullName.isPresent() );       
System.out.println("Full Name: " + fullName.orElseGet( () ->"[none]" ) );
System.out.println( fullName.map( s ->"Hey " + s +"!" ).orElse("Hey Stranger!" ) );
如果Optional類的實例爲非空值的話,isPresent()返回true,否從返回false。爲了防止Optional爲空值,orElseGet()方法通過回調函數來產生一個默認值。map()函數對當前Optional的值進行轉化,然後返回一個新的Optional實例。orElse()方法和orElseGet()方法類似,但是orElse接受一個默認值而不是一個回調函數。下面是這個程序的輸出:
1
2
3
Full Name isset?false
Full Name: [none]
Hey Stranger!
讓我們來看看另一個例子:
1
2
3
4
5
Optional< String > firstName = Optional.of("Tom" );
System.out.println("First Name is set? " + firstName.isPresent() );       
System.out.println("First Name: " + firstName.orElseGet( () ->"[none]" ) );
System.out.println( firstName.map( s ->"Hey " + s +"!" ).orElse("Hey Stranger!" ) );
System.out.println();
下面是程序的輸出:
1
2
3
First Name isset?true
First Name: Tom
Hey Tom!
********************************************************************************************************************************************************************************************************************************************************

比如以前你從數據庫裏獲取一個對象,然後對他進行操作,可能是這樣的代碼
1
2
3
4
5
Object o = dao.find()
if(o != null)
{
    doSomething(o)
}
但是用函數式編程來寫就該是這樣
1
2
Optional<Object> o = dao.find()
o.ifPresent(item->doSometing(o));
再舉個例子,若你從數據庫查出來的對象要是空的話需要報錯,以前會這樣寫
1
2
3
4
5
6
Object o = dao.find()
if(o == null)
{
    throw Exception
}
return o;
但是現在你可以這樣寫
1
2
Optional<Object> o = dao.find()
return o.orElseThrow(Exception::new)
還可以簡化成 
1
return dao.find().orElseThrow(Exception::new);
********************************************************************************************************************************************************************************************************************************************************
optional的正確用法!!!
先又不得不提一下 Optional 的三種構造方式: Optional.of(obj),  Optional.ofNullable(obj) 和明確的 Optional.empty()
Optional.of(obj): 它要求傳入的 obj 不能是 null 值的, 否則還沒開始進入角色就倒在了 NullPointerException 異常上了.
Optional.ofNullable(obj): 它以一種智能的, 寬容的方式來構造一個 Optional 實例. 來者不拒, 傳 null 進到就得到 Optional.empty(), 非 null 就調用 Optional.of(obj).
那是不是我們只要用 Optional.ofNullable(obj) 一勞永逸, 以不變應二變的方式來構造 Optional 實例就行了呢? 那也未必, 否則 Optional.of(obj) 何必如此暴露呢, 私有則可?
我本人的觀點是:  1. 當我們非常非常的明確將要傳給 Optional.of(obj) 的 obj 參數不可能爲 null 時, 比如它是一個剛 new 出來的對象(Optional.of(new User(...))), 或者是一個非 null 常量時;  2. 當想爲 obj 斷言不爲 null 時, 即我們想在萬一 obj 爲 null 立即報告 NullPointException 異常, 立即修改, 而不是隱藏空指針異常時, 我們就應該果斷的用 Optional.of(obj) 來構造 Optional 實例, 而不讓任何不可預計的 null 值有可乘之機隱身於 Optional 中.
存在即返回, 無則提供默認值
1
2
return user.orElse(null); //而不是 return user.isPresent() ? user.get() : null;
return user.orElse(UNKNOWN_USER);
存在即返回, 無則由函數來產生
1
return user.orElseGet(() -> fetchAUserFromDatabase());//而不要 return user.isPresent() ? user: fetchAUserFromDatabase();
存在纔對它做點什麼
1
2
3
4
5
6
user.ifPresent(System.out::println);
 
//而不要下邊那樣
if (user.isPresent()) {
  System.out.println(user.get());
}
map 函數隆重登場
當 user.isPresent() 爲真, 獲得它關聯的 orders, 爲假則返回一個空集合時, 我們用上面的 orElse, orElseGet 方法都乏力時, 那原本就是 map 函數的責任, 我們可以這樣一行
1
2
3
4
5
6
7
8
return user.map(u -> u.getOrders()).orElse(Collections.emptyList())
 
//上面避免了我們類似 Java 8 之前的做法
if(user.isPresent()) {
  return user.get().getOrders();
}else {
  return Collections.emptyList();
}
map  是可能無限級聯的, 比如再深一層, 獲得用戶名的大寫形式 *map自帶空指針判斷
1
2
3
return user.map(u -> u.getUsername())
           .map(name -> name.toUpperCase())
           .orElse(null);
這要擱在以前, 每一級調用的展開都需要放一個 null 值的判斷
1
2
3
4
5
6
7
8
9
10
11
User user = .....
if(user !=null) {
  String name = user.getUsername();
  if(name != null) {
    return name.toUpperCase();
  }else {
    returnnull;
  }
}else {
  returnnull;
}
一句話小結: 使用 Optional 時儘量不直接調用 Optional.get() 方法, Optional.isPresent() 更應該被視爲一個私有方法, 應依賴於其他像 Optional.orElse(), Optional.orElseGet(), Optional.map() 等這樣的方法.
4.Stream
publicclass Streams  {
    privateenum Status {
        OPEN, CLOSED
    };
     
    privatestaticfinalclass Task {
        privatefinal Status status;
        privatefinal Integer points;
 
        Task(final Status status,final Integer points ) {
            this.status = status;
            this.points = points;
        }
         
        public Integer getPoints() {
            return points;
        }
         
        public Status getStatus() {
            return status;
        }
         
        @Override
        public String toString() {
            return String.format( "[%s, %d]", status, points );
        }
    }
}
Task類有一個分數的概念(或者說是僞複雜度),其次是還有一個值可以爲OPEN或CLOSED的狀態.讓我們引入一個Task的小集合作爲演示例子:
1
2
3
4
5
final Collection< Task > tasks = Arrays.asList(
    new Task( Status.OPEN, 5 ),
    new Task( Status.OPEN, 13 ),
    new Task( Status.CLOSED, 8 )
);
我們下面要討論的第一個問題是所有狀態爲OPEN的任務一共有多少分數?在Java 8以前,一般的解決方式用foreach循環,但是在Java 8裏面我們可以使用stream:一串支持連續、並行聚集操作的元素。
1
2
3
4
5
6
7
8
// Calculate total points of all active tasks using sum()
finallong totalPointsOfOpenTasks = tasks
    .stream()
    .filter( task -> task.getStatus() == Status.OPEN )
    .mapToInt( Task::getPoints )
    .sum();
         
System.out.println("Total points: " + totalPointsOfOpenTasks );
程序在控制檯上的輸出如下:
1
Total points: 18
這裏有幾個注意事項。第一,task集合被轉換化爲其相應的stream表示。然後,filter操作過濾掉狀態爲CLOSED的task。下一步,mapToInt操作通過Task::getPoints這種方式調用每個task實例的getPoints方法把Task的stream轉化爲Integer的stream。最後,用sum函數把所有的分數加起來,得到最終的結果.
在繼續講解下面的例子之前,關於stream有一些需要注意的地方(詳情在這裏).stream操作被分成了中間操作與最終操作這兩種。
中間操作返回一個新的stream對象。中間操作總是採用惰性求值方式,運行一個像filter這樣的中間操作實際上沒有進行任何過濾,相反它在遍歷元素時會產生了一個新的stream對象,這個新的stream對象包含原始stream中符合給定謂詞的所有元素。
像forEach、sum這樣的最終操作可能直接遍歷stream,產生一個結果或副作用。當最終操作執行結束之後,stream管道被認爲已經被消耗了,沒有可能再被使用了。在大多數情況下,最終操作都是採用及早求值方式,及早完成底層數據源的遍歷。
stream另一個有價值的地方是能夠原生支持並行處理。讓我們來看看這個算task分數和的例子。
1
2
3
4
5
6
7
8
// Calculate total points of all tasks
finaldouble totalPoints = tasks
   .stream()
   .parallel()
   .map( task -> task.getPoints() ) // or map( Task::getPoints )
   .reduce( 0, Integer::sum );
     
System.out.println("Total points (all tasks): " + totalPoints );
這個例子和第一個例子很相似,但這個例子的不同之處在於這個程序是並行運行的,其次使用reduce方法來算最終的結果。
下面是這個例子在控制檯的輸出:
1
Total points (all tasks): 26.0
經常會有這個一個需求:我們需要按照某種準則來對集合中的元素進行分組。Stream也可以處理這樣的需求,下面是一個例子:
1
2
3
4
5
// Group tasks by their status
final Map< Status, List< Task > > map = tasks
    .stream()
    .collect( Collectors.groupingBy( Task::getStatus ) );
System.out.println( map );
這個例子的控制檯輸出如下:
1
{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}
讓我們來計算整個集合中每個task分數(或權重)的平均值來結束task的例子。****
1
2
3
4
5
6
7
8
9
10
11
12
// Calculate the weight of each tasks (as percent of total points)
final Collection< String > result = tasks
    .stream()                                       // Stream< String >
    .mapToInt( Task::getPoints )                    // IntStream
    .asLongStream()                                 // LongStream
    .mapToDouble( points -> points / totalPoints )  // DoubleStream
    .boxed()                                        // Stream< Double >
    .mapToLong( weigth -> (long )( weigth *100 ) )// LongStream
    .mapToObj( percentage -> percentage +"%" )     // Stream< String>
    .collect( Collectors.toList() );                // List< String >
         
System.out.println( result );
下面是這個例子的控制檯輸出:
1
[19%, 50%, 30%]
最後,就像前面提到的,Stream API不僅僅處理Java集合框架。像從文本文件中逐行讀取數據這樣典型的I/O操作也很適合用Stream API來處理。下面用一個例子來應證這一點。
1
2
3
4
final Path path =new File( filename ).toPath();
try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
    lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );
}
對一個stream對象調用onClose方法會返回一個在原有功能基礎上新增了關閉功能的stream對象,當對stream對象調用close()方法時,與關閉相關的處理器就會執行。
5.函數式接口(Functional Interface)
所謂的函數式接口,當然首先是一個接口,然後就是在這個接口裏面只能有一個抽象方法。這種類型的接口也稱爲SAM接口,即Single Abstract Method interfaces。函數式接口裏是可以包含默認方法,因爲默認方法不是抽象方法,其有一個默認實現,所以是符合函數式接口的定義的;函數式接口裏是可以包含靜態方法,因爲靜態方法不能是抽象方法,是一個已經實現了的方法,所以是符合函數式接口的定義的;函數式接口裏是可以包含Object裏的public方法,這些方法對於函數式接口來說,不被當成是抽象方法(雖然它們是抽象方法);因爲任何一個函數式接口的實現,默認都繼承了Object類,包含了來自java.lang.Object裏對這些抽象方法的實現; 函數式接口裏允許子接口繼承多個父接口,但每個父接口中都只能存在一個抽象方法,且必須的相同的抽象方法。
/**
* java 8 之前通常使用匿名內部類完成
*/
Collections.sort(dtoList, new Comparator<PlatformCouponOrderDTO>() {
@Override
public int compare(PlatformCouponOrderDTO a, PlatformCouponOrderDTO b) {
return DateUtils.getMinutesBetween(a.getTime(), b.getTime());
}
});

/**
* java 8 之後使用Lambda表達式實現函數式接口,使代碼量明顯減少許多
*/
Collections.sort(dtoList, (a, b) -> DateUtils.getMinutesBetween(a.getTime(), b.getTime()));

舉個栗子:Predicate 和Consumer接口(java.util.function包下的接口
在Predicate接口中,有以下5個方法:(判斷輸入的對象是否符合某個條件,返回一個boolean)
@FunctionalInterface
public interface Predicate<T> {

boolean test(T t);

default Predicate<T>and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
defaultPredicate<T> negate() {
return (t) -> !test(t);
}

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);
}
Consumer接口(接受單個輸入參數且沒有返回值,Consumer接口期望執行帶有副作用的操作,即改變輸入參數的內部狀態)源碼實現如下:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
我們來考慮一下學生的例子:Student類包含姓名,分數以及待付費用,每個學生可根據分數獲得不同程度的費用折扣:
class Student{
String firstName;
String lastName;
Double grade;
Double feeDiscount = 0.0;
Double baseFee = 20000.0;
public Student(String firstName, String lastName, Double grade) 
{
this.firstName = firstName;
this.lastName = lastName;
this.grade = grade;
}
public void printFee(){
Double newFee = baseFee - ((baseFee * feeDiscount) / 100);
System.out.println("The fee after discount: " + newFee);
}
}
使用predicate的test方法判斷學生滿足的折扣條件,Consumer接口的accept方法來更改學生的折扣屬性。
public class PreidcateConsumerDemo {
public static Student updateStudentFee(Student student, Predicate<Student> predicate, Consumer<Student> consumer){ //Use the predicate to decide when to update the discount.
if ( predicate.test(student)){
//Use the consumer to update the discount value.
consumer.accept(student);
}
return student;
}
}
updateStudentFee方法的調用如下所示:
public static void main(String[] args) {
Student student1 = new Student("Ashok","Kumar", 9.5);
student1 = updateStudentFee(student1,
//Lambda expression for Predicate interface
student -> student.grade > 8.5,
//Lambda expression for Consumer inerface
student -> student.feeDiscount = 30.0);
student1.printFee();
Student student2 = new Student("Rajat","Verma", 8.0);
student2 = updateStudentFee(student2, student -> student.grade >= 8, student -> student.feeDiscount = 20.0);
student2.printFee();
}

發佈了18 篇原創文章 · 獲贊 8 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章