Java Interop

Calling JVM methods, accessing properties, resolving types

Notch is designed to live inside the Java ecosystem. JVM classes, methods, and properties are first-class values in Notch code.

Quick reference

import java.time.LocalDate
java.lang.System.out.println("hello!")
list = java.util.ArrayList()
list.size          # getter
list.size()        # method call
java.util.List.of(1, 2, 3)         # static method
java.lang.Character.TYPE           # static property
for v in java.time.DayOfWeek.values
  print(v)
end

Calling methods

java.lang.System.out.println("hello!")

When more than one overload of a method exists, Notch picks the best match by parameter count, then by direct argument-type assignability, then by whether a registered coercion can bridge the argument type to the parameter type. Less-specific matches lose to more-specific ones.

Constructors

Call a constructor by invoking the type value:

list = java.util.ArrayList()
list.add("a")
list.add("b")
list.size

After the two add calls, list.size is 2. Constructor overload resolution uses the same getBestMatch logic as method invocation.

Null handling

A property access or method call on null produces a diagnostic error message that points at the failing expression, rather than throwing a NullPointerException from inside the runtime. Use is empty or compare against null explicitly to guard.

Property access

Property access works on JVM objects via their getter methods. Both bean-style and direct field names are accepted.

list = [1, 2, 3]
list.size
list.size()

Both forms return 3; list.size reads the getter, list.size() calls the method.

Snake-case and camelCase access both resolve to the same getter:

d = java.time.LocalDate.now()
d.dayOfMonth
d.day_of_month

Type resolution

Fully-qualified class names resolve to type values:

java.lang.String

Returns a NotchType describing the class. JVM primitives are accessible as bare identifiers:

int

Bringing a type into scope

A fully-qualified name always works: java.io.IOException. To use a Java type by its short name as a value - for example err = IOException("disk gone") - import it first:

import java.io.IOException
err = IOException("disk gone")

Add an as clause to bind the type to a different local name - useful for shortening a long name, disambiguating a collision, or just making inline calls read more clearly:

import java.util.List as JavaList
JavaList.of(1, 2, 3)

Exception types are the one exception to needing an import: they resolve unqualified in throw, catch, and new without one (throw IOException("..."), catch IOException, new IOException("...")). Using the bare name as a plain value still needs the import.

Static methods

java.util.List.of(1, 2, 3)

Static properties

java.lang.Character.TYPE

Iterating an enum

Java enums iterate via the values static accessor:

for v in java.time.DayOfWeek.values
  print(v)
end

The runtime special-cases enums in its iterable coercion, so values works without an explicit call.

See also