S1U10 [Java] 심화(Effective) 회고

    반응형

    [애너테이션(Annotation)]

    🧬 프로그램에게 코드에 대한 정보를 제공한다. 특정 클래스, 메서드 등 타겟이 정해져 있고 그 외에는 영향을 주지 않는다.
    표준 애너테이션과 메타 애너테이션이 있다. 사용자 정의 애너테이션도 있는데 깊게 학습하지 않았다. @interface 타입으로 생성하여 애너테이션을 생성하는데, 다른 클래스나 인터페이스를 상속받지 못한다. (이미 자동으로 Annotation 인터페이스를 상속받았다)
    표준 애너테이션은 자바에서 기본 제공하는데, 컴파일, 런타임 등에 무언가를 알리는 데에 많이 사용된다. 대표적으로 @Override(컴파일러에게 오버라이딩 한 메서드임을 알림), @SuppressWarnings("all")(컴파일러에게 경고메세지를 나타내지 않아도 됨을 알린다), @Deprecated(사용하지 않는 필드/메서드임을 알림), @FunctionalInterface(함수형 인터페이스임을 알림 -> 코드 작성 실수 방지) 등이 있다.
    메타 애너테이션은 애너테이션을 정의하는 데에 사용한다. @Target(FIELD)(애너테이션 적용 대상 지정), @Retention(RUNTIME)(애너테이션 지속 시간 지정), 이 외에도 @Documented, @Inherited, @Repeatable 등이 있다.

    일반 애너테이션과 메타 애너테이션은 기본적으로 사용방법이 복잡하지 않기 때문에 무리가 없을 것 같다. 하지만 애너테이션을 커스텀하여 사용하는 방법은 모르겠다. 아직은 사용할 일이 있을까 싶지만 나중에 기회가 되면 연습해보고싶다. 그리고 애너테이션을 읽고 그 애너테이션을 알아서 식별해주는 그 실행 방법이 궁금해졌다.


    [람다(Lambda)]

    🧬 람다는 함수형 프로그래밍을 지원하는 문법이다. 메서드를 식으로 표현한 것으로, 람다 사용 시 코드가 간결해진다. 식으로 표현하면서 메서드 이름이 없어지므로 익명 함수의 한 종류라고 할 수 있겠다.
    람다식으로 표현하는 메서드는 독립적으로 존재할 수 없고 어쨋건 클래스 안에 존재해야 한다. 그를 위해 함수형 인터페이스를 사용한다. 인터페이스에 정의된 추상메서드를 오버라이딩 하여 람다식을 다루는 것이다. 함수형 인터페이스를 사용하면 참조변수 타입으로 원하는 메서드에 접근이 가능하다. 빈번하게 사용되는 함수형 인터페이스는 자바에서 제공하고 있다. (Function, Consumer 등)
    람다식에 있는 불필요한 매개변수 등을 제거해서 더욱 더 간단하게 만들어줄 수 있는 것이 메서드 레퍼런스이다. 클래스(참조변수)::메서드 형식으로 작성한다. 메서드 대신 new를 넣으면 생성자 참조도 가능해진다.

    작성하는 데 한참이 걸린 챕터이다. 함수형 인터페이스 관련해서는 보충이 더욱 필요할 것 같다. 추상적으로 이해가 되었는데 말로 설명하자니 꽤나 어렵다. 그리고 어쨌든 함수형 인터페이스를 정의하고 그것을 익명으로 구현해서 사용하는 건데 꼭 @FunctionalInterface라는 어노테이션을 달아주어야 하는 이유가 궁금하다. 함수형 인터페이스임을 알리고 함수형 인터페이스에 적합한 코드인지를 검사하는 거겠지만(함수형 인터페이스 메서드와 람다식은 1:1로 매칭되어야 하기 때문에 말이다.), 기능적으로 차이는 없는지 궁금하다. 무튼 회고를 위해 이것저것 검색하다 1급 객체까지 알게 되었고 스트림을 사용할 때 람다식을 매개로 사용할 수 있는 것도 1급 객체이기 때문인 것을 알았다. (사용 가능하기 때문에 1급 객체인 건가?) 확신이 드는 공부는 아니었다.. 함수형 프로그래밍이라는 건 역시 리액트를 하면서 스크립트 언어로 많이 작성했었는데, java를 함수형으로 사용하려니 긴가민가 헷갈린다. =()->{} 이 문법은 ts 문법과 같아서 아 그거구나 할 수 있었던 것 같다. 메서드 참조는 스트림을 사용할 때 코드를 훨씬 간단하게 만들어 주는데 아직 습관이 안 되었는지 나는 i -> i 그냥 이렇게 작성해주는 게 아직은 편하다. 학습 때는 자바에서 제공하는 인터페이스들을 그냥 스윽 보고 지나갔는데 그 부분은 한 번 다시 주의깊게 읽어봐야겠다.


    [스트림(Stream)]

    🧬 배열과 컬렉션 요소를 반복하며 하나씩 참조해서 람다식으로 처리해준다. 많은 데이터에 꽤 복잡한 연산을 짧은 코드로 수행하게 해준다. 중간 연산을 거쳐 최종 연산에 도달하기까지 파이프라인(여러개의 스트림이 연결된 구조)으로 해결한다.
    중간 연산은 스트림으로 반환되기 때문에 연속수행이 가능하다. 주요 메서드로는 filter(조건에 맞게 필터링), dinstinct(중복 제거), sorted(정렬), map(매핑) 등이 있다.
    최종 연산은 한 번 수행하는 순간 스트림이 닫히며 해당 스트림은 더이상 사용할 수 없게 된다. 주요 최종 연산 메서드는 forEach(반복하며 연산 결과 확인), 기본 집계 함수들(sum, average, max, count 등), collect(다른 종류로 수집해준다) 등이 있다.
    Optional은 NullPointerException을 객체 차원에서 효율적으로 방지해준다. Optioanl 클래스는 모든 타입의 객체를 담을 수 있는 래퍼 클래스이다. null을 예상해서 기본값을 지정해줄 수 있기 때문에 NPE가 방지된다. Optional 객체 또한 메서드 체이닝이 가능하다.

    스트림을 사용하면서 코드가 많이 짧아진 것이 체감되었다. stream 개념과 문법은 원래도 알고 있었지만 혼자서 작성하기는 힘들었는데 코플릿도 풀고 다른 문제 풀 때도 많이 써보면서, 이번 기회에 많이 익숙해진 것 같아 기쁘다. 구글링을 통해서는 stream을 찾아도 처음 개념부터 잡기가 힘들었는데 이번 챕터를 통해 많이 익힐 수 있었다. 아직 Optional에 대해서는 익숙하지 않지만 null을 방지하기 위해 값을 한 번 더 래핑해주는 개념이라는 정도만 이해했다. 그리고 문득.. 처음 보는데 알고싶은 개념은 책을 통해서 공부해야 겠다는 생각이 들었다.


    [파일 입출력(File I/O)]

    🧬 파일을 다루는 스트림이 있다. FileInput(Output)Stream이다. 스트림을 통해선 단방향으로만 데이터 전송이 가능하기 때문에 각각의 스트림이 존재한다. FileInputStream을 통해 파일을 읽어와서, read로 내용을 읽을 수도 있고, 향상된 성능을 위해 보조 스트림(BufferedInputStream)을 사용해 읽을 수도 있다.
    하지만 이들은 바이트 기반의 스트림이다. 자바의 char 타입은 2바이트이기 때문에 문자 기반 스트림을 제공한다. 그것이 FileReader, FileWriter이다.

    주의깊게 공부한 챕터는 아니지만 코드를 통해 파일을 작성하고, 불러와서 읽을 수도 있는 게 재미있었다. 비록 실습할 때 인코딩 때문에 조금 애를 먹긴 했지만 말이다..


    [스레드(Thread)]

    🧬 명령문들의 집합은 프로그램(애플리케이션), 실행 중인 프로그램은 프로세스, 하나의 코드 실행 흐름을 스레드라고 한다.
    프로세스가 메인 스레드 만을 가진다면 단일(싱글) 스레드 프로그램, 여러 스레드를 가진다면 멀티 스레드 프로그램이다. 스레드가 여럿 있다면 이 여러 스레드가 병렬로 작업을 수행할 수 있다.
    그런데 이 여러 스레드들이 동일한 데이터를 공유하여 사용하다보면 문제가 일어날 가능성이 높다. 이를 해결하기 위해 스레드 동기화를 해주어야 한다. 임계영역을 지정해, 스레드가 락을 획득/반납하며 임계영역에 접근할 수 있도록 설정할 수 있다.
    스레드는 (생성 NEW, 실행 대기 RUNNABLE, 일시정지 WATING, TIMED_WATING, BLOCKED, 소멸 TERMINATED) 상태를 가진다.
    start(실행 대기 상태로 전환), sleep(ms만큼 스레드 일시정지(TIMED_WATING)) yield(다른 스레드에게 실행 양보) join(작업을 일시정지 시켜준다. sleep은 static 메서드이지만 join은 인스턴스 메서드.) wait와 notify(스레드 간 협업. 일시정지와 실행대기를 번갈아가며 진행) interrupt(sleep, wait, join에 의해 일시정지된 스레드 실행 대기 상태로 복귀) 등의 메서드를 사용하여 실행을 제어한다.

    정처기 공부할 때 열심히 공부했던 프로세스, 스레드.. 특히 무작정 공부했던 운영체제의 프로세스 스케줄링이 이 챕터와 연관되어있다는 사실을 알게 된 순간 눈이 번쩍 뜨였다. 개념은 잘 알겠고, 실습 코드로도 스레드를 생성해보고 또 상태를 제어해봤지만 커다란 프로젝트에 이 스레딩을 적용한다면 어떻게 해야할까 곰곰히 생각하게 되었다. 스프링 프레임워크까지 더해진다해도 같은 방법으로 진행할 수 있을까? 그리고 임계영역을 잘 설정할 수 있을까? 솔직히 자신은 없다.


    [자바 가상 머신(Java Virtual Machine)]

    🧬 JVM은 자바 프로그램을 실행시킨다. JVM을 통해 자바 프로그램이 운영체에 독립적으로 실행될 수 있다.
    프로그램이 실행되면 JVM이 CPU로부터 메모리를 할당받는 시점이 있다. 그 메모리인 Runtime Data Area는 크게 다섯 종류로 구분되어있지만 Stack 영역과 Heap 영역에 대해 설명하자면, stack은 메서드가 실행되는 동안 여러 변수들, 연산 결과 등을 저장하는 공간이다. heap에는 new로 생성된 객체들이 주로 저장되는데, heap에는 인스턴스 그 자체가 저장되고, 이 저장된 주소를 가리키는 값이 stack 영역에 선언된 변수에 저장되어 있다.
    Java의 주된 특징 중 하나는 Garbage Collection이 발생한다는 것이다. 메모리를 자동으로 관리해주는 것이다. 더이상 참조되지 않는 변수 등 상태를 유지하지 못하고 버려진 객체들을 알아서 식별하여 제거해준다. GC 실행은 JVM이 GC 스레드 외 모든 스레드를 중단 시키고(Stop the world), 사용/미사용 메모리를 식별하여(Mark) 미사용 메모리를 정리하는 과정(Sweep)을 거친 뒤, 스레드를 재개하는 과정으로 동작한다.

    부트캠프를 시작하기 전에는 JVM과 그 메모리 영역의 구조가 괴애애애앵장히 추상적이고 상상이 안 되었다면, 여러번 반복적으로 학습을 하면서 조금 추상적인 정도로 바뀌었다.ㅎㅎ 학습을 하면서 아주 만족스러웠던 부분 중 하나가 JVM의 메모리 구조에 대해서 조금 더 많은 이해를 할 수 있게 된 것이었다. GC도 항상 자바의 특성으로 익히 들어왔던 이야기지만 자세히 알지 못했는데 실행구역이나 방식까지 공부를 하고 나니 또 새로웠다. 적합한 그림과 설명으로 함께 공부하니 나름 이해하기가 수월했던 흥미로운 챕터이다.


    🛁Unit 10. [Java] Effective를 마치고!

    TIL을 작성하면서 좌절했던 때가 생각난다. 이해가 잘 되지도 않는데 마음이 조급해서 화도 나고 우울하기도 했었는데, 그래도 직접 실습해가면서, 만들어가면서 공부를 하니 그 때보단 마음이 시원하다. 여전히 의문인 구석은 아주 많이 남아있지만, 그래도 이번 섹션의 마지막 유닛인만큼 그 난이도가 있으리라 생각하고 그저 공부하려고 한다.
    그리고 직접 해보는 것의 중요성을 많이 느꼈다. 이게 무슨 인터페이스를 구현하고 있다고? 뭘 상속받았다고? 이런 것도 하나하나 까보면서 직접 눈으로 확인하고, 그래서 이걸 하지 않으면 오류가 나는구나, 그래서 이렇게 하는구나를 몸소 느끼면서 공부하니까 더 기억에 잘 남게 되는 것 같다.
    물론 스레드 같은 부분은 아직 응용해보지 못했지만ㅎㅎ 조금 더 스스로 연습해보는 데에 시간을 많이 사용해봐야겠다. 그런데 문제는.. 회고 작성하는 데 너무너무 오래걸린다는 것ㅋㅋ 엄청 금방 작성한 글도 있는데, 특히나 이번 유닛은 모르는 점을 내 말로 설명하기 위해 이해하고 시작하려다보니 서치하고 익히는 데만 많은 시간을 쓰게 된 것 같다. 그래도 얻는 게 있었다면 잘 된 거겠지.. 너무너무 어려웠고 힘들었고 또 재미있었던 심화 회고 끝.

    반응형

    댓글