動靜結合?Ruby 和 Java 的基礎語法比較(入門篇)

前言

悉尼華助中心太極拳老師爲華裔長者新開免費太極班-中國僑網

這篇文章示例代碼比較多, Java 程序員可以看到一些 Ruby 相關語法和使用,Ruby 程序員可以看看 Java 的基本語法和使用方法,本文比較長,將近萬字左右,預計需要十幾分鍾,如果有耐心讀完文章的話,你將獲得和了解:

  • Ruby 語言的基本語法和使用方式
  • Java 語言的基本語法和使用方式
  • 從老司機的角度分析和講解 Ruby 和 Java 語言語法的特點和區別
  • 它們的各自適合併且擅長的應用場景

網上單獨介紹 Ruby ,Java 的文章應該很多,但是對比兩種編程語言的基本語法使用的文章應該不多見,寫這篇文章的目的主要是對自己近期幾個月學習 Ruby 做總結和回顧,我之前最熟悉的編程語言是 Java,我個人認爲合格的程序員應該掌握多門語言,多學一門語言沒有壞處,在解決問題的時候可以多些思路,在經歷最近幾個月的橫向對比和使用感受,先拋我個人結論,在個人項目或者小型團隊,技術能力較強的團隊我推薦使用 Ruby, 在團隊需要快速擴展和大型項目規劃的情況下我推薦 Java,因爲得益於 Java 語法的嚴謹性和安全性,很大程度上可以保證團隊水平的下限,Java 較強的工程化規約和代碼類型檢查,可以保證新手不至於寫出破壞性很強的代碼,如果把兩種語言作爲一個簡單的比如,最直觀的感受就是可以把 Ruby 和 Java 比做金庸小說裏的兩把武器:

  • Ruby 設計精妙,體積小巧靈活迅捷如風,就像紫薇軟劍那般鋒芒畢露,使用者可以隨心所欲,不必被太多語法和規則限制
  • Java 老成持重,雖然語法和年代較爲古板囉嗦,但是卻長年佔據 TIOBE 編程語言排行榜第一名,真可謂是重劍無鋒,大巧不工

在很多人的印象中 Ruby 主要是在初創公司會比較流行,例如早期的 AirbnbGitLab 都是使用 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_HOMECLASS_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 經過 StreamLambda 語法增強後,遍歷數組也沒有想象中那麼古板,示例代碼:

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 雖然循環方式多,但是平時常用的也就 eachfor 會比較多,在循環上的區別,大多隻是兩種語言在語法上的區別

方法

分類

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-ininclude 使用,爲類提供增強和更多的可能性

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 方法

image-20200427221028455

兩種語言的基本語法對比就先寫到這裏,暫時就不寫分析和總結了,因爲我後續還會繼續探索 RubyJava 在其他使用層面的使用區別對比,例如字符串,數據類型,集合,哈希,最後想留一個問題:你覺得靜態語言和動態語言最明顯的區別在哪裏?他們各自會存在什麼問題?在什麼場景下你會偏向動態語言,什麼場景你會偏向靜態語言?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章