over 4 years ago

今天來點輕鬆的,這應該算是看完《超越Java -- 探討程式語言的未來》多年後最近再次思考的一些心得:近幾年Java太重視Library (J2EE已經變成龐然大物)了,忽略了語言本身的問題(J2SE 7的auto-closable resource in try-catch和J2SE 8的Lambda恐怕是繼J2SE 5的Generic/Autoboxing後針對語言本身所做的少數改變)。Java SE 8已經發布,甚至短短一個月不到(2014/3/18 -> 2014/4/15),就發布的Java SE 8 Update 5 (從Oracle接手後,版本號碼跳超快的),下一個版本Java SE 9似乎已經開始蠢蠢欲動,不過在接觸幾種不同語言後,其實有幾個語言特性(不一定是功能),我到蠻希望Java能加入的。

第一個我希望加入的是property,其實蠻多語言都有類似的語言特性,例如Objective C和C#都有,property可以像存取變數值那樣使用,例如像Code List 1中for(Publication* publication in person.publications)取得著作,或像Code List 2中的page.Height = 400指定頁面高度,但它和直接把成員變數宣告成public不同,因為property還是有存取權限的設定,由不同存取權限的getter和setter所組成的,所以在Code List 2中,改變高度是會通知handlers (observer pattern)的。只是多數語言有提供預設實作,或是提供機制自動產生,例如@synthesize firstName;。雖然Java可以使用Lombok自動生成getter/setter,但終究不是語言的一部分,而且我討厭用annotation做這件事(好吧!關於annotation純屬我個人喜好問題)。

Code List 1 - The property in Objective C
// In Person.h
@interface Person : NSObject

@property (nonatomic, readonly) NSInteger age;
@property (nonatomic, readonly) NSArray* publications;
@property (nonatomic) NSString* firstName;
@property (nonatomic) NSString* lastName;

@end

// In Person.m
@implementation Person {
    NSMutableArray* _publications;
}

@synthesize firstName;
@synthesize lastName;

- (NSInteger)age {
    NSDate* date = [NSDate date];
    NSCalendar* calendar = [NSCalendar currentCalendar];
    NSDateComponents* components = [calendar components:NSYearCalendarUnit fromDate:self.birthday toDate:now  options:0];
    return [ageComponents year];
}

- (NSArray*)publications {
    return _publications;
}
@end

// In somewhere alse
Person* person = [controller getPerson];
for(Publication* publication in person.publications) {
    // do something
}
Code List 2 - The property in C#
// In Page.cs
public class Page : Drawable {

    /// <summary>
 /// Gets or sets the height of the page
 /// </summary>
 public int Height {
        get { return _height; }
        set {
            _height = value;
            NotifyContentUpdatedEventHandlers();
        }
    }
}

// In somewhere alse
Page page = createEmptyPage();
page.Height = 400;

第二個特性是literal data structure declaration,JavaScript和Objective C等語言都有提供類似的特性。有時候在處理資料時,只是想要個簡單的資料物件(data object),但就是一定要寫個class,然後提供getter/setter的實作,這實在太麻煩了,以Code List 3為例,馬上就能組出一個company資料物件,沒錯,其實Code List 3是Objective C從JavaScript Object Notation (JSON)那裡學來的(Code List 4),只是要加上小老鼠@作為識別。Java也許該考慮一下!小抱怨一下,為什麼內建的JSON API只有在J2EE中才有,難道client不需要解析JSON嗎?真是奇怪的一件事。

Code List 3 - The literal data structure declaration
id company = @{
    @"name": @"Far Far AwayCompany",
    @"address": @"I don't want to know",
    @"foundedOn": @1999,
    @"employees": @[ @"Bill", @"Steve", @"John" ]
};
Code List 4 - JavaScript Object Notation
var company = {
    "name": "Far Far AwayCompany",
    "address": "I don't want to know",
    "foundedOn": 1999,
    "employees": [ "Bill", "Steve", "John" ]
};

第三個希望新增到Java中的特性是category,這是我在學Objective C後很喜歡的一個語言特性,category是一種可以用來黏合新實作到既有物件的方式,但又不是繼承,因為型別沒有改變,也不是C#的partial keyword。這在設計複雜的專案時很方便,可以依據功能分類到不同的檔案中,而且可以依據需求只使用部分的功能。例如:POJO (Plain Old Java Object)物件與JSON之間的轉換,到底應該算是物件本身自己該做的事?還是由別的物件負責做轉換?

我剛開始學OO語言時,會覺得是前者,但後來多看了很多設計後,我傾向後者,畢竟物件可輸出成多種格式(JSON或XML),想要新增一種輸出格式時,得修改原物件有點違反open close principle,而且在沒有原始碼的情況下,修改原物件也不可能,只能用繼承,但繼承又會改變型態。但如果有category機制,就可以將新格式輸出的實作黏合到既有物件中。以Code List 5為例,Person物件在原本的Person.h中只需要提供domain model所需的邏輯即可,而JSON相關的邏輯實作則是放在JSON的category中,若需要JSON相關的邏輯實作,才需要匯入(import) Person+JSON.h,若不需要,只需匯入一般的Person.h即可,這時JSON相關的實作也看不到。而且在沒有原始碼的情況下也可以用category來黏合新實作,我就常用category替NSString加新東西。

Code List 5 - Category
// In Person.h
@interface Person : NSObject

// domain model logic

@end

// In Person+JSON.h
@interface Person (JSON)

+ (instancetype)fromJson:(id)json;

- (void)updateWithJson:(id)json;

- (id)toJson;

@end

// In somewhere alse
#import "Person+JSON.h"

Person* person = [Person fromJson:json];

第四個特性是extension,這也是Objective C的一個語言特性。我希望被加入Java中和測試有關,Objective C雖然有 @public@protected@private關鍵字,但是用在成員變數上,而不是函式上。Objective C沒有要求函式一定要宣告在.h檔中,所以大多數情況下是把不希望被知道的函式直接寫在.m檔中,有時候為了測試方便會希望,某些原本宣告成private的函式能在測試期間暫時變成public,這時extension就派上用場了,以Code List 6為例,將希望公開的函式放在Person.h檔中,不希望公開但希望在測試期間被看到的private函式則放Person_Private.h中,公開程式時只公開Person.h檔,Person_PRivate.h檔則不公開,測試時匯入Person_Private.h檔就能看到想測的private函式(其實,Objective C的extension還可以狠,連extension的.h檔都不需要,直接在測試程式中,強制把private methods變成公開的XD)。雖然Java沒有.h標頭檔和.m實作檔分離的設計,但如果能有private interface implementation的話,也許可以提供類似的特性。

Code List 6 - Extension
// In Person.h
@interface Person : NSObject

// public domain model logic

@end

// In Person_Private.h
#import "Person.h"

@interface Person()

- (void)privateMethod;

@end

// In Person.m
#import "Person_Private.h"

@implementation Person

// Implement the methods that declared in Person.h and Person_Private.h

@end

// In PersonTests.m
#import "Person_Private.h"

@implementation PersonTests

- (void)testPrivateMethod {
    // Can see and test -(void)privateMethod method declared in Person_Private.h in test mode.
}

@end

第五個特性是與IDE整合用的preprocessor。雖然有不少人因為IDE越來越笨重開始回歸單純,使用一般(說一般也不對,因為還是有syntax highlighted等功能)的文字編輯器寫程式,但我想還是有不少人是用IDE在寫程式(我也一度覺得Eclipse超慢的,在想找替代品時發現4.3又變快了才繼續使用),Objective C和C#在與IDE整合上就表現得很不錯,Java有蠻多好的IDE,但語言對於IDE則完全沒有支援。像我就會依照功能整理程式碼,所以我會在Objective C裡用#param mark - UITableViewDelegate Methods將實作UITableViewDelegate的函式集中分區塊,或在C#裡用#region Properties#endregion將所有的properties包起來成為一個區塊,當IDE看到這些preprocessor時,可以做最佳化顯示,例如在XCode裡,如果有使用#param mark,在上方導覽列的函示列表(Figure 1)就會根據mark的名稱將函式分區顯示,在找函式時更方便。

Figure 1 - XCode provides integration with preprocessors

這幾年新出的語言都強打在少寫code和提高可讀性,更重要的是能更容易發展出domain specific language,就這一點Java確實有點顯得疲態了。其實文中列的特性大多是一些語法糖衣,但對程式的可讀性和抽象度都能提昇不少,我覺得挺實用也很划算的。

← Quick Glance of Java 8 - Lazy Evaluation & Parallel Stream What I want to have in the future Java? →