The habits that separate working Java from good Java — mostly from Joshua Bloch's Effective Java.
Naming conventions
| Element | Convention | Example |
|---|---|---|
| Class, interface, record, enum | PascalCase, nouns | OrderService, Comparable |
| Method | camelCase, verbs | calculateTotal(), isEmpty() |
| Variable / parameter | camelCase | itemCount |
Constant (static final) | UPPER_SNAKE_CASE | MAX_RETRIES |
| Package | lowercase, reversed domain | com.example.billing |
| Type parameter | single capital letter | T, E, K, V, R |
| Enum constant | UPPER_SNAKE_CASE | Status.IN_PROGRESS |
Effective Java highlights
Composition over inheritance — the classic bug
Subclassing HashSet to count insertions double-counts, because HashSet.addAll internally calls the overridden add. You depended on an implementation detail of the superclass.
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
public class Main {
static class CountingSet<E> extends HashSet<E> {
int added = 0;
@Override public boolean add(E e) { added++; return super.add(e); }
@Override public boolean addAll(Collection<? extends E> c) {
added += c.size();
return super.addAll(c); // calls our add() again for every element!
}
}
public static void main(String[] args) {
CountingSet<String> s = new CountingSet<>();
s.addAll(List.of("a", "b", "c"));
System.out.println(s.added); // expected 3
}
}6equals without hashCode
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class Main {
static final class Bad {
final int id;
Bad(int id) { this.id = id; }
@Override public boolean equals(Object o) { return o instanceof Bad b && b.id == id; }
// hashCode NOT overridden → identity hash codes differ
}
static final class Good {
final int id;
Good(int id) { this.id = id; }
@Override public boolean equals(Object o) { return o instanceof Good g && g.id == id; }
@Override public int hashCode() { return Objects.hash(id); }
}
public static void main(String[] args) {
Set<Bad> bad = new HashSet<>();
bad.add(new Bad(1));
System.out.println(bad.contains(new Bad(1)));
Set<Good> good = new HashSet<>();
good.add(new Good(1));
System.out.println(good.contains(new Good(1)));
}
}false
trueReturn empty, not null
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class Main {
static final Map<String, List<String>> ORDERS = Map.of("asha", List.of("book", "pen"));
static List<String> ordersOf(String user) {
return ORDERS.getOrDefault(user, List.of()); // never null
}
static Optional<String> firstOrder(String user) {
return ordersOf(user).stream().findFirst();
}
public static void main(String[] args) {
for (String item : ordersOf("ravi")) System.out.println(item); // simply loops 0 times
System.out.println(ordersOf("ravi").size());
System.out.println(firstOrder("asha").orElse("none"));
System.out.println(firstOrder("ravi").orElse("none"));
}
}0
book
noneCode hygiene checklist
| Instead of… | Prefer… |
|---|---|
new Integer(5) (deprecated) | Integer.valueOf(5) or autoboxing |
Vector, Hashtable, Stack | ArrayList, HashMap / ConcurrentHashMap, ArrayDeque |
Date, Calendar, SimpleDateFormat | LocalDate, Instant, DateTimeFormatter |
int status codes | an enum |
Manual close() in finally | try-with-resources |
if (x != null) x.foo() chains | Optional for return values, or Objects.requireNonNullElse |
Why prefer composition over inheritance?
Why return an empty collection instead of null?