2장 객체의 생성과 삭제

규칙 2. 생성자 인자가 많을 때는 Builder 패턴 적용 또한 고려하자.

 

static factory method와 constructor는 둘 다 선택적 인자가 많은 경우에 적응하기 힘들다는 공통적인 단점을 가지고 있다.

 

1. 점층적 생성자 패턴(telescoping constructor pattern)

선택적 인자가 10여개가 넘을 시, 보통 점층적 생성자 패턴(telescoping constructor pattern)을 적용한다.

필수 인자 생성자 정의 후, 추가적으로 선택적 인자를 받는 생성자를 추가하여 해당 생성자를 호출하는 방식으로 대표적인 예는 다음과 같다.

public class NutritionFacts{
  private final int servingSize;
  private final int servings;
  private final int calories;
  private final int fat;
  private final int sodium;
  private final int carbohydrate;
  
  public NutritionFacts(int servingSie, int servings){
    this(servingSize, servings, 0);
  }
  
  public NutritionFacts(int servingSie, int servings, int calories){
    this(servingSize, servings, calories, 0);
  }
  
  public NutritionFacts(int servingSie, int servings, int calories, int fat){
    this(servingSize, servings, calories, fat, 0);
  }
  
  public NutritionFacts(int servingSie, int servings, int calories, int fat, int sodium){
    this(servingSize, servings, calories, fat, sodium, 0);
  }
  
  public NutritionFacts(int servingSie, int servings, int calories, int fat, int sodium, int carbohydrate){
    this.servingSize = servingSize;
    this.servings = servings;
    this.calories = calories;
    this.fat = fat;
    this.sodium = sodium;
    this.carbohydrate = carbohydrate;
  }
}

정말 그지같다.

코드만 봐도 알 수 있다싶이, 점층적 생성자 패턴은 인자수가 증가 할 수록 작성하기도, 읽기도 어려운 코드가 된다.

특히 코드만 봐서 해당 인자가 어떤 인자인지 알 기가 힘들다. (인자의 순서를 하나씩 세어봐야 한다.)

 

2. 자바빈 패턴(JavaBeans Pattern)

두번째 방법으로는 자바빈 규약에 따르는 클래스를 생성 한 뒤, setter 메서드로 각 값들을 설정해 주는 방법이 있다.

public class NutritionFacts{
  private final int servingSize;
  private final int servings;
  private final int calories;
  private final int fat;
  private final int sodium;
  private final int carbohydrate;
  
  public NutritionFacts(){}

  //Setter Method
  public void setServingSize(int servingSize){ this.servingSize = servingSize; }
  public void setServings(int servings){ this.servings = servings; }
  public void setCalories(int calories){ this.calories = caloires; }
  public void setFat(int fat){ this.fat = fat; }
  public void setSodium(int sodium){ this.sodium = sodium; }
  public void setCarbohydrate(int carbohydrate){ this.arbohydrate = arbohydrate; }
}

자바빈 패턴은 점층적 생성자 패턴에 있던 문제는 없지만, 1회의 함수 호출로 객체 생성을 마칠 수 없으므로 객체 일관성이 깨질 수 있다.

또한 자바빈 패턴은 immutable 클래스를 만들 수 없다는 단점이 있다.

 

3. 빌더 패턴 (Builder Pattern)

빌더 패턴은 점층적 생성자 패턴의 안전성과 자바빈 패턴의 가독성을 결합한 대안이다.

객체를 생성하는 빌더 클래스(builder class)를 만들고, 이 생성자에 필수 인자를 전달하여 빌더 객채(builder object)를 만든다.

그 후 빌더 객체에 정의된 setter method를 호출하여 선택적 인자들을 추가하고 build 메서드를 호출하여 immutable 객체를 만든다.

public class NutritionFacts{
  private final int servingSize;
  private final int servings;
  private final int calories;
  private final int fat;
  private final int sodium;
  private final int carbohydrate;
  
  private NutritionFacts(Builder builder){
    this.servingSize = builder.servingSize;
    this.servings = builder.servings;
    this.calories = builder.calories;
    this.fat = builder.fat;
    this.sodium = builder.sodium;
    this.carbohydrate = builder.carbohydrate;
  }
  
  //builder class
  public static class Builder {
    //필수 인자
    private final int servingsize;
    private final int servings;
    //선택적 인자(초기화)
    private final int calories = 0;
    private final int fat = 0;
    private final int sodium = 0;
    private final int carbohydrate = 0;
   
    public Builder(int servingSize, int servings){
      this.servingSize = servingSize;
      this.servings = servings;
    }
    
    public Builder calories(int calories){
      this.calories = calories;
      return this;
    }
    
    public Builder fat(int fat){
      this.fat = fat;
      return this;
    }
    
    public Builder sodium(int sodium){
      this.sodium = sodium;
      return this;
    }
    
    public Builder carbohydrate(int carbohydrate){
      this.carbohydrate = carbohydrate;
      return this;
    }
    
    public NutritionFacts build(){
      return new NutritionFacts(this);
    }
  }  
}

해당 코드는 모든 인자가 final로 immutable한 NutritionFacts 객체이다.

위에서 작성한 빌더 패턴은 객체 자신을 반환하므로 다음과 같이 chaining하여 사용 할 수 있다.

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();

 

빌더 패턴의 장점

또한 빌더패턴을 적용하면서 얻을 수 있는 장점은 다음과 같다.

  1. 불변식(invariant)을 적용 할 수 있다.

    build 메서드, 혹은 build가 호출 되기 전에 불변식에 위반되었는지 검사 할 수 있다.

    public NutritionFacts build(){
      if(/*불변식 검사*/){
        throw new IllegalArgumentExcepiton();
      }
      return new NutritionFacts(this);
    }
    
    public Builder carbohydrate(int carbohydrate){
      if(/*불변식 검사*/){
        throw new IllegalArgumentExcepiton();
      }
      this.carbohydrate = carbohydrate;
      return this;
    }
    
  2. 객체를 생성 할 때 여러 개의 가변인자(varargs, ex : String…)를 인자로 받을 수 있다.

    가변인자는 생성자 및 메서드에 하나만 사용 가능하다. 하지만 빌더 패턴을 적용하면 각 선택적 인자의 setter 메서드에 가변 인자를 적용 할 수 있다.

    (기본적으로 메서드 시그니쳐의 마지막 인자에만 지정한다.)

  3. 빌더 패턴은 유연하다.

    하나의 빌더 객체로 여러 객체를 만들 수 있다.

    기존에 사용했던 빌더 객체에서 setter 메서드로 값만 바꾼 후 build 하면 다른 객체가 생성된다.

    또한 빌더 객체는 어떤 필드의 값은 자동으로 채우도록 할 수 있다.(예 : 자동으로 증가하는 일련 번호 등.)

  4. 인자가 설정된 빌더는 훌륭한 추상적 팩토리이다.

    클라이언트는 빌더를 다른 메서드에 넘겨서 하나 이상의 객체를 만들도록 할 수 있다.

    다음과 같은 제네릭을 적용한 인터페이스면 어떤 객체를 만드는 빌더냐에 관계 없이 모든 빌더에 적용 할 수 있다.

    (빌더 객체를 인자로 받는 모든 메서드에 전달 할 수 있다.)

    public interface Builder<T> {
      public T build();
    }
    

    빌더 객체의 인자를 제한 할 경우, 보통 한정적 와일드카드 자료형을 통해 인자의 자료형을 제안한다.

    ex ) Tree buildTree(Builder<? extends Node> nodeBuilder){ … }

 

빌더 패턴의 단점

객체를 생성 하려면 우선 빌더 객체를 생성해야한다는 단점이 있다.

실무에서는 빌더 객체 생성에 의한 오버헤드의 문제는 없겠지만, 성능이 중요한 상황에선 그렇지 않을 수 도 있다.

또한 점층적 생성자 패턴에 비해 만흔 코드를 요구하기 때문에 인자가 충분히 많은 상황에서 이용해야 한다.

'Effective Java > 2장. 객체의 생성과 삭제' 카테고리의 다른 글

규칙 1 . Static Factory Method  (0) 2018.01.03

Effective Java

2장 객체의 생성과 삭제

규칙 1. 생성자 뿐만 아니라 정적 팩터리 메서드의 사용도 고려하자.

객체를 생성할때 일반적으로 public으로 선언되어있는 생성자를 사용한다.

하지만 생성자 외에 static factory method(정적 팩토리 메서드)를 사용해서 객체를 생성 할 수 있다.

 

정적 팩토리 메서드의 장점

  1. 생성자와 달리 static factory method에는 이름이 존재한다.

    해당 메서드가 어떤 객체를 생성하는지 알 수 있어 가독성을 높일수 있다.

  2. 생성자와 달리 호출 될 때마다 새로운 객체를 생성할 필요가 없다.

    • 이뮤터블 클래스일 경우 이미 만든 객체를 활용할 뿐만 아니라 객체를 캐시하여 재사용 하여 객체를 불필요하기 생성하는 것을 피할 수 있다.
    • 특정시점에 객체가 얼마나 존재하는지 제어할수 있다.(instance-controlled class). 이를 통해 싱글턴 패턴 클래스, 객체 생성이 불가능한 클래스 생성 등을 만들수 있다. 또한 이뮤터블 클래스일 경우 같은 객체가 존재하지 않도록 할 수 있다.
  3. 생성자와 달리 자료형의 하위 자료형 객체를 반환 할 수 있다.

    • 반환되는 객체의 클래스를 유영하게 결정 할 수 있다.

    • 구현 세부 사항을 감출수 있으므로 간결한 API가 가능하며 이는 인터페이스 기반 프레임 워크에 적합하다.

    • 대표적인 예 : JDK 1.5에 도입된 EnumSet -

      EnumSet은 public 생성자가 없으며 정적 팩토리 메서드만 가지고있다.

      EnumSet은 정적 팩토리 매서드로 두가지 구현체 중 하나를 반환한다.

      • RegularEnumSet : enum 상수들이 64개 이하일경우, enum의 갯수를 long 변수 하나만 사용해 표현한다.
      • JumboEnumSet : enum상수들이 64개 이상일 경우, enum의 갯수를 long형 배열을 사용해 표현한다.
    • Service Provider Framework : 정적 팩토리 메서드가 작성된 시기에 반환될 구현체 클래스가 존재하지 않아도 무방하다.

      • 대표적인 예 : JDBC

      • Service Provider Framework는 세가지 핵심 컴포넌트로 구성된다.

        • Service Interface : 서비스 제공자가 구현한다. (JDBC : Connection)
        • Provider Registration API : 구현체를 서비스에 등록하여 클라이언트가 사용 가능하도록한다. (JDBC : DriverManger.registDriver)
        • Service Access API : 클라이언트에게 서비스 구현체를 제공한다.(DriverManger.getConnection)
        • Service Provider Interface : 선택적이며, 서비스 제공자가 구현한다. 서비스 구현체의 객체를 생성하기 위한 인터페이스이다. 이가 없으면 자바의 리플렉션등으로 객체가 생성된다.(JDBC : Driver)
  4. 형인자 자료형(parameterized type) 객체를 만들 때 편리하다.

    형인자 자료형 클래스의 생성자를 호출할 때는, 문맥상 형인자가 명백하더라도 반드시 인자로 형인자를 전달해야한다.

    Map<String, List<String>> m = new HashMap<String, List<String>>();
    

    이처럼 형인자가 늘어남에 따라 길고 복잡한 코드가 만들어진다. 하지만 정적 팩토리 메서드로 컴파일러가 형인자를 유추하도록 할 수 있다. 이를 자료형 유추(type interface)라고 부른다.

    public static <K, V> HashMap<K, V> newInstance(){
      return new HashMap<K, V>();
    }
    Map<String, List<String>> m = HashMap.newInstance();
    

JDK 1.7 이후부터는 생성자를 이용해서도 형인자 유추가 가능하다.

 

정적 팩토리 메서드의 단점

  1. 정적 팩토리 메서드만 있는 클래스는 public이나 protected로 선언된 생성자가 없으므로 하위 클래스를 만들 수 없다.
  2. 정적 팩토리 메서드가 다른 정적 메서드와 확연히 구분되지 않는다.

요약

정적 팩터리 메서드와 public 생성자는 용도가 서로 다르다. 그 차이점과 장단점을 이해하고 정적 팩토리 메서드가 효과적인 경우가 많으니 무조건 public 생성자만 만드는 것은 삼가자.

'Effective Java > 2장. 객체의 생성과 삭제' 카테고리의 다른 글

규칙 2 . Builder Pattern  (1) 2018.01.14

Elasticsearch


Elasticsearch와 RDBMS의 데이터구조 비교

ElasticsearchRDBMS
인덱스데이터베이스
타입테이블
도큐먼트로우
필드컬럼
매핑스키마


클러스터

엘라스틱서치의 가장 큰 시스템 단위.

하나의 클러스터는 하나 이상의 노드로 구성.

여러대의 서버가 하나의 클러스터로 구성할 수 있으며, 그 반대도 가능하다.


노드

데이터를 색인하고 검색을 수행하는 단위 프로세스

각 노드는 1개 이상의 데이터 원본과 복사본을 가지고 다른 위치에 나누어 저장한다.

노드가 종료되거나 실행에 실패할 경우 다른 노드로 데이터를 이동한다.

  • 마스터노드 : 클러스터 상태 관리
  • 데이터노드 : 데이터/입출력, 검색 수행
  • 클라이언트 노드 : 색인 및 검색을 위한 명령, 결과 전달


샤드와 복사본(Shard and Replica)

  • 샤드는 데이터 검색을 위해 구분되는 최소 단위.
  • 색인된 데이터는 여러개의 샤드로 분할되어 저장된다. (분산처리)
  • 기본적으로 인덱스당 5개의 샤드와 5개의 레플리카로 분리.
  • 샤드와 레플리카를 설정할 때 말고 사용자가 직접 샤드에 접근할 일은 없다.
  • 생성된 인덱스의 샤드 설정은 변경 불가능하다.

최초 샤드 (Primary Shard)

  • 데이터가 색인되어 저장되는 공간.
  • 최초 샤드에 데이터가 색인되면 동일한 수 만큼 레플리카를 생성.
  • 최초 샤드가 유실되는 경우 레플리카를 최초 샤드로 승격.
  • 최초 샤드와 레플리카는 서로 다른 노드에 저장한다


REST API

  • 엘라스틱서치는 기본적으로 REST API으로 데이터를 처리한다.
  • REST API를 용하기 위한 주소는 다음과 같다.
    • http://host:port/{INDEX}/{TYPE}/{DOCUMENTID}

검색

  • 검색 기능은 query 명령어를 이용해 수행한다.
  • 타입 및 인덱스의 범위로 질의가 가능하다.
  • 여러개의 인덱스를 묶어서 멀티 인덱스의 범위로 질의 또한 가능하다.
  • URI를 통한 검색 시 
  • REQUEST BODY 검색
    • JSON을 통해 질의한다.
    • QueryDSL 사용

SEARCH TYPE

검색을 수행하는 방법을 지정.

query_then_fetch : 전체 샤드의 검색이 모두 수행된 후 결과 출력. 전체 취합된 결과를 size 매개변수에서 지정한 수만큼 출력.

query_and_fetch : 샤드별로 검색되는대로 결과 출력. size * 샤드의 갯수만큼 출력.

dfs_query_then_fetch : 검색방식은 query_then_fetch와 같으나 정확한 스코어링을 위해 검색어들을 사전 처리.

dfs_query_and_fetch : 검색방식은 query_and_fetch와 같으나 정확한 스코어링을 위해 검색어들을 사전 처리한다.

count : 검색된 도큐먼트를 배제하고 전체 hits수만 출력.

scan : 검색 결과를 바로 보여주지 않고 저장했다가 _scroll_ id를 사용해 나중에 결과 출력


쿼리와 필터

Elasticsearch의 검색 방법은 크게 쿼리와 필터로 나뉜다.

쿼리와 필터는 모두 QueryDSL로 작성한다.



쿼리(Query)필터(Filter)
검색 대상(일반적)Full Text(전문 검색)Bianry(Y/N)
점수 계산OX
캐싱XO
응답속도(상대적)느림빠름



형태소 분석 과정


  • 대문자는 모두 소문자로 변환한다.
  • 중복된는 단어는 제거한다.
  • 분석 과정을 거치고 거장도니 토큰을 term이라고 한다.

Query

term 쿼리

  • term 옵션을 사용.
  • 질의문이 저장된 term과 정확히 일치하는 내용을 찾는다.


{

"query" : {

"term" : {

"message" : "취소"

}

}

}

 



terms 쿼리

  • terms 옵션으로 2개 이상의 텀을 같이 검색한다.
  • terms 쿼리의 값은 항상 배열 형식으로 입력한다.
  • minimum_shuld_match 옵션으로 최소 일치 수도 지정 가능하다.


{

"query" : {

"terms" : {

"message" : ["인증", "취소"],

"minimum_shuld_match" : 2

}

}

}

 



match 쿼리, mutlti match 쿼리

  • match 옵션을 사용한다.
  • 질의문 또한 형태소 분석을 거친 뒤 사용한다.
  • multi_match옵션을 사용하면 여러 필드에서 검색 가능하다.
  • analyzer 옵션으로 원하는 형태소 분석 적용을 가능하다.
    • 한글은 엘라스틱서치에서 기본 지원하지 않아 은전한닢(http://eunjeon.blogspot.kr)이라는 커스텀 형태소 분석기를 이용한다.


{

"query" : {

"match" : {

"message" : {

"query" : "인증을 취소합니다."

}

}

}

} 





bool 쿼리

내부의 질의로 다른 질의를 포함시켜 사용한다.

쿼리를 조건문인 bool 조합으로 적용하여 최종 검색 결과를 나타낸다

  • must : AND 조건
  • must_not : NOT 조건
  • shuld : 해당할 필요는 없으니 해당 될 경우 더 높은 점수를 가진다.

fuzzy 쿼리 

  • 편집거리 알고리즘(르벤슈타인 알고리즘)을 기반으로 유사 단어 검색 지원.
  • 숫자, 날짜 형식을 대상으로 범위 검색으로 응용 가능.



Filter

스코어를 계산하지 않아 쿼리에 비해 월등히 빠름.

결과가 메모리에 캐싱됨(_cache 옵션으로 캐싱 여부 설정 가능)


은전한닢 (한글 형태소 분석기)

http://eunjeon.blogspot.kr/

품사태그표 : https://docs.google.com/spreadsheets/d/1-9blXKjtjeKZqsf4NzHeYJCrr49-nXeRF6D80udfcwY/edit#gid=589544265


엘라스틱서치 점수 측정 방법

  • Term frequency: 문서(필드) 안에 term이 자주 출현할수록 고득점
  • Inverse document frequency: 색인된 모든 문서(필드)에 term이 적게 존재할수록 고득점
  • Length norm: 문서(필드)가 짧을수록 고득점

http://www.popit.kr/bm25-elasticsearch-5-0%EC%97%90%EC%84%9C-%EA%B2%80%EC%83%89%ED%95%98%EB%8A%94-%EC%83%88%EB%A1%9C%EC%9A%B4-%EB%B0%A9%EB%B2%95/




https://www.slideshare.net/seunghyuneom/elastic-search-52724188

책 읽어주는 딥러닝

https://www.slideshare.net/carpedm20/deview-2017-80824162

  • Data
    1.  한국어 음성 데이터는 공개된 데이터가 없어서 직접 만들어야 한다.
    2. Google Speech API, Text Similarity를 통해 자동화

  • Model : Tacotron, Deep voice 2
    • Tacotron
      1. Encoder -
        1. Character Embeddings : 해당 텍스트를 가장 잘 나타내는 숫자를 생성
      2. Decoder -
        1. 음성을 생성 할 수 있는 스팩트로그램을 생성
        2. 스팩트로그램 : 음성이 되기 직전의 숫자
        3. Attention
      3. Vocoder - 스팩트로그램을 음성으로 변환
        1. Griffin-Lim : 스팩트로그램을 음성으로 만들어주는 알고리즘
      4. Attention -
          1. 텍스트의 어디에 집중할 것인가를 계산,
          2. 스팩트그램을 만드는 RNN에 Atteintion을 전달
          3. Attention의 중요성 :일반화
          4.  학습하지 않았던 문장도 얼마나 잘 말할 수 있는가?

Tacotron : Enbedding, Bidirectinal RNN

    • Deep Voice 2
    1. Multi Speaker Tacotron : 여러 목소리를 트레이닝 한다.
    2. Speaker Embedding
      1. 캐릭터 임베딩 뿐만 아니라 발화자를 잘 나타내는 숫자를 생성
    3. Multi Speaker Attention
      1. 어텐션을 임의의 어텐션으로 교체하여 커스터마이징 할 수 있다.

  • Code 



그런 REST API로 괜찮은가

http://slides.com/eungjun/rest#/

  • 2000년에 REST에 대해 논문을 발표한 Roy T. Fielding가 현재 대부분의 REST API는 제약 조건을 따르지 않는다고 한다.

  • REST API : REST 아키텍쳐 스타일을 따르는 API
    • REST를 구성하는 스타일 
      • client-server
      • stateless
      • cache
      • uniform interface
      • layered system
      • code-on-demand (optional)

  • REST API의 제약 조건은 현재 http를 통해서 대부분 만족한다. 다만 Uniform interface는 만족하지 못하는 경우가 많다.

  • uniform interface중에서 특히  Self-descriptive와 HATEOAS를 잘 만족하지 못한다.
    • Self-descriptive - 메시지는 스스로에 대해 명세할 수 있어야한다. (헤더 / 미디터타입 등)
    • HATEOAS - 애플리케이션의 상태는 hyperlink에 의헤 명세되어야한다.

  • 독립적 진화
    • 서버와 클라이언트가 각각 독립적으로 진화한다
    • 서버의 기능이 변경되어도 클라이언트는 업데이트 할 필요가 없다.
    • 대표적인 예시 : 웹브라우저

  • 즉 REST API란 하이퍼텍스트를 포함한 self-descriptive한 메시지의 uniform interface를 통해 리소스에 접근하는 API.

  • Self-descriptive를 지키는 방법
    1. 미디어타입 정의
    2. Profile
  • HATEOAS
    1. data로 링크 등 다음 상태를 전달한다.
    2. HTTP 헤더를 통해 상태를 전달한다.

오픈소스 데이터베이스, 은행 서비스에 첫발을 내밀다.

https://www.slideshare.net/deview/135-80845610

  • MySQL의 Replication을 통한 데이터 이중화 
    • Async Replication 
      • master의 데이터를 async로 slave에 replication한다. (mysql의 replication은 async로 동작한다.)
      • 비동기 replication 으로 master 장애시 데이터 유실 위험이 있다.

  • Lossless Replication 
    • slave 어딘가에 반드시 변경 이력이 남아있다.
    • Binary log를 전 slave에 전송, 단 하나의 slave에서라도 ack가 온다면 storage commit. 
    • slave중 최소 하나는 로그를 가지고있다
    • master 장애 시 slave중 어딘가에 있을 로그를 통해 데이터를 복구한다.

  • MHA (Master High Availability)
    • 30초 이내로 데이터 유실 없이 복구
    • 3초마다 DB 헬스체크, 3회 실패시 장애 인지후 페일오버.
    • Lossless Replication을 통한 relay log로 데이터 복구.
  • Domain Failover 
    • VIP failover가 아닌 Domain Failover를 채택.
    • 서비스 도메인(master) 장애시 slave로 DNS를 설정하여 장애 대응.
    • 서비스 도메인의 TTL은 0로 설정한다.
    • DNS는 캐싱하지 않는다.
    • 서비스는 커넥션풀로 동작한다.

  • Scale-out
    • 데이터 특성에 따라 파티셔닝.
    • static : 데이터 변경이 거의 없는 static 데이터는 캐시를 이용한다.
    • dynamic, historical : 변경이 잦은 사용자 데이터 혹은 로그 데이터는 DB를 통해 확장한다.
      • SERVICE : innoDB only, Range Partitoning한다.
      • ARCHIVE : 장기보관 데이터, tokuDB에  Range Partitoning한다.
    • tokuDB ? : http://gywn.net/2014/05/fractal-index-in-tokudb/
      • Fractal Index : 피벗에 버퍼를 두어 IOPS를 줄임.




 

람다 표현식이란?


자바 8에 추가된 함수형 프로그래밍 요소

객체지향이 객체의 필드에 데이터를 저장하고 그를 다루는데에 반해,

함수형 프로그래밍은 선언적이어서 statement보다 선언, 혹은 표현식을 사용한다.

 

자바8의 람다식은 다음과 같은 형태를 가진다


parameters -> expression body

ex ) Comparator comparator = (a, b) -> a.compareTo(b)



람다 표현식의 특징


  1. 타입 선언이 선택적이다 .

  2. 싱글 파라미터의 경우 괄호가 필요 없다.

  3. 중괄호 선택 : 한문장일 경우 중괄호가 필요 없다

  4. return 키워드 선택 : 한문장일 경우 생략 가능하다, 다만 중괄호를 포함한 경우 무조건 return 포함해야한다.

 

람다식을 이용하려면 오버라이드 할 메서드가 포함된 함수형 인터페이스가 필요하다.

함수형 인터페이스는 함수를 하나만 가지는 인터페이스에, 함수형 인터페이스 어노테이션(@FunctionalInterface)를 붙여 명시한다.

자바 API에 내장된 대표적인 함수형 인터페이스로는 Runable이나 Comparator가 있다.

 



Java 8 이전 방식과 람다 표현식


1. 클래스 생성

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("I have implemented Runnable");
    }
 
    public static void main(String args[]) {
        MyRunnable runnable = new MyRunnable();
        runnable.run();
    }
}
2. 익명 클래스

Runnable runnable = new Runnable() {
  @Override
  public void run() {
    System.out.println("I have implemented Runnable");
  }
};
runnable.run();

3. 람다식 사용

Runnable runnable = () -> System.out.println("I have implemented Runnable");
runnable.run();



함수형 인터페이스

 

람다를 사용하기 위해선 추상메서드를 하나만 가지는 함수형 인터페이스가 필요하다.

함수형 메서드의 추상 메서드 시그너처를 함수 디스크립터라고 한다.

 

자바8에는 java.util.function에 Predicate, Consumer, Function 등, 그 외 여러 인터페이스가 이미 구현되어있다.

 

대표적인 함수형 인터페이스


함수형 인터페이스

함수 디스크립터

Predicate<T>

T->boolean

Consumer<T>

T->void

Function<T, R>

T->R

Supplier<T>

()->T

UnaryOperator<T>

T->T

BinaryOperator<T>

(T, T)->T

BiPredicate<L, R>

(L, R)->boolean

BiConsumer<L, R>

(L, R)->void

BiFunction<T, U, R>

(T, U,)->R


  • autoboxing에 대한 리소스를 줄이기 위해 각 함수형 인터페이스엔 기본형 특화 인터페이스가 존재하며, 인터페이스 네임 서두에 자료형을 붙인 형태이다.

  • Function같은 경우 파라미터와 반환형을 명시하기 위해 ToIntFunction, DoubleToIntFunction등이 존재한다.

 

형식 검사


컴파일러는 람다가 사용되는 컨텍스트를 기반으로 람다의 형식을 추론한다.

어떤 컨텍스트에서 기대되는 람다 표현식의 형식을 대상 형식이라고 부른다.

형식을 검사하는 순서는 다음과 같다.


  1. 람다 선언부 확인(람다가 매서드 파라미터로 사용시 매서드 선언부 확인)

  2. 선언부에서 대상형식 확인.

  3. 대상형식이 함수형 인터페이스인지 확인.

  4. 대상형식의 추상 메서드 확인.

  5. 추상메서드의 함수 디스크립터와 람다의 시그니처를 비교하여 형식 검사를 완료한다.

 

형식 추론


자바 컴파일러는 콘텍스트를 통해 람다 표현식과 관련된 함수형 인터페이스를 추론한다..

즉 형식 검사를 통해 함수 디스크립터를 알 수 있으므로, 컴파일러는 람다의 시그니처 또한 추론 할 수 있다.

람다의 시그니쳐 추론을 통해 람다 표현식의 파라미터 형식에 접근 할 수 있으므로 파라미터의 형식이 생략 가능하다.

(앞서 정리한 특징 중 '1. 타입 선언이 선택적이다'에 해당한다.)

 

 


람다 표현식의 유효 범위


람다표현식은 java에서 정의된 명명규칙인 name conflicts와 shadowing 규칙이 똑같이 적용된다.

즉 람다 파라미터나 람다 표현식 내에 람다가 선언되는 지역의 지역변수와 똑같은 이름의 변수를 선언 할 수 없다.

this키워드 또한 람다표현식 내에서 사용할 경우, 람다 표현식이 선언되는 메서드의 this 파라미터를 의미한다.


 

외부 유효범위의 변수 접근


람다 표현식 외부에 있는 변수를 람다가 사용할 경우, 해당 변수를 자유 변수라 부른다.

람다가 자유변수를 capture하기 위해서

자유 변수는 final이거나 변하지 않는 effectivley final 변수여야 한다.




참조 : https://dzone.com/articles/a-little-lambda-tutorial, 카이호스트만 코어 자바 8

Async



node.js의 장점으로 꼽는 점 중 하나가 non-Blocking으로써 비동기적으로 작동한다는 점이다.


그렇기 때문에 node.js의 대부분의 기능들은 콜백 패턴을 이용한 비동기 처리를 사용는데, 이러한 점 때문에 실제 개발에 콜백 지옥으로 인해 난관을 겪곤 한다.


콜백 지옥이란 콜백 패턴을 이용하는 함수에, 연달아 추가 기능을 사용할 경우 콜백이 계속해서 중첩되는 상태를 말한다.


task1(a, function(err, result1){     task2(b, function(err, result2){         task3(c, function(err, result3){             task4(d, function(err, result4){                 //함수 실행             });         });     }); });


위 예제는 err에 대한 처리와 각 파라미터에 대한 알고리즘을 베재한 코드이다. 

4중첩이지만 실제 코드를 node.js로 코딩할땐 흔히 4중첩이상은 흔히 볼 수 있다.


이러한 콜백 지옥을 해결하기 위해서 다양한 모듈이 있다. 

자바스크립트의 Promise 패턴을 이용한 방법(대부분의 모듈은 Promise를 지원한다.), 혹은 Async나 Step이라는 모듈로 흐름을 제어하는 방법이다.


여기서는 Async모듈을 사용하는 방법을 정리하고자 한다.


async : https://github.com/caolan/async

docs : http://caolan.github.io/async/

설치 : npm install async --save



async 모듈의 메소드는 크게 콜렉션, 흐름제어, 유틸 3종류로 나뉘어진다. 주로 사용하는건 콜렉션과 흐름제어이고, 이 포스팅 자체는 흐름제어가 주제이므로 이를 주로 다루겠다.



흐름 제어 메서드


async의 흐름 제어 메서드는 정말 다양한 종류가 있지만, 내가 주로 사용한 메서드는 다음과 같다.


- 순차 실행 : series, waterfall

- 병렬 실행 : parallels



순차 실행

순차 실행은 이름 그대로 메서드들을 순차적으로 실행하는 메서드이다. 여기엔 인자를 전달하지 않고 순차적으로만 실행하는 메서드인 series와, 

다음 메서드에 인자를 전달할 수 있는 waterfall 메서드가 있다.


1. series


시리즈 메서드는 다음과 같은 형태를 가진다.

async.series([task1,task2,task3], function(err, results){ // series 완료 콜백 });

첫번째 파라미터는 실행할 함수들을 순서대로 배열에 넣어 삽입한다, 두번째 메서드는 이 serise가 완료되었을때 실행되는 콜백 함수를 넣는데,


이 콜백에는 실행 메서드들 중 실패하는 경우 err에 인자를 전달하며 그 뒤 테스크들의 실행을 중단하고 콜백 함수를 호출한다.


모두 성공할 경우 콜백 함수의 results에 배열에 있는 각 테스크들의 실행 결과를 배열로 전달받는다.


배열에 들어가는 메서드들은 callback을 인자로 가지는 함수 형태로 만들어야 한다.




function task1(callback){     //실패시     if(err){         callback(err);     }     //성공시     callback(null, '성공1'); }


성공 시에는 callback인자를 함수로 호출하는데, 첫 인자를 null로 전달하면 되고


실패 시에는 첫 null에 err를 전달하면 된다.


async.series([
    function (callback) {
        if (err) {
            callback('실패1');
        }
        callback(null, '성공1');
    }, 
    function (callback) {
        if (err) {
            callback('실패2');
        }
        callback(null, '성공2');
    },
    function (callback) {
        if (err) {
            callback('실패3');
        }
        callback(null, '성공3');
    }],
    function(err, results) {
        // serise 완료 콜백
        if(err){
            //err시 err난 테스크에서 전달한 err값을 전달받는다
            console.log(err);
        }
        //모두 성공시, 성공 값들을 전달받는다
        //예) ['성공1','성공2','성공3']
        console.log(reuslts);
});



2. waterfall


serise와 waterfall은 둘다 순차 실행이지만

serise는 테스크들이 독립적으로 실행되어 모든 결과를 모아 배열로 콜백에 전달하는 반면에, 

waterfall은 각 테스크들의 값을 다음 인자로 전달한다는 점에서 다르다.


async.waterfall([task1,task2,task3], function(err, result){ // waterfall 완료 콜백 });

메서드의 형태는 searise와 얼핏 비슷하나, task에서 다음 콜백을 호출 할 때 인자를 전달 할 수 있다.



function task1(callback){ //실패 시 if(err){ callback(err); } //성공 시 callback(null, '성공', '1'); } function task2(arg1, arg2, callback){ //실패 시 if(err){ callback(err); } //성공 시 callback(null,'성공2'); }

async.waterfall([ function (callback) { if (err) { callback('실패1'); } callback(null, '성공','1'); }, function (arg1, arg2, callback) { if (err) { callback('실패2'); } //arg1 : '성공', arg2 : 1 console.log(arg1, arg2); callback(null, '성공2'); }, function (arg1, callback) { if (err) { callback('실패3'); } //arg1 : '성공2' console.log(arg1); callback(null, '성공3'); }], function(err, result) { // waterfall 완료 콜백 if(err){ //err시 err난 테스크에서 전달한 err값을 전달받는다 console.log(err); } //모두 성공시, 마지막에 결과 값을 전달 받는다. //result : '성공3' console.log(reuslt); });




병렬 실행


parallel


parallel도 앞서 본 메서드들과 같이 task의 배열과 콜백을 인자로 받는다. 다만 순차 실행이 아닌 전달 받은 task들을 병렬, 즉 동시에 실행한다.


그리고 모든 테스크들이 전부 다 끝난 다음에서야 콜백 함수가 실행된다.

async.parallel([task1,task2,task3], function(err, results){
// parallel 완료 콜백
});


동시 실행이기 때문에 각 task들은 serise와 같이 독립적으로 실행되며, results에는 모든 함수의 결과가 저장된다.


메서드의 형태는 serise와 비슷하다.

async.parallel([
    function (callback) {
        if (err) {
            callback('실패1');
        }
        callback(null, '성공1');
    }, 
    function (callback) {
        if (err) {
            callback('실패2');
        }
        callback(null, '성공2');
    },
    function (callback) {
        if (err) {
            callback('실패3');
        }
        callback(null, '성공3');
    }],
    function(err, results) {
        // parallel 완료 콜백
        if(err){
            //err시 err난 테스크에서 전달한 err값을 전달받는다
            console.log(err);
        }
        //모두 성공시, 성공 값들을 전달받는다
        //예) ['성공1','성공2','성공3']
        console.log(reuslts);
});







node.js를 이용한 프로젝트를 진행하면서 비동기 처리에 따른 콜백 지옥을 벗어나고자 많은 고민이 있었다. 


async 모듈, 그리고 promise 만으로도 대부분의 콜백 지옥을 해소 할 수 있다고 하므로 반복해서 숙지하도록 노력해야겠다.

'Javascript > node.js' 카테고리의 다른 글

node.js 모듈  (0) 2016.03.30
node.js 시작하기  (0) 2016.03.01


[문제 링크]


발상적으로 좀 어려웠던 문제다. 최단거리를 구하는 것이기 때문에 BFS로 전체 경우를 계산에 풀 면 되는데.


처음엔 구슬들을 모두 벡터로 처리해 복잡해져서 고전했다. 


좌표가 겹치게 되는 구슬은 어차피 이후 행적은 모두 동일하기 때문에 그냥 SET으로 중복되는부분은 제거하는 방식으로 풀면 된다.



#include <iostream>
#include <string>
#include <queue>
#include <set>
using namespace std;
int t, n, m;
char maze[11][11];
typedef pair<int, int> coordi;
const char nextAc[4] = { 'L','R','U','D' }, reAc[4] = { 'R','L','D','U' };
const int relPos[4][2] = { { -1,0 },{ 1,0 },{ 0,-1 },{ 0,1 } };
coordi exitPos;
class Info {
public:
    set<coordi > pos;
    string ret;
    Info() { ret = ""; }
    Info(set<coordi > nSet, string nRet) {
        pos = nSet;
        ret = nRet;
    }
};
Info* Maze(Info in) {
    queue<Info> q;
    q.push(in);
    //BFS
    while (!q.empty()) {
        Info cur = q.front(); q.pop();
        char curAc = ' ';
        if (cur.ret.length() != 0) curAc = cur.ret[cur.ret.length() - 1];
        for (int i = 0; i<4; i++) {
            set<coordi> nextSet;
            //가지치기. 
            if (nextAc[i] != curAc && curAc != reAc[i]) {
                for (set<coordi>::iterator it = cur.pos.begin(); it != cur.pos.end(); it++) {
                    int px = (*it).first, py = (*it).second;
                    bool flag = true;
                    //다음 이동이 벽이기 전까지 이동. 혹은 현재 위치가 출구일때 까지
                    while (maze[py + relPos[i][1]][px + relPos[i][0]] != '#') {
                        px += relPos[i][0];
                        py += relPos[i][1];
                        if(maze[py][px] == 'O') flag = false;
                    }
                    if(flag) nextSet.insert(make_pair(px, py));
                }
                //다음 값이 현재 값과 일치하면 continue
                if (nextSet == cur.pos) continue;
                //set의 사이즈가 0이면 모두 탈출 -> 결과 객체 반환 
                if(nextSet.size() == 0){
                    return new Info(nextSet, cur.ret + nextAc[i]);
                }
                //현재 액션의 길이가 10이 넘어서면 NULL 반환 
                else if ((cur.ret + nextAc[i]).length() > 10) return NULL;
                q.push(Info(nextSet, cur.ret + nextAc[i])); 
            
            }
        }   
    }
    return NULL;
}
int main() {
    cin >> t;
    while (t--) {
        Info in;
        cin >> n >> m;
        for (int y = 0; y<n; y++) {
            for (int x = 0; x<m; x++) {
                cin >> maze[y][x];
                if (maze[y][x] == '.') in.pos.insert(make_pair(x, y));
                else if (maze[y][x] == 'O') {
                    exitPos = make_pair(x, y);
                }
            }
        }
        Info* retInfo = Maze(in);
        if (retInfo == NULL) cout << "XHAE" << endl;
        else cout << retInfo->ret << endl;
    }
}

'Algorithm > Problems' 카테고리의 다른 글

백준 - 10217 KCM Travel  (0) 2016.06.20
백준 - 10216 Count Circle Groups  (0) 2016.06.20
백준 - 10215 Colored Bead Work  (0) 2016.06.20
백준 - 1992 쿼드트리  (1) 2016.05.25
백준 - 10215 Colored Bead Works  (0) 2016.05.25


[문제 링크]



그래프 문제이다. 최단거리를 구하는 문제이기 때문에 다익스트라를 이용해서 풀 수 있다.


다만 가중치만 존재하는 것이 아니라 금액도 같이 주어지기 때문에 DP를 이용해서 풀어야한다.


특정 공항에 도착했을때 남은 금액과 가중치가 모두 다르기 때문이다.





#include <cstdio>
#include <vector>
#include <utility>
#include <queue>
#include <cstring>
#define MAXV 987654321
using namespace std;
int t, n, m, k,cache[102][10002];
class Info {
public:
    int v, x, d;
    Info(int v, int x, int d) :v(v), x(x), d(d) {
    }
};
struct cmp {
    bool operator()(Info x, Info y) {
        return x.d > y.d;
    }
};
vector<Info> adj[102];
int kcmTravel() {
    int ret = MAXV;
    priority_queue<Info, vector<Info>, cmp> pq;
    //memoization reset 
    memset(cache, -1, sizeof(cache));
    cache[1][0] = 0;
    pq.push(Info(1, 0, 0));
    while (!pq.empty()) {
        Info cur = pq.top(); pq.pop();
        //현재 저장된 가중치보다 크거나 비용이 초과하면  continue 
        if (cur.d > cache[cur.v][cur.x] || cur.x > m) continue;
        //인접리스트 체크 
        for (int i = 0; i < adj[cur.v].size(); i++) {
            Info tmp = adj[cur.v][i];
            int acCost = tmp.x + cur.x;
            if ((cache[tmp.v][acCost] == -1 || cache[tmp.v][acCost] > cur.d + tmp.d)&& cur.x + tmp.x <= m) {
                cache[tmp.v][acCost] = cur.d + tmp.d;
                pq.push(Info(tmp.v, acCost, cur.d + tmp.d));
            }
        }
    }
    //도착지의 최소 비용을 구한다. 
    for (int i = 0; i <= m; i++) {
        if (cache[n][i] != -1 && ret>cache[n][i])
            ret = cache[n][i];
    }
    return ret;
}
int main() {
    scanf("%d", &t);
    while (t--) {
        //인접리스트 초기화 
        for (int i = 0; i <= 101; i++) adj[i].clear();
        int u, v, x, d;
        //input 
        scanf("%d %d %d", &n, &m, &k);
        for (int i = 0; i<k; i++) {
            scanf("%d %d %d %d", &u, &v, &x, &d);
            adj[u].push_back(Info(v, x, d));
        }
        int tmp = kcmTravel();
        printf(tmp == MAXV ? "Poor KCM\n" : "%d\n", tmp);
    }
}

'Algorithm > Problems' 카테고리의 다른 글

백준 - 10218 Maze  (1) 2016.06.20
백준 - 10216 Count Circle Groups  (0) 2016.06.20
백준 - 10215 Colored Bead Work  (0) 2016.06.20
백준 - 1992 쿼드트리  (1) 2016.05.25
백준 - 10215 Colored Bead Works  (0) 2016.05.25

+ Recent posts