Back to Java

I wrote hundreds of thousands of lines of code in Java between 1996 and 2002. I wrote web frameworks, spreadsheets, and much much more in Java 1.0 through Java 1.4.

Compared to mid-90's (pre-templates) C++, Java was a totally amazing language. And the JVM is the best run-time for any computer language.

The Long Strange Trip

In 2002, I started doing C#. Then Ruby. Then Scala. Some Haskell and most recently, I've been doing a lot of Clojure.

So, the amount of Java 1.5/Generics I did up through this year is very limited.

Got a Java Gig

This year, I got a gig writing some very interesting code (writing a compiler). But, the client is a Java shop. The project already had a Java base. And the client was adamant that the project be done all in Java... not Scala... not Clojure... not JRuby, but Java.

So, I've spent the last 6 months writing thousands of lines of Java code. I've seen how Java has evolved over the last 10 years and it's shocking. Shocking how bad Java has become.

Basically, Java is pointlessly verbose. Generics add, as far as I can tell, about 20%-30% to the overall character count in a given program. While Scala's types, with type inference, are a net positive vs. Ruby (the jury is out in my mind about the weight of Scala's type system versus its value against the less typed Clojure.) Java's amazing verbose, visually distracting, ugly as anything I can think of Generics are not worth the marginal improvement in type checking vs. Java 1.4.

So, I'm going to go through what I think could make Java better without breaking the language.

This Is Is Not a "Scala Is Better Post"

Note that this post is not a "Scala is better" post. While some of the ideas in the post come from Scala, I do not see Scala (or Clojure) as a reasonable replacement for Java. Scala is just too much of a language and I've seen a ton of Scala mis-use.

Further, I think Scala has to a great degree suffered some of the same growing pains that Java did (adding weight and complexity to the language [mostly via the type system for Scala] to address marginal issues), which is not surprising given both languages' lineage.

Some of This Stuff May Be Proposed or in JDK 8

These are my thoughts. I'm not a player in JCP-land. I know about lambdas in Java 8 thanks to an excellent lunch with Sam Pullara. But I'm not up on a lot of JCP traffic, so these thoughts may be covered by JCP proposals.

Default Public

Default all declarations to public unless there's an explicit visibility level. Yes, I understand this breaks some existing code, but let's just make the default thing the right thing.

Why? Because the fewer pieces of noise we have as part of our code, the more the signal will stand out.

Last Expression Is the Return Value

Another place where the signal becomes more valuable is no longer requiring the return keyword. The last expression becomes the return value:

int plusOne(int x) {x + 1;}

This is only applicable if there are no return statements in the method. So, if you're going to use return for flow-of-control programming, you must be explicit. But for small, simple methods, getting rid of return means less visual noise.

Block Expressions

Anything in curly braces is a block expression, rather than a block statement. This means that if curly-brace-thing could be an expression, it's treated as such.

String s = {
  var d = new Date();
  "The current date is "+d;
}

Some Type Inferencing

I would like to see automatic assignment inference. Basically:

var name = "David"; // it's a String, duh

We do not need to make var a keyword. Basically, the type var becomes the "infer the type" type.

And basic left-hand-side parameter type inferencing (note the IntelliJ already uses this syntax):

 List<String> stuff = new ArrayList<~>();

And return type inferencing if there's a single path to the return value (no return statements except the last line of the method):

 plusOne(int x) // it's an int
 {
   x + 1;
 }

All of the above changes can be done with data already known to the compiler. There's no fancy algorithms needed. It's just the reduction of repeated words in the program... and with the Generics type inferencing, it's what IDEs already display to the coder anyway.

Better Equality Testing

Add === as an alias to the equals method, but require the righthand side of the === be co-variant with the righthand side.

Also, add !== as !equals.

Simple, Immutable Data Structures, with metadata

Representing simple data in Java is a huge pain. Yes, the IDEs help out a lot in terms of creating getters/setter. But at the end of the day, a lot of our programs contain raw data. So, I propose adding inner data structures to Java.

These are a combination of Scala's case classes with Clojure's metadata facility thrown in.

structs can only be inside a top-level public class.

For example:

public class MyStuff {
  struct Name(firstName = "David", lastName = "Pollak", 
              age = 49, Date birthday = null)
    
  public static boolean funWithNames() {
    var archer = Name("Archer").age(10);
    var tessa = Name().firstName("Tessa").lastName("kitten").
                age(1);
                    
    assert(tessa.age === 1);
    assert(tessa.getAge() === 1);
                    
    archer !== tessa;
  }
      
  public static void metaData() {
    var david = Name("David", "Pollak");
    Name d2 = david.setMeta("geek", "true");
    assert(david === d2); // metadata not part of equality testing
    assert(david != d2); // object instances not the same
    assert(david.getMeta("geek") === null);
    assert(d2.getMeta("geek") === "true");
        
  }
}

What do you get with a struct?

  • Immutable object creation without new and with positional constructors for each parameter as well as simply, nominal chaining of creation and new values.
  • A Map<String, String> of metadata that's associated with each struct.
  • Default values for each of the fields. This allows the addition of fields, and new versions of a library with additional parameters will not break code accessing old versions.
  • A toString method that prints the field names and values.
  • A correctly implemented equals method that does all field comparison with proper treating of null fields.
  • A hashCode method that computes the hash code (this should be cached if all of the fields are known to be immutable).
  • Field level accessor methods.
  • Per-field copiers: field(param) This allows the creation of a new instance with a new value. The metadata is copied.
  • Lots of metadata is available for each struct including the list of field names, the type of each field. This metadata could be used for a fast, efficient, automatically generated set of serializers/deserializers (e.g., JSON, JDBC, XML).

Also, please note the syntax for the definition. The type is inferred if it can be.

For each class that contains a struct, there will be a ClassName.struct interface. The interface is implemented by all the structs and the interface contains all of the shared fields. For example:

class People {
  struct Parent(String name = null, List<People.struct> kids = null)
  struct Kid(String name = null, Parent parent = null)
      
  String getName(struct person) {person.name;}
}

There are a bunch of things that can also be done with annotations (e.g., enforcing the JSON serializability of a struct, etc.)

But having lightweight way to define a data pattern at the language level makes code faster to write, easier to maintain and more readable.

Smarter Casting

Java casting is too verbose and error prone. Using the if/instanceof/cast pattern takes a lot of code and is very error prone because the class is specified twice... once in the test and once in the cast.

We can borrow one of my favorite features from Scala: pattern matching, but doing it simply:

String testIt(Object o) {
  switch o {
    String s: s;
    Number n: "It's a number: "+n;
    Date d if d.millis() > 0: {
       var formattedDate = formatDate(d);
       "It's a modern date: "+formattedDate;
       }
    default: "No clue";
  }
}

Okay... what do we have? The class is tested. If the class is a match, then assign it to the variable (which only has the scope of the line). Next, run the guard to see if the case should be taken. If so, run the expression to the right of the :.

Note that this form of switch is an expression rather than a statement. Also, note that there is no fall-through.

So, what else does this make easier?

String whatSize(int i) {
  switch i {
  v if s < 10 : "small";
  v if s < 100 : "medium";
  default: "large";
  }
}

The next thing we can do is apply it to structs (no, this is not pattern matching):

String aboutPerson(People.struct p) {
  switch p all {
  Parent p: "A parent named "+p.name+" #"+p.kids.size();
  Kid k: "A kid with "+(k.parent.kids.size() - 1)+" sibs";
  }
}

The all keyword after the express means that there must be a match. This is useful if one adds a new struct to a class because all the places where there's switching on the struct will flag a compile-time error.

Immutable Collections

Java desperately needs an excellent immutable collection package.

Basically, the package would be a pure rip-off of Clojure's excellent Map and Vector classes. We don't need a lot of fancy Scala-like collections.

Immutable collections that do not implement java.util.List or java.util.Map . Basically, these collections would be stand-alone.

Combined with JDK 8 lambdas, I'd get a whole ton of what I love from Scala and Clojure in terms of collection manipulation.

So, There You Have It

So... there you have it. I think the above would make Java a much easier to deal with language. It would reduce verbosity. It would allow dealing with data as data which is fairly common, especially when writing services that marshal data in and out of systems.

The above changes would reduce Java's verbosity without significantly detracting from Java's "it tells a story" code. And, yes, it's possible to write fairly impenetrable Scala code. We don't want to go where Scala went.

What else would I like to see? Call-by-name parameters (I don't know if they'll be part of lambdas). Properties. It would also be interesting to see if it'd be possible to have some form of global type inferencing in Java. But all that stuff is for another day.

Rock on!