ABAP

CREATE OBJECT 금지? NEW 인스턴스 생성법 #shorts #SAP #ABAP

▶ YouTube에서 보기

이 글이 답하는 질문

  • CREATE OBJECT와 NEW 연산자, 무엇이 다른가?
  • 기존 코드를 NEW로 리팩토링하려면 어떻게 해야 할까?
  • 예외 처리, 팩토리 패턴, 단위 테스트에서 NEW를 어떻게 활용하나?
  • NEW #(...)으로 타입 추론이 실패하는 이유는?

이 글을 보기 전에

이 글은 ABAP 객체지향 기본 개념을 이미 알고 있는 개발자를 대상으로 합니다. 클래스(CLASS), 인스턴스 메서드, 생성자(CONSTRUCTOR), 참조 변수(TYPE REF TO)의 의미를 알고 있다면 바로 따라올 수 있습니다. ABAP 740 이상에서 추가된 인라인 선언(DATA(...))에 친숙하면 NEW 연산자의 장점을 더 잘 느낄 수 있습니다.

테스트 환경

  • SAP NetWeaver AS ABAP 7.40 SP02 이상 (S/4HANA 전 릴리스 포함)
  • SAP BTP ABAP Environment (Steampunk) — 클라우드 ABAP에서 권장 문법
  • ABAP Development Tools (ADT) for Eclipse — 인라인 선언 자동완성 지원

실습을 위해 자신의 패키지에 임시 클래스(예: ZCL_DEMO_CAR)를 생성할 권한이 필요합니다. SAP GUI 기반 SE80도 가능하지만 ADT 사용을 권장합니다.

핵심 개념

NEW 연산자는 "타입이 정해진 새 객체를 표현식으로 만들어 반환"하는 인스턴스 생성 연산자입니다. 기존 CREATE OBJECT가 "선언과 생성을 분리"했다면, NEW는 "선언과 생성을 한 줄로 결합"합니다.

비유하자면, CREATE OBJECT가 "빈 박스를 먼저 만들고 내용물을 채워 넣는 2단계 조립"이라면, NEW는 "공장에서 완제품을 받아 바로 자리에 놓는 1단계 배송"입니다.

기본 구문 비교는 다음과 같습니다.

" 구문법: CREATE OBJECT
DATA lo_car TYPE REF TO zcl_car.
CREATE OBJECT lo_car
  EXPORTING
    iv_model = 'Sonata'
    iv_year  = 2026.

" 신문법: NEW 연산자
DATA(lo_car) = NEW zcl_car( iv_model = 'Sonata'
                            iv_year  = 2026 ).

NEW 연산자의 핵심 동작 규칙은 다음과 같이 정리할 수 있습니다.

  1. 타입 지정 방식: NEW class_name( ... )로 명시하거나, 좌변이 이미 타입을 가지고 있으면 NEW #( ... )처럼 #을 써서 컨텍스트로부터 타입을 추론할 수 있습니다.
  2. 반환값: NEW 연산자는 새로 만든 객체를 참조하는 데이터 참조를 반환합니다. 좌변이 클래스 참조면 자동으로 캐스팅되어 할당됩니다.
  3. 생성자 파라미터: 괄호 안에 IMPORTING 파라미터를 키=값 형태로 전달합니다. CREATE OBJECT의 EXPORTING 키워드가 사라져 더 간결합니다.
  4. 표현식 위치: 메서드 인자, 테이블 행, RETURN 문 등 표현식이 들어갈 수 있는 모든 위치에서 사용 가능합니다.

직접 해보기

1. 기본 예제 — CREATE OBJECT를 NEW로 바꾸기

간단한 자동차 클래스로 두 문법의 차이를 직접 비교해 봅니다.

CLASS zcl_car DEFINITION PUBLIC FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    METHODS constructor
      IMPORTING iv_model TYPE string
                iv_year  TYPE i.
    METHODS describe
      RETURNING VALUE(rv_text) TYPE string.
  PRIVATE SECTION.
    DATA mv_model TYPE string.
    DATA mv_year  TYPE i.
ENDCLASS.

CLASS zcl_car IMPLEMENTATION.
  METHOD constructor.
    mv_model = iv_model.
    mv_year  = iv_year.
  ENDMETHOD.

  METHOD describe.
    rv_text = |{ mv_year } { mv_model }|.
  ENDMETHOD.
ENDCLASS.
" Before (구문법)
DATA lo_old TYPE REF TO zcl_car.
CREATE OBJECT lo_old
  EXPORTING
    iv_model = 'Avante'
    iv_year  = 2026.
WRITE / lo_old->describe( ).

" After (신문법)
DATA(lo_new) = NEW zcl_car( iv_model = 'Avante' iv_year = 2026 ).
WRITE / lo_new->describe( ).

" 좌변에 타입이 이미 있을 때는 # 사용
DATA lo_typed TYPE REF TO zcl_car.
lo_typed = NEW #( iv_model = 'Grandeur' iv_year = 2026 ).

코드 라인 수가 절반 가까이 줄어듭니다. 특히 인라인 선언 DATA(...)과 결합하면 변수 선언과 인스턴스화가 한 문장에 끝나, 흐름을 따라가기가 훨씬 수월해집니다.

2. 실무 시나리오 — 컬렉션과 예외 처리

실무에서는 여러 인스턴스를 한꺼번에 생성해 내부 테이블에 담는 경우가 많습니다. NEW 연산자는 VALUE 연산자와 자연스럽게 조합됩니다.

TYPES: BEGIN OF ty_car_ref,
         car TYPE REF TO zcl_car,
       END OF ty_car_ref.
TYPES tt_car_ref TYPE STANDARD TABLE OF ty_car_ref WITH EMPTY KEY.

DATA(lt_cars) = VALUE tt_car_ref(
  ( car = NEW zcl_car( iv_model = 'Sonata'    iv_year = 2024 ) )
  ( car = NEW zcl_car( iv_model = 'Avante'    iv_year = 2025 ) )
  ( car = NEW zcl_car( iv_model = 'Grandeur'  iv_year = 2026 ) ) ).

LOOP AT lt_cars INTO DATA(ls_row).
  WRITE / ls_row-car->describe( ).
ENDLOOP.

생성자가 예외를 던질 수 있는 경우, NEW 연산자도 동일하게 TRY ... CATCH로 감싸야 합니다. 로깅을 함께 적용한 예시는 다음과 같습니다.

CLASS zcx_car_invalid DEFINITION INHERITING FROM cx_static_check.
ENDCLASS.

CLASS zcl_car DEFINITION ... .
  PUBLIC SECTION.
    METHODS constructor
      IMPORTING iv_model TYPE string
                iv_year  TYPE i
      RAISING   zcx_car_invalid.
ENDCLASS.

" 호출부
TRY.
    DATA(lo_car) = NEW zcl_car( iv_model = ''  " 잘못된 입력
                                iv_year  = 2026 ).
  CATCH zcx_car_invalid INTO DATA(lx_error).
    cl_demo_output=>display( |생성 실패: { lx_error->get_text( ) }| ).
ENDTRY.

이 패턴은 RAP의 비관리형(Unmanaged) 시나리오나 OData 서비스 핸들러에서 도메인 객체를 생성할 때 자주 등장합니다.

3. 프로덕션 — 팩토리, 의존성 주입, 단위 테스트

대규모 코드에서는 NEW를 호출부에 직접 쓰기보다 팩토리 메서드 안에 가두는 편이 테스트와 유지보수에 유리합니다. 팩토리는 인터페이스 참조를 반환하고, 내부에서 NEW로 구체 클래스를 만들어 돌려줍니다.

INTERFACE zif_vehicle.
  METHODS describe RETURNING VALUE(rv_text) TYPE string.
ENDINTERFACE.

CLASS zcl_car DEFINITION ...
  PUBLIC SECTION.
    INTERFACES zif_vehicle.
    ALIASES describe FOR zif_vehicle~describe.
ENDCLASS.

CLASS zcl_vehicle_factory DEFINITION PUBLIC FINAL CREATE PRIVATE.
  PUBLIC SECTION.
    CLASS-METHODS create_car
      IMPORTING iv_model      TYPE string
                iv_year       TYPE i
      RETURNING VALUE(ro_obj) TYPE REF TO zif_vehicle
      RAISING   zcx_car_invalid.
ENDCLASS.

CLASS zcl_vehicle_factory IMPLEMENTATION.
  METHOD create_car.
    ro_obj = NEW zcl_car( iv_model = iv_model
                          iv_year  = iv_year ).
  ENDMETHOD.
ENDCLASS.

호출부는 구체 클래스를 알 필요가 없고, 단위 테스트에서는 팩토리를 더블(test double)로 대체할 수 있어 결합도가 낮아집니다. ABAP Unit으로 검증하는 코드는 다음과 같이 작성합니다.

CLASS ltcl_car DEFINITION FINAL FOR TESTING
  DURATION SHORT RISK LEVEL HARMLESS.
  PRIVATE SECTION.
    METHODS describe_returns_formatted FOR TESTING.
ENDCLASS.

CLASS ltcl_car IMPLEMENTATION.
  METHOD describe_returns_formatted.
    DATA(lo_car) = NEW zcl_car( iv_model = 'Avante' iv_year = 2026 ).
    cl_abap_unit_assert=>assert_equals(
      act = lo_car->describe( )
      exp = '2026 Avante' ).
  ENDMETHOD.
ENDCLASS.

성능 관점에서는 NEW와 CREATE OBJECT의 런타임 비용이 일반적으로 동일합니다. 컴파일 시점에 같은 바이트코드로 번역되기 때문입니다. 보안 관점에서는 인스턴스 생성 권한을 CREATE PUBLIC / CREATE PROTECTED / CREATE PRIVATE로 제어할 수 있으며, NEW 연산자는 이 가시성 규칙을 그대로 따릅니다. CREATE PRIVATE 클래스를 외부에서 NEW로 만들 수는 없습니다.

삽질 노트

Q1. "Type 'zcl_xxx' cannot be used after NEW" 컴파일 오류가 납니다.
해당 클래스가 CREATE PRIVATE이거나 추상(ABSTRACT) 클래스로 정의돼 있을 가능성이 높습니다. NEW는 인스턴스화 가능한 구체 클래스에서만 사용할 수 있습니다. 클래스 정의의 CREATE PUBLIC 여부를 확인하거나, 팩토리 메서드를 통해 우회하세요.

Q2. NEW #( ... )를 썼는데 "Type cannot be derived" 오류가 발생합니다.
#은 좌변(또는 컨텍스트)에 명시적인 타입이 있을 때만 동작합니다. DATA(lo) = NEW #( ... )처럼 인라인 선언 좌변이 비어 있는 경우에는 타입을 추론할 근거가 없어 실패합니다. NEW zcl_car( ... )처럼 클래스를 명시하거나, 미리 타입이 있는 변수에 할당해야 합니다.

Q3. 인터페이스 참조를 받고 싶은데 NEW 다음에 인터페이스를 적을 수 있나요?
적을 수 없습니다. NEW 뒤에는 항상 인스턴스화 가능한 구체 클래스 이름만 옵니다. 다만 좌변이 인터페이스 참조이면 NEW로 만든 구체 객체가 자동으로 인터페이스 참조로 캐스팅됩니다. 예: DATA lo TYPE REF TO zif_vehicle. lo = NEW zcl_car( ... ).

  • EXPORTING 키워드 습관: NEW 괄호 안에는 EXPORTING을 쓰지 않습니다. 키=값만 나열합니다.
  • 생성자 시그니처 변경 시 일괄 검색: 인라인 NEW가 코드 곳곳에 흩어져 있으면 파라미터명을 바꿀 때 영향 범위가 넓어집니다. ADT의 "Where-Used"를 적극 활용하세요.
  • 가독성 vs 단축: 한 줄에 NEW를 여러 번 중첩하면 디버깅이 어려워집니다. 중첩이 2단 이상이면 변수로 분리하는 편이 가독성에 좋습니다.

더 파볼 주제

  • VALUE 연산자: 구조체와 내부 테이블을 인라인으로 채우는 표현식. NEW와 가장 자주 조합됩니다.
  • CAST / CONV 연산자: 참조 타입 다운캐스팅과 값 변환을 표현식으로 처리.
  • FOR 표현식과 REDUCE: 컬렉션 생성과 집계를 함수형 스타일로 작성.
  • RAP 비즈니스 객체: 핸들러 클래스 내부에서 NEW로 도메인 엔티티를 인스턴스화하는 패턴.
  • 의존성 주입과 ABAP Unit Test Double Framework: 팩토리로 분리한 NEW를 테스트에서 어떻게 대체할지 학습.

더 읽어볼 자료

핵심 한 줄

NEW 연산자는 CREATE OBJECT의 2단계 조립을 한 줄짜리 표현식으로 줄여, ABAP 코드를 더 짧고 읽기 쉽게 만든다.

댓글 0

아직 댓글이 없습니다.