In today's #REVIEW post I want to review one of my latest Java books: Effective Java. I was looking for a more advanced book about Java and therefore I found this book from Joshua Bloch. I bought the third edition of this book which was updated on 27th December 2017 for Java 7, 8, and 9. The book titled itself as the “The Definitive Guide to Java Platform Best Practices”. It has about 420 pages and it took me two weeks to read through it. The following topics were covered with best practices advice and example code:
- Creating and Destroying Objects
- Methods Common to All Objects
- Classes and Interfaces
- Generics
- Enums and Annotations
- Lambdas and Streams
- Methods
- General Programming
- Exceptions
- Concurrency
- Serialization
In this blog post, I will cover my top five Java learnings from this book.
Learning #1: Default interface methods
Since Java 8 we are now able to add default methods to our interfaces. With this feature, you can implement a default version of your method in the interface and the implementing class can override the default implementation as needed. Therefore you are able to extend your interface definition without breaking your existing codebase and you can add further functionality. You declare the default method with the keyword default after the visibility modifier. The following fictional example describes this new feature:
1 2 3 4 5 6 7 8 9 | public interface MyJava8Interface { public void doFoo(String foo); public default void doBar(String bar) { System.out.println(bar); } } |
Two possible implementations can look like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public class ClassOne implements MyJava8Interface { @Override public void doFoo(String foo) { System.out.println("foo is: " + foo); } } // explicitly overriding the default doBar(String bar) method public class ClassTwo implements MyJava8Interface { @Override public void doFoo(String foo) { System.out.println(foo + foo); } @Override public void doBar(String bar) { System.out.println(bar.toUpperCase()); } } |
Calling both methods results in:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public class TestJava8DefaultMethod { public static void main(String[] args) { ClassOne one = new ClassOne(); // result: 'foo is: Hello World!' one.doFoo("Hello World!"); // result: 'Hello World!' -> default implementations one.doBar("Hello World!"); ClassTwo two = new ClassTwo(); // result: 'Hello World!Hello World!' two.doFoo("Hello World!"); // result: 'HELLO WORLD!' -> overriden default implementation two.doBar("Hello World!"); } } |
Learning #2: Assign variables and methods to enums
My next learning is about enums in Java. Over the last years, I just used enums to represent a predefined set of values like for currencies: EURO, DOLLAR, YEN, etc. but this was just a small subset of the enum capabilities. You are also able to define internal attributes and methods for your enums. In the book, the author used the following example where he represented the mathematical operations as enums:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public enum Operation { PLUS("+") { public double apply(double x, double y) { return x + y; } }, MINUS("-") { public double apply(double x, double y) { return x - y; } }, TIMES("*") { public double apply(double x, double y) { return x * y; } }, DIVIDE("/") { public double apply(double x, double y) { return x / y; } }; private final String symbol; private Operation(String symbol) { this.symbol = symbol; } @Override public String toString() { return this.symbol; } public abstract double apply(double x, double y); } |
You can use the following snippet to test the enum above:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class EnumOperationRunner { public static void main(String[] args) { double x = 5.0; double y = 1.5; for (Operation op : Operation.values()) { System.out.printf("%f %s %f = %f %n", x, op, y, op.apply(x, y)); } /** result: * 5,000000 + 1,500000 = 6,500000 * 5,000000 - 1,500000 = 3,500000 * 5,000000 * 1,500000 = 7,500000 * 5,000000 / 1,500000 = 3,333333 **/ } } |
Learning #3: Underscore in long numbers
The next one is quite small learning but really helpful if you work with numbers with more then four digits. With Java, you can use underscores in your initialization of your numbers (double, float, int, long) for visual separation purposes. Imagine you have a really long number (e.g. amount of money) as a method variable or a static variable:
1 2 3 4 5 6 7 8 9 10 | public class LongNumbers { // hard to read private static int debt = 112400003; private static double interestRate = 1.304121134; public static void main(String[] args) { System.out.println(debt * interestRate); } } |
With the underscore, you can write the same code as:
1 2 3 4 5 6 7 8 9 10 | public class LongNumbersBetter { // hard to read private static int debt = 112_400_003; private static double interestRate = 1.304_121_134; public static void main(String[] args) { System.out.println(debt * interestRate); } } |
Learning #4: Working with varargs
While working with public methods of several Java frameworks I came along methods where I could pass 0 to n arguments to a method and up until now I never took time to investigate how this is technically possible. One chapter of the Effective Java book is dedicated to the so-called varargs
. With varargs, your method can take 0 to n arguments of the defined type. Internally Java arrays are used to make this possible. You define a vararg argument with three dots like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public class IntVarargs { public static void main(String[] args) { IntVarargs obj = new IntVarargs(); System.out.println("Sum 1: " + obj.buildSum(1337, 1)); System.out.println("Sum 2: " + obj.buildSum(1, 2, 42, 1, -3)); System.out.println("Sum 3: " + obj.buildSum()); } public int buildSum(int... numbers) { int sum = 0; for (int number : numbers) { sum += number; } return sum; } } |
Learning #5: Logical sorting objects
My last learning is not new to the Java language but I always implemented the sorting of my domain objects in a different way. For comparing and therefore sorting Java objects you can use the interface Comparable<T>
. Implementing this interface you have to provide an implementation of the public int compareTo(T object)
method. This method should return zero if the compared objects are equal, a negative value if the compared object is less and a positive value if the compared object is greater then the base object. In the following example, I use the domain object Person which has an attribute age that is used for comparing persons.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public class Person implements Comparable<Person> { private int age; private String name; public Person(int age, String name) { super(); this.age = age; this.name = name; } public int getAge() { return age; } @Override public int compareTo(Person person) { return Integer.compare(this.age, person.getAge()); } @Override public String toString() { return "Person [age=" + age + ", name=" + name + "]"; } } |
For sorting a collection of persons I'll use the method Collections.sort(List<T> list)
which will pick up my implemented compareTo
method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public class CompareToPerson { public static void main(String[] args) { Person p1 = new Person(24, "Max"); Person p2 = new Person(35, "Paul"); Person p3 = new Person(10, "Foo"); List<Person> persons = new ArrayList<>(); persons.addAll(Arrays.asList(p1, p2, p3)); for (Person person : persons) { System.out.println(person); } Collections.sort(persons); System.out.println("After sorting:"); for (Person person : persons) { System.out.println(person); } } } |
Effective Java review summary: Once and for all, I can say the book is definitely worth the 35 € for the Kindle edition. There were a lot of advanced Java topics covered and I got deeper insights into several language features of the latest Java versions.
Have fun reading Effective Java,
Phil.