Great! :)
Thanks, we'll contact you soon.
Java is a major release of the Java 8 functional programming language that introduced several new features, including lambda expressions and streams. These features have made Java a more robust and expressive language, and they have also made it easier to write concise, concurrent, parallel, functional, readable, and maintainable code.
Java 8 features are shaping the future of modern software development. First, they are making Java a more powerful and expressive language. This is attracting new developers to the Java platform and making it a more attractive choice for developing modern Java applications.
In this blog post, we will explore some of the most important new features of Java 8 and how they can be used to solve modern software development challenges.
From modern Java applets to desktop GUIs, Java has been used in many ways, but most people are familiar with Java for its use on the backend in N-tier web applications. Throughout the 1990s and 2000s, OOP dominated the programming landscape.
Business use cases were modeled as objects, with classes designed in hierarchies as chains of inheritance and complex object graphs represented through object composition.
An entire discipline, known as the Gang of Four (GoF) design patterns, was preached and practiced as the most powerful approach to model a business use case as an OO solution. These objects representing business entities in Java were popularly known as Plain Old Java Objects (POJOs).
POJOs were powerful in another way. They reflected what was then the ubiquitous way of representing records in relational (RDBMS) databases. A single POJO, or entity, represented one record in a database table. A collection of POJOs represented a query of records from a database table.
The joins between tables mirrored an object graph in modern Java. New frameworks to simplify the persistence of objects in an RDBMS database, called object-relational mappers, became commonplace. The most popular Hibernate has been used in many n-tier applications.
In the late 2000s and early 2010s, the software development landscape began to undergo significant structural changes. Cloud-hosted solutions replaced the server room, and data analytics emerged as a response to the arrival of Big Data and the need to understand it all.
The CPU had reached its limits, and distributed computing rose to meet this growing demand for computing power. Software applications ran across clusters of server instances, and distributed database solutions arrived to meet the demand for storing and querying Big Data in a way that RDBMS simply no longer could.
New architectures for these emerging technologies made object-oriented programming obsolete, and POJOs persisted in discrete transactions and weren't as effective anymore. The data streaming architecture that we know today uses the log representation of data, where data is stored as events in an event log.
Another architecture that has increased in popularity as a replacement for the POJO model is CQRS or Command Query Responsibility Segregation. Rather than updating single-row records in an RDBMS database, the changes to those entities are stored as events in an event log and persisted to NoSQL databases, such as Cassandra or DynamoDB.
Functional languages that had emerged in the early 2000s seemed a natural fit for working with these data streams. Rather than manipulating POJOs with procedural logic, applying the business logic by mapping functions over the data as it flowed was the more elegant solution.
Where Java classes have "methods," functional languages exalted these methods by declaring that functions were themselves objects. Data streaming and concurrency frameworks, such as Kafka, Spark, and Akka, are written in Scala, arguably the most popular functional language on the market today.
Under Sun, the original owner of the Java language, Java releases didn't progress according to a strategic vision. New versions were released after it was deemed that enough features and bug fixes had accumulated to justify a release.
From versions 1 through 7, Java remained fundamentally the same -- an object-oriented language designed to develop n-tier web services. Facing the pendulum's swing from object-oriented to functional, Java set about re-imagining itself as a hybrid of object-oriented and functional features.
In March 2014, Java 8 was released. With the incorporation of several features of Java 8 functional programming, Java represented the most significant transformation between any prior releases. The functional new features of Java 8 largely mirror those found in Scala, Java's functional JVM sister language.
Traditional imperative Java 8 functional programming presents several scenarios where a request for data returns nothing. An uninitiated class field, the first item of an empty list, or no database record for a given query all result in null.
Handling situations that can produce null values requires defensive programming with if-else logic to check for empty lists and uninitiated fields. Failure to catch null situations results in the dreaded NullPointerException, which seldom results in graceful error handling.
Optionals are a Java 8 functional programming construct borrowed from the Scala Option. Optional is a context in which values or objects are contained. If there is data, then the data is contained in that Optional.
In the above null scenario, the result is an Optional.empty. The values within Optionals can be manipulated by applying functions through Option.map(). The benefit of this is that Optional.empty handles null scenarios in a more deterministic way without expensive exception handling.
Pre-Java 8, logic was contained in class members known as methods, which are functions that essentially belong to the class in which they are defined.
Lambdas are simply functions that have been elevated to object status. They can exist independently of the classes in which they are defined and, as objects, can be passed to other functions and methods as parameters.
They fit well within the functional programming space and are arguably its most defining feature. Not only can lambdas be mapped over Optionals to transform the values contained therein, but they can also be mapped over Java collections and streams.
Before lambdas, working with collections required verbose imperative coding to manipulate the collection mechanically. Additionally, mapping lambdas over collections results in a more algebraic style of reasoning and more concise code.
Streams, which should be distinct from input and output streams of the original modern Java IO library, are powerful functional new features of Java 8. Java 8 streams are a functional representation of collections and effectively an abstraction of data sources.
While streams can be created from finite Java collections, they can also be attached to a data source that continually produces or generates data. This provides an ability to transform, enhance, and filter data elements from an effectively infinite source with the same functional programming approach used on finite collections.
Coding is more concise when a continuous data stream can be reasoned about as a distinct data set.
As previously mentioned, Java was initially designed as an object-oriented language, and to many modern Java developers, it remains fundamentally that way. N-tier web services backed by relational databases are still very much with us, and representing business entities with POJOs is a regular practice.
While early adopters of Java 8's functional features have become passionate about this new paradigm, a considerable wellspring of talent still exists in the object-oriented space.
Java 8 functional programming represents a more algebraic way of thinking, while imperative programming is more mechanical and logical. Whether learning a purely functional language from scratch or learning how to use the new Java 8 features, many consider functional programming to present a steep learning curve to the novice developer. Recruiting talent in a tight labor market is a serious concern for many businesses staffing a project.
Adoption of functional or "fancy" new Java 8 features should also consider the complexity and side-effects of those features. Eager and pervasive use of lambdas by early adopters can have adverse consequences, and inconsistent use of lambdas affects the readability of code.
In addition, Java's verbosity should be viewed as a good thing. Sometimes, by intentionally writing extra code, you will produce code that's easier to understand and maintain. Functions are great tools, but they should be used responsibly and not just to decrease the number of characters in code. Functions should be used to create code that is easy to understand. This will help you produce high-performing systems that are easier to maintain in the long run.
Furthermore, new features often need to be more stable and highly performant when it comes to any language. You should determine if the modern Java version you're using is stable and if that version will be supported over the next two to three years. POCs and spikes are an excellent way to test new features for stability and performance when in doubt.
A good approach for deciding whether to favor an object-oriented or a functional approach is to base your decision on the technology stack for the project. Here are some perspectives and use cases to consider:
Optionals are the most straightforward functional feature to work with, and they're a good starting point for those new to the functional paradigm. Not only do they solve the age-old problem of the NullPointerException, but they also make for better modeling of the use cases that result in nulls.
For example, an Enrollment entity with an empty list of students is not an error case that should throw an exception since it might just be a course that hasn't yet enrolled any students. It's a good idea to begin the move to Java 8 functional programming by replacing verbose yet brittle defensive programming with a functional approach more representative of these use cases.
Scalable, commercial solutions are seldom, if ever, written in raw Java. Open source frameworks and libraries are often used to abstract away the boilerplate modern Java code of REST interfaces, persisting data and generating the presentation layer.
These frameworks often require lambdas in their interfaces and method signatures. Since these frameworks are built using recognizable patterns, conforming to their interfaces results in code that is more likely to conform to patterns and best practices.
For example, the Spring framework uses lambdas for CRUD logic in its persistence templates. Akka, a popular concurrency framework, uses lambdas for the receive function in its actors, the core unit of Akka design.
N-tier applications are still required and found in abundance, but new architectures have partially displaced them in response to data analytics, distributed computing, and even streaming technologies. POJOs persisted by CRUD operations are the mainstay of N-tier apps. As a result, they rely more heavily on the original object-oriented design of the modern Java language.
New technologies, however, don't see the data as discrete RDMBS records but rather as a continuous stream of data flowing through the application. In the data modeling sense, these streams are more amenable to Java 8 streams, as mapping, transforming, and enhancing functions over the data are more practical in this case.
• A Kafka cluster to enqueue arriving data.
• Any combination of Spark Streaming and Akka Streams to enhance and transform the data.
• A Cassandra database to capture the aggregate result.
Java streams and lambdas would be recommended over traditional object-oriented Java programming in this case.
From its earliest days as the platform-independent language for web applets and desktop apps, modern Java has remained relevant by growing to meet the needs of a changing technology world.
Concise languages with opinionated frameworks, such as Ruby on Rails, have arisen for rapid development of defined scope applications, and functional languages have grown in popularity due to their effectiveness in data analytics and working with event logs.
Java has faced these changes to the technology landscape by adapting. Java 8 still serves the classic n-tier web application through frameworks like Spring Boot and Hibernate, and it’s as effective as ever at delivering as an object-oriented language.
For newer technologies and approaches, like data analytics and CQRS event logs, Java 8 features powerful functional tools. By choosing a strategy with the language features and open source frameworks and libraries most suited to the project, new features of Java 8 have the power and flexibility to meet your needs.
Find out how our expert Java developers will add value to your project by scheduling a call with us!
Integrant’s Vision is to transform the software development lifecycle through predictable results.