[Section 1] Java 심화 (애너테이션, 람다, 스트림, 파일입출력, 스레드, 자바가상머신)

dohyoungK·2023년 5월 19일
0

Java 심화

  • 애너테이션(Annotation)

    : 소스 코드가 컴파일되거나 실행될 때 컴파일러 및 다른 프로그램에게 필요한 정보를 전달해주는 문법 요소

    • 애너테이션의 종류

      • 표준 애너테이션: JDK에 내장된 일반적인 애너테이션

        애너테이션설명
        @Override메서드 앞에 붙임으로써 선언한 메서드가 상위 클래스의 메서드를 오버라이딩하거나 추상 메서드를 구현한다고 컴파일러에게 알려주는 역할
        @Deprecated기존에 사용하던 기술이 대체되어 기존 코드를 더이상 사용하지 않도록 유도하는 역할
        @SuppressWarnings("경고 메시지")컴파일 경고 메시지가 나타나지 않도록 하는 역할
        @FunctionalInterface함수형 인터페이스를 선언할 때, 컴파일러가 함수형 인터페이스의 선언이 바르게 되었는지 확인하는 역할
      • 메타 애너테이션: 다른 애너테이션을 정의할 때 사용하는 애너테이션(@interface 키워드를 사용해 애너테이션을 정의하고, @Targer, @Retention을 사용해 애너테이션의 적용 대상과 유지 기간을 지정)

        • @Target
          : 애너테이션을 적용할 대상 지정하는 역할

          대상 타입적용 범위
          ANNOTATION_TYPE애너테이션
          CONSTRUCTOR생성자
          FIELD필드(멤버변수, 열거형 상수)
          LOCAL_VARIABLE지역변수
          METHOD메서드
          PACKAGE패키지
          PARAMETER매개변수
          TYPE타입(클래스, 인터페이스, 열거형)
          TYPE_PARAMETER타입 매개변수
          TYPE_USE타입이 사용되는 모든 대상
        • @Documented
          : javadoc으로 작성한 문서에 포함되도록 하는 역할

        • @Inherited
          : 하위 클래스가 애너테이션을 상속받도록 하는 역할

        • @Rentention
          : 애너테이션의 지속 시간을 결정하는 데 사용

          유지 정책설명
          SOURCE소스파일에 존재, 클래스파일에는 존재 X
          CLASS클래스 파일에 존재, 실행 시 사용 불가
          RUNTIME클래스 파일에 존재, 실행 시 사용 가능
        • @Repeatable
          : 애너테이션을 여러 번 붙일 수 있도록 허용하는 역할

  • 람다식(Lambda Expression)

    : 메서드를 하나의 식으로 표현하는 것으로 함수형 프로그래밍 기법을 지원하는 자바 문법요소

    • 람다식의 기본 문법

      void sayHello() {
         System.out.println("Hello");
      }
      
      // 람다식으로 표현할 때 반환타입과 이름을 생략가능
      () -> System.out.println("Hello");
    • 함수형 인터페이스(Functional Interface)

      : 기존의 인터페이스 문법을 활용해 람다식을 다루기 위해 사용하며, 1개의 추상 메서드를 갖는 인터페이스

      @FunctionalInterface
      interface ExampleFuction {
         int sum(int num1, int num2);
      }
      
      public static void main(String[] args) {
         ExampleFuction exampleFuction = (num1, num2) -> num1 + num2;
      }
    • 메서드 참조

      : 람다식에서 불필요한 매개변수를 제거할 때 사용

      public class Calculator {
         public static int staticMethod(int x, int y) {
            return x + y;
         }
         
         public int instanceMethod(int x, int y) {
            return x + y;
         }
      }
      
      IntBinaryOperator operator;
      
      // 정적메서드
      // 클래스이름::메서드이름
      operator = Calculator::staticMethod;
      operator.applyAsInt(3, 5);
      
      
      // 인스턴스 메서드
      // 인스턴스이름::메서드이름
      Calculator calculator = new Calculator();
      operator = calculator::instanceMethod;
      operator.applyAsInt(3, 5);
  • 스트림(Stream)

    : 배열, 컬렉션의 요소를 하나씩 참조해 람다식으로 처리할 수 있도록 하는 반복자

    • 스트림의 특징

      1. 스트림의 처리 과정 = 생성 -> 중간 연산 -> 최종 연산
      2. 스트림은 원본 데이터를 변경하지 않는다.
      3. 스트림은 일회용이다.
      4. 스트림은 내부 반복자(for문, while문 같은 외부 반복자가 아닌 내부에 람다식을 주입해 요소를 반복처리하는 방식)이다.
    • 스트림의 생성

      • 배열 스트림 생성

        String[] arr = new String[]{"AAA", "BBB", "CCC"};
        
        // Arrays.stream() 사용
        Stream<String> stream = Arrays.stream(arr);
        
        // Stream.of() 사용
        Stream<String> stream = Stream.of(arr);
      • 컬렉션 스트림 생성

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        
        // list.stream() 사용
        Stream<Integer> stream = list.stream();
      • 임의의 수 스트림 생성

        // 임의의 수 무한 생성
        IntStream ints = new Random().ints();
        
        // 스트림을 5개까지 생성
        IntStream ints = new Random().ints(5);
        IntStream ints = new Random().ints().limit(5);
        
        // 1~10까지 특정 범위의 수 생성
        IntStream ints = new Random().rangeClosed(1, 10);
    • 스트림의 중간 연산

      • 필터링(Filtering)

        distinct() : 요소들의 중복 제거
        filter() : 조건에 따라 요소들을 필터링

        List<String> list = Arrays.asList("AAA", "BBB","CCC", "AAA", "EEE");
        
        // distinct() 중복 제거
        list.stream().distinct().forEach(e -> System.out.println(e));
        
        // filter() C로 시작하는 요소만 필터링
        list.stream().filter(e -> e.startsWith("C")).forEach(e -> System.out.println(e));
      • 매핑(Mapping)

        map() : 요소들을 특정 형태로 변환하거나 원하는 필드만 추출할 때 사용
        flatMap() : 중첩 구조를 제거하고 단일 컬렉션으로 만들어 주는 역할

        List<String> list = Arrays.asList("aaa", "bbb","CCC", "ddd", "EEE");
        List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
        String[][] stringArr = new String[][]{{"AA", "BB"}, {"CC", "DD"}};
        
        // 요소들을 하나씩 대문자로 변환
        list.stream().map(e -> e.toUpperCase());
        
        // 각 요소에 3을 곱한 값으로 변환
        nums.stream().map(e -> e * 3);
        
        // flatMap()을 사용해 중첩 구조를 제거
        Arrays.stream(stringArr).flatMap(Arrays::stream).forEach(System.out::println);
      • 정렬(Sorting)

        sorted() : 요소 정렬

        List<String> animals = Arrays.asList("Tiger", "Lion", "Monkey", "Duck", "Horse", "Cow");
        
        // sorted() 내에 아무런 입력이 없으면 기본적으로 오름차순 정렬
        animals.stream().sorted();
        
        // 내림차순 정렬
        animals.stream().sorted(Comparator.reverseOrder());
    • 스트림의 최종 연산

      • 기본 집계(sum(), count(), average(), max(), min())

        int[] arr = {1, 2, 3, 4, 5};
        
        // 요소 개수 카운팅
        long count = Arrays.stream(arr).count();
        // 합계
        long sum = Arrays.stream(arr).sum();
        // 평균
        double average = Arrays.stream(arr).average().getAsDouble();
        // 최대값
        int max = Arrays.stream(arr).max().getAsInt();
        // 최소값
        int min = Arrays.stream(arr).min().getAsInt();
        // 첫 요소
        int first = Arrays.stream(arr).findFirst().getAsInt();
      • 매칭(allMatch(), anyMatch(), noneMatch())

        int[] arr = {1, 2, 3, 4, 5};
        
        // 모든 요소가 짝수라면 true
        Arrays.stream(arr).allMatch(e -> e % 2 == 0);
        
        // 요소중 하나라도 짝수라면 true
        Arrays.stream(arr).anyMatch(e -> e % 2 == 0);
        
        // 모든 요소가 짝수가 아니라면 true
        Arrays.stream(arr).noneMatch(e -> e % 2 == 0);
      • 요소 소모(reduce())

        T reduce(T identity, BinaryOperator accumulator) : 스트림의 요소를 줄여나가면서 연산 수행(identity는 연산 초기값, accumulator는 각 연산의 조건식)

        int[] arr = {1, 2, 3, 4, 5};
        
        int sum = Arrays.stream(arr).map(e -> e * 2).reduce((a, b) -> a + b).getAsInt();
        // 연산 과정
        // (1, 2) -> 누적값 a = 3, 다음 연산값 b = 3
        // (3, 3) -> 누적값 a = 6, 다음 연산값 b = 4
        // (6, 4) -> 누적값 a = 10, 다음 연산값 b = 5
        // (10, 5) -> 최종 결과 15
      • 요소 수집(collect())

        class Student {
           public enum Gender {Male, Female};
           private String name;
           private int score;
           private Gender gender;
           
           public Student(String name, int score, Gender gender) {
              this.name = name;
              this.score = score;
              this.gender = gender;
           }
           
           public String getName() {
              return name;
           }
           
           public int getScore() {
              return score;
           }
           
           public Gender getGender() {
              return gender;
           }
        }
        
        List<Student> totalList = Arrays.asList(
               new Student("AAA", 100, Student.Gender.Male),
               new Student("BBB", 80, Student.Gender.Male),
               new Student("CCC", 90, Student.Gender.Female),
               new Student("DDD", 60, Student.Gender.Female)
        );
        
        // 남자인 요소들을 모아 Map으로 반환
        Map<String, Integer> maleMap = totalList.stream()
               .filter(s -> s.getGender() == Student.Gender.Male)
               .collect(Collectors.toMap(
                       student -> student.getName(), // Key
                       student -> student.getScore() // Value
               ));
  • 스레드(Thread)

    • 프로세스와 스레드

      프로세스 : 실행 중인 애플리케이션
      스레드 : 데이터와 애플리케이션이 확보한 자원을 활용해 소스 코드를 실행하는 실행 흐름

      • 메인 스레드(Main Thread) : 자바 애플리케이션을 실행하면 가장 먼저 실행되는 메서드는 main 메서드이고, 메인 스레드가 main 메서드를 실행
      • 멀티 스레드(Multi Thread) : 한 프로세스는 여러 스레드를 가질 수 있고, 메인 스레드에서 또 다른 스레드를 생성할 때 멀티 스레드 프로세스가 됨.
    • 스레드의 생성과 실행

      1. Runnable 인터페이스 구현한 객체에서 run()을 구현

      2. Thread 클래스를 상속받은 하위 클래스에서 run()을 구현

        class ThreadTask1 implements Runnable {
           public void run() {
              for (int i = 0; i< 100; i++) System.out.print("#");
           }
        }
        
        // Runnable 인터페이스를 구현한 ThreadTask1 객체 생성해 실행
        Thread thread1 = new Thread(new ThreadTask1());
        thread1.start();
        
        -------------------------------------------------
        
        class ThreadTask2 extends Thread {
           public void run() {
              for (int i = 0; i< 100; i++) System.out.print("#");
           }
        }
        
        // Thread 클래스를 상속받은 ThreadTask2 객체 생성해 실행
        Thread thread2 = new ThreadTask2();
        thread2.start();
    • 스레드의 이름

      : 메인 스레드는 "main" 이름을 가지고, 그 외 추가로 생성한 스레드는 0번부터 "Thread-n"의 이름을 가진다.

      Thread thread3 = new Thread(new Runnable() {
         public void run() {
            System.out.println("Get Thread Name");
         }
      });
      
      thread3.start();
      // getName()으로 이름 조회
      System.out.println(thread3.getName()); // Thread-0 출력
      
      // setName()으로 이름 변경
      thread3.setName("AAA");
      
      //Thread.currentThread()를 사용해 현재 실행 중인 스레드의 참조 가능
      Thread.currentThread().getName(); // main 출력
    • 스레드 동기화

      : 멀티 스레드의 경우 여러 스레드가 같은 데이터를 공유하게 되어 문제 발생, 따라서 임계 영역을 사용해 스레드 동기화를 적용한다.

      임계영역(Critical Section) : 오직 하나의 스레드만 코드를 실행할 수 있는 코드 영역
      락(Lock) : 임계 영역을 포함하고 있는 객체에 접근할 수 있는 권한

      class Account {
         // synchronized 키워드를 사용해 메서드 전체를 임계 영역으로 지정
         public synchronized boolean withdraw(int money) {
            if (balance >= money) {
               try {Thread.sleep(1000);} catch (Exception error) {}
               balance -= money;
               return true;
            }
            return false;
         }
         
         // synchronized 키워드를 사용해 특정 영역을 임계 영역으로 지정
         // synchronized () 괄호 안에 객체의 참조를 넣고, 해당 코드를 실행하는 스레드가 this에 해당하는 락을 얻는다.
         public boolean withdraw(int money) {
            synchronized (this) {
               if (balance >= money) {
                  try {Thread.sleep(1000);} catch (Exception error) {}
                  balance -= money;
                  return true;
               }
               return false;
            }
         }
      }
  • JVM

    : 자바 가상 머신(Java Virtual Machine)은 자바로 작성한 소스 코드를 해석해 실행하는 별도 프로그램이다. JVM이 자바 프로그램과 운영체제 사이 통역가 역할을 하면서 자바는 다른 프로그래밍 언어와 달리 운영체제에 독립적이다.

    • JVM 동작 과정

      1. 자바로 소스 코드를 작성하고 실행하면 컴파일러가 실행되며 컴파일이 진행되고, 결과로 .java의 자바 소스 코드가 .class의 바이트 코드 파일로 변환된다.
      2. JVM은 운영 체제로부터 소스 코드 실행에 필요한 메모리를 할당받는다.
      3. 클래스 로더(Class Loader)가 바이트 코드 파일을 JVM 내부로 불러들여 런타임 데이터 영역에 적재시킨다.
      4. 로드가 완료되면 실행 엔진이 런타임 데이터 영역의 바이트 코드 파일을 실행시킨다.
      5. 실행 엔진은 인터프리터를 통해 코드를 한 줄씩 기계어로 번역하고 실행시키거나, JIT Compiler(Just In Time Compiler)를 통해 바이트 코드 전체를 기계어로 번역해 실행시킨다.
    • JVM 메모리 구조

      • Stack 영역

        : 메서드가 호출되면 메서드를 위한 공간인 메서드 프레임이 생성되고 메서드 내부에서 사용하는 참조변수, 매개변수, 지역변수, 리턴값 등이 임시로 저장된다. 그리고 이러한 메서드 프레임이 스택에 쌓이고 동작이 완료되면 역순으로 제거된다.
      • Heap 영역

        : JVM이 작동하면 Heap 영역은 자동 생성되고, 객체나 인스턴스 변수, 배열이 저장된다.
    • Garbage Collection

      : 메모리를 자동으로 관리하는 프로세스이고, 프로그램에서 더 이상 사용하지 않는 객체를 찾아 삭제하거나 제거하여 메모리를 확보한다.

      • 동작 방식

        • Heap 영역은 객체가 얼마나 살아있냐에 따라 Young, Old 영역 2가지로 나뉜다.
        • Young 영역에서는 새롭게 생성된 객체가 할당되고, 이 영역에서 활동하는 가비지 컬렉터를 Minor GC라고 부른다.
        • Old 영역에서는 Young 영역에서 상태를 유지하고 살아남은 객체들이 복사되고 Young 영역보다 가비지는 적게 발생한다. 그리고 이 영역의 가비지 컬렉터를 Major GC라고 부른다.
        • 가비지 컬렉터의 동작방식은 2가지 단계를 따른다.
        1. Stop The World
          : JVM이 애플리케이션의 실행을 멈추는 작업으로, 가비지 컬렉션을 실행하는 스레드를 제외한 모든 스레드의 작업은 중단되고, 정리가 완료되면 재개된다.
        2. Mark and Sweep
          : Mark는 사용되는 메모리와 사용하지 않는 메모리를 식별하는 작업으로, Sweep은 Mark 단계에서 사용되지 않는다고 식별된 메모리를 헤제하는 작업이다.

0개의 댓글