動靜結合?Ruby 和 Java 的基礎語法比較(入門篇)
- 2020 年 4 月 29 日
- 筆記
前言
這篇文章示例程式碼比較多, Java 程式設計師可以看到一些 Ruby 相關語法和使用,Ruby 程式設計師可以看看 Java 的基本語法和使用方法,本文比較長,將近萬字左右,預計需要十幾分鐘,如果有耐心讀完文章的話,你將獲得和了解:
- Ruby 語言的基本語法和使用方式
- Java 語言的基本語法和使用方式
- 從老司機的角度分析和講解 Ruby 和 Java 語言語法的特點和區別
- 它們的各自適合併且擅長的應用場景
網上單獨介紹 Ruby ,Java 的文章應該很多,但是對比兩種程式語言的基本語法使用的文章應該不多見,寫這篇文章的目的主要是對自己近期幾個月學習 Ruby 做總結和回顧,我之前最熟悉的程式語言是 Java,我個人認為合格的程式設計師應該掌握多門語言,多學一門語言沒有壞處,在解決問題的時候可以多些思路,在經歷最近幾個月的橫向對比和使用感受,先拋我個人結論,在個人項目或者小型團隊,技術能力較強的團隊我推薦使用 Ruby, 在團隊需要快速擴展和大型項目規劃的情況下我推薦 Java,因為得益於 Java 語法的嚴謹性和安全性,很大程度上可以保證團隊水平的下限,Java 較強的工程化規約和程式碼類型檢查,可以保證新手不至於寫出破壞性很強的程式碼,如果把兩種語言作為一個簡單的比如,最直觀的感受就是可以把 Ruby 和 Java 比做金庸小說里的兩把武器:
- Ruby 設計精妙,體積小巧靈活迅捷如風,就像紫薇軟劍那般鋒芒畢露,使用者可以隨心所欲,不必被太多語法和規則限制
- Java 老成持重,雖然語法和年代較為古板啰嗦,但是卻長年佔據 TIOBE 程式語言排行榜第一名,真可謂是重劍無鋒,大巧不工
在很多人的印象中 Ruby 主要是在初創公司會比較流行,例如早期的 Airbnb,GitLab 都是使用 Ruby 作為開發語言,Ruby 是一門很靈活也很優雅的動態語言,解釋運行,有興趣了解的同學可以點開 鏈接 查看維基百科的詞條,Ruby 語法精鍊,做相同的事情程式碼行數通常會比 Java 要短的多,使用 Ruby 寫程式的的過程是非常舒服的,因為不必拘泥於那些刻板強制的語法規範,可以讓開發者隨心所欲的表達自己的想法,不必強制分號結尾,不強制中括弧,不強制方法參數長度等語法規範所限制,這種靈活的表達方式設計體現在語言使用的方方面面,並且如果你是用 Mac OS
則系統天生支援 Ruby 開發環境,在 Mac 終端
輸入以下命令就可以看到 Ruby 版本號:
ruby -v
# ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin19]
然後只要在終端裡面鍵入 irb
就可以進入調式模式,不像要運行 Java 程式首先安裝 JDK
然後再配置環境變數 JAVA_HOME
和 CLASS_PATH
經過繁瑣的配置才勉強可以執行 java
命令執行 class 程式,在 irb
模式下,對於簡單的邏輯程式可以先在調式模式將程式碼寫出來驗證想法的可行後再加入到程式碼庫中去,使用起來非常的方便,示例如下:
>irb
>2.6.5 :001 > p "hello world"
# => "hello world"
下面簡單的寫一個 Hello World 程式進行對比,兩種程式語言在列印 HelloWorld 程式上的寫法,示例程式碼如下:
// java print
public class Main {
public static void main(String[] args) {
System.out.println("hello world!"); // 注意分號結尾
}
}
# ruby print
p "hello world!"
通過一個簡單的 Hello World 程式你就可以發現兩者的明顯區別:
- Ruby 的執行是從上到下順序執行,main 方法則是 Java 程式的唯一入口
- Ruby 不必用
;
號結束符,不必使用{}
聲明程式碼塊,函數式方法傳參甚至不用使用()
(挺有意思)
經過以上講解,大家可能會對開始產生一些興趣,不過這僅僅只是開始,後面主要簡單介紹一下 Ruby 常用的對象,條件,循環,方法,運算符,數值,數組,字元串,散列等使用方法,本文不算嚴格意義的文章,因為示例程式碼量佔了文章的 50% ,而且本文的特點就是會在語法將 Ruby 和 Java 進行對比,不過還是會講解 Ruby 基本語法為主,本文偏入門級水平,介紹的內容都是平時使用比較的多的場景,暫時不會涉及到例如 Ruby 的 metaprogramming 和 Java 的 反射等較為深入的知識點,可能後續會有單獨的文章進行分析,看完文章應該可以用寫一些簡單的程式用於跑一些簡單的腳本應該是夠用了,實際上腳本處理程式也正是 Ruby 很擅長的領域
補充:文章對比 Java,Ruby 兩種語言在語法上的區別,並不是爭論哪種程式語言的好壞優劣,我個人觀點是:程式語言本身沒有好壞之分,只有在不同場景下做出合適的選擇,而且熟悉 Java 的同學也應該明白 Java 的優勢從來都不在語言和語法層面,而是在生態,並發編程,虛擬機和穩定這些特性才是 Java 的核心競爭力,在生態上 Spring Framework
為代表的高品質輪子覆蓋 Java 應用的方方面面,可以說在輪子的多樣性上面,暫時沒有哪種語言可以跟 Java 抗衡,所以如果簡單的用語法概括語言的好壞就非常以偏概全的看法,話不多說,我們進入正題,先列一下文章大綱,入門篇只會簡單說一些基本語法:
- 多重賦值
- 條件判斷
- 循環
- 方法
- 類和模組
- 運算符
- 異常處理
多重賦值
每個變數單獨賦值的場景大多相同,就不做介紹,在程式開發中,我們經常會把多個變數同時賦值,這樣效率會高很多,每種語言對多重賦值的支援都不同,我們先通過一段程式碼對比 Java,Ruby 語言對於多重賦值的不同寫法:
// Java 多重賦值
int a, b, c = 1, 2, 3; // compile error
int a, long b, short c = 1, 2L, 3; // complier error
int a = 1, b = 2, c =3; // compile pass
# Ruby 中多重賦值非常輕鬆
a, b, c = 1, 2, 3
# => [1, 2, 3]
# 兼容不同類型
a, s, c = 1, "2", 3
# => [1, "2", 3]
# 兼容不同長度
a, b, c = 1, 2
# => [1, 2, nil]
a, b, c, = 1, 2, 3, 4
# => [1, 2, 3] 自動裁掉超出的長度
結合以上案例感覺 Java 對多重賦值不是很友好,很多不合規範的語法在編譯期就會被攔截並且報錯,簡單對比後總結:
- Java 因為強類型,所以對賦值的比較限制多,例如只能對同類型的變數進行簡單的賦值
- Ruby 中多重賦值比較輕鬆,不用考慮類型,長度等問題,過長和過短都不會在編譯時拋出問題
- Ruby 在聲明類型的時候不需要像 Java 那樣聲明類型,這也是動態語言的特性,我個人是比較喜歡的
條件判斷
Ruby 的條件判斷主要有以下三種:
- if 語句
- unless 語句
- case 語句
先看實例和對比程式碼:
a, b = 10, 20
p "a 比 b 大" if a > b
p "a 比 b 小" if a < b
# => a 比 b 小
# unless 條件就不多做介紹,用法剛好與 if 語句相反,類似java中的 != 運算符,實例程式碼:
a, b = 10, 20
p "a 和 b 不相等" unless a == b
# => a 和 b 不相等
int a = 10, b = 20;
if (a > b) System.out.println("a 比 b 大"); // 在合作項目中還是推薦使用中括弧 {}
if (a < b) System.out.println("a 比 b 小");
//=> a 比 b 小
int a = 10, b = 20;
if (a != b) System.out.println("a 和 b 不相等");
//=> a 比 b 小
還有 case 語句主要用於多條件進行判斷,語句用法是 casewhenend 進行組合條件判斷,功能跟 Java 中的 switch 相同,還有邏輯運算符 ==, !=, ||, && 都是通用的基本知識,所以就不寫詳細說明和寫示例程式碼了,不然會顯得很啰嗦
總結:
條件判斷語句用法非常簡單,兩種程式語言基本類似語言類似,不過還是有以下區別:
- Ruby 在關鍵字選擇上多一些,例如
unless
實際上是替代了運算符!=
,也增加了一些可讀性 - if 語法基本相似,但 Java 強製表達式必須使用括弧
()
,Ruby則不需要 - Ruby 使用 ifthenend 語法標記程式碼塊,不同於 Java 使用中括弧
{}
標記程式碼塊 - Ruby 條件判斷 if/unless 放在程式碼後面,程式看上去可以更加緊湊和簡潔
循環
Ruby 的循環結構語句比較豐富,相比 Java 只有 for,while 兩種循環方式來說,Ruby 中的可用的循環方法有:time,while,each,for,until,loop,不過大多都異曲同工,就不一一介紹了,本章節主要圍繞平時常用的幾個需求來做一個簡單的講解,對比兩種語言的使用區別,具體如下:
- 如何執行一個固定次數的循環 ?
- 如何遍歷一個數組 ?
- 如何遍歷一個 Hash ?
執行固定次數的循環是 time循環 方法的拿手好戲,用於和語句也很簡單,如果不需要下標值,|i|
參數也是可以移除的,示例程式碼如下
3.time do |i| # i 也可以省略
p "第#{i}次列印"
end
# => 第0次列印
# => 第1次列印
# => 第2次列印
在 Java 中想要執行固定長度的循環,不能通過 forEach
只能通過古老的 for..i
來實現,具體程式碼如下:
for (int i = 0; i < 3; i++) {
System.out.println("第" + i + "次列印");
}
// 第0次列印
// 第1次列印
// 第2次列印
如何遍歷一個數組?
在 Ruby 中通常會推薦使用 **each ** 不僅語法簡單,而且可以輕鬆拿到元素值,示例程式碼如下:
["abc","efg","hmn"].each do |e|
p "#{e}!"
end
#=> abc! dfg! hmn!
Java 在 JDK 8 經過 Stream
和 Lambda
語法增強後,遍曆數組也沒有想像中那麼古板,示例程式碼:
Arrays.asList("abc", "dfg","hmn").forEach(e ->
System.out.println(e + "!")
);
// abc! dfg! hmn!
不過在平時遍曆數組的時候經常會遇到一種需求,不僅想要拿到數組的元素,還需要拿到當前循環的索引值,Ruby 中提供一個特別的 each 方式實現,就是 each_with_index
方法,它會把 [元素, 索引]
傳入到 do 程式碼塊的後,具體示例程式碼:
["abc","def","ghi"].each_with_index do |e, i|
p "當前元素 #{e} , 以及第 #{i} 次循環"
end
#=> "當前元素 abc , 以及第 0 次循環"
#=> ...
Java 想要實現相同循環效果就不能用基於迭代器的 ForEach 實現了,只能用 for..i
實現,示例程式碼如下:
List<String> list = Arrays.asList("abc", "deg", "ghi");
for (int i = 0; i < list.size(); i++) {
String e = list.get(i);
System.out.println("當前元素" + e + ",以及第 " + i + "次循環");
}
// 當前元素abc,以及第 0次循環
// ..
如何遍歷一個 Hash ?
Hash 是 Ruby 的常用的對象,因此循環遍歷獲取 K,V 也是相當方便的,示例程式碼:
hash = {name: "apple", age: 15, phone: "15815801580"}
hash.each do |k, v|
p "key: #{k}, value: #{v}"
end
#=> key: name, value: apple
#=> ...
Java 中最常用的 K-V 結構的 Hash 實現是基於 Map
介面的 HashMap
,它是一種非執行緒安全的哈希表實現,之所以常用是因為它兼顧的效率和時間的平衡,內部是通過數組實現,採用使用鏈表法
處理哈希衝突,JDK 8 後又引入 紅黑樹
解決哈希衝突過多導致鏈表過長的問題,這塊就先不展開講了,不然可以講很久,示例程式碼展示 Java 如何遍歷 Hash:
Map<String, String> hashMap = new HashMap<>();
hashMap.put("name", "apple");
hashMap.put("age", "15");
hashMap.put("phone", "15815801580");
for (Map.Entry<String, String> entry : hashMap.entrySet()) {
System.out.println("key :" + entry.getKey() + ", value : " + entry.getValue());
}
// key :name, value : apple
// ....
Java 遍歷 Hash 的方式還有很多種,我們這裡只展示最常用的用法,通過 ForEach 遍歷 entrySet() 返回的集合即可。
最後再說一個有意思的循環方法,不過使用場景應該很少,一個沒有終止的循環 loop方法,因為沒有終止條件,所以必須依賴 break 關鍵字跳出循環,Java 也可以很輕鬆實現這種循環效果,只是語法上不同而已,我們可以看看以下實例對比:
// java 使用 while(true) 或者 for(;;) 實現無限循環
while (true) System.out.println("i use java");
# ruby 無限循環
loop do
p "i use ruby"
end
如果程式進入無限循環就只能通過 CTRL + C
來終止程式運行了
總結:循環上兩種語言區別不大,Ruby 雖然循環方式多,但是平時常用的也就 each
, for
會比較多,在循環上的區別,大多只是兩種語言在語法上的區別
方法
分類
Ruby 中的方法大致可分為 3 類:
- 實例方法
- 類方法
- 函數式方法
實例方法:Ruby 中的實例方法 Instance method 和 Java 中的普通方法類似,顧名思義就是調用方必須是一個類的實例(對象),需要調用實例方法就必須先通過類構造一個實例對象才能進行調用,具體請看示例程式碼:
# ruby 中的實例方法
[1, 2, 3] .clear # 清理數組 => []
100.to_s # int 轉 string => "100"
"100".to_i # string 轉 int => 100
["a", "b", "c"].index("b") # 查找下標 => result: 1
// java 中的實例方法
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("abc"); // 實例方法 append
stringBuilder.append("efg");
List<String> strList = new ArrayList<>();
strList.add("abc"); // 實例方法 add
strList.add("efg");
類方法:Ruby 的類方法 class method 可以理解為 Java 的靜態方法,就是需要類對象作為接收方的方法,指無需構建類的對象即可以直接通過類調用其自身的方法,大多常見於工具類當中,請看示例程式碼:
// java 中的靜態方法
Arrays.asList(T...a) // 數組轉集合
Executors.newCachedThreadPool() // 創建執行緒池
# ruby 中的類方法
Hash.new # 創建散列對象
Time.new # 創建時間對象
函數方法是指沒有接收者的方法,這種類型方法在Java中倒是不存在,參考示例程式碼,例如上文中函數方法 p
p "hello"
puts "print words"
定義實例方法
Ruby 定義方法非常簡單,沒有 Java 那麼多的格式規範:修飾符:靜態聲明:返回值:方法名:(參數...)
,在方法聲明的形式上要簡單許多,主要通過 def
關鍵字定義,具體參考示例程式碼:
// java define method
public static int add(int x, int y) {
return x * y
}
add(2, 5)
// 10
# ruby define method
def add(x, y)
x * y
end
add(2, 5)
#=> 10
# 帶默認值的方法
def add(x=2, y=6)
x * y
end
# 省略參數使用默認值調用方法
add
#=> 12
# 指定參數的方法
add(2, 5)
#=> 10
在方法的命名規則,兩種語言還有如下區別:
- Ruby 方法名可由英文字母,數字,下劃線組成,但不能以數字開頭,例如 hello_with_name
- Java 方法名首字母必須由英文小寫開頭,英文格式遵循駝峰原則,不允許出現連接符,例如 addPerson
返回值return: 上面的 ruby 方法並沒有聲明 return 語句也可以拿到返回值,並不代表 ruby 沒有 return 關鍵字,ruby 有一個特點就是如果沒有聲明 return 語句那麼方法最後一個表達式會成為方法的返回值遵循這個約定所以上述的方法就可以省略 return 關鍵字,所以在日常開發中會較少的使用 return 關鍵字
定義類方法
前面講過 Ruby 的類方法實際上就等於 Java 的靜態方法,Ruby 中定義類方法的示例程式碼:
# ruby 定義類方法
class Hello
# class << self 定義類方法的一種方式
class << self
def say_morning
p "hello good morning"
end
#... class << self 塊後面還可以定義多個類方法
end
end
Hello.say_morning # 類方法
#=> "hello good morning"
h = Hello.new
h.say_morning # 如果用實例對象類方法就會報錯! undefined method 'say_morning'
在 Java 中定義靜態方法的示例:
public class Hello {
public static void sayMorning() {
System.out.println("hello good morning");
}
public static void main(String[] args) {
Hello.sayMorning();
}
}
#=> "hello good morning"
定義方法也都是很簡單的知識點,通過以上程式,我們可以得出:
- Ruby 使用
class << self
或者class << 類名
可以將程式碼塊內的方法全部聲明為類方法 - Java 使用
static
修飾符定義靜態方法,不能定義塊,我想可能因為規範和可讀性的原因
Ruby 的特點是特定的功能都可以有N種不同的方式實現,所以定義類方法不但只有 class << self ~ end
還可以使用 def class_name.method_name ~ end
這種形式定義 class_name 是類名,method_name 為方法名,如果是在當前 class 上下文中可以像示例程式碼用同樣的形式 def self.method_name ~ end
定義類方法
方法參數
默認參數 Rudy 的方法默認參數是我個人比較喜歡的特性,Java 程式里方法參數是強類型檢查的,就是必須按照參數定義的類型進行傳參,JDK 1.5 後 Java 也出了可變參的特性,不過因為實現效果不是很理性,目前在主流 Java 開發規範中還是不被推薦使用的,我們先看一段 Java 定義參數和使用參數的示例程式碼:
// 方法要求類型,順序,並且必傳
public void show(String name, int age, String address) {
System.out.println(name + ", " + age + ", " + address);
}
// new Person().show("胖大海"); // 編譯出錯,參數不符合要求
// new Person().show("胖大海", 19); // 編譯出錯,參數不符合要求
new Person().show("胖大海", 19, "北京朝陽區"); // 編譯通過輸出:胖大海, 19, 北京朝陽區
Ruby 則會靈活一些,具體請看示例程式碼:
# 無需聲明類型,帶默認值的參數可不傳
def show(name, age=18, address="北京市")
p "#{name}, #{age}, #{address}"
end
Person.new.show("胖胖") #=> 輸出:胖胖, 18, 北京市
不過對於可變參數兩種語言的實現幾乎相同,形式上都是對參數進行特殊標記,Java 是通過在參數前面加...
標識,ruby 則在參數前面使用 *
號標識,解釋器會對這種語法用數組進行轉換,兩者程式碼量也差不多,沒有什麼差別,簡單看下示例程式碼:
public void names(String ...names) {
System.out.println("params :" + Arrays.toString(names));
}
new Person().names("java", "ruby", "go", "c++");
// 輸出結果 :params :[java, ruby, go, c++]
def names(*names)
p "params: #{names}"
end
Person.new.names("java", "ruby", "go", "c++")
# 輸出結果:params: [\"java\", \"ruby\", \"go\", \"c++\"]
簡單通過 2 段程式碼的對比,我們可以對兩種語言的方法參數得出以下結論:
- Java 方法會嚴格按照定義,強制要求類型,值必傳,否則編譯期會報錯,並且無法在聲明時定義參數的默認值
- Ruby 方法參數未設定默認值,不傳參數,只會在執行期報錯,但如果聲明時定義參數默認值,則參數可不傳
- Ruby 方法參數無需定義類型,動態語言的類型大多是推斷出來的
- 可變參數兩者實現方式相同,Java 通過
類型...names
實現,Ruby 通過*names
語義實現
方法的基本使用大概就講到這裡,函數方法定義平時使用不多就暫時先不聊,繼續了解還可以看看:定義帶塊的方法,關鍵字參數等都是一些語法糖,就不詳細講解了,接下來聊聊類和模組
類和模組
類
Ruby 也是通過 class
關鍵字定義類,簡單的用法參考以下程式碼:
class Hello
end
h = Hello.new
Java 也是通過 class
定義類,不同的是最外層的類 Java 必須使用 public
進行修飾,具體請看示例程式碼:
public class Hello {
public static void main(String[] args) {
Hello h = new Hello();
}
}
那麼 Ruby 和 Java 在定義類方面有什麼區別?
- Ruby 類只有
initialize
構造函數,Java 可以根據參數不同定義不同的構造函數,Java 構造函數必須於類名相同 - Ruby 和 Java 在類的命名規則上是一致的,類名必須是首字母大寫開頭
- Java 通過
public class
修飾類(內部類通過class
修飾),Ruby 則通過 class 修飾類 - Java 類名必須與文件名相同,Ruby 的文件名和類名不要求強制關聯
兩種程式語言在構造函數上對比的示例程式碼:
# 帶構造函數的 Ruby 類
class Hello
def initialize(name="Ruby") # 默認參數
@name = name
end
def say_hello # 實例方法
p "hello #{@name} ! how are you?"
end
end
h = Hello.new("james")
h.say_hello
#=> "hello james ! how are you?"
// 帶構造函數的 java 類
public class Hello {
private String name;
// 有參構造函數
public Hello(String youName) {
this.name = youName;
}
public void sayHello() {
System.out.println("hello! " +name+ " how are you ?");
}
public static void main(String[] args) {
Hello h = new Hello("jack");
h.sayHello();
}
}
#=> "hello! jack how are you ?"
方法聊到這裡,下來聊聊方法里的常量
常量對比
如果在 Java 和 Ruby 中定義常量,參考示例程式碼:
// Java 中定義常量
public class Hello {
// 常量必須是 static final 修飾,代表不可變
public static final String VERSION = "1.0";
public static void main(String[] args) {
// Hello.VERSION = "1.5"; // 嘗試修改則會在編譯期報錯
System.out.println(Hello.VERSION);
}
}
#=>"1.0"
# Ruby 中定義常量
class PhoneNumber
BeiJing = "020"
GuangZhou = "021"
end
p PhoneNumber::BeiJing #=> "020"
p PhoneNumber::GuangZhou #=> "021"
Phonenumber::BeijING = "303" #=> Ruby 可以修改常量,不會報錯,但會提示警告
p PhoneNumber.Beijing #=> ERROR undefined method !
在定義常量上的區別:
- 命名規則:Ruby 要求常量首字母大寫,可用駝峰也可全大寫,Java 則要求常量全部大寫,並且必須是
final
static
修飾(Java 里的 final 代表不可變,可以聲明類,方法和變數) - 調用方式:Ruby 必須使用
::
通過類名進行外部訪問常量,java 把常量只是當成普通的局部變數,使用連接符.
直接訪問即可 - 修改變數:Java 不允許修改常量,任何修改的動作會讓編譯器報錯
Connot assign a value to final variable
並且無法通過編譯,Ruby 則不同,允許修改常量,但解釋器會提示警告資訊:warning: already initialized constant
訪問級別
Ruby 和 Java 在方法訪問級別上沒有什麼很大不同,只是 Ruby 沒有包(Package)的概念,所有自然也就沒有 Java 裡面的包訪問許可權,細節上但是還有些許區別,Ruby 的三種訪問級別的定義方法,具體用法直接看示例程式碼:
# 定義方法時聲明訪問許可權
private def call_v1
p "call_v1 is private"
end
# 定義方法後,再設定訪問許可權
def call_v2
p "call_v2 is private"
end
private :call_v2 # 設定 call_v2 為 private
# 對程式碼塊設定, 以下的方法定義為 private
private
def call_v3
p "call_v3 is private"
end
def call_v3
p "call_v4 is private"
end
Java 設定方法訪問級別的方式則比較單一,只能在定義時聲明:
private String priv() {
return "priv is private";
}
綜上所述,兩種語言在訪問級別的差異和總結:
- Java 方法默認修飾符是 包訪問許可權
- Ruby 方法默認訪問級別是 public(initialize 例外)
- Java 方法只能在定義的時候使用關鍵字設定訪問級別
- Ruby 常用的則有三種方式可以設定方法的訪問級別,非常靈活
繼承
Ruby 和 Java 的所有類都是基於 Object
的子類,Ruby 則還有更加輕量級的 BasicObject
原始類,這裡先不詳細描述,繼承這個概念也不多說,面向對象的基礎知識,直接先看兩種語言實現繼承的方式
Ruby 通過 < 父類名
實現繼承,示例程式碼:
class Car
def drive
p "car start.."
end
end
class SuperCar < Car
def speed_up
p "speed to 200km ..."
end
end
s = SuperCar.new
s.drive
s.speed_up
#=> "car start.."
#=> "speed to 200km ..."
Java 通過 extends
實現繼承的,示例程式碼:
class Car {
void drive(){
System.out.println("Car Start ...");
}
}
public class SuperCar extends Car {
void speedUp() {
System.out.println("speed to 200km...");
}
public static void main(String[] args) {
SuperCar c = new SuperCar();
c.drive();
c.speedUp();
}
}
// Car Start ...
// speed to 200km...
關於類的繼承方面我們可以得出以下總結:
- Ruby 通過
<
實現繼承, Java 通過extends
關鍵字實現繼承 - Ruby ,Java 在類沒有指定父類的情況下都默認繼承
Object
類
關於繼承還有一些經驗分享的就是,繼承的特性更多用於重寫父類和多態,如果是想要復用公共的功能,但是類之類沒有明顯的繼承關係的話,就應該遵循組合優先大於繼承的原則,不過在 Ruby 中很好的通過 Mix-in 擴展解決的繼承這個問題
模組和Mix-in
模組使用 module
關鍵字創建,命名規則和類一樣,首字母必須大寫,我們先來看看如何創建模組
module Display
def open
p "open display..."
end
end
Display.open # private method `open' called for Display:Module (NoMethodError)
模組是 Ruby 的特色功能,定位也很明確,有以下幾個特點:
-
不能擁有實例,不能被繼承,所以模組定位清晰,僅僅表示事物的通用行為
-
函數僅僅只能在內部被調用,除非使用
module_function
聲明模組函數 -
模組更多是結合
Mix-in
和include
使用,為類提供增強和更多的可能性
Ruby 中的模組提供的命名空間 namespace 概念就跟 Java 的包(Package)類似,都是用於區分相同的類,常量,Mix-in 結合 include
也就類似 Java 裡面的靜態導入,在 Java 中 import static
可以無需聲明包路徑直接調用導入類的變數和方法,所謂的 Mix-in 就是利用模組的抽象能力把非繼承關係的類之間的共性進行抽象和復用,有些類似 AOP 的概念,可以使程式碼既強大又靈活
當我們用 OOP 思想對現實進行抽象的時候,會發現很多非繼承關係又存在共同功能的事物,例如智慧手機,手錶繼承自不同的父類,但是他們又都有看時間 watch_time
的能力,我們用程式碼展現這種繼承關係,請看示例程式碼:
class Phone
def call
p "phone call.."
end
end
class IPhone < Phone
# iPhone 繼承自 Phone 類,但是不僅可以打電話,還可以看時間
def watch_time
p "watch_time 12:00:000"
end
end
class Watch
# 手錶都可以看時間
def watch_time
p "watch_time 12:00:000"
end
end
class AppleWatch < Watch
# AppleWatch 不僅看時間,還有運動的功能
def run
p "start run"
end
end
結合上面的程式碼,我們可以看到 watch_time 程式碼重複,對於不同繼承體系但是相同功能的時候,就可以用 Mix-in 解決這個問題,思路如下:
- 將例如 watch_time 相同的方法和程式碼,抽出定義在 module 模組中
- 使用
include
引入模組,將方法引入到實際的類中
使用 Mix-in 後我們可以看下程式碼變化,示例程式碼:
module WatchTime
# 抽出通用的方法
def watch_time
p "watch_time 12:00:000"
end
end
class Phone
def call
p "phone call.."
end
end
class IPhone < Phone
include WatchTime
end
class Watch
include WatchTime
end
class Apple_watch < Watch
# apple_watch 不僅看時間,還可以運動
def run
p "start run"
end
end
使用 Mix-in 這種靈活的語法實現了魚和熊掌兼得,即沒有破壞繼承結構關係又實現共性方法的程式碼復用問題,因為 Java 沒有 Mix-in 的概念所以就不展示示例程式碼了,不過 Java 也有自己的解決方案,而且在 Java
的解決程式碼復用問題通常都應該遵循 組合大於繼承
的原則,因為 Java
的語言設計讓繼承更多用於多態而非復用
運算符
簡單說一下運算符,雖然大多程式語言的運算符非常的簡單,賦值運算,邏輯運算,條件運算符所有語言的使用方式都幾乎差不多,好像沒什麼好講的,但 Ruby 靈活的語法是有不少語法糖,還是可以 Java 程式設計師羨慕的一下的,假設一張我們在業務程式碼中經常遇到的情況,根據表達式取值,當表達式為 true 時改變變數的值,這種簡單邏輯賦值在 Java 只能這樣寫,請看示例程式碼
String value = "abc";
if (condition != null) {
value = condition;
}
// 看上去很啰嗦
這種情況在 Ruby 中一行程式碼可以實現相同語義:
# 當 condition 表達式為 true 執行 value = condition , 否則執行 value = "abc"
value = condition || "abc"
只所以可以實現是因為 Ruby 有一個不同 Java 的特定, Ruby 對象都可以用於進行布爾表達式判斷,判斷邏輯為**對象本身不為 nil 或者 false 表達式則為 true,否則為 false **
還有一種邏輯則是取相反的情況,例如我們經常遇到一種情況是,判斷數組不為空的時候取數組的某一個下標,在 Java 中只能這樣寫,示例程式碼
List<String> list = Arrays.asList("a", "b", "c");
String item = null;
if (list != null) {
item = list.get(0);
}
// "a"
這種情況可以用邏輯運算符 &&, 它剛好與上面 || 相反,也是一行程式碼可以實現相同功能
str_list = ["a", "b", "c"]
item = str_list && str_list[0]
#=> "a"
我個人非常喜歡這種簡潔的寫法,不過建議在多人項目中不要用太多語法糖,不然可能會造成項目程式碼可讀性混亂
異常處理
很多程式設計師大部分時間都花在查錯上,所以迅速定位異常很關鍵,先看看 Ruby 的異常格式 文件名:行號:in 方法名:錯誤資訊(異常類名)
簡單的用法就不寫示例程式碼了,不然佔用太多篇幅,兩種語言處理異常方法大同小異,具體處理方式有如下區別:
- Ruby 處理異常使用
begin ~ rescue ~ ensure ~ end
這裡太簡單就不寫示例程式碼了 - Java 7 使用
try ~ catch ~ finally
到 Java 8 後有了更高效的try ~ with ~ resources
可自動關閉資源
不過 Ruby 的 Retry
倒是 Java 沒有的特性,它適合對一些容易出錯的程式(例如調用外部 API )可以進行快速重試,具體可以請看示例程式碼
class HandleException
def zero
x, y = 100, 0
begin
x = x / y
rescue
p '執行異常邏輯'
y = 2
retry
end
x
end
end
h = HandleException.new
p h.zero
#=>"執行異常邏輯"
#=>50
上述程式非常簡單,大概邏輯是首次執行會拋出異常,然後被 rescue 捕獲後重新複製,第二次運算成功,Java 如果要實現相同的語義的話,則程式碼沒有這麼簡潔了,跟上章節的邏輯運算符 &&
,||
類似 resuce
也具備條件表達式的運算,具備很強的表達能力,我們嘗試對以上述程式碼進行一些簡化,示例程式碼:
x, y = 100, 0
x = x / y rescue 50
# => x = 50
當運算 x / y
沒有出現異常則運算 x = x / y,當出現異常被 resuce
捕獲後則運算 x = 50,但相同邏輯在 Java 裡面就會顯得有點啰嗦,請看示例程式碼:
int x = 100, y = 0;
try {
x = x / y;
}catch (ArithmeticException ae) {
x = 50;
}
// x = 50
不過像這種小技巧建議只用於簡單的場景,如果業務流程複雜,為了保證程式碼清晰易懂還是建議使用標準的 begin ~ rescue ~ ensure ~ end
異常處理語句 ,異常章節到此結束,在文章尾部我們總結一下 Java 和 Ruby 在異常處理的區別:
- Ruby 標準異常庫都是繼承
Exception
類,程式通常只能處理StandarError
異常或其子類 - Java 異常都是繼承
Throwable
,異常被劃分為Error
異常和Exception
,程式通常只能處理Exception
的子類RuntimeException
以及其子類 - Ruby 支援
retry
從異常中快速重試,rescue
表達式簡化異常程式碼處理,Java 則沒有該功能 - Java 主動拋異常的使用
throw new Exception
,而 Ruby 則使用raise
方法
兩種語言的基本語法對比就先寫到這裡,暫時就不寫分析和總結了,因為我後續還會繼續探索 Ruby
和 Java
在其他使用層面的使用區別對比,例如字元串,數據類型,集合,哈希,最後想留一個問題:你覺得靜態語言和動態語言最明顯的區別在哪裡?他們各自會存在什麼問題?在什麼場景下你會偏向動態語言,什麼場景你會偏向靜態語言?