Effective Java with Groovy - How
language can Influence Good Practices
Naresha K, Technical Excellence Coach | Consultant |
Agile & Cloud Transformation Catalyst
@naresha_k
https://blog.nareshak.com/
About Me
http://nareshak.blogspot.com/
http://nareshak.blogspot.com/
!7
https://www.flickr.com/photos/hchalkley/30724738
!8
Data
Information
Knowledge
Wisdom
!9
https://www.flickr.com/photos/drauh/2456223489
!10
!11http://radio-weblogs.com/0112098/2003/08/29.html
initial idea was to make a little dynamic language which
compiles directly to Java classes and provides all the nice
(alleged) productivity benefits
- James Strachan
Know Your Path
The problem/ Context
What does ‘Effective Java’ say?
The traps
Groovier Solution
Lessons Learned
!12
Tool Wisdom
!13
https://www.flickr.com/photos/twid/410697715
!14
class Product {
Long id
String code
String name
BigDecimal price
static constraints = {
code unique: true
}
}
P #1
!15
def actionE1(){
Product p = new Product(code: 'P101',
name: 'Effective Java', price: 499.00)
p.save(flush: true)
render p
}
def actionE2(){
Product p = new Product(code: 'P101',
name: 'Effective Java', price: 499.00)
Product p2 = Product.findByCode('P101')
render p == p2
}
false
P #2
!16
def actionH1(){
Product p = new Product(code: 'P102',
name: 'Programming Groovy 2', price: 2217.78)
p.save(flush: true)
def stock = [:]
stock[p] = 100
session.setAttribute('stock', stock)
render stock
}
def actionH2(){
def stock = session.getAttribute('stock')
println stock
Product p = new Product(code: 'P102',
name: 'Programming Groovy 2', price: 2217.78)
render stock[p]
}
null
!17
!18
#8: Obey the general contract when
overriding equals
#9: Always override hashCode when
you override equals
!19
import groovy.transform.EqualsAndHashCode
@EqualsAndHashCode(includes='id')
class Product {
Long id
String code
String name
BigDecimal price
static constraints = {
code unique: true
}
}
!20
import groovy.transform.EqualsAndHashCode
@EqualsAndHashCode(includes=‘code')
class Product {
Long id
String code
String name
BigDecimal price
static constraints = {
code unique: true
}
}
P #1
!21
def actionE1(){
Product p = new Product(code: 'P101',
name: 'Effective Java', price: 499.00)
p.save(flush: true)
render p
}
def actionE2(){
Product p = new Product(code: 'P101',
name: 'Effective Java', price: 499.00)
Product p2 = Product.findByCode('P101')
render p == p2
}
true
P #2
!22
def actionH1(){
Product p = new Product(code: 'P102',
name: 'Programming Groovy 2', price: 2217.78)
p.save(flush: true)
def stock = [:]
stock[p] = 100
session.setAttribute('stock', stock)
render stock
}
def actionH2(){
def stock = session.getAttribute('stock')
println stock
Product p = new Product(code: 'P102',
name: 'Programming Groovy 2', price: 2217.78)
render stock[p]
}
100
!23
AST Transformation
Single point of representation for any
knowledge
!24
List<Integer> numbers = Arrays.asList(10, 20, 30, 40);
for(int i=0; i<numbers.size(); i++){
System.out.println(numbers.get(i));
}
!25
http://upload.wikimedia.org/wikipedia/commons/8/82/Cable_closet_bh.jpg
!26
#46: Prefer for-each loops to
traditional for loops
!27
def numbers = [10, 20, 30, 40]
def sum = 0
for(int number in numbers){
sum += number
}
println "Sum: " + sum
!28
numbers.each{ number->
println number
}
println numbers.collect { number ->
number * 2
}
println numbers.inject(0,
{result, number -> result + number }
)
10
20
30
40
[20, 40, 60, 80]
100
!29
Closures
Favor Internal iterators to external iterators
Minimize the moving parts in your code
!30
public static void main( String[] args ) {
float price = 0.1f;
float total = 0;
for(int i=0; i<10; i++){
total += price;
}
System.out.println("Total: " + total);
}
Total: 1.0000001
!31
public static void main( String[] args ) {
double price = 0.1;
double total = 0;
for(int i=0; i<10; i++){
total += price;
}
System.out.println("Total: " + total);
}
Total: 0.9999999999999999
!32
#48: Avoid float and double if exact
answers are required
!33
public static void main( String[] args ) {
BigDecimal price = new BigDecimal(0.1);
BigDecimal total = new BigDecimal(0);
for(int i=0; i<10; i++){
total = total.add(price);
}
System.out.println("Total: " + total);
}
Total: 1.000000000000000055511151231257827021181
5834045410156250
!34
public static void main( String[] args ) {
BigDecimal price = new BigDecimal(“0.1”);
BigDecimal total = new BigDecimal(0);
for(int i=0; i<10; i++){
total = total.add(price);
}
System.out.println("Total: " + total);
}
Total: 1.0
!35
def price = 0.1
def total = 0
10.times {
total += price
}
println "Total: " + total
Total: 1.0
!36
Principle Of Least Astonishment
Select appropriate default
!37
[1, 3, 4, 8, 16, 9]
•Find all even numbers
•Find all odd numbers
•Find numbers divisible by 4
•Find numbers greater than 5
!38
#21: Use function objects to
represent strategies
!39
interface Filter{
def includes(number)
}
class OddFilter implements Filter{
def includes(number){ number % 2 != 0 }
}
class DivisibleBy4Filter implements Filter{
def includes(number){ number % 4 == 0 }
}
def doFilter(numbers, Filter filter){
def filteredValues = []
for(int number in numbers){
if(filter.includes(number)){
filteredValues << number
}
}
filteredValues
}
println doFilter(numbers, new OddFilter())
println doFilter(numbers, new DivisibleBy4Filter())
!40
def divisibleBy4 = { number ->
number % 4 == 0
}
def odd = { number ->
number % 2 != 0
}
def numbers = [1, 3, 4, 8, 16, 9]
println "Odd numbers: " + numbers.findAll(odd)
println "Div by 4: " + numbers.findAll(divisibleBy4)
Odd numbers: [1, 3, 9]
Div by 4: [4, 8, 16]
!41
Closures
No boilerplate code
polymorphic code reading skill not required
Less duplicate code (potentially)
Bring in Order
!42
@ToString
class Person {
String name
int age
}
def geeks = [
new Person(name: 'Arun', age: 35),
new Person(name: 'Raj', age: 30),
new Person(name: 'Abhi', age: 35),
new Person(name:'Kumar', age: 32),
new Person(name: 'Kumar', age: 25)
]
!43
#12: Consider implementing
Comparable
!44
println 10 <=> 20
println 20 <=> 10
println 10 <=> 10
-1
1
0
!45
@ToString
class Person implements Comparable<Person>{
String name
int age
int compareTo(Person other){
name <=> other.name
}
}
Default Sort Order
!46
println geeks.sort(false)
Original:
[
Person(Arun, 35),
Person(Raj, 30),
Person(Abhi, 35),
Person(Kumar, 32),
Person(Kumar, 25)
]
Sorted:
[
Person(Abhi, 35),
Person(Arun, 35),
Person(Kumar, 32),
Person(Kumar, 25),
Person(Raj, 30)
]
Sort by Age
!47
println geeks.sort(false) { a, b -> a.age <=> b.age}
Original:
[
Person(Arun, 35),
Person(Raj, 30),
Person(Abhi, 35),
Person(Kumar, 32),
Person(Kumar, 25)
]
Sorted:
[
Person(Kumar, 25),
Person(Raj, 30),
Person(Kumar, 32),
Person(Arun, 35),
Person(Abhi, 35)
]
Sort by age, name
!48
int compareTo(Person other) {
if (this.is(other)) {
return 0
}
java.lang.Integer value = 0
value = this .name <=> other .name
if ( value != 0) {
return value
}
value = this .age <=> other .age
if ( value != 0) {
return value
}
return 0
}
Sort by age, name
!49
println geeks.sort(false, { a, b ->
[{it.age}, {it.name}].findResult { c ->
c(a) <=> c(b) ?: null
}
})
Original:
[
Person(Arun, 35),
Person(Raj, 30),
Person(Abhi, 35),
Person(Kumar, 32),
Person(Kumar, 25)
]
Sorted:
[
Person(Kumar, 25),
Person(Raj, 30),
Person(Kumar, 32),
Person(Abhi, 35),
Person(Arun, 35)
]
Sort by age, name
!50
Original:
[
Person(Arun, 35),
Person(Raj, 30),
Person(Abhi, 35),
Person(Kumar, 32),
Person(Kumar, 25)
]
Sorted:
[
Person(Kumar, 25),
Person(Raj, 30),
Person(Kumar, 32),
Person(Abhi, 35),
Person(Arun, 35)
]
@Sortable(includes = "name, age")
@ToString
class Person {
String name
int age
}
!51
Syntactic Sugar - Spaceship Operator
Simplify common tasks
Million Dollar Effort?
!52
?
Million Dollar Effort !!!
!53
null
!54
def List<Speaker> getSpeakers(String conference){
null
}
def gidsSpeakers = getSpeakers('GIDS19')
if(gidsSpeakers != null){
}
!55
#43: Return empty arrays or
collections, not nulls
!56
def numbers = [2, 3, 1, 4]
println numbers.collect { it + 1} [3, 4, 2, 5]
numbers = []
println numbers.collect { it + 1} []
println numbers
.findAll { it % 2 == 0}
.collect { it * 2}
.sum()
!57
println null.collect { it } []
println null.collect { it }.sum() null
Caught: java.lang.NullPointerException: Cannot invoke method sum() on null object
println null.collect { it }.sum()
println null.sum()
!58
!59
NullObject
No boilerplate code
Life is too short for null checks
!60
!61
#3: Enforce the singleton property
with a private constructor
or an enum type
!62
class Manager{
private static final Manager manager =
new Manager()
private Manager(){ super() }
static Manager getInstance(){ manager }
}
def m1 = Manager.getInstance()
def m2 = Manager.getInstance()
println m1 == m2 true
!63
class Manager{
private static final Manager manager =
new Manager()
private Manager(){ super() }
static Manager getInstance(){ manager }
}
def m = new Manager()
println m Manager@12e61fe6
!64
@Singleton
class Manager{
}
def m1 = Manager.getInstance()
def m2 = Manager.getInstance()
println m1 == m2
def m = new Manager()
println m
Caught: java.lang.RuntimeException: Can't instantiate singleton Manager.
Use Manager.instance
!65
private static Manager manager
static Manager getInstance(){
if(!manager){ manager = new Manager() }
manager
}
!66
private static Manager manager
static synchronized Manager getInstance(){
if(!manager){ manager = new Manager() }
manager
}
!67
@Singleton(lazy=true)
class Manager{
}
println Manager.@instance
def m1 = Manager.getInstance()
println Manager.@instance
null
Manager@12e61fe6
!68
@Singleton(lazy=true)
class Manager{
}
println Manager.@instance
def m1 = Manager.getInstance()
println Manager.@instance
null
Manager@12e61fe6
!69
!70
private static volatile Manager instance
public static Manager getInstance() {
if ( instance != null) {
return instance
} else {
synchronized (Manager) {
if ( instance != null) {
return instance
} else {
return instance = new Manager()
}
}
}
}
!71
YAGNI
Premature optimisation is the root of all evil
Don’t punish the punctual
AST Transformation
!72
https://www.flickr.com/photos/38080114@N07/8594601982/
!73
#15: Minimize Mutability
Rules to make a class mutable
• Don’t provide any mutators
• Ensure that the class can’t be extended
• Make all fields final
• Make all fields private
• Ensure exclusive access to any mutable components
!74
!75
class ImmutableClass{
private final def field1
private final def field2
//...
private final def field10
public ImmutableClass(f1, f2,… f10){
//initialization
}
}
!76
import groovy.transform.Immutable
@Immutable
class Rectangle{
int length
int breadth
}
def r = new Rectangle(length: 10, breadth: 5)
println r // Rectangle(10, 5)
!77
Code
!78
public Rectangle(java.util.HashMap args) {
metaClass = /*BytecodeExpression*/
if ( args .length == null) {
} else {
this .length = (( args .length) as int)
}
if ( args .breadth == null) {
} else {
this .breadth = (( args .breadth) as int)
}
}
!79
Syntactic Sugar
Readability Matters
AST Transformation
!80
!81
#15: Favour composition over
inheritance
!82
class PhoneNumbers{
private @Delegate List phoneNumbers
PhoneNumbers(numbers){
phoneNumbers = numbers
}
def bengaluruNumbers(){
phoneNumbers.findAll { it.startsWith('80') }
}
}
!83
def ph = ['9812312345', '9812312346', '802312347'] as
PhoneNumbers
println ph
println ph.find { it == '9812312345'}
println ph.find { it.endsWith('6') }
println ph.bengaluruNumbers()
Traits
trait CanSing {
def sing() {
println "Singing"
}
}
trait CanDance {
def dance() {
println "Dancing"
}
}
class Person implements CanSing, CanDance {}
Person reema = new Person()
reema.sing()
reema.dance()
!85
AST Transformation
Simplify
Traits
Take Away
Some of the ‘Effective Java’ already built into
the language
AST Transformations reduce the effort to
implement few more
Effective Java implementations may not always be
effective Groovy implementations (Traps)
!86
Take Away
Programming languages can reduce the friction to
implement good practices
!87
Thank You

Effective Java with Groovy - How Language can Influence Good Practices