2016년 1월 22일 금요일

1월 3주차 주간 소식들..

트위터엔 외국 분들이 팔로우 해주신 분들이 계셔서 관심있는 소식들은 그때 그때 링크를 트윗으로 공유하고 있는데.. 번역은 요즘 시간상 많이 부담되서 간단한 요약 정도라도 올려보자는 취지로 작성하는 글입니다. 한번에 모아서 작성하기 보다는 수시로 업데이트 하는 형태가 될 것같습니다..

조금 늦긴 했지만 첫주 포스팅을 시작하며..

1월 3주차 소식들..


미분류 카테고리

머신러닝 플렛폼 TensorFlow 소스를 공개했던 구글이 사용 방법에 대한 무료 강좌를 개설 했다는 소식.. 머신러닝 분야의 중급에서 고급 수준의 코스라고 함.. 링크

애플의 VR관련 소식인데.. 버지니아 테크의 Doug Bowman 교수를 영입 했다는 소식, 마이너리티 리포트와 같은 3D 사용자 인터페이스 생성과 관련한 경험이 풍부한 분인듯 한데 애플의 기존 하드웨어 구성이나 행보를 봐선 게이밍과 같이 하드웨어 성능을 크게 요구하는 시장보다는 일반적인 멀티미디어나 일상 생활과 연관된 제품을 연구하고 있는게 아닌가 싶음.. 링크

소프트웨어

구글의 V8 자바스크립트 엔진에서 동작하는 Node.js 프로젝트에 MS에서 얼마전 소스를 공개한 엣지 브라우저의 ChakraCore 엔진에서도 Node.js가 동작하게 해달라는 요청을 했다고.. 이는 삼성과 마찬가지로 IoT 환경에서 자바스크립트 앱을 사용할 목적으로 보인다는 소식.. 링크

모질라 재단의 언어인 Rust의 1.6 Stable 버전이 발표됨 링크

구글의 새로운 압축 알고리즘인 Brotli가 크롬 49 버전에서 기존의 정적 컨텐츠 압축용도의 Zopfli를 대체할 것이라고.. 파이어폭스 44도 탑제 예정이고 MS도 엣지 브라우저에 도입 고려중이라는 소식.. 링크

Windows 10 Fast Ring 참가자 대상으로 Build 11102가 배포 되었다는 소식. RedStone 빌드 넘어오면서 AMD 그래픽 카드의 Radeon Settings 쪽이 정상 동작하지 않는 기능들이 있는데 알려진 문제의 그래픽 스택쪽 버그랑 연관이 있는건가 싶기도.. 링크

2015년 12월 30일 수요일

AMD의 VSR + Fluid Motion 을 이용한 4K 60FPS 영상 감상

먼저 VSR과 Fluid Motion에 대해 간단히 소개 하자면 VSR은 Virtual Super Resolution의 약어로 모니터에서 정식으로 지원하지 않는 더 높은 해상도로 렌더링 해서 보여주는 기술입니다. 얻을 수 있는 장점은 안티앨리어싱(Anti-Aliasing, 이하 AA) 기능을 사용할 때와 비슷한 보정 효과를 얻을 수 있으며 개인적으로는 해상도를 높이는 쪽이 퍼포먼스 면에서 유리한 편이라 VSR기능을 AA보다 선호하는 편입니다. VSR 기능을 지원하는 비디오 카드는 다음과 같습니다.
  • AMD Radeon™ R9 Fury X
  • AMD Radeon™ R9 300 시리즈
  • AMD Radeon™ R7 300 시리즈
  • AMD Radeon™ R7 260 이상
  • AMD Radeon™ HD 7900 시리즈
  • AMD Radeon™ HD 7800 시리즈
  • AMD Radeon™ HD 7790과 AMD A 시리즈 APU들 (A6 7400K 이상)
모니터의 해상도에 따라 지원되는 VSR 해상도 역시 다르며 사용 가능한 VSR 해상도 목록은 다음 링크에서 확인 가능합니다. 일반적으로 4K라고 부를 수 있는 해상도인 3840 X 2160이나 3840 X 2400 해상도가 지원되는 경우는 모니터가 1920 X 1080 @60Hz이거나 1920 X 1200 @60Hz 이면서 GPU는 통가코어(R9 285, R9 380, 380X)나 퓨리 계열인 경우만 지원 된다고 되어 있습니다. 물론 꼭 4K가 아니더라도 VSR을 이용 가능하다면 업스케일 된 해상도를 이용하는 장점을 얻을 수 있습니다.

Fluid Motion 기능의 경우 Catalyst Omega(14.12) 이후 지원하기 시작한 기능으로 GCN 기반 카드의 경우 지원하는 것으로 보이며 일반적인 영상은 대부분 초당 24프레임으로 보여지지만 GPU를 이용해서 중간에 필요한 화면이 추가적으로 생성 된 60프레임으로 보여지도록 하는 기술입니다.

기능을 이용하기 위해 개인적으로 사용중인 소프트웨어 리스트는 다음과 같습니다.
세팅에 대해 살펴보면 먼저 K-Lite 코덱팩은 Mega 버전을 풀 패키지 설정으로 설치(설치 중간에 협찬 소프트를 같이 설치할 것인지 물어오는데 설치하지 않음으로 선택하면 코덱팩만 설치가능 합니다.) 하고 Default 옵션으로 세팅 하였습니다. 그렇기 때문에 비디오 플레이어는 Media Player Classic- Home Cinema(이하 MPC-HC) 버전에 렌더러는 Enhanced Video Renderer(이하 EVR) 코덱은 LAV 비디오 코덱을 이용하게 됩니다. Bluesky Frame Rate Converter는 MPC 플레이어나 다음의 팟플레이어에서 Fluid Motion 기능을 사용할 수 있도록 해주는 필터입니다.

2015년 8월 10일 월요일

Java의 stack size에 대해..

참고 : http://stackoverflow.com/questions/20030120/java-default-stack-sizehttp://stackoverflow.com/questions/10481528/in-java-which-objects-are-put-on-the-stack-and-which-on-the-heaphttp://stackoverflow.com/questions/6366211/what-are-the-roots

JVM의 VM 옵션의 -XX옵션 중에 ThreadStackSize라는 스택의 크기를 지정할 수 있는 옵션이 있다(JRockit의 경우 -Xss). JVM은 Java 메소드 호출 정보와 Native 메소드 호출 정보를 저장할 장소가 필요한데 JVM에 따라 둘을 한 스택 프레임에서 관리하는 경우도 있고 분리해서 관리하는 경우도 있다.  따라서 여기서 지칭하는 스택은 자바에서 각각의 쓰레드(thread)별로 최소 1개 이상 할당되는 저장소를 가리킨다.

스택의 기본 크기는 플렛폼에 따라 다르며 신규 쓰레드가 생성되면 정해진 스택사이즈의 메모리 공간이 힙(heap)과는 별도의 공간을 이용하여 각각의 쓰레드에 할당되게 되는 것이다.

-XX:ThreadStackSize=512 Thread Stack Size (in Kbytes). (0 means use default stack size) [Sparc: 512; Solaris x86: 320 (was 256 prior in 5.0 and earlier); Sparc 64 bit: 1024; Linux amd64: 1024 (was 0 in 5.0 and earlier); all others 0.]

쓰레드에 할당된 스택에는 지역변수, 파라메터, 반환주소 등을 기억하며 일반적으로 객체는 저장되지 않으며 참조 주소만 기억하게 된다. 이와 같은 특성을 가지고 있기 때문에 스택에 저장되는 원시타입(primitive type) 데이터가 많다면 array와 같은 컬렉션 객체로 관리하게 되면 스택에 저장되는 데이터의 사이즈를 줄일 수 있을 것이다.

이와 관련하여 예외가 존재하긴 하는데 그럴 경우 장점은 스택은 캐쉬될 가능성이 높기 때문에 지역변수에 대해 캐시의 지역성을 높여 접근시간을 줄이고 힙에서 관리할 데이터를 줄여 가비지 컬렉터(gargabe collector)를 덜 바쁘게 하는 효과와 싱글 쓰레드 액세스가 보장되기 때문에 쓰레드 잠김(locking)과 관련한 체크가 필요없어지는 장점을 얻을 수 있다고 한다. 그밖에 참고할 만한 자료로 상단 링크의 Escape Analysis 항목이 도움이 될 것이다.(일단 글의 주제는 스택 사이즈이다보니..-_-;;)

스택의 크기 설정과 관련해서 발생할 수 있는 문제가 몇가지 있는데 만약 스택의 크기를 크게 잡았을 때 쓰레드가 기하급수적으로 늘어난다면 OutOfMemory 오류를 접할 수 있을 것이고 반대로 너무 작게 잡은 상태라면 쓰레드에서 기억해야 하는 데이터의 크기보다 스택의 크기가 작아 StackOverFlow 오류를 보게될 수 있다.

마지막으로 사용된 스택 프레임은 해당 쓰레드가 종료되면 자동으로 제거 되게 될 것이다..

2015년 6월 20일 토요일

최근 IT 업계의 흐름을 지켜보며..

원래 예전 같으면 ATiMania.com에 올렸을 법한 글이지만.. 사이트가 사라진 관계로 블로그에..

한참 사이트 운영에 심취해 있을때는 하루에 보통 10개 이상의 기사를 번역하곤 했었는데 정보의 생산자 입장에서 소비자 입장으로 전환하고 나서 부터 피드를 통해 하루에 소비하는 기사가 평균 2-300개 정도 되는 것 같다. 보통 헤드라인만 보고 지나가고 관심이 가는 내용만 글을 스키밍 해서 읽는 편인데 여튼 최근 기억나는 몇가지를 적어보려고 한다.

사실 요즘 과열된 IT 시장을 보면 과거 IT 버블때와 비슷한 느낌도 들기는 하지만 그때와는 조금 다르다는 생각이 드는게 버블 붕괴 당시는 IT 동력이 바닥나서 거품이 한번에 빠져나가는 양상이었는데 요즘은 새로운 기술과 트렌드가 끊임없이 나오고 있다는게 가장 큰 차이인 것 같다.

최근 인텔의 알테라 인수와 관련해서 구글, 페이스북, 트위터와 같은 거대 인터넷 회사를 중심으로 커스텀칩 수요가 증가하고 있다는 것을 알게 되었는데 비트코인 마이닝을 해본 경험이 있다면 범용인 GPU의 OpenCL 연산을 이용하는 것 보다 마이닝 연산에 특화된 FPGA 칩이 퍼포먼스와 전력소모 면에서 월등히 앞선다는 것을 알고 있을 것이다. 이와 마찬가지로 검색이나 추천서비스와 같은 특정 서비스에 특화된 연산을 수행할 수 있는 칩을 이용하여 데이터센터를 구축할려는 움직임이 있는 것 같다.

사실 AMD의 경우 최근 소니의 PS4와 마소의 XBOX ONE의 커스텀칩 제공을 통해 이시장에 먼저 발을 담그긴 했는데 어떤식으로 대응해 나갈지는 아직 미지수인 상태이지만 FPGA 진영의 다른 업체인 자일링스와 협력을 하게될 가능성도 있어보인다. 예전에 아수스에 인수될 예정이었던 기가바이트가 불현듯 다시 독립을 선언한 적이 있었는데 구글의 데이터센터에 제품을 납품하던 시절이랑 얼추 비슷했던걸로 봐서 어쩌면 커스텀칩 시장이 AMD가 다시 살아날 수 있는 기회가 될 수 있을지도 모르겠다는 생각이 든다.

AMD의 경우 거듭되는 CPU 시장에서의 실패로인해 재정적으로 고통받고 있고 그나마 올해 이후의 전망이 조금 나아질 것으로 보인다. 2005년 즈음이었나 과거 X1K 제품을 소개했을때 CTM(Close to Metal)이라는 개념을 선보였는데 간략히 설명하자면 과거 ATi의 필 로저스 아저씨가 소개한 것과 같이 GPU를 좀더 프로그래밍 가능하게 만들어 CPU를 사용하듯 GPU도 좀더 다양한 상황에서 써보자는 개념이었는데 이제서야 소프트웨어 적인 지원이 충분히 갖춰진 상황이라고 해야할까.

기본적으로 GPU는 CPU와 다르게 매우 많은수의 병렬 파이프라인 구조를 갖고 멀티쓰레드 연산에 특화되어있다. 그리고 3D 그래픽 연산의 특성상 부동소수점 연산을 주로 수행하며 CPU와는 다른 분야에서 강점을 보이며 둘은 상호 보완적인 관계이다. 최근 하드웨어 구성의 추세는 하나 이상의 GPU를 갖는 시스템이 증가하고 있다는 점인데 기존에는 멀티 GPU의 활용이 동일 벤더의 동등한 퍼포먼스의 칩을 사용하지 않으면 연산된 결과물을 합치는 동기화 작업이 불가능 했는데 이런 문제가 최근 발표된 애플의 메탈, 크로노스의 불칸 그리고 마소의 DX12에서 비동기 렌더링 기술을 통해 해결된 상태이기 때문에 외장 GPU 사용 시 잉여스럽던 내장 그래픽의 활용도가 증가할 것으로 보인다.

올해 하반기 출시 예정인 애플의 OSX 엘 카피탄 그리고 마소의 윈도우10이 그동안 하드웨어의 발전을 따라오지 못하던 소프트웨어 시장에 어떤 변화를 이끌어낼 수 있을지가 기대된다.

2015년 5월 21일 목요일

IFTTT로 Slack 채널에 날씨정보 알리기

이 글은 일종의 잡 스케줄러 혹은 이벤트 트리거를 웹과 모바일에서 손쉽게 구현할 수 있는 IFTTT(IF This Then That)를 이용하여 팀챗 툴인 Slack의 특정 채팅 채널에 날씨 정보를 리포팅하는 방법을 다루고 있습니다.

기본적인 IFTTT의 활용법을 다루고 있지만 이를 응용하여 IFTTT와 연동되는 여러 서비스를 이용한 자신만의 서비스를 손쉽게 만들 수 있습니다.

"This"를 이용하여 "That"에 어떤 동작을 할것인지는 Recipe라는 항목에서 생성할 수 있는데 This와 That에 해당하는 항목이 바로 Channel이며 이를 이용하기 위해서는 먼저 자신이 원하는 채널을 설정을 해야합니다.

다음은 Weather 채널을 설정하는 절차입니다.


로그인 후 Channels 항목을 확인합니다.


Liftstyle 항목에 Weather channel이 존재하는 것을 확인 할 수 있습니다.(이미 연동된 상태에서는 이미지 하단에 CONNECTED라고 표시 됩니다)

채널을 클릭하면 Connect 버튼이 보이며 이를 클릭하면 다음과 같은 화면을 볼 수 있습니다.


원하는 지역의 이름을 이용하여 검색 후 Connect 버튼을 누릅니다.

이와 마찬가지로 Slack의 채널을 설정합니다.

자신이 이용중인 Slack 계정의 정보를 이용하여 IFTTT와 연동 시킵니다.

자 이제 Recipe를 생성할 차례입니다.


이미지에서는 이미 해당 레시피가 생성된 상태일때의 모습이며 Create a Recipe를 클릭합니다.


이제 this와 that 항목에 앞서 설정한 채널을 지정하는 일이 남았습니다. this를 클릭하여 이벤트 발생 조건을 선택합니다.


제공되는 이벤트 발생 조건은 다음과 같으며 여기서 오늘의 날씨 리포트를 선택하였습니다.



원하는 시각을 설정하고 Create Trigger를 클릭합니다.

이제 that 항목에 어떤 동작을 할 것인지 설정할 차례입니다.



Slack 채널에 메시지를 포스팅하는 액션을 선택합니다.


메시지를 포스팅 할 채널과 메시지 내용에 대한 설정을 완료한 후 Create Action을 선택합니다.


자 이제 마무리 단계입니다. 생성한 레시피가 어떤 동작을 하는지 간단한 정보를 타이틀에 기술하고 해당 레시피가 동작할 때 알림을 받을 것인지 선택한 후 Create Recipe를 선택하여 레시피 생성을 완료합니다.

이제 등록된 레시피를 통해 매일 아침 7시면 등록한 채널에 오늘의 날씨 정보가 자동으로 포스팅 되는 것을 확인하실 수 있을겁니다.

My Recipes 항목을 통해 자신이 생성한 레시피 정보에 대한 수정이나 삭제와 같은 관리 기능 또한 제공됩니다.

2014년 7월 6일 일요일

Java Object의 hashCode()와 equals() 메소드

참고 :  http://www.javaworld.com/article/2074996/hashcode-and-equals-method-in-java-object---a-pragmatic-concept.html

java.lang.Object의 메소드인 hashCode()와 equals()는 보편적으로 사용되는 메소드는 아니지만 상황에 따라 오버라이드 되어 사용되기도 합니다. 오버라이드시 어떤 기능을 할 수 있는지 한번 알아보도록 하겠습니다.

hashCode()

Object에 포함된 기본 상태의 hashCode() 메소드는 객체에 대한 메모리 주소 매핑 정보를 기반으로 정수 값을 반환 합니다. Object 클래스의 소스를 보면 알 수 있겠지만 hashCode() 메소드는 다음과 같이 작성 되어 있습니다.

public native int hashCode();

hashCode() 메소드가 JVM상에 네이티브 코드로 직접 구현되어 있다는 것을 의미하며 메모리 주소를 특정한 지역 정보 형태로 제공한다는 것을 알 수 있습니다. 이런 특징이 있음에도 불구하고 이 메소드는 구현체에서 오버라이드 될 수 있습니다.

equals()

equals() 메소드는 두개의 객체가 동일한지 비교할 수 있는 특별한 메소드 입니다. 자바에는 동일함을 비교하는 방식이 두 종류가 있는데 하나는 "==" 연산자를 사용하는 것이며 다른 하나는 "equals()" 메소드 입니다. equals() 메소드의 경우 다음과 같이 작성 되어 있습니다.

public boolean equals(Object obj) {
        return (this == obj);
    }

오버라이드가 가능하다는 측면에서 둘의 차이를 좀더 생각해 보자면 객체의 비교에 있어 ".equals()"는 서로 같은 값을 갖는 관계를 이야기 하며 "==" 연산자의 경우 물리적으로 완전히 동일함을 이야기 한다고 볼 수 있을 것 입니다.

규약

hashCode()와 equals()는 결국 상호 연관관계에 있는 메소드로 둘은 동시에 오버라이드 되어야 하며 그렇지 않으면 API문서에 나와있는 메소드 규약을 위반하게 됩니다.

테스트

테스트를 위해 hashCode()와 equals()모두를 오버라이드한 클래스와 둘다 변경하지 않은 클래스를 작성 하였습니다.

package reference.value;

public class Robot {
 private int productNumber;
 private String modelName;

 public Robot(int productNumber, String modelName) {
  super();
  this.productNumber = productNumber;
  this.modelName = modelName;
 }

 public int getProductNumber() {
  return productNumber;
 }

 public void setProductNumber(int productNumber) {
  this.productNumber = productNumber;
 }

 public String getModelName() {
  return modelName;
 }

 public void setModelName(String modelName) {
  this.modelName = modelName;
 }

}

package reference.value;

public class CustomRobot {
 private int productNumber;
 private String modelName;

 public CustomRobot(int productNumber, String modelName) {
  super();
  this.productNumber = productNumber;
  this.modelName = modelName;
 }

 public int getProductNumber() {
  return productNumber;
 }

 public void setProductNumber(int productNumber) {
  this.productNumber = productNumber;
 }

 public String getModelName() {
  return modelName;
 }

 public void setModelName(String modelName) {
  this.modelName = modelName;
 }

 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((modelName == null) ? 0 : modelName.hashCode());
  result = prime * result + productNumber;
  return result;
 }

 @Override
 public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (getClass() != obj.getClass())
   return false;
  CustomRobot other = (CustomRobot) obj;
  if (modelName == null) {
   if (other.modelName != null)
    return false;
  } else if (!modelName.equals(other.modelName))
   return false;
  if (productNumber != other.productNumber)
   return false;
  return true;
 }

}

이 둘의 차이를 알아보기 위해 다음과 같은 테스트 클래스를 작성하였습니다.

package reference.value;

import java.util.HashSet;

public class TestHashAndEqual {

 public static void main(String[] args) throws Exception {
  Robot r1 = new Robot(1999, "R2D2");
  Robot r2 = new Robot(1999, "R2D2");
  Robot r3 = r1;
  
  // toString in Object
  System.out.println("r1 is " + r1 + " , r2 is " + r2 );
  System.out.println("r1 == r2 result : " + (r1 == r2 ? true : false));
  System.out.println("r1 == r3 result : " + (r1 == r3 ? true : false));
  System.out.println("Equals between r1 and r2 : " + r1.equals(r2));
  System.out.println("HashCode r1 : " + r1.hashCode() + " , r2 : " + r2.hashCode());
  System.out.println("Identity HashCode r1 : " + System.identityHashCode(r1) + " , r2 : " + System.identityHashCode(r2));
  
  HashSet<Robot> rHS = new HashSet<Robot>();
  
  rHS.add(r1);
  rHS.add(r2);
  rHS.add(r3);
  
  System.out.println("HashSet Size : " + rHS.size());
  
  System.out.println("\n==============================\n");
  
  HashSet<CustomRobot> cRHS = new HashSet<CustomRobot&gt();
  
  CustomRobot cR1 = new CustomRobot(1999, "R2D2");
  CustomRobot cR2 = new CustomRobot(1999, "R2D2");
  CustomRobot cR3 = cR1;
  
  System.out.println("cR1 is " + cR1 + " , cR2 is " + cR2 );
  System.out.println("cR1 == cR2 result : " + (cR1 == cR2 ? true : false));
  System.out.println("cR1 == cR3 result : " + (cR1 == cR3 ? true : false));
  System.out.println("Equals between cR1 and cR2 : " + cR1.equals(cR2));
  System.out.println("HashCode cR1 : " + cR1.hashCode() + " , cR2 : " + cR2.hashCode());
  System.out.println("Identity HashCode cR1 : " + System.identityHashCode(cR1) + " cR2 : " + System.identityHashCode(cR2));
  
  cRHS.add(cR1);
  cRHS.add(cR2);
  cRHS.add(cR3);
  
  System.out.println("HashSet Size : " + cRHS.size());
  
 }

}

실행한 결과는 다음과 같습니다.

r1 is reference.value.Robot@15db9742 , r2 is reference.value.Robot@6d06d69c
r1 == r2 result : false
r1 == r3 result : true
Equals between r1 and r2 : false
HashCode r1 : 366712642 , r2 : 1829164700
Identity HashCode r1 : 366712642 , r2 : 1829164700
HashSet Size : 2

==============================

cR1 is reference.value.CustomRobot@49b52c2 , cR2 is reference.value.CustomRobot@49b52c2
cR1 == cR2 result : false
cR1 == cR3 result : true
Equals between cR1 and cR2 : true
HashCode cR1 : 77288130 , cR2 : 77288130
Identity HashCode cR1 : 2018699554 cR2 : 1311053135
HashSet Size : 1

두 메소드의 오버라이드를 통해 내용면에서 같은 멤버변수의 값을 갖는 객체라면 물리적으로 동일하지 않더라도 논리적으로는 서로 같은 객체로 인식하는 것을 알 수 있습니다.

HashSet의 경우 중복을 배제하는 컬렉션 클래스이기 때문에 사이즈 또한 이에 기반하여 증가하는 것을 확인 할 수 있으며 HashMap의 경우 이를 이용하여 Key에 일반적으로 사용되는 String이 아닌 별도의 객체를 이용하여 키를 구성하는 것 또한 가능하게 됩니다.

2014년 6월 17일 화요일

Java 8의 새로운 기능 Lambda

참고 :  http://programming.oreilly.com/2014/04/whats-new-in-java-8-lambdas.html

아직 익숙한 개념은 아니지만 Java 8에서 새롭게 추가된 기능 중 큰 비중을 차지하는 람다 표현식에 대해 이야기 해보려고 합니다.

람다란 무엇인가?

람다는 어떠한 행위를 나타내는 축약된 단일 메소드 클래스이며 변수 처럼 어딘가에 할당시키거나 다른 메소드들에 변수를 이용해 값을 전달 하는 것과 동일한 형태로 전달 될 수 있습니다. 솔식히.. 말로는 잘 와닿지 않습니다.. 그렇기 때문에 진행하면서 코드로 이야기를 하도록 하죠..

람다 구문 

input arguments -> body

람다 표현식은 일반 메소드와 같이 입력 변수부분과 동작을 기술하는 부분 그리고 선택적인 반환값으로 이루어져 있습니다. 위와 같이 화살표(->)를 기준으로 좌측은 메소드 변수를 우측은 이 변수들로 무엇을 할 것인가 하는 행위를 나타냅니다.

람다의 타입 : Functional Interface

자바는 형식화된 언어(Typed Language)로 필수적으로 타입을 선언하는 것이 일반적입니다. 그럼 람다의 타입은 대체 무엇인가? 라는 질문을 하지 않을 수 없는데 람다는 다음과 같이 설계 되었다고 합니다.

람다는 기존의 익명 클레스를 통해서 익숙한 익명 메소드 전략을 재활용한 것으로 새롭게 타입이 추가된 것은 아니라고 합니다. 대신 특별한 인터페이스인 Functional Interface를 갖습니다. 이는 일반적인 인터페이스와 동일하지만 다음의 추가적인 2개의 특징을 갖는다고 합니다.
  1. 단 하나의 추상 메소드를 갖음.
  2. 선택적 이지만 @FunctionalInterface 주석을 추가하여 람다 표현식으로 사용 될 수 있음(이 방식이 강력 추천 됨).
자바에는 무수히 많은 단일 메소드의 인터페이스들이 존재하며 이것들은 Functional Interface로 개조 되었습니다. 만약 필요에 의해 Functional Interface를 만들고자 한다면 하나의 추상 메소드를 갖는 인터페이스를 정의하고 @FunctionalInterface만 상단에 붙여주면 됩니다. 버전업 된 API 문서에서 Functional Interface가 적용된 것인지 확인 할 수 있습니다.

조건에 맞춰 다음과 같은 인터페이스를 정의해 보았습니다.

package lambda.works;

@FunctionalInterface
public interface TestFunctionalInterface<T> {
    public T doSomething(T t1, T t2);

}

그리고 다음과 같은 모델 클레스를 하나 작성 하였습니다.

package lambda.works;

public class CargoWorks {
    private int boxQty;

    public CargoWorks(int boxQty) {
        this.boxQty = boxQty;

    }

    public int getBoxQty() {
        return boxQty;

    }

    public void setBoxQty(int boxQty) {
        this.boxQty = boxQty;

    }

} 

람다 표현식을 이용하여 Functional Interface를 다음과 같이 구현해 보았습니다.

//두 스트링의 연결
TestFunctionalInterface<String> stringAdder = (String s1, String s2) -> s1 + s2;

//두 수의 곱
TestFunctionalInterface<Integer> multipleNumbers = (Integer i1, Integer i2) -> i1 * i2;

//두 박스의 합
TestFunctionalInterface<CargoWorks> quantityAdder = (CargoWorks c1, CargoWorks c2) -> {
        c1.setBoxQty(c1.getBoxQty() + c2.getBoxQty());
        return c1;

};


TestFunctionalInterface가 generic type 인터페이스이기 때문에 서로 다른 타입으로 구현이 가능한 것을 알 수 있습니다. 결국 람다 표현식의 타입은 Functional Interface라고 볼 수 있습니다.

이렇게 정의된 것은 다음과 같이 사용 될 수 있습니다.

package lambda.works;

public class ImplFunctions {
    //두 스트링의 연결
    TestFunctionalInterface<String> stringAdder = (String s1, String s2) -> s1 + s2;

    private void concatnateStrings(String s1, String s2) {
        System.out.println("Concatenated result : " + stringAdder.doSomething(s1, s2));

    }

}

매우 다양하게 정의 된 비지니스 로직이 위와 같이 단순한 절차에 의해 사용될 수 있습니다.

Java 8 이전과의 차이

package lambda.works;

public class TestPreJava8 {
    TestFunctionalInterface<CargoWorks> quantityMerger = new TestFunctionalInterface<CargoWorks>() {
        @Override
        public CargoWorks doSomething(CargoWorks c1, CargoWorks c2) {
            c1.setBoxQty(c1.getBoxQty() + c2.getBoxQty());
            return c1;
        }
        
    };

}

Java 8 이전이라면 앞서 소개한 람다 표현식은 위의 익명 클레스와 같이 표현 될 것입니다. 이걸 사용하려면 다음과 같겠죠..

public void preJava8Method() {
        TestFunctionalInterface<CargoWorks> quantityMerger = ...;
        
        CargoWorks c1 = new CargoWorks(1000);
        CargoWorks c2 = new CargoWorks(2000);
        
        CargoWorks mergedQunatiy = quantityMerger.doSomething(c1, c2);

}

아마도 기존의 방식대로면 람다 표현식과 같은 기능을 구현할 수는 있겠지만 불필요한 코드들이 많이 늘어나게 될 것 입니다. 람다의 간결함을 예로 들기 위해 다음은 다양한 행위가 있을 경우에 대한 예 입니다.

//두 박스의 합
TestFunctionalInterface<CargoWorks> quantityAdder = (CargoWorks c1, CargoWorks c2) -> {
        c1.setBoxQty(c1.getBoxQty() + c2.getBoxQty());
        return c1;
};
    
//수가 많은 박스
TestFunctionalInterface<CargoWorks> compareBoxes = (CargoWorks c1, CargoWorks c2) -> {
        if(c1.getBoxQty() > c2.getBoxQty()) {
            return c1;
        } else {
            return c2;
        }
        
};
    
//또 다른 박스관련 작업(람다에서는 기존에 존재하는 다른 메소드(여기에선 thatThingYouDo)도 사용이 가능합니다)
TestFunctionalInterface<CargoWorks> otherJobs = (CargoWorks c1, CargoWorks c2) -> thatThingYouDo(c1, c2);

위와 같은 여러 행위가 있다고 가정하면 다음의 메소드는 어떻게 처리할 것인지를 매개변수로 함께 받아서 처리할 수 있습니다.

private void applyBehavior(TestFunctionalInterface<CargoWorks> applySomething, CargoWorks c1, CargoWorks c2) {
        applySomething.doSomething(c1, c2);
}


Functional Interface인 Runnable

단일 메소드를 갖는 인터페이스 중 가장 있기 있는 걸 꼽으라면 아마도 Runnable일 것 입니다. 아무것도 반환하지 않는 run 메소드를 갖고 있는데 보통 쓰레드를 이용해서 프로그램의 성능을 향상시키기 위해 많이 사용되는 인터페이스 입니다.

기존에 익명클래스 방식을 사용한 코드는 다음과 같습니다.

new Thread(new Runnable() {
            @Override
            public void run() {
                doSomething();
                
            }
        }).start();


이것을 람다 표현식을 사용하면 다음과 같이 사용할 수 있습니다.

new Thread(() -> doSomething()).start();

바로 Thread의 생성자에 람다 표현식을 이용해서 행위를 전달할 수 있게 되는 거죠. 바로 Thread 클래스가 받아들이는 Runnable이 Functional Interface이기 때문에 가능해 지는 코드 입니다.

이것을 조금 더 응용해본다면 다음과 같은 코드도 작성할 수 있습니다.

package lambda.works;

public class AsyncManager {
    public void runAsync(Runnable r) {
        new Thread(r).start();
    }
}

그리고 이것을 이용하여..

package lambda.works;

public class TestFunctions {
    private AsyncManager manager = new AsyncManager();

    //두 스트링의 연결
    TestFunctionalInterface<String> stringAdder = (String s1, String s2) -> s1 + s2;
       
    //두 수의 곱
    TestFunctionalInterface<Integer> multipleNumbers = (Integer i1, Integer i2) -> i1 * i2;
    
    //두 박스의 합
    TestFunctionalInterface<CargoWorks> quantityAdder = (CargoWorks c1, CargoWorks c2) -> {
        c1.setBoxQty(c1.getBoxQty() + c2.getBoxQty());
        return c1;
    };
    
    private void takeResults() {
        CargoWorks cargoNoOne = new CargoWorks(1000);
        CargoWorks cargoNoTwo = new CargoWorks(2000);
       
        manager.runAsync(() -> System.out.println("Running in Async mode"));
        manager.runAsync(() -> {
            for(int i = 0; i < 100; i++) {
                System.out.println("just counting : " + i);
            }
        });
        manager.runAsync(() -> System.out.println("String : " + stringAdder.doSomething("A", "b")));
        manager.runAsync(() -> System.out.println("Number : " + multipleNumbers.doSomething(1, 2)));
        manager.runAsync(() -> System.out.println("Qty : " + quantityAdder.doSomething(cargoNoOne, cargoNoTwo).getBoxQty()));

    }
    
    public static void main(String[] args) throws Exception {
        new TestFunctions().takeResults();
    }
}

이렇게 테스트를 해보면 각각은 쓰레드로 동작하기 때문에 결과가 뒤섞여서 나옴을 알 수 있습니다. 참 쉽죠?

마치며..

개인적으로 람다 표현식은 기존의 Java 프로그래밍에 꽤 많은 변화를 가져오지 않을까 생각됩니다. 아마도 병렬 프로그래밍과 비동기 프로그래밍이 보편화된 시대의 도래를 촉진하겠죠.. 아직 람다에 대해 많은 것을 알지는 못하지만 Java 8에서 가장 핫한 녀석인것 같다는 생각입니다.