比較與分析Groovy與Java
Groovy與Java的比較(上)
1.支持函數(shù)式編程,不需要main函數(shù)
2.默認(rèn)導(dǎo)入常用的包,包括:
java.io
java.math
java.net
java.util
groovy.lang
groovy.util
3.斷言不支持jvm的-ea參數(shù)進(jìn)行開(kāi)關(guān)
4.支持對(duì)對(duì)象進(jìn)行布爾求值
5.類不支持default作用域,且默認(rèn)作用域?yàn)閜ublic
6.受檢查類型異常(Checked Exception)也可以不用捕獲
7.一些新的運(yùn)算符
8.groovy中基本類型也是對(duì)象,可以直接調(diào)用對(duì)象的方法,如:
- assert (-12345).abs() == 12345
但浮點(diǎn)運(yùn)算是基于BigDecimal類
- assert 0.25 instanceof BigDecimal
- assert 0.1 * 3 == 0.3
- assert 1.1 + 0.1 == 1.2
- assert 1 / 0.25 == 4
Groovy與Java的比較(中)
9.字符串的處理
String對(duì)象和java類似,但沒(méi)有character的概念,沒(méi)有迭代每個(gè)字符的方法。
使用單引號(hào)定義普通字符串,雙引號(hào)定義的字符串可以包含Groovy運(yùn)算符,$符號(hào)則需要轉(zhuǎn)義("\$"),如:
- String name = "Ben"
- String greeting = "Good morning, ${name}"
- assert greeting == 'Good morning, Ben'
- String output = "The result of 2 + 2 is: ${2 + 2}"
- assert output == "The result of 2 + 2 is: 4"
還可以使用三個(gè)連續(xù)的"來(lái)定義多行字符串,如:
- String getEmailBody(String name) {
- return """Dear ${name},
- Thank you for your recent inquiry. One of our team members
- will process it shortly and get back to you. Some time in
- the next decade. Probably.
- Warmest and best regards,
- Customer Services
- """
- }
char類型的使用方法:
- char ch = 'D'
- assert ch instanceof Character
- String str = "Good morning Ben"
- str = str.replace(' ' as char, '+' as char)
- assert str == "Good+morning+Ben"
10.as運(yùn)算符,用于沒(méi)有集成關(guān)系的類型間強(qiáng)制類型轉(zhuǎn)換,如:
- assert 543667 as String == "543667"
- assert 1234.compareTo("34749397" as int) < 0
可通過(guò)實(shí)現(xiàn)asType(Class) 方法來(lái)實(shí)現(xiàn)自定義的as行為,默認(rèn)的方法包括:
11.一些集合類型的語(yǔ)法甜頭(Syntax sugar for lists, maps, and ranges)
從語(yǔ)言層面支持List\Map\Range類型,而不是通過(guò)SDK中的類
使用[]創(chuàng)建創(chuàng)建和初始化List、Map,如:
- List myList = [ "apple", "orange", "lemon" ]
- Map myMap = [ 3: "three", 6: "six", 2: "two" ]
- assert 3 == [ 5, 6, 7 ].size()
List\Map支持?jǐn)?shù)組風(fēng)格的用法
- List numbers = [ 5, 10, 15, 20, 25 ]
- assert numbers[0] == 5 //獲取List中的對(duì)象
- assert numbers[3] == 20
- assert numbers[-1] == 25 //逆序獲取List對(duì)象
- assert numbers[-3] == 15
- numbers[2] = 3 //更新List對(duì)象
- assert numbers[2] == 3
- numbers < < 30 //添加數(shù)據(jù)
- assert numbers[5] == 30
- Map items = [ "one": "apple",
- "two": "orange",
- "three": "pear",
- "four": "cherry" ]
- assert items["two"] == "orange" //從Map中獲得對(duì)象
- assert items["four"] == "cherry"
- items["one"] = "banana" //更新Map中對(duì)象
- assert items["one"] == "banana"
- items["five"] = "grape" //增加對(duì)象到中
- assert items["five"] == "grape"
新的類型:Range
Range實(shí)現(xiàn)了java.util.List,可以作為L(zhǎng)ist使用,并擴(kuò)展了包含(..)和排除(..< )運(yùn)算符
- // an inclusive range
- def range = 5..8
- assert range.size() == 4
- assert range.get(2) == 7
- assert range[2] == 7
- assert range instanceof java.util.List
- assert range.contains(5)
- assert range.contains(8)
- // lets use an exclusive range
- range = 5..< 8
- assert range.size() == 3
- assert range.get(2) == 7
- assert range[2] == 7
- assert range instanceof java.util.List
- assert range.contains(5)
- assert ! range.contains(8)
- //get the end points of the range without using indexes
- def range = 1..10
- assert range.from == 1
- assert range.to == 10
- List fruit = [
- "apple",
- "pear",
- "lemon",
- "orange",
- "cherry" ]
- for (int i in 0..< fruit.size()) { //Iterates through an exclusive range B
- println "Fruit number $i is '${fruit[i]}'"
- }
- List subList = fruit[1..3] //Extracts a list slice C
12.一些省時(shí)的特性
行末的分號(hào)(;)不是必須的。在沒(méi)有分號(hào)的情況下,groovy計(jì)算一行如果是有效的表達(dá)式,則認(rèn)為下一行是新的表達(dá)式,否則將聯(lián)合下一行共同作為一個(gè)表達(dá)式。分隔多行的表達(dá)式,可以用/符號(hào),如:
- String fruit = "orange, apple, pear, " \
- + "banana, cherry, nectarine"
方法調(diào)用時(shí)的圓括號(hào)()不是必須的(但建議保留)。但在無(wú)參方法調(diào)用,或第一個(gè)參數(shù)是集合類型定義時(shí)還是必須的:
- println "Hello, world!"
- println()
- println([1, 2, 3, 4])
方法定義中的return語(yǔ)句不是必須的,沒(méi)有return的情況下,將返回方法體中最后一行的值,如下面的方法返回value+1:
int addOne(int value) { value + 1 }
Groovy與Java的比較(下)
13.語(yǔ)言級(jí)別的正則表達(dá)式支持
使用斜線(/)定義正則表達(dá)式,避免java中的多次轉(zhuǎn)義,如"\\\\\\w"相當(dāng)于/\\\w/。
如果要作為java中的Pattern對(duì)象使用,可以使用~符號(hào)表示,如:
- assert ~"London" instanceof java.util.regex.Pattern
- assert ~/\w+/ instanceof java.util.regex.Pattern
使用=~運(yùn)算符進(jìn)行匹配
- assert "Speaking plain English" =~ /plain/
使用==~運(yùn)算符進(jìn)行精確匹配
- assert !("Speaking plain English" ==~ /plain/)
- assert "Speaking plain English" ==~ /.*plain.*/
捕獲分組,如:
- import java.util.regex.Matcher
- String str = "The rain in Spain falls mainly on the plain"
- Matcher m = str =~ /\b(\w*)ain(\w*)\b/
- if (m) {
- for (int i in 0..< m.count) {
- println "Found: '${m[i][0]}' - " +
- "prefix: '${m[i][1]}'" +
- ", suffix: '${m[i][2]}'"
- }
- }
輸出:
- Found: 'rain' - prefix: 'r', suffix: ''
- Found: 'Spain' - prefix: 'Sp', suffix: ''
- Found: 'mainly' - prefix: 'm', suffix: 'ly'
- Found: 'plain' - prefix: 'pl', suffix: ''
14.簡(jiǎn)化的javabean
直接使用“.屬性名”的方法代替getter,如:
- Date now = new Date()
- println "Current time in milliseconds: ${ now.time }"
- now.time = 103467843L
- assert now.time == 103467843L
屬性定義不需要setter/getter。未指定作用域的屬性,groovy自動(dòng)認(rèn)為是private并生為其成setter/getter,也可以根據(jù)需要進(jìn)行覆寫。如下除了最后一個(gè)字段,都是屬性:
- class MyProperties {
- static String classVar
- final String constant = "constant"
- String name
- public String publicField
- private String privateField
- }
簡(jiǎn)化bean的初始化,可以使用Map進(jìn)行初始化,或鍵值對(duì)的方法,如
- DateFormat format = new SimpleDateFormat(
- lenient: false,
- numberFormat: NumberFormat.getIntegerInstance(),
- timeZone: TimeZone.getTimeZone("EST"))
可以使用屬性的方式讀取map:
- Map values = [ fred: 1, peter: 5, glen: 42 ]
- assert values.fred == 1
- values.peter = 10
- assert values.peter == 10
注:groovy將map的key作為字符串處理,除非是數(shù)字或者用圓括號(hào)包含。這里的fred就是字符串"fred",但引號(hào)不是必須的,只有在key包含空格、句點(diǎn)或其他不能作為Groovy標(biāo)示符的字符存在時(shí)才需要。如果需要使用一個(gè)變量的值作為key,則使用圓括號(hào),如 [ (fred): 1 ]。
15.groovy不具備的java特性
不能用單引號(hào)定義字符類型,但可以使用as運(yùn)算符將一個(gè)字母的字符串轉(zhuǎn)換為字符類型
for循環(huán)中不能用逗號(hào)分隔多個(gè)運(yùn)算符,如下面的代碼是不允許的:
- for (int i = 0, j = 0; i < 10; i++, j++) { ... }
不支持DO...WHILE循環(huán),但可以使用while...for運(yùn)算代替
不支持內(nèi)部類和匿名類,但支持閉包和在一個(gè)文件中定義多個(gè)類
16.groovy的重要特性——閉包:
可以看作一個(gè)匿名方法定義,可以賦予給一個(gè)變量名、作為參數(shù)傳遞給方法調(diào)用、或者被方法返回。也可以想象為只有一個(gè)方法定義的匿名類。
閉包的語(yǔ)法{ < arguments> -> < body> },如:
- List fruit = [ "apple", "Orange", "Avocado", "pear", "cherry" ]
- fruit.sort { String a, String b -> a.compareToIgnoreCase(b) }
- println "Sorted fruit: ${fruit}"
注:sort方法只有一個(gè)閉包類型的參數(shù),省略了圓括號(hào);閉包中使用了默認(rèn)的return值
當(dāng)沒(méi)有參數(shù)傳入時(shí),仍然需要保留箭頭的存在{-> ... }
只有一個(gè)參數(shù)傳入時(shí),可以省略箭頭,隱式的創(chuàng)建一個(gè)it參數(shù),引用當(dāng)前對(duì)象,如:
- [ "apple", "pear", "cherry" ].each { println it }
可以將閉包賦予一個(gè)變量,如
- Closure comparator = { String a, String b ->
- a.compareToIgnoreCase(b)
- }
- List fruit = [ "apple", "Orange", "Avocado", "pear", "cherry" ]
- fruit.sort(comparator)
- println "Sorted fruit: ${fruit}"
- assert comparator("banana", "Lemon") < 0
只有一個(gè)參數(shù)的閉包,可以不傳入?yún)?shù),運(yùn)行時(shí)隱式的傳入null參數(shù)
當(dāng)閉包是一個(gè)方法的最后一個(gè)參數(shù)時(shí),可以寫在圓括號(hào)外面,如:
- List list = [ 1, 3, 5, 6 ]
- list.inject(0, { runningTotal, value -> runningTotal + value })
可以這樣寫:
- assert 15 == list.inject(0) { runningTotal, value -> runningTotal + value }
便于閉包中具有多行時(shí)代碼更加清晰
不要濫用閉包。當(dāng)閉包作為一個(gè)屬性時(shí),不要在子類中覆寫,實(shí)在需要這樣做,使用方法。使用閉包也無(wú)法利用java中很多AOP框架的特性
17.groovy的重要特性——?jiǎng)討B(tài)編程
動(dòng)態(tài)的使用屬性,如下的java代碼:
- public void sortPeopleByGivenName(List< Person> personList) {
- Collections.sort(personList, new Comparator< Person>() {
- public int compare(Person p1, Person p2) {
- return p1.getFamilyName().compareTo(p2.getFamilyName());
- }
- } ) ;
- }
可使用下面的代替,當(dāng)需要使用其他字段比較時(shí),不需要修改代碼
- def sortPeople(people, property) {
- people.sort { p1, p2 -> p1."${property}" < => p2."${property}" }
- }
將一個(gè)String作為屬性或方法名進(jìn)行調(diào)用,如:
- peopleList.sort()
- peopleList."sort"()
動(dòng)態(tài)類型(duck typing:"if it walks like a duck and talks like a duck, it’s probably a duck):運(yùn)行期解析對(duì)象的屬性和方法,允許在運(yùn)行時(shí)增加對(duì)象的屬性和方法而不修改源代碼,因此可能出現(xiàn)調(diào)用未定義方法的情況。
動(dòng)態(tài)編程帶來(lái)的危險(xiǎn):
編譯器不能檢查到類型錯(cuò)誤、方法或?qū)傩缘腻e(cuò)誤調(diào)用,應(yīng)該養(yǎng)成編寫測(cè)試的習(xí)慣
難以調(diào)試,使用“單步跳入(step into)”經(jīng)常進(jìn)入一些反射中,使用“運(yùn)行到光標(biāo)處(run to cursor)”代替
動(dòng)態(tài)的類型定義使代碼難以閱讀,使用良好的命名、注釋,盡量明確定義變量類型,便于IDE檢測(cè)ht potential type errors in the call潛在的錯(cuò)誤。
18.Groovy JDK中的增強(qiáng)
Collection/Array/String具有size()方法
Collection/Array/String具有each(closure)方法,方便的進(jìn)行遍歷
Collection/Array/String具有find(closure)、findAll(closure)方法,find返回第一個(gè)符合條件的對(duì)象,findAll返回所有符合條件對(duì)象列表,如:
- def glen = personList.find { it.firstName == "Glen" }
Collection/Array/String具有collect(closure)方法,對(duì)集合中每個(gè)對(duì)象執(zhí)行一段方法后,返回結(jié)果集,如:
- def names = [ "Glen", "Peter", "Alice", "Graham", "Fiona" ]
- assert [ 4, 5, 5, 6, 5 ] == names.collect { it.size() }
Collection/Array/String具有sort(closure)方法,包括:
一個(gè)參數(shù)的閉包,如:
- def names = [ "Glen", "Peter", "Ann", "Graham", "Veronica" ]
- def sortedNames = names.sort { it.size() }
- assert [ "Ann", "Glen", "Peter", "Graham", "Veronica" ] == sortedNames
兩個(gè)參數(shù)的閉包,如:
- def names = [ "Glen", "Peter", "Ann", "Graham", "Veronica" ]
- def sortedNames = names.sort { name1, name2 ->
- name1.size() < => name2.size()
- }
- assert [ "Ann", "Glen", "Peter", "Graham", "Veronica" ] == sortedNames
Collection/Array具有join(String)方法
- def names = [ "Glen", "Peter", "Alice", "Fiona" ]
- assert "Glen, Peter, Alice, Fiona" == names.join(", ")
File.text屬性讀取文件內(nèi)容作為字符串返回
File.size()方法返回文件的byte值,相當(dāng)于File.length()方法
File.withWriter(closure)方法,從文件創(chuàng)建一個(gè)Writer對(duì)象傳給閉包,閉包執(zhí)行完畢后,依賴的輸出流自動(dòng)安全關(guān)閉。另外還有若干with...方法查看文檔
Matcher.count返回相應(yīng)Matcher的匹配數(shù)量
Number.abs()方法,對(duì)數(shù)字求絕對(duì)值
Number.times(closure)執(zhí)行n次閉包,將當(dāng)前執(zhí)行的次數(shù)作為參數(shù)傳給閉包
19.XML的處理
示例的XML:
- < root>
- < item qty="10">
- < name>Orange< /name>
- < type>Fruit< /type>
- < /item>
- < item qty="6">
- < name>Apple< /name>
- < type>Fruit< /type>
- < /item>
- < item qty="2">
- < name>Chair< /name>
- < type>Furniture< /type>
- < /item>
- < /root>
處理程序
- import groovy.xml.MarkupBuilder
- import groovy.util.XmlSlurper
- def file = new File("test.xml")
- def objs = [
- [ quantity: 10, name: "Orange", type: "Fruit" ],
- [ quantity: 6, name: "Apple", type: "Fruit" ],
- [ quantity: 2, name: "Chair", type: "Furniture" ] ]
- def b = new MarkupBuilder(new FileWriter(file)) 創(chuàng)建MarkupBuilder對(duì)象
- b.root {
- 動(dòng)態(tài)調(diào)用root方法,但builder對(duì)象并沒(méi)有該方法,把它作為一個(gè)新的XML對(duì)象的根節(jié)點(diǎn),并且把方法名作為根節(jié)點(diǎn)名稱
- objs.each { o ->
- item(qty: o.quantity) {
- name(o.name)
- type(o.type)
- }
- }
- }
- 遍歷集合,創(chuàng)建節(jié)點(diǎn),其中item/name/type也是動(dòng)態(tài)的方法,以方法名作為節(jié)點(diǎn)名,方法參數(shù)作為節(jié)點(diǎn)的屬性
- def xml = new XmlSlurper().parse(file)
- 使用XmlSlurper對(duì)象解析內(nèi)存中的XML文件
- assert xml.item.size() == 3
- assert xml.item[0].name == "Orange"
- assert xml.item[0].@qty == "10"
- 使用動(dòng)態(tài)的屬性名讀取XML節(jié)點(diǎn)
- 使用@字符讀取節(jié)點(diǎn)屬性
- println "Fruits: ${xml.item.findAll {it.type == 'Fruit'}*.name }"
- println "Total: ${xml.item.@qty.list().sum {it.toInteger()} }"
20.最佳實(shí)踐
使用地道的Groovy語(yǔ)法:盡可能使用groovy中簡(jiǎn)化后的語(yǔ)法風(fēng)格,減少代碼量
實(shí)驗(yàn):使用groovy console或shell可以方便的實(shí)驗(yàn)groovy代碼
盡可能使用方法,而不是閉包。方法易于理解,也利于和java交互
在方法簽名中盡可能的使用確定的類型,便于代碼閱讀和IDE的錯(cuò)誤檢測(cè)。在使用動(dòng)態(tài)類型時(shí)要有清晰完善的文檔注釋
【編輯推薦】