Most important things to know about modern Java ☕ 8 ⇒ 11 ☕

braintelligence.pl 5 lat temu

String java = "java8" var java = "java10" – mostly Java 8 up to Java 11.

Java 8 features were here for quite some time. To be honest I didn’t even spend much time with previous versions. I’m already from that generation of programmers that started with stream operations right away. Which means that filter, map, reduce is the way to go for me. Not to mention generics, enums and other Java 5 features. From my perspective, it was already there from the beginning. Before jumping into streams we need to understand how those functional things can work with non-functional Java. These additions are not part of the core language and they are just utility package which means that Java is not purely functional. Obviously, that is not an obstacle and it’s more than enough for our daily development.

Also, come and join the facebook group: JVM programmers or follow blog fanpage (if you liked the post).

Something is missing? Give me a hint in the comment section I’ll add it!

What you’ll find here

  • Few things before going into the article: lazy/eager loading, run/compile time, immutable/mutable objects.
  • Most important things about functional interfaces.
  • Streams and what’s the difference between intermediate operation and terminal operation?
  • Keyword var. No, this is not a Javascript! This is Java 10!
  • Optional, Optionals. No more NULLs.????
  • What is a defensive copy pattern for mutable objects? On example from old date API.
  • Brand new time and date API from Java 8! ?
  • Let’s have some fun with simple code katas! Map, Filter, Reduce! ?‍?

What is missing?

  • asynchronous code – that topic is big enough for another article.

Few prerequisites to know before jumping into the article

Before going into specifics it’s good to have an understanding of a few concepts.

If you’re already familiar with these concepts jump straight to Functional Interfaces (click, click).

What is eager/lazy loading (design pattern)?

  • ? Eager loading – data initialization occurs on the spot, immediately.
  • ? Lazy loading – we just defer initialization of an object as long as it’s possible.

Those styles/patterns are used for example in Hibernate. Eager initialization is when the object is created at the program execution. Even though the object is created programmer may not use it and sometimes that looks like a total waste of memory. That’s why we have another way of doing it – lazy initialization. That way object is created at the runtime. Which means when you really need it (when you use it). Basically, lazy initialization might improve application performance. For the sake of this post you just need to have a basic idea of what it is. See example on GitHub.

Compile time vs runtime?

? Compile Time – it is a time when you build/compile your code. After a successful compilation, you have your executable file but sometimes that fails and you have stack trace everywhere. In statically typed languages like Java variable is known at compile time which means that IDE can catch errors at the very early stage like int number = “string value”; ~ typecheck error
Runtime – when the program is working (after compilation). There is a lot of things that can go wrong: NullPointerException, division by zero, running out of memory, opening a file that isn’t there and much more.

Mutability ?‍ vs Immutability ?‍

What if I told you that String str = “rick” is immutable?
Also, that you can change its contents to something else str = “rick & morty”?
Does it make sense to you? If you are beginner probably not.
How something can be immutable and can be changed in the same time?
So… Let’s make it even more complicated and add final keyword to it.
Now we have final String str = "rick".
With that you cannot assign a value to it because compiler type-check error gets in a way. Aggrr…

? final String str = "" – means we cannot change object reference so that it points into another object. In other words, we are not able to change the memory address of this object and we cannot assign a value once more.

? String str = "" – means we cannot change a state of the object that we created. What we’re actually doing is a creation of another string object with the value of (“rick & morty”) and we change its reference from str=”rick” to str=”rick & morty”.

final String str = "rick"; str = "rick & morty"; // typecheck error String str = "rick"; str = "rick & morty"; // that works // Reference is kind of address where the object is stored. // That storage is actually a JVM heap.

Steps to create an immutable class ?

  1. No setters or any other mutators.
  2. Class can’t be extended. Either final class or private constructor to achieve that.
  3. Fields are private and final.
public final class Rick { private final String id; public Rick(String id) { this.id = id; } public String getId() { return id; } } // That way there is only one Rickest Rick in the our concurrent multiverse!

Immutability is interesting concept and there is few more facts about it but this is beyond this post. Either way you may ask why immutable objects are preferred? On the one hand, you do concurrent applications meaning they run multiple threads and these multiple threads shouldn’t interfere with each other. They shouldn’t change the state of another thread, but on the other hand, there is the cost of creating these objects because each time you use the immutable object you create a new object and that will take some memory. Each time you change your object you have to take a copy of it. Basically we have to find equilibrium somewhere. What does have bigger value to you? Resources, memory or thread safety? That again depends on context but generally its better to use immutable objects as Joshua Bloch points in Effective Java.

Something to read later:

What is Functional Interface?

Functional interface – is an interface that contains only one abstract method and has a @FunctionalInterface annotation. There is no need for it, but with that interface is more readable/informative, so use it! Also thanks to that IDE shows you type-check error when you want to create more than one abstract method.

Explicit functional interface has abstract keyword

The most common example of functional interface from the standard library is probably Runnable.

@FunctionalInterface public interface Runnable { public abstract void run(); // EXPLICIT functional interface example }

Implicit functional interface – doesn’t have abstract keyword

Below example has watch() method which is implicitly abstract (meaning no abstract keyword).

@FunctionalInterface public interface Film{ public String watch(String str); // IMPLICIT functional interface example } // And using it is very simple Film film = title -> "Watching " + title " !"; // lambda expression film.watch("Rick and Morty"); // Watching Rick and Morty !

More about interfaces

Before Java 8 interfaces could only have abstract methods. Implementation of these interfaces were in separate classes. When you tried to add something new to the interface you had to provide an implementation of each class that has your interface. That was literally hell. So… Java 8 introduced a concept of default methods and static methods which allows us to use interfaces with implementations. That construct provides backward compatibility so that existing interfaces can use lambda expression without implementing methods in the implementation classes. If we’re already at the interfaces there is also little thing added with Java 9. You can define private methods inside of interface, yay. ?

interface Film { String watch(String str); default String preview(String str) { ... } // can be overriden static String preview(String str) { ... } // cannot be overriden private String adultOnly() { ... } // private method from Java 9 } class FilmImpl implements Film { @Override public String watch(String str) { // Only watch() is needed to implement! previewFilm(); return "Rick and Morty"; } private void previewFilm() { // static method - film preview/trailer Film.preview("Rick and Morty"); // default method - film preview/trailer FilmImpl film = new FilmImpl(); film.preview("Rick and Morty"); } }

Toolbox ? (most important functional interfaces)

? Supplier<T> { T get() }

Doesn’t take any object just provides a new object.

? Consumer<T> { void accept(T t) }

Accepts an object and doesn’t return anything.

? BiConsumer<T, U> { void accept(T t, U u) }

Accepts two different (or not) object and doesn’t return anything.

? Predicate<T> { boolean test(T t) }

Takes an object and returns a boolean.

? BiPredicate<T, U> { boolean test(T t, U u) }

Takes two parameters and returns a boolean.

? Function<T, R> { R apply(T t) }

Takes an object as a parameter and returns another object.

? BiFunction<T, U, R> { R apply(T t, U u) }

This function has 3 generic types T, U, R. Takes two objects as a parameter and returns another object.

Intermediate and terminal operations on stream ?

? Intermediate Operation – these lazy bastards are methods of a stream and always return a Stream<T>. As was said they are Lazy which means that they need a terminal operation to invoke them and that means that they do not process any data. They are only a DECLARATION of the operation on a given stream. Same as String str is a variable declaration.

? Terminal Operation (final) – these bastards are not lazy and they work very hard. Thanks to them computation on a stream is triggered and some side-effect is performed such as count() which can count elements, amazing!

Intermediate operations (lazy)

  • filter(Predicate<T>), map(Function<T>)
  • peek(Consumer<T>)
  • flatmap(Function<T>)
  • sorted(Comparator<T>)
  • distinct()
  • limit(long n), skip(long n)
  • takeWhile(Predicate<T>) – sorted only
  • dropWhile(Predicate<T>) – sorted only

Terminal/final operations

    • reduce
    • forEach
    • toArray
    • collect
    • min, max, count
    • anyMatch, noneMatch, allMatch
    • findFirst
    • findAny

Intermediate and terminalexample ?

.peek – mainly used for debugging purposes (returns a Stream just like all other intermediate operations).
.forEach -performs an action on each element of a stream (returns void). Triggers processing of data.
// Example data: var numbers = Stream.of("1", "12", "123", "1234"); var result = new ArrayList<>(); // This is good example of what cannot be infered by the compiler (var is not acceptable here) Predicate<String> isTextLengthValid = text -> text.length() > 2; // ---------------- intermediate operation (Lazy) ---------------- // numbers.filter(isTextLengthValid) // nothing is filtered .peek(System.out::println) // nothing happen, nothing is printed .peek(result::add); // nothing is added to List result.size() // result list remains empty // ---------------------- terminal operation --------------------- // numbers.filter(text -> text.length() > 2) // data is filtered .peek(System.out::println) // data is printed .forEach(result::add); // data is added to a List result.size(); // result list contains 2 elements

The stream can be called ONLY ONCE!

// Example data: var tools = Arrays.asList("Hammer", "Nokia 3030"); var streamOfTools = tools.stream(); // ---------------------- first stream call --------------------- // var firstCall = streamOfTools .peek(System.out::println) .collect(Collectors.joining(", ")); firstCall // Hammer, Nokia 3030 // ---------------------- second stream call --------------------- // var secondCall = streamOfTools .peek(System.out::println) .collect(Collectors.joining(", ")); // java.lang.IllegalStateException: stream has already been operated upon or closed // You can't re-use streams. // Stream forbids calling two terminal operations. // You need to create another stream if you want another terminal operation.

So… That’s why we use this construct: ?

It is more concise, stream is used just once and of course code is more readable. Plain and simple.
var tools = Arrays.asList("Hammer", "Nokia 3030"); tools.stream() .peek(System.out::println) .collect(Collectors.joining(", "));

Did you notice var? No, this is not Javascript. This is Java 10! ?

From time to time we’re going to use this Java 10 keyword. If you have coded Kotlin/Scala you know this keyword very well.

  • Explicit type in Kotlin: var str: String = "value" – is there anything to say?
  • Implicit type in Kotlin: var str = "value" – here type is inferred by the compiler.

It’s good that Java is going into that direction but after coding in Kotlin I miss that choice when I could just write:

  • var abc= "value" or var abc: String = "value"

Not to mention val which is kind of immutable, final (read-only) where var is mutable (read and write). Especially val quite useful and preferable whenever possible. The fact that most parts of our application are immutable provides lots of benefits, such as a more predictable behavior and thread safety. In the case of Java, we only have var which serves the purpose of inferring types by the compiler to some point.

// CANNOT infer types of: var rick; var rick = null; var rick = () -> System.out.println("Rick created Morty"); var rick = this::createMorty; // CAN infer types of: var rick = new Rick(); var morties = new ArrayList<Morty>(MortyFactory.makeMorties()); var dimensionPosition = new ArrayList<DimensionPosition>(); var wuut = new ArrayList<Map<String, List<Integer>>>(); var list = new List.of(1, 2.0, "3"); // OR don't use infered type Rick rick = new Rick(); List<Morty> morties = new ArrayList<Morty>(MortyFactory.makeMorties()); Collection<DimensionPosition> dimensionPosition = new ArrayList<DimensionPosition>(); List<Map<String, List<String>>> wuut = new ArrayList<>(); List<? extends Serializable> list = new List.of(1, 2.0, "3"); // I'm curious which style do you prefer? or when use you use one over another? // Do you prefer to use explicit variable declaration or var?

Optional – no more NULLs! ????

Soo… Now a bit controversial topic Optional<T>. Which some of us like and others don’t. Let’s not go further into that discussion. Personally, I like optionals and I use them when needed. Kotlin has null safety out-of-box that’s one of things why I like Kotlin. Okay then… Back to Java. Basically, you have two choices whether you want your variable to be null or not. That’s all.

// might be null String str = ... // cannot be null Optional<String> str = ...

So… We have three ways to create an instance of Optional

var opt = Optional.of("notNull") – you know that it will not contain null otherwise NullPointerException at your face.

var opt = Optional.ofNullable(mightBeNull) – you don’t know either it will contain null or not.

var opt = Optional.empty() – returns empty Optional. Before Java 8 you’d need to use a null reference here.

?Old goodies

String value = ""; if (value != null) { System.out.println(value); } else { System.out.println("Not found"); }

Higher-order functions for the rescue more complexity! (joking)

//---------- print when there is value ----------// Optional<String> optionalValue = Optional.ofNullable("value"); optionalValue.ifPresent(System.out::println); // 'value' is printed //---------- if-else with Optionals ----------// var anotherOptional = Optional.ofNullable(null); anotherOptional.or(() -> Optional.of("Rick")); // Optional[Rick] //---------- another kind of if-else ----------// value.ifPresentOrElse( e -> System.out.print(e), // 'value' is printed () -> System.out.print("Not found") // ... ); // ifPresentOrElse(Consumer, Runnable)

Java 9 gave us interoperability between Optionals and Streams.

// Let's say we want ONLY VALUES from this stream of optional integers... Stream<Optional<Integer>> stream = Stream.of( Optional.of(1), Optional.of(2), Optional.empty() ); var ints = stream.flatMap(Optional::stream); ints.forEach(System.out::println); // 1, 2

A more realistic example of optional or(): ?

class Customer { private String name; public Customer(String name) { this.name = name; } } class OrOrOr { public static void main(String[] args) { Optional<Customer> defaultCustomer = Optional.of(new Customer("better something than nothing")); Optional<Customer> customer = getInternalCustomer() .or(() -> getExternalCustomer()) .or(() -> defaultCustomer); System.out.println(customer.get()); // Rick } public static Optional<Customer> getInternalCustomer() { return Optional.empty(); // for example call from your database } public static Optional<Customer> getExternalCustomer() { return Optional.of(new Customer("Rick")); // for example call from some external API } }

Isn’t Optional more complex than a simple if-else?

Sometimes it is. But… Optional is useful because it forces you to think about the object. Another thing is that you can fluently chain calls where any call might return null. This is useful when each operation cannot be null, for example:

CreateShowRickAndMorty.start() .parameterRick(rick) .parameterMorty(morty) .execute();

There is also another Option for NULLs! ?

Option from vavr library – which is simpler to use because it is less ambiguous than its big brother java.util.Optional. Younger brother of Optional has only one method Option.of() which is less ambiguous to use in my opinion. It has two implementations:

None and Some indicating that there is or there isn’t value.

Option.of(null) // None Option.of(value) // Some[value] Option.some(null) // Some[null], not possible in Optional Option.none() // None

We will not discuss it further because it’s beyond the scope of this article, but if you’re curious you can google it. Great article that points out basics: Do we have a better Option here? by Michał Chmielarz.

? Date and time from the age of dinosaurs ??

Our IT world is going lighting fast and something that was developed ten or twenty years ago it’s often bad because it has many flaws and that’s good. We learn from mistakes, and next time we do it better. Sometimes our own mistakes, sometimes from other people mistakes. Going back to Date API we have few old ways of creating a date from java.util.Date and you shouldn’t use any of them. As was said these objects have many flaws and are part of JDK 1.0/1.1 which was created around 1996-1997 (old and deprecated). Let me show you a few examples.

Date date = new Date(); // get date & time of now (new Date is mutable) Calendar calendar = Calendar.getInstance(); calendar.set(1997, 1, 10); // 10.02 (10 February) instead of 10.01 (10 January) (not very intuitive) // Mutable means you can modify it (we don't want that) var morty = new Morty(); var date = morty.getCreatedAt(); print(morty.getCreatedAt()); // Mon Nov 26 20:51:02 CET 2018 date.setTime(0L); print(morty.getCreatedAt()); // Thu Jan 01 01:00:00 CET 1970 // We can overcome that issue with defensive copy pattern public class Morty { private Date createdAt; public Date getCreatedAt() { return new Date(this.createdAt.getTime()); } } // Basically we create new Date object with the right value in it. // Overheads - new object on each call which means more work for our Garbage Collector.

Transforming old ? into modern Date API

Date date = new Date(); Instant instant = date.toInstant();

? Finally, brand new time and date API ?

import java.time – new time API that introduces a few new concepts and ensures interoperation with legacy API.

All the classes are immutable and thread-safe.

1⃣ Instant – which is a point on the timeline

Precision – nanosecond

  • Instant.ofEpochSecond(0) – is 1970-01-01T00:00:00Z
  • Instant.now() – is the current Instant. For example 2018-10-15T13:15:00.991877Z
  • Instant.MIN – is 1,000,000,000 years ago.
  • Instant.MAX – is December 31 of the year 1,000,000,000.

2⃣ Duration – is the time that elapsed between two Instants

Precision – nanosecond

Instant start = Instant.now(); // time consuming computations Instant end = Instant.now(); Duration.between(start, end).toNanos(); // e.g. 650000

3⃣ LocalDate – for dates without time zone

Precision – day

// Let's say we want to represent a date like 1754.01.24 LocalDate.of(1754, 1, 24); // 1754-01-24 // Instant precision is not needed here. // That's why we have LocalDate which has only a day precision

4⃣ Period – is the amount of time between two LocalDate

Precision – day

var start = LocalDate.of(2000, Month.JANUARY, 10); // better to use Month enum (more descriptive) var end = LocalDate.of(2010, 3, 20); Period.between( start, end ) // P10Y1M10D .getYears() // 10 .getMonths() // 2 // OR .get(ChronoUnit.YEARS) // 10 .get(ChronoUnit.MONTHS) // 2 // You may want to get actual number of months between those two dates start.until(end, ChronoUnit.MONTHS);// 120

5⃣ LocalTime – for the time of a day (similar to a LocalDate)

Precision – nanosecond

LocalTime.of(14, 15); // 14:15

6⃣ Zoned Time

Date and time precision is in nanoseconds.

var firstConference = ZonedDateTime.of( LocalDate.of(2000, Month.JANUARY, 20), LocalTime.of(14, 15), ZoneId.of("Europe/Warsaw") )
Let’s make the conference! ?
// First I want to schedule a conference a year later var europeTime = firstConference.plus(Period.ofYears(1)); // Also this time we want to invite people from Africa/Nairobi var africaTime = europeTime.withZoneSameInstant(ZoneId.of("Africa/Nairobi")) // It would be cool to format them accordingly to the specification that we have (somewhere) europeTime.format(DateTimeFormatter.ISO_DATE_TIME); // 2001-01-20T14:15:00+01:00[Europe/Warsaw] africaTime.format(DateTimeFormatter.RFC_1123_DATE_TIME); // Sat, 20 Jan 2001 16:15:00 +0300
Complete example: ?
var europeTime = ZonedDateTime.of( LocalDate.of(2001, Month.JANUARY, 20), LocalTime.of(14, 15), ZoneId.of("Europe/Warsaw") ); var africaTime = europeTime .withZoneSameInstant(ZoneId.of("Africa/Nairobi")) .format(DateTimeFormatter.RFC_1123_DATE_TIME);

New HTTP Client ?

Finally, HTTP client that is nice to use. Supports HTTP/2, WebSocket and Reactive Streams.

java.net.http stuff is created with a builder pattern. Plain and simple static method newBuilder().build().

Three important objects to know:

? HttpClient: send, sendAsync
? HttpRequest: uri, header, headers, GET, POST, PUT, DELETE
? HttpResponse: uri, headers, statusCode, body

And what would be life without an example:
var httpClient = HttpClient.newBuilder() .build(); var mortyHttpRequest = HttpRequest.newBuilder() .uri(URI.create("https://google.com")) .GET() .build(); HttpResponse<String> response = httpClient.send(mortyHttpRequest , HttpResponse.BodyHandlers.ofString()); response.statusCode() // 3xx
Waait, what? Why there is 3xx status code in the response? ?

Actually, HTTP client by default doesn’t follow redirects. We need to specify that in our client by simply adding a method:

followRedirects(HttpClient.Redirect.NORMAL)

var httpClient = HttpClient.newBuilder() .followRedirects(HttpClient.Redirect.NORMAL) .build(); var mortyHttpRequest = HttpRequest.newBuilder() .uri(URI.create("https://google.com")) .GET() .build(); HttpResponse<String> response = httpClient.send(mortyHttpRequest , HttpResponse.BodyHandlers.ofString()); response.statusCode() // now 2xx as expected!

It is nice, but for me, restTemplate from Spring is a bit better (or I just got used to it). On the other hand, let’s say we need two or more instances of restTemplate with different timeouts that way we need to create two different beans and things might get a little messy, so it might be better to use java.net.http in that case by adding .timeout() method to a request.

More snippets on OpenJDK docs (also there is asynchronous code – worth a minute to read).
Immutable collections from Java 9
  • var emptyImmutableList = List.of("1", "2")
  • var emptyImmutableSet = Set.of("1", "2")
  • var emptyImmutableMap = Map.of(1, "one", 2, "two")

My thoughts about modern Java Stack: ?

Obviously, the tech stack is heavily dependent on the company and team that you work with.

Those are only my personal (for now) preferences and they change from time to time.

  • Consider using docker and docker-compose (great thing for learning).
  • After that try kubernetes or docker-swarm for container orchestration (more for production than learning)
  • Consider using Gradle over Maven.
  • Use docker to create monitoring with ELK stack, Grafana, Prometheus, InfluxDB and others – choose your poison. ?
  • Learn Kotlin ?‍?‍?‍

While ( lots of OR ????? )

~ Yerba in my case […]

Other features of Java: ?

  • JShell – REPL, you can now run your Java code in terminal, to be honest, I never used that. Might be useful for bit shifting but that is one of the things that you don’t do very often (or just I don’t do that).
  • Modularity (Project Jigsaw) – the main goal of project jigsaw is to make easier for developers to construct and maintain libraries. Also, it’s useful for guys from the Java platform. Quick-start from OpenJDK or Baeldung introduction to modularity.
  • Garbage Collectors:

Useful libraries: ?

? Guavaif you stop someone on the street and ask him for the most popular google library he would probably say Guava. Even though we have Java 8+ now there is still a few useful things in Guava. ImmutableCollections, Preconditions, I/O and more. Another thing is that not everyone has a possibility to work with the newest JDK so guava might be the answer. Check their wiki.

? Vavr– another must have is Vavr which allows us to write more functional code. Probably even more important library than Guava.

? JOOλanother great library that has all the pieces that Java 8 misses.

? Vertx.io – event bus, a non-blocking library for asynchronous applications. Didn’t have a chance to use it, so I can’t say much about it, but I heard a lot of good things about it. The question is whether to introduce another library if we have Spring 5?

Articles to read: ?

About Java in general:

About Optionals:

Few conference/meetups talks: ?

Cover photo by Todd Quackenbush on Unsplash.

Idź do oryginalnego materiału