[LG CNS 6기] 본 과정 15일차 TIL / [백엔드] - Collection, Generics, Stream, 함수형 인터페이스

김승진·2026년 8월 18일

LG CNS AM 6기 TIL

목록 보기
24/46

1. 오늘의 한 줄 요약

Collection, Generics, Stream, 람다

2. 배운 내용

2.1 지난 OOP 복습

본격적으로 새 내용에 들어가기 전에 OOP의 은닉화(싱글톤 패턴), 상속, 다형성(변수타입/매개변수/메서드), 추상화(추상클래스/인터페이스)까지 흐름을 한 번 정리하고 시작했다.

2.2 Collection API - List, Set, Map

배열 대신 실무에서 많이 쓰는 게 Collection API라고 한다.
배열을 확장시켜놓은 자료구조 느낌이라고 설명하셨다.

  • List — 중복 허용 O , 순서 존재 O , 요소는 객체만, 가변길이
  • Set — 중복 허용 X, 순서 존재 X, 요소는 객체만, 가변길이
  • Map — {key : value} 형태
List<String> list = new ArrayList<String>();
list.add("String");
Set<String> set = new HashSet<>();
set.add("hong");
set.add("inspire");
set.add("lgcns");
set.add("hong"); // 중복이라 무시됨

DB로 치면 table 하나가 행과 열로 이루어진 객체인 것처럼, List에 DTO를 담으면 그게 테이블의 여러 행(row)을 담는 것과 비슷한 느낌이다.

List에는 상속 관계의 DTO들도 그대로 담긴다.

List<PersonDTO> personList = new ArrayList<PersonDTO>();
personList.add(student);
personList.add(teacher);
personList.add(manager);

StudentDTO/TeacherDTO/ManagerDTO가 다 PersonDTO를 상속받고 있어서 가능한 건데, 저번에 배운 매개변수의 다형성(업캐스팅)이 List에도 똑같이 적용되는 것이다.

2.3 Boxing/Unboxing과 Wrapper Class

Collection은 객체만 담을 수 있어서, 기본 타입을 넣으려면 Wrapper Class로 자동 변환된다.

  • Boxing: primitive type → reference type (예: int → Integer)
  • Unboxing: reference type → primitive type
  • char → Character도 같은 방식

2.4 Generics - 타입 파라미터

<> 안에 타입을 미리 정해두는 문법이다. 이걸 안 쓰면 꺼낼 때마다 캐스팅이 필요한데, 타입을 지정하면 캐스팅이 사라진다.

컬렉션 관련해서 관례적으로 쓰는 문자들:

  • T - type
  • E - element (컬렉션의 요소, 컴파일 시점 타입 안정성 + 불필요한 캐스팅 지양이 목적)
  • K - key
  • V - value
  • N - number

제네릭 클래스도 만들어봤다.

public class ResponseTemplate<T> {
    private int code;
    private String message;
    private T data;
    ...
}
// 입력성공 : 201, CREATED, xxxDTO
// 목록조회 : 200, OK, list
ResponseTemplate<List<PersonDTO>> response =
    new ResponseTemplate<List<PersonDTO>>(200, "OK", personList);

응답 코드(201/200)랑 메시지(CREATED/OK)를 상황별로 다르게 담아서 돌려주는 껍데기를 하나 만든 셈이다. T 자리에 뭐가 들어가든 같은 구조로 응답을 감쌀 수 있다.

2.5 제네릭 와일드카드 (extends vs super)

메서드 매개변수 타입 선언이나 리턴 타입 지정할 때 자주 쓰는 문법이라고 배웠다.

  • ? extends T — 읽기 전용 (T의 하위 타입)
  • ? super T — 쓰기 전용 (T의 상위 타입)
Map<String, List<? extends PersonDTO>> map = new HashMap<>();
map.put("student", studentList);
map.put("teacher", teacherList);
map.put("manager", managerList);

StudentDTO 리스트, TeacherDTO 리스트, ManagerDTO 리스트를 하나의 Map에 다 담으려니 그냥 List<PersonDTO>로는 타입이 안 맞아서 와일드카드가 필요했다. ? extends PersonDTO라고 하면 "PersonDTO거나 그 자식 타입"이라는 뜻이라 셋 다 넣을 수 있었다.

2.6 명령형 처리 vs 선언적 처리 (Stream API)

지금까지 짜던 for문 방식은 명령형 처리 방식이다.
"어떻게" 할지를 하나하나 다 적는 방식이다.

// 명령형 처리
List<String> filteringList = new ArrayList<String>();
for (int idx = 0; idx < personList.size(); idx++) {
    PersonDTO person = personList.get(idx);
    if (person.getName().startsWith("j")) {
        filteringList.add(person.getName().toUpperCase());
    }
}

Stream API는 선언적 처리 방식이다. "무엇을" 원하는지만 적으면 된다.

// Stream API (선언적 처리)
// 컬렉션을 Stream 객체로 바인딩하고 - 중간연산(0~N) - 연산결과를 최종연산(1)
List<String> filteringList = personList.stream()
    .filter(s -> s.getName().startsWith("j"))
    .map(s -> s.getName().toUpperCase())
    .collect(Collectors.toList());

두 코드 결과는 같지만, 아래쪽은 인덱스도 없고 for문도 없다.
가독성, 병렬처리, 유지보수 측면에서 Stream이 낫다.

2.7 함수형 인터페이스

인터페이스가 가질 수 있는 메서드가 딱 하나인 것을 함수형 인터페이스라고 한다. 람다식을 쓰려면 이 형태여야 한다.

@FunctionalInterface
public interface InspireFunction {
    public int max(int x, int y);
}
InspireFunction func01 = (x, y) -> x > y ? x : y;
System.out.println(func01.max(100, 200)); // 200

InspireFunction func02 = (x, y) -> x + y;
System.out.println(func02.max(100, 200)); // 300

같은 인터페이스인데 대입하는 람다에 따라 동작이 완전히 달라진다는 게 신기했다. 메서드 이름은 max인데 func02는 실제로는 덧셈을 하고 있다.

자바 표준 함수형 인터페이스 4가지:

인터페이스매개변수반환타입메서드
SupplierXOget()
ConsumerOXaccept()
FunctionOOapply()
PredicateOBooleantest()
Supplier<String> supplier = () -> "inspire";
System.out.println(supplier.get());
Consumer<String> consumer = (str) -> System.out.println(str.split(" ")[0]);
consumer.andThen(System.out::println).accept("lgcns inspire");

andThen은 Consumer 두 개를 이어붙여서 같은 입력값을 순서대로 각각 처리하게 만드는 역할을 한다. 위 코드는 consumer가 먼저 첫 단어만 출력하고, 이어붙인 System.out::println이 원본 문자열 전체를 한 번 더 출력한다.

System.out::println처럼 타입::메서드명 형태로 쓰는 걸 메서드 참조라고 하는데, (x) -> System.out.println(x)를 줄여쓴 것과 같다.

2.8 Stream API 정리

  • 코드의 가독성, 병렬처리, 유지보수 향상
  • 원본 데이터(Collection)는 손상되지 않음
  • 일회성이라 한 번 최종연산을 거치면 다시 못 쓴다
List<String> brands = Arrays.asList("samsung", "lg", "lgcns", "inspire", "camp", "6th");

Stream<String> stream = brands.stream();
stream.forEach((brand) -> System.out.println(brand));

// 메서드 참조방식
stream = brands.stream();
stream.forEach(System.out::println);

3. 오늘의 회고

  • 느낀 점: 3일동안 연휴여서 그런가, 수업에 집중이 좀 어려웠다. 개념도 새로운 개념도 많고 해서 더 찾아봐야 할 것 같다. 그리고 Oracle 공식 문서도 보는 습관을 들여야겠다.
  • 다음에 할 것: 학습내용 정리 및 코드 재분석

#LGCNS #LGCNS6기 #개발자 #LGCNSINSPIRECAMP

profile
이것저것

0개의 댓글