Interoperation with Java
Ceylon is designed to execute in a virtual machine environment (loosely speaking, in an environment with built-in garbage collection), but it doesn't have its own native virtual machine. Instead, it "borrows" a virtual machine designed for some other language. For now the options are:
- the Java Virtual Machine, or
- a JavaScript virtual machine.
In this chapter, we're going to learn about how to interoperate with native code running on the Java Virtual Machine, and focus on all the nitty-gritty details and corner cases involved in inter-language interop. In the next chapter we'll discuss JavaScript.
Defining a native Java module
Many Ceylon modules that call native Java code are designed
to execute only on the JVM. In this case, we declare the whole
Ceylon module as a native JVM module using the native
annotation.
native ("jvm")
module ceylon.formatter "1.3.3" {
shared import java.base "7";
shared import com.redhat.ceylon.typechecker "1.3.3";
import ceylon.collection "1.3.3";
...
}
A cross-platform module may call native Java code too, but in
this case we need to apply the native
import to import
statements that declare dependencies on native Java jar
archives.
module ceylon.formatter "1.3.3" {
native ("jvm") shared import java.base "7";
native ("jvm") shared import com.redhat.ceylon.typechecker "1.3.3";
import ceylon.collection "1.3.3";
...
}
Furthermore, in this case, we must annotate declarations which make use of the Java classes in these archives.
native ("jvm")
void hello() {
import java.lang { System }
System.out.println("Hello, world!");
}
It's not necessary to annotate individual declarations in a module that is already declared as a native JVM module.
Importing Java modules containing native code
A Ceylon program can only use native code belonging to a module. Ceylon doesn't have the notion of a global class path, and not every Ceylon program can be assumed to be executing on the JVM, so dependencies to native Java code must always be expressed explicitly in the module descriptor.
The native Java modules your Ceylon program depends on might come from a Ceylon module repository, from a Maven repository, or they might be part of the JDK or Android SDK.
Depending on the JDK
The Java SE Development Kit (JDK) is represented in Ceylon as a set of modules, according to the modularization proposed by the Jigsaw project. So to make use of the JDK we need to import one or more of these modules. For example:
- the module
java.base
contains core packages includingjava.lang
,java.util
, andjavax.security
, - the module
java.desktop
contains the AWT and Swing desktop UI frameworks, and -
java.jdbc
contains the JDBC API.
Thus, if we need to use the Java collections framework in our
Ceylon program, we need to create a Ceylon module that depends
on java.base
.
native ("jvm")
module org.jboss.example "1.0.0" {
import java.base "7";
}
Now, we can simply import the Java class we're interested in and use it like any ordinary Ceylon class:
import java.util { HashMap }
void hashyFun() {
value hashMap = HashMap<String,Object>();
}
Tip: using JavaFX
JavaFX is part of the JDK, but it's split into several modules,
javafx.base
, javafx.graphics
, javafx.media
, etc, so you'll
probably need to import several of them, for example:
native ("jvm")
module example.javafx.circles "1.0.0" {
shared import java.base "8";
shared import javafx.base "8";
shared import javafx.graphics "8";
shared import javafx.controls "8";
}
Depending on the Android SDK
For Android development, it's necessary to use the
Ceylon Android plugin for Gradle to create
a Ceylon module repository containing the bits of the Android
SDK that are needed to compile an app written in Ceylon.
This repository will be created in the subdirectory
build/intermediates/ceylon-android/repository
of your
Ceylon Android project. This repository must be specified as
the source of all Android-related Java modules using --rep
,
and as the provider of the Java SDK using the command-line
argument --jdk-provider
of ceylon compile
.
However, when compiling using Gradle, or from inside Android
Studio, the repository is created by the build, and the
compiler option is set automatically, so you don't need to
mess with it explicitly. You can find it in .ceylon/config
,
if you're interested:
[compiler]
jdkprovider=android/24
[repositories]
lookup=./build/intermediates/ceylon-android/repository
Note: Android itself has an immensely complicated toolchain, and Android apps cannot reasonably be compiled using only commmand line tools. In practice, you'll always use Gradle to build an Android app.
A basic boilerplate module descriptor for Android development looks like this:
native ("jvm")
module com.acme.example.androidapp "1.0" {
import java.base "7";
import android "24";
import "com.android.support.appcompat-v7" "24.2.1";
import "com.android.support.design" "24.2.1";
}
You can get started with Ceylon on Android by following this getting started guide.
Depending on a Java archive
To make use of native code belonging to a packaged .jar
archive, you have two options:
- add the archive to a Ceylon module repository, along with
JBoss Modules metadata defined in a
module.xml
ormodule.properties
file, or - import the archive directly from a Maven repository.
To add a Java .jar
to a Ceylon module repository, you need to
provide some metadata describing its dependencies. A module.xml
or module.properties
file specifies dependency information for
a .jar
.
- The format of the Ceylon
module.properties
file is documented here, and - the JBoss Modules
module.xml
descriptor format is defined here.
The command line tool ceylon import-jar
can help make this
task easier.
If you're using Ceylon IDE for Eclipse, and you don't want to
write the module.xml
descriptor by hand, go to
File > Export ... > Ceylon > Java Archive to Module Repository
.
Depending on a Maven module
Alternatively, the Ceylon module architecture interoperates with
Maven via Aether. You can import a module from maven by specifying
the maven:
repository type:
import maven:org.hibernate:"hibernate-core" "5.0.4.Final";
The module name is composed from the Maven group id and artifact id, and the artifact id must be quoted.
You can find more information here.
Depending on Java EE
The Java EE APIs are available in Ceylon Herd, so you can import them like this:
import javax.javaeeapi "7.0";
Alternatively, you can get them from Maven:
import maven:javax:"javaee-api" "7.0";
The second approach might work better if you're also importing other related Java modules from Maven.
Interoperation with Java types
Calling and extending Java types from Ceylon is mostly completely transparent. You don't need to do anything "special" to use or extend a Java class or interface in Ceylon, or to call its methods.
There's a handful of things to be aware of when writing Ceylon code that calls a Java class or interface, arising out of the differences between the type systems of the two languages.
Certain very abstract Java supertypes are mapped to Ceylon types
There are three Java classes and one interface which simply
can't be used at all in Ceylon code, because Ceylon provides
types which are exactly equivalent in the package
ceylon.language
. When these types occur in the signature
of an operation in Java, they're always represented by the
equivalent Ceylon type:
-
java.lang.Object
is represented by Ceylon'sObject
class, -
java.lang.Exception
is represented by Ceylon'sException
class, -
java.lang.Throwable
is represented by Ceylon'sThrowable
class, and -
java.lang.annotation.Annotation
is represented by the interfaceAnnotation
.
It's an error to attempt to import
one of these Java types
in Ceylon code.
Java primitive types are mapped to Ceylon types
You're never exposed to Java primitive types when calling a Java method or field from Ceylon. Instead:
-
boolean
is represented by Ceylon'sBoolean
class, -
char
is represented by Ceylon'sCharacter
class, -
long
,int
, andshort
are represented by Ceylon'sInteger
class, -
byte
is represented by Ceylon'sByte
class, -
double
andfloat
are represented by Ceylon'sFloat
class, and -
java.lang.String
is represented by Ceylon'sString
class.
Almost all of the time, this behavior is completely intuitive. But there's two wrinkles to be aware of...
Gotcha!
According to these rules, all conversions from a Java primitive to a Ceylon type are widening conversions, and are guaranteed to succeed at runtime. However, conversion from a Ceylon type to a Java primitive type might involve an implicit narrowing conversion. For example, if:
- a Ceylon
Integer
is assigned to a Javaint
orshort
, - a Ceylon
Float
is assigned to a Javafloat
, or if - a Ceylon UTF-32
Character
is assigned to a Java 16-bitchar
,
the assignment can result in silent overflow or loss of precision at runtime.
Note: it is not a goal of Ceylon's type system to warn about
operations which might result in numeric overflow. In general,
almost any operation on a numeric type, including +
or *
,
can result in numeric overflow.
Gotcha again!
There's no mapping between Java's wrapper classes like
java.lang.Integer
or java.lang.Boolean
and Ceylon basic
types, so these conversions must be performed explicitly by
calling, for example, longValue()
or booleanValue()
, or
by explicitly instantiating the wrapper class, just like you
would do in Java when converting between a Java primitive
type and its wrapper class.
This is mainly important when working with Java collection
types. For example, a Java List<Integer>
doesn't contain
Ceylon Integer
s.
import java.lang { JInteger=Integer }
import java.util { JList=List }
JList<JInteger> integers = ... ;
for (integer in integers) { //integer is a JInteger!
Integer int = integer.longValue(); //convert to Ceylon Integer
...
}
(This isn't really much worse than Java, by the way: a Java
List<Integer>
doesn't hold primitive Java int
s either!)
Worse, a Java List<String>
doesn't contain Ceylon
String
s.
import java.lang { JString=String }
import java.util { JList=List }
JList<JString> strings = ... ;
for (string in strings) { //string is a JString!
String str = string.string; //convert to Ceylon String
...
}
Watch out for this!
Tip: converting Java strings
Explicitly converting between String
and Java's
java.lang.String
is easy:
- the
.string
attribute of a Java string returns a Ceylon string, and - one of the constructors of
java.lang.String
accepts a CeylonString
, or, alternatively, - the function
Types.nativeString()
in the packagejava.lang
converts a Ceylon string to a Java string without requiring an object instantiation.
Tip: converting Java primitive wrapper types
Likewise, conversions between Ceylon types and Java primitive wrapper types are just as trivial, for example:
- the
.longValue()
methods ofjava.lang.Long
andjava.lang.Integer
return a CeylonInteger
, and - the constructors of
java.lang.Integer
andjava.lang.Long
accept a CeylonInteger
, as do the staticvalueOf()
methods of these types.
Likewise:
- the
.doubleValue()
methods ofjava.lang.Double
andjava.lang.Float
return a CeylonFloat
, and - the constructors of
java.lang.Double
andjava.lang.Float
accept a CeylonFloat
, as do the staticvalueOf()
methods of these types.
The story is similar for other primitive wrapper types.
Tip: using the small
annotation
If, for some strange reason, you really need a 32-bit int
or float
at the bytecode level, instead of the 64-bit long
or double
that the Ceylon compiler uses by default, you can
use the small
annotation.
small Integer int = string.hash; //a 32-bit int
You can also use small
to represent a Character
as a
16-bit char
at the bytecode level, instead of as a 32-bit
int
.
small Character char = charArray.get(0); //a 16-bit char
It's important to understand that small Integer
isn't a
different type to Integer
. So any Integer
is directly
assignable to a declaration of type small Integer
, and
the compiler will silently produce a narrowing conversion,
which could result in silent overflow or loss of precision
at runtime.
Note also that small
is defined as a hint, and may be
completely ignored by the compiler. (And, indeed, it is
always ignored when compiling to JavaScript.)
Java array types are represented by special Ceylon classes
Since there are no primitively-defined array types in Ceylon,
arrays are represented by special classes. These classes are
considered to belong to the package java.lang
. (Which belongs
to the module java.base
.) So these classes must be explicitly
imported!
-
boolean[]
is represented by the classBooleanArray
, -
char[]
is represented by the classCharArray
, -
long[]
is represented by the classLongArray
, -
int[]
is represented by the classIntArray
, -
short[]
is represented by the classShortArray
, -
byte[]
is represented by the classByteArray
, -
double[]
is represented by the classDoubleArray
, -
float[]
is represented by the classFloatArray
, and, finally, -
T[]
for any object typeT
is represented by the classObjectArray<T>
.
We can obtain a Ceylon Array
without losing the identity
of the underlying Java array.
import java.lang { ByteArray }
ByteArray javaByteArray = ByteArray(10);
Array<Byte> byteArray = javaByteArray.byteArray;
You can think of the ByteArray
as the actual underlying
byte[]
instance, and the Array<Byte>
as an instance of
the Ceylon class Array
that wraps the byte[]
instance.
The module ceylon.interop.java
contains a raft of
additional methods for working with these Java array types.
Java object types and null values
Java's primitive types do not hold null values, so any primitive type is mapped to a non-null Ceylon type. The situation is a bit more complicated for Java object and array types, which are treated differently depending upon whether they occur:
- as a method return type or field type, or
- as a method parameter type.
Non-primitive parameter types are treated as nullable
Java method signatures offer no information about whether a method parameter accepts a null value, except in the very special case of a parameter with primitive type. Therefore, Ceylon assigns an optional type to every parameter of non-primitive type.
Null return values are checked at runtime
Likewise, Java field or method types offer no information about
whether a field or method can produce a null value, except,
again, in the special case of a primitive type. But it would be
unacceptably intrusive for Ceylon to treat almost every Java
method or field as returning an optional type, forcing you to
explicitly check for null
every time you call Java! So Ceylon
doesn't do that. Instead, it treats Java methods and fields as
having non-optional type.
But that's, of course, unsound from the point of view of the
type system of Ceylon. A method can still return null
at
runtime!
Therefore, the compiler must insert runtime null value checks wherever Ceylon code calls a Java function that returns an object type, or evaluates a Java field of object type, and assigns the result to an non-optional Ceylon type.
In this example, no runtime null value check is performed,
since the return value of System.getProperty()
is assigned to
an optional type:
import java.lang { System }
void printUserHome() {
String? home //optional type
= System.getProperty("user.home");
print(home);
}
In this example, however, a runtime type check occurs when
the return value of System.getProperty()
is assigned to the
non-optional type String
:
import java.lang { System }
void printUserHome() {
String home //non-optional type, possible runtime exception
= System.getProperty("user.home");
print("home: " + home);
}
However, no runtime check occurs upon assignment to a
value
or function
with inferred type. Thus, there is no
runtime check at all in this code:
import java.lang { System }
void printUserHome() {
value home //inferred non-optional type, no runtime check!
= System.getProperty("user.home");
print(home);
}
Instead, a runtime check is inserted when the value
or
function
with inferred type is used in a way which
indicates it must be non-null
:
import java.lang { System }
void printUserHome() {
value home //no runtime check
= System.getProperty("user.home");
print("home: " + home); //runtime check here instead
}
These runtime checks ensure that null
can never unsoundly
propagate from native Java code with unchecked null values
into Ceylon code with checked null values, resulting in an
eventual NullPointerException
in Ceylon code far from the
original call to Java.
Java types annotated @Nullable
are exposed as optional types
There are now a number of Java frameworks that provide
annotations (@Nullable
and @Nonnull
/@NonNull
/@NotNull
)
for indicating whether a Java type can contain null
values.
The Ceylon compiler understands these annotations.
So, as an exception to the above discussion, when one of these annotations is present, Java null values are checked at compile time, and no runtime checks are necessary.
Java methods, properties, and constants
Ceylon makes use of the JavaBeans conventions when representing a Java class.
Java properties are exposed as Ceylon attributes
A getter or getter/setter pair belonging to a Java class will appear to a Ceylon program as an attribute. For example:
import java.util { Calendar, TimeZone }
void calendaryFun() {
Calendar calendar = Calendar.instance;
TimeZone timeZone = calendar.timeZone;
Integer timeInMillis = calendar.timeInMillis;
}
If you want to call the Java setter method, assign a value to
the attribute using =
, the assignment operator:
calendar.timeInMillis = system.milliseconds;
However, if there is no exactly-matching getter method for a setter, the setter is treated as a regular method.
Gotcha!
Note that there are certain corner cases here which might be confusing. For example, consider this Java class:
public class Foo {
public String getBar() { ... }
public void setBar(String bar) { ... }
public void setBar(String bar, String baz) { ... }
}
From Ceylon, this will appear as if it were defined like this:
shared class Foo {
shared String bar { ... }
assign bar { ... }
shared void setBar(String bar, String baz) { ... }
}
Therefore:
- to call the single-argument setter, use an assignment
statement like
foo.bar = bar
, but - to call the two-argument setter, use a method call like
foo.setBar(bar,baz)
.
Other Java methods are exposed as Ceylon methods
An Java method that doesn't follow the JavaBeans property conventions is represented to the Ceylon program as a regular method.
Gotcha: calling Java methods from Ceylon
There's two minor limitations to be aware of when calling Java methods from Ceylon.
- You can't call Java methods using the named argument syntax, since Java 7 doesn't expose the names of parameters at runtime (except for code compiled on Java 8 with a special compiler switch explicitly enabled).
- You can't obtain a method reference, nor a static method reference, to an overloaded method.
Methods accepting a SAM interface
Java has no true function types, so there's no equivalent to
Ceylon's Callable
interface in Java. Instead, Java features
SAM (Single Abstract Method) conversion where an anonymous
function is converted by the compiler to an instance of an
interface type like java.util.function.Predicate
which
declares only one abstract method.
Thus, there are many operations in the Java SDK and other
Java libraries which accept a SAM interface. For example,
Stream.filter()
accepts a Predicate
. Such methods are
represented in Ceylon as an overloaded method:
- one overload accepts the SAM, like
Stream<T> filter(Predicate<in T> predicate)
, and - the second overload accepts a Ceylon function type, like
Stream<T> filter(Boolean(T) predicate)
.
Thus, we can pass anonymous functions and function references to such Java APIs without needing to explicitly implement the SAM interface type.
import java.util {
Arrays
}
import java.util.stream {
Collectors { toList }
Stream { with=\iof }
}
value list
= Stream.with("hello", "world", "goodbye")
.filter((s) => s.longerThan(2))
.map(String.uppercased)
.collect(toList<String>());
As you can see, use of the Java streams API is completely natural in Ceylon.
Tip: getting a function from a SAM
There's no inverse conversion from a SAM type to a Ceylon
function type. A Predicate<T>
can't be passed directly to
an operation that expects a Boolean(T)
. But this is
perfectly fine, because it's completely trivial to perform
the conversion explicitly, just by taking a reference to the
test
method of Predicate
:
Predicate<String> javaPredicate = ... ;
Boolean(String) ceylonPredicate = javaPredicate.test;
Remember, the Ceylon language defines no implicit type conversions anywhere!
Java constants and enum
values
Java constants like Integer.MAX_VALUE
and Java enum values,
like RetentionPolicy.RUNTIME
, follow an all-uppercase naming
convention. Since this looks rather alien in Ceylon code, it's
acceptable to refer to them using camel case. This:
Integer maxInteger = JInteger.maxValue;
RetentionPolicy policy = RetentionPolicy.runtime;
is preferred to this:
Integer maxInteger = JInteger.\iMAX_VALUE;
RetentionPolicy policy = RetentionPolicy.\iRUNTIME;
However, both options are accepted by the compiler.
Tip: switching over Java enum
types
A Java enum
type is treated as as enumerated class. Each
enumerated value of the enum
type is represented as a
value constructor
of the class. Thus, it's possible to switch
over the members
of a Java enum
, just like you can in Java.
RetentionPolicy policy = ... ;
switch (policy)
case (RetentionPolicy.source) {
...
}
case (RetentionPolicy.runtime) {
...
}
case (RetentionPolicy.\iclass) {
...
}
Java generic types
A generic instantiation of a Java type like java.util.List<E>
is representable without any special mapping rules. The Java
type List<String>
may be written as List<String>
in Ceylon,
after importing List
and String
from the module java.base
.
import java.lang { String }
import java.util { List, ArrayList }
List<String> strings = ArrayList<String>();
The only problem here is that it's really easy to mix up
Java's String
, List
, and ArrayList
with the Ceylon
types with the same names!
Tip: disambiguating Java types using aliases
It's usually a good idea to distinguish a Java type with the
same name as a common Ceylon type using an import
alias. So
we would rewrite the code fragment above like this:
import java.lang { JString = String }
import java.util { JList = List, JArrayList = ArrayList }
JList<JString> strings = JArrayList<JString>();
That's much less likely to cause confusion!
Wildcards and raw types are represented using use-site variance
In pure Ceylon code, we almost always use declaration-site variance. However, this approach doesn't work when we interoperate with Java generic types, which are by nature all invariant. In Java, covariance or contravariance is represented at the point of use of the generic type, using wildcards. Therefore, Ceylon also supports use-site variance (wildcards).
Use-site variance is indicated using the keywords out
and in
:
-
List<out Object>
has a covariant wildcard, and is equivalent toList<? extends Object>
in Java, and -
Topic<in Object>
has a contravariant wildcard, and is equivalent toTopic<? super Object>
in Java.
Java raw types are also represented with a covariant wildcard.
The raw type List
is represented as List<out Object>
in
Ceylon.
Note that for any invariant generic type Type<X>
, the
instantiations Type<out Anything>
and Type<in Nothing>
are
exactly equivalent, and are supertypes of every other
instantiation of Type
.
Gotcha!
Instances of Java generic types don't carry information about
generic type arguments at runtime. So certain operations which
are legal in Ceylon are rejected by the Ceylon compiler when
a Java generic type is involved. For example, the following code
is illegal, since JList
is a Java generic type, with erased
type arguments:
import java.lang { JString = String }
import java.util { JList = List, JArrayList = ArrayList }
Object something = JArrayList<JString>();
if (is JList<JString> something) { //error: type condition cannot be fully checked at runtime
...
}
This isn't a limitation of Ceylon; the equivalent code is also illegal in Java!
Tip: dealing with Java type erasure
When you encounter the need to narrow to an instantiation of a Java generic type, you can use an unchecked type assertion:
import java.lang { JString = String }
import java.util { JList = List, JArrayList = ArrayList }
Object something = JArrayList<JString>();
if (is JList<out Anything> something) { //ok
assert (is JList<JString> something); //warning: type condition might not be fully checked at runtime
...
}
This code is essentially the same as what you would do in Java:
- test against the raw type
List
usinginstanceof
, and then - narrow to
List<String>
using an unchecked typecast.
Just like in Java, the above code produces a warning, since the type arguments in the assertion cannot be checked at runtime.
Overloading methods and constructors
When a class or interface is declared native("jvm")
, it may
declare overloaded methods and constructors. Both default
constructors and named constructors may be overloaded.
An overloaded method or constructor must be marked with the
annotation overloaded
, which is considered to belong to the
package java.lang
in the module java.base
.
import java.lang { overloaded }
native("jvm")
class Native {
variable Integer int;
shared overloaded new () {
int = 0;
}
shared overloaded new (String string) {
int = parseInteger(string);
}
shared overloaded new (Integer int) {
this.int = int;
}
shared overloaded set(String string) {
int = parseInteger(string);
}
shared overloaded set(Integer int) {
this.int = int;
}
shared Integer get() => int;
}
This is especially useful when implementing a Java interface that declares an overloaded method.
Inheriting Java types and refining Java methods
A Ceylon class may extend a Java class and/or implement Java interfaces, even if the Java types are generic.
As we saw above, Java method parameter types are treated as optional. However, when refining a Java method in a Ceylon class, we're allowed to redeclare the parameter as non-optional, for example:
//java interface
public interface Stringifier<T> {
public String stringify(T thing);
}
//ceylon implementation
class EntryStringifier<Key,Item>()
satisfies Stringifier<Key->Item> {
stringify(Key->Item entry)
=> "``entry.key``->``entry.item``";
}
Here, we did not have to declare entry
as being of type
<Key->Item>?
, though we could have, if we expected our
method to be called with a null
argument.
There's nothing else particularly interesting to say about this topic, except to point out one small wrinkle. In Java, the following inheritance hierarchy is legal:
interface Foo {
public boolean test();
}
interface Bar {
public boolean test();
}
class Baz implements Foo, Bar {
@Override
public boolean test() {
return true;
}
}
However, a Ceylon class which implements the Java types Foo
and Bar
is not legal,
since a Ceylon class may not define an operation that refines
two completely different but same-named operations unless they
both descend from a common supertype definition:
class Baz() satisfies Foo & Bar {
//error: May not inherit two declarations with the
//same name that do not share a common supertype
test() => true;
}
In this case, it's necessary to either:
- split
Baz
into two classes, - define an intermediate Java interface that inherits
Foo
andBar
, or - define a common superinterface of
Foo
andBar
that declarestest()
.
Java annotations
Java annotations may be applied to Ceylon program elements. An initial lowercase identifier must be used to refer to the Java annotation type.
For example:
import javax.persistence {
id,
generatedValue,
entity,
column,
manyToOne,
}
entity
class Person(firstName, lastName, city) {
id generatedValue
column { name = "pid"; }
value _id = 0;
shared String firstName;
shared String lastName;
manyToOne { optional=false; }
shared City city;
string => "Person(``_id``, ``firstName`` ``lastName``, ``city.name``)";
}
Note that id
here refers to the Java annotation
javax.persistence.Id
.
Tip: specifying arguments to anotation parameters of type Class
Annotation values of type java.util.Class
may be specified
by passing the corresponding Ceylon ClassDeclaration
. For
example, you would use type = `class Person`
where you
would have used type = Person.class
in Java.
Java language modifiers are represented as annotations
The following Java language modifiers do not naturally
correspond to any annotation in ceylon.language
:
-
transient
, -
volatile
, -
synchronized
, -
native
, and -
strictfp
.
Ceylon treats these modifiers as annotations belonging
to the package java.lang
(in the module java.base
).
Therefore, if you want to use one of these JVM-specific modifiers in Ceylon, you must explicitly import it:
import java.lang { volatile }
volatile variable value counter = 0;
Of these annotations, transient
and volatile
are the most
commonly-used in Ceylon code. We discourage direct use of
synchronized
, which is extremely vulnerable to deadlocks,
preferring the use of java.util.concurrent
.
Syntax sugar that applies to Java types
Certain syntactic constructs that are defined by the language
specification in terms of types defined in ceylon.language
are also supported for similar Java types. These constructs
are:
- the
for
loop and comprehensions, - resource expressions in
try
, - the element lookup and
in
operators, and - the "spread" operators
*.
and*
.
Java Iterable
or array in for
It's possible to use a java.lang.Iterable
or Java array in
a Ceylon for
statement or comprehension.
import java.lang { JIterable=Iterable }
JIterable<Object> objects = ... ;
//for statement
for (obj in objects) {
...
}
//comprehension
print(", ".join { for (obj in objects) if (exists obj) obj.string.trimmed });
Imagine how great it would be to be able to write a comprehension involving a Java collection in Java itself!
Wait, a quick reminder...
Gotcha! (redux)
As we already noted above, for
does nothing special to the
type of the iterated elements of a Java array or Iterable
:
if the elements are Java String
s, they're not magically
transformed to Ceylon String
s:
import java.lang { ObjectArray, JString=String }
ObjectArray<JString> strings = ...;
for (s in strings) { // s is a JString
String string = s.string; //string is a Ceylon String
...
}
Just don't forget to use s.string
to get the Ceylon String
if that's what you need!
Gotcha again!
Also note that java.lang.Iterable
is not supported together
with entry or tuple destructuring:
JIterable<String->String> stringPairs = ...;
for (key->item in stringPairs) {
// not supported
}
In practice it's unusual to have a Java Iterable
containing
Ceylon Entry
s or Tuple
s.
Java AutoCloseable
in try
Similarly, it's possible to use a Java AutoCloseable
in a
Ceylon try
statement.
import java.io { File, FileInputStream }
File file = ... ;
try (inputStream = FileInputStream(file)) {
...
}
The semantics, naturally, are identical to what you get in Java.
Java collections and operators
Two of Ceylon's built-in operators may be applied to Java types:
- the element lookup operator (
list[index]
) may be used with Java arrays,java.util.List
, andjava.util.Map
, and - the containment operator (
element in container
) may be used with the typejava.util.Collection
.
This is syntax sugar you don't even have in Java itself!
import java.util { JArrayList=ArrayList }
value list = JArrayList<String>();
list.add("hello");
list.add("world");
list[0] = "goodbye";
assert ("world" in list);
printAll { for (word in list) word };
There's almost no friction here; if you prefer to use Java's
collection types instead of the mutable collection types
defined by the module ceylon.collection
, go right ahead!
Tip: creating Java lists
The following idioms are very useful for instantiating Java
List
s:
import java.util { Arrays }
value words = Arrays.asList("hello", "world");
value squares = Arrays.asList(for (x in 0..100) x^2);
value args = Arrays.asList(*process.arguments);
(Note that these code examples work because Arrays.asList()
has a variadic parameter.)
Tip: copying Java collection elements into Ceylon collections
You can use the spread operator to obtain a Ceylon collection with the same elements as a Java collection:
import java.util { JList=List }
JList<Object> objects = .... ;
value sequenceOfObjects = [*objects];
value setOfObjects = set { *objects };
A comprehension gives you even more power:
import java.util { JList=List }
import java.lang { JString=String }
JList<JString> strings = .... ;
value sequenceOfStrings = [ for (str in strings) str.string ];
However, copying collections by nature involves memory allocation and this can be slow. A more efficient approach is to wrap the Java collection. Fortunately, Ceylon has a library for that.
Utility functions and classes
The class Types
, which is considered to belong to the package
java.lang
in the module java.base
offers some extremely useful
static utility methods. For example, Types.nativeString()
converts Ceylon strings to java.lang.String
s and Types.charArray()
converts Ceylon strings into Java char[]
arrays.
In the module ceylon.interop.java
you'll find a suite
of useful utility functions and classes for Java interoperation.
For example, there are classes that adapt between Ceylon
collection types and Java collection types.
Tip: getting a java.util.Class
An especially useful function is Types.classForType
, which
obtains an instance of java.util.Class
for a given type.
import java.lang {
JClass=Class,
Types { classForType }
}
JClass<Integer> jc = classForType<Integer>();
print(jc.protectionDomain);
The functions Types.classForInstance
, Types.classForModel
, and
Types.classForDeclaration
are also useful.
Tip: converting between Iterable
s
Another useful adaptor is CeylonIterable
, which lets
you apply any of the usual operations of a Ceylon stream
to a Java Iterable
.
import java.util { JList=List, JArrayList=ArrayList }
import ceylon.interop.java { CeylonIterable }
...
JList<String> strings = JArrayList<String>();
strings.add("hello");
strings.add("world");
CeylonIterable(strings).each(print);
(Alternatively, we could have used CeylonList
in this
example.)
Similarly there are CeylonStringIterable
, CeylonIntegerIterable
,
CeylonFloatIterable
,CeylonByteIterable
and CeylonBooleanIterable
classes which as well as converting the iterable type also
convert the elements from their Java types to the corresponding
Ceylon type.
Additional support for Java interoperation
Ceylon provides the following additional features to support interoperation with the Java platform.
META-INF
and WEB-INF
Some Java frameworks and environments require metadata packaged
in the META-INF
or WEB-INF
directory of the module archive,
or sometimes even in the root directory of the module archive.
We've already seen how to package resources in Ceylon module
archives by placing the resource files in the module-specific
subdirectory of the resource directory, named resource
by
default.
Then, given a module named net.example.foo
:
- resources to be packaged in the root directory of the module
archive should be placed in
resource/net/example/foo/ROOT/
, - resources to be packaged in the
META-INF
directory of the module archive should be placed inresource/net/example/foo/ROOT/META-INF/
, and - resources to be packaged in the
WEB-INF
directory of the module archive should be placed inresource/net/example/foo/ROOT/WEB-INF/
.
Interoperation with Java serialization
Ceylon classes are implicitly serializable by Java's built-in object serialization facility.
When compiling a Ceylon class, the compiler adds the supertype
java.io.Serializable
to the generated Java class, along with
a package-private default constructor. Neither the supertype,
nor the constructor, are visible to other Ceylon code. But
they're enough to make the Ceylon class serializable via
Java's built-in binary serialization APIs.
Of course, if your Ceylon object holds a reference to some other object that's not serializable, you still won't be able to serialize the Ceylon object!
Interoperation with Java's ServiceLoader
Ceylon services and service providers work transparently
with Java's service loader architecture, having been
designed and implemented as a simple abstraction of Java's
ServiceLoader
.
- Annotating a Ceylon class with the
service
annotation makes the class available to Java'sServiceLoader
. - Similarly, a Ceylon module may gain access to Java services
just by calling
Module.findServiceProviders()
.
The service
annotation and Module.findServiceProviders()
work
portably across the JVM and JavaScript environments.
Java EE and other annotation-driven frameworks
There are a number of widely-used Java frameworks that depend upon direct reflection-based access to the fields of annotated classes.
For example, the following libraries and frameworks use this approach extensively:
- javax.inject
- Java EE (CDI, JPA, JAXB, JAX-RS, EJB, etc)
- Hibernate
- Spring Framework
- Google Guice
When using this sort of framework in Ceylon, you'll probably need to enable a special compiler mode which slightly changes the way the compiled Ceylon class represents its internal state using Java fields.
With EE mode activated, Ceylon classes should work transparently with these frameworks and libraries. (Note: we've already tested all the above listed technologies with Ceylon, to be sure they work correctly!)
In many cases EE mode is activated implicitly when you import a Java module or use a Java EE annotation.
You'll often need to use the late
annotation in conjunction
with EE mode.
Alternative module runtimes
When you execute a Ceylon program using ceylon run
, it
executes on a lightweight module container that is based on
JBoss Modules. But it's also possible to execute a Ceylon
module in certain other module containers, or without any
module container at all.
Tip: running your program without a Ceylon installation
Don't forget that you can run
a Ceylon program assembled using ceylon assemble --include-runtime
on a machine with no Ceylon installation. In this case, the
JBoss Modules-based module container is included in the .cas
archive itself.
Deploying Ceylon on OSGi
Ceylon is fully interoperable with OSGi, so that Ceylon modules:
- can be deployed as pure OSGi bundles in an OSGi container out-of-the-box, without any modification of the module archive file,
- can embed additional OSGi metadata, to declare services for example, and
- can easily use OSGi standard services.
This provides a great and straightforward way to run Ceylon modules inside Java EE application servers or enterprise containers that support or are are built around OSGi.
You can learn more about Ceylon and OSGi from the reference documentation.
Deploying Ceylon on Jigsaw
The command line tool ceylon jigsaw
deploys a module and
all its dependencies to a Jigsaw-style mlib/
directory. Since
Jigsaw is not yet released, and is still undergoing changes, this
functionality is considered experimental.
Deploying Ceylon on Java EE or WildFly Swarm
These command line tools make it easy to assemble a Ceylon module that makes use of Java EE APis in a Java EE environment:
-
ceylon war
repackages a module and its dependencies as a Java EE.war
web archive, and -
ceylon swarm
repackages a module, its dependencies, and the Wildfly Swarm environment as a.jar
archive.
Tip: including static web content
To include a directory containing static web resources like HTML,
image, and JavaScript files in an archive generated by ceylon war
or ceylon swarm
, use --resource-root
or -R
, for example:
ceylon war -R=web com.acme.todolist
Deploying Ceylon as a fat jar
To run a Ceylon program on a machine with no Ceylon distribution
available, and without the runtime module isolation provided by
JBoss Modules, the command line tool ceylon fat-jar
is
indispensable. The command simply assembles a Java .jar
archive
that contains a Ceylon module and everything it depends on at
runtime.
Publishing Ceylon via Maven
Finally, the ceylon maven-export
command assembles a Maven
repository containing a Ceylon module and its dependencies. This is
most useful if we want to make a Ceylon module available to a Java
project that is built using Maven.
However, this isn't the only way to integrate Ceylon modules into a Maven build.
Ceylon and Maven
Every compiled Ceylon module archive includes generated Maven
metadata, in META-INF/maven/groupId/artifactId/pom.xml
and
META-INF/maven/groupId/artifactId/pom.properties
.
You can specify the Maven group id and artifact id in your module descriptor:
module org.hibernate.core //Ceylon module name
maven:org.hibernate:"hibernate-core" //Maven group id + artifact id
"2.1.1" { //module version
...
}
If this information is missing, the group id and artifact id will be inferred from the module name.
There are three ways to publish a Ceylon module to a Maven repository, allowing use of the Ceylon module directly from Java via Maven:
- simply publish the Ceylon module to Ceylon Herd, which automatically makes it available in the Herd Maven repository,
- use the
ceylon maven-export
command to assemble a Maven repository containing the Ceylon module archive and its dependencies, or - build the Ceylon modules using Maven.
If you build your Ceylon modules using Maven, or if you use
ceylon maven-export
, you can easily publish the resulting module
archives to a public Maven repository such as Maven Central.
All Ceylon SDK platform modules are already available in Maven Central,
under the group id org.ceylon-lang
. (They're also available in
under the same group id
in the Herd Maven repository.) For example, ceylon.collection
has the
group id org.ceylon-lang
and artifact id ceylon.collection
.
There's more ...
You can learn more about native
declarations from the
reference documentation.
Ceylon's experimental support for Jigsaw is covered in greater detail in this blog post.
In a mixed Java/Ceylon project, you'll probably need to use a
build system like Gradle, Maven, or Apache ant
. Ceylon has
plugins for each of these build
systems.
Finally, we're going to learn about interoperation with languages like JavaScript with dynamic typing.