Java 8 핵심 기능 완벽 가이드

Java 8 주요 특징

Java 8(2014년 3월 Oracle 공식 릴리스)은 Java 생태계의 중대한 전환점입니다. 함수형 프로그래밍 패러다임을 도입하여 코드의 간결성과 가독성을 크게 향상시켰습니다.

Lambda 표현식

Lambda는 익명 클래스의 축약형 문법으로, 메서드를 값처럼 전달할 수 있게 합니다. 화살표 연산자 ->를 기준으로 좌측에는 매개변수, 우측에는 실행 본문을 배치합니다.

public class LambdaDemo {
    public static void main(String[] args) {
        // 기존 익명 클래스 방식
        Runnable legacy = new Runnable() {
            @Override
            public void run() {
                System.out.println("전통적 방식");
            }
        };
        
        // Lambda 변환
        Runnable modern = () -> System.out.println("현대적 방식");
        
        // 매개변수가 있는 경우
        Comparator<String> lengthCompare = (a, b) -> a.length() - b.length();
        
        new Thread(modern).start();
        new Thread(() -> System.out.println("즉석 생성")).start();
    }
}

함수형 인터페이스

추상 메서드를 단 하나만 보유한 인터페이스를 의미하며, @FunctionalInterface 어노테이션으로 검증할 수 있습니다. Java 8에서 제공하는 대표적인 내장 함수형 인터페이스는 다음과 같습니다.

인터페이스입력반환메서드
Consumer<T>Tvoidaccept(T t)
Supplier<T>없음TT get()
Function<T,R>TRR apply(T t)
Predicate<T>Tbooleanboolean test(T t)
public class FunctionalInterfaceDemo {
    public static void main(String[] args) {
        // Consumer: 소비 패턴
        executePayment(amount -> System.out.println("결제 완료: " + amount + "원"), 50000);
        
        // Supplier: 생성 패턴
        int[] luckyNumbers = generateRandoms(() -> (int)(Math.random() * 45) + 1, 6);
        System.out.println(Arrays.toString(luckyNumbers));
        
        // Function: 변환 패턴
        String processed = transform(String::toUpperCase, "hello world");
        
        // Predicate: 필터 패턴
        List<String> candidates = Arrays.asList("Kim", "Lee", "Park", "Choi");
        List<String> selected = filterByCondition(s -> s.length() >= 4, candidates);
    }
    
    static void executePayment(Consumer<Integer> payment, int amount) {
        payment.accept(amount);
    }
    
    static int[] generateRandoms(Supplier<Integer> generator, int quantity) {
        return IntStream.range(0, quantity).map(i -> generator.get()).toArray();
    }
    
    static String transform(Function<String, String> converter, String target) {
        return converter.apply(target);
    }
    
    static List<String> filterByCondition(Predicate<String> condition, List<String> items) {
        return items.stream().filter(condition).collect(Collectors.toList());
    }
}

메서드 참조

Lambda 본문이 단순 메서드 호출일 경우 더 간결한 문법으로 대체 가능합니다.

public class MethodReferenceDemo {
    public static void main(String[] args) {
        // 객체::인스턴스메서드
        Consumer<String> printer = System.out::println;
        
        // 클래스::정적메서드
        Comparator<Integer> numericOrder = Integer::compare;
        
        // 클래스::인스턴스메서드
        Function<Employee, String> nameExtractor = Employee::getName;
        
        // 클래스::new (생성자 참조)
        Supplier<Employee> factory = Employee::new;
    }
}

Stream API

컬렉션 데이터 처리를 선언적으로 표현하는 API입니다. 원본 데이터를 변경하지 않고 파이프라인을 구성하여 연산을 수행합니다.

Stream 생성

public class StreamCreation {
    public static void main(String[] args) {
        List<Product> inventory = Arrays.asList(
            new Product("노트북", 1200000),
            new Product("모니터", 350000),
            new Product("키보드", 80000)
        );
        
        // 컬렉션 기반
        Stream<Product> fromCollection = inventory.stream();
        
        // 배열 기반
        Stream<String> fromArray = Arrays.stream(new String[]{"A", "B", "C"});
        
        // 직접 생성
        Stream<Integer> direct = Stream.of(10, 20, 30);
        
        // 무한 스트림
        Stream<Integer> sequence = Stream.iterate(0, n -> n + 2).limit(10);
        Stream<Double> randoms = Stream.generate(Math::random).limit(5);
        
        // 기본형 특화
        IntStream prices = IntStream.rangeClosed(1000, 5000);
    }
}

중간 연산

public class StreamIntermediate {
    public static void main(String[] args) {
        List<Order> orders = initializeData();
        
        // 필터링 + 중복 제거 + 정렬 + 제한
        orders.stream()
            .filter(o -> o.getAmount() > 100000)
            .distinct()
            .sorted(Comparator.comparing(Order::getDate))
            .skip(2)
            .limit(5)
            .forEach(System.out::println);
        
        // 매핑: 객체에서 특정 속성 추출
        List<String> customerNames = orders.stream()
            .map(Order::getCustomerName)
            .collect(Collectors.toList());
        
        // 병렬 처리
        orders.parallelStream()
            .map(o -> heavyCalculation(o))
            .collect(Collectors.toList());
    }
}

최종 연산

public class StreamTerminal {
    public static void main(String[] args) {
        List<Sale> records = loadSalesData();
        
        // 집계
        long totalCount = records.stream().count();
        Optional<Sale> cheapest = records.stream().min(Comparator.comparing(Sale::getPrice));
        Optional<Sale> priciest = records.stream().max(Comparator.comparing(Sale::getPrice));
        
        // 리듀스
        int totalRevenue = records.stream()
            .map(Sale::getPrice)
            .reduce(0, Integer::sum);
        
        // 컬렉팅
        Map<String, List<Sale>> groupedByRegion = records.stream()
            .collect(Collectors.groupingBy(Sale::getRegion));
        
        Set<String> uniqueProducts = records.stream()
            .map(Sale::getProduct)
            .collect(Collectors.toSet());
    }
}

새로운 날짜/시간 API

기존 Date, Calendar의 설계 결함과 스레드 안전성 문제를 해결한 java.time 패키지를 제공합니다. 모든 객체는 불변(immutable)입니다.

핵심 클래스

public class ModernDateTime {
    public static void main(String[] args) {
        // 현재 시점
        LocalDate today = LocalDate.now();
        LocalTime now = LocalTime.now();
        LocalDateTime current = LocalDateTime.now();
        
        // 직접 생성
        LocalDate birthDate = LocalDate.of(1995, 5, 23);
        LocalTime wakeUp = LocalTime.of(7, 30);
        
        // 조작 (기존 객체 불변, 새 객체 반환)
        LocalDate nextWeek = today.plusWeeks(1);
        LocalDate lastMonth = today.minusMonths(1);
        LocalDate withYearChanged = today.withYear(2025);
        
        // 특정 속성 추출
        DayOfWeek dayOfWeek = today.getDayOfWeek();
        int year = today.getYear();
    }
}

포맷팅과 파싱

public class DateFormatting {
    public static void main(String[] args) {
        // 패턴 정의
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일 HH시 mm분");
        
        // 포맷팅
        String formatted = LocalDateTime.now().format(formatter);
        
        // 파싱
        LocalDateTime parsed = LocalDateTime.parse("2024년 12월 25일 14시 30분", formatter);
    }
}

Instant와 ZoneId

public class InstantAndZone {
    public static void main(String[] args) {
        // UTC 기준 시간戳
        Instant timestamp = Instant.now();
        long epochMillis = timestamp.toEpochMilli();
        
        // 시区 정보
        ZoneId seoul = ZoneId.of("Asia/Seoul");
        ZoneId tokyo = ZoneId.of("Asia/Tokyo");
        
        // 타임존 변환
        ZonedDateTime inKorea = timestamp.atZone(seoul);
        ZonedDateTime inJapan = timestamp.atZone(tokyo);
        
        // 레거시 호환
        Date legacyDate = new Date();
        Instant converted = legacyDate.toInstant();
        LocalDateTime modern = LocalDateTime.ofInstant(converted, seoul);
        
        // 역변환
        Instant backToInstant = modern.atZone(seoul).toInstant();
        Date recovered = Date.from(backToInstant);
    }
}

스레드 안전한 포맷터

public class ThreadSafeFormatter {
    private static final DateTimeFormatter PATTERN = 
        DateTimeFormatter.ofPattern("yyyyMMdd");
    
    public static void main(String[] args) throws Exception {
        ExecutorService workers = Executors.newFixedThreadPool(10);
        
        Callable<LocalDate> parser = () -> 
            LocalDate.parse("20241225", PATTERN);
        
        List<Future<LocalDate>> futures = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            futures.add(workers.submit(parser));
        }
        
        for (Future<LocalDate> future : futures) {
            System.out.println(future.get());
        }
        
        workers.shutdown();
    }
}

태그: java lambda Stream API Functional Interface Method Reference

7월 9일 04:51에 게시됨