Ruby面試精選30題 - Day17 Ruby裡的freeze和frozen?

freeze照字面解釋就是凍結的意思。.frozen?可以傳回truefalse,幫我們確認凍結是否為真。詳細怎麼使用呢?就讓我們繼續看下去!


重點摘要:


Ruby經典面試題目 #17

  • 解釋Ruby裡的freeze?frozen
    Explan when to use freeze and frozen in Ruby?`

在Ruby裡面有一件好玩的事,就是連大寫的常數CONSTANT也可以修改!來看看下面的例子:

ROLE_CONSTANT = "Rubyist"
ROLE_CONSTANT << "Ironman"
puts ROLE_CONSTANT.inspect

<<幫我們改變了ROLE_CONSTANT常數:

tingdeMacBook-Air:Ironman tingtinghsu$ ruby freeze.rb
"RubyistIronman"

大家對於常數應該要「恆常不變」的印象,竟然都會改變!更別說是一般的變數了:

variable_array = %w( string integer array )
print variable_array
puts "-add something-"
variable_array << 'hash'
print variable_array

結果顯示:

["string", "integer", "array"]-add something-
["string", "integer", "array", "hash"]

而freeze照字面解釋就是凍結的意思。如果我們要確保某個常數/變數不會被修改,就需要利用到.freeze方法。

.freeze 用在常數

透過使用.freeze方法加在常數值之後,我們可以產生真正意義上的常數,讓常數永遠不變(immutable)。

ROLE_CONSTANT = "Rubyist".freeze
ROLE_CONSTANT << "Bartender"
puts ROLE_CONSTANT.inspect # can't modify frozen String (RuntimeError)

如果硬要修改的話,就會產生RuntimeError錯誤。

.freeze 用在變數

而變數也一樣,我們想把新值加入已經被freeze的陣列,也會出現RuntimeError

frozen_array = %w( ice water steam )
frozen_array.freeze
frozen_array << 'fire' #can't modify frozen Array (RuntimeError)

值得注意的是,雖然陣列不能被改變,但陣列裡的字串string仍然可以被改變!

frozen_array = %w(ice water steam)
frozen_array.freeze

frozen_array[0][2] ="y"
p frozen_array #=> ["icy", "water", "steam"]

Array裡的第一個字串從ice變成icy了。為了避免這種狸貓換太子的事情發生,我們來介紹一個新的方法:.each(&:freeze)

.each(&:freeze)

除了用.freeze確保陣列無法被改變,也要加入.each(&:freeze)方法,做好雙重保障,讓陣列裡面的各個值不能被修改。

frozen_array = %w(ice water steam)
frozen_array.freeze
frozen_array.each(&:freeze)
#frozen_array << 'fire'

frozen_array[0][2] ="y" #`[]=': can't modify frozen String (RuntimeError)

加入.each(&:freeze)之後,[]=方法是不是就無法被隨便使用了呢?:)

.frozen?

.frozen?可以傳回truefalse,幫我們確認凍結是否為真。

frozen_array.freeze
p frozen_array.frozen? #=> true

.map(&:frozen?)

如果要檢查集合裡(Array或Hash)的元素是否被freeze,可以使用.map(&:frozen?)

frozen_array.each(&:freeze)
p frozen_array.map(&:frozen?) # =>[true, true, true]
p frozen_array.each(&:frozen?) # doesn't work! print nothing.

注意:如果改成.each(&:frozen?)是無效的。因為我們曾在[第14天]的文章提到,.map方法會幫我們產生新陣列、放入新值,在以上的例子是放入布林值[true, true, true]。而.each不會產生新陣列。

使用freeze的效能

提到freeze具有不可變(immutable)的功能,就讓我想起自己在[鐵人賽第七天]文章提到的Symbol符號與String的字串比較。檢查是否可變的最好方式,就是檢查object_id

frozen_array = %w(ice water steam)
frozen_array.freeze
3.times do
  p frozen_array.object_id
end

陣列在記憶體位置裡不改變:

70243150761140
70243150761140
70243150761140

而如果我們不使用.each(&:freeze)方法,凍結陣列中的各個元素的話,

frozen_array = %w(ice water steam)
#frozen_array.each(&:freeze) 註解,暫時拿掉方法
5.times do
  p frozen_array[1].object_id
end

陣列內值的記憶體位置仍然會跑來跑去!

70200015881620
70200015881420
70200015881320
70200015881140
70200015880960

這篇文章的Benchmark(標竿測試,讓程式設計者很方便的測量程式的執行時間),幫我們比較.freeze和沒有freeze過的變數,結果發現freeze過的變數佔用的記憶體空間少,速度也更快!

舉例來說,使用在Rails的Route路由頁面,就可以大幅加快網路存取速度。另外這篇文章也提醒我們,擁抱、並習慣這種以簡單的方式就能優化Ruby效能的寫法唷。:)

Ref: