inblog logo
|
LifeLog, DevLog
    Java

    Java 기초 정리

    미비한 점만 정리했다!
    KYJTHEYJ's avatar
    KYJTHEYJ
    Nov 04, 2025
    Java 기초 정리
    Contents
    Optional 객체람다스트림Thread

    Optional 객체

    • 값이 있을 수도 있고 없을 수도 있는 컨테이너

    • 메서드 반환 자료형에 선언해서 해당 메서드가 null을 반환될 가능성을 전달

    • ofNullable()을 사용하여 null을 반환될 수 있는 객체를 감싸서 사용

    • isPresent() 같은 API를 통해 확인하여 처리

    public class Camp {
    
        private Student student;
    
        // null을 반환할 수 있는 메서드
        public Student getStudent() {
            return student;
        }
    
        // nullable check 가능하게 만드는 Optional
        public Optional<Student> getStudentOptional() {
            return Optional.ofNullable(student);
        }
    
        public void setStudent(Student student) {
            this.student = student;
        }
    }
    
    public class Student {
    
        private String name;
        
        public Student(String name) {
            this.name = name;
        }
    
        public String getName() {
            return this.name;
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Camp camp = new Camp();
    
            // null check 방법 1
            Optional<Student> student = camp.getStudentOptional();
    
    		// isPresent() -> 값이 존재할 경우 true, 없으면 false
            if(student.isPresent()) {
                String studentName = student.get().getName();
                System.out.println("studentName = " + studentName);
            } else {
                System.out.println("student is null");
            }
    
            // null check 방법 2 -> 생성자를 이용해서 이름에 배정해버리는 방식
            // orElseGet()은 값이 없을 때만 기본값을 제공하는 로직을 실행하는 메서드
            Student student2 = camp.getStudentOptional().orElseGet(() -> new Student("미등록 학생"));
            System.out.println("studentName = " + student2.getName());
        }
    }
    

    람다

    • 익명 클래스를 더 간결하게 표현하는 문법

    • 함수형 인터페이스를 통해 구현하는 것을 권장

      • 1개의 추상 메서드만 가져야하기 때문

      • 허나, 하나의 추상 메서드를 가진 일반 일반 인터페이스를 통해서도 가능함

      public class Main {
          public static void main(String[] args) {
              // 익명 클래스
              Calculator calculator = new Calculator() {
                  @Override
                  public int sum(int a, int b) {
                      return a + b;
                  }
              };
      
              int result = calculator.sum(1, 2);
              System.out.println("result = " + result);
      
              // 람다식
              Calculator calculator2 = (int a, int b) -> a + b;
              int result2 = calculator2.sum(1, 2);
              System.out.println("result2 = " + result2);
      
              // 람다식을 매개변수로 전달 -> 여하튼 Calculator로 컴파일러가 추론하기 때문에 가능
              int result3 = calculate(1, 2, calculator2);
              System.out.println("result3 = " + result3);
      
              // 람다식을 직접 전달
              int result4 = calculate(1, 2, (int a, int b) -> a + b);
              System.out.println("result4 = " + result4);
          }
      
          public static int calculate(int a, int b, Calculator calculator) {
              return calculator.sum(a, b);
          }
      }
      
      @FunctionalInterface
      public interface Calculator {
          int sum(int a, int b); // 하나의 추상 메서드만 선언해야함
          int sum(int a, int b, int c); // Error 한개만 가져야함
      }

    스트림

    • 데이터를 효율적으로 처리할 수 있는 흐름

    • 선언형 스타일 → 가독성 좋음

    • 스트림은 컬렉션과 자주 활용

    • 데이터 준비 → 중간연산 → 최종연산

    • Stream 은 불변 변환용도
      내부 요소를 변환해서 다시 적용해야지 같은 건 Stream 의 목적이 아님을 유의

    단계

    설명

    주요 API

    1. 데이터 준비

    컬렉션을 스트림으로 변환

    stream(), parallelStream()

    2. 중간 연산 등록
    (즉시 실행되지 않음)

    데이터 변환 및 필터링

    map(), filter(), sorted()

    3. 최종 연산

    최종 처리 및 데이터 변환

    collect(), forEach(), count()

    List<Integer> result6 = arrList.stream() // 1. 데이터 준비: 스트림 생성
            .filter(num -> num % 2 == 0)    // 2. 중간 연산: 짝수만 필터링
            .map(num -> num * 10)           // 3. 중간 연산: 10배로 변환
            .collect(Collectors.toList()); // 4. 최종 연산: 리스트로 변환
    System.out.println(result6);

    • 중간연산 정리

      연산자

      역할

      간단한 예시

      filter()

      조건에 맞는 요소만 남김

      .filter(x -> x > 10)

      map()

      요소를 변환

      .map(x -> x * 2)

      flatMap()

      중첩된 Stream 평탄화

      .flatMap(List::stream)

      distinct()

      중복 제거

      .distinct()

      sorted()

      정렬

      .sorted() 또는 .sorted(Comparator)

      limit()

      앞에서 n개만 남김

      .limit(5)

      skip()

      앞에서 n개 건너뜀

      .skip(2)

      peek()

      중간 디버깅용

      .peek(System.out::println)

      카테고리

      주요 연산자

      기능 요약

      필터링

      filter(), distinct()

      조건 맞는 요소만 남기거나 중복 제거

      변환/매핑

      map(), flatMap()

      형태를 바꾸거나 내부 컬렉션을 펼침

      정렬/슬라이싱

      sorted(), limit(), skip()

      순서 조정, 일부만 추출

      디버깅용

      peek()

      중간 과정 출력

      • filter() → 조건

        List<Integer> result = Stream.of(5, 10, 15, 20)
            .filter(x -> x >= 10)
            .collect(Collectors.toList());
        System.out.println(result); // [10, 15, 20]
        
      • map() → 각 요소 변환

        List<String> result = Stream.of("apple", "banana")
            .map(s -> s.toUpperCase())
            .collect(Collectors.toList());
        System.out.println(result); // [APPLE, BANANA]
        
      • flatMap() → 중첩 Stream 평탄화

        List<List<String>> data = List.of(
            List.of("A", "B"),
            List.of("C", "D")
        );
        List<String> result = data.stream()
            .flatMap(List::stream)
            .collect(Collectors.toList());
        System.out.println(result); // [A, B, C, D]
        
      • distinct() → 중복 제거

        List<Integer> result = Stream.of(1, 2, 2, 3, 3)
            .distinct()
            .collect(Collectors.toList());
        System.out.println(result); // [1, 2, 3]
        
      • sorted() → 정렬

        List<Integer> result = Stream.of(3, 1, 2)
            .sorted()
            .collect(Collectors.toList());
        System.out.println(result); // [1, 2, 3]
        
        // 또는 Comparator
        .sorted((a, b) -> b - a) // 내림차순
        
      • limit(), skip() → 일부만 잘라내기

        List<Integer> result = Stream.of(1, 2, 3, 4, 5)
            .skip(2)   // 앞의 2개 건너뛰기
            .limit(2)  // 그 다음 2개만
            .collect(Collectors.toList());
        System.out.println(result); // [3, 4]
        
      • peek() → 중간 확인용

        List<Integer> result = Stream.of(1, 2, 3)
            .peek(x -> System.out.println("before map: " + x))
            .map(x -> x * 10)
            .peek(x -> System.out.println("after map: " + x))
            .collect(Collectors.toList());
        

    • 최종연산 정리

      • collect() → 스트림의 요소들을 모아서하나의 결과(자료구조)로 만드는 역할

      • toList()

        List<String> names = Stream.of("홍길동", "김철수", "이영희")
            .filter(n -> n.length() == 3)
            .collect(Collectors.toList());
        
        

        결과: ["홍길동", "김철수", "이영희"]

      • toSet()

        Set<Integer> nums = Stream.of(1, 2, 2, 3)
            .collect(Collectors.toSet());
        
        

        결과: [1, 2, 3] (중복 제거됨)

      • joining(”str”);

        String result = Stream.of("a", "b", "c")
            .collect(Collectors.joining(", "));
        
        

        결과: "a, b, c"

      • toMap(key → key, value → value)

        Map<String, Integer> map = Stream.of("apple", "banana", "cherry")
            .collect(Collectors.toMap(
                s -> s,           // key
                s -> s.length()   // value
            ));
        
        

        결과: {apple=5, banana=6, cherry=6}

      • summingInt(number → number), averagingInt… 등등

        int sum = Stream.of(1, 2, 3, 4, 5)
            .collect(Collectors.summingInt(i -> i));
        
        

        결과: 15

      • 정리

        구분

        설명

        예시

        toList()

        List로 수집

        .collect(Collectors.toList())

        toSet()

        Set으로 수집

        .collect(Collectors.toSet())

        toMap()

        Map으로 수집

        .collect(Collectors.toMap(...))

        joining()

        문자열로 합치기

        .collect(Collectors.joining(", "))

        summingInt()

        합계 계산

        .collect(Collectors.summingInt(...))

        groupingBy()

        그룹화 (SQL의 GROUP BY처럼)

        .collect(Collectors.groupingBy(...))

      • forEach() → 스트림의 요소를 하나씩 실행하거나 출력할 때

      Stream.of("A", "B", "C")
          .forEach(item -> System.out.println(item));
      
      //----------
      
      A
      B
      C
      
      //----------
      
      // 자주 쓰는 패턴
      list.stream()
          .filter(x -> x.startsWith("A"))
          .forEach(System.out::println);
      
      
      • count() → 스트림의 요소 개수를 셀 때

      long cnt = Stream.of("A", "BB", "CCC")
          .filter(s -> s.length() >= 2)
          .count();
      
      System.out.println(cnt); // 2
      
      

    Thread

    • 프로그램 내 독립적으로 실행되는 하나의 작업 단위

    • 싱글/멀티로 구현 가능

    • Runnable

      • Thread의 구현을 담당하는 인터페이스

        • 왜 사용?

          • Thread는 쓰레드를 제어하기 위해 존재함

            • 내부 실행 로직도 구현 가능하나 내부 실행 로직마저도 확장받아 구현할 경우 재사용성과 타 클래스의 메서드를 사용할 수 있는 확장의 가능성이 없어짐

    public class MyRunnable implements Runnable extends anotherClass { // 확장 가능
    
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + "- started");
    
            for (int i = 0; i < 10; i++) {
                System.out.println(threadName + "-" + i);
    
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
    
            System.out.println(threadName + "- finished");
        }
    }
    
    public class MyThread extends Thread { // -> Thread를 상속받아야 하므로 확장 가능성 없어짐
    
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + "Thread Start");
    
            for (int i = 0; i < 10; i++) {
                System.out.println(threadName + "Thread Started" + i);
    
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
    
            System.out.println(threadName + "Thread End");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            System.out.println("main Thread Start");
            MyThread myThread = new MyThread();
            MyThread myThread2 = new MyThread();
    
            long startTime = System.currentTimeMillis();
    
            System.out.println("main Thread - myThread Start");
            myThread.start();
    
            System.out.println("main Thread - myThread2 Start");
            myThread2.start();
    
            // join을 통해 두 쓰레드가 끝나기 전까지 타 쓰레드를 기다리게 만듬
            try {
                myThread.join();
                myThread2.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
    
            long endTime = System.currentTimeMillis();
            System.out.println("process time: " + (endTime - startTime)); // join이 없으면 바로 종료될 수 있음
            System.out.println("main Thread End");
            
    
            //---------------------------------------------------
    
            // Runnable 사용
            System.out.println("main Thread Start");
            MyRunnable myRunnable = new MyRunnable();
            myRunnable.executedAnotherMethod(); // Runnable 클래스에서 확장받아온 메서드
            Thread myThread3 = new Thread(myRunnable);
            Thread myThread4 = new Thread(myRunnable);
    
            long startTime2 = System.currentTimeMillis();
    
            System.out.println("main Thread - myThread3 Start");
            myThread3.start();
    
            System.out.println("main Thread - myThread4 Start");
            myThread4.start();
    
            // join을 통해 두 쓰레드가 끝나기 전까지 타 쓰레드를 기다리게 만듬
            try {
                myThread3.join();
                myThread4.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
    
            long endTime2 = System.currentTimeMillis();
            System.out.println("process time: " + (endTime2 - startTime2)); // join이 없으면 바로 종료될 수 있음
            System.out.println("main Thread End");
        }
    }
    
    Share article
    Contents
    Optional 객체람다스트림Thread

    LifeLog, DevLog - https://github.com/KYJTHEYJ

    RSS·Powered by Inblog