✦ 112개의 모던 패턴 · Java 8 → Java 25

Java는 진화했습니다.
당신의 코드도 그럴 수 있습니다.

모던 Java 코드 스니펫 모음. 오래된 Java 패턴과 깔끔하고 모던한 대체 코드를 나란히 비교합니다.

✕ 이전
public class Point {
    private final int x, y;
    public Point(int x, int y) { ... }
    public int getX() { return x; }
    public int getY() { return y; }
    // equals, hashCode, toString
}
✓ 모던
public record Point(int x, int y) {}
공유 𝕏 🦋 in
🤖
GitHub Copilot으로 Java 코드베이스를 현대화하세요. Copilot이 레거시 패턴을 모던 Java로 자동으로 마이그레이션하도록 도와줍니다.

모든 비교

112개 스니펫
표시:
Language

간결한 표준 생성자

이전 public record Person(String name, List<String> pets) { // Full canonical constructor public Person(String name, List<String> pets) { Objects.requireNonNull(name); this.name = name; this.pets = List.copyOf(pets); } }
모던 public record Person(String name, List<String> pets) { // Compact constructor public Person { Objects.requireNonNull(name); pets = List.copyOf(pets); } }
마우스를 올려 모던 코드 보기 →
Language

컴팩트 소스 파일

이전 public class HelloWorld { public static void main(String[] args) { System.out.println( "Hello, World!"); } }
모던 void main() { IO.println("Hello, World!"); }
마우스를 올려 모던 코드 보기 →
Language

인터페이스 기본 메서드

이전 // Need abstract class to share behavior public abstract class AbstractLogger { public void log(String msg) { System.out.println( timestamp() + ": " + msg); } abstract String timestamp(); } // Single inheritance only public class FileLogger extends AbstractLogger { ... }
모던 public interface Logger { default void log(String msg) { IO.println( timestamp() + ": " + msg); } String timestamp(); } // Multiple interfaces allowed public class FileLogger implements Logger, Closeable { ... }
마우스를 올려 모던 코드 보기 →
Language

익명 클래스의 다이아몬드 연산자

이전 Map<String, List<String>> map = new HashMap<String, List<String>>(); // anonymous class: no diamond Predicate<String> p = new Predicate<String>() { public boolean test(String s) {..} };
모던 Map<String, List<String>> map = new HashMap<>(); // Java 9: diamond with anonymous classes Predicate<String> p = new Predicate<>() { public boolean test(String s) {..} };
마우스를 올려 모던 코드 보기 →
Language

default 없는 완전한 switch

이전 // Must add default even though // all cases are covered double area(Shape s) { if (s instanceof Circle c) return Math.PI * c.r() * c.r(); else if (s instanceof Rect r) return r.w() * r.h(); else throw new IAE(); }
모던 // sealed Shape permits Circle, Rect double area(Shape s) { return switch (s) { case Circle c -> Math.PI * c.r() * c.r(); case Rect r -> r.w() * r.h(); }; // no default needed! }
마우스를 올려 모던 코드 보기 →
Language

유연한 생성자 본문

이전 class Square extends Shape { Square(double side) { super(side, side); // can't validate BEFORE super! if (side <= 0) throw new IAE("bad"); } }
모던 class Square extends Shape { Square(double side) { if (side <= 0) throw new IAE("bad"); super(side, side); } }
마우스를 올려 모던 코드 보기 →
Language

when 가드가 있는 패턴

이전 if (shape instanceof Circle c) { if (c.radius() > 10) { return "large circle"; } else { return "small circle"; } } else { return "not a circle"; }
모던 return switch (shape) { case Circle c when c.radius() > 10 -> "large circle"; case Circle c -> "small circle"; default -> "not a circle"; };
마우스를 올려 모던 코드 보기 →
Language

Javadoc 주석에서 Markdown 사용

이전 /** * Returns the {@code User} with * the given ID. * * <p>Example: * <pre>{@code * var user = findUser(123); * }</pre> * * @param id the user ID * @return the user */ public User findUser(int id) { ... }
모던 /// Returns the `User` with /// the given ID. /// /// Example: /// ```java /// var user = findUser(123); /// ``` /// /// @param id the user ID /// @return the user public User findUser(int id) { ... }
마우스를 올려 모던 코드 보기 →
Language

모듈 임포트 선언

이전 import java.util.List; import java.util.Map; import java.util.stream.Collectors; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path;
모던 import module java.base; // All of java.util, java.io, java.nio // etc. available in one line
마우스를 올려 모던 코드 보기 →
Language

instanceof 패턴 매칭

이전 if (obj instanceof String) { String s = (String) obj; System.out.println(s.length()); }
모던 if (obj instanceof String s) { IO.println(s.length()); }
마우스를 올려 모던 코드 보기 →
Language

switch의 패턴 매칭

이전 String format(Object obj) { if (obj instanceof Integer i) return "int: " + i; else if (obj instanceof Double d) return "double: " + d; else if (obj instanceof String s) return "str: " + s; return "unknown"; }
모던 String format(Object obj) { return switch (obj) { case Integer i -> "int: " + i; case Double d -> "double: " + d; case String s -> "str: " + s; default -> "unknown"; }; }
마우스를 올려 모던 코드 보기 →
Language

패턴의 기본 타입

이전 String classify(int code) { if (code >= 200 && code < 300) return "success"; else if (code >= 400 && code < 500) return "client error"; else return "other"; }
모던 String classify(int code) { return switch (code) { case int c when c >= 200 && c < 300 -> "success"; case int c when c >= 400 && c < 500 -> "client error"; default -> "other"; }; }
마우스를 올려 모던 코드 보기 →
Language

인터페이스 private 메서드

이전 interface Logger { default void logInfo(String msg) { System.out.println( "[INFO] " + timestamp() + msg); } default void logWarn(String msg) { System.out.println( "[WARN] " + timestamp() + msg); } }
모던 interface Logger { private String format(String lvl, String msg) { return "[" + lvl + "] " + timestamp() + msg; } default void logInfo(String msg) { IO.println(format("INFO", msg)); } default void logWarn(String msg) { IO.println(format("WARN", msg)); } }
마우스를 올려 모던 코드 보기 →
Language

레코드 패턴 (구조 분해)

이전 if (obj instanceof Point) { Point p = (Point) obj; int x = p.getX(); int y = p.getY(); System.out.println(x + y); }
모던 if (obj instanceof Point(int x, int y)) { IO.println(x + y); }
마우스를 올려 모던 코드 보기 →
Language

데이터 클래스를 위한 레코드

이전 public class Point { private final int x, y; public Point(int x, int y) { ... } public int getX() { return x; } public int getY() { return y; } // equals, hashCode, toString }
모던 public record Point(int x, int y) {}
마우스를 올려 모던 코드 보기 →
Language

타입 계층을 위한 봉인된 클래스

이전 // Anyone can extend Shape public abstract class Shape { } public class Circle extends Shape { } public class Rect extends Shape { } // unknown subclasses possible
모던 public sealed interface Shape permits Circle, Rect {} public record Circle(double r) implements Shape {} public record Rect(double w, double h) implements Shape {}
마우스를 올려 모던 코드 보기 →
Language

내부 클래스의 정적 멤버

이전 class Library { // Must be static nested class static class Book { static int globalBookCount; Book() { globalBookCount++; } } } // Usage var book = new Library.Book();
모던 class Library { // Can be inner class with statics class Book { static int globalBookCount; Book() { Book.globalBookCount++; } } } // Usage var lib = new Library(); var book = lib.new Book();
마우스를 올려 모던 코드 보기 →
Language

인터페이스의 정적 메서드

이전 // Separate utility class needed public class ValidatorUtils { public static boolean isBlank( String s) { return s == null || s.trim().isEmpty(); } } // Usage if (ValidatorUtils.isBlank(input)) { ... }
모던 public interface Validator { boolean validate(String s); static boolean isBlank(String s) { return s == null || s.trim().isEmpty(); } } // Usage if (Validator.isBlank(input)) { ... }
마우스를 올려 모던 코드 보기 →
Language

Switch 표현식

이전 String msg; switch (day) { case MONDAY: msg = "Start"; break; case FRIDAY: msg = "End"; break; default: msg = "Mid"; }
모던 String msg = switch (day) { case MONDAY -> "Start"; case FRIDAY -> "End"; default -> "Mid"; };
마우스를 올려 모던 코드 보기 →
Language

여러 줄 문자열을 위한 텍스트 블록

이전 String json = "{\n" + " \"name\": \"Duke\",\n" + " \"age\": 30\n" + "}";
모던 String json = """ { "name": "Duke", "age": 30 }""";
마우스를 올려 모던 코드 보기 →
Language

var를 사용한 타입 추론

이전 Map<String, List<Integer>> map = new HashMap<String, List<Integer>>(); for (Map.Entry<String, List<Integer>> e : map.entrySet()) { // verbose type noise }
모던 var map = new HashMap<String, List<Integer>>(); for (var entry : map.entrySet()) { // clean and readable }
마우스를 올려 모던 코드 보기 →
Language

_를 사용한 이름 없는 변수

이전 try { parse(input); } catch (Exception ignored) { log("parse failed"); } map.forEach((key, value) -> { process(value); // key unused });
모던 try { parse(input); } catch (Exception _) { log("parse failed"); } map.forEach((_, value) -> { process(value); });
마우스를 올려 모던 코드 보기 →
Collections

Collectors.teeing()

이전 long count = items.stream().count(); double sum = items.stream() .mapToDouble(Item::price) .sum(); var result = new Stats(count, sum);
모던 var result = items.stream().collect( Collectors.teeing( Collectors.counting(), Collectors.summingDouble(Item::price), Stats::new ) );
마우스를 올려 모던 코드 보기 →
Collections

컬렉션 불변 복사

이전 List<String> copy = Collections.unmodifiableList( new ArrayList<>(original) );
모던 List<String> copy = List.copyOf(original);
마우스를 올려 모던 코드 보기 →
Collections

불변 리스트 생성

이전 List<String> list = Collections.unmodifiableList( new ArrayList<>( Arrays.asList("a", "b", "c") ) );
모던 List<String> list = List.of("a", "b", "c");
마우스를 올려 모던 코드 보기 →
Collections

불변 맵 생성

이전 Map<String, Integer> map = new HashMap<>(); map.put("a", 1); map.put("b", 2); map.put("c", 3); map = Collections.unmodifiableMap(map);
모던 Map<String, Integer> map = Map.of("a", 1, "b", 2, "c", 3);
마우스를 올려 모던 코드 보기 →
Collections

불변 셋 생성

이전 Set<String> set = Collections.unmodifiableSet( new HashSet<>( Arrays.asList("a", "b", "c") ) );
모던 Set<String> set = Set.of("a", "b", "c");
마우스를 올려 모던 코드 보기 →
Collections

Map.entry() 팩토리

이전 Map.Entry<String, Integer> e = new AbstractMap.SimpleEntry<>( "key", 42 );
모던 var e = Map.entry("key", 42);
마우스를 올려 모던 코드 보기 →
Collections

리스트 역순 반복

이전 for (ListIterator<String> it = list.listIterator(list.size()); it.hasPrevious(); ) { String element = it.previous(); System.out.println(element); }
모던 for (String element : list.reversed()) { IO.println(element); }
마우스를 올려 모던 코드 보기 →
Collections

시퀀스 컬렉션

이전 // Get last element var last = list.get(list.size() - 1); // Get first var first = list.get(0); // Reverse iteration: manual
모던 var last = list.getLast(); var first = list.getFirst(); var reversed = list.reversed();
마우스를 올려 모던 코드 보기 →
Collections

타입이 지정된 스트림 toArray

이전 List<String> list = getNames(); String[] arr = new String[list.size()]; for (int i = 0; i < list.size(); i++) { arr[i] = list.get(i); }
모던 String[] arr = getNames().stream() .filter(n -> n.length() > 3) .toArray(String[]::new);
마우스를 올려 모던 코드 보기 →
Collections

수정 불가능한 컬렉터

이전 List<String> list = stream.collect( Collectors.collectingAndThen( Collectors.toList(), Collections::unmodifiableList ) );
모던 List<String> list = stream.toList();
마우스를 올려 모던 코드 보기 →
Strings

스트림으로서의 문자열 문자

이전 for (int i = 0; i < str.length(); i++) { char c = str.charAt(i); if (Character.isDigit(c)) { process(c); } }
모던 str.chars() .filter(Character::isDigit) .forEach(c -> process((char) c));
마우스를 올려 모던 코드 보기 →
Strings

String.formatted()

이전 String msg = String.format( "Hello %s, you are %d", name, age );
모던 String msg = "Hello %s, you are %d" .formatted(name, age);
마우스를 올려 모던 코드 보기 →
Strings

String.indent()와 transform()

이전 String[] lines = text.split("\n"); StringBuilder sb = new StringBuilder(); for (String line : lines) { sb.append(" ").append(line) .append("\n"); } String indented = sb.toString();
모던 String indented = text.indent(4); String result = text .transform(String::strip) .transform(s -> s.replace(" ", "-"));
마우스를 올려 모던 코드 보기 →
Strings

String.isBlank()

이전 boolean blank = str.trim().isEmpty(); // or: str.trim().length() == 0
모던 boolean blank = str.isBlank(); // handles Unicode whitespace too
마우스를 올려 모던 코드 보기 →
Strings

줄 분할을 위한 String.lines()

이전 String text = "one\ntwo\nthree"; String[] lines = text.split("\n"); for (String line : lines) { System.out.println(line); }
모던 String text = "one\ntwo\nthree"; text.lines().forEach(IO::println);
마우스를 올려 모던 코드 보기 →
Strings

String.repeat()

이전 StringBuilder sb = new StringBuilder(); for (int i = 0; i < 3; i++) { sb.append("abc"); } String result = sb.toString();
모던 String result = "abc".repeat(3); // "abcabcabc"
마우스를 올려 모던 코드 보기 →
Strings

String.strip() 대 trim()

이전 // trim() only removes ASCII whitespace // (chars <= U+0020) String clean = str.trim();
모던 // strip() removes all Unicode whitespace String clean = str.strip(); String left = str.stripLeading(); String right = str.stripTrailing();
마우스를 올려 모던 코드 보기 →
Streams

Collectors.flatMapping()

이전 // Flatten within a grouping collector // Required complex custom collector Map<String, Set<String>> tagsByDept = // no clean way in Java 8
모던 var tagsByDept = employees.stream() .collect(groupingBy( Emp::dept, flatMapping( e -> e.tags().stream(), toSet() ) ));
마우스를 올려 모던 코드 보기 →
Streams

Optional.ifPresentOrElse()

이전 Optional<User> user = findUser(id); if (user.isPresent()) { greet(user.get()); } else { handleMissing(); }
모던 findUser(id).ifPresentOrElse( this::greet, this::handleMissing );
마우스를 올려 모던 코드 보기 →
Streams

Optional.or() 폴백

이전 Optional<Config> cfg = primary(); if (!cfg.isPresent()) { cfg = secondary(); } if (!cfg.isPresent()) { cfg = defaults(); }
모던 Optional<Config> cfg = primary() .or(this::secondary) .or(this::defaults);
마우스를 올려 모던 코드 보기 →
Streams

부정을 위한 Predicate.not()

이전 List<String> nonEmpty = list.stream() .filter(s -> !s.isBlank()) .collect(Collectors.toList());
모던 List<String> nonEmpty = list.stream() .filter(Predicate.not(String::isBlank)) .toList();
마우스를 올려 모던 코드 보기 →
Streams

스트림 수집기

이전 // Sliding window: manual implementation List<List<T>> windows = new ArrayList<>(); for (int i = 0; i <= list.size()-3; i++) { windows.add( list.subList(i, i + 3)); }
모던 var windows = stream .gather( Gatherers.windowSliding(3) ) .toList();
마우스를 올려 모던 코드 보기 →
Streams

조건자가 있는 Stream.iterate()

이전 Stream.iterate(1, n -> n * 2) .limit(10) .forEach(System.out::println); // can't stop at a condition
모던 Stream.iterate( 1, n -> n < 1000, n -> n * 2 ).forEach(IO::println); // stops when n >= 1000
마우스를 올려 모던 코드 보기 →
Streams

Stream.mapMulti()

이전 stream.flatMap(order -> order.items().stream() .map(item -> new OrderItem( order.id(), item) ) );
모던 stream.<OrderItem>mapMulti( (order, downstream) -> { for (var item : order.items()) downstream.accept( new OrderItem(order.id(), item)); } );
마우스를 올려 모던 코드 보기 →
Streams

Stream.ofNullable()

이전 Stream<String> s = val != null ? Stream.of(val) : Stream.empty();
모던 Stream<String> s = Stream.ofNullable(val);
마우스를 올려 모던 코드 보기 →
Streams

스트림 takeWhile / dropWhile

이전 List<Integer> result = new ArrayList<>(); for (int n : sorted) { if (n >= 100) break; result.add(n); } // no stream equivalent in Java 8
모던 var result = sorted.stream() .takeWhile(n -> n < 100) .toList(); // or: .dropWhile(n -> n < 10)
마우스를 올려 모던 코드 보기 →
Streams

Stream.toList()

이전 List<String> result = stream .filter(s -> s.length() > 3) .collect(Collectors.toList());
모던 List<String> result = stream .filter(s -> s.length() > 3) .toList();
마우스를 올려 모던 코드 보기 →
Streams

가상 스레드 실행자

이전 ExecutorService exec = Executors.newFixedThreadPool(10); try { futures = tasks.stream() .map(t -> exec.submit(t)) .toList(); } finally { exec.shutdown(); }
모던 try (var exec = Executors .newVirtualThreadPerTaskExecutor()) { var futures = tasks.stream() .map(exec::submit) .toList(); }
마우스를 올려 모던 코드 보기 →
Concurrency

CompletableFuture 체이닝

이전 Future<String> future = executor.submit(this::fetchData); String data = future.get(); // blocks String result = transform(data);
모던 CompletableFuture.supplyAsync( this::fetchData ) .thenApply(this::transform) .thenAccept(IO::println);
마우스를 올려 모던 코드 보기 →
Concurrency

가상 스레드를 이용한 동시 HTTP 요청

이전 ExecutorService pool = Executors.newFixedThreadPool(10); List<Future<String>> futures = urls.stream() .map(u -> pool.submit( () -> fetchUrl(u))) .toList(); // manual shutdown, blocking get()
모던 try (var exec = Executors .newVirtualThreadPerTaskExecutor()) { var results = urls.stream() .map(u -> exec.submit( () -> client.send(req(u), ofString()).body())) .toList().stream() .map(Future::join).toList(); }
마우스를 올려 모던 코드 보기 →
Concurrency

ExecutorService 자동 닫기

이전 ExecutorService exec = Executors.newCachedThreadPool(); try { exec.submit(task); } finally { exec.shutdown(); exec.awaitTermination( 1, TimeUnit.MINUTES); }
모던 try (var exec = Executors.newCachedThreadPool()) { exec.submit(task); } // auto shutdown + await on close
마우스를 올려 모던 코드 보기 →
Concurrency

락 없는 지연 초기화

이전 class Config { private static volatile Config inst; static Config get() { if (inst == null) { synchronized (Config.class) { if (inst == null) inst = load(); } } return inst; } }
모던 class Config { private static final StableValue<Config> INST = StableValue.of(Config::load); static Config get() { return INST.get(); } }
마우스를 올려 모던 코드 보기 →
Concurrency

모던 프로세스 API

이전 Process p = Runtime.getRuntime() .exec("ls -la"); int code = p.waitFor(); // no way to get PID // no easy process info
모던 ProcessHandle ph = ProcessHandle.current(); long pid = ph.pid(); ph.info().command() .ifPresent(IO::println); ph.children().forEach( c -> IO.println(c.pid()));
마우스를 올려 모던 코드 보기 →
Concurrency

스코프 값

이전 static final ThreadLocal<User> CURRENT = new ThreadLocal<>(); void handle(Request req) { CURRENT.set(authenticate(req)); try { process(); } finally { CURRENT.remove(); } }
모던 static final ScopedValue<User> CURRENT = ScopedValue.newInstance(); void handle(Request req) { ScopedValue.where(CURRENT, authenticate(req) ).run(this::process); }
마우스를 올려 모던 코드 보기 →
Concurrency

안정적인 값

이전 private volatile Logger logger; Logger getLogger() { if (logger == null) { synchronized (this) { if (logger == null) logger = createLogger(); } } return logger; }
모던 private final StableValue<Logger> logger = StableValue.of(this::createLogger); Logger getLogger() { return logger.get(); }
마우스를 올려 모던 코드 보기 →
Concurrency

구조적 동시성

이전 ExecutorService exec = Executors.newFixedThreadPool(2); Future<User> u = exec.submit(this::fetchUser); Future<Order> o = exec.submit(this::fetchOrder); try { return combine(u.get(), o.get()); } finally { exec.shutdown(); }
모던 try (var scope = new StructuredTaskScope .ShutdownOnFailure()) { var u = scope.fork(this::fetchUser); var o = scope.fork(this::fetchOrder); scope.join().throwIfFailed(); return combine(u.get(), o.get()); }
마우스를 올려 모던 코드 보기 →
Concurrency

Duration을 사용한 Thread.sleep

이전 // What unit is 5000? ms? us? Thread.sleep(5000); // 2.5 seconds: math required Thread.sleep(2500);
모던 Thread.sleep( Duration.ofSeconds(5) ); Thread.sleep( Duration.ofMillis(2500) );
마우스를 올려 모던 코드 보기 →
Concurrency

가상 스레드

이전 Thread thread = new Thread(() -> { System.out.println("hello"); }); thread.start(); thread.join();
모던 Thread.startVirtualThread(() -> { IO.println("hello"); }).join();
마우스를 올려 모던 코드 보기 →
I/O

역직렬화 필터

이전 // Dangerous: accepts any class ObjectInputStream ois = new ObjectInputStream(input); Object obj = ois.readObject(); // deserialization attacks possible!
모던 ObjectInputFilter filter = ObjectInputFilter.Config .createFilter( "com.myapp.*;!*" ); ois.setObjectInputFilter(filter); Object obj = ois.readObject();
마우스를 올려 모던 코드 보기 →
I/O

파일 메모리 매핑

이전 try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.WRITE)) { MappedByteBuffer buffer = channel.map( FileChannel.MapMode.READ_WRITE, 0, (int) channel.size()); // Limited to 2GB // Freed by GC, no control }
모던 FileChannel channel = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.WRITE); try (Arena arena = Arena.ofShared()) { MemorySegment segment = channel.map( FileChannel.MapMode.READ_WRITE, 0, channel.size(), arena); // No size limit // ... } // Deterministic cleanup
마우스를 올려 모던 코드 보기 →
I/O

Files.mismatch()

이전 // Compare two files byte by byte byte[] f1 = Files.readAllBytes(path1); byte[] f2 = Files.readAllBytes(path2); boolean equal = Arrays.equals(f1, f2); // loads both files entirely into memory
모던 long pos = Files.mismatch(path1, path2); // -1 if identical // otherwise: position of first difference
마우스를 올려 모던 코드 보기 →
I/O

모던 HTTP 클라이언트

이전 URL url = new URL("https://api.com/data"); HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("GET"); BufferedReader in = new BufferedReader( new InputStreamReader(con.getInputStream())); // read lines, close streams...
모던 var client = HttpClient.newHttpClient(); var request = HttpRequest.newBuilder() .uri(URI.create("https://api.com/data")) .build(); var response = client.send( request, BodyHandlers.ofString()); String body = response.body();
마우스를 올려 모던 코드 보기 →
I/O

InputStream.transferTo()

이전 byte[] buf = new byte[8192]; int n; while ((n = input.read(buf)) != -1) { output.write(buf, 0, n); }
모던 input.transferTo(output);
마우스를 올려 모던 코드 보기 →
I/O

콘솔 I/O를 위한 IO 클래스

이전 import java.util.Scanner; Scanner sc = new Scanner(System.in); System.out.print("Name: "); String name = sc.nextLine(); System.out.println("Hello, " + name); sc.close();
모던 String name = IO.readln("Name: "); IO.println("Hello, " + name);
마우스를 올려 모던 코드 보기 →
I/O

Path.of() 팩토리

이전 Path path = Paths.get("src", "main", "java", "App.java");
모던 var path = Path.of("src", "main", "java", "App.java");
마우스를 올려 모던 코드 보기 →
I/O

파일 읽기

이전 StringBuilder sb = new StringBuilder(); try (BufferedReader br = new BufferedReader( new FileReader("data.txt"))) { String line; while ((line = br.readLine()) != null) sb.append(line).append("\n"); } String content = sb.toString();
모던 String content = Files.readString(Path.of("data.txt"));
마우스를 올려 모던 코드 보기 →
I/O

try-with-resources 개선

이전 Connection conn = getConnection(); // Must re-declare in try try (Connection c = conn) { use(c); }
모던 Connection conn = getConnection(); // Use existing variable directly try (conn) { use(conn); }
마우스를 올려 모던 코드 보기 →
I/O

파일 쓰기

이전 try (FileWriter fw = new FileWriter("out.txt"); BufferedWriter bw = new BufferedWriter(fw)) { bw.write(content); }
모던 Files.writeString( Path.of("out.txt"), content );
마우스를 올려 모던 코드 보기 →
Errors

유용한 NullPointerException

이전 // Old NPE message: // "NullPointerException" // at MyApp.main(MyApp.java:42) // Which variable was null?!
모던 // Modern NPE message: // Cannot invoke "String.length()" // because "user.address().city()" // is null // Exact variable identified!
마우스를 올려 모던 코드 보기 →
Errors

다중 캐치 예외 처리

이전 try { process(); } catch (IOException e) { log(e); } catch (SQLException e) { log(e); } catch (ParseException e) { log(e); }
모던 try { process(); } catch (IOException | SQLException | ParseException e) { log(e); }
마우스를 올려 모던 코드 보기 →
Errors

switch의 null 케이스

이전 // Must check before switch if (status == null) { return "unknown"; } return switch (status) { case ACTIVE -> "active"; case PAUSED -> "paused"; default -> "other"; };
모던 return switch (status) { case null -> "unknown"; case ACTIVE -> "active"; case PAUSED -> "paused"; default -> "other"; };
마우스를 올려 모던 코드 보기 →
Errors

Optional 체이닝

이전 String city = null; if (user != null) { Address addr = user.getAddress(); if (addr != null) { city = addr.getCity(); } } if (city == null) city = "Unknown";
모던 String city = Optional.ofNullable(user) .map(User::address) .map(Address::city) .orElse("Unknown");
마우스를 올려 모던 코드 보기 →
Errors

인수 없는 Optional.orElseThrow()

이전 // Risky: get() throws if empty, no clear intent String value = optional.get(); // Verbose: supplier just for NoSuchElementException String value = optional .orElseThrow(NoSuchElementException::new);
모던 // Clear intent: throws NoSuchElementException if empty String value = optional.orElseThrow();
마우스를 올려 모던 코드 보기 →
Errors

레코드 기반 오류 응답

이전 // Verbose error class public class ErrorResponse { private final int code; private final String message; // constructor, getters, equals, // hashCode, toString... }
모던 public record ApiError( int code, String message, Instant timestamp ) { public ApiError(int code, String msg) { this(code, msg, Instant.now()); } }
마우스를 올려 모던 코드 보기 →
Errors

Objects.requireNonNullElse()

이전 String name = input != null ? input : "default"; // easy to get the order wrong
모던 String name = Objects .requireNonNullElse( input, "default" );
마우스를 올려 모던 코드 보기 →
Date/Time

날짜 형식 지정

이전 // Not thread-safe! SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String formatted = sdf.format(date); // Must synchronize for concurrent use
모던 DateTimeFormatter fmt = DateTimeFormatter.ofPattern( "uuuu-MM-dd"); String formatted = LocalDate.now().format(fmt); // Thread-safe, immutable
마우스를 올려 모던 코드 보기 →
Date/Time

Duration과 Period

이전 // How many days between two dates? long diff = date2.getTime() - date1.getTime(); long days = diff / (1000 * 60 * 60 * 24); // ignores DST, leap seconds
모던 long days = ChronoUnit.DAYS .between(date1, date2); Period period = Period.between( date1, date2); Duration elapsed = Duration.between( time1, time2);
마우스를 올려 모던 코드 보기 →
Date/Time

HexFormat

이전 // Pad to 2 digits, uppercase String hex = String.format( "%02X", byteValue); // Parse hex string int val = Integer.parseInt( "FF", 16);
모던 var hex = HexFormat.of() .withUpperCase(); String s = hex.toHexDigits( byteValue); byte[] bytes = hex.parseHex("48656C6C6F");
마우스를 올려 모던 코드 보기 →
Date/Time

나노초 정밀도를 가진 Instant

이전 // Millisecond precision only long millis = System.currentTimeMillis(); // 1708012345678
모던 // Microsecond/nanosecond precision Instant now = Instant.now(); // 2025-02-15T20:12:25.678901234Z long nanos = now.getNano();
마우스를 올려 모던 코드 보기 →
Date/Time

java.time API 기초

이전 // Mutable, confusing, zero-indexed months Calendar cal = Calendar.getInstance(); cal.set(2025, 0, 15); // January = 0! Date date = cal.getTime(); // not thread-safe
모던 LocalDate date = LocalDate.of( 2025, Month.JANUARY, 15); LocalTime time = LocalTime.of(14, 30); Instant now = Instant.now(); // immutable, thread-safe
마우스를 올려 모던 코드 보기 →
Date/Time

Math.clamp()

이전 // Clamp value between min and max int clamped = Math.min(Math.max(value, 0), 100); // or: min and max order confusion
모던 int clamped = Math.clamp(value, 0, 100); // value constrained to [0, 100]
마우스를 올려 모던 코드 보기 →
Security

키 파생 함수

이전 SecretKeyFactory factory = SecretKeyFactory.getInstance( "PBKDF2WithHmacSHA256"); KeySpec spec = new PBEKeySpec( password, salt, 10000, 256); SecretKey key = factory.generateSecret(spec);
모던 var kdf = KDF.getInstance("HKDF-SHA256"); SecretKey key = kdf.deriveKey( "AES", KDF.HKDFParameterSpec .ofExtract() .addIKM(inputKey) .addSalt(salt) .thenExpand(info, 32) .build() );
마우스를 올려 모던 코드 보기 →
Security

PEM 인코딩/디코딩

이전 String pem = "-----BEGIN CERTIFICATE-----\n" + Base64.getMimeEncoder() .encodeToString( cert.getEncoded()) + "\n-----END CERTIFICATE-----";
모던 // Encode to PEM String pem = PEMEncoder.of() .encodeToString(cert); // Decode from PEM var cert = PEMDecoder.of() .decode(pemString);
마우스를 올려 모던 코드 보기 →
Security

RandomGenerator 인터페이스

이전 // Hard-coded to one algorithm Random rng = new Random(); int value = rng.nextInt(100); // Or thread-local, but still locked in int value = ThreadLocalRandom.current() .nextInt(100);
모던 // Algorithm-agnostic via factory var rng = RandomGenerator.of("L64X128MixRandom"); int value = rng.nextInt(100); // Or get a splittable generator var rng = RandomGeneratorFactory .of("L64X128MixRandom").create();
마우스를 올려 모던 코드 보기 →
Security

강력한 난수 생성

이전 // Default algorithm — may not be // the strongest available SecureRandom random = new SecureRandom(); byte[] bytes = new byte[32]; random.nextBytes(bytes);
모던 // Platform's strongest algorithm SecureRandom random = SecureRandom.getInstanceStrong(); byte[] bytes = new byte[32]; random.nextBytes(bytes);
마우스를 올려 모던 코드 보기 →
Security

기본 TLS 1.3

이전 SSLContext ctx = SSLContext.getInstance("TLSv1.2"); ctx.init(null, trustManagers, null); SSLSocketFactory factory = ctx.getSocketFactory(); // Must specify protocol version
모던 // TLS 1.3 is the default! var client = HttpClient.newBuilder() .sslContext(SSLContext.getDefault()) .build(); // Already using TLS 1.3
마우스를 올려 모던 코드 보기 →
Tooling

AOT 클래스 사전 로딩

이전 // Every startup: // - Load 10,000+ classes // - Verify bytecode // - JIT compile hot paths // Startup: 2-5 seconds
모던 // Training run: $ java -XX:AOTCacheOutput=app.aot \ -cp app.jar com.App // Production: $ java -XX:AOTCache=app.aot \ -cp app.jar com.App
마우스를 올려 모던 코드 보기 →
Tooling

내장 HTTP 서버

이전 // Install and configure a web server // (Apache, Nginx, or embedded Jetty) // Or write boilerplate with com.sun.net.httpserver HttpServer server = HttpServer.create( new InetSocketAddress(8080), 0); server.createContext("/", exchange -> { ... }); server.start();
모던 // Terminal: serve current directory $ jwebserver // Or use the API (JDK 18+) var server = SimpleFileServer.createFileServer( new InetSocketAddress(8080), Path.of("."), OutputLevel.VERBOSE); server.start();
마우스를 올려 모던 코드 보기 →
Tooling

컴팩트 객체 헤더

이전 // Default: 128-bit object header // = 16 bytes overhead per object // A boolean field object = 32 bytes! // Mark word (64) + Klass pointer (64)
모던 // -XX:+UseCompactObjectHeaders // 64-bit object header // = 8 bytes overhead per object // 50% less header memory // More objects fit in cache
마우스를 올려 모던 코드 보기 →
Tooling

프로파일링을 위한 JFR

이전 // Install VisualVM / YourKit / JProfiler // Attach to running process // Configure sampling // Export and analyze // External tool required
모던 // Start with profiling enabled $ java -XX:StartFlightRecording= filename=rec.jfr MyApp // Or attach to running app: $ jcmd <pid> JFR.start
마우스를 올려 모던 코드 보기 →
Tooling

프로토타이핑을 위한 JShell

이전 // 1. Create Test.java // 2. javac Test.java // 3. java Test // Just to test one expression!
모던 $ jshell jshell> "hello".chars().count() $1 ==> 5 jshell> List.of(1,2,3).reversed() $2 ==> [3, 2, 1]
마우스를 올려 모던 코드 보기 →
Tooling

JSpecify null 안전성을 갖춘 JUnit 6

이전 import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; class UserServiceTest { // JUnit 5: no null contracts on the API // Can assertEquals() accept null? Check source... // Does fail(String) allow null message? Unknown. @Test void findUser_found() { // Is result nullable? API doesn't say User result = service.findById("u1"); assertNotNull(result); assertEquals("Alice", result.name()); } @Test void findUser_notFound() { // Hope this returns null, not throws... assertNull(service.findById("missing")); } }
모던 import org.junit.jupiter.api.Test; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import static org.junit.jupiter.api.Assertions.*; @NullMarked // all refs non-null unless @Nullable class UserServiceTest { // JUnit 6 API is @NullMarked: // assertNull(@Nullable Object actual) // assertEquals(@Nullable Object, @Nullable Object) // fail(@Nullable String message) @Test void findUser_found() { // IDE warns: findById returns @Nullable User @Nullable User result = service.findById("u1"); assertNotNull(result); // narrows type to non-null assertEquals("Alice", result.name()); // safe } @Test void findUser_notFound() { @Nullable User result = service.findById("missing"); assertNull(result); // IDE confirms null expectation } }
마우스를 올려 모던 코드 보기 →
Tooling

다중 파일 소스 런처

이전 $ javac *.java $ java Main // Must compile all files first // Need a build tool for dependencies
모던 $ java Main.java // Automatically finds and compiles // other source files referenced // by Main.java
마우스를 올려 모던 코드 보기 →
Tooling

단일 파일 실행

이전 $ javac HelloWorld.java $ java HelloWorld // Two steps every time
모던 $ java HelloWorld.java // Compiles and runs in one step // Also works with shebangs: #!/usr/bin/java --source 25
마우스를 올려 모던 코드 보기 →
Enterprise

EJB 타이머 대 Jakarta 스케줄러

이전 @Stateless public class ReportGenerator { @Resource TimerService timerService; @PostConstruct public void init() { timerService.createCalendarTimer( new ScheduleExpression() .hour("2").minute("0")); } @Timeout public void generateReport(Timer timer) { // runs every day at 02:00 buildDailyReport(); } }
모던 @ApplicationScoped public class ReportGenerator { @Resource ManagedScheduledExecutorService scheduler; @PostConstruct public void init() { scheduler.scheduleAtFixedRate( this::generateReport, 0, 24, TimeUnit.HOURS); } public void generateReport() { buildDailyReport(); } }
마우스를 올려 모던 코드 보기 →
Enterprise

EJB 대 CDI

이전 @Stateless public class OrderEJB { @EJB private InventoryEJB inventory; public void placeOrder(Order order) { // container-managed transaction inventory.reserve(order.getItem()); } }
모던 @ApplicationScoped public class OrderService { @Inject private InventoryService inventory; @Transactional public void placeOrder(Order order) { inventory.reserve(order.getItem()); } }
마우스를 올려 모던 코드 보기 →
Enterprise

JDBC ResultSet 매핑 대 JPA Criteria API

이전 String sql = "SELECT * FROM users" + " WHERE status = ? AND age > ?"; try (Connection con = ds.getConnection(); PreparedStatement ps = con.prepareStatement(sql)) { ps.setString(1, status); ps.setInt(2, minAge); ResultSet rs = ps.executeQuery(); List<User> users = new ArrayList<>(); while (rs.next()) { User u = new User(); u.setId(rs.getLong("id")); u.setName(rs.getString("name")); u.setAge(rs.getInt("age")); users.add(u); } }
모던 @PersistenceContext EntityManager em; public List<User> findActiveAboveAge( String status, int minAge) { var cb = em.getCriteriaBuilder(); var cq = cb.createQuery(User.class); var root = cq.from(User.class); cq.select(root).where( cb.equal(root.get("status"), status), cb.greaterThan(root.get("age"), minAge)); return em.createQuery(cq).getResultList(); }
마우스를 올려 모던 코드 보기 →
Enterprise

JDBC 대 jOOQ

이전 String sql = "SELECT id, name, email FROM users " + "WHERE department = ? AND salary > ?"; try (Connection con = ds.getConnection(); PreparedStatement ps = con.prepareStatement(sql)) { ps.setString(1, department); ps.setBigDecimal(2, minSalary); ResultSet rs = ps.executeQuery(); List<User> result = new ArrayList<>(); while (rs.next()) { result.add(new User( rs.getLong("id"), rs.getString("name"), rs.getString("email"))); } return result; }
모던 DSLContext dsl = DSL.using(ds, SQLDialect.POSTGRES); return dsl .select(USERS.ID, USERS.NAME, USERS.EMAIL) .from(USERS) .where(USERS.DEPARTMENT.eq(department) .and(USERS.SALARY.gt(minSalary))) .fetchInto(User.class);
마우스를 올려 모던 코드 보기 →
Enterprise

JDBC 대 JPA

이전 String sql = "SELECT * FROM users WHERE id = ?"; try (Connection con = dataSource.getConnection(); PreparedStatement ps = con.prepareStatement(sql)) { ps.setLong(1, id); ResultSet rs = ps.executeQuery(); if (rs.next()) { User u = new User(); u.setId(rs.getLong("id")); u.setName(rs.getString("name")); } }
모던 @PersistenceContext EntityManager em; public User findUser(Long id) { return em.find(User.class, id); } public List<User> findByName(String name) { return em.createQuery( "SELECT u FROM User u WHERE u.name = :name", User.class) .setParameter("name", name) .getResultList(); }
마우스를 올려 모던 코드 보기 →
Enterprise

JNDI 조회 대 CDI 주입

이전 public class OrderService { private DataSource ds; public void init() throws NamingException { InitialContext ctx = new InitialContext(); ds = (DataSource) ctx.lookup( "java:comp/env/jdbc/OrderDB"); } public List<Order> findAll() throws SQLException { try (Connection con = ds.getConnection()) { // query orders } } }
모던 @ApplicationScoped public class OrderService { @Inject @Resource(name = "jdbc/OrderDB") DataSource ds; public List<Order> findAll() throws SQLException { try (Connection con = ds.getConnection()) { // query orders } } }
마우스를 올려 모던 코드 보기 →
Enterprise

JPA 대 Jakarta Data

이전 @PersistenceContext EntityManager em; public User findById(Long id) { return em.find(User.class, id); } public List<User> findByName(String name) { return em.createQuery( "SELECT u FROM User u WHERE u.name = :name", User.class) .setParameter("name", name) .getResultList(); } public void save(User user) { em.persist(user); }
모던 @Repository public interface Users extends CrudRepository<User, Long> { List<User> findByName(String name); }
마우스를 올려 모던 코드 보기 →
Enterprise

JSF 관리 빈 대 CDI Named 빈

이전 @ManagedBean @SessionScoped public class UserBean implements Serializable { @ManagedProperty("#{userService}") private UserService userService; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void setUserService(UserService svc) { this.userService = svc; } }
모던 @Named @SessionScoped public class UserBean implements Serializable { @Inject private UserService userService; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
마우스를 올려 모던 코드 보기 →
Enterprise

수동 JPA 트랜잭션 대 선언적 @Transactional

이전 @PersistenceContext EntityManager em; public void transferFunds(Long from, Long to, BigDecimal amount) { EntityTransaction tx = em.getTransaction(); tx.begin(); try { Account src = em.find(Account.class, from); Account dst = em.find(Account.class, to); src.debit(amount); dst.credit(amount); tx.commit(); } catch (Exception e) { tx.rollback(); throw e; } }
모던 @ApplicationScoped public class AccountService { @PersistenceContext EntityManager em; @Transactional public void transferFunds(Long from, Long to, BigDecimal amount) { var src = em.find(Account.class, from); var dst = em.find(Account.class, to); src.debit(amount); dst.credit(amount); } }
마우스를 올려 모던 코드 보기 →
Enterprise

Message-Driven Bean 대 Reactive Messaging

이전 @MessageDriven(activationConfig = { @ActivationConfigProperty( propertyName = "destinationType", propertyValue = "jakarta.jms.Queue"), @ActivationConfigProperty( propertyName = "destination", propertyValue = "java:/jms/OrderQueue") }) public class OrderMDB implements MessageListener { @Override public void onMessage(Message message) { TextMessage txt = (TextMessage) message; processOrder(txt.getText()); } }
모던 @ApplicationScoped public class OrderProcessor { @Incoming("orders") public void process(Order order) { // automatically deserialized from // the "orders" channel fulfillOrder(order); } }
마우스를 올려 모던 코드 보기 →
Enterprise

Servlet 대 JAX-RS

이전 @WebServlet("/users") public class UserServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String id = req.getParameter("id"); res.setContentType("application/json"); res.getWriter().write("{\"id\":\"" + id + "\"}"); } }
모던 @Path("/users") public class UserResource { @GET @Produces(MediaType.APPLICATION_JSON) public Response getUser( @QueryParam("id") String id) { return Response.ok(new User(id)).build(); } }
마우스를 올려 모던 코드 보기 →
Enterprise

Singleton EJB 대 CDI @ApplicationScoped

이전 @Singleton @Startup @ConcurrencyManagement( ConcurrencyManagementType.CONTAINER) public class ConfigCache { private Map<String, String> cache; @PostConstruct public void load() { cache = loadFromDatabase(); } @Lock(LockType.READ) public String get(String key) { return cache.get(key); } @Lock(LockType.WRITE) public void refresh() { cache = loadFromDatabase(); } }
모던 @ApplicationScoped public class ConfigCache { private volatile Map<String, String> cache; @PostConstruct public void load() { cache = loadFromDatabase(); } public String get(String key) { return cache.get(key); } public void refresh() { cache = loadFromDatabase(); } }
마우스를 올려 모던 코드 보기 →
Enterprise

SOAP 웹 서비스 대 Jakarta REST

이전 @WebService public class UserWebService { @WebMethod public UserResponse getUser( @WebParam(name = "id") String id) { User user = findUser(id); UserResponse res = new UserResponse(); res.setId(user.getId()); res.setName(user.getName()); return res; } }
모던 @Path("/users") @Produces(MediaType.APPLICATION_JSON) public class UserResource { @Inject UserService userService; @GET @Path("/{id}") public User getUser(@PathParam("id") String id) { return userService.findById(id); } }
마우스를 올려 모던 코드 보기 →
Enterprise

Spring Framework 7 API 버전 관리

이전 // Version 1 controller @RestController @RequestMapping("/api/v1/products") public class ProductControllerV1 { @GetMapping("/{id}") public ProductDtoV1 getProduct( @PathVariable Long id) { return service.getV1(id); } } // Version 2 — duplicated structure @RestController @RequestMapping("/api/v2/products") public class ProductControllerV2 { @GetMapping("/{id}") public ProductDtoV2 getProduct( @PathVariable Long id) { return service.getV2(id); } }
모던 // Configure versioning once @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configureApiVersioning( ApiVersionConfigurer config) { config.useRequestHeader("X-API-Version"); } } // Single controller, version per method @RestController @RequestMapping("/api/products") public class ProductController { @GetMapping(value = "/{id}", version = "1") public ProductDtoV1 getV1(@PathVariable Long id) { return service.getV1(id); } @GetMapping(value = "/{id}", version = "2") public ProductDtoV2 getV2(@PathVariable Long id) { return service.getV2(id); } }
마우스를 올려 모던 코드 보기 →
Enterprise

JSpecify를 사용한 Spring Null 안전성

이전 import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; public class UserService { @Nullable public User findById(@NonNull String id) { return repository.findById(id).orElse(null); } @NonNull public List<User> findAll() { return repository.findAll(); } @NonNull public User save(@NonNull User user) { return repository.save(user); } }
모던 import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @NullMarked public class UserService { public @Nullable User findById(String id) { return repository.findById(id).orElse(null); } public List<User> findAll() { return repository.findAll(); } public User save(User user) { return repository.save(user); } }
마우스를 올려 모던 코드 보기 →
Enterprise

Spring XML 빈 설정 대 어노테이션 기반

이전 <!-- applicationContext.xml --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="userRepository" class="com.example.UserRepository"> <property name="dataSource" ref="dataSource"/> </bean> <bean id="userService" class="com.example.UserService"> <property name="repository" ref="userRepository"/> </bean> </beans>
모던 @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } @Repository public class UserRepository { private final JdbcTemplate jdbc; public UserRepository(JdbcTemplate jdbc) { this.jdbc = jdbc; } } @Service public class UserService { private final UserRepository repository; public UserService(UserRepository repository) { this.repository = repository; } }
마우스를 올려 모던 코드 보기 →
112
모던 패턴
17
지원 JDK 버전
10
카테고리
0
필요한 Python 코드 줄 수
ESC