Skip to content

Instantly share code, notes, and snippets.

@bhavanki
Created August 13, 2013 15:40
Show Gist options
  • Save bhavanki/6222497 to your computer and use it in GitHub Desktop.
Save bhavanki/6222497 to your computer and use it in GitHub Desktop.
Notes on transitioning from Java to Groovy

Notes on Groovy

Optional (Duck) Typing

class Duck {
  def walk() { println "I'm a duck, I walk" }
  def swim() { println "I'm a duck, I swim" }
  def quack() { println "QUACK" }
}
class Person {
  def walk() { println "I'm a person, I walk" }
  def swim() { println "I'm a person, I swim" }
  def quack() { println "QUACK, I say" }
}
def d = new Duck()
def p = new Person()
d.walk()  // OK, has method
p.walk()  // OK, has method

Collections and Maps

def things = ['Hello', 'Groovy', 'World']
def things = ['Hello', 'Groovy', 'World'] as Set
Set things = ['Hello', 'Groovy', 'World']
def colors = [red: 1, green: 2, blue: 3]
assert things[1] == 'Groovy'
assert things[-1] == 'World'
assert colors['red'] == 1
assert colors.green = 2

Plain Old Groovy Objects

class Thing {
  String name  // private field, public getter and setter
  int count  // ditto
}
def t = new Thing()
t.name = 'Bob'

class Thing {
  String name
  int count
  void setName(String name) {  // no getter now
    this.name = name
  }
}

class Thing {
  final String name  // no setter available
  Thing(String name) {
    this.name = name
  }
}

class Thing {
  private String name  // no setter unless you define it
  String getName() { name }
}

Closures

def hello = { println 'hello' }
hello.call()
hello()  // nicer
def printTheParam = { println it }  // default is one parameter
printTheParam('hello')
printTheParam 'hello'  // nicer
def printTheParam = { whatToPrint -> println whatToPrint }
def add = { int x, int y -> x + y }
def printNow = { -> println new Date() }

int i = 0
def c = { ->
  println "i: $i"  // can read i, in same scope as closure
  i++  // can write it too
} as Clickable  // has only one method, same parameter types, so coerce OK

import java.sql.Connection
def conn = [
  close: { -> println 'closed' },
  setAutoCommit: { boolean autoCommit -> println "$autoCommit" }
  // calling undefined methods => UnsupportedOperationException
] as Connection

class SomeClass {  // this class is the closure "owner", method calls go here
  void callClosure() {
    def dogAndCat = {
      woof()  // OK, in scope of closure
      meow()  // fail, not defined
    }
    dogAndCat()
  }
  void woof() { println 'Woof!' }
}

Terseness

class Person {
  String firstName
  String initial
  String lastName
  Integer age
}
// Map constructor if no others defined, uses setters
def author = new Person(firstName: 'Hunter', initial: 'S', lastName: 'Thompson')

def in = new FileInputStream('file.txt')  // try-catch of checked exceptions optional

if (nullobject) { println 'null == false' }
if (collectionNonEmpty) { println 'non-empty collection == true' }
if (arrayNonEmpty) { println 'ditto' }
if (mapNonEmpty) { println 'ditto' }
if (iter) { println 'iterator with stuff left == true' }
if (enumer) { println 'ditto' }
if (regex) { println 'matching regex pattern == true' }
if ("non-zero") { println 'true' }
if (1234) { println 'non-zero == true' }
if ('c') { println 'ditto' }

Semicolons are optional!

def easyReturn { 'return this string' }

def PublicClass {
  String publicField
  int publicMethod { 42 }
}  // private and protected still OK, use @PackageScope for that

println "parens optional except when no args"
def catted = StringUtils.concat("or when", "on RHS of assignment")

Auto-imports: java.lang, .io, .util, .net, groovy.lang, .util, BigDecimal, BigInteger

Differences from Java

String[] init = ['cannot', 'use', 'curly braces']
def init2 = ['must', 'define', 'list', 'and', 'cast'] as String[]
@Annotation(['same', 'for', 'annotation', 'array', 'values'])

for (bar in bars) { println 'in is a keyword' }
def warning = { println 'and so is def!' }

There is no do-while loop!

int count = someCalc()
for (int i = 0; i < count; i++) {
  println "Cannot initialize multiple variables in start of for loop")
}
someCalc().times { println "or just do this" }
for (i in 0..someCalc() - 1) { println "or this" }

assert result == "this"  // on objects, it's overloaded to call .equals
assert oneObject.is(sameObject)  // replaces ==
assert result?.equals("this")  // same overload

Object connection = createConnection()
connection.close()  // in Java, calls for Object always; in Groovy, calls based on runtime type

Strings

println 'Single quote strings are fine'
println '''Multi-line
works
too'''
def fullName = "$person.firstName ${person.initial ? person.initial + ' ' : ''}$person.lastName"  // GString
println """$person.firstName
$person.lastName"""  // double-quote multi-line

String str = 'Groovy Strings are groovy'
assert str[4] == 'v'  // 'v' is a string here, not a char
assert str[0..5] == 'Groovy'
assert str[19..-1] == 'groovy'
assert str[15..17] == 'are'
assert str[17..15] == 'era'

Static This

private static final Logger LOGGER = Logger.getLogger(this)  // class!

MOP

Important Methods on Groovy classes

Object invokeMethod(String name, Object args)
Object getProperty(String property)
void setProperty(String property, Object newValue)
MetaClass getMetaClass()
void setMetaClass(MetaClass metaClass)

Mucking with Methods and Properties

List.metaClass.removeRight = { int index ->
  delegate.remove(delegate.size() - 1 - index)
  // delegate is the instance that the closure is invoked on
}

class MathUtils {
  int add(int i, int j) { i + j }
  def invokeMethod(String name, Object args) {
    println "Undefined method $name"
  }
}

class Person {
  private String name  // no getters or setters outside class
  def getProperty(String property) {
    if ('name'.equals(property)) {
      return this.name
    }
  }  // now p.name works
  def setProperty(String property, Object newValue) {
    if ('name'.equals(property)) {
      this.name = newValue
    }
  }  // now p.name = 'you' works
}

class Person {
  String name
  def propertyMissing(String property) {
    if ('eman'.equals(property)) {
      return name.reverse()
    }
    throw new MissingPropertyException(property, getClass())
  }  // also $static_propertyMissing
  def methodMissing(String methodName, args) {
    if ('knight'.equals(methodName)) {
      name = 'Sir ' + name
      return
    }
    throw new MissingMethodException(methodName, getClass(), args)
  }  // also $static_methodMissing, which will also look for closures
}

New Operators

String name = person?.organization?.parent?.name  // totally null-safe
String name = person.name ?: defaultName  // abbreviated "Elvis"

def numbers = [1.4, 2.7, 3.1]
assert [1, 2, 3] == numbers*.intValue()  // call across collection "spread"
println (lastName <=> p?.lastName)  // -1, 0, or 1 "spaceship"
println p.@name  // direct field access, bypass getter
def things = ['a', 'b', 'b', 'c'] as Set  // type coercion
assert 1 in [1, 2, 5]  // shortcut for Collection.contains
def addMethod = new MathUtils().&add  // method reference => closure

Operator Overloading

class Person {
  String name
  List children = []
  def leftShift(Person child) {
    children << child
    this  // overload methods must return this
  }
}
def parent = new Person()
def child = new Person()
parent.children.add child  // or parent.children << child, neato
parent << child  // there's the overload
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment