比較與分析Groovy與Java
Groovy與Java的比較(上)
1.支持函數(shù)式編程,不需要main函數(shù)
2.默認導入常用的包,包括:
java.io
java.math
java.net
java.util
groovy.lang
groovy.util
3.斷言不支持jvm的-ea參數(shù)進行開關
4.支持對對象進行布爾求值
5.類不支持default作用域,且默認作用域為public
6.受檢查類型異常(Checked Exception)也可以不用捕獲
7.一些新的運算符
8.groovy中基本類型也是對象,可以直接調(diào)用對象的方法,如:
- assert (-12345).abs() == 12345
但浮點運算是基于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對象和java類似,但沒有character的概念,沒有迭代每個字符的方法。
使用單引號定義普通字符串,雙引號定義的字符串可以包含Groovy運算符,$符號則需要轉(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"
還可以使用三個連續(xù)的"來定義多行字符串,如:
- 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運算符,用于沒有集成關系的類型間強制類型轉(zhuǎn)換,如:
- assert 543667 as String == "543667"
- assert 1234.compareTo("34749397" as int) < 0
可通過實現(xiàn)asType(Class) 方法來實現(xiàn)自定義的as行為,默認的方法包括:
11.一些集合類型的語法甜頭(Syntax sugar for lists, maps, and ranges)
從語言層面支持List\Map\Range類型,而不是通過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支持數(shù)組風格的用法
- List numbers = [ 5, 10, 15, 20, 25 ]
- assert numbers[0] == 5 //獲取List中的對象
- assert numbers[3] == 20
- assert numbers[-1] == 25 //逆序獲取List對象
- assert numbers[-3] == 15
- numbers[2] = 3 //更新List對象
- 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中獲得對象
- assert items["four"] == "cherry"
- items["one"] = "banana" //更新Map中對象
- assert items["one"] == "banana"
- items["five"] = "grape" //增加對象到中
- assert items["five"] == "grape"
新的類型:Range
Range實現(xiàn)了java.util.List,可以作為List使用,并擴展了包含(..)和排除(..< )運算符
- // 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.一些省時的特性
行末的分號(;)不是必須的。在沒有分號的情況下,groovy計算一行如果是有效的表達式,則認為下一行是新的表達式,否則將聯(lián)合下一行共同作為一個表達式。分隔多行的表達式,可以用/符號,如:
- String fruit = "orange, apple, pear, " \
- + "banana, cherry, nectarine"
方法調(diào)用時的圓括號()不是必須的(但建議保留)。但在無參方法調(diào)用,或第一個參數(shù)是集合類型定義時還是必須的:
- println "Hello, world!"
- println()
- println([1, 2, 3, 4])
方法定義中的return語句不是必須的,沒有return的情況下,將返回方法體中最后一行的值,如下面的方法返回value+1:
int addOne(int value) { value + 1 }
Groovy與Java的比較(下)
13.語言級別的正則表達式支持
使用斜線(/)定義正則表達式,避免java中的多次轉(zhuǎn)義,如"\\\\\\w"相當于/\\\w/。
如果要作為java中的Pattern對象使用,可以使用~符號表示,如:
- assert ~"London" instanceof java.util.regex.Pattern
- assert ~/\w+/ instanceof java.util.regex.Pattern
使用=~運算符進行匹配
- assert "Speaking plain English" =~ /plain/
使用==~運算符進行精確匹配
- 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.簡化的javabean
直接使用“.屬性名”的方法代替getter,如:
- Date now = new Date()
- println "Current time in milliseconds: ${ now.time }"
- now.time = 103467843L
- assert now.time == 103467843L
屬性定義不需要setter/getter。未指定作用域的屬性,groovy自動認為是private并生為其成setter/getter,也可以根據(jù)需要進行覆寫。如下除了最后一個字段,都是屬性:
- class MyProperties {
- static String classVar
- final String constant = "constant"
- String name
- public String publicField
- private String privateField
- }
簡化bean的初始化,可以使用Map進行初始化,或鍵值對的方法,如
- 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ù)字或者用圓括號包含。這里的fred就是字符串"fred",但引號不是必須的,只有在key包含空格、句點或其他不能作為Groovy標示符的字符存在時才需要。如果需要使用一個變量的值作為key,則使用圓括號,如 [ (fred): 1 ]。
15.groovy不具備的java特性
不能用單引號定義字符類型,但可以使用as運算符將一個字母的字符串轉(zhuǎn)換為字符類型
for循環(huán)中不能用逗號分隔多個運算符,如下面的代碼是不允許的:
- for (int i = 0, j = 0; i < 10; i++, j++) { ... }
不支持DO...WHILE循環(huán),但可以使用while...for運算代替
不支持內(nèi)部類和匿名類,但支持閉包和在一個文件中定義多個類
16.groovy的重要特性——閉包:
可以看作一個匿名方法定義,可以賦予給一個變量名、作為參數(shù)傳遞給方法調(diào)用、或者被方法返回。也可以想象為只有一個方法定義的匿名類。
閉包的語法{ < arguments> -> < body> },如:
- List fruit = [ "apple", "Orange", "Avocado", "pear", "cherry" ]
- fruit.sort { String a, String b -> a.compareToIgnoreCase(b) }
- println "Sorted fruit: ${fruit}"
注:sort方法只有一個閉包類型的參數(shù),省略了圓括號;閉包中使用了默認的return值
當沒有參數(shù)傳入時,仍然需要保留箭頭的存在{-> ... }
只有一個參數(shù)傳入時,可以省略箭頭,隱式的創(chuàng)建一個it參數(shù),引用當前對象,如:
- [ "apple", "pear", "cherry" ].each { println it }
可以將閉包賦予一個變量,如
- 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
只有一個參數(shù)的閉包,可以不傳入?yún)?shù),運行時隱式的傳入null參數(shù)
當閉包是一個方法的最后一個參數(shù)時,可以寫在圓括號外面,如:
- List list = [ 1, 3, 5, 6 ]
- list.inject(0, { runningTotal, value -> runningTotal + value })
可以這樣寫:
- assert 15 == list.inject(0) { runningTotal, value -> runningTotal + value }
便于閉包中具有多行時代碼更加清晰
不要濫用閉包。當閉包作為一個屬性時,不要在子類中覆寫,實在需要這樣做,使用方法。使用閉包也無法利用java中很多AOP框架的特性
17.groovy的重要特性——動態(tài)編程
動態(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());
- }
- } ) ;
- }
可使用下面的代替,當需要使用其他字段比較時,不需要修改代碼
- def sortPeople(people, property) {
- people.sort { p1, p2 -> p1."${property}" < => p2."${property}" }
- }
將一個String作為屬性或方法名進行調(diào)用,如:
- peopleList.sort()
- peopleList."sort"()
動態(tài)類型(duck typing:"if it walks like a duck and talks like a duck, it’s probably a duck):運行期解析對象的屬性和方法,允許在運行時增加對象的屬性和方法而不修改源代碼,因此可能出現(xiàn)調(diào)用未定義方法的情況。
動態(tài)編程帶來的危險:
編譯器不能檢查到類型錯誤、方法或?qū)傩缘腻e誤調(diào)用,應該養(yǎng)成編寫測試的習慣
難以調(diào)試,使用“單步跳入(step into)”經(jīng)常進入一些反射中,使用“運行到光標處(run to cursor)”代替
動態(tài)的類型定義使代碼難以閱讀,使用良好的命名、注釋,盡量明確定義變量類型,便于IDE檢測ht potential type errors in the call潛在的錯誤。
18.Groovy JDK中的增強
Collection/Array/String具有size()方法
Collection/Array/String具有each(closure)方法,方便的進行遍歷
Collection/Array/String具有find(closure)、findAll(closure)方法,find返回第一個符合條件的對象,findAll返回所有符合條件對象列表,如:
- def glen = personList.find { it.firstName == "Glen" }
Collection/Array/String具有collect(closure)方法,對集合中每個對象執(zhí)行一段方法后,返回結果集,如:
- def names = [ "Glen", "Peter", "Alice", "Graham", "Fiona" ]
- assert [ 4, 5, 5, 6, 5 ] == names.collect { it.size() }
Collection/Array/String具有sort(closure)方法,包括:
一個參數(shù)的閉包,如:
- def names = [ "Glen", "Peter", "Ann", "Graham", "Veronica" ]
- def sortedNames = names.sort { it.size() }
- assert [ "Ann", "Glen", "Peter", "Graham", "Veronica" ] == sortedNames
兩個參數(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值,相當于File.length()方法
File.withWriter(closure)方法,從文件創(chuàng)建一個Writer對象傳給閉包,閉包執(zhí)行完畢后,依賴的輸出流自動安全關閉。另外還有若干with...方法查看文檔
Matcher.count返回相應Matcher的匹配數(shù)量
Number.abs()方法,對數(shù)字求絕對值
Number.times(closure)執(zhí)行n次閉包,將當前執(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對象
- b.root {
- 動態(tài)調(diào)用root方法,但builder對象并沒有該方法,把它作為一個新的XML對象的根節(jié)點,并且把方法名作為根節(jié)點名稱
- objs.each { o ->
- item(qty: o.quantity) {
- name(o.name)
- type(o.type)
- }
- }
- }
- 遍歷集合,創(chuàng)建節(jié)點,其中item/name/type也是動態(tài)的方法,以方法名作為節(jié)點名,方法參數(shù)作為節(jié)點的屬性
- def xml = new XmlSlurper().parse(file)
- 使用XmlSlurper對象解析內(nèi)存中的XML文件
- assert xml.item.size() == 3
- assert xml.item[0].name == "Orange"
- assert xml.item[0].@qty == "10"
- 使用動態(tài)的屬性名讀取XML節(jié)點
- 使用@字符讀取節(jié)點屬性
- println "Fruits: ${xml.item.findAll {it.type == 'Fruit'}*.name }"
- println "Total: ${xml.item.@qty.list().sum {it.toInteger()} }"
20.最佳實踐
使用地道的Groovy語法:盡可能使用groovy中簡化后的語法風格,減少代碼量
實驗:使用groovy console或shell可以方便的實驗groovy代碼
盡可能使用方法,而不是閉包。方法易于理解,也利于和java交互
在方法簽名中盡可能的使用確定的類型,便于代碼閱讀和IDE的錯誤檢測。在使用動態(tài)類型時要有清晰完善的文檔注釋
【編輯推薦】