Ajax 라는 개념은 웹에 데이터를 갱신 시, 브라우저 새로고침 없이 서버로부터 데이터를 받는 게 좋다는 생각에서 출발하였다.


 Ajax 는 Asynchronous Javascript And XML 의 약어로 데이터를 이동하고 화면을 구성하는데 있어서 네트워크 Delay 에 따른 데이터의 구성과 View 의 표현을 비동기방식으로 처리하는 메커니즘이다.

(비동기에 대한 내용은 다음을 참고해보면 좋을듯하다.

http://jins-dev.tistory.com/entry/%EB%8F%99%EA%B8%B0Synchronous-%EC%9E%91%EC%97%85%EA%B3%BC-%EB%B9%84%EB%8F%99%EA%B8%B0Asynchronous-%EC%9E%91%EC%97%85-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%EB%B8%94%EB%9D%BDBlocking-%EA%B3%BC-%EB%84%8C%EB%B8%94%EB%9D%BDNonBlocking-%EC%9D%98-%EA%B0%9C%EB%85%90?category=760150)

쉽게 말하자면 웹화면을 데이터를 받은 이후에 추가로 갱신하지 않고 서버와 통신하는 방식이다.




Ajax 메커니즘이 적용되지 않은 초기, 인터넷 사용자가 많지 않던 시절의 웹 사이트 구성 로직은 다음과 같다. (Old School)


(1) 웹사이트를 구성하는 Image 를 가져온다.


(2) 웹사이트의 중요 Data 들을 서버에서 가져온다.


(3) 웹사이트 표현을 위한 Javascript 를 가져온다.


(4) 리소스(Image, Data)가 모두 전송되면 데이터를 HTML 뷰에 뿌려준다.


가장 단순하면서도 명확한 기존의 방식은, 네트워크 딜레이를 사용자가 직접 맞부딪친다는 맹점을 갖고 있다. 

또한, 초창기의 SC 모델은 UI 의 표현 역시 서버에서 처리했기 때문에 모든 UI 의 갱신이 요청을 수반하며, 네트워크의 낭비라는 결과도 초래한다.

이 네트워크의 낭비는 사용자의 요구에 응답하는 상호작용에 있어서 큰 장애물이 될 수 있다.

웹이 서비스 상품으로 진화하면서, UI(User Interaction) 와 UX(User Experience) 가 중요해지면서, 서비스는 AJAX 메커니즘을 도입하게 된다.


Ajax 메커니즘을 적용시킨 형태의 구성로직이다. (New School)


(1) 웹사이트를 구성하는 Image 를 요청한다. (XMLHttpRequest)


(2) 웹사이트의 중요 Data 들을 서버에 요청한다. (XMLHttpRequest)


(3) Image와 Data를 제외한 HTML 뷰를 전부 그려준다.


(4) Data 와 Image 를 받는대로 필요한 만큼만 클라이언트에서 사용하고 저장한다. (HttpResponse + Javascript)


AJAX 메커니즘을 도입함으로써, 서버쪽에서 처리하던 UI를 위한 데이터 처리를 클라이언트에서 담당하게 되면서 네트워크 비용의 절감 및 비동기 통신의 이점으로 Delay 없는 화면 전환도 가능하게 된다.


다만, 초창기의 브라우저들은 Ajax 를 지원하지 않았고, 클라이언트 중심의 리소스 호출이 이루어지다보니 보안상의 몇몇 이슈가 있다는 단점이 있다.

개인적으로 생각하는 단점으로는... 무엇보다 디버깅이 쉽지가 않다.


사용자 입장에서는 굉장히 데이터를 빠르게 가져오는 것처럼 느껴지지만 동적으로 화면을 구성하기 때문에 구현은 복잡해진다. 

서버로 데이터를 요청하고 응답을 가져오는 동안 웹은 화면의 구성 등 다른 업무를 처리할 수 있으며 응답이 도착하면 콜백으로 이후의 작업을 하기 때문에 화면 전체의 갱신이 불필요하다.


Ajax 를 사용하는데 있어 주로 사용되는 데이터 포맷은 CSV, JSON, XML 형식이 있다.(XML 만 사용하는 것이 아니다.) 

GET, POST, DELETE, UPDATE 등 일반적인 HTTP 요청을 이용해 CRUD 를 처리하며 REST API 와 궁합이 잘맞는다. 


Ajax 는 방식 이지 프레임워크나 라이브러리가 아니기 때문에 구현은 여러가지가 있을 수 있다. 

Xmlhttp 통신을 이용한 Vanila Javascript 로 구현도 가능하지만, 일반적으로 angular 나 jquery 등의 framework에서 구현된 형태의 Ajax 를 이용한다.


Ajax 를 이용한 내부 동작은 위에 언급한 정해진 데이터 포맷 형태로 요청을 하고 나서 들어오는 응답에 대한 처리를 event loop 에서 받아 처리하는 방식이다. 





Java8 에서는 기존의 Java 에 비해 다양한 기능들이 접목되었다. 

Optional 과 Lambda, Stream API 와 같은 기능들이 그것인데, 이는 새로이 대세로 떠오르고 있는 함수형 프로그래밍의 메타를 적용한 Java 의 새로운 진화라 할 수 있겠다.


Java8 에 대한 전반적인 소개는 다음 링크를 참조하자.

(http://jins-dev.tistory.com/entry/Java8-%EC%97%90%EC%84%9C-%EC%83%88%EB%A1%9C-%EC%83%9D%EA%B2%A8%EB%82%9C-API-%EB%93%A4?category=760006)


본 포스팅에서 집중해볼 것은 실무에서도 이제 널리 사용되고 있는 Stream API 에 대한 내용과 간단한 사용방법들이다.


 대부분의 이전 Java는 데이터를 처리하기 위해 컬렉션을 사용하고 이는 전통적인 반복문과 조건문 사용에 기인한다. 

컬렉션을 사용하여 할 수 있는 기능은 무한하고 강력하지만, 정작 데이터를 처리하는 패턴은 SQL에서 데이터를 찾는것과 비슷하게 데이터를 찾거나, 묶는것 정도에 국한된다.


 Java8의 Stream API 는 컬렉션을 이용했던 기존의 코드를 좀 더 깔끔하게 구현하고 병렬처리에 있어서도 이점을 가질 수 있도록 하기 위하여 제공된다. 

Stream API의 기본이 되는 원형 stream() 메서드는 모든 컬렉션 타입에 대해 제공되며, 컬렉션 내의 Element 들에 대해 하나씩 구분한 Stream 결과를 나타낸다. 


먼저 Stream API 가 어떻게 구성되는지 알아보자.

Stream API 는 생성연산, 중간연산, 최종연산으로 Flow 가 구성되어 있다. 생성연산은 스트림의 생성을, 중간 연산은 스트림을 통한 데이터 가공 및 변환을 담당하고 최종 연산은 Stream 의 사용을 담당한다. 다음은 연산별로 Stream API 를 정리한 내용이다.



순서대로 스트림 생성연산, 중간연산, 최종연산을 분류하였다.

Stream API 를 사용하기 위해서는 반드시 위의 Pipeline 을 순서대로 따라야 하며, 여러개의 중간연산을 붙이는 것도 가능하다. 다만, 생성연산과 중간연산들은 Stream 을 반환해야만 추가 연산이 가능하다는 점을 잊지 말자.

이를 이용해서 다음 리스트에서 원하는 정보를 추출해보자.


Stream API 를 이용하면 위의 리스트에서 70 이상의 숫자들을 정렬해서 다음과 같이 추출해낼 수 있다.



결과도 다음과 같이 확인해볼 수 있다.



코드 역시 상당히 직관적으로 이해할 수 있다. 코드상에서 우리는 numList 의 Stream 을 생성하여, 70 이상인 요소들만 필터링하여 정렬 후 Array 형태로 반환하고 있다.


이처럼 Stream API 를 사용하면 만들어진 스트림을 별도의 저장이나 가공없이 filter(속성에 맞게 필터링), sorted(정렬), map(정보를 추출), collect(정보를 가공) 할 수 있으며 이 메서드들은 collect에 의해 연속되어 처리된 결과를 리턴하거나 형변환, 또는 Stream 연산의 결과를 취합하여 반환한다. 


다음은 몇가지 자주 사용되는 연산에 대한 설명이다.

(1) map
함수 형태 : Stream<R> map(Function<? super T, ? extends R> mapper)
설명 :  T 타입 객체를 입력받아 R 타입을 반환하는 스트림 생성. 가장 많이 쓰이는 함수 중 하나이며, Stream 을 다른 형태의 Stream 으로 매핑시키는 역할을 한다.

(2) filter
함수 형태 : Stream<T> filter(Predicate<? super T> predicate)
설명 : Predicate(T 를 입력으로 받아 Boolean 을 반환하는 조건 식) 람다식이 true를 반환하는 새로운 스트림 생성

(3) flatMap
함수 형태 : Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
설명 : T 타입을 입력으로 받아, R 타입의 객체를 1:N 으로 매핑하는 스트림을 생성. 말그대로 평평하게(Flat) 만드는 함수이다.

(4) peek
함수 형태 : Stream<T> peek(Consumer<? super T> action)
설명 : T 타입의 매개변수를 입력받아 void 를 반환하는 함수를 실행하고 Stream 을 그대로 반환.

(5) limit
함수 형태 : Stream<T> limit(long maxSize)
설명 : maxSize까지의 Element 들만 반환하는 스트림 생성

(6) sorted
함수 형태 : sorted(), sorted(Comparator<T> comparator)
설명 : 정렬된 스트림을 생성. Integer 라면 자동정렬하지만, T 자료형에 대해 직접 Comparator 구현 필요하다. 
입력으로 주어지는 전체 스트림의 요소를 한꺼번에 정렬하기 때문에, 연속적으로 적용이 불가능하다.

(7) distinct
함수 형태 : distinct()
설명 : 값은 값을 갖는 요소를 중복해서 발생하지 않는 스트림 생성

8번부터는 Terminal Operation 들이다.

(8) forEach
함수 형태 : void forEach(Consumer<? super T> consumer)
설명 : T 타입을 입력으로 받아 void 를 수행하고 void 를 반환한다. for문과 동일하지만, 동작은 동일하지 않다. 
(forEach 는 Stream API 의 일부라는 점을 명심하자. 일반 for 구문과 다르게 PipeLine 을 충실히 따른다.)

(9) count
함수 형태 : count()
설명 : Stream 의 요소의 갯수를 반환한다.

(10) anyMatch
함수 형태 : boolean anyMatch(Predicate<? super T> predicate)
설명 : T를 입력받아 boolean 을 반환하는 predicate 람다식을 만족하는 항목이 Stream 에 하나라도 존재하는지를 반환한다.

(11) noneMatch
함수 형태 : boolean noneMatch(Predicate<? super T> predicate)
설명 : anyMatch 와 동일하지만 람다식을 만족하는 항목이 없는지를 반환한다.

(12) collect
함수 형태 : <R,A> R collect(Collector<? super T,A,R> collector)
설명 : Stream 을 Collector 형태로 치환한다. List 나 Map 등의 Collection 으로 변환하는 함수가 들어간다.


Stream API 를 사용할 때 꼭 알아두어야 할 점은 Stream은 요소들을 보관하지 않으며 요소들은 하부의 컬렉션에 보관되거나 필요할 때 생성된다는 점이다. 또한 원본을 변경하지 않는 대신 결과를 담은 새로운 스트림을 반환한다. 

스트림 연산은 가능하면 지연(Lazy) 처리 되기 때문에 결과가 필요하기 전에는(최종 연산 이전) 실행되지 않는다. 

따라서 최종연산 이전의 연산의 순서는 보장할 수 없다. (가령, Stream 연산에 2개 이상의 sorted 가 있을 경우 어떤 sorted 가 먼저 수행될지는 불분명하다.)


이 말을 다시한번 정리해보자. 스트림은 최대한 연산을 지연하며, 그에 따라 최종연산에 오기 전까지는 연산이 실제로 수행되지 않는다는 것이다.


이를 통해 잘 알아두어야 할 점은, 각 연산별 Pipeline 은 
절차적이지 않다는 점이다. 가령, stream 에 peek 을 통해 연산을 수행한 뒤 collect 로 취합하더라도 루프를 중첩해서 돌지 않는다. 
Stream 은 최종 연산 수행시 내부 파이프라인을 통해 최적화된 연산을 수행한다.

이처럼 Stream 은 각 요소 단위로 연산이 수행되는데 지연(Lazy) 처리 특성에 따라 최종연산에서 한꺼번에 처리가 되므로 최종연산이 선언되지 않은 체인 스트림에서 동작을 수행할 경우 이는 반영되지 않는다. 즉, 다음과 같은 코드는 아무 동작도 하지 않는다.



위의 연산은 peek 이라는 중개 연산을 마지막으로 구문을 마치고 있기 때문에 결과적으로 Stream 연산이 수행되지 않는다. 따라서 출력도 되지 않는다.


이러한 Stream의 특성을 Lazy & ShortCircuit 이라 하며 이는 Stream 이 Collection 과 다르게 그 자체만으로 자료구조가 아닌 연산을 위한 자료구조인 특성을 반영한다 


Stream 을 이용하면 가독성이 뛰어나고, 잘 사용하면 성능적으로도 뛰어난 결과를 가져올 수 있으며 쉽게 병렬 처리 환경으로 이식도 가능하다.

Stream 의 응용은 무궁무진하며 사용하기 위해서는 익숙해지는 것이 중요하다. 많이 사용해보고 적절하게 응용해보도록 하자.





 에버그린 브라우저(Evergreen Browser), 자동적으로 브라우저가 사용자에 대한 별도의 재설치를 요구하지 않고도 업데이트 가 가능한 브라우저를 말한다


 초기 웹브라우저들은 Evergreen 방식을 사용하지 않으나, Web 기술의 발전에 따라 사용자의 편의성과 업데이트를 위해 설계된 방식이다.

Chrome은 대표적인 에버그린 브라우저이며, Firefox나 근래의 IE와 같은 많은 주요 브라우저들이 Evergreen 방식의 업데이트로 브라우저를 제공하고 있다.


 많은 개발자들이 인지하기 힘든 빠른 업데이트 정책에 대해 개발하는 데 있어서 호환의 어려움을 얘기하지만, 많은 브라우저들이 하위 호환성에 대한 고려는 충분히 하고 있기 때문에 우려할 수준은 아닌 것으로 보인다.

 

 웹 개발자들이 개발을 염두해둘 때, Javascript 신기술을 도입하는 데 있어서 체크해야할 사항이 되기도 한다. 

특히 ES6 와 같은 최신 Javascript 가 내장된 브라우저를 찾는 일을 하게 되는데, 주요 에버그린브라우저에서 사용할 목적이라면 브라우저가 알아서 Javascript 를 지원하는 업데이트를 해주기 때문에 이는 큰 걱정거리가 안된다.




 가비지컬렉터 의 동작을 이해하는 건 Java 개발자의 가장 필수적인 요건이다. 나름 Java 를 할 줄 안다고 자만하고 있었는데, 이 부분을 공부하면서 깊이 반성하였다.


 C/C++ 개발자의 경우 객체를 관리하는데 있어서 메모리는 순전히 개발자의 몫이다. 필요한 시점에 생성하고 불필요한 시점에 반환해야 하며 올바른 메모리 주소를 참조할 수 있도록 관리해주어야 한다. 

반면 Java 의 경우 개발자는 이 부분에 있어서 신경을 덜 써도 되며 이는 Java 언어가 제공하는 Garbage Collector 가 이 일을 해주기 때문이다. Garbage Collector 는 항상 background 에서 데몬 쓰레드로 돌아가면서 접근 불가능한(Unreachable) 상태가 된 객체들의 메모리를 정리해준다.



 먼저 Garbage Collectors (이하 GC) 의 동작을 이해하기 위해서는 JVM 의 메모리 관리에 대해 알아야 한다. JVM에는 일반적으로 Young Generation / Old Generation 이라는 두가지의 물리적 공간이 존재한다.


 GC는 2가지 전제를 갖고 있다. 대부분의 객체가 금방 Unreachable 한 상태가 된다는 것과 Old 객체에서 Young 객체로의 참조가 적다는 점이다. 다음은 각 물리 공간에 GC 가 어떻게 동작하는지를 설명한다.


- Young Generation 영역 : 새롭게 생성한 객체가 위치한다. 많은 객체가 이 영역에 생성되었다 사라지며 이를 Minor GC라고 한다.


- Old Generation 영역 : 접근불가능한 상태가 되지않아 Young 영역에서 살아남은 객체가 이 영역으로 복사된다. Young 영역보다 크게 할당되며 GC는 적게 발생한다. 이 영역에서 객체가 사라질 때 Major GC 또는 Full GC 가 발생한다. 

Old 영역에서는 Card table이라는 512 byte의 Chunk 가 존재하며 Old영역의 객체 중 Young 영역의 객체를 참조하는 객체의 정보들을 저장한다.


 Young 영역은 Eden 영역과 2개의 Survivor 영역으로나뉜다. New 를 이용해서 객체를 생성하면 이는 Eden 영역에 위치하게 된다. Eden 영역에서 GC가 한번 발생 후 살아남은 객체는 Survivor 영역 중 하나로 이동한다. 이 때 객체가 Eden 영역에서 Survivor1, Survivor2 영역으로 이동할 때 Minor GC 가 수행된다.


지속적인 Eden 영역에서의 GC 이후 Survivor 영역으로 객체가 계속 쌓이고 Survivor 영역 내 빈곳으로 살아남은 객체들이 이동한다. 이러한 과정을 계속 반복 후 Survivor 영역들이 가득차게 되면, 남은 객체가 Old 영역으로 이동한다.


 2개의 Survivor 영역에 모두 데이터가 존재하거나 모두 사용량이 0이면 시스템은 비정상이다. Old 영역은 기본적으로 데이터가 가득차면 GC를 수행하며 GC 방식은 JDK 7 기준으로 5가지가 있다.


(1) Serial GC : Mark-sweep-compact 알고리즘이 사용된다. Old 영역에 살아있는 객체를 Mark 하고 Heap의 앞부분부터 살아있는 객체를 Sweep한 뒤, 힙의 앞부분부터 객체를 쌓는다.(Compact) 메모리와 CPU 코어수가 적을 때 좋다.


(2) Parallel GC : Serial GC와 기본적인 알고리즘은 같지만, GC를 처리하는 Thread의 개수가 여러 개다. 메모리와 CPU 코어 수가 많을수록 좋다.


(3) Parallel Old GC : Parallel GC와 같지만 Old 영역의 GC 알고리즘만 다르다. Mark-Summary-Compact 알고리즘이며 조금 더 복잡하다.


(4) CMS GC : Stop-the-world 이후 Initial marking 시 살아있는 객체만 찾는다. 이후 concurrent mark 단계에서 참조를 따라가며 새로 추가되거나 참조가 끊긴 객체들을 remark 한다. 모든 작업이 멀티스레드 환경에서 동시진행되기 때문에 stop-the-world 시간이 매우 짧은 대신 memory와 CPU 를 많이 사용하고 compaction 단계가 제공되지 않는다.


(5) GI GC : Young 영역과 Old 영역이 없이 매우 빠르게 객체를 할당하고 GC한다.



 GC가 실행되기 전 JVM은 Application의 실행을 멈춘다. 이를 Stop-the-World 라 하며 GC 쓰레드 이외의 쓰레드들의 작업이 멈춘다. 이 때 이 Stop the world 를 줄이는 작업을 GC 튜닝이라 한다.


GC 튜닝이 모든 Java Application 에 필수적인 것은 아니지만, 크리티컬한 요청을 담당하는 서버나 코어 엔진의 경우 GC 튜닝이 필요하다. GC 튜닝을 위해 지켜야할 기본적인 원칙은 GC 튜닝이 로직에 영향을 미치지 않도록 가능한 늦게 수행하고, 객체 생성을 최소화하는 것이다.


 즉, GC 튜닝은 Java 코드 최적화와 맞물려 있는 영역이라고도 할 수 있다. 가령 String 의 append 시, + 연산으로 2개 이상의 String 을 더하는 대신, StringBuilder 등을 쓰는 것도 일종의 메모리 튜닝이라고 할 수 있다. 그 외에는 설정적인 부분으로 JVM 옵션으로 메모리 크기를 조절하고 GC 방식을 옵션으로 지정해주는 등이 있다.


(1) 메모리 관련 GC 튜닝을 위한 JVM 옵션

 -Xms : JVM 시작 시 힙 영역의 크기

 -Xmx : 최대 힙 영역 크기

 -XX:NewRatio : New 영역과 Old 영역의 비율. (New 영역의 비율을 전달한다.)

 -XX:NewSize : New 영역의 크기

 -XX:SurvivorRatio : Eden 영역과 Survivor 영역의 비율 (Survivor 영역의 비율을 전달한다.)


(2) GC 방식 관련 GC 튜닝을 위한 JVM 옵션

 - Serial GC :

-XX:+UseSerialGC 


 - Parallel GC : 

-XX:+UseParallelGC

-XX:ParallelGCThreads=value


 - Parallel Compacting GC : 

-XX:+UseParallelOldGC


 - CMS GC :   

-XX:+UseConcMarkSweepGC 

-XX:+UseParNewGC

-XX:+CMSParallelRemarkEnabled

-XX:CMSInitiatingOccupancyFraction=value 

-XX:+UseCMSInitiatingOccupancyOnly

 - G1 : 

-XX:+UnlockExperimentalVMOptions

-XX:+UseG1GC


위와 같은 옵션을 적용한다고 해도 GC 의 튜닝이 비약적인 시스템의 성능 향상을 가져오게 되리라고 보장할 수는 없다. 결국 GC 튜닝은 계속적인 모니터링을 통해서 이루어지는 것으로, 복잡한 시스템의 메모리 구조를 공식처럼 맞춰서 설정하기 보다는 지속적인 모니터링과 성능 향상을 위한 노력이 필요하다.




기본이되는 내용이지만 짚고 가야할 내용이라 정리해두었다.



 일반적으로 Primitive Type 대해서 final 선언되면 해당 객체를 바꿀 없지만, Object 경우 final 선언되면 해당 객체 혹은 해당 객체 멤버의 내용은 바꿀 있다.


 Object 앞에 선언되는 final 의미는 해당 객체를 가리키는 포인터를 바꿀 없게 하는 의미로, 해당 이름으로 객체의 선언 / 재할당을 막는 다는 의미가 된다.


(ex)

Public foo(Obj_1 Obj1, final Obj_2 obj2){

             Obj1 = new Obj_1();      //가능

             Obj2 = new Obj_2();      //불가능

             Obj1.setId(12345);         //가능

             Obj2.setId(12345);         //가능

}


 시스템이 굉장히 복잡하다고 가정했을 , 특히 멀티 쓰레드 환경에서 Thread Safe 하지 않은 객체에 대해 자바 가상 머신의 예기치 못한 동작으로 특정 메모리 영역의 객체는 얼마든지 다른 객체로 변할 여지가 있다.


이는 개발자 입장에서는 버그이지만 JVM입장에서는 버그가 아닌 동작일 있다. 그런 경우를 방지하기 위해 메모리를 참조하는 주소를 final 키워드를 이용해서 고정시켜놓으면 그러한 불상사를 예방할 있다.


 Java8에서는 Lambda 식과 같은 함수형 프로그래밍을 지원하기 위한 API를 포함해서 이를 위한 특징적인 API 들이 많이 생겨났다.

그 중에서도 이 포스팅에서는 핵심적인 3가지, Stream, Optional, Lambda 에 대해 정리하고자 한다.


 먼저 컬렉션을 다루기위한 API 중에는 Stream API 가 존재한다.

Java 에서 컬렉션은 데이터를 관리하고 제어하기 위한 좋은 컨테이너이지만 구현의 인터페이스 적인 측면에 있어서는 깔끔하다고 보기는 힘들다.


특히 이것은 SQL 문과 비교해보면 이해가 쉽다. 학생 정보를 담고 있는 데이터 컬렉션에서 특정 성적 이상을 가진 학생들을 찾을 때 Collection을 이용하면 다음과 같이 구현할 수 있다.


List<Student> students = getStudentList();
List<Student> List = new ArrayList();
For(Student s : students)
{
    If(s.getGrade() >= 80)
        List.add(s);
}


반면, Sql 문을 사용한다면 이는 조건절이 포함된 SELECT 구문 하나로 처리가 가능하다.

Java8의 Stream API는 이러한 복잡한 구조의 데이터 처리를 간단하게 해주어 복잡도를 낮추는 설계가 가능하게 한다. 또한 이런 종류의 Lazy collection 은 멀티 스레드 및 병렬처리 환경에서도 개발자에게 상당히 유연한 선택지를 갖게 한다. 또한 한번만 소비 가능한 형태의 컬렉션이기 때문에 메모리 관리 차원에서의 이점도 있다.



List<Student> list = students.stream().filter(t->t.getGrade()>=80).collect(Collectors.toList());


Stream API 는 위와 같이 Collection 의 각 요소들을 따로따로 주어진 함수구문(위에서는 람다식)에 따라 처리하며 내부 버퍼를 통해 주어진 형태로 반환해준다.


Stream API 에는 위에 적용된 filter 나 collector 와 같은 메서드를 포함해서, map, flatMap, reduce, peek 등 다양한 메서드를 제공한다.

Stream API 의 자세한 사용에 있어서는 추가로 포스팅하고자 한다.


 새로 추가된 Optional API 는 Java8 에서 제공하는 Null이 포함되지 않는 Collection이다.

NullPointerException 을 걱정하지 않아도 되며, ifpresent(함수식) 기능을 통해 무결성 검증과 동시에 함수식을 수행할 수도 있다. 

가령 NULL 이 될 수 있는 객체에 대해 다음과 같이 핸들링 할 수 있다. 



public Student getStudentById(String studentId) {
// Stream API 의 findFirst() 는 그 자체로 Optional 을 return 하기 때문에 사실 이는 좀 어색한 함수 구현이다.
return getStudentList().stream()
.filter(student->student.getStudentId().equals(studentId)).findFirst().get();
}

public void foo() {

Student student = getStudentById("철수");

if(Optional.ofNullable(student).isPresent()) {
System.out.println("철수라는 학생이 존재합니다.");
} else {
System.out.println("철수라는 학생은 없습니다.");
}
}


위와 같이 Optional 을 사용하면 Null 을 핸들링할 수 있는 객체를 다룰 수 있다. if(student == null) 과 같은 형태 대신 좀 더 직관적으로 다양한 이용이 가능하다.


마지막으로 Java8의 람다식은 함수형 인터페이스를 제공함으로써 지원이 된다.

 함수형 인터페이스를 만드는 방법은 @FunctionalInterface 어노테이션을 이용해 명시적으로 생성도 가능하지만, method가 하나 존재하는 인터페이스를 선언함으로써 생성이 가능하다.

다음의 예시를 참조할 수 있다.



Public interface Foo
{
    Int calc(int a, int b);
}
{
    Foo add = (int a, int b) -> { return a+b; };
    Foo minus = (int a, int b) -> { return a-b; };
    Int addv = add.calc(3, 5);      //8
    Int minusv = minus.calc(5, 3);  //2
}


위의 예시는 직접 FunctionalInterface 를 구현하여 Lambda 식을 적용해본 것이고, 실제 많이 사용되는 것은 Java8 에서 제공하는 API 들이다. 다음은 그 몇가지 종류이다.


Function<T, R> : T 타입의 입력파라미터를 받아 R 타입을 리턴한다.


Supplier<T> : void 타입의 파라미터를 입력받아 T 타입을 리턴한다.


Consumer<T> : T 타입의 파라미터를 받아 void 를 리턴한다.


Predicate<T> : T 타입의 입력을 받아 boolean 을 리턴한다.


BiPredicate<T, U> : T와 U 타입을 입력받아 boolean 을 리턴한다.


UnaryOperator<T, T> : T 타입 2개를 입력받아 T 타입을 리턴한다.


이 외에도 많은 종류의 FunctionalInterface 를 제공하며, 잘 사용하면 함수형 프로그래밍의 장점을 누릴 수 있다.





 Java에서는 Static 변수의 Non static 초기화를 위해 Static Initializer를 지원한다.

이는 static final 변수를 주로 예외를 throw하는 함수의 return 값으로 초기화하는데 사용된다.


다음과 같이 사용하면 된다.


(ex)



public class Example {

private static final Lib val;

static{
Lib tmp = null;
try{
tmp = new Lib();
}catch(UnknownHostException e){
//생성자가 이 예외를 반환하므로 일반적인 방법으로는 선언과 동시에 초기화가 불가능하다.
}
}

}



 이는 Lazy Initialization 의 예시로, 이러한 테크닉을 이용하면 Lazy Instantiation 과 유사하게 디자인 패턴 내의 코드를 작업하는 데 있어서 유연성을 가져올 수 있다.


 위의 예제에서 val 이라는 멤버변수는 final 인데다가 static 변수이면서 유효한 Lib 형의 객체이다. 안전하면서도 효율적이다.

위의 코드는 심지어 Thread-safe 하기 때문에 멀티 스레드 환경에서도 문제 없다.

 또한 위의 주석에 설명이 달려있듯, 생성자 자체가 예외를 발생시키는 경우 위와 같이 바깥에서 Try Catch 로 감싸줌으로써 안전한 초기화가 가능하다.


 다만, 이런 형태에서 위의 변수 val, 즉, Static Initialized Variable 은 클래스로더에 종속되기 때문에, 매번 객체 생성 시, 서로 다른 클래스 로더에서 Static 코드 블록을 호출하게 된다. 


 만약 하고자하는 바가 싱글턴이거나, 외부 코드에 종속적이라면 추천되지 않는 패턴이다. (물론 그렇게 쓰이는 경우는 흔치 않다.)





 리눅스에서 Java를 백그라운드 프로세스로 실행하려 할 때 다음과 같이 뒤에 &를 붙여주면 된다. 

(사실 리눅스에서는 자바 뿐 아니라 프로세스 실행 시 &만 붙여주면 백그라운드로 실행된다.)


 $ java -jar {run.java} &


- 단, 위와 같이 백그라운드 프로세스는 터미널이 필요하기 때문에(터미널에 종속되는 것이다.) 터미널 종료시, 로그아웃 시 프로세스도 같이 종료된다.


 이를 지원하기 위한 명령어로 nohup 명령어가 있다.

 이를 이용하면 리눅스에서 SIGHUP을 받지 않는 프로세스를 만들 수 있다.

 터미널이 종료될 때 프로세스가 죽는 이유는 해당 프로세스가 쉘의 자식 프로세스로 지정되어 있기 때문이다.

 부모 프로세스가 죽을 때 던지는 SIGHUP 을 자식 프로세스가 받게 되어 죽게 되고, 이를 무시할 수 있는 nohup을 이용하여 죽지 않게 만들 수 있다.


$ nohup java 클래스명&


 이는 사용이 편리하나 중지, 재시작 등 서비스가 일반적으로 갖고 있는 제어 루틴을 갖지 못하기 때문에 외부 명령어로 Kill 등을 해야 한다. 물론 Kill을 할 때 프로세스의 동작 제어는 많은 예외처리를 요구하게 된다.


 Java의 JVM은 UNIX 계열의 시그널 처리를 할 수 없기 때문에 따로 처리를 해줘야 하는데 이러한 번거로움을 Apache Commons Daemon을 이용하면 쉽게 해결할 수 있다.

 Jsvc는 중지 및 제어 신호에 대해 지정된 메서드를 실행할 수 있게 해주는 인터페이스를 갖고 있다. 


 Jsvc를 이용하려면 Daemon 인터페이스를 구현해야 한다. 또 쓰레드로 실행되지 않을 시 종료처리가 원만하게 되지 않기 때문에 143 오류가 발생하므로 Runnable 형태로 구현해야 한다.


 인터페이스를 구현하면 Jsvc를 이용하여 실행이 가능하지만 경로 입력이 힘들고 커맨드로 Start / Stop / Restart 로직의 제어가 힘들기 때문에 일반적으로 쉘스크립트를 작성한다.


 스크립트에는 Jsvc 커맨드를 주어 실행시키고 .jar 파일을 연결하여 Parameter로 전달해주도록 한다. 자세한 스크립트는 다음 링크를 참조한다. (win100.tistory.com/120)


 명령어 설정을 이용하면 로그의 Redirection이 가능하다. 터미널이 없으므로 주로 파일로 redirect 한다.





RAII는 C++ 진영에서 자주 쓰이는 Idiom으로 자원의 안전한 사용을 위해서 객체가 쓰이는 Scope를 벗어나면 자원을 해제해주는 기법이다. 메모리 누수를 관리하는 효과적인 기법이며, 제대로만 사용하면 오히려 별도의 관리 모듈이 붙는 것보다 안정적이다.


이는 C++ 에서 자원을 얻을 때 초기화가 되어야 하며, 유효한 객체 형태가 반환되어야하는 것을 의미한다. 반대로 객체가 사라질 때에는 가진 자원을 전부 반환해야 하며 객체가 유효하지 않은 형태가 되어야 한다.


 그리고 이것은 C++ 의 메모리 누수를 관리하는 철학이며, 이를 Scope 단위로 관리하고자 한다.


 그렇기 때문에 C/C++ 과 같은 통칭 .NET 등에서 Unmanaged 라 불리는 언어들을 다룰 때에는 메모리를 사용하는 각 변수들의 유효한 Scope 를 정확히 파악하는 것이 아주 중요하다.


 heap의 자원은 명시적 해제 이전에 해제되지 않지만 Stack 변수의 경우 Scope를 벗어나면 Destructor가 호출되는 것을 이용한 것이다.


 Exception 등 Control Flow Breaking 시에도 RAII를 이용하여 자원관리를 할 수 있으며 C++의 제작단계에서 스트로스트룹이 finally를 굳이 고안하지 않은 이유이기도 하다.



물론 이러한 특성들이 많은 개발자들의 C++의 진입 장벽을 높이는데에 포인터와 같이 큰 몫을 차지하고 있기 때문에 C++11 등 모던 C++에서는 여러 가지 장치들을 통해 메모리를 관리해주려는 노력을 하고 있다.


'Programming Language > C&C++' 카테고리의 다른 글

Stdafx.h 란?  (0) 2018.09.25
[Old] C++Type Casting 정리  (0) 2018.09.25

 Java에서 모든 클래스의 부모 클래스인 Object 클래스는 toString을 비롯해 equals hashCode라는 메서드를 갖고 있다.

즉, 모든 클래스는 toString 과, equals, hashCode 를 갖고 있다고 볼 수 있으며 이는 자바 객체들의 특징이라고 할 수 있다.

여기에서 헷갈리는 부분이 equals와 hashCode 의 차이이며, 아래는 그 내용을 정리한 글이다.



 Equals는 두 객체의 내용이 같은지 동등성(equality)을 비교하며 hashCode는 두 객체가 같은 객체인지 동일성(identity)을 비교하는 역할을 한다.


 즉 hashCode를 사용하면 두 객체의 참조 비교를 하며 완전히 같은지를 판단한다.

 참고로 Java Map put 당시의 hashCode를 기록하므로 hashCode함수를 오버라이딩하여 구현할 시 중간에 값의 변경으로 hashCode가 변경되지 않도록 유의해야 한다.


 다음은 hashCode() 와 관련해서 정의된 규약이다.


-      equals()로 비교시 두개의 오브젝트가 같다면 hashCode() 값도 같아야 한다.


-      Equals()로 비교시 false라면 hashCode() 값은 같을수도 다를수도 있다. 성능을 위해서는 hashCode() 값이 다른 것이 낫다.


-      hashCode() 값이 같다고 해서 equals() true인 것은 아니다. 해싱 알고리즘 문제로 같은 해시값이 나올 수 있다.

 

: 다음은 equals() 와 관련된 규약이다.


-      Reflexive : Object는 그 자신과 항상 equals해야 한다.


-      Symmetric : a.equals(b) 일때 b.equals(a) 는 성립해야 한다.


-      Transitive : a.equals(b) 이고 b.equals(c) 라면 c.equals(a) 는 성립한다.


-      Consistent : equals의 호출은 객체가 변하지 않는 이상 항상 같은 결과를 반환해야 한다.


-      Null comparison : null과의 equals 비교는 NPE가 아닌 false를 반환해야 한다.



위 내용은 다음 블로그를 많이 참조 하였다. 

(http://anster.tistory.com/160, http://iilii.egloos.com/4000476)



+ Recent posts