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