blob: 47b5bfd9a206d7b0392bf1f851f41a11991f3596 [file]
//////////////////////////////////////////
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
//////////////////////////////////////////
ifndef::core-semantics[]
:core-semantics: core-semantics.adoc
endif::[]
ifndef::core-syntax[]
:core-syntax: core-syntax.adoc
endif::[]
= Differences with Java
Groovy tries to be as natural as possible for Java developers. We’ve
tried to follow the principle of least surprise when designing Groovy,
particularly for developers learning Groovy who’ve come from a Java
background.
Here we list all the major differences between Java and Groovy.
== Default imports
All these packages and classes are imported by default, i.e. you do not
have to use an explicit `import` statement to use them:
* java.io.*
* java.lang.*
* java.math.BigDecimal
* java.math.BigInteger
* java.net.*
* java.time.*
* java.util.*
* groovy.lang.*
* groovy.util.*
== Multi-methods
In Groovy, the methods which will be invoked are chosen at runtime. This is called runtime dispatch or multi-methods. It
means that the method will be chosen based on the types of the arguments at runtime. In Java, this is the opposite: methods
are chosen at compile time, based on the declared types.
The following code, written as Java code, can be compiled in both Java and Groovy, but it will behave differently:
[source,java]
----
include::../test/DifferencesFromJavaTest.groovy[tags=multimethods,indent=0]
----
In Java, you would have:
[source,java]
----
include::../test/DifferencesFromJavaTest.groovy[tags=multimethods_java,indent=0]
----
Whereas in Groovy:
[source,java]
----
include::../test/DifferencesFromJavaTest.groovy[tags=multimethods_groovy,indent=0]
----
That is because Java will use the static information type, which is that `o` is declared as an `Object`, whereas
Groovy will choose at runtime, when the method is actually called. Since it is called with a `String`, then the
`String` version is called.
== Array initializers
In Java, array initializers take either of these two forms:
[source,java]
----
include::../test/DifferencesFromJavaTest.groovy[tags=arraycreate_fail,indent=0]
----
In Groovy, the `{ ... }` block is reserved for closures.
That means that you cannot create array literals using Java's array initializer shorthand syntax.
You instead borrow Groovy's literal list notation like this:
[source,groovy]
----
include::../test/DifferencesFromJavaTest.groovy[tags=arraycreate_success,indent=0]
----
For Groovy 3+, you can optionally use the Java's array initializer long syntax:
[source,groovy]
----
include::../test/DifferencesFromJavaTest.groovy[tags=arraycreate3_success,indent=0]
----
== Package scope visibility
In Groovy, omitting a modifier on a field doesn't result in a package-private field like in Java:
[source,groovy]
----
include::../test/DifferencesFromJavaTest.groovy[tags=packageprivate_property,indent=0]
----
Instead, it is used to create a _property_, that is to say a _private field_, an associated _getter_ and an associated
_setter_.
It is possible to create a package-private field by annotating it with `@PackageScope`:
[source,groovy]
----
include::../test/DifferencesFromJavaTest.groovy[tags=packageprivate_field,indent=0]
----
== ARM blocks
Java 7 introduced ARM (Automatic Resource Management) blocks (also know as try-with-resources) blocks like this:
[source,java]
----
Path file = Paths.get("/path/to/file");
Charset charset = Charset.forName("UTF-8");
try (BufferedReader reader = Files.newBufferedReader(file, charset)) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
----
Such blocks are supported from Groovy 3+.
However, Groovy provides various methods relying on closures, which have the same effect while being more idiomatic. For example:
[source,groovy]
----
new File('/path/to/file').eachLine('UTF-8') {
println it
}
----
or, if you want a version closer to Java:
[source,groovy]
----
new File('/path/to/file').withReader('UTF-8') { reader ->
reader.eachLine {
println it
}
}
----
== Inner classes
WARNING: The implementation of anonymous inner classes and nested classes follow Java closely,
but there are some differences, e.g.
local variables accessed from within such classes don't have to be final.
We piggyback on some implementation details we use for `groovy.lang.Closure`
when generating inner class bytecode.
=== Static inner classes
Here's an example of static inner class:
[source,groovy]
---------------------
include::../test/DifferencesFromJavaTest.groovy[tags=innerclass_1,indent=0]
---------------------
The usage of static inner classes is the best supported one. If you
absolutely need an inner class, you should make it a static one.
=== Anonymous Inner Classes
[source,groovy]
---------------------
include::../test/DifferencesFromJavaTest.groovy[tags=innerclass_2,indent=0]
---------------------
=== Creating Instances of Non-Static Inner Classes
In Java you can do this:
[source,java]
----------------------------------
include::../test/DifferencesFromJavaTest.groovy[tags=innerclass_3_java,indent=0]
----------------------------------
Before 3.0.0, Groovy doesn't support the `y.new X()` syntax. Instead, you have to write `new X(y)`, like in the code below:
[source,groovy]
----------------------------------
include::../test/DifferencesFromJavaTest.groovy[tags=innerclass_3,indent=0]
----------------------------------
[WARNING]
Caution though, Groovy supports calling methods with one
parameter without giving an argument. The parameter will then have the
value null. Basically the same rules apply to calling a constructor.
There is a danger that you will write new X() instead of new X(this) for
example. Since this might also be the regular way we have not yet found
a good way to prevent this problem.
[NOTE]
Groovy 3.0.0 supports Java style syntax for creating instances of non-static inner classes.
== Lambda expressions and the method reference operator
Java 8+ supports lambda expressions and the method reference operator (`::`):
[source,java]
----
Runnable run = () -> System.out.println("Run"); // Java
list.forEach(System.out::println);
----
Groovy 3 and above also support these within the Parrot parser.
In earlier versions of Groovy you should use closures instead:
[source,groovy]
----
Runnable run = { println 'run' }
list.each { println it } // or list.each(this.&println)
----
Since Groovy 6.0, intersection-type casts (https://docs.oracle.com/javase/specs/jls/se21/html/jls-15.html#jls-15.16[JLS §15.16])
are supported on lambdas and method references, so a lambda can opt into `Serializable`
or other marker interfaces, just like in Java:
[source,groovy]
----
Runnable r = (Runnable & Serializable) () -> println('hi') // serializable lambda
----
Groovy additionally accepts the `as` form, where parentheses are required around
the intersection:
[source,groovy]
----
def r = { -> println 'hi' } as (Runnable & Serializable)
----
See the <<{core-semantics}#intersection-cast,Intersection-type cast and coercion>> section in the semantics guide.
== GStrings
As double-quoted string literals are interpreted as `GString` values, Groovy may fail
with compile error or produce subtly different code if a class with `String` literal
containing a dollar character is compiled with Groovy and Java compiler.
While typically, Groovy will auto-cast between `GString` and `String` if an API declares
the type of a parameter, beware of Java APIs that accept an `Object` parameter and then
check the actual type.
== String and Character literals
Singly-quoted literals in Groovy are used for `String`, and double-quoted result in
`String` or `GString`, depending whether there is interpolation in the literal.
[source,groovy]
----
include::../test/DifferencesFromJavaTest.groovy[tags=type_depends_on_quoting_AND_whether_we_actually_interpolate,indent=0]
----
Groovy will automatically cast a single-character `String` to `char` only when assigning to
a variable of type `char`. When calling methods with arguments of type `char` we need
to either cast explicitly or make sure the value has been cast in advance.
[source,groovy]
----
include::../test/DifferencesFromJavaTest.groovy[tags=single_char_strings_are_autocasted,indent=0]
----
Groovy supports two styles of casting and in the case of casting to `char` there
are subtle differences when casting a multi-char strings. The Groovy style cast is
more lenient and will take the first character, while the C-style cast will fail
with exception.
[source,groovy]
----
include::../test/DifferencesFromJavaTest.groovy[tags=chars_c_vs_groovy_cast,indent=0]
----
== Behaviour of `==`
In Java, `==` means equality of primitive types or identity for objects.
In Groovy, `==` means equality in all places.
For non-primitives, it translates to `a.compareTo(b) == 0`,
when evaluating equality for `Comparable` objects,
and `a.equals(b)` otherwise.
To check for identity (reference equality), use the `is` method: `a.is(b)`.
From Groovy 3, you can also use the `===` operator (or negated version): `a === b` (or `c !== d`).
== Primitives and wrappers
In a pure object-oriented language, everything would be an object.
Java takes the stance that primitive types, such as int, boolean and double,
are used very frequently and worthy of special treatment.
Primitives can be efficiently stored and manipulated but can't be used in all contexts where an object could be used.
Luckily, Java auto boxes and unboxes primitives when they are passed as parameters or used as return types:
[source,java]
----
public class Main { // Java
float f1 = 1.0f;
Float f2 = 2.0f;
float add(Float a1, float a2) { return a1 + a2; }
Float calc() { return add(f1, f2); } // <1>
public static void main(String[] args) {
Float calcResult = new Main().calc();
System.out.println(calcResult); // => 3.0
}
}
----
<1> The `add` method expects wrapper then primitive type arguments,
but we are supplying parameters with a primitive then wrapper type.
Similarly, the return type from `add` is primitive, but we need the wrapper type.
Groovy does the same:
[source,groovy]
----
class Main {
float f1 = 1.0f
Float f2 = 2.0f
float add(Float a1, float a2) { a1 + a2 }
Float calc() { add(f1, f2) }
}
assert new Main().calc() == 3.0
----
Groovy, also supports primitives and object types, however, it goes a little further
in pushing OO purity; it tries hard to treat _everything_ as an object.
Any primitive typed variable or field can be treated like an object, and it
will be link:core-object-orientation.html#_primitive_types[auto-wrapped] as needed.
While primitive types might be used under the covers,
their use should be indistinguishable from normal object use whenever possible,
and they will be boxed/unboxed as needed.
Here is a little example using Java trying to (incorrectly for Java) dereference a primitive `float`:
[source,java]
----
public class Main { // Java
public float z1 = 0.0f;
public static void main(String[] args){
new Main().z1.equals(1.0f); // DOESN'T COMPILE, error: float cannot be dereferenced
}
}
----
The same example using Groovy compiles and runs successfully:
[source,groovy]
----
class Main {
float z1 = 0.0f
}
assert !(new Main().z1.equals(1.0f))
----
Because of Groovy's additional use of un/boxing, it does not follow
Java's behavior of widening taking priority over boxing.
Here's an example using `int`
[source,groovy]
----
include::../test/PrimitiveTest.groovy[tags=widening_vs_boxing,indent=0]
----
<1> This is the method that Java would call, since widening has precedence over unboxing.
<2> This is the method Groovy actually calls, since all primitive references use their wrapper class.
[[primitive-optimisation-numbers]]
=== Numeric Primitive Optimisation with `@CompileStatic`
Since Groovy converts to wrapper classes in more places, you might wonder
whether it produces less efficient bytecode for numeric expressions.
Groovy has a highly optimised set of classes for doing math computations.
When using `@CompileStatic`, expressions involving only primitives
uses the same bytecode that Java would use.
[[zero-edge-case]]
=== Positive/Negative zero edge case
Java float/double operations for both primitives and wrapper classes follow the IEEE 754 standard
but there is an interesting edge case involving positive and negative zero.
The standard supports distinguishing between these two cases and while in many scenarios
programmers may not care about the difference, in some mathematical or data science scenarios
it is important to cater for the distinction.
For primitives, Java maps down onto a special
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.dcmp_op[bytecode instruction]
when comparing such values which has the property that
"Positive zero and negative zero are considered equal".
[source,ruby]
----
jshell> float f1 = 0.0f
f1 ==> 0.0
jshell> float f2 = -0.0f
f2 ==> -0.0
jshell> f1 == f2
$3 ==> true
----
For the wrapper classes, e.g. jdk:java.base/java.lang.Float#equals(java.lang.Object)[Float#equals],
the result is `false` for this same case.
[source,ruby]
----
jshell> Float f1 = 0.0f
f1 ==> 0.0
jshell> Float f2 = -0.0f
f2 ==> -0.0
jshell> f1.equals(f2)
$3 ==> false
----
Groovy on the one hand tries to follow Java behavior closely, but on the other
switches automatically between primitives and wrapped equivalents in more places.
To avoid confusion we recommend the following guidelines:
* If you wish to distinguish between positive and negative zero, use the `equals`
method directly or cast any primitives to their wrapper equivalent before using `==`.
* If you wish to ignore the difference between positive and negative zero, use the `equalsIgnoreZeroSign`
method directly or cast any non-primitives to their primitive equivalent before using `==`.
These guidelines are illustrated in the following example:
[source,groovy]
----
include::../test/PrimitiveTest.groovy[tags=pos_neg_zero,indent=0]
----
<1> Recall that for non-primitives, `==` maps to `.equals()`
== Conversions
Java does automatic widening and narrowing
https://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html[conversions].
[cols=">2s,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a"]
.Java Conversions
|============================================================================================
| 8+|*Converts to*
| Converts from | *boolean* | *byte* | *short* | *char* | *int* | *long* | *float* | *double*
| boolean | - | N | N | N | N | N | N | N
| byte | N | - | Y | C | Y | Y | Y | Y
| short | N | C | - | C | Y | Y | Y | Y
| char | N | C | C | - | Y | Y | Y | Y
| int | N | C | C | C | - | Y | T | Y
| long | N | C | C | C | C | - | T | T
| float | N | C | C | C | C | C | - | Y
| double | N | C | C | C | C | C | C | -
|============================================================================================
* 'Y' indicates a conversion Java can make +
* 'C' indicates a conversion Java can make when there is an explicit cast +
* 'T` indicates a conversion Java can make but data is truncated +
* 'N' indicates a conversion Java can't make
Groovy expands greatly on this.
[%autowidth]
[cols=">2s,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a,^1a"]
.Groovy Conversions
|=================================================================================================================================================================================================================
| 18+|*Converts to*
| Converts from |[.rotate]`boolean` |[.rotate]`Boolean` |[.rotate]`byte` |[.rotate]`Byte` |[.rotate]`short` |[.rotate]`Short` |[.rotate]`char` |[.rotate]`Character` |[.rotate]`int` |[.rotate]`Integer` |[.rotate]`long` |[.rotate]`Long` |[.rotate]`BigInteger` |[.rotate]`float` |[.rotate]`Float` |[.rotate]`double` |[.rotate]`Double` |[.rotate]`BigDecimal`
| `boolean` | - | B | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N
| `Boolean` | B | - | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N | N
| `byte` | T | T | - | B | Y | Y | Y | D | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y
| `Byte` | T | T | B | - | Y | Y | Y | D | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y
| `short` | T | T | D | D | - | B | Y | D | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y
| `Short` | T | T | D | T | B | - | Y | D | Y | Y | Y | Y | Y | Y | Y | Y | Y | Y
| `char` | T | T | Y | D | Y | D | - | D | Y | D | Y | D | D | Y | D | Y | D | D
| `Character` | T | T | D | D | D | D | D | - | D | D | D | D | D | D | D | D | D | D
| `int` | T | T | D | D | D | D | Y | D | - | B | Y | Y | Y | Y | Y | Y | Y | Y
| `Integer` | T | T | D | D | D | D | Y | D | B | - | Y | Y | Y | Y | Y | Y | Y | Y
| `long` | T | T | D | D | D | D | Y | D | D | D | - | B | Y | T | T | T | T | Y
| `Long` | T | T | D | D | D | T | Y | D | D | T | B | - | Y | T | T | T | T | Y
| `BigInteger` | T | T | D | D | D | D | D | D | D | D | D | D | - | D | D | D | D | T
| `float` | T | T | D | D | D | D | T | D | D | D | D | D | D | - | B | Y | Y | Y
| `Float` | T | T | D | T | D | T | T | D | D | T | D | T | D | B | - | Y | Y | Y
| `double` | T | T | D | D | D | D | T | D | D | D | D | D | D | D | D | - | B | Y
| `Double` | T | T | D | T | D | T | T | D | D | T | D | T | D | D | T | B | - | Y
| `BigDecimal` | T | T | D | D | D | D | D | D | D | D | D | D | D | T | D | T | D | -
|=================================================================================================================================================================================================================
* 'Y' indicates a conversion Groovy can make +
* 'D' indicates a conversion Groovy can make when compiled dynamically or explicitly cast +
* 'T' indicates a conversion Groovy can make but data is truncated +
* 'B' indicates a boxing/unboxing operation +
* 'N' indicates a conversion Groovy can't make.
The truncation uses <<{core-semantics}#the-groovy-truth,Groovy Truth>> when converting to `boolean`/`Boolean`. Converting
from a number to a character casts the `Number.intvalue()` to `char`. Groovy constructs `BigInteger` and `BigDecimal`
using `Number.doubleValue()` when converting from a `Float` or `Double`, otherwise it constructs using `toString()`.
Other conversions have their behavior defined by `java.lang.Number`.
== Extra keywords
Groovy has many of the same keywords as Java and Groovy 3 and above also has the same `var` reserved type as Java.
In addition, Groovy has the following keywords:
* `as`
* `def`
* `in`
* `trait`
* `it` // within closures
Groovy is less stringent than Java in that it allows some keywords to appear in places that would be illegal in Java,
e.g. the following is valid: `var var = [def: 1, as: 2, in: 3, trait: 4]`.
Never-the-less, you are discouraged from using the above keywords in places that might cause confusion even when
the compiler might be happy. In particular, avoid using them for variable, method and class names,
so our previous `var var` example would be considered poor style.
Additional documentation is available for <<{core-syntax}#_keywords,keywords>>.