effective-java
Java best practices guide based on Effective Java. Use when reviewing Java code, discussing design patterns, object creation, equals/hashCode, Optional, Stream API, exception handling, or concurrency. Applies Joshua Bloch's principles.
When & Why to Use This Skill
This Claude skill acts as an expert Java programming consultant based on the industry-standard principles of 'Effective Java' by Joshua Bloch. It provides actionable guidance for reviewing Java code, focusing on object creation, design patterns, memory efficiency, and the proper use of modern features like Streams and Lambdas to ensure high-quality, maintainable software architecture.
Use Cases
- Automated Code Review: Analyze Java source code to identify anti-patterns and suggest improvements based on proven best practices for object-oriented design.
- Refactoring Complex Constructors: Guide the transition from telescoping constructors to the Builder pattern or static factory methods to improve code readability and maintainability.
- Performance Optimization: Identify and eliminate common efficiency bottlenecks such as unnecessary object creation, improper autoboxing, and inefficient pattern matching.
- API Design and Safety: Ensure robust class design by minimizing mutability, favoring composition over inheritance, and correctly implementing equals and hashCode contracts.
- Modernizing Legacy Code: Assist in migrating verbose anonymous classes to modern, side-effect-free Stream API pipelines and Lambda expressions for cleaner functional programming.
- Generics and Type Safety: Apply advanced generic techniques like Bounded Wildcards (PECS) to create flexible and type-safe APIs.
| name | effective-java |
|---|---|
| description | Java best practices guide based on Effective Java. Use when reviewing Java code, discussing design patterns, object creation, equals/hashCode, Optional, Stream API, exception handling, or concurrency. Applies Joshua Bloch's principles. |
Effective Java Best Practices
IMPORTANT: All output must be in Traditional Chinese.
Creating Objects
Item 1: Static Factory Methods over Constructors
// GOOD - Static factory with descriptive name
public static Order createPending(CustomerId customerId, List<OrderItem> items) {
return new Order(OrderId.generate(), customerId, items, OrderStatus.PENDING);
}
public static Order reconstitute(OrderId id, CustomerId customerId,
List<OrderItem> items, OrderStatus status) {
return new Order(id, customerId, items, status);
}
// BAD - Constructor doesn't convey intent
public Order(OrderId id, CustomerId customerId, List<OrderItem> items, OrderStatus status)
Advantages:
- Descriptive names (
createEmpty,valueOf,of,from) - Can return cached instances
- Can return subtypes
- Reduces verbosity with type inference
Item 2: Builder Pattern for Many Parameters
// GOOD - Builder for complex objects
public class HttpRequest {
private final String url;
private final String method;
private final Map<String, String> headers;
private final Duration timeout;
private final int retries;
private HttpRequest(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = Map.copyOf(builder.headers);
this.timeout = builder.timeout;
this.retries = builder.retries;
}
public static Builder builder(String url) {
return new Builder(url);
}
public static class Builder {
private final String url;
private String method = "GET";
private final Map<String, String> headers = new HashMap<>();
private Duration timeout = Duration.ofSeconds(30);
private int retries = 3;
private Builder(String url) {
this.url = Objects.requireNonNull(url);
}
public Builder method(String method) {
this.method = method;
return this;
}
public Builder header(String key, String value) {
this.headers.put(key, value);
return this;
}
public Builder timeout(Duration timeout) {
this.timeout = timeout;
return this;
}
public HttpRequest build() {
return new HttpRequest(this);
}
}
}
// Usage
HttpRequest request = HttpRequest.builder("https://api.example.com")
.method("POST")
.header("Content-Type", "application/json")
.timeout(Duration.ofSeconds(10))
.build();
Item 6: Avoid Creating Unnecessary Objects
// BAD - Creates new Boolean on each call
Boolean.valueOf("true"); // Fine, but...
new Boolean("true"); // NEVER do this
// BAD - Pattern compiled on every call
public boolean isValidEmail(String email) {
return email.matches("^[A-Za-z0-9+_.-]+@(.+)$"); // Compiles pattern each time!
}
// GOOD - Compile once, reuse
private static final Pattern EMAIL_PATTERN =
Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$");
public boolean isValidEmail(String email) {
return EMAIL_PATTERN.matcher(email).matches();
}
// BAD - Autoboxing in loop
Long sum = 0L;
for (long i = 0; i < 1_000_000; i++) {
sum += i; // Creates ~1M Long objects!
}
// GOOD - Use primitive
long sum = 0L;
for (long i = 0; i < 1_000_000; i++) {
sum += i;
}
Classes and Interfaces
Item 15: Minimize Accessibility
// BAD - Exposes internal state
public class Order {
public List<OrderItem> items; // Mutable field exposed!
}
// GOOD - Proper encapsulation
public class Order {
private final List<OrderItem> items;
public List<OrderItem> getItems() {
return Collections.unmodifiableList(items);
}
}
Item 17: Minimize Mutability
// GOOD - Immutable Value Object using record
public record Money(BigDecimal amount, Currency currency) {
public Money {
Objects.requireNonNull(amount);
Objects.requireNonNull(currency);
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
}
public Money add(Money other) {
validateSameCurrency(other);
return new Money(this.amount.add(other.amount), this.currency);
}
}
// For complex objects, use Builder + private constructor
public final class Transaction {
private final String id;
private final Money amount;
private final LocalDateTime timestamp;
private Transaction(Builder builder) { /* ... */ }
// No setters - all fields final
}
Item 18: Favor Composition over Inheritance
// BAD - Inheritance breaks encapsulation
public class InstrumentedHashSet<E> extends HashSet<E> {
private int addCount = 0;
@Override
public boolean add(E e) {
addCount++;
return super.add(e); // Works
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c); // BUG: addAll calls add(), double counting!
}
}
// GOOD - Composition (Wrapper/Decorator)
public class InstrumentedSet<E> implements Set<E> {
private final Set<E> delegate;
private int addCount = 0;
public InstrumentedSet(Set<E> delegate) {
this.delegate = delegate;
}
@Override
public boolean add(E e) {
addCount++;
return delegate.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return delegate.addAll(c); // Correct: delegates without calling our add()
}
// Delegate all other Set methods...
}
Methods Common to All Objects
Item 10 & 11: equals and hashCode Contract
public class OrderId {
private final String value;
public OrderId(String value) {
this.value = Objects.requireNonNull(value);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof OrderId other)) return false;
return value.equals(other.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
}
// Or simply use record (auto-generates equals/hashCode)
public record OrderId(String value) {
public OrderId {
Objects.requireNonNull(value);
}
}
Rules:
- Override
hashCodewhen you overrideequals - Equal objects must have equal hash codes
- Use
Objects.hash()for simple cases - Consider caching hash code for expensive computations
Generics
Item 26: Don't Use Raw Types
// BAD - Raw type, loses type safety
List orders = new ArrayList();
orders.add("not an order"); // Compiles, fails at runtime
// GOOD - Parameterized type
List<Order> orders = new ArrayList<>();
orders.add("not an order"); // Compile error!
Item 31: Use Bounded Wildcards (PECS)
Producer Extends, Consumer Super
// Producer - reads from collection, use extends
public void processOrders(List<? extends Order> orders) {
for (Order order : orders) { // Can read as Order
process(order);
}
}
// Consumer - writes to collection, use super
public void addOrders(List<? super Order> destination) {
destination.add(new Order()); // Can write Order
}
// Example: Copying from producer to consumer
public static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T item : src) {
dest.add(item);
}
}
Lambdas and Streams
Item 42: Prefer Lambdas to Anonymous Classes
// BAD - Verbose anonymous class
Collections.sort(orders, new Comparator<Order>() {
@Override
public int compare(Order o1, Order o2) {
return o1.getCreatedAt().compareTo(o2.getCreatedAt());
}
});
// GOOD - Lambda
orders.sort((o1, o2) -> o1.getCreatedAt().compareTo(o2.getCreatedAt()));
// BETTER - Method reference
orders.sort(Comparator.comparing(Order::getCreatedAt));
Item 45: Use Streams Judiciously
// GOOD - Clear, readable stream
List<String> customerNames = orders.stream()
.filter(o -> o.getStatus() == OrderStatus.CONFIRMED)
.map(Order::getCustomerId)
.distinct()
.map(customerRepository::findById)
.flatMap(Optional::stream)
.map(Customer::getName)
.sorted()
.toList();
// BAD - Overusing streams, hard to debug
orders.stream()
.collect(groupingBy(Order::getCustomerId,
collectingAndThen(
reducing((o1, o2) -> o1.getTotal() > o2.getTotal() ? o1 : o2),
opt -> opt.map(Order::getTotal).orElse(Money.ZERO)
)
))
.entrySet().stream()
.sorted(Map.Entry.<CustomerId, Money>comparingByValue().reversed())
.limit(10)
.forEach(e -> log.info("{}: {}", e.getKey(), e.getValue()));
// GOOD - Use loop when clearer
Map<CustomerId, Order> largestByCustomer = new HashMap<>();
for (Order order : orders) {
largestByCustomer.merge(order.getCustomerId(), order,
(existing, current) -> existing.getTotal() > current.getTotal() ? existing : current);
}
Item 46: Prefer Side-Effect-Free Functions
// BAD - Side effects in stream
List<Order> processed = new ArrayList<>();
orders.stream()
.filter(Order::isPending)
.forEach(o -> {
o.confirm(); // Mutating!
processed.add(o); // Side effect!
orderRepository.save(o); // Side effect!
});
// GOOD - Collect, then process
List<Order> pendingOrders = orders.stream()
.filter(Order::isPending)
.toList();
for (Order order : pendingOrders) {
order.confirm();
orderRepository.save(order);
}
Exceptions
Item 69: Use Exceptions for Exceptional Conditions
// BAD - Using exception for flow control
try {
int i = 0;
while (true) {
process(array[i++]);
}
} catch (ArrayIndexOutOfBoundsException e) {
// Done
}
// GOOD - Normal control flow
for (int i = 0; i < array.length; i++) {
process(array[i]);
}
Item 72: Favor Standard Exceptions
| Exception | Use Case |
|---|---|
IllegalArgumentException |
Invalid parameter value |
IllegalStateException |
Object in wrong state for method |
NullPointerException |
Null where prohibited |
UnsupportedOperationException |
Method not supported |
NoSuchElementException |
No element available |
Item 73: Throw Appropriate to Abstraction
// BAD - Low-level exception leaks
public Order findOrder(OrderId id) {
try {
return jdbcTemplate.queryForObject(...);
} catch (EmptyResultDataAccessException e) {
throw e; // Exposes implementation detail!
}
}
// GOOD - Translate to domain exception
public Order findOrder(OrderId id) {
try {
return jdbcTemplate.queryForObject(...);
} catch (EmptyResultDataAccessException e) {
throw new OrderNotFoundException(id, e);
}
}
Code Review Checklist
| Check | Good | Bad |
|---|---|---|
| Object creation | Static factory / Builder | Telescoping constructors |
| Value objects | record or immutable class |
Mutable with setters |
| Collections | List.of(), Map.of(), unmodifiableList |
Exposed mutable collections |
| Optional | orElseThrow(), map(), filter() |
get() without isPresent() |
| Streams | Reasonable pipeline, side-effect free | Complex nested collectors, mutations |
| Exceptions | Domain-specific, standard exceptions | Generic Exception, flow control |
| Generics | Bounded wildcards (PECS) | Raw types |
Additional Resources
For detailed explanations:
- references/object-creation.md - Items 1-9 complete guide
- references/classes-and-interfaces.md - Items 15-25 details
- references/lambdas-streams.md - Stream API patterns
- references/concurrency.md - Thread safety patterns