一键导入
java-patterns
Modern Java 17+ patterns — records, sealed classes, Stream API, CompletableFuture, Spring Boot, JPA. Activate when writing or reviewing Java.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Modern Java 17+ patterns — records, sealed classes, Stream API, CompletableFuture, Spring Boot, JPA. Activate when writing or reviewing Java.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Optimize token usage and context window discipline. Reduce costs and improve response quality through smart context management.
Unified context lifecycle for FlowDeck sessions — ingest, filter, prune, protect, summarize, and persist with telemetry.
Predict affected files, modules, APIs, tests, and DB paths before changes. Returns an impact map for human review.
Map architecture, conventions, and file structure into `.codebase/`. Use when onboarding or before deep feature work.
Plan differently when the agent has low certainty — ask for clarification or narrow scope instead of pretending full understanding.
Protect critical context from pruning during compaction. Preserve active plans, safety files, pending operations, and user intent anchors.
| name | java-patterns |
| description | Modern Java 17+ patterns — records, sealed classes, Stream API, CompletableFuture, Spring Boot, JPA. Activate when writing or reviewing Java. |
| origin | FlowDeck |
Modern Java for production applications. Focuses on Java 17+ features and Spring Boot conventions.
Activate when:
Records eliminate boilerplate for data classes. Use them for DTOs, value objects, and command/query objects.
// Compiler generates constructor, getters, equals, hashCode, toString
public record Point(double x, double y) {}
// Custom constructor for validation
public record EmailAddress(String value) {
public EmailAddress {
Objects.requireNonNull(value);
if (!value.contains("@")) {
throw new IllegalArgumentException("invalid email: " + value);
}
value = value.toLowerCase();
}
}
// Records can implement interfaces
public record PageRequest(int page, int size) implements Serializable {
public PageRequest {
if (page < 0) throw new IllegalArgumentException("page must be >= 0");
if (size < 1 || size > 100) throw new IllegalArgumentException("size must be 1-100");
}
public int offset() { return page * size; }
}
Use sealed classes to model a fixed set of variants. The compiler enforces exhaustive pattern matching.
public sealed interface PaymentResult
permits PaymentResult.Success, PaymentResult.Declined, PaymentResult.Error {
record Success(String transactionId, BigDecimal amount) implements PaymentResult {}
record Declined(String reason) implements PaymentResult {}
record Error(Throwable cause) implements PaymentResult {}
}
// Pattern matching ensures all cases are handled
String message = switch (result) {
case PaymentResult.Success s -> "Charged " + s.amount();
case PaymentResult.Declined d -> "Declined: " + d.reason();
case PaymentResult.Error e -> "Error: " + e.cause().getMessage();
};
// ❌ Old style — verbose cast
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}
// ✅ Pattern variable — binding in same expression
if (obj instanceof String s) {
System.out.println(s.length());
}
// Combining with guards
if (obj instanceof String s && s.length() > 5) {
System.out.println("long string: " + s);
}
// ✅ Multiline strings without escaping
String json = """
{
"name": "Alice",
"age": 30
}
""";
String query = """
SELECT u.id, u.email
FROM users u
WHERE u.active = true
AND u.created_at > :since
""";
Optional communicates "might be absent" in a return type. Never use it as a field type or parameter.
// ✅ Return type — caller must handle absence
public Optional<User> findByEmail(String email) {
return userRepository.findByEmail(email);
}
// ✅ Chained operations
String displayName = findByEmail(email)
.map(User::displayName)
.orElse("Anonymous");
// ✅ Throw if absent with meaningful message
User user = findByEmail(email)
.orElseThrow(() -> new UserNotFoundException(email));
// ✅ Execute only if present
findByEmail(email).ifPresent(user -> auditLog.record(user.id()));
// ❌ Never null-check Optional itself
Optional<User> opt = findByEmail(email);
if (opt != null && opt.isPresent()) { ... } // wrong
// ❌ Never use as a field type
class Service {
private Optional<Cache> cache; // wrong — use @Nullable or just null
}
List<String> activeEmails = users.stream()
.filter(User::isActive)
.sorted(Comparator.comparing(User::lastName))
.map(User::email)
.toList(); // Java 16+ unmodifiable list
// Collectors
Map<Department, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::department));
Map<Boolean, List<User>> partitioned = users.stream()
.collect(Collectors.partitioningBy(User::isAdmin));
// Reduce
BigDecimal total = cart.items().stream()
.map(Item::price)
.reduce(BigDecimal.ZERO, BigDecimal::add);
// One user has many orders, each order has many items
List<Item> allItems = users.stream()
.flatMap(u -> u.orders().stream())
.flatMap(o -> o.items().stream())
.toList();
// ✅ Parallel for CPU-bound work on large, independent datasets
long count = IntStream.range(0, 10_000_000)
.parallel()
.filter(n -> isPrime(n))
.count();
// ❌ Parallel with shared mutable state — data race
List<Integer> result = new ArrayList<>();
IntStream.range(0, 1000).parallel()
.forEach(result::add); // non-thread-safe!
// ❌ Parallel for I/O-bound work — blocks common ForkJoinPool
Stream.of(urls).parallel()
.map(this::fetch) // ties up ForkJoin threads on I/O
.toList();
// Rule: parallel only when dataset is large, work is CPU-bound,
// and no shared mutable state exists. Benchmark first.
// Chain transformations
CompletableFuture<String> result = CompletableFuture
.supplyAsync(() -> fetchUser(userId)) // background thread
.thenApply(User::email) // same thread
.thenApplyAsync(this::sendWelcome, executor); // different thread
// Compose dependent futures (flatMap equivalent)
CompletableFuture<Order> order = CompletableFuture
.supplyAsync(() -> findUser(id))
.thenCompose(user -> placeOrder(user, items)); // avoids nested futures
// Recover from failure
CompletableFuture<User> withFallback = fetchUser(id)
.exceptionally(ex -> {
log.warn("fetch failed, using guest", ex);
return User.guest();
});
// Wait for multiple independent futures
CompletableFuture<Void> all = CompletableFuture.allOf(
sendEmail(user), updateCache(user), auditLog(user)
);
all.join(); // blocks — use only at the top of the call stack
// Timeout
CompletableFuture<User> withTimeout = fetchUser(id)
.orTimeout(2, TimeUnit.SECONDS)
.exceptionally(ex -> User.guest());
// ✅ Constructor injection — dependencies are explicit and final
@Service
public class OrderService {
private final OrderRepository orders;
private final PaymentGateway payments;
private final EventPublisher events;
public OrderService(
OrderRepository orders,
PaymentGateway payments,
EventPublisher events
) {
this.orders = orders;
this.payments = payments;
this.events = events;
}
}
// ❌ Field injection — hides dependencies, breaks tests
@Service
public class OrderService {
@Autowired private OrderRepository orders; // bad
}
@ConfigurationProperties(prefix = "payment")
public record PaymentConfig(
String apiKey,
URI baseUrl,
Duration timeout,
int maxRetries
) {}
// application.yml
// payment:
// api-key: sk_live_...
// base-url: https://api.stripe.com
// timeout: 5s
// max-retries: 3
@Repository // data access — wraps DataAccessException
@Service // business logic
@Component // general-purpose bean
@RestController // HTTP endpoints (combines @Controller + @ResponseBody)
// ❌ N+1: one query for orders, then one query per order for items
List<Order> orders = orderRepo.findAll();
orders.forEach(o -> System.out.println(o.getItems().size()));
// ✅ Fetch join: single query
@Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.userId = :userId")
List<Order> findWithItems(@Param("userId") Long userId);
// ✅ Or use @EntityGraph for reuse
@EntityGraph(attributePaths = {"items", "items.product"})
List<Order> findByUserId(Long userId);
// Default for @OneToMany is LAZY — keep it that way
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
private List<OrderItem> items;
// EAGER causes joins on every query, even when items aren't needed
// Only use EAGER for @ManyToOne / @OneToOne where the association is always needed
// @Transactional on service methods, not repository methods
@Service
public class TransferService {
@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount) {
Account from = accounts.findById(fromId).orElseThrow();
Account to = accounts.findById(toId).orElseThrow();
from.debit(amount);
to.credit(amount);
// both saves happen in the same transaction
}
}
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock OrderRepository orders;
@Mock PaymentGateway payments;
@InjectMocks OrderService service;
@Test
void placeOrder_chargesPaymentAndSavesOrder() {
var user = aUser().build();
var items = List.of(anItem().withPrice(BigDecimal.TEN).build());
when(payments.charge(any(), any())).thenReturn(chargeSuccess());
var result = service.placeOrder(user, items);
assertThat(result.status()).isEqualTo(OrderStatus.CONFIRMED);
verify(orders).save(argThat(o -> o.total().equals(BigDecimal.TEN)));
}
}
// Unit test — no Spring context
@ExtendWith(MockitoExtension.class)
class UserServiceTest { ... }
// Web layer only — no full context
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired MockMvc mvc;
@MockBean UserService service;
@Test
void getUser_returns200() throws Exception {
when(service.findById(1L)).thenReturn(Optional.of(aUser().build()));
mvc.perform(get("/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.email").value("alice@example.com"));
}
}
// Full integration test
@SpringBootTest(webEnvironment = RANDOM_PORT)
@Transactional // rolls back each test
class OrderIntegrationTest { ... }
<properties>
<java.version>21</java.version>
<spring-boot.version>3.3.0</spring-boot.version>
</properties>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>${java.version}</release>
<compilerArgs><arg>-parameters</arg></compilerArgs>
</configuration>
</plugin>
java {
toolchain { languageVersion = JavaLanguageVersion.of(21) }
}
tasks.withType<JavaCompile> {
options.compilerArgs.add("-parameters")
}
// ❌ Checked exceptions force callers to handle or declare them
public List<User> loadUsers() throws IOException, ParseException { ... }
// ✅ Wrap in unchecked and carry the cause
public List<User> loadUsers() {
try {
return parser.parse(Files.readString(path));
} catch (IOException | ParseException e) {
throw new UserLoadException("failed to load users from " + path, e);
}
}
// ❌ Creates a new String object every iteration — O(n²) allocations
String result = "";
for (String s : list) {
result += s + ", ";
}
// ✅ StringBuilder is O(n)
var sb = new StringBuilder();
for (String s : list) {
sb.append(s).append(", ");
}
String result = sb.toString();
// ✅ Or use String.join / Collectors.joining
String result = String.join(", ", list);
// ❌ Unnecessary boxing in tight loops
Long sum = 0L;
for (long i = 0; i < 1_000_000; i++) {
sum += i; // unboxes sum, adds, boxes result each iteration
}
// ✅ Use primitives
long sum = 0L;
for (long i = 0; i < 1_000_000; i++) {
sum += i;
}
// ✅ LongStream avoids boxing entirely
long sum = LongStream.range(0, 1_000_000).sum();