Java不支持運算符重載 = 小白也能學編程
Java之所以不支持運算符重載,并不是如下原因:
- 會使JVM變得復雜、性能下降:君不見C++內置運算符重載的能力?C++的性能在任何時代秒殺Java相信沒有爭議。
- 便于靜態分析、工具化等:一葉障目、不見泰山。運算符重載只是一種動態特性,動態語言的形式化靜態分析方法已經有成熟的方法論。
- Java是面向對象語言:Ruby是比Java更徹底的面向對象的語言,然而它對運算符重載的支持非常優秀,在Ruby中一切都是對象,幾乎一切都可以override。
不支持運算符重載的根本原因,是源自James Gosling設計Java的初衷:那就是要讓Java的學習門檻足夠低,這樣才能讓這個編程語言被更多的人使用,從而擁有最大的市場占有率。
Java誕生之前, 基本上是C/C++的天下。光C語言的一個指針,就嚇退了多少莘莘學子?C++引入更多的動態特性:多態、多重繼承、函數重載、函數重寫、運算符重載、泛型……這更不知道讓多少人望而卻步!
正是在那樣的大環境下,James Gosling才萌生了“開發一個小白都能上手”的編程語言的念頭。
運算符重載的底層思想并不是面向對象
運算符重載的底層邏輯來自函數式編程。它的祖師爺是Lisp,一個“從來被模仿、從未被超越”的神級語言。
可以負責任地講,如今流行的Python、Javascript、Typescript、Go、Ruby、Haskell、Scala、Groovy等,在動態高級特性上都是在不斷模仿60多年前的Lisp。包括Java從誕生起就在鼓吹的垃圾回收等優點,全部都是“偷師”Lisp。有興趣的小伙伴可以自行下載Lisp的發明者——John McCarthy老爺爺1960年發表的GC論文。
函數式語言的核心思想其實是數學。
說得更白話一點:通過數學表達式描述問題,而不是人肉模擬解答過程。問題描述完了,也就解決了——運行時處理執行細節。
說得更學院派一點:通過無狀態的函數加以其他優化特性,將這些函數組件進行拼接。
看到這里,估計有不少人要來拍磚:運算符重載看起來那么復雜,明明可以定義方法或者函數來解決,除了裝逼格,沒有實用價值。
筆者這里回應一下:數學本來就不是普通大眾擅長的,數學的目的就是用最簡潔的方式來解決最復雜的問題。所以函數式語言從誕生之初,就沒有想過要蕓蕓眾生。它追求的是大道至簡。
這里來看一個例子:計算一組數據(假設放在一個一維數組中)的標準差。
如果不采用函數式編程,采用通常的面向過程或者面向對象的編程范式,那么只能:
第一步,先通過循環體(for/foreach/while等),挨個遍歷求出平均值mean;
第二步,再來一次循環,挨個求與mean的差值并平方,然后逐個累加得到平方合sumOfSquares;
第三步,對sumOfSquares調用平方根函數,求出最終值standardDeviation。
下面我們來進化一點:
有基本函數式編程概念的小伙伴可能會寫出如下的簡化范式(這里以Ruby為例):
mean = a.inject {|x,y| x+y } / a.size
sumOfSquares = a.map{|x| (x-mean)**2 }.inject{|x,y| x+y }
standardDeviation = Math.sqrt(sumOfSquares/(a.size-1))
但是真正的函數式編程高手是會這樣寫的:
第一步:寫一個通用的數學意義上的復合函數(f(g(x)) = f*g(x))的表達:
module Functional
def apply(enum)
enum.map &self
end
alias | apply
def reduce(enum)
enum.inject &self
end
alias <= reduce
def compose(f)
if self.respond_to?(:arity) && self.arity == 1
lambda {|*args| self[f[*args]] }
else
lambda {|*args| self[*f[*args]] }
end
end
alias * compose
end
第二步:把計算標準差所需要的各個元素的數學表達列示好:
sum = lambda {|x,y| x+y } # A function to add two numbers
mean = (sum<=a)/a.size # Or sum.reduce(a) or a.inject(&sum)
deviation = lambda {|x| x-mean } # Function to compute difference from mean
square = lambda {|x| x*x } # Function to square a number
第三步:像寫標準差的數學表達式一樣,一步到位:
standardDeviation = Math.sqrt((sum<=square*deviation|a)/(a.size-1))
總結
Java之所以流行,并不是因為其語言設計得最優秀,相反地,在很多地方——比如泛型、Lambda、完全面向對象等設計上都存在不足。它的成功在于:揚長避短,把所有牛X的高級語言特性在一開始全部都拋棄,留一個最小核,然后通過營銷,大規模地培養本語言陣營的程序員,建立各種各樣的“輪子”,成就了巨無霸的生態;在站穩格局之后,慢慢地再逐步添加回來一些以前拋棄的其他語言的優秀特性——這是一種比較實用的策略,但是帶來的惡果就是:歷史包袱比較重,導致新特性很多時候是“半殘”的。
回到運算符重載本身,對于高手,可以利用該特性寫出極具“魔性”、接近數學語言的代碼,這樣的代碼可以體現“極簡之美”——但是,一個不利影響就是:數學不好的小伙伴,不容易看得懂,也很難體會其中蘊含的“數學之美”。