Java虛擬機生態技術及其7種編程語言探秘(上)
【引言】
Java虛擬機(JVM)使計算機能夠運行Java程序以及其他語言編寫的程序,這些程序被編譯成Java字節碼。
JVM由一個規范來描述JVM實現中所需要的內容。
JVM參考實現是由OpenJDK項目以開放源碼的形式開發的,包括一個名為HotSpot的JIT編譯器。Oracle公司提供的商業上支持的Java版本都是基于OpenJDK運行庫。Eclipse OpenJ9是OpenJDK項目的另一個開源的JVM。
【JVM規范】
Java虛擬機是一個抽象的虛擬計算機, 它需要遵循一些JVM規范。
其中所使用的垃圾收集算法和Java虛擬機指令的任何內部優化如翻譯成機器代碼都沒有被事先指定。
這樣做的主要原因是為了避免對實現者造成不必要的約束。任何Java的應用程序只能在Java虛擬機的內部運行,這個虛擬機必須遵守這些的規范。
從Java平臺標準版(J2SE)5.0開始,在Java社區進程下, JVM規范的修改作為JSR 924開發, 截止到2006年,為支持類文件格式(JSR 202)提出的修改作為JSR 924的維護版本進行。
發布的JVM規范藍皮書前言中說:
“我們的意圖是,此規范應該充分地描述Java虛擬機,以便使兼容的無塵室實現成為可能。Oracle提供了驗證Java虛擬機實現是否正確的測試。”
Oracle的一個JVM被命名為HotSpot,另一個是JRockit,是從BEA Systems繼承的。
無塵室的Java實現包括Kaffe、OpenJ9和Skelmir的CEE-J。
Oracle擁有Java商標,并該商標來認證實現套件是否完全符合Oracle的規范。
【類加載器】
JVM?字節代碼的組織單位之一是類。一個類加載器實現必須能夠識別和加載任何符合Java類文件格式的內容。除了必須要識別類文件之外,也可以自由得添加識別其他二進制形式的實現。
類加載器有三個基本任務,它們按照嚴格的順序執行:
1.??加載:找到并導入類型的二進制數據
2.??連接:執行核查、準備和可選的引用解析。
a)??驗證:確保輸入類型的正確性。
b)??準備工作:為類變量分配內存,并將內存初始化為默認值。
c)??解析:將類型中的符號引用轉換為直接引用。
3.??初始化:調用Java代碼,將類變量初始化為合適的起始值。
一般來說,類加載器有兩種類型:bootstrap類加載器和用戶定義的類加載器。
每個Java虛擬機實現都必須有一個bootstrap類加載器,能夠加載可信類。Java虛擬機規范并沒有規定類加載器應該如何定位類。
【虛擬機架構】
JVM操作的是原始數據(整數和浮點數)和引用。
JVM從根本上來說是一個32位的機器。
Long和double類型是64位的,支持原生的,但由于每個單元都是32位,所以在一個幀的本地變量或操作符堆棧中消耗了兩個存儲單元。
boolean是作為8位字節值來操作的,0代表false,1代表true(雖然自從Java虛擬機規范第二版澄清了這個問題之后,boolean就被視為一種類型,但是在編譯和執行的代碼中,除了方法簽名中的名字和boolean數組的類型外,boolean和字節之間沒有什么區別。
布爾數組的類型是boolean[],但每個元素使用8位,JVM沒有將boolean打包到位數組中的內置能力,所以除了類型不同外,它們的執行和行為與字節數組相同。在所有其他的使用中,布爾類型實際上對JVM來說是未知的,因為所有對布爾類型進行操作的指令也都是用來對字節進行操作的。
JVM有一個垃圾收集堆,用于存儲對象和數組。代碼、常量和其他類數據都存儲在?"方法區"中。方法區在邏輯上是堆的一部分,但具體的實現可能會將方法區與堆分開處理,因為一般不會對“方法區”進行垃圾收集。
每個JVM線程也有自己的調用堆(為了清楚起見,稱為"Java虛擬機堆"),它存儲的是幀。每次調用一個方法時都會創建一個新的幀,當該方法退出時,該幀會被銷毀。
每個幀提供了一個"操作符棧"和一個"本地變量"的數組。操作符棧用于管理計算的操作符以及接收被調用方法的返回值,而局部變量的作用與寄存器相同,也用于傳遞方法參數。因此,JVM既是一個棧機,又是一個寄存器機。
【字節碼指令】
JVM有以下幾組任務的指令:
1.??加載和存儲
2.??算術
3.??類型轉換
4.??對象創建和操作
5.??操作數棧管理(push/pop)
6.??控制傳輸(分支)
7.??方法調用和返回
8.??拋出異常
9.??基于監控器的并發性
其目的是二進制的兼容性。每個特定的主機操作系統都需要有自己的JVM和運行時的實現。這些JVM在語義上對字節碼的解釋方式是一樣的,但具體實現可能不同。
比模擬字節碼更復雜的是,必須兼容并有效地實現必須映射到每個主機操作系統的Java核心API。
這些指令是操作的是一組通用的抽象數據類型,而不是任何特定指令集架構的本機數據類型。
【JVM編程語言】
JVM編程語言是指任何具有可以用Java虛擬機托管的有效類文件來表達功能的語言。類文件包含Java虛擬機指令(Java字節碼)和符號表,以及其他輔助信息。類文件格式是一種與硬件和操作系統無關的二進制格式,用于表示已編譯的類和接口。
有幾種JVM編程語言,既有移植到JVM的老語言,也有完全新的語言。
JRuby和Jython可能是現有語言中最著名的老語言移植,即Ruby和Python的移植。
在新語言中,Clojure、Apache Groovy、Scala和Kotlin是最受歡迎的語言。JVM語言的一個顯著特點是它們之間是相互兼容的,因此,Scala庫可以與Java程序一起使用,反之亦然。
Java 7 JVM在Java平臺上實現了JSR 292:支持動態類型化語言,這是一個在JVM中支持動態類型化語言的新功能。該功能是在達芬奇機器項目中開發的,其任務是擴展JVM,使其支持Java以外的語言。
【JRuby】
JRuby類似于標準的Ruby解釋器,區別只是用Java語言來編寫。?JRuby與Ruby有一些相同的概念,包括面向對象編程和動態類型。其關鍵的區別在于,JRuby與Java緊密結合,可以直接從Java程序中調用它。
l??下載:https://www.jruby.org/download
l??將JRuby提取到一個目錄中。
l??將該目錄的bin子目錄添加到你的路徑。
l??測試一下:jruby -v
JRuby的一個強大功能是它能夠調用Java平臺的類。要做到這一點,首先必須通過調用?"require 'java'"來加載JRuby的Java支持。下面的例子創建了一個帶有JLabel的Java JFrame:
require?'java'
frame?=?javax.swing.JFrame.new
frame.getContentPane.add?javax.swing.JLabel.new('Hello,?World!')
frame.setDefaultCloseOperation?javax.swing.JFrame::EXIT_ON_CLOSE
frame.pack
frame.set_visible?true
JRuby還允許用戶使用更類似于Ruby的下劃線方法命名來調用Java代碼,并將JavaBean屬性作為屬性來引用。
frame.content_pane.add?label
frame.visible?=?true
JRuby可以很容易地被Java調用,這可以通過使用JSR 223 Scripting for Java 6或Apache Bean Scripting框架來做到。
//?使用JSR?233腳本編寫Java?6的例子
ScriptEngineManager?mgr?=?new?ScriptEngineManager();
ScriptEngine?rbEngine?=?mgr.getEngineByExtension("rb");
try?{
rbEngine.eval("puts?'Hello?World!'");
}?catch?(ScriptException?ex)?{
ex.printStackTrace();
}
【Jython】
Jython是Python編程語言的一個實現,運行在Java平臺上。在1999年之前被稱為JPython。
Jython程序可以導入和使用任何Java類。除了一些標準模塊外,Jython程序使用的是Java類而不是Python模塊。Jython幾乎包含了標準Python編程語言發行版中的所有模塊,只是缺少一些最初在C語言中實現的模塊。Jython可以按需或靜態地將Python源代碼編譯成Java字節碼。
下載:https://www.jython.org/download
java -jar D:/tool/jython-standalone-2.7.2.jar
import?org.python.util.PythonInterpreter;
public?class?JythonHelloWorld?{
public?static?void?main(String[]?args)?{
try(PythonInterpreter?pyInterp?=?new?PythonInterpreter())?{
pyInterp.exec("print('Hello?Python?World!')");
}
}
}
from?java.lang?import?System?#?Java?import
print('Running?on?Java?version:?'?+?System.getProperty('java.version'))
print('Unix?time?from?Java:?'?+?str(System.currentTimeMillis()))
【Clojure】
Clojure是Java平臺上的Lisp編程語言的一種現代、動態、功能化的實現。
和其他Lisp編程語言一樣,Clojure把代碼當作數據來處理,并有一個Lisp宏系統。
目前的開發過程是由社區驅動的,由Rich Hickey監督。
Clojure提倡不可變性,使用不可變的數據結構,鼓勵程序員對身份及其狀態進行顯式管理。
這種注重用不可變值和顯式時間遞進構造進行編程的做法是為了開發出更健壯的程序,特別是在開發并發程序時追求程序簡單、快速。
它的類型系統完全是動態的,最近在努力尋求漸進式類型化的實現.
Clojure的商業支持是由Cognitect提供的,Clojure會議每年都會在全球范圍內舉辦,其中最著名的是Clojure/conj。
步驟:https://clojure.org/guides/getting_started
user=> (+?3?4)
7
user=> (+?10?*1)
17
user=> (+?*1 *2)
24
(ns?clojure.examples.hello
(:gen-class))
(defn?hello-world?[]
(println?"Hello?World"))
(hello-world)
(ns?reader.tasklist
(:gen-class?;?
:extends?org.xml.sax.helpers.DefaultHandler?;?
:state?state?;?
:init?init)?;?
(:use?[clojure.java.io?:only?(reader)])
(:import?[java.io?File]
[org.xml.sax?InputSource]
[org.xml.sax.helpers?DefaultHandler]
[javax.xml.parsers?SAXParserFactory]))
【Groovy】
Apache Groovy是一種與Java語法兼容的面向對象編程語言,適用于Java平臺。它既是一種靜態語言,也是一種動態語言,具有類似于Python、Ruby和Smalltalk的特性。
它既可以作為Java平臺的編程語言,也可以作為Java平臺的腳本語言,被編譯成Java虛擬機(JVM)字節碼,并可與其他Java代碼和程序庫無縫互操作。
Groovy使用一種類似于Java的卷標式語法。Groovy支持閉合(Closure)、多行字符串和嵌入字符串中的表達式。
Groovy的一部分強項在于它的AST轉換,通過注解觸發。
大多數有效的Java文件也是有效的Groovy文件。雖然這兩種語言很相似,但Groovy代碼可以更緊湊,因為它不需要Java所要求的所有元素,這使得Java程序員可以從熟悉的Java語法開始,逐步學習Groovy,然后再掌握更多的Groovy編程習慣用語。
Groovy有如下Java沒有的功能: 靜態和動態鍵入(使用關鍵字def)、操作符重載、列表和關聯數組(map)的原生語法、正則表達式的原生支持、多態迭代、字符串插值、增加了輔助方法,以及安全導航操作符?.來自動檢查空指針(例如,variable.method(),或variable.field)。
從第2版開始,Groovy還支持模塊化(能夠根據項目的需要,只提供所需的jar包,從而減少了Groovy的程序庫大小)、類型檢查、靜態編譯、Project Coin語法增強、多語言塊和使用Java 7中引入的invokedynamic指令的持續性能增強。
Groovy通過內嵌式文檔對象模型(DOM)語法,為各種標記語言(如XML和HTML)提供了本地支持。這個功能可以用統一而簡潔的語法和編程方法來定義和操作許多類型的異構數據資產。
與Java不同,Groovy源代碼文件可以作為(未編譯的)腳本執行。
如果它包含任何類定義之外的代碼,比如一個有主方法的類,或者是一個Runnable或GroovyTestCase,那么它就可以作為一個(未編譯的)腳本執行。Groovy?腳本在執行之前會被完全解析、編譯并生成(類似于?Python?和?Ruby)。這些都是在引擎下進行的,編譯后的版本不會作為過程的工件保存。
步驟:https://groovy-lang.org/install.html
GroovyBeans是JavaBeans的Groovy版本。Groovy隱式地生成了getter和setter。在下面的代碼中,setColor(String color)和getColor()是隱式生成的。最后兩行看似直接訪問顏色,實際上是在調用隱式生成的方法。
class?AGroovyBean?{
String?color
}
def?myGroovyBean?=?new?AGroovyBean()
myGroovyBean.setColor('baby?blue')
assert?myGroovyBean.getColor()?==?'baby?blue'
myGroovyBean.color?=?'pewter'
assert?myGroovyBean.color?==?'pewter'
Groovy為處理列表和映射(Map)提供了與Java類似的簡單、一致的語法。
def?movieList?=?['Dersu?Uzala',?'Ran',?'Seven?Samurai']??//?看起來像一個數組,其實是一個列表
assert?movieList[2]?==?'Seven?Samurai'
movieList[3]?=?'Casablanca'??//?添加一個元素到列表中
assert?movieList.size()?==?4
def?monthMap?=?[?'January'?:?31,?'February'?:?28,?'March'?:?31?]??//聲明一個映射(Map)
assert?monthMap['March']?==?31??//?訪問數據項
monthMap['April']?=?30??//?添加一個數據項
assert?monthMap.size()?==?4
Groovy通過ExpandoMetaClass、Extension Modules(僅在Groovy 2中)、類似Objective-C的分類表和DelegatingMetaClass提供了對原型擴展的支持。
ExpandoMetaClass提供了一種特定領域的語言(DSL)來方便地表達類中的變化,類似于Ruby的開放類概念:
Number.metaClass?{
sqrt?=?{?Math.sqrt(delegate)?}
}
assert?9.sqrt()?==?3
assert?4.sqrt()?==?2
Groovy通過原型修改的代碼,這在Java中是看不到的。只有這些屬性或者方法通過元類注冊表注冊以后才能從Java中訪問更改后的代碼。
Groovy還允許重載方法,如getProperty()、propertyMissing()等,使開發者能夠以簡化的以方面為導向的方式攔截對對象的調用,并為它們指定一個動作。下面的代碼使類java.lang.String能夠響應hex屬性:
enum?Color?{
BLACK('#000000'),?WHITE('#FFFFFF'),?RED('#FF0000'),?BLUE('#0000FF')
String?hex
Color(String?hex)?{
this.hex?=?hex
}
}
String.metaClass.getProperty?=?{?String?property?->
def?stringColor?=?delegate
if?(property?==?'hex')?{
Color.values().find?{?it.name().equalsIgnoreCase?stringColor?}?.hex
}
}
assert?"WHITE".hex?==?"#FFFFFF"
assert?"BLUE".hex?==?"#0000FF"
assert?"BLACK".hex?==?"#000000"
assert?"GREEN".hex?==?null
Grails框架廣泛使用元編程來實現GORM動態查找器,比如User.findByName('Josh')等。
Groovy的語法允許在某些情況下省略括號和點。以下是groovy的代碼:
take(coffee).with(sugar,?milk).and(liquor)
可寫成:
take?coffee?with?sugar,?milk?and?liquor
這使得特定領域語言(DSL)的開發看起來像普通英語一樣。
雖然Groovy主要是一種面向對象的語言,但它也提供了功能型編程的特性。
根據Groovy的文檔。Groovy中閉合的工作原理類似于'方法指針',使代碼可以在以后的時間點修改和運行。Groovy的閉合支持自由變量,即沒有作為參數顯式傳遞但在聲明的上下文中存在的變量,如部分應用(它稱之為?"curry"),委托、隱式、類型化和非類型化參數
當對一個確定類型的集合進行處理時,可以推斷出傳給集合上的操作的閉合:
list?=?[1,?2,?3,?4,?5,?6,?7,?8,?9]
/*
*?非零數被強制為真,所以當?it?%?2?==0?(偶數)時,它是假的。
*?隱含的?"it"參數的類型可以被IDE推斷為整數。
*?它也可以寫成:
*?list.findAll?{?Integer?i?->?i?%?2?}。
*?list.findAll?{?i?->?i?%?2?}
*/
def?odds?=?list.findAll?{?it?%?2?}
assert?odds?==?[1,?3,?5,?7,?9]
可以將一組表達式寫在一個閉合塊中,而不需要引用其實現,那么在稍后的時候可以使用委托的方式分配響應的對象:
//?這段代碼包含的表達式沒有指向一個實現。
def?operations?=?{
declare?5
sum?4
divide?3
}
/*
*?這個類將處理一些操作,它可以在上述閉合中使用。另一個類也
*?可以聲明相同的方法,例如在計算中使用webservice操作。
*/
class?Expression?{
BigDecimal?value
/*
*?在調用declare方法時如果傳遞了一個Integer作為參數,但它被強制轉化為BigDecimal。如果這個類有一個'declare(Integer)'方法,怎會首先使用這個方法。
*/
def?declare(BigDecimal?value)?{
this.value?=?value
}
def?sum(BigDecimal?valueToAdd)?{
this.value?+=?valueToAdd
}
def?divide(BigDecimal?divisor)?{
this.value?/=?divisor
}
def?propertyMissing(String?property)?{
if?(property?==?"print")?println?value
}
}
//?這里定義的是上面的代碼塊中的表達式。
operations.delegate?=?new?Expression()
operations()
Curry通常被稱為部分應用,這個Groovy的特性允許將閉合的參數設置為默認參數中的任何一個參數,用綁定的值創建一個新的閉合。
向curry()方法提供一個參數將修改一個參數。提供N個參數將修改參數1 ....N。
def?joinTwoWordsWithSymbol?=?{?symbol,?first,?second?->?first?+?symbol?+?second?}
assert?joinTwoWordsWithSymbol('#',?'Hello',?'World')?==?'Hello#World'
def?concatWords?=?joinTwoWordsWithSymbol.curry('?')
assert?concatWords('Hello',?'World')?==?'Hello?World'
def?prependHello?=?concatWords.curry('Hello')
//def?prependHello?=?joinTwoWordsWithSymbol.curry('?',?'Hello')
assert?prependHello('World')?==?'Hello?World'
Curry也可以用rcurry()進行反向使用(將參數N修改為N - 1)。
def?power?=?{?BigDecimal?value,?BigDecimal?power?->
value**power
}
def?square?=?power.rcurry(2)
def?cube?=?power.rcurry(3)
assert?power(2,?2)?==?4
assert?square(4)?==?16
assert?cube(3)?==?27
Groovy還支持惰性評估(Lazy Evaluation),減少(Reduce)/折疊(Fold),無限結構和不變性等等。
在JavaScript Object Notation(JSON)和XML處理上,Groovy采用了Builder模式,使得數據結構的創建沒有那么繁瑣。例如,下面的XML:
可以通過以下Groovy代碼生成:
def?writer?=?new?StringWriter()
def?builder?=?new?groovy.xml.MarkupBuilder(writer)
builder.languages?{
language(year:?1995)?{
name?"Java"
paradigm?"object?oriented"
typing?"static"
}
language?(year:?1995)?{
name?"Ruby"
paradigm?"functional,?object?oriented"
typing?"duck?typing,?dynamic"
}
language?(year:?2003)?{
name?"Groovy"
paradigm?"functional,?object?oriented"
typing?"duck?typing,?dynamic,?static"
}
}
并且還可以通過StreamingMarkupBuilder以流媒體的方式進行處理。要想生成JSON,可以將MarkupBuilder換成JsonBuilder。
要解析數據并搜索,可使用Groovy的findAll方法:
def?languages?=?new?XmlSlurper().parseText?writer.toString()
//這里使用了Groovy的regex正則表達式語法,用了匹配器(=~),該匹配器執行后返回一個布爾類型值:如果該值包含我們的字符串,則為true,否則為false。
def?functional?=?languages.language.findAll?{?it.paradigm?=~?"functional"?}
assert?functional.collect?{?it.name?}?==?["Groovy",?"Ruby"]
在Groovy中,字符串可以通過使用GStrings對變量和表達式進行插值:
BigDecimal?account?=?10.0
def?text?=?"The?account?shows?currently?a?balance?of?$account"
assert?text?==?"The?account?shows?currently?a?balance?of?10.0"
包含變量和表達式的GStrings必須用雙引號聲明。
一個復雜的表達式必須用大括號括起來:
BigDecimal?minus?=?4.0
text?=?"The?account?shows?currently?a?balance?of?${account?-?minus}"
assert?text?==?"The?account?shows?currently?a?balance?of?6.0"
//?如果沒有括號來隔離表達式,結果是這樣的:
text?=?"The?account?shows?currently?a?balance?of?$account?-?minus"
assert?text?==?"The?account?shows?currently?a?balance?of?10.0?-?minus"
表達式評估可以通過使用箭頭語法進行延遲:
BigDecimal?tax?=?0.15
text?=?"The?account?shows?currently?a?balance?of?${->account?-?account*tax}"
tax?=?0.10
//稅額是在GString申報后改變的。
//只有當表達式被實際執行時,變量才會被綁定。
assert?text?==?"The?account?shows?currently?a?balance?of?9.000"
根據Groovy自己的文檔,?“當Groovy編譯器編譯Groovy腳本和類時,在這個過程中的某個時刻,源碼最終會以具體語法樹的形式在內存中,然后轉化為抽象語法樹。
AST?轉換的目的是讓開發人員能夠在編譯過程中定位AST,?以便在AST轉化成JVM運行的字節碼之前,能夠對其進行修改。
AST?轉換為Groovy提供了改進的編譯時刻元編程能力,使其在語言層面上有了強大的靈活性,同時又不會對運行時的性能造成影響。”
Groovy中的AST的例子有:
l??類別和Mixin轉換
l??不可變的AST宏
l??新化改造
l??單一實例轉換
等等。
根據Groovy的文檔,“Traits是語言的一種結構化構造,它允許:行為的組成、接口的運行時實現、行為重寫以及與靜態類型檢查/編譯的兼容性。”
Traits可以被看作是攜帶默認實現和狀態的接口。一個trait是用trait關鍵字定義的:
trait?FlyingAbility?{?/*聲明trait?*/
String?fly()?{?"I'm?flying!"?}?/*?聲明trait的方法?*/
}
然后,它可以像普通的接口一樣使用關鍵字implements:
class?Bird?implements?FlyingAbility?{}?/*?將FlyingAbility屬性添加到Bird類的功能中?*/
def?bird?=?new?Bird()?/*?實例化一個新的Bird?*/
assert?bird.fly()?==?"I'm?flying!"?/*?鳥類自動獲取FlyingAbility?trait的行為??*/
Traits的應用范圍很廣,從應用開發到測試都可以用到。
采用Groovy的著名例子包括:
l??Adaptavist ScriptRunner,嵌入了一個Groovy實現,用于自動化和擴展Atlassian工具,全球超過20000個組織在使用。
l??Apache OFBiz,開源的企業資源規劃(ERP)系統,使用Groovy開發。
l??Eucalyptus作為一個云管理系統,使用了大量的Groovy代碼。
l??Gradle是一個熱門的構建自動化工具,使用Groovy開發。
l??LinkedIn在他們的一些子系統中使用了Groovy和Grails。
l??Jenkins,一個用于持續集成的平臺。從版本2開始,Jenkins提供了一個Pipeline插件,允許用Groovy編寫構建指令。
l??Liferay,?在他們的kaleo工作流程中使用了groovy。
l??Sky.com使用Groovy和Grails來服務海量的在線媒體內容。
l??SmartThings是一個面向智能家居和消費類物聯網的開放平臺,它使用了Groovy的面向安全的子集。
l??SoapUI用Groovy作為Webservice測試開發語言。
l??Survata是一家市場研究初創公司,它使用Groovy和Grails。
l??歐洲專利局(EPO)在Groovy基礎上開發了一種數據流編程語言,"利用了每個國家專利局溝通過程中的相似之處,從而將其轉化為一個單一的、通用的過程"。
l??Groovy可以集成到任何JVM環境中,JBoss Seam框架除了Java之外,還提供了Groovy作為一種開發語言,開箱即用。
l??vCalc.com在其數學包引擎中使用Groovy。
l??Wired.com使用Groovy和Grails開發了網站的獨立產品評論部分。
l??XWiki SAS在其合作的開源產品中使用Groovy作為腳本語言。
許多集成開發環境(IDE)和文本編輯器都支持Groovy:
l??Android Studio,用于制作Android應用程序的IDE
l??Atom Editor
l??Eclipse,通過Groovy-Eclipse插件
l??Emacs,通過groovy-emacs-mode項目的groovy-mode。
l??IntelliJ IDEA社區版,Grails/Griffon僅在終極版中可以使用。
l??JDeveloper,與Oracle ADF配合使用
l??jEdit,一個Java平臺的高級文本編輯器。
l??Kate是KDE的高級文本編輯器,支持Groovy和其他200多種文件格式。
l??NetBeans,從6.5版本開始支持Groovy
l??Notepad++,一個Microsoft Windows上的高級文本編輯器。
l??Sublime Text,一個跨平臺文本編輯器。
l??TextMate
l??Visual Studio Code
l??UltraEdit,通用程序編輯器
架構設計
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。