24-04-2023
10 advanced Java developments tips you should know
by Juliane Bazilewitz, Backend Developer @ Xpand IT
Java is one of the top languages used worldwide. New versions are frequently being released with a lot of new features for us to enjoy.
In this blogpost, we discussed 10 Java hacks that can be used to improve your work as a Java Developer. And remember: Work smart, not hard!
1. Use Streams for Data Processing
Streams was introduced in Java 8 and use functional-style operations to process data. It enables us to do a bunch of operations to certain collections with simpler code. The common operations are: filter, map, reduce, find, match and sort. The source can be a collection, IO operation, or array that provides the data to a stream.
The Stream interface is defined in java.util.stream package.
Example:
List<String> cities = Arrays.asList( "Paris", "Barcelona", "Coimbra", "Cairo", "Beijing"); cities.stream() // Paris, Barcelona, Coimbra, Cairo, Beijing .filter(a -> a.startsWith("C")) // Coimbra, Cairo .map(String::toUpperCase) // COIMBRA, CAIRO .sorted() // CAIRO, COIMBRA .forEach(System.out::println); // CAIRO, COIMBRA
In the example we are filtering the cities that start with the letter “C”. We also do the conversion to uppercase and sort it before printing the result.
Output:
CAIRO, COIMBRA
2. Use Optional for Nullable Objects
Optional API was introduced in Java 8. As a java developer you would have felt the pain of getting NullPointerException.
Example:
Object obj = null; // created null variable obj.toString(); // method call over null variable
Using Optional you protect your code against unintended null pointer exceptions. A main way to avoid null pointer exceptions using Optional is using an empty optional. We can also process over the object if the object is present or null.
Empty optional example:
Optional<String> objEmpty = Optional.empty(); assertFalse(objEmpty.isPresent());
Nullable value example:
Object obj = null; Optional<Object> objOpt = Optional.ofNullable(obj); System.out.println(objOpt.isPresent()); // prints false
Non-null value example:
Optional<Object> opt = Optional.of(new Object());
3. Use Builder Pattern for Complex Objects
Builder is a creational design pattern. With Builder pattern we can create different representation from an object using the same construction process, especially for more complex object.
So where to use Builder Pattern? Builder Pattern is used when it’s necessary to use a constructor with a long list of parameters or when there’s a long list of constructors with different parameters. It is also used when the object creation contains optional parameters.
Example:
Use List<DocumentAction> availableActions = new DocumentActionEvaluator() .withDocument(document) .withSource(source) .withUser(user) .eval();
Instead of
List<DocumentAction> availableActions = new ArrayList<>(); availableActions.setDocument(document); availableActions.setDocument(source); availableActions.setDocument(user);
4. Use StringBuilder to String Concatenation
StringBuilder provides an array of String-building utilities that’s make String manipulation easier. StringBuilder is used to perform concatenation operation. The same can be achieved through the “+” operator, but StringBuilder has a more efficient way.
Example:
StringBuilder strB = new StringBuilder(100); strB.append("Xpand-IT") .append(" is a") .append(" good") .append(" place") .append(" to work"); assertEquals("Xpand-IT is a good place to work", strB.toString());
5. Use Java Agents to instrument code
Imagine that you have your code already in production, and you need to understand some issue that is currently happening? Instead of making changes in your code to add logs in the middle of the code, you can use Java Agents instead.
Java Agents are part of the Java Instrumentation API. Instrumentation is a technique used to change an existing application adding code to it at runtime. It has been part of the library since JDK 1.5.
For more details on how to build and load a Java Agent, you can check this Baeldung post.
6. Use Generics
Java Generics was introduced in JDK 5.0. Why use Generics? Java Generics allows us to create a single class, interface, and method that can be used with different types of data (objects). Without generics, many of the features that we use in Java today would not be possible.
Generics type Class:
class Main { public static void main(String[] args) { // initialize generic class with Integer data GenericsClass<Integer> intObj = new GenericsClass<>(5); System.out.println("Generic Class returns: " + intObj.getData()); // initialize generic class with String data GenericsClass<String> stringObj = new GenericsClass<>("Java Programming"); System.out.println("Generic Class returns: " + stringObj.getData()); } } // create a generics class class GenericsClass<T> { // variable of T type private T data; public GenericsClass(T data) { this.data = data; } // method that return T type variable public T getData() { return this.data; } }
Output:
Generic Class returns: 5 Generic Class returns: Java Programming
Generics type Method:
public <T> List<T> fromArrayToList(T[] a) { return Arrays.stream(a).collect(Collectors.toList()); } public void testFromArrayToListGenericMethod() { Integer[] intArray = {1, 2, 3, 4, 5}; List<String> stringList = fromArrayToList(intArray, Object::toString); assertThat(stringList, hasItems("1", "2", "3", "4", "5"));
7. Use Varargs to pass a variable number of arguments
Varargs were introduced in Java 5. It is short-form for variable-length arguments. This will allow the method to accept an arbitrary number of arguments of the specified data type
Syntax of varargs: A variable-length argument is specified by three periods(…).
Example:
public class VarargsExample { // Method that accepts a variable number of integer arguments public static void printNumbers(int… numbers) { System.out.println("Number of arguments: " + numbers.length); // get the number of arguments passed System.out.print("Arguments: "); for (int number : numbers) { System.out.print(number + " "); } System.out.println(); } public static void main(String[] args) { printNumbers(1); printNumbers(4, 7, 2); printNumbers(5, 8, 3, 10, 15); printNumbers(); // This will work as well, with zero arguments } } }
8. Use regular expressions to search and manipulate text
A regular expression (regex) defines a search pattern for strings in a more precise way than the regular contains methods (or others alike). Regular Expression can be used to search, edit or manipulate text. A regular expression is not language specific but they differ slightly for each language. It has a long learning curve, but once you’ve master it, you will certainly start using it almost all the time!
Java Regex classes are present in java.util.regex package that contains three classes:
- Matcher Class – Defines a pattern (to be used in a search)
- Pattern Class – Used to search for the pattern
- PatternSyntaxException Class – Indicates syntax error in a regular expression pattern
Example with Matcher class:
Pattern pattern = Pattern.compile("Xpand-IT", Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher("Visit Xpand-IT blog!"); boolean matchFound = matcher.find(); if(matchFound) { System.out.println("Match found"); } else { System.out.println("Match not found"); } // Outputs Match found
9. Use the ternary operator to simplify if-else statements
Ternary Operator is used in place of if-else, if-elseif and switch-case statements. The ternary operator consists of a condition that evaluates to either true or false, plus a value that is returned if the condition is true and another value that is returned if the condition is false.
Ternary Operator enhances the conciseness and readability of code.
Example:
//Regular if-else if(var1) { variable = var2; } else { variable = var3; } // ternary operator variable = var1 ? var2 : var3;
10. Try-with-resources
The Try-with-resources statement is a try statement that declares one or more resources in it. The resource is as an object that must be closed after finishing the program. Using try-with-resources we replaced the tradicional try-catch-finally block.
try(FileInputStream input = new FileInputStream("file.txt")) { int data = input.read(); while(data != -1){ System.out.print((char) data); data = input.read(); }
A try-with-resources block can still have the catch and finally blocks, which will work in the same way as with a traditional try block.
Conclusion
Here are some tips to optimize, improve the performance of your code and make it more readable.
There are multiple ways to solve problems.
Leave a comment
Comments are closed.