N4JS and Java
Introduction
N4JS is an extension of ECMAScript. It is therefore as different from Java as ECMAScript is. There are additional features which are quite similar to Java, actually making ECMAScript feel more like JavaScript. In the following, we describe the most important similarities and differences of Java and N4JS.
Similarities
The general idea of N4JS is to make ECMAScript as type safe as Java. For that reason, N4JS adds a static type system to ECMAScript. Many concepts of this type system are similar to Java’s type system.
Nominal Typing
N4JS supports both, nominal and structural typing. Java has a nominal type system and this is also the default mode in N4JS. When you just use a simple type reference, nominal typing (as in Java) is applied.
The following example should be easily understood by Java developers:
class A { public foo(): void {} }
class B extends A {}
class C { public foo(): void {} }
function f(p: A) {}
f(new B());
f(new C()); // error: C is not a subtype of A.
This example will produce an error in the last line because C
is not a nominal subtype of A
.
Overriding of members in N4JS is quite similar to Java although N4JS allows for slightly more flexibility due to the underlying ECMAScript semantics (see Function Subtyping).
Generics
The most important concept (taken more or less 1:1 from Java) is generics. There are a lot of languages providing support for generics. Often there is limited support compared to Java, or different concepts are used. There are languages, for example, which support generic types but not generic functions, or do not support wildcards. Languages such as Scala, for instance, have different concepts for generics.
N4JS provides all features related to generics as Java does. Supported are:
-
generic types and generic methods (and functions)
-
type variables with lower bounds
-
parameterization with wildcards (and upper and lower bounds)
N4JS even uses the very same algorithm to infer type variables as Java 8. This algorithm is, of course, slightly adapted to support extra features of N4JS such as union types.
As a consequence, generics and parameterization should immediately be understood by Java programmers (as far as 'immediately understood' can be applied to generics).
At the moment, the N4JS type system has some known issues when substituting type variables and instantiating wildcard expressions. For the latter, Java uses a technique called "capture conversion" while N4JS implements a slightly simpler one called "existential types". Future releases of N4JS will fix these issues.
Interfaces and Abstract Declarations
ECMAScript 2015 introduces classes similar to Java. In both languages, single inheritance is used. Although this simplifies many scenarios, the limitations of single inheritance are often problematic. In Java, many of these problematic cases can be solved with interfaces. N4JS also provides the concept of interfaces, allowing for classes to implement multiple interfaces. Interfaces in N4JS allow for the definition of default methods as in Java 8, therefore overcoming most of the problems introduced by single inheritance. Related to interfaces are abstract methods and, consequently, abstract classes which are also supported in N4JS similarly to Java.
N4JS adapts the instanceof
operator so it can be used in combination with interfaces at runtime.
Access Modifiers
When designing larger systems and frameworks, limiting access to certain elements is important for maintainability. Similar to Java, N4JS introduces access modifiers to limit visibility of types and members. The following table shows the access modifiers of N4JS compared to Java:
Java |
N4JS |
Remark |
|
Public |
Types and members, similar semantic |
||
Protected |
Members only, similar semantic |
||
Package |
Project |
See below |
While it is possible to organize modules in folders which could be interpreted as packages, N4JS does not really support the notion of packages. Instead, it uses projects to encapsulate coherent functionality. Project is the default visibility in N4JS as package is in Java. The semantics are also similar in both cases: elements (types or members) can only be accessed from the same container (project in N4JS and package in Java).
Differences
Auto-Boxing
In Java, primitive types can be auto-boxed to their corresponding object types (and vice versa). This is also true for ECMAScript but there are subtle differences which might lead to misconceptions as shown in the following example:
let bool: boolean = false;
let Bool: Boolean = new Boolean( false );
console.log( bool.valueOf(), bool ? "true" : "false");
console.log( Bool.valueOf(), Bool ? "true" : "false");
Probably surprising for Java programmers, this would print out
false 'false'
false 'true'
In order avoid these problems, N4JS does not support auto-boxing. Instead, primitives and object types have to be
explicitly converted using either the constructor
, Object.valueof
or other related methods.
Function Subtyping
Function (or method) subtyping comes into play in two cases: method overriding and passing functions as arguments
(callbacks, for example). In both cases, the type checker has to ensure that a function f
is compatible with
another function f'
(in other words, that the type of f
is a subtype of the type of f'
). In Java this is called
"override compatible".
In Java, a method f
is override compatible to a f'
if and only if
-
it has the same name
-
it has the same number of parameters
-
the type of each parameter of
f
must be a supertype of the corresponding parameter off'
-
its return type is a subtype of the return type of
f'
In ECMAScript, it is possible to call a function of a method with more or less arguments than declared formal parameters. Calling a function with less arguments is not allowed in N4JS (unless the parameters are declared as optional). The definition of "override compatible" is, therefore, a little bit different in N4JS.
In N4JS,f'
is override comptabible to f
(or its type is a subtype of the type of f
), if
-
it has the same name (in case of method override)
-
it has the same number or less of parameters, or superfluous parameters are optional
-
the type of each parameter of
f'
must be a subtype of the corresponding parameter off
-
its return type is a subtype of the return type of
f
, orf
has no return type (it’s void).
For example, the following code is correct in N4JS while it would cause compile errors in Java:
class A {
foo(s: string): void {}
}
class B extends A {
@Override
foo(): number { return 0 }
}
Overloading
There is no method overloading in ECMAScript and therefore there cannot be overloading in N4JS. In order to 'emulate' overloading to a certain degree, union types and optional parameters can be used.
Static Members
In Java, a static member of a class can be accessed either
-
via the declaring class (or a subclass)
-
via an instance
In N4JS, a static member can only be called via the declaring class.
Note that the this
literal is bound to the class (to the constructor function, in fact). This enables
static polymorphism as shown in the next example:
class A {
public static s() { console.log("A.s"); this.t(); };
public static t() { console.log("A.t"); };
}
class B extends A {
@Override
public static t() { console.log("B.t"); };
}
A.s();
B.s();
This will print out
A.s
A.t
A.s
B.t
The last line in particular may be surprising for Java programmers.