Gap撈Tech

5 大特性令 Swift 比 Java 優勝

前言

見到 《 Google is said to be considering Swift as a ‘first class’ language for Android 》 呢一篇報道之後,心情非常滾動,因為阿 Gap 不嬲都覺得 Swift 係一隻非常好嘅 Mordern Programming Language,最緊要係有強大嘅 Apple iOS/Mac App 做後台,之後更加 Open Source 出黎加速發展,一定會有好多人學、討論、Use in Production。

回想當初阿 Gap 由主力寫 Java 轉去主力寫 Scala 之後,發現一個 Language 對 Productivity、Maintainance 影響可以好大,亦從此之後對 Programming language 嘅要求提升咗好多。

由於有 Scala 底子去學 Swift,所以好快已經上手,覺得 Swift 有好多同 Scala 好類似嘅特性,阿 Gap 認為其中有 5 大特性亦好能夠令 Code 變得更 Clean & Elegant,所以先決定寫呢一篇文章分享比大家!

Why Swift Must be Better

歷史巨人的確好難作出革命性嘅改變

第一代 Java 喺 1996 設計出黎,當年嘅電腦無現今咁強大咁普遍,Application 亦無現今咁複雜咁龐大,所以 Language 本身嘅設計只有「咁多」,加上 Java 經常以 Backward Compatibility 作為賣點,所以阿 Gap 認為 Java 本身嘅 Language Features 必定比較落後。

相反,Swift 係比 Java 累積多十幾年嘅經驗設計出黎,解決一啲 Developer 經常遇到嘅開發問題、提升 Developer 嘅開發效率、提升 Coding 嘅 Expressiveness,並實踐呢個概念:

Let the compiler do it for you

所以 Swift 比 Java 更強大係一個不爭嘅事實。

Java -> Swift ?

相信寫開 Java 無論係唔係寫 Android 嘅朋友,第一次接觸 Swift 都未必感覺到 Swift 嘅強大。因為多數有深厚 Java 功力嘅 Programmer 好容易會停留咗係 Java 嘅世界,嘗試用「Java way」再「Write it in Swift」。

呢個想/做法只係一種 Syntax 轉換,唔可能體驗到 Swift 有幾強大。

原因好簡單

  • Java 「做到」嘅 Swift 都會「做到」
  • Swift 「做到」嘅,Java 可能要花好大氣力先「做到」

Java :「Java can ONLY work in this way」

Swift :「Swift can work in a better way」

Swift 提供嘅 Language 特性可以改變整個 Software Architecture,新嘅 Software Design 可以比表達得比 Java 更 Clean、更容易 Maintain,從而更高 Productivity。

不過 Software Design 呢個已經係非常 Advance 嘅 Topic。阿 Gap 先介紹比較易理解嘅 5 大特性 - 非常幫助到 Developer 提升 Productivity,同時又可以令啲 Code 更容易 Maintain。


1. Type Inference

Java 同 Swift 都係 Static-Typing Language,Static-Typing 可以令 Code 更容易理解、Maintain,所以係 Large Scale Application 嘅「必需品」。

但係當我地 Develop 一啲細 Scope Features 嘅時候,用咗邊一種 Type 根本顯然易見,若果仲要打埋個 Type 出黎只會拖慢開發進度,Type inference 就可以係呢個位置發揮作用。

好似 Java 咁,其實好明顯就知道 a 係一個 Map<String,Integer>,但都要特登寫出黎...

// Java 9
Map<String,Integer> a = Map.of("a",1, "b", 2, "c", 3);

// Java >= 5
Map<String,Integer> a = new HashMap<String, Integer>() {{  
    put("a",1);
    put("b",2);
    put("c",3);
}}

當一段 Code,任何一個 Programmer 可以直接睇得出個 Type,根本無需要寫出黎,如果一定要寫埋出黎只會令啲 Code 變得累贅。

Type inference 可以保留 Static-Typing 嘅優點,開發速度又可以媲美 Dynamic Typing 嘅 Language 例如 (PHP、javascript)

// Swift
var a = ["a": 1, "b": 2, "c":3 ]

a["d"] = "4" // fail, compile error: String can't cast to int  
a["d"] = 4   // OK

print(a.dynamicType) // Dictionary<String, Int>  

2. Null safe

阿 Gap 相信無論你有幾深厚 Java 功力,相信你最多時間見到嘅 Exception 就係 NullPointerException。因為 Java 設計嗰陣懶咗,容許你 Define Variable Without assigning anything (default null),呢個亦係好出名公認嘅缺陷叫做 -「Billion Dollar Mistake」,有興趣嘅朋友可以自己 Google 睇下個典故。

// Java
String a;  
System.out.print(a.toLowerCase()); // Throw null pointer exception  

呢一個問題其實非常嚴重,極度影響 Programmer 寫 Code 嘅自信,有時候要 trace 幾個 Function 先會知道個 Variable 由邊度 Assign,判斷有無機會 null,事關無可能每個地方都加 if (a != null) ...

直至去到 Java 8 先出咗一個官方 Optional<T> Wrapper「希望」可以解決到呢個問題,但係實際上要花極大量氣力重寫所有 Method,將所有有機會 Return null 嘅 Method 寫成 return Optional.ofNullable(a) + Return Type 做 Optional<T> 先會有希望真正解決到 null 呢個問題。

相反 Swift 本身就係 Null safe, Compile Time 時已經會 Fail 你唔比你過,要 Assign 一個 nil 比 Variable 必需要有 ? 呢個 Optional Syntax。

// Swift
var a0 // fail, compile error  
var a1 = nil // fail, compile error  
var a2: String = nil // fail, compile error  
var a3: String? = nil // OK  
print(a3) // OK, print nil  
print(a3.capitalizedString) // fail, compile error  
print(a3?.capitalizedString) // OK, print nil

a3 = "abc"  
print(a3?.capitalizedString) // OK, print Optional(ABC)  
print(a3!.capitalizedString) // OK, print ABC  

3. Pattern matching

現實世界好多野都會可以以一個 Pattern、一個 Truth Table 出現,好多 Value 其實都應該係 Optional,喺 Java 想完全避免 NullPointerException 就必需用到 Optional 去做,但其實係 Java 玩 Optional 要 Unwrap 嘅時候非常痛苦。

例如有兩個 Optional Input nameOpt addressOpt,我要分別係不同情況做不同嘅野。係 Java 大約可以咁樣表達 :

// Java >= 8
Optional<String> getInput() {  
    // 50% chance to get Optional.empty()
    final boolean random = new Random().nextInt(1) % 2 == 0;
    return random ? Optional.of("input") : Optional.empty();
}

final Optional<String> nameOpt = getInput();  
final Optional<String> addressOpt = getInput();

if(nameOpt.isPresent() && addressOpt.isPresent()) {  
    final String name = nameOpt.get();
    final String address = addressOpt.get();
    System.out.println(String.format("Both name=%s address=%s exist ",name,address));
} 
else if (nameOpt.isPresent()){  
    final String name = nameOpt.get();
    System.out.println(String.format("Only name=%s exist",name));
} 
else if (addressOpt.isPresent()){  
    final String address = addressOpt.get();
    System.out.println(String.format("Only address=%s exist",address));
} 
else {  
    System.out.println("name and adress not exist");
}

係 Java 8 處理一個 Optional 可以直接用Optiona.ifPresent(..)unwrap。但要處理多過一個 Optional 嘅時候非常痛苦;阿 Gap 唔會建議用 Nested Optional.ifPresent() ,一定係用 Optional.get()逐個 unwrap 出黎,之後再做 Logic,非常麻煩。如果你怕煩唔用 Optional 做包住啲「真係 Optional 嘅野」當愈 Scale 愈大嘅時候,又會好容易跌落 2. 提到 NullPointerException 嘅陷阱入面。

另外,if, else if 會比較難 Trace,事關我地無需要將所有 Condition 寫入 if 入面,例如其中一個 else if (nameOpt.isPresent()) 呢一個 if 好明顯無提到 !addressOpt.isPresent() ,因為呢個 else if 嘅 Condition 已經建基於第一個 if,即係要由最頂嘅 if trace 到最尾先會完全理解到。

但如果使用 Swift 內置嘅 Pattern matching,成段 Code 將會變得更乾淨、清楚、易明。

註解: String?(Optional String) 只會係 .Some(String) 或者 .None

先將兩個 Optional String 變成一個 Tuple (nameOpt,addressOpt) 再做 Pattern matching

// Swift
// import Foundation

func getInput() -> String? {  
    let random = arc4random_uniform(2) % 2 == 0
    return random ? "input" : nil
}
let nameOpt: String? = getInput()  
let addressOpt: String? = getInput()

switch (nameOpt,addressOpt) {  
case let (.Some(name), .Some(address)):  
    print("Both name=\(name) address=\(address) exist ")
case let (.None,.Some(address)):  
    print("Only address\(address) exist")
case let (.Some(name), .None) :  
    print("Only name\(name) exist")
default:  
    print("name and adress not exist")
}

unwrap Optional value 喺 Swift 簡潔好多,加上每個 Case 都喺 Mutually exclusive,提升 Readability,如果寫嘅 Case 唔係 Mutually exclusive 嘅話,Compiler 一樣會出 Warning 提你,如圖:


4. Named + Default parameters

Named parameter 真的非常重要,可以大大提升 Readability、大大減低 Human error、有助 Debug。另外好多情況我地都需要 Default Parameter 去設計 Function。

以下呢個 doSomething 有兩個 Default Parameter,兩個 Required Parameter,總共可以 4 個 配搭嘅 Call 法;

// Swift
func getInput(field name: String) -> String {  
    return "Input \(name)"
}

func doSomething(fistName fname: String, lastName lname: String, address addr: String = "N/A", country c: String = "HK") {  
    print(fname,lname,addr,c)
}

let firstName = getInput(field: "firstName")  
let lastName = getInput(field: "lastName")  
let address = getInput(field: "address")  
let country = getInput(field:"country")

// with two default value
doSomething(  
    fistName: firstName,
    lastName: lastName
)

// with one default value (country)
doSomething(  
    fistName: firstName,
    lastName: lastName,
    address: address
)

// with one default value (address)
doSomething(  
    fistName: firstName,
    lastName: lastName,
    country: country
)

// all default value is overrided
doSomething(  
    fistName: firstName,
    lastName: lastName,
    address: address,
    country: country
)

如果要做番一模一樣嘅 Logic,係 Java 心水清嘅你會係點做?4 個都係 String Type,相信Method overloading 已經無可能做到。喺唔起多個 Class 嘅情況下,相信只有以下呢個唯一做法:

// Java
void doSomething(String fname, String lname, String addr, String country) {  
  if(addr == null) addr = "N/A";
  if(country == null) country = "HK";
  System.out.println(fname + lname + addr + country);
}

差唔多姐? 你再諗下你會點 Call 呢個 Method ?

// Java
// What is the null suppose to be?
doSomething(firstName,lastName,null,null);  
doSomething(firstName,lastName,address,null);  
doSomething(firstName,lastName,null,country);  
doSomething(firstName,lastName,address,country);

// without named paramenter...
// Can you notify the bug when you type out this?
// it is safe to compile but logically error
doSomething(lastName,firstName,country, address);  

5. Custom Operator

好多時一啲 Operator 例如 + - ++ :+ 可以好有意思咁表達想做嘅野, Programmer 設計 Library 嘅時候如果可以有意義地運用到 Operator,做出黎嘅 Library 可以比文字表達得更有意思。 但 Java 就因為唔可以有 Custom Operator,令到好多 Class 用起上黎極度不方便,最經典又最常用嘅例子當然係 java.math.BigDecimal

例如我想好簡單咁計 BigDecimala - b * (a + b) - b,係 Java 要寫成咁樣

// Java
import java.math.BigDecimal;

BigDecimal a = new BigDecimal(2);  
BigDecimal b  = new BigDecimal(3);

a.subtract(b.multiply(a.add(b))).subtract(b);

System.out.println(a.toString());

如果用 Swift 寫個簡單版嘅 BigDecimal 淨係要黎計 int 會係點? (實際嘅 BigDecimal Implementation 複雜好多)

// Swift
// BigDecimal Library
class BigDecimal {  
    var value: Int

    init(value: Int){
        self.value = value
    }
}

func + (left: BigDecimal, right: BigDecimal) -> BigDecimal {  
    return BigDecimal(value : left.value + right.value)
}

func - (left: BigDecimal, right: BigDecimal) -> BigDecimal {  
    return BigDecimal(value : left.value - right.value)
}

func * (left: BigDecimal, right: BigDecimal) -> BigDecimal {  
    return BigDecimal(value : left.value * right.value)
}

寫好咗啲 func 之後就可以直接用

// Swift
let a = BigDecimal(value: 2)  
let b = BigDecimal(value: 3)

let result = a - b * ( a + b ) - b  
print(result.value) // -16  

更多用法例如 User + User -> Array<User>

// Swift
class User{  
    var name: String

    init(name: String) {
        self.name = name
    }
}

func + (left:User, right: User) -> Array<User> {  
    return [left,right]
}

func + (left:Array<User>, right: User) -> Array<User> {  
    return left + [right]
}

let gap = User(name : "gap")  
let gary = User(name : "gary")

let users = gap + gary + gap  
print(users) // [User, User, User]

有感而發

Java 係一隻有二十年歷史、一隻打著 「Backward Compatibility」旗號嘅 Language,呢個旗號喺呢二十年黎相信變成一個好大包袱、制肘令佢進步得好慢,開發體驗根本無可能同新晉 Language 比較。

文章開首,阿 Gap 曾經提到主力寫緊嘅 Scala 同 Swift 好相似,無錯,文章提到嘅 Swift 5 大特性 Scala 亦全部有齊。

阿 Gap 覺得 Scala 條 Learning Curve 的確好 Deep 好難,但學識咗可以完美取代 Java,同時可以減低好多 Programming 煩惱,將 Productivity 提升到去另一個層次。

完美取代 Java?

Quote from Scala Official Documentation

Scala runs on the JVM. Java and Scala classes can be freely mixed, no matter whether they reside in different projects or in the same. They can even mutually refer to each other, the Scala compiler contains a subset of a Java compiler to make sense of such recursive dependencies.

Java libraries, frameworks and tools are all available. Build tools like ant or maven, IDEs like Eclipse, IntelliJ, or Netbeans, frameworks like Spring or Hibernate all work seamlessly with Scala. Scala runs on all common JVMs and also on Android.

總結

阿 Gap 估計好多人認為呢單 《 Google is said to be considering Swift as a ‘first class’ language for Android 》 係 Google 出口術想嚇下 Oracle,全因早兩個星期單 Orcale 獅子開大口要 Google 比 $9.3B 版權費 《Oracle seeks $9.3 billion for Google’s use of Java in Android》

但阿 Gap 就覺得如果用一個 Developer Perspective 去睇,可能純粹因為 Java 有太多歷史包袱、太唔爭氣,同其它 Programming Language 比下去,令 Google 班 Developers 萌生轉其它 Language 做 Android 嘅想法。

成日聽見有人話「我用 Java 都做到啦!」咁可以代表啲乜野?

「做到」只係最基本要求,呢個年代比較嘅重點根本唔係「做唔做到」,而係比較邊個 Abstract 得更好,達到「Simple、Clean、Elegant、Intuitive」嘅境界。

可能你覺得文章入面只係講咗幾個好 Fundamental 嘅例子比較唔得大體,但你試想下放大到去一個過千個 File 嘅 Application ?

如果連一個 Fundamental 嘅例子都可以比較得出優劣,比較一個完整嘅 Application 優劣將會差天共地!

假如呢個世界有一個指標叫做 Application Complexity,Swift 同 Java 寫出黎嘅 Application 相信可以相差一個 Log 嘅 Order of Magnitude,數學啲咁表達就係:

Application Complexity

Swift = O(n log n), Java = O(n^2) , where n = Number Of business logics

送句格言比睇到最後嘅 Developer

If a programming language is sufficient powerful, then the limit is your imagination.

Obviously, Java is NOT powerful enough :) - Gap


Swift Resource

Run Swift Online
IBM Swift sandbox

FRP in iOS
https://gist.github.com/JaviLorbada/4a7bd6129275ebefd5a6

Swift Evolution
https://github.com/apple/swift-evolution

Kitura @IBM - Web famework & Http Server for Swift
https://developer.ibm.com/swift/products/kitura/


Author image
About Gap
Hong Kong
A Hongkong programmer who loves design & coding and believes open source software business can change the world.