March 8, 2015

10 most useful Groovy features (part 2 of 2)


I dedicate my first blog post ever to the programming language I do like the most: Groovy. Here, I’d like to share what in my opinion are the top 10 most useful features that JVM language offers (which Java doesn’t offer). Here goes part 2, make sure to check out the first part here.

6. AST transformations

In Groovy:
@TupleConstructor
@EqualsAndHashCode
@ToString(includePackage=false)
@Sortable // as of 2.4, does not work with fields, only properties
private static class Address {
    final String street
    final int number
}

public static String intoString() {
    return new Address("Main Road", 42).toString()
}

public static boolean equals() {
    return new Address("Main Road", 42) ==
        new Address("Main Road", 42)
}

public static List<String> sort() {
    return sort(new Address("Second Street", 1), new Address("Main Road", 43), 
        new Address("Main Road", 42))
}

public static List<String> sort(Object... input) {
    return input.sort()*.toString()
}
In Java:
private static class Address implements Comparable<Address>{
    private final String street;
    private final int number;

    public Address(String street, int number) {
        this.street = street;
        this.number = number;
    }

    public String getStreet() {
        return street;
    }

    public int getNumber() {
        return number;
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 29 * hash + Objects.hashCode(this.street);
        hash = 29 * hash + this.number;
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Address other = (Address) obj;
        if (!Objects.equals(this.street, other.street)) {
            return false;
        }
        if (this.number != other.number) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "AstGroovy$Address(" + street + ", " + number + ")";
    }

    @Override
    public int compareTo(Address obj) {
        int last = this.street.compareTo(obj.street);
        return last == 0 ? ((Integer)this.number).compareTo(obj.number) : last;
    }
}

public static String intoString() {
    return new Address("Main Road", 42).toString();
}

public static boolean equals() {
    return new Address("Main Road", 42).equals(new Address("Main Road", 42));
}

public static List<String> sort() {
    return AstGroovy.sort(new Object[]{
        new Address("Second Street", 1), 
        new Address("Main Road", 43), 
        new Address("Main Road", 42)});
}
AST transformations do well deserve their place in this top 10 list as they are true boilerplate-code-killers. Technically speaking, they provide a means to declaratively manipulate bytecode generation at compile time through annotations. What sounds very fancy at first is very simple in its application: Groovy comes with a bunch of built-in annotations, which, when applied to classes (or methods, or other artifacts, respectively) will manipulate the outcome of the compiled class. As these annotations have full access to the abstract syntax tree (AST), they can literally add, remove or replace any piece of code.

Although you could theoretically build your very own transformations, in practice, you will typically just rely on the 20+ highly useful AST annotations which you get out of the box. Most of them allow you to specify declaratively what would, in Java, take a considerable amount of boilerplate code.

In the example, we use some annotations to create a constructor, to implement equals() / hashCode(), toString(), and even the Comparable interface. Note also that as these modifications take place in the compile phase, their result is perfectly statically typesafe.

As you can see, this drastically decreases the amount of boilerplate code necessary to create similar outcome in pure Java. Of course, you would typically use your IDE to generate those methods, but then your code will still suffer from diminished maintainability due to the boated source code.

7. String interpolation

In Groovy:
public static String getString(String input) {
    return "Input String is $input with ${input.size()} characters."
}
In Java:
public static String getString(String input) {
    String pattern = "Input String is {0} with {1} characters.";
    return MessageFormat.format(pattern, input, input.length());
}
Even though Formatter provides some more convenient means to concatenate Strings in Java, that language still should have native support for String interpolation. Actually, any major language should have that.

In Groovy, again, this feature is built in (labeled as GString), and it does certainly increase code readability. Groovy also features additional syntactical sugar for String construction, such as escaped Strings and multiline Strings.

8. switch / “in” operator

In Groovy:
public static String switchCase(Object input) {
    switch (input) {
        case "S":
            return "It's the String 'S'."
        case {it in String && (it as String).startsWith('S')}:
            return "It starts with 'S'."
        case ~/^[A-Z].*/:
            return "It starts in upper case."
        case String:
            return "It's a String."
        case Integer:
            return "It's an Integer."
        default:
            return "It's anything else."
    }
}
In Java:
public static String switchCase(Object input) {
    if (input.equals("S")) {
        return "It's the String 'S'.";
    }
    if (input instanceof String) {
        String stringInput = (String) input;
        if (stringInput.startsWith("S")) {
            return "It starts with 'S'.";
        }
        if (stringInput.matches("^[A-Z].*")) {
            return "It starts in upper case.";
        }
        return "It's a String.";
    }
    if (input instanceof Integer) {
        return "It's an Integer.";
    }
    return "It's anything else.";
}
In Java, the switch/case statement is the most concise way to centrally specify a case differentiation. However, as the switch candidate is limited to enums or Strings (as of Java 8), the actual benefit of that construct is drastically limited.

In Groovy, on the other hand, the switch is performed by applying the in operator on the candidate for every case branch. Now, said in operator is predefined for the most commonly used JDK types such as String, collections, and others, to evaluate to some kind of “affiliation” query; e.g. when queried with a collection, it is evaluated whether the candidate is an element of the collection, when queried with a class, it is evaluated whether the candidate is of that class, when queried with a closure, it is evaluated whether the closure, when called with the candidate as its argument, returns true, and so on.

As you can imagine, this transforms the switch/case statement in Groovy from an elegant, but mostly useless structure into a structure which is actually both elegant and very powerful. It’s actually that powerful that one might consider an else if statement in Groovya code smell because you can typically make the implementation of even some sophisticated conditional statements much more readable by using a single switch/case statement. Plus, you are free to override the in operation for any of your classes. Can we have that in Java, please?

9. GPath XML / JSON parser

We use an excerpt from the Maven project pom.xml as an example XML input:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>ch.codebulb</groupId>
    <artifactId>Top10GroovyFeatures</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>2.4.0</version>
        </dependency>
    </dependencies>
    <name>Top10GroovyFeatures</name>
</project>
In Groovy:
@CompileDynamic
public static String parseJunitScope() {
    def project = new XmlSlurper().parseText(XML_INPUT)
    return project.dependencies.dependency.find {it.groupId == "junit"}.scope
}
In Java:
public static String parseJunitScope() {
    try {
        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
        Document doc = dBuilder.parse(new InputSource(new StringReader(XML_INPUT)));
        doc.getDocumentElement().normalize();
        
        XPathFactory xPathFactory = XPathFactory.newInstance();
        XPath xPath = xPathFactory.newXPath();
        XPathExpression xPathExp = xPath.compile("/project/dependencies/dependency[groupId='junit']/scope");
        return (String) xPathExp.evaluate(doc, XPathConstants.STRING);
        
    } catch (ParserConfigurationException | SAXException | IOException | XPathExpressionException ex) {
        throw new RuntimeException(ex);
    }
}
That meme on the right pretty much summarizes it. XML / JSON parsing in Groovy means no boilerplate code, and straight XPath node selection instead, thanks to the clever combination of native XPath support (in the so-called “GPath” syntax) and node selection through closures on Iterable node collections.

This syntax works for both XML and JSON whereas it’s worth noting that as of version 2.3, Groovy features one of the fastest JSON parsers on the JVM.

So, even though it’s not every day  you parse XML, this clearly is a potential Java-killer when it comes to it, thus deserving its place in this top Groovy feature list.

10. Spread-dot-operator

In Groovy:
protected static final List<String> INPUT_LIST = ["a", "b", "c"].asImmutable()

public static List<String> spreadDot() {
    return INPUT_LIST*.toUpperCase()
}
In Java:
private static final List<String> INPUT_LIST = SpreadDotGroovy.INPUT_LIST;
        
public static List<String> spreadDot() {
    return INPUT_LIST.stream().map(String::toUpperCase).collect(Collectors.toList());
}
Sure, it’s a tiny feature (you could actually take it as syntactic sugar for Iterable’s collect() method), but it’s very helpful when trying to keep code clear and concise. Again, this works in static compilation, too, which makes one wonder why Java misses that useful little operator.

Thus, I added this one particular feature to that list really due to its global applicability and wide-spread usefulness.

Conclusion

Groovy introduces a vast amount of additional functionality and syntax whilst still resembling close to its Java roots. For me, Groovy’s greatest achievement is that it both extends and, at the same time, simplifies Java-like programming as does no other JVM language, which provides a smooth learning curve as well as a gradual migration path for Java projects.

Here, I presented ten of my favorite Groovy features in very loose graduation, and still, of course, that list could go on and on. Especially the list’s last item has been the toughest choice as there are just so many more features which could make it to that list because they are just about equally as helpful, depending on your project and use case. Examples include syntactic simplifications such as omitting the end-of-line colon, default imports, default visibility, == equals, but also things like operator overloading, ranges and builders.

Now this article has quite turned into a “Groovy vs. Java” competition, which clearly was biased towards Groovy. But please don’t get me wrong! Java still is a very useful language, and it shines especially in environments with strong robustness / performance criteria. After all, Groovy has never been designed as a Java replacement, but rather, as an complementary technology. However, it would be ignorant not to carefully examine which technology best fits any given project, thus introducing Groovy for where it really shines. I firmly believe that the future of JVM programming is of a polymorph nature, where multiple languages are used in conjunction, each one bringing in its own strength.

What do you think about this list? Do you agree with my “top Groovy feature” rating? Please let me know which features you missed on my list, and list your own favorite Grooyv features in the comment section below.

If you are interested in the details of the code samples used in this article, you can check them out on GitHub alongside JUnit equality tests.

Updated June 7, 2015: Included a reference to my new blog post about Java 8 lambdas vs. Groovy closures.


Pages: 1 2

No comments:

Post a Comment