DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Introducing Graph Concepts in Java With Eclipse JNoSQL
  • Using Java Stream Gatherers To Improve Stateful Operations
  • How to Merge HTML Documents in Java
  • The Future of Java and AI: Coding in 2025

Trending

  • Creating a Web Project: Caching for Performance Optimization
  • Developers Beware: Slopsquatting and Vibe Coding Can Increase Risk of AI-Powered Attacks
  • Manual Sharding in PostgreSQL: A Step-by-Step Implementation Guide
  • How to Merge HTML Documents in Java
  1. DZone
  2. Coding
  3. Java
  4. What Is Project Amber in Java?

What Is Project Amber in Java?

Curious about Project Amber? Take a look at the ongoing effort to enhance the productivity of Java developers everywhere.

By 
Mahmoud Anouti user avatar
Mahmoud Anouti
·
Updated Dec. 30, 17 · Tutorial
Likes (13)
Comment
Save
Tweet
Share
17.9K Views

Join the DZone community and get the full member experience.

Join For Free

In this post, we’re going to delve into some details of the features being targeted in Project Amber, which was introduced in early 2017 by its lead and language architect Brian Goetz. This project aims to add some really cool beans to the Java programming language that improve the developer’s productivity when writing Java code.

As this is still work in progress, other features could be added to the project in the future. Even though these features may seem to have been addressed late in the Java timeline, it’s worth considering that the Java team has historically been rather cautious in introducing new features to evolve the language, as Goetz explains in a talk about the project.

Local Variable Type Inference

In Java 5, generics were introduced with the possibility for the compiler to infer type arguments during generic method calls, as shown in the below example:

private <T> void foo(T t) {
    ...
}

public void bar() {
    this.foo(1);  // with implicit type, compiler infers type argument
    this.<Integer>foo(1); // with explicit typing
}


Further enhancements to type inference were done over the next releases, including the diamond operator in Java 7, enhancements in Java 8 along with lambda and stream support, and in Java 9 allowing the diamond operator in anonymous classes when the inferred type is denotable. With this feature (specified in JEP 286), the compiler will be able to infer declaration types of local variables, subject to certain limitations. The main requirement for the compiler is that the initializer needs to be included with the variable declaration. With this enhancement, a statement like:

File inputFile = new File("input.txt");


Could be written as:

var inputFile = new File("input.txt");


Like any other form of type inference, the main benefit is avoiding the redundant typing of the variable type when it can be easily known from the right hand side of the assignment. It is important to remember here that the var keyword does not mean that the variable is dynamically typed. It is just a syntax to avoid writing the manifest type of the local variable; the static typing nature of Java remains intact. Strictly speaking, var is a reserved type name that gets desugared to the variable initializer type by the compiler.

Not every form of local variable declaration can use var to infer the declaration or manifest type of the variable. The following cases do not allow the use of var:

  • Local variables without initializers, such as File inputFile;
  • Local variables initialized to null.
  • Initializers that expect a target type, such as a lambda, a method reference or an array initializer.

As such, below are examples where type inference is not allowed:

// not allowed, lambda expression needs an explicit target-type
var func = s -> Integer.valueOf(s);

// not allowed, method reference needs an explicit target-type
var biConsumer = LogProcessor::process;

// not allowed, array initializer needs an explicit target-type
var array = { 1, 2 };


The majority of local variable declarations in typical code could benefit from this feature. For example, in the OpenJDK codebase, only 13% of local variables cannot be re-written using var. Therefore, the cost of broadening local type inference to include cases like the above may be too high compared to the amount of applicable code that can further benefit from it.

This feature is included in the planned Java 10 release, which is expected to be available in March 2018.

Enhanced Enums

This feature enhances enums in two aspects. First, it allows declaring a generic enum, which combines the flexibility and type safety of generics with the simplicity and powerful semantics of an enum. Second, it enhances enums so that an enum constant that is declared as generic or overrides behavior via a class body gets its own type information, along with its own state and behavior.

In some use cases, we may need to define enum constants where each is bound to a certain type. A typical example is an enum that contains mappings to Java types, where a generic enum can be used shown in the below JsonType example:

public enum JsonType<T> {
    STRING<String>(String.class),
    LONG<Long>(Long.class),
    DOUBLE<>(Double.class), // can use a diamond operator to infer
    BOOLEAN<>(Boolean.class),
    ...

    final Class<T> mappedClass;

    JsonType(Class<T> mappedClass) {
        this.mappedClass = mappedClass;
    }

    public T convert(Object o) {
        ...
    }
}


In this case, the enum constant STRING has a sharper type JsonType<String>, enum constant LONG is of type JsonType<Long>, and so on. One could further customize each enum constant with additional state and/or methods:

public enum JsonType<T> {
    STRING<>(String.class),

    // LONG is has an anonymous class as type now,
    // we can use a diamond as long as inferred type is denotable
    LONG<>(Long.class) {
        public String desc = "Long JSON type";

        public boolean isLongValue(JSONValue value) {
            ...
        }
    },
    DOUBLE<>(Double.class),
    BOOLEAN<>(Boolean.class),
    ...
}


Since the enum constant LONG has a class body, its type is an anonymous class whose supertype is JsonType<Long>.

This feature is discussed more in JEP 301 which still has a “Candidate” status, so not all risks may have been addressed and is not expected to reach JDK 10.

Enhancements to Lambda Expressions

These are a couple of additional features added to lambda along with improving type inference for methods involving lambdas as arguments. The first feature is the ability to use an underscore to denote an unused parameter in a lambda:

BiFunction<Integer, String, String> biss = (i, _) -> String.valueOf(i);


As of Java 9, an underscore can no longer be used as an identifier, and with this feature, it now carries a special meaning in the context of lambdas.

The second feature introduced in this JEP for lambdas is the ability to shadow variables declared in the enclosing scope of a lambda by re-using the same variable names to declare the lambda parameters:

int i = 0;
// can declare lambda parameter named i, shadowing the local variable i
BiFunction<Integer, String, String> biss = (i, _) -> String.valueOf(i);


Currently, it is not allowed to re-use i in the lambda expression because lambdas are lexically scoped and generally do not allow shadowing variables. Last but not least, improving overload resolution for methods invoked with either a lambda or a method reference as an argument is optionally targeted in this project. This should fix false compilation errors that may be commonly encountered when writing methods that accept functional interfaces:

m(Predicate<String> ps) { ... }
m(Function<String, String> fss) { ... }

m(s -> false) // error due to ambiguity, although Predicate
              // should have been inferred

class Foo {
    static boolean g(String s) { return false }
    static boolean g(Integer i) { return false }
}

m(Foo::g) // error due to ambiguity, although boolean g(String s)
          // should have been selected


Pattern Matching

The next feature in the Amber project introduces a powerful construct called a pattern. The motivation behind this feature is the commonly used boilerplate code shown below:

String content = null;

if (msg instanceof JsonMessage) {

    content = unmarshalJson((JsonMessage) msg);

} else if (msg instanceof XmlMessage) {

    content = unmarshalXml((XmlMessage) msg);

} else if (msg instanceof PlainTextMessage) {

    content = ((PlainTextMessage) msg).getText();

} ...


Each condition branch checks if the object is of a certain type, then casts it to that type and extracts some information from it. Pattern matching is a generalization of this “test-extract-bind” technique and can be defined as:

  • a predicate that can be applied to a target
  • a set of binding variables that are extracted from the object matching the predicate

Instead of the above, we could apply a type-test pattern on the object msg using a new matches operator. As a first step, this would remove the redundant cast:

String content = null;

if (msg matches JsonMessage json) {

    content = unmarshalJson(json);

} else if (msg matches XmlMessage xml) {

    content = unmarshalXml(xml);

} else if (msg matches PlainTextMessage text) {

    content = text.getText();

} ...


The existing switch statement already makes use of the simplest form of patterns: the constant pattern. Given an object whose type is allowed in a switch statement, we test the object if it matches any of several constant expressions. Now the switch statement would also benefit from type-test patterns:

String content;

switch (msg) {

    case JsonMessage json:      content = unmarshalJson(json); break;

    case XmlMessage xml:        content = unmarshalXml(xml); break;

    case PlainTextMessage text: content = text.getText(); break;

    ...

    default:                    content = msg.toString();
}


This switch could be now be considered a “type switch” but it’s actually a generalized switch that can take other types of patterns; in this example, type-test patterns. In addition to generalizing a switch statement, this feature suggests a further improvement by allowing a switch to be also used as an expression instead of a statement, making the above look even more readable:

String content = switch (msg) {

    case JsonMessage json      -> unmarshalJson(json);

    case XmlMessage xml        -> unmarshalXml(xml);

    case PlainTextMessage text -> text.getText();

    ...

    default                    -> msg.toString();
}


Another kind of a pattern that can be used for matching and that is further proposed in this JEP is a deconstruction pattern. It matches an expression against a certain type, and extracts variables based on the signature of an existing constructor in the matched type:

// Using a deconstruction pattern with nested type patterns
if (item matches Book(String isbn, int year)) {
    // do something with isbn and published
}


What’s really nice about this is that the component String isbn is itself a type-test pattern that matches the isbn property of the Book object against a String and extracts it into the isbn variable, and the same for the int year pattern. This means patterns may be nested within each other. Another example is nesting a constant pattern in a deconstruction pattern:

// Using a deconstruction pattern with a nested constant pattern
if (item matches Book("978-0321349606")) {
    ...
}


Finally, within a future work scope of this JEP are sealed types, which allow defining types whose subtypes can be limited by the programmer. With this feature, a type could be marked as “sealed” to mean that the subtypes are restricted to a known set. It is similar to a final type, but instead gives the programmer a way to restrict the hierarchy of child types. For example, in an bookstore application we may only need to handle certain items like books, DVDs, etc. In this case we could seal our BookstoreItem parent class. This can be very useful because it removes the burden of handling default cases whenever we are switching over the possible subtypes.

Data Classes

A lot of times all that a class is responsible of is holding data. And with the current way to writing such classes, we usually end up writing too much boilerplate to customize methods based on the state of the object, e.g. by overriding equals(), hashCode(), toString(), etc. Furthermore, there is nothing in the language that just tells the compiler or the reader that this class is a simple data holder class.

In order to define such semantics, this feature seeks to introduce data classes of the following form (syntax and semantics still under discussion; for a comprehensive discussion see this document on the OpenJDK site):

__data class Point(int x, int y) { }


which would be translated into the following at compile time:

final class Point extends java.lang.DataClass {
    final int x;
    final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    // destructuring pattern for Point(int x, int y)
    // state-based equals, hashCode, and toString
    // public read accessors for x and y
}


The general idea is to have the compiler generate all that boilerplate for the programmer (similar to what is done with enums), and have the programmer still able to override methods like equals() or implementing interfaces. At this point, some design decisions are in progress, for example it is undecided whether immutability will be enforced or if mutability could be allowed, which would definitely impact the implementation of this feature along with the thread-safety of such classes.

Summary

Project Amber aims to bring features that can make writing Java code more readable and concise, and target specific use cases such as using generic enums or data classes. Local variable type inference enables the programmer to defer thinking about the variable type to whenever it is initialized. Enhanced enums allows a more flexible approach to solving specific problems with less code. Lambda leftovers improves lambda support with a couple of small changes. Pattern matching provides a powerful construct to writing conditional logic and reduces boilerplate coding. And finally, data classes allow the programmer to segregate plain data carriers from other classes. So far, only local variable type inference is planned in the Java 10 release, but with the accelerated release timeline of Java, the others can be rolled out as soon as they are ready.

Java (programming language)

Published at DZone with permission of Mahmoud Anouti, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Introducing Graph Concepts in Java With Eclipse JNoSQL
  • Using Java Stream Gatherers To Improve Stateful Operations
  • How to Merge HTML Documents in Java
  • The Future of Java and AI: Coding in 2025

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!