일단 씻고 나가자

Integer.toString() / String.valueOf() , Stream map() / mapToObj() 본문

Language/Java

Integer.toString() / String.valueOf() , Stream map() / mapToObj()

일단 씻고 나가자 2025. 3. 25. 16:11

사건 발생

코딩 테스트를 풀다가 int 배열을 String 배열로 바꿀 때 에러가 났다.
 

 
IDE로 코드를 옮겨보니 mapToObj 에서 에러가 나더라.
보니까 아예 boxed() 이후에는 mapToObj가 아니라 map이 들어가야 했다.
 
 
 
 
 
그래서 문득 mapToObj, map 두 메서드가 있는 걸 생각해냈고, 다음과 같이 고쳐봤다.
 

 
 
 
 
 
도무지 이해가 가지 않는 에러. 그래 나랑 한번 해보자 하고 이렇게도 고쳐봤다.
 

 
 
 
 
 
컴파일 에러는 무조건 나의 무지 탓이니라.
찾아 보니 Integer::toString / String::valueOf 및 map / mapToObj 의 차이를
알지도 못하고 사용하고 있었다.
 
+ 이거 이해하는 데 .
 
 
 
 
 

Integer.toString() / String.valueOf()

둘 다 정수형을 문자형으로 파싱할 때 사용된다.
(valueOf 는 정수 말고도 다른 데이터 형도 가능)
 
 
 
 
 
먼저 Integer.toString() 이다.
 

 
내용은 확인할 필요 없고,
메서드 시그니쳐를 확인해보면 int 를 매개 변수로 받고 있다.
 
 
 
 
 
다음은 String.valueOf() 이다.
 

 
내부적으로 Integer의 toString()을 활용하고 있다.
따라서 두 메서드는 다른 것이 아닌, 같은 성능과 결과를 내는 것이라고 보면 된다.
 
 
 


 
 
 
어 그러면 valueOf 를 사용하면 toString 을 또 호출하니까 메모리나 성능에 손해가 있나?
JVM의 JIT(Just In Time) 컴파일러가 '인라이닝'으로 최적화 시점에서 이를 방지해준다.
 
 
인라이닝이란 메서드 호출을 삭제하고 메서드 내용을 바로 삽입하는 과정이다.

 
예를 들어 이렇다.
보통 함수를 호출하면 JVM은 스택 프레임을 생성하고, 함수 종료 시 리턴하는데,
함수를 코드에 직접 박아 최적화하는 기법이다.
 
 
모든 함수가 인라이닝 되진 않으며,
다음과 같은 조건 하의 메서드가 주로 인라이닝 된다.

  • 크기가 작은 메서드
  • 자주 호출되는 메서드 (핫스팟 메서드)
  • 재귀 호출 X
  • 동적 바인딩 X (static, final, private 유리 - 호출 대상이 변하지 않음)

 
 
 


 
 
 
 
 

Stream map() / mapToObj()

둘 다 특정 형태의 변수를 다른 형태의 변수로 바꿔주나,
하나하나씩 차근차근 살펴볼 필요가 있다.
 
 
 


 
 
 
먼저 직접 두 메서드를 비교하기 전에,
Stream이 제공하는 자료구조 형태를 살펴보아야 한다.
 
 
int(Integer)를 기준으로, Stream은 IntStream / Stream<Integer> 두 가지 형태를 제공하는데,
 
Stream<?> 형태는 어떤 객체가 오더라도 stream의 자료구조 안에 넣는 형태이고,
IntStream은 premitive 타입을 자료구조로 다루는 형태이다. (DoubleStream 등이 따로 있음)
 
 
 
왜 이렇게 나눠놨냐? 당연히 성능 때문이다.
 
int, long, double 등 premitive 타입은 객체 연산을 위해 wrapper를 제공하지만,
boxing 과정에서 불필요한 자원이 들게 된다.
 
또한 premitive 타입이 보장되기 때문에
sum() / average() / max() / min() 등의 숫자 연산 메서드를 제공해주고,
객체 변환을 위해 boxed() / mapToObj() 메서드도 제공해준다.
 
 
 


 
 
 

map()

map() 부터 살펴보자.
map() 은 Stream<객체> , PremitiveStream 둘 모두에서 사용할 수 있는 메서드이다.
 
 
 
 
 
먼저 Stream은
Arrays.stream(어떤 배열) 같은 형태로 특정 객체 컬렉션을 stream으로 변환할 때,
해당 배열의 타입에 따라 알아서 반환 값을 정해준다.
 

Stream<객체>PremitiveStream

 
왼쪽 그림과 같이 어떤 객체를 stream으로 반환하면 Stream<객체> 형태로 반환하고,
오른쪽 그림과 같이 premitive type을 stream으로 반환하면 PremitiveStream 형태로 반환한다.
 
 
* 이때 IntStream의 형태를 .boxed() 로 boxing 하게 되면
PremitiveStream -> Stream<Integer> 으로 형태가 바뀌게 되는데,
이때 {1, 2, 3} 의 기본형 배열이 {Integer(1), Integer(2), Integer(3)} 처럼 객체 형태로 개별 원소가 바뀌게 된다.
 
 
 
 
 
자, 그러니까 Stream 이 두 가지 형태를 가졌다는 건 알았고,
이젠 두 형태의 map() 이 다른 반환값을 가진다는 걸 알아야 한다.
 

Stream<객체>PremitiveStream

 
이와 같이 어떤 Stream의 형태였냐에 따라 같은 map() 메서드라도 다른 반환값을 가지는 것을 볼 수 있으며,
 
 
 
따라서 PremitiveStream 에서 map(다른 객체로 변환) 의 메서드는 사용할 수 없고
오로지 IntStream 만을 반환하는 로직만 사용할 수 있다는 것을 알 수 있다.
 

 
 
 
 
 
우리의 목표는 int(Integer) 를 String 으로 바꾸는 것.
앞의 과정을 통해 IntStream.map() 은 못 쓴다는 걸 알았고,
 
그럼 .boxed() 를 통해 IntStream 을 Stream<Integer> 으로 바꾸어 사용하면 어떨까?
Stream<객체> 형태로 바뀌기 때문에 이 방법은 논리적으로 오류가 없다!!!!!!!
 
 
 

 
하지만 오류는 있다.
껄껄 쉽게 될 줄 알았니?
근데 사람 미치게 만드는 게 또 이거는 된다.
 
 
 

 

 
 
 
 
 
Integer::toString 만 안 되는 이유는 뭘까??
그것은 Integer 는 내부적으로 2 개의 toString() 을 가지고 있기 때문이다.
 

 
 
 
 
 
Stream 에서의 람다 추론은 기본적으로 다음 두 가지를 할 수 있다.
 

  • 매개 변수가 0개 혹은 1개일 때 알아서 어떻게 활용할지 판단한다.
  • static 메서드를 실행할 것인지, 인스턴스 메서드를 실행할 것인지 판단한다.

 
그러니까 클래스::메서드 형태에서, 메서드() / 메서드(stream 원소) 두 가지를 알아서 판단해준다는 뜻이다.
 
 
 
 
 
당연히 뭔 소린지 와닿지 않는다. 예를 들어보자.
 

 
메서드가 인스턴스/static 이냐, 매개변수가 있냐/없냐 가 달라졌음에도
같은 코드로 Stream.map() 메서드 호출은 정상 실행된다.
 
 
 
그러니까 Stream 이 [클래스::메서드] 형태로 해당 클래스의 메서드를 실행하려고 할 때,
 

  1. 번째 case) Stream 이 순회하는 개별 any 객체가 forString() 을 실행하도록 해야겠다.
  2. 번째 case) Stream 이 순회하는 개별 any 객체를 Any.forString() static 메서드의 매개변수에 넣어야겠다.

이 두 가지 경우를 알아서 추론하여 실행해준다는 것이다.
 
 
 
 
 
다시 Integer::toString 으로 돌아가면 어떨까?
IntStream 의 경우, 순회하는 원소들도 int(Integer), toString(int i) 의 매개변수도 int 이다.
 
 
그럼 컴파일러는 혼란에 빠진다.
 

  1. 번째 case) Stream 이 순회하는 개별 Integer 의 객체.toString() 을 실행 해야하나?
  2. 번째 case) Stream 이 순회하는 개별 int 를 Integer.toString(int i) 메서드의 매개변수에 넣어야하나?

 
따라서 IntStream.boxed().map(Integer::toString) 은 컴파일 에러를 낼 수밖에 없고,
이는 boxed() 를 통해 개별 int 가 객체로 진화했기 때문에 벌어진 일이다.
 
 
 
 
 

mapToObj()

mapToObj() 는 어떨까?
mapToObj() 는 심플하게, PremitiveStream 에서만 사용 가능하고, Stream<객체> 에 사용할 수 없다.
 
왜인지 추론하자면 Premitive 타입 자료형을 객체로 만들어주기 위해 map 대신 다른 메서드를 구현한 것 같고,
따라서 mapToObj 는 객체로 매핑한다는 메서드 명처럼 이미 Object 인 Stream 에는 사용하지 못하는 것이다.
 
 
Premitive 타입에서만 사용 가능하니까 IntStream 이지 Stream<Integer> 가 아니고,
즉, Stream 내부에선 Integer 가 아닌 int 원소들이 돌아다닐 것이므로,
 
얘네는 객체가 아니니까 위에서의 map() 의 경우처럼
멤버 메서드인지 static 메서드인지 헷갈릴 필요 없이 무조건 static toString(int i) 가 선택될 것이다.
 
 
 
따라서 mapToObj() 의 매개 변수로, String::valueOf 는 물론, Integer::toString 또한 사용할 수 있다 ^-^
 
 
 
 
 
+ 추가로, boxed() 역시 PrimitiveStream 에서만 사용 가능하다.
mapToObj() 의 의미와 비슷하게, primitive 를 wrapper 로 바꿔주는 역할을 위해 존재하는 메서드 같다.
 

  • IntStream.boxed()         -> int 를 Integer 으로
  • IntStream.mapToObj()   -> int 를 다른 객체로

 
이렇기 때문에 boxed() 를 한 순간 자연스럽게 int 가 아닌 일반 객체와 같은 취급을 받게 되고,
따라서 boxed() 으로 객체 변환을 했는데 굳이 mapToObj() 로 객체 변환을 할 필요가 없어 사용할 수 없다.
 
반대도 마찬가지, mapToObj() 로 이미 객체인 원소를 boxed() 로 객체로 바꿔줄 이유가 없기 때문에,
boxed().mapToObj() / mapToObj().boxed() 둘 다 불가능한 것이다.
 

 
 

정리

아리송한 여러분을 위해,
그리고 금붕어 머리통의 미래의 나를 위해 총정리 가겠다.
 
 
 
Stream 의 자료구조는 두 가지

  • Stream<?>
  • PremitiveStream

 
 
 
Stream 에서 람다 추론은 두 가지

  • 매개 변수가 없는, 개별 원소의 인스턴스 메서드 실행
  • 매개 변수가 하나, 개별 원소를 static 메서드의 매개변수에 넣어 실행

 
 
 
각 구조마다 쓸 수 있는 메서드와 의미는 다음과 같음

\map()mapToObj()boxed()
같은 형태의 Stream 반환Object 형태의 Stream 반환Wrapper 형태의 Stream 반환
PremitiveStream
PremitiveStream

-> PremitiveStream

PremitiveStream

-> Stream<Object>
PremitiveStream

-> Stream<Wrapper>
Stream<Object>
Stream<Object>

-> Stream<Object>

XX

 
 
- Integer::toString 은 Stream<Wrapper> 에서 Integer -> String 변환 시에만 toString() 때문에 문제가 생김
 
 
 
 
 

마치며

멍청해서 하루종일 검색해야 이해할 수 있었다.
(애꿎은 GPT 에게 느낌표를 남발하며 공격해서 미안하다..)
 
Stream 과 람다의 개념도 부족했는데, toString 의 에러 때문에 더 화가 나버린 것.
 
근데 덕분에 항상 공부해야지.. 공부해야지.. 했던 Stream 과 람다의 추론 방식에 대해 알게 된 것 같아
아주 아주 기분이 좋다.
앞으로 관련 개념을 봐도 속으로 우쭐댈 수 있을 것.
 
나를 죽이지 못하는 고통은 나를 강하게 만들 것이다.. (육군임)
 
 
 

'Language > Java' 카테고리의 다른 글

업 캐스팅 / 다운 캐스팅 (UpCasting / DownCasting)  (0) 2024.07.30