Java的面向?qū)ο缶幊?/a>">Java的面向?qū)ο缶幊?/a>
1028
2022-05-30
【引言】
Java虛擬機(JVM)使計算機能夠運行Java程序以及其他語言編寫的程序,這些程序被編譯成Java字節(jié)碼。
JVM由一個規(guī)范來描述JVM實現(xiàn)中所需要的內(nèi)容。
JVM參考實現(xiàn)是由OpenJDK項目以開放源碼的形式開發(fā)的,包括一個名為HotSpot的JIT編譯器。Oracle公司提供的商業(yè)上支持的Java版本都是基于OpenJDK運行庫。Eclipse OpenJ9是OpenJDK項目的另一個開源的JVM。
Groovy有一個替代性的實現(xiàn):
l??Grooscript將Groovy代碼轉(zhuǎn)換為JavaScript代碼。?雖然Grooscript與Apache Groovy相比有一些局限,但它可以在服務器和客戶端使用領(lǐng)域類。它還提供了Grails 3.0版本的插件支持,以及在線代碼轉(zhuǎn)換。
【Scala】
Scala是一種通用編程語言,它提供了對功能型編程的支持和強大的靜態(tài)類型系統(tǒng)。Scala的設(shè)計是簡潔的,它的許多設(shè)計旨在解決針對Java的一些批評。
Scala的源碼最終被編譯成Java字節(jié)碼,產(chǎn)生的可執(zhí)行代碼就可以在Java虛擬機上運行。
Scala提供了與Java的語言互操作性,用兩種語言編寫的程序庫可以相互在Scala或Java的代碼中調(diào)用。
與Java一樣,Scala也是面向?qū)ο蟮模彩鞘褂靡环N大括號來標識程序內(nèi)部的Scope,這是C家族編程語言的特點。
與Java不同的是,Scala具有Scheme、標準ML和Haskell等功能型編程語言的許多特性,包括卷積、不變性、惰性評估和模式匹配。
它還有一個高級類型系統(tǒng),支持代數(shù)數(shù)據(jù)類型、協(xié)方差和反方差、高階類型(Higher-Order)和匿名類型。
Scala的其他特征如運算符重載、可選參數(shù)、命名參數(shù)和原始字符串, 是Java中沒有的。
相反,Scala中沒有異常檢查,這在Java中是存在的,這一點是非常有爭議的。
Scala這個名字是可擴展性和語言的合成,寓意是它的設(shè)計會隨著用戶的需求而改進。
步驟:https://docs.scala-lang.org/getting-started/index.html
//?Java:
int?mathFunction(int?num)?{
int?numSquare?=?num*num;
return?(int)?(Math.cbrt(numSquare)?+
Math.log(numSquare));
}
//?Scala:?從Java直接轉(zhuǎn)換
//?不需要導入;?scala.math
//?已經(jīng)作為?"math?"導入
def?mathFunction(num:?Int):?Int?=?{
var?numSquare:?Int?=?num*num
return?(math.cbrt(numSquare)?+?math.log(numSquare)).
asInstanceOf[Int]
}
另一種寫法:
//?Scala:
//?使用類型推理,省略了?"return?"語句。
//?使用?"toInt"方法,聲明numSquare是不可變的。
import?math._
def?mathFunction(num:?Int)?=?{
val?numSquare?=?num*num
(cbrt(numSquare)?+?log(numSquare)).toInt
}
一些語法差異:
l??Scala不需要用分號來結(jié)束語句。
l??值類型用大寫:Int, Double, Boolean,而不是int, double, boolean。
l??參數(shù)和返回的類型跟在Pascal一樣在后面,而不是像C語言那樣在前面。
l??方法前必須加上def。
l??本地變量或類變量必須在前面加上val表示不可變的變量或var表示可變的變量。
l??返回操作符在函數(shù)中是不必要的,雖然允許返回;最后執(zhí)行的語句或表達式的值通常是函數(shù)的值。
l??Scala用foo.asInstanceOf[Type]代替Java的?cast?運算符(Type)foo,或者專門的函數(shù),如toDouble或toInt。
l??Java使用import foo.*;,Scala使用的是import foo._。
l??函數(shù)或方法foo()也可以作為單純的foo調(diào)用;方法?thread.send(signo)也可以作為單純的線程發(fā)送signo調(diào)用;方法foo.toString()也可以作為單純的foo toString調(diào)用。
這些不同是為了特定領(lǐng)域語言的支持。
其他一些基本的語法差異:
l??數(shù)組引用被寫成函數(shù)調(diào)用,例如:?array(i)?而不是?array[i]。(在Scala內(nèi)部,前者會擴展成array.apply(i),返回引用)
l??通用類型被寫成例如List[String]而不是Java的List
l??Scala用的是實際的單實例類Unit,而不是偽類型void。
下面的例子Java和Scala中的類的定義的對比。
//?Java:
public?class?Point?{
private?final?double?x,?y;
public?Point(final?double?x,?final?double?y)?{
this.x?=?x;
this.y?=?y;
}
public?Point(
final?double?x,?final?double?y,
final?boolean?addToGrid
)?{
this(x,?y);
if?(addToGrid)
grid.add(this);
}
public?Point()?{
this(0.0,?0.0);
}
public?double?getX()?{
return?x;
}
public?double?getY()?{
return?y;
}
double?distanceToPoint(final?Point?other)?{
return?distanceBetweenPoints(x,?y,
other.x,?other.y);
}
private?static?Grid?grid?=?new?Grid();
static?double?distanceBetweenPoints(
final?double?x1,?final?double?y1,
final?double?x2,?final?double?y2
)?{
return?Math.hypot(x1?-?x2,?y1?-?y2);
}
}
//?Scala
class?Point(
val?x:?Double,?val?y:?Double,
addToGrid:?Boolean?=?false
)?{
import?Point._
if?(addToGrid)
grid.add(this)
def?this()?=?this(0.0,?0.0)
def?distanceToPoint(other:?Point)?=
distanceBetweenPoints(x,?y,?other.x,?other.y)
}
object?Point?{
private?val?grid?=?new?Grid()
def?distanceBetweenPoints(x1:?Double,?y1:?Double,
x2:?Double,?y2:?Double)?=?{
math.hypot(x1?-?x2,?y1?-?y2)
}
}
上面的代碼顯示了Java和Scala在定義類的時候的一些概念上的差異:
l??Scala沒有靜態(tài)變量或方法。相反,它有單實例對象,本質(zhì)上是只有一個實例的類。單實例對象是用object而不是類來聲明的。通常把靜態(tài)變量和方法放在一個與類名相同的單實例對象中,這個單實例對象就被稱為同伴對象。
l??對應構(gòu)造函數(shù)參數(shù),Scala有類參數(shù),類似于函數(shù)的參數(shù)。當使用val或var修改器聲明時,也會定義為相同的名稱的成員變量,并從類參數(shù)中自動初始化。
l??Scala中的默認可見性是public。
如上所述,與Java相比,Scala在語法上有很大的靈活性。下面是一些靈活性的例子:
l??分號不是必須的;
l??任何方法都可以用作信息操作符。如:"%d蘋果".format(num)和"%d蘋果?"format num是等價的。
l??apply和update方法有語法短式。foo()=42?是foo.update(42)的語法短式。
l??Scala區(qū)分了no-parens(def foo = 42)和?empty-parens(def foo() = 42)方法。
l??以冒號(:)結(jié)尾的方法名稱參數(shù)在左側(cè),而接收方在右側(cè)。比如, 4 :: 2 :: Nil?跟?Nil.::(2).::(4)相同。
l??類成員變量可以作為獨立的getter和setter方法透明地實現(xiàn)。
l??允許在方法調(diào)用中使用大括號代替括號。
l??For-expressions可以容納任何定義單元方法的類型,如map、flatMap和filter等。
就其本身而言,這些例子有的看起來是有問題的,但總的來說,它們的目的是允許在Scala定義特定領(lǐng)域,而不需要擴展編譯器。例如,Erlang的特殊語法,即actor !?消息可以在Scala庫中實現(xiàn),而不需要語言擴展。
Java對原始類型(如int和boolean)和引用類型(任何類)進行了明確的區(qū)分。只有引用類型才允許繼承,基類是java.lang.Object。
在Scala中,所有的類型可以繼承,基類是Any,其直接的子類是AnyVal(值類型,類似Int和Boolean)和AnyRef(引用類型,類似Java中的引用類型)。
這意味著,在Scala中不存在像Java中對底層類型和盒裝類型(例如int vs. Integer)的區(qū)分;盒裝和解盒對用戶來說是完全透明的。Scala 2.10允許用戶定義新的值類型。
Scala?有?for-expressions,它類似于?Haskell?等語言中的?list comprehensions,或?Python?中的?list comprehensions?和?generator expressions?的組合,而不是?Java?的?"foreach "循環(huán),用于在迭代器中循環(huán)。使用?yield?關(guān)鍵字的?for-expressions?允許通過迭代一個現(xiàn)有的集合來生成一個新的集合,返回一個相同類型的新集合。
它們被編譯器翻譯成一系列的map、flatMap和filter調(diào)用。如果不使用?yield,代碼會翻譯成?foreach。
val?s?=?for?(x?<-?1?to?25?if?x*x?>?50)?yield?2*x
運行它的結(jié)果是:
Vector(16,?18,?20,?22,?24,?26,?28,?30,?32,?34,?36,?38,?40,?42,?44,?46,?48,?50)
一個比較復雜的例子是:
//?給出一個映射,指定一組推文中提到的Twitter用戶,
//和每個用戶被提及的次數(shù),查詢用戶的次數(shù)
///從已知政客的映射中返回一個新的民主黨政客的映射。
val?dem_mentions?=?for?{
(mention,?times)?<-?mentions
account??????????<-?accounts.get(mention)
if?account.party?==?"Democratic"
}?yield?(account,?times)
Expression ( mention, times) <- mentions是模式匹配的一個例子。迭代映射會返回一組鍵-值的圖元組,而模式匹配可以很容易地將這些圖元組分解成鍵和值的獨立變量。同樣的,理解的結(jié)果也會返回鍵-值tuple,因為源對象(來自于變量?mentions)是一個map,所以這些tuple會被自動構(gòu)建成map。需要注意的是,如果?mentions?是一個列表、集、數(shù)組或其他圖元組的集合,那么上面完全相同的代碼將產(chǎn)生一個相同類型的新集合。
在支持Java中所有的面向?qū)ο筇匦缘耐瑫r, Scala還以各種方式增強了這些特性,Scala還提供了大量通常只有在功能型編程語言中才有的功能。這些特性加在一起,使得Scala程序可以用幾乎完全的功能型風格來編寫,同時也允許功能型和面向?qū)ο蟮娘L格混合使用。
例如:
l??不區(qū)分語句和表達式
l??類型推理
l??具有捕捉語義的匿名函數(shù)(即閉合)。
l??不可變的變量和對象
l??惰性評價
l??有限的連續(xù)(自2.8以來)
l??高階函數(shù)
l??嵌套函數(shù)
l??Currying
l??模式匹配
l??代數(shù)數(shù)據(jù)類型(通過案例類)
l??Tuples
這方面與C語言或Java不同,與Lisp等語言卻類似,Scala不區(qū)分語句和表達式。
實際上,所有的語句都是求值的表達式。
在C語言或Java中被聲明為返回void的函數(shù),以及像while這樣邏輯上不返回值的語句,在Scala中被認為是返回Unit類型,這是一個單實例類型,只有一個對象。
從邏輯上來說,從來沒有返回的函數(shù)和操作符(例如throw操作符或使用異常退出的函數(shù)),邏輯上都有返回類型Nothing,這是一個不包含任何對象的特殊類型;也就是說,有這么一個底層類型,即每個可能類型的子類。(這反過來又使Nothing類型與每一個類型兼容,使類型推理能夠正常運行。)
同樣的,一個if-then-else"語句?"實際上是一個表達式,它產(chǎn)生一個值,也就是評估選取兩個分支中的一個的結(jié)果。
這意味著,這樣的代碼塊可以插入到任何需要表達式的地方,省去客三元運算符。
//?Java:
int?hexDigit?=?x?>=?10???x?+?'A'?-?10?:?x?+?'0';
//?Scala:
val?hexDigit?=?if?(x?>=?10)?x?+?'A'?-?10?else?x?+?'0'
出于類似的原因,在Scala中,返回語句不是必須的,事實上也不鼓勵使用。就像Lisp中一樣,代碼塊中的最后一個表達式就是該代碼塊的值,如果該代碼塊是函數(shù)的主體,那么它將被函數(shù)返回。
為了說明所有的函數(shù)都是表達式,即使是返回Unit的方法也要寫成等號:
def?printValue(x:?String):?Unit?=?{
println("I?ate?a?%s".format(x))
}
或等同于(用類型推理,省略了不必要的括號):
def?printValue(x:?String)?=?println("I?ate?a?%s"?format?x)
由于類型推理的關(guān)系,通常可以省略變量的類型、函數(shù)的返回值和許多其他表達式,因為編譯器可以推導出來。
例如val x = "foo"(對于一個不可變的常量或不可變的對象)或var x = 1.5(對于一個以后可以改變值的變量)。
Scala?中的類型推理基本上是局部的,這與?Haskell、ML?和其他更純粹的功能語言中使用的?Hindley-Milner?算法不同。
這樣做是為了方便面向?qū)ο缶幊獭?/p>
其結(jié)果是,某些類型仍然需要聲明(最明顯的是,函數(shù)參數(shù)和遞歸函數(shù)的返回類型),如:
def?formatApples(x:?Int)?=?"I?ate?%d?apples".format(x)
或(為遞歸函數(shù)聲明的返回類型)
def?factorial(x:?Int):?Int?=
if?(x?==?0)
1
else
x*factorial(x?-?1)
在?Scala?中,函數(shù)就是對象,有一種方便的語法用于指定匿名函數(shù)。一個例子是表達式?x => x < 2,它指定了有一個參數(shù)的函數(shù),用來比較參數(shù)是否小于2。 它相當于Lisp中的(lambda (x) (< x 2))。
注意,無論是x的類型還是返回類型都不需要顯式指定,一般可以通過類型推理來推斷;但也可以顯式指定,例如:(x: Int) => x < 2或(x: Int) => (x < 2):Boolean。
匿名函數(shù)的行為就像真正的閉合一樣,它們自動捕獲任何在閉合函數(shù)里面可用的變量。這些變量即使在閉合函數(shù)返回后也是可用的,而且不像Java的匿名內(nèi)類那樣,不需要聲明為final變量。
如果這些變量是可突變的,甚至可以修改這些變量,而且在下次調(diào)用匿名函數(shù)時,修改后的值將是可用的。
還有一種更簡短的匿名函數(shù)的形式是使用占位符變量。例如,如下所示:
list?map?{?x?=>?sqrt(x)?}
可以簡明扼要地寫成:
list?map?{?sqrt(_)?}
或者:
list?map?sqrt
Scala對不可變和可變變量進行了區(qū)分。可變變量是用var關(guān)鍵字聲明的,而不可變值是用val關(guān)鍵字聲明的。使用val關(guān)鍵字聲明的變量不能被重新賦值,就像在Java中使用final關(guān)鍵字聲明的變量不能被重新賦值一樣。但是,需要注意的是,val只具有淺層次的不可變性,也就是說,被val引用的對象本身并不保證是不可變的。
然而,不可變類是被鼓勵的,Scala標準庫提供了豐富的不可變集合類。Scala提供了大多數(shù)集合類的mutable和immutable變體,除非mutable版本被顯式地導入,否則總是使用immutable版本。
immutable變體是一種持久化的數(shù)據(jù)結(jié)構(gòu),它總是返回一個舊對象的更新副本,而不是在原地破壞性地更新舊對象。一個例子是不可變的鏈接式列表,在這個例子中,將一個元素預加到列表中,通過返回一個新的列表節(jié)點,該節(jié)點由該元素和對列表尾部的引用組成。將一個元素添加到列表中,只能通過將舊列表中的所有元素預置到一個新的列表中,并只將新元素預置到新的列表中來完成。
同樣的,在列表中間插入一個元素,會復制列表的前半部分,但保留對列表后半部分的引用。這就是所謂的結(jié)構(gòu)共享。
這種機制是為了更好的支持并發(fā)。因為沒有共享對象。
默認情況下,評估是嚴格的。換句話說,Scala會在表達式可用的時候就對其進行評估,而不是根據(jù)需要進行評估。但是,可以用lazy關(guān)鍵字聲明一個變量的惰性屬性,這意味著在變量第一次被引用之前,產(chǎn)生該變量值的代碼不會被評估。
各種類型的惰性集合也是存在的(如類型為Stream的惰性的鏈接列表),任何集合都可以用view方法使之成為惰性的。惰性的集合提供了一個很好的語義契合度,比如服務器生成的數(shù)據(jù),只有在實際需要元素的時候,才會對代碼進行評估,以生成后面的列表元素。
功能型編程語言通常提供Trail調(diào)用優(yōu)化,以便在不出現(xiàn)堆棧溢出問題的情況下廣泛使用遞歸。
Java字節(jié)碼中的限制使JVM上的Trail調(diào)用優(yōu)化變得復雜化。一般來說,Trail調(diào)用自己的函數(shù)可以進行優(yōu)化,但相互遞歸的函數(shù)不能進行優(yōu)化。
Trampolines被認為是一種解決方法。自Scala 2.8.0(2010年7月14日發(fā)布)以來,Scala庫中的對象scala.utilator.control.TailCalls就提供了對Trampolines的支持。一個函數(shù)可以選擇用?@tailrec?注解,在這種情況下,除非它是Trail遞歸,否則不會編譯。
Scala內(nèi)置了對模式匹配的支持,它可以被認為是一個更復雜的、可擴展的switch語句的版本,可以匹配任意的數(shù)據(jù)類型(而不僅僅是整數(shù)、布爾和字符串等簡單類型),包括任意嵌套。
還提供了一種被稱為case類的特殊類型的類,它自動支持模式匹配,可以用來模擬許多函數(shù)式編程語言中使用的代數(shù)數(shù)據(jù)類型。
從Scala的角度來看,case類只是一個普通的類,編譯器會自動為其添加某些行為,而這些行為也可以手動提供,例如,定義提供深度比較和散列的方法,以及在模式匹配過程中根據(jù)構(gòu)造函數(shù)參數(shù)對case類進行解構(gòu)。
一個使用模式匹配的quicksort算法的例子:
def?qsort(list:?List[Int]):?List[Int]?=?list?match?{
case?Nil?=>?Nil
case?pivot?::?tail?=>
val?(smaller,?rest)?=?tail.partition(_?
qsort(smaller)?:::?pivot?::?qsort(rest)
}
在上面的模式匹配示例中,匹配操作符的主體是一個部分函數(shù),它由一系列的case表達式組成,以第一個匹配表達式為準,類似于開關(guān)語句的主體。
部分函數(shù)也用于try語句中的異常處理部分:
try?{
...
}?catch?{
case?nfe:NumberFormatException?=>?{?println(nfe);?List(0)?}
case?_?=>?Nil
}
部分函數(shù)可以單獨使用,對它的調(diào)用相當于在它上面做了一個匹配。
前面的quicksort代碼可以這樣寫:
val?qsort:?List[Int]?=>?List[Int]?=?{
case?Nil?=>?Nil
case?pivot?::?tail?=>
val?(smaller,?rest)?=?tail.partition(_?
qsort(smaller)?:::?pivot?::?qsort(rest)
}
Scala是一門純粹的面向?qū)ο蟮恼Z言,每個值都是一個對象。對象的數(shù)據(jù)類型和行為由類和特征來描述。類的抽象可以通過子類和靈活的基于mixin的組合機制來進行擴展,從而避免了多重繼承的問題。
Traits是Scala對Java的接口的替代。Java 8以下版本中的接口是高度限制的,只能包含抽象函數(shù)聲明。
Traits類似于mixin類,除了缺少類參數(shù),它擁有普通抽象類的所有能力。
super運算符在traits中表現(xiàn)得特別好,除了繼承之外,還可以使用組合方式進行連鎖。下面的例子是一個簡單的窗口系統(tǒng):
//?抽象
def?draw()
}
class?SimpleWindow?extends?Window?{
def?draw()?{
println("in?SimpleWindow")
//?畫一個簡單的窗口
}
}
trait?WindowDecoration?extends?Window?{}
trait?HorizontalScrollbarDecoration?extends?WindowDecoration?{
//?為了讓?"super()"起作用,需要?"抽象重寫",因為父節(jié)點的
//?函數(shù)是抽象的,此處常規(guī)的?"覆蓋"就夠了。
abstract?override?def?draw()?{
println("in?HorizontalScrollbarDecoration")
super.draw()
//?現(xiàn)在畫一個水平滾動條
}
}
trait?VerticalScrollbarDecoration?extends?WindowDecoration?{
abstract?override?def?draw()?{
println("in?VerticalScrollbarDecoration")
super.draw()
//?現(xiàn)在畫一個垂直滾動條
}
}
trait?TitleDecoration?extends?WindowDecoration?{
abstract?override?def?draw()?{
println("in?TitleDecoration")
super.draw()
//?現(xiàn)在畫出標題欄
}
}
一個變量可以這樣聲明:
val?mywin?=?new?SimpleWindow?with?VerticalScrollbarDecoration?with?HorizontalScrollbarDecoration?with?TitleDecoration
調(diào)用mywin.draw()的結(jié)果是:
in?TitleDecoration
in?HorizontalScrollbarDecoration
in?VerticalScrollbarDecoration
in?SimpleWindow
Scala配備了一個富有表現(xiàn)力的靜態(tài)類型系統(tǒng),該系統(tǒng)主要是強制要求安全、連貫地使用抽象。該類型系統(tǒng)支持不健全:
l??類和抽象類型作為對象成員
l??結(jié)構(gòu)類型
l??路徑依賴型
l??復合物類型
l??明確類型的自我引用
l??通用類
l??多態(tài)性方法
l??上限和下限
l??差異
l??注解
l??視圖
Scala能夠通過使用情況來推斷類型。這使得大多數(shù)靜態(tài)類型聲明是可選的。靜態(tài)類型不需要顯式聲明,除非編譯器報錯指明有此需要。
在實踐中,為了代碼的清晰,一些靜態(tài)類型是顯式聲明的。
Scala中的一種常見技術(shù),被稱為?"增強我的程序庫"。這類似于C#中的擴展方法概念,但更加強大,因為該技術(shù)不限于添加方法,它可以用來實現(xiàn)新的接口。
在Scala中,這種技術(shù)涉及到從"接收"方法的類型聲明一個隱式轉(zhuǎn)換,將方法從類型"接收"到一個新的類型(通常是類),這個新的類型封裝了原來的類型并提供了額外的方法。如果在給定類型中找不到方法,編譯器會自動搜索任何適用的隱式轉(zhuǎn)換類型,將其轉(zhuǎn)換為提供該方法的類型。
這種技術(shù)允許使用附加庫將新的方法添加到現(xiàn)有的類中,這樣只有導入附加庫的代碼才能獲得新的功能,其他的代碼不受影響。
下面的例子展示了用方法isEven和isOdd來增強類型Int:
object?MyExtensions?{
implicit?class?IntPredicates(i:?Int)?{
def?isEven?=?i?%?2?==?0
def?isOdd??=?!isEven
}
}
import?MyExtensions._??//?將隱含的豐富性納入范圍
4.isEven??//?->?true
導入MyExtensions的成員,將隱式轉(zhuǎn)換為擴展類IntPredicates的成員導入。
Scala的標準庫除了標準的Java并發(fā)API外,還包括了對actor模型的支持。Lightbend Inc.提供了一個平臺,其中包括Akka,一個單獨的開源框架,它提供了基于actor的并發(fā)。
Akka行為體可以是分布式的,也可以與軟件事務性內(nèi)存(transactors)相結(jié)合。基于通道的消息傳遞的替代通信順序進程(CSP)實現(xiàn)是Communicating Scala對象,或者干脆通過JCSP。
Actor就像一個帶郵箱的線程實例。它可以通過?system.actorOf?創(chuàng)建,覆蓋接收方法來接收消息,并使用?! (感嘆號)方法來發(fā)送消息。下面的例子顯示了一個EchoServer接收消息,然后打印消息:
val?echoServer?=?actor(new?Act?{
become?{
case?msg?=>?println("echo?"?+?msg)
}
})
echoServer?!?"hi"
Scala還內(nèi)置了對數(shù)據(jù)并行編程的支持,其形式為Parallel Collections,從2.9.0版本開始就集成到標準庫中。
下面的例子顯示了如何使用Parallel Collections來提高性能:
val?urls?=?List("https://scala-lang.org",??"https://github.com/scala/scala")
def?fromURL(url:?String)?=?scala.io.Source.fromURL(url)
.getLines().mkString("\n")
val?t?=?System.currentTimeMillis()
urls.par.map(fromURL(_))?//?par返回一個集合的并行實現(xiàn)
println("time:?"?+?(System.currentTimeMillis?-?t)?+?"ms")
除了actor支持和數(shù)據(jù)并行性之外,Scala還支持Futures和Promises、軟件事務性內(nèi)存和事件流等異步編程。
用Scala編寫的最著名的開源集群計算解決方案是Apache Spark。此外, 與Spark和其他流處理技術(shù)齊名的發(fā)布-訂閱消息隊列Apache Kafka也是用Scala編寫的。
在Scala中測試代碼有幾種方法:
ScalaTest支持多種測試風格,并可以與基于Java的測試框架集成。
ScalaCheck是一個類似于Haskell的QuickCheck.specs2的庫,是一個用于編寫可執(zhí)行軟件規(guī)范的庫。
ScalaMock提供了對高階函數(shù)和卷積函數(shù)測試的支持。
JUnit和TestNG是用Java編寫的流行測試框架。
Scala經(jīng)常與Groovy和Clojure進行比較。它們的本質(zhì)區(qū)別在于類型系統(tǒng)、對面向?qū)ο蠛凸δ苄途幊痰闹С殖潭龋约芭cJava的語法相似性。
Scala是靜態(tài)類型化的,而Groovy和Clojure都是動態(tài)類型化的。
這使得Scala類型系統(tǒng)更復雜,更難理解,但卻能在編譯時捕獲幾乎所有類型錯誤,并能使執(zhí)行速度大大加快。
相比之下,動態(tài)類型化則需要更多的測試來確保程序的正確性,一般來說速度較慢,但程序的靈活性和簡單性更強。
關(guān)于速度差異,Groovy和Clojure的當前版本允許可選的類型注解,在靜態(tài)類型的情況下可以避免動態(tài)引入的開銷。
當使用最新版本的JVM時,這種動態(tài)開銷會進一步降低,因為JVM已經(jīng)為使用動態(tài)類型化參數(shù)定義的方法增強了一條調(diào)用動態(tài)指令。
這些進步縮小了靜態(tài)類型化和動態(tài)類型化之間的速度差距。
盡管如此,當執(zhí)行效率非常重要時,像Scala這樣的靜態(tài)類型化語言仍然是首選。
關(guān)于編程范式,Scala繼承了Java的面向?qū)ο竽P停⒁愿鞣N方式進行了擴展。
Groovy雖然也是強烈的面向?qū)ο螅⒅赜跍p少動詞性。
在Clojure中,面向?qū)ο蟮木幊淌遣粡娬{(diào)的,功能型編程是該語言的主要優(yōu)勢。
Scala也有很多功能型編程機制,包括像Haskell等高級功能型語言中的功能,讓開發(fā)者在這兩種范式之間進行選擇,或者更多的時候,是兩者的一些組合。
關(guān)于與Java的語法相似性,Scala繼承了很多Java的語法,這跟Groovy一樣。
另一方面,Clojure沿用了Lisp語法,在外觀和理念上是不同的。
由于Scala的許多高級特性,學習Scala是比較困難的。
Groovy則不然,盡管它也是一門功能豐富的語言,但它主要被設(shè)計成腳本語言。
l??2009年4月,Twitter宣布將其后端的大部分內(nèi)容從Ruby轉(zhuǎn)為Scala,并打算將來把其余部分也轉(zhuǎn)換。
l??Gilt?使用?Scala?和?Play Framework。
l??Foursquare使用Scala和Lift。
l??Coursera使用Scala和Play框架[105]。
l??蘋果公司在某些團隊中使用Scala以及Java和Play框架。
l??《衛(wèi)報》的高流量網(wǎng)站guardian.co.uk在2011年4月宣布從Java轉(zhuǎn)向Scala。
l??《紐約時報》在2014年透露,其內(nèi)部內(nèi)容管理系統(tǒng)Blackbeard使用Scala、Akka和Play構(gòu)建的內(nèi)部內(nèi)容管理系統(tǒng)。
l??2013年,《赫芬頓郵報》開始采用Scala作為其內(nèi)容分發(fā)系統(tǒng)Athena的一部分。
l??瑞士銀行UBS批準了Scala用于一般的生產(chǎn)用途。
l??LinkedIn使用Scalatra微框架為其Signal API提供動力。
l??Meetup使用Unfiltered工具包進行實時API。
l??Remember the Milk使用了Unfiltered toolkit、Scala和Akka的公共API和實時更新。
l??Verizon尋求用Scala做一個"下一代框架"。
l??Airbnb開發(fā)的開源機器學習軟件"Aerosolve",用Java和Scala編寫。
l??Zalando將其技術(shù)棧從Java轉(zhuǎn)移到Scala和Play。
l??SoundCloud的后端使用Scala,采用了Finagle(微服務)、Scalding和Spark(數(shù)據(jù)處理)等技術(shù)。
l??Databricks在Apache Spark大數(shù)據(jù)平臺上使用Scala。
l??Morgan Stanley在其金融和資產(chǎn)相關(guān)項目中廣泛使用Scala。
l??Google/Alphabet公司內(nèi)部有一些團隊使用Scala,主要是由于收購了Firebase和Nest等公司。
l??沃爾瑪加拿大公司的后端平臺使用Scala。
l??Duolingo使用Scala作為生成課程的后端模塊。
l??HMRC在許多英國政府稅務申請中使用Scala。
【Kotlin】
Kotlin是一種跨平臺、靜態(tài)類型化的通用編程語言,它具有類型推理功能。
Kotlin是為了與Java完全互操作而設(shè)計的,其標準庫的JVM版本依賴于Java類庫,但類型推理使其語法更加簡潔。
Kotlin主要針對JVM,也可以編譯成JavaScript或本地代碼(通過LLVM)。語言開發(fā)費用由JetBrains承擔,Kotlin基金會保護Kotlin商標。
2019年5月7日,谷歌宣布Kotlin編程語言現(xiàn)在是其Android應用開發(fā)者的首選語言,自2017年10月Android Studio 3.0發(fā)布以來,Kotlin已經(jīng)被納入了標準Java編譯器的替代方案。Android Kotlin編譯器默認以Java 6為目標,但允許程序員在Java 8到13之間進行選擇,從而進行優(yōu)化。
【Java】
Java是一種通用的編程語言,它是面向?qū)ο蟮耐ㄓ镁幊陶Z言,其設(shè)計目的是盡可能減少對實現(xiàn)的依賴。
它的目的是讓應用程序開發(fā)人員可以一次編譯到處運行。
這意味著編譯后的Java代碼可以在所有支持Java的平臺上運行,而不需要重新編譯。
Java的語法類似于C和C++,但它的底層比這兩個都少。
截至2019年,根據(jù)GitHub的數(shù)據(jù),Java是最受歡迎的編程語言之一,特別是對于客戶端-服務器Web應用來說,更為流行,據(jù)說有900萬開發(fā)者。
Java最初由James Gosling在Sun Microsystems公司(后被Oracle收購)開發(fā),1995年作為Sun Microsystems公司Java平臺的核心組件發(fā)布。
原始的和參考實現(xiàn)的Java編譯器、虛擬機和類庫最初是由Sun公司以專有許可證發(fā)布。
截至2007年5月,為了遵守Java社區(qū)進程的規(guī)范,Sun已經(jīng)將其大部分的Java技術(shù)以GNU通用公共許可證的形式重新授權(quán)給。
同時,其他一些人也開發(fā)了這些Sun技術(shù)的替代實現(xiàn),如GNU Java編譯器(字節(jié)碼編譯器)、GNU Classpath(標準庫)和IcedTea-Web(用于小程序的瀏覽器插件)。
最新的版本是2020年3月發(fā)布的Java 14。2018年9月25日發(fā)布的Java 11是目前的長期支持(LTS)版本;Oracle在2019年1月為遺留的Java 8 LTS發(fā)布了最后一次支持供商業(yè)使用的免費的公共更新。
Oracle(和其他公司)強烈建議用戶卸載舊版本的Java,因為未解決的安全問題會帶來嚴重的風險,Oracle建議用戶立即過渡到最新版本(目前為Java 14)或LTS版本。
【語言排名】
在2018年版的?"Java狀態(tài)?"調(diào)查中,收集了5160名開發(fā)者關(guān)于各種Java相關(guān)話題的數(shù)據(jù),在JVM上替代語言的使用量方面,Scala排在第三位。與上一年度的調(diào)查相比,Scala在替代JVM語言中的使用率下降了近四分之一(從28.4%降至21.5%),被Kotlin超越,從2017年的11.4%上升至2018年的28.8%,而在2018年,Scala的使用率則被Kotlin超越。
【字節(jié)碼驗證器】
Java的一個基本理念是安全性,即任何用戶程序都不能使主機崩潰或以其他不當方式干擾主機上的其他操作,并且可以保護受信任代碼的某些方法和數(shù)據(jù)結(jié)構(gòu)不被同一JVM內(nèi)執(zhí)行的非信任代碼訪問或損壞。
此外,經(jīng)常導致數(shù)據(jù)損壞或不可預測行為的常見程序員錯誤也會避免,比如訪問數(shù)組越界或使用未初始化指針等。
Java的這些特性(類模型、垃圾收集堆和驗證器)結(jié)合起來提供了這種安全性。
JVM在執(zhí)行字節(jié)碼之前,會對字節(jié)碼進行驗證。這種驗證主要包括三種類型的檢查:
1.??分支總是在有效的位置
2.??數(shù)據(jù)總是被初始化,引用總是類型安全的。
3.??嚴格控制對私人數(shù)據(jù)和方法或打包的私人數(shù)據(jù)和方法的訪問權(quán)限。
其中前兩個檢查主要是在加載類和驗證步驟中進行,以確保其有資格被使用。第三種主要是動態(tài)地執(zhí)行,當一個類的數(shù)據(jù)項或方法首次被另一個類訪問時進行。
驗證器只允許有效程序中的某些字節(jié)碼序列,如跳轉(zhuǎn)(分支)指令只能針對同一方法內(nèi)的一條指令。
此外,驗證器確保任何給定的指令都在固定的堆棧位置上操作,允許JIT編譯器將堆棧訪問轉(zhuǎn)化為固定的寄存器訪問。
正因為如此,當使用JIT編譯器時,JVM作為一種堆棧架構(gòu)并不意味著速度上的性能會像通常基于寄存器的架構(gòu)那樣喪失。
對于JIT編譯器來說,在代碼驗證的JVM架構(gòu)面前,無論是獲得命名的虛寄存器還是必須分配給目標架構(gòu)的寄存器的虛棧位置,都沒有什么區(qū)別。
事實上,代碼驗證使得JVM不同于經(jīng)典的堆棧架構(gòu),其中用JIT編譯器進行高效仿真的JVM更加復雜,通常由一個較慢的解釋器來完成。
最初的字節(jié)碼校驗器規(guī)范使用的自然語言在某些方面是不完整或不正確的。人們進行了許多嘗試,將JVM指定為一個正式系統(tǒng)。通過這樣做,可以更徹底地分析當前JVM實現(xiàn)的安全性,并防止?jié)撛诘陌踩┒础M瑫r,如果正在運行的應用程序被證明是安全的,也可以跳過不必要的安全檢查來優(yōu)化JVM。
【遠程代碼的安全執(zhí)行】
虛擬機架構(gòu)允許對機器內(nèi)的代碼采取的操作進行非常精細的控制。它假定代碼在?"語義上"是正確的,也就是說,它成功地通過了正式的字節(jié)碼驗證器過程,這個過程由一個工具具體化,這個工具可能是在虛擬機之外的虛擬機。
這樣做的目的是為了安全地執(zhí)行來自遠程的非信任代碼,這是Java小程序和其他安全代碼下載所使用的模式。一旦經(jīng)過字節(jié)碼驗證后,下載的代碼就會在一個受限的?"沙箱"中運行,其目的是為了保護用戶不受錯誤行為或惡意代碼的影響。
作為對字節(jié)碼驗證過程的補充,發(fā)布者可以購買一個證書,用它來對小程序進行數(shù)字簽名作為安全證書,允許他們要求用戶跳出沙盒,進入本地文件系統(tǒng)、剪貼板、執(zhí)行外部軟件或使用網(wǎng)絡。
正式的字節(jié)碼驗證器的證明已經(jīng)由Javacard業(yè)內(nèi)人士完成(Java卡字節(jié)碼嵌入式驗證器的正式開發(fā))。
【字節(jié)碼解釋器和適時編譯器】
對于每個硬件架構(gòu),都需要不同的Java字節(jié)碼解釋器。當計算機擁有Java字節(jié)碼解釋器時,它可以運行任何Java字節(jié)碼程序,同樣的程序可以在任何擁有這樣解釋器的計算機上運行。
當Java字節(jié)碼被解釋器執(zhí)行時,其執(zhí)行速度總是會比編譯成原生機器語言的相同程序的執(zhí)行速度慢。
這個問題可以通過JIT(just-in-time(JIT)編譯器)執(zhí)行Java字節(jié)碼來緩解。JIT編譯器可以在執(zhí)行程序時將Java字節(jié)碼翻譯成本地機器語言。這樣,程序的翻譯部分就可以比它們被解釋的速度快得多。這種技術(shù)被應用于程序中那些經(jīng)常被執(zhí)行的部分。這樣,JIT編譯器就可以大大加快整個程序的執(zhí)行時間。
Java編程語言和Java字節(jié)碼之間沒有必然的聯(lián)系。用Java編寫的程序可以直接編譯到計算機機器語言中,用Java以外的其他語言編寫的程序也可以編譯成Java字節(jié)碼。
Java字節(jié)碼的目的是獨立于平臺,并且是安全的。有些JVM實現(xiàn)不包括解釋器,而是只包含一個即時編譯器。
【W(wǎng)eb瀏覽器中的JVM】
在Java平臺誕生之初,JVM作為一種可以創(chuàng)建豐富互聯(lián)網(wǎng)應用的Web技術(shù)進行銷售。
截止到2018年,大多數(shù)Web瀏覽器和操作系統(tǒng)都沒有附帶Java插件,也不允許加載任何非Flash插件。在JDK 9中,Java瀏覽器插件被棄用。
NPAPI Java瀏覽器插件的設(shè)計是為了讓JVM執(zhí)行所謂的Java小程序嵌入到HTML頁面中。對于安裝了該插件的瀏覽器,小程序可以在頁面上分配給它的矩形區(qū)域內(nèi)進行繪制工作。
因為插件包含了JVM,所以Java小程序不限于Java編程語言;任何支持JVM的語言都可以在插件中運行。
盡管小程序不能在其矩形區(qū)域外修改內(nèi)容,它提供一組需要用戶授權(quán)的API允許小程序訪問用戶的麥克風或使用3D加速功能。作為其主要競爭技術(shù)的Adobe Flash Player在這方面的工作方式與此相同。
根據(jù)W3Techs的數(shù)據(jù),截至2015年6月,Java applet和Silverlight在所有網(wǎng)站中的使用量已經(jīng)下降到了0.1%,而Flash的使用量則下降到了10.8%。
【JavaScript JVMs和解釋器】
截至2016年5月,JavaPoly允許用戶導入未修改的Java庫,并直接從JavaScript中調(diào)用它們。即使用戶的計算機上沒有安裝Java,?JavaPoly允許網(wǎng)站使用未修改的Java庫。
【編譯成JavaScript】
隨著JavaScript執(zhí)行的速度不斷提高,再加上移動設(shè)備的使用越來越多,而這些設(shè)備的瀏覽器不支持插件的使用。
因此,人們正努力將源碼或JVM字節(jié)碼編譯成JavaScript來服務這些用戶。
編譯JVM字節(jié)碼,這在整個JVM的編程語言中是通用的。 主要的JVM字節(jié)碼到JavaScript編譯器有TeaVM,?Dragome Web SDK中的編譯器,Bck2Brwsr和j2js-compiler。
從JVM編程語言到JavaScript的主要編譯器包括Google Web Toolkit中包含的Java-to-JavaScript編譯器、Clojurescript(Clojure)、GoroScript(Apache Groovy)、Scala.js(Scala)等。
【Java運行時環(huán)境】
Oracle發(fā)布的Java運行時環(huán)境(JRE)是一個免費的軟件發(fā)行版,包含一個獨立的JVM (HotSpot)、Java標準庫(Java類庫)、一個配置工具,以及直到JDK 9中停止使用的瀏覽器插件。
JRE是個人電腦上最常見的Java環(huán)境,安裝在筆記本電腦和臺式機上。包括功能型手機和早期智能手機在內(nèi)的移動電話很可能包含一個JVM,用于運行Java平臺的微型版應用程序。
大多數(shù)現(xiàn)代的智能手機、平板電腦和其他手持式PC運行Java應用的時候,很可能是通過支持Android操作系統(tǒng)來實現(xiàn)的,而Android操作系統(tǒng)包括一個與JVM規(guī)范不兼容的開源虛擬機。Google的Android開發(fā)工具將Java程序作為輸入,并輸出Dalvik字節(jié)碼,這是Android設(shè)備上的虛擬機的原生輸入格式。
【性能】
JVM規(guī)范在實現(xiàn)細節(jié)上給了實現(xiàn)者很大的回旋余地。從Java 1.3開始,Oracle的JRE包含了一個名為HotSpot的JVM。它被設(shè)計成了一個高性能的JVM。
為了加快代碼的執(zhí)行速度,HotSpot依賴于just-in-time編譯。為了加速對象分配和垃圾回收,HotSpot使用了代間堆。
【代間堆】
Java虛擬機堆是JVM用于動態(tài)內(nèi)存分配的內(nèi)存區(qū)域。
在HotSpot中,堆被分為幾代:
l??年輕一代堆存儲的是臨時對象,這些對象被創(chuàng)建后會立即進行垃圾回收。
l??持續(xù)時間較長的對象會被存儲到老一代堆中。這個內(nèi)存被細分為兩個幸存者空間,分別存儲在上一次和下一次垃圾收集后幸存下來的對象。
在Java 8之前,永久代(或permgen)用于類定義和類相關(guān)的元數(shù)據(jù)。它不屬于堆的一部分。從Java 8開始刪除了永久代。
最初沒有永久代,對象和類被存儲在同一個區(qū)域。由于類的卸載比對象的收集要少得多,因此將類結(jié)構(gòu)移動到一個特定的區(qū)域,可以顯著提高性能。
【安全性】
Oracle的JRE被安裝在大量的計算機上。 而使用過期版本的JRE的終端用戶很容易受到許多已知的攻擊。 這讓人們普遍認為Java是不安全的,自從Java 1.7之后,Oracle的JRE for Windows包含了自動更新功能。
在Java瀏覽器插件停用之前,任何網(wǎng)頁都有可能運行Java小程序,這就為惡意網(wǎng)站提供了一個容易訪問的攻擊面。2013年卡巴斯基實驗室報告稱,Java插件是計算機犯罪分子的首選方法。許多黑客把Java漏洞包部署到被黑網(wǎng)站中,鑒于此,2018年9月25日推出的Java 11中刪除了Java applet。
【小結(jié)】
JVM(Java虛擬機)是運行JVM應用程序的運行時引擎,它是JRE(Java Runtime Environment)的一部分。
本文從JVM規(guī)范,Web瀏覽器中得JVM和運行時環(huán)境幾個方面對JVM相關(guān)的知識進行了探索,重點關(guān)注了一下JVM生態(tài)環(huán)境下的7種編程語言(由于目前本文篇幅已經(jīng)很長,對于Kotlin和Java的詳細探究會在以后的文章中進行),希望對當前和以后的業(yè)務開發(fā)有所裨益。
歡迎討論。
架構(gòu)設(shè)計
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔相應法律責任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。