Each of the java hacks may seem small in isolation, but together they help write code that is easier to read, less error-prone, and more maintainable. They also encourage a modern Java style that leverages the language’s evolving features rather than sticking to outdated idioms.
Some of these might seem basic, but I guarantee you'll learn something new.
1. Use Try-With-Resources Instead of Manual Resource Management
What not to do
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("data.txt"));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
Good practice
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
Why It’s Better:
The try-with-resources statement automatically closes the resource when the try block is exited (whether normally or via an exception). This reduces boilerplate code and eliminates potential resource leaks.
2. Use StringBuilder for String Concatenation in Loops
What not to do
String result = "";
for (int i = 0; i < 1000; i++) {
result += i; // Each iteration creates a new String
}
Good practice
StringBuilder result = new StringBuilder();
for (int i = 0; i < 1000; i++) {
result.append(i);
}
String finalResult = result.toString();
Why It’s Better:
Since Java strings are immutable, using +
inside loops creates many intermediate objects. StringBuilder
(or StringBuffer
in a multi-threaded context) minimizes object creation and improves performance.
3. Favor Early Returns to Reduce Deep Nesting
What not to do
public void processOrder(Order order) {
if (order != null) {
if (order.isValid()) {
if (order.hasItems()) {
// Process order
} else {
// Handle no items
}
} else {
// Handle invalid order
}
} else {
// Handle null order
}
}
Good practice
public void processOrder(Order order) {
if (order == null) {
// Handle null order
return;
}
if (!order.isValid()) {
// Handle invalid order
return;
}
if (!order.hasItems()) {
// Handle no items
return;
}
// Process order
}
Why It’s Better:
Using early returns simplifies the control flow by avoiding deep nesting. This leads to code that’s easier to read and maintain.
4. Use Enums Instead of Primitive Constants
What not to do
public class StatusConstants {
public static final int STATUS_ACTIVE = 1;
public static final int STATUS_INACTIVE = 0;
}
public void processStatus(int status) {
if (status == StatusConstants.STATUS_ACTIVE) {
// Active logic
}
}
Good practice
public enum Status {
ACTIVE, INACTIVE;
}
public void processStatus(Status status) {
if (status == Status.ACTIVE) {
// Active logic
}
}
Why It’s Better:
Enums provide type safety, make the code more self-documenting, and allow you to add behavior (methods, fields) directly to the constants.
5. Leverage Optional
for Cleaner Null Handling
What not to do
public String getName(Person person) {
if (person != null && person.getName() != null) {
return person.getName();
}
return "Unknown";
}
Good practice
import java.util.Optional;
public String getName(Person person) {
return Optional.ofNullable(person)
.map(Person::getName)
.orElse("Unknown");
}
Why It’s Better:
Using Optional
makes the intent clear: the method may or may not return a value. It provides a fluent API to deal with the absence of a value, reducing the risk of NullPointerException
and “if-else” clutter.
6. Employ Lambdas and Method References to Reduce Boilerplate
What not to do (Anonymous inner class)
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
Good practice
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.sort(String::compareTo);
Why It’s Better:
Lambdas and method references make the code more concise and expressive by eliminating the need for verbose anonymous inner classes.
7. Use Static Factory Methods Instead of Constructors When Appropriate
What not to do
public class Complex {
public Complex(double real, double imaginary) {
// Constructor logic
}
}
Complex c = new Complex(1.0, 2.0);
Good practice
public class Complex {
private final double real;
private final double imaginary;
private Complex(double real, double imaginary) {
this.real = real;
this.imaginary = imaginary;
}
public static Complex of(double real, double imaginary) {
// You can add extra validation or caching here if needed
return new Complex(real, imaginary);
}
}
Complex c = Complex.of(1.0, 2.0);
8. Use Parameterized Logging to Avoid Unnecessary String Concatenation
What not to do
logger.info("User " + user.getName() + " has logged in at " + System.currentTimeMillis());
Good practice
logger.info("User {} has logged in at {}", user.getName(), System.currentTimeMillis());
Why It’s Better:
Many logging frameworks (like SLF4J) support parameterized logging. This defers string concatenation until it’s confirmed that the log level is enabled, thereby improving performance and readability.
9. Use Objects.requireNonNull
for Input Validation
What not to do
public void setName(String name) {
if (name == null) {
throw new IllegalArgumentException("name cannot be null");
}
this.name = name;
}
Good practice
import java.util.Objects;
public void setName(String name) {
this.name = Objects.requireNonNull(name, "name cannot be null");
}
Why It’s Better:Objects.requireNonNull
centralizes null-checking in a single, readable line. It immediately signals that the argument is required, reducing boilerplate and the chance of a missed check.
10. Use computeIfAbsent
Instead of Manual Checks for Map Initialization
What not to do
Map<String, List<String>> map = new HashMap<>();
if (!map.containsKey("key")) {
map.put("key", new ArrayList<>());
}
map.get("key").add("value");
Good practice
Map<String, List<String>> map = new HashMap<>();
map.computeIfAbsent("key", k -> new ArrayList<>()).add("value");
Why? computeIfAbsent
removes boilerplate code and ensures the key is initialized only once.
As i said a few might look simple, but there's definitely something here to level up your skills.