From bill-java-skills
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.
How this skill is triggered — by the user, by Claude, or both
Slash command
/bill-java-skills:effective-javasonnetThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
IMPORTANT: All output must be in Traditional Chinese.
IMPORTANT: All output must be in Traditional Chinese.
Prefer static factories over constructors: descriptive names (createEmpty, of, from), can return cached instances, can return subtypes.
// Descriptive naming conveys intent
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);
}
Use Builder when a class has 4+ parameters, especially optional ones. The Builder copies fields in the constructor to ensure immutability (Map.copyOf).
// Compact constructor validates in 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);
}
}
The critical non-obvious trap: addAll calls add internally in HashSet.
// BAD - Inheritance breaks encapsulation
public class InstrumentedHashSet<E> extends HashSet<E> {
private int addCount = 0;
@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;
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return delegate.addAll(c); // Correct: no double counting
}
}
Producer Extends, Consumer Super — non-obvious rule that enables maximum flexibility.
// Producer - reads from collection, use extends
public void processOrders(List<? extends Order> orders) {
for (Order order : orders) { process(order); }
}
// Consumer - writes to collection, use super
public void addOrders(List<? super Order> destination) {
destination.add(new Order());
}
// Copy: src is producer (extends), dest is consumer (super)
public static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T item : src) { dest.add(item); }
}
Use loop when it's clearer than a stream chain. Multi-level nested collectors hurt readability — prefer a loop with Map.merge.
// BAD - Side effects in stream (mutation + I/O inside forEach)
orders.stream()
.filter(Order::isPending)
.forEach(o -> {
o.confirm(); // Mutating!
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);
}
// BAD - Low-level exception leaks implementation detail
public Order findOrder(OrderId id) {
try {
return jdbcTemplate.queryForObject(...);
} catch (EmptyResultDataAccessException e) {
throw e; // Caller shouldn't know about JDBC!
}
}
// GOOD - Translate to domain exception
public Order findOrder(OrderId id) {
try {
return jdbcTemplate.queryForObject(...);
} catch (EmptyResultDataAccessException e) {
throw new OrderNotFoundException(id, e);
}
}
| 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 | Nested collectors, mutations |
| Exceptions | Domain-specific, standard exceptions | Generic Exception, flow control |
| Generics | Bounded wildcards (PECS) | Raw types |
@Entity 換成 record。Record 沒有無參數構造器 + 可變欄位,Hibernate proxy 無法運作List.copyOf()build() 內做驗證,否則 compact constructor 的 validation 被繞過List.copyOf() 才真正安全,unmodifiableList 只是視圖npx claudepluginhub xinqilin/claude-dev-toolkit-marketplace --plugin bill-java-skillsProvides best practices for modern Java (17+) code: null safety with Optional and annotations, immutability via records, concurrency with CompletableFuture and virtual threads, sealed classes.
Enforces Java 17+ coding standards for Spring Boot services: naming, immutability, Optional, streams, exceptions, generics, and project layout.
Enforces Java coding standards for Spring Boot and Quarkus services: naming, immutability, Optional, streams, exceptions, generics, CDI, reactive patterns, and project layout.