BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles Java: The Missing Features

Java: The Missing Features

Lire ce contenu en français

Bookmarks

In this article, we look at some of the "missing features" of Java. Before we get fully underway, however, we should point out that there are some features that we deliberately don’t consider. This is usually because the feature has either been extensively discussed elsewhere, or would require too much work at VM level. For example:

  • No reified generics.

This has been discussed at great length elsewhere and much of the commentary frequently misunderstands type erasure. In reality when they say "I don’t like type erasure", many Java developers actually mean "I want List<int>" . The issue of primitive specialisation of generics is only tangentially related to type erasure, and run-time visible generics are actually much less useful than Java folk wisdom insists.

  • VM-level unsigned arithmetic.

Java’s lack of support for unsigned arithmetic types was a familiar complaint from developers in the earliest years of the platform, but it represented a deliberate design choice. Choosing to only implement signed arithmetic simplified Java considerably. To introduce unsigned integral types now would be a huge root-and-branch change which could introduce a lot of subtle and hard to find bugs, and the risk of destabilizing the platform is just too great.

  • Long indices for arrays.

Again, this feature is simply too deep a change in the depths of the JVM, with a broad set of consequences, not least in the behavior and semantics of the garbage collectors. However, it should be noted that Oracle are looking at providing related functionality via a project called VarHandles.

It’s also worth pointing out that we are not too concerned with the precise details of how a Java syntax for a given feature would work. As Brian Goetz has pointed out on many occasions, discussions of Java’s feature tend to over-focus on the syntax, at the expense of thinking deeply about the semantics that a given feature should enable.

Having set some frames of reference, let’s get started and take a look at the first of our missing features.

More Expressive Import Syntax

Java’s import syntax is quite limited. The only two options available to the developer are either the import of a single class or of an entire package. This leads to cumbersome multi line imports if we want just some but not all of a package, and necessitates IDE features such as import folding for most large Java source files.

Extended import syntax, allowing multiple classes to be imported from a single package with a single line would make life a bit simpler:

import java.util.{List, Map};

The ability to locally rename (or alias) a type would improve readability and help reduce confusion between types with the same short class name:

import java.util.{Date : UDate};
import java.sql.{Date : SDate};
import java.util.concurrent.Future;
import scala.concurrent.{Future : SFuture};

Enhanced wildcards would also help, like this:

import java.util.{*Map};

This is a small, but useful, language change and has the benefit that it can be implemented entirely within javac.

Collection Literals

Java has some (albeit limited) syntax for declaring array literals. For example:

int[] i = {1, 2, 3};

This syntax has a number of drawbacks, such as the requirement that array literals only appear in initializers.

Java’s arrays aren’t collections, and the "bridge methods" provided in the helper class Arrays also have some major drawbacks. For example, the Arrays.asList() helper method returns an ArrayList, which seems entirely reasonable, until closer inspection reveals that it is not the usual ArrayList but rather Arrays.ArrayList. This inner class does not implement the optional methods of List and so some familiar methods will throw OperationNotSupportedException. The result is an ugly seam in the API making it awkward to move between arrays and collections.

There’s no real reason the language has to omit a syntax for declaring array literals; after all, many languages provide a simple syntax for doing so. For example, in Perl, we can write:

my $primes = [2, 3, 5, 7, 11, 13];
my $capitals = {'UK' => 'London', 'France' => 'Paris'};

and in Scala:

val primes = Array(2, 3, 5, 7, 11, 13);
val m = Map('UK' -> 'London', 'France' -> 'Paris');

Java, unfortunately, does not offer useful collection literals. They have been repeatedly talked about, both for Java 7 and 8, but have never materialised. The case of object literals is also interesting, but much harder to achieve within Java’s type system.

Structural Typing

Java’s type system is famously nominal, to the point of being described as "name obsessed". As all variables are required to be of named types, there is no possibility of having a type that can only be expressed via a definition of its structure. In other languages, such as Scala, it is possible to express a type not by declaring it as implementing an interface (or a Scala trait), but instead simply by asserting that the type must have a particular method. For example:

def whoLetTheDucksOut(d: {def quack(): String}) {
  println(d.quack());
}

This will accept any type that possesses a quack() method, regardless of whether there is any inheritance or shared interface relationship between the types.

The use of quack() as an example is not an accident - structural typing can be thought of as related to "duck typing" in languages like Python, but of course in Scala the typing is happening at compile time, due to the flexibility of Scala’s type system in representing types that would be difficult or impossible to express in Java.

As first noted by James Iry, Java’s type system does actually allow a very limited form of structural typing. It is possible to define a local anonymous type that has additional methods, and providing that one of the new additional methods is immediately called, Java will allow the code to compile.

Unfortunately, the fun stops there, and one "structural" method is all we can call, because there is no type that can be returned from a "structural" method that encodes the additional information that we want. As Iry notes, the structural methods are all valid methods, and are present in the bytecode and for reflective access; they just can’t be represented in Java’s type system. This probably shouldn’t be that surprising, as under the hood, this mechanism is actually implemented by producing an additional class file that corresponds to the anonymous local type.

Algebraic Data Types

Java’s generics provide the language with parameterized types, which are reference types that have type parameters. Concrete types can then be created by substituting the type parameter for some actual type. Such types can be thought of as being composed of their "container" type (the generic type) and the "payload" types (the values of the type parameters).

However, some languages support types that are composite, but in a strikingly different manner from Java’s generics (or simple composition to create a new data type). One common example is tuples, but a more interesting case is that of the “sum type”, sometimes referred to as a “disjoint union of types” or a “tagged union”.

A sum type is a single-valued type (variables can only hold one value at a time), but the value can be any valid value of a specified range of distinct types. This holds true even if the disjoint types that can provide values have no inheritance relationship between them. For example, in Microsoft’s F# language we can define a Shape type, instances of which can be either rectangles or circles:

type Shape =
| Circle of int
| Rectangle of int * int

F# is a very different language from Java, but closer to home, Scala has a limited form of these types. The mechanism used is Scala’s sealed types as applied to case classes. A sealed type in Scala is not extensible outside of the current compilation unit. In Java, this would basically be the same as a final class, but Scala takes the file as the basic compilation unit, and multiple top-level public classes can be declared in a single file.

This leads to a pattern where a sealed abstract base class is declared, along with some subclasses, which correspond to the possible disjoint types of the sum type. Scala’s standard library contains many examples of this pattern, including Option[A], which is Scala’s equivalent of Java 8’s Optional<T> type.

In Scala, an Option is either Some or None and the Option type is a disjoint union of the two possibilities.

If we were to implement a similar mechanism in Java, then the restriction that the compilation unit is fundamentally the class would makes this feature much less convenient than in Scala, but we can still conceive of ways to make it work. For example, we could extend javac to process a new piece of syntax on any classes we wanted to seal:

final package example.algebraic;

This syntax would indicate that the compiler was to only allow extension of the class bearing a final package declaration within the current directory, and to reject any attempt to extend it otherwise. This change could be implemented within javac, but it would obviously not be completely safe from reflective code without runtime checks. It would also, unfortunately, be somewhat less useful than in Scala, as Java lacks the rich match expressions that Scala provides.

Dynamic call sites

With version 7, the Java platform added a feature that turns out to be surprisingly useful. The new invokedynamic bytecode was designed to be a general purpose invocation mechanism.

Not only does it allow dynamic languages to run on top of the JVM, but it also allows aspects of the Java type system to be extended in previously impossible ways, allowing default methods and interface evolution to be added. The price for this generality is a certain amount of unavoidable complexity, but once this has been understood, invokedynamic is a powerful mechanism.

One limitation of dynamic invocation is quite surprising. Despite introducing this support with Java 7, the Java language does not provide any way to directly access dynamic invocation of methods. The whole point of dynamic dispatch is to allow developers to defer until execution time, and participate in, decisions about which method to call from a given call site.

(Note: The developer should not confuse this type of dynamic binding with the C# keyword dynamic. This introduces an object that dynamically resolves its bindings at runtime, and will fail if the object cannot actually support the requested method calls. Instances of these dynamic objects are indistinguishable from objects at runtime and the mechanism is accordingly rather unsafe.)

While Java does use invokedynamic under the hood to implement lambda expressions and default methods, there is no direct access to allow application developers to do execution time dispatch. Put another way, the Java language does not have a keyword or other construct to create general-purpose invokedynamic call sites. The javac compiler simply will not emit an invokedynamic instruction outside of the language infrastructure use cases.

Adding this feature to the Java language would be relatively straightforward. Some sort of keyword, or possibly annotation would be needed to indicate it, and it would require additional library and linkage support.

Glimmers of hope?

The evolution of language design and implementation is the art of the possible, and there are plenty of examples of major upheavals that took aeons to be fully adopted across languages. For example, it was only with C++14 that lambda expressions finally arrived.

Java’s pace of change is often criticised but one of James Gosling’s guiding principles was that if a feature was not fully understood, it should not be implemented. Java’s conservative design philosophy has arguably been one of the reasons for its success, but it has also attracted a lot of criticism from younger developers impatient for faster change within the language. Is there ongoing work that might deliver some of the missing features here discussed? The answer is, perhaps, a cautious maybe.

The mechanism by which some of these ideas may be realized is one that we have referred to before - invokedynamic. Recall that the idea behind it is to provide a generalised invocation mechanism that is deferred until execution time. Recent enhancement proposal JEP 276 offers the possibility of standardising a library called Dynalink. This library, originally created by Attila Szegedi during his tenure at Twitter, was originally proposed as a way to implement “meta-object protocols” in the JVM. It was adopted by Oracle when Szegedi joined the firm and was used extensively in the internals of the Nashorn implementation of Javascript on the JVM. JEP 276 now proposes to standardise this and make it available as an official API for all languages on the JVM. An overview of Dynalink is available from Github but the library has moved on significantly since those resources were written.

Essentially, Dynalink provides a general way to talk about object-oriented operations, such as “get value of property”, “set value of property”, “create new object”, “call method” without requiring that the semantics of those operations be fulfilled by the corresponding, static-typed, low-level operations of the JVM.

This opens the door to using this linking technology to implement dynamic linkers with different behavior from that of the standard Java linker. It can also be used as a sketch of how new type system features could be implemented in Java.

In fact, this mechanism has already been evaluated by some Scala core developers, as a possible replacement mechanism for implementing structural types in Scala. The current implementation is forced to rely upon reflection, but the arrival of Dynalink could change all that.

About the Author

Ben Evans is the CEO of jClarity, a Java/JVM performance analysis startup. In his spare time he is one of the leaders of the London Java Community and holds a seat on the Java Community Process Executive Committee. His previous projects include performance testing the Google IPO, financial trading systems, writing award-winning websites for some of the biggest films of the 90s, and others.

Rate this Article

Adoption
Style

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Community comments

  • JEP 269 Convenience Factory Methods for Collections

    by Stuart Marks,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Hi Ben, regarding lack of collection literals in Java, we've decided not to enhance the Java programming language to include them. Briefly, the reason is that simple collection literals would introduce too strong a dependency between the language and the core collections framework; and a general collection literal mechanism to support any collection framework (including future frameworks or third party frameworks) would be too complicated. Instead, we're enhancing the existing collection framework APIs to make it more convenient to create collections. See JEP 269, which is currently targeted to JDK 9. The APIs don't "literally" provide collection literals, but they provide much of the benefit. I also discussed this in my recent presentation at Devoxx BE 2015.

  • Import statement syntax?

    by Dave Brosius,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    People care about import statement syntax? Really? I don't think i've typed an import statement in like 10 years.

  • No Reason to Change Java Approach

    by Steve Naidamast,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    I see no reason for Java to change its cautious approach to language updates. The advantage is that since Java is primarily the guidepost of the Java Community it doesn't experience the constant upheavals that we in the Microsoft world do. As you have probably noted Microsoft is famous for creating, promoting, and then suddenly dropping technologies in favor of the newest toys it can come up with. This has done my part of the profession little good; in fact it has harmed it significantly since Microsoft professional developers do not have the in-depth knowledge bases that the Java Community appear to has.

    There are many senior (older) and more cognizant younger developers that see this and make their voices heard but we tend to be drowned out by the younger set that seems to want a sound-bite professional development orientation that their smart-devices have provided for them in their daily lives.

    Stick with "slow & steady" and it is possible that the Java Community will outlast that of the Microsoft one as it continues to rip itself to shreds every few years...

  • Nitpicking

    by Henri de Feraudy,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Actually lambda expressions arrived in C++11.

  • Java Language is stuck with JVM

    by Marco Ramirez,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Hi. Interesting Article.

    There are several changes that are proposed the Java Language,
    but, all cases should consider one thing: The Java Virtual Machine.

    As you mention with the Unsigned/Signed Numeric support,
    the issue is solved at the JVM level,
    not at the language level.

    On of the things that I notest, long time ago,
    is that the JVM is class-based, not package/superpackage based,
    altought, it looks like it does.

    Where each class definition is used as a singleton object.

    That's why things like delegates or functors,
    that are another requested feature,
    where not implemented until "invokedispatch"
    was implemented.

    JVM competitor, the CLR, uses extended versions,
    of a DLL/Shared Libraries, that match the package/superpackage concept.

    And, supports objects and classes, but,
    manages them in "packages".

    Shared Libraries ("DLL" in Windowze),
    naturally work like "superpackages",
    not surprise why Microsoft Engineers,
    just added metadata.

    Even if we still want to use the non platform dependant "*.jar" files,
    we can learn some stuff.

    The "packages" idea was also applied in Delphi,
    long time ago.

    In order to support many features,
    a radical JVM change is required.

    The "Jigsaw" proposal,
    seems to match some of this.

    Cheers.

  • unsigned arithmetic

    by Dan Howard,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    You missed the most important reason for the lack of unsigned arithmetic - that it's unsafe. If an expression contained something unsigned then it could cast that way messing up the result. There was no good reason to add complexity to the type system to support something that wasn't necessary since the 80's.

  • I don't miss Java at all....

    by Danny Trieu,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Already committed to Scala. Writing code hasn't been this fun before.

  • BigDecimal Operator Overloading(Or other solutions to ease its usage) For Finance Applications

    by Alex Freemanson,

    Your message is awaiting moderation. Thank you for participating in the discussion.


    "When you start working with BigDecimal and BigInteger (for now on lets call it BigNumbers) in java you found out the lack of operator overloading is at the very least annoying.

    Instead of writing
    double a = b + c * d

    you need to write something like
    BigDecimal a = b.add(c).multiply(d);

    This is not only awful and verbose, it is also "wrong"! You got it? Yes! In standard mathematical infix notation, * has more precedence than +, so
    b + c * d

    means
    b + ( c * d ) //multiplication comes first!


    Well, bignumutils allows you to write
    //a = b + c * d
    BigDecimal a = b.add((c.multiply(d));

    as
    //a = b + c * d
    BigDecimal a = op(b, PLUS, c, TIMES, d);
    "

    Link: code.google.com/p/bignumutils/

    Some other links about this from my notes:

    www.adam-bien.com/roller/abien/entry/operator_o...
    blog.joda.org/2007/07/java-7-what-to-do-with-bi...

  • Anything can be argued.

    by Jim Balter,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    "Java’s conservative design philosophy has arguably been one of the reasons for its success"

    Yeah, and the design philosophies of C and C++ are arguably among the reasons for the success of those languages. But it's not a *good* argument.

  • Re: Nitpicking

    by Ben Evans,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Thanks - will fix.

  • Re: JEP 269 Convenience Factory Methods for Collections

    by Ben Evans,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Thannks Stuart - looks great. Really appreciate all your efforts here.

  • RE: More Expressive Import Syntax

    by Steve Marivelle,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    You make some interesting points about import statements but I disagree that they need to be more expressive...people should really learn about importing specific classes (even if they are same names from different packages). Perhaps the classes don't need to be imported at all and just one feature is needed from one or the other. There are ways around this and no need for the folks to come up with "nice to have" features just to solve a lazy development problem. Just my 2 cents worth.

  • Re: I don't miss Java at all....

    by Faisal Waris,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    It's been a while since I wrote Java code but I do remember that it felt like doing "busy work". Newer languages like Scala and F# are much more expressive and so why not take advantage of them while still leveraging the existing ecosystems, respectively.

  • Reified generics are much more than an extension of generics to primitive types

    by Manish Java,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Reified generics would allow doing the following:


    class Foo<T> { static T bar; }

    class Foo<T> { void bar() { T baz = new T(); } }

    class Foo<T> { void bar() { Class<T> baz = T.class; } }


    They would also prevent the following:


    void foo(Collection<Bar> bars) { Collection baz = (Collection) bars; }


    Having used reified generics in other programming languages, I find their absence in Java quite problematic and limiting.

  • String interpolation

    by Thai Dang Vu,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    I find it very convenient.

  • Re: RE: More Expressive Import Syntax

    by Kedar Mhaswade,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    I agree. Having (for instance) both SFuture and Future (or UtilDate and JodaDate) in a single importing class not only indicates complex fusion of ideas, but also indicates variable (in this case, Class) naming problems leading to code reading nightmares.

  • Reified generics and type erasure

    by Marek Kolodziej,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    I don't think most people care about reified generics solely because they want primitive collections - that specialized issue (pun intended) is already being addressed by Project Valhalla. The issue here is different paths of execution depending on the type, which is currently erased.

    In Java, this is very hard, unless you explicitly provide a Class instance that you can query, along with the original generic type. In Scala, you can do something like this, but it's hellish - and this is a trivial example. For more elaborate cases, this would be even worse.

    scala> def specializedBehavior[T : ClassTag](s: Traversable[T]) =
    | println(s"got ${s.getClass.getName}[${implicitly[ClassTag[T]].erasure.getName}]")

    scala> specializedBehavior(Vector(1, 2))
    got scala.collection.immutable.Vector[int]

    scala> specializedBehavior(Vector(1.41, 3.14))
    got scala.collection.immutable.Vector[double]

    scala> specializedBehavior(Set("foo", "bar"))
    got scala.collection.immutable.Set$Set2[java.lang.String]

  • Java is missing almost all features in c# and visual basic

    by Lee YuQi,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Except unsafe features(in c#) such as unverifiable pointers,type reference, cdecl style vararg. And On Error directive (in visual basic) .

  • Re: No Reason to Change Java Approach

    by Christian Freund,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    I work with C/C++ on Windows and Linux for a longer time than Java exists and I experienced in my all-day-worklife that almost all Java-developers are not having "in-depth knowledge bases" that are better than the ones of our COBOL and C++ Developers. The argument is total nonsense. There may be a few people that know Java almost completely and have a big experience and expertise.

    The most code I see nowadays in the finance-sector where people replace a binary of 200KB that has its work done in 20ms with a java-monster that needs over 100MB and runs 800ms for the same task on the same platform. The only way to get java-stuff running smooth is to write deamons in java that preload everthing and consume a lot of resources even while there is nothing happening and then it is a success when it runs 20 times slower than the native solution and not 100 times.

    The thing where Microsoft implements new languages and approaches is still something that you do not need to follow and no F# VB classic vs. .net or Office-Integration scripter is working on a performance-critical level. That is like the 99% of people learning Java nowadays in university and will never setup any software-architecture that will be professionally used. So dont compare bananas with apples here.

    The languages that are removed and appear are even compatible so someone doing classic VB and now VB.net for his office-tool-automation can start into it with a big experience and does not need to learn a whole new thing.

  • Very good.

    by Brad Pickler,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Very good conclusion. I agree with you when you said: "Java’s conservative design philosophy has arguably been one of the reasons for its success, but it has also attracted a lot of criticism from younger developers impatient for faster change within the language"

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

BT