자바 8에서 A 데이터를 B 데이터로 변환하는 방법은?

2017-10-18 10:47

코드스쿼드 교육을 시작했다. 이번 교육은 기존의 강의 주도 방식과는 다르게 코드 리뷰가 주이다. 앞으로 코드 리뷰 과정에서 나오는 간단한 예제 코드를 활용해 개선 방법을 찾아본다.

"pobi,crong,honux"와 같은 형태로 쉼표를 구분자로 가지는 문자열을 전달하면 3개의 Car 인스턴스를 생성하면서 이름으로 사용한다.

Car는 다음과 같다.

public class Car {
	private String carName;
	private int position;
	
	public Car(String carName) {
		super();
		this.carName = carName;
	}
	public String getCarName() {
		return carName;
	}
	public int getPosition() {
		return position;
	}
}

문자열을 Car로 변환하는 메소드는 다음과 같다.

        private List<Car> parseCar(String fullString){
		List<String> carNames = Arrays.asList(fullString.split(","));
		List<Car> carList = new ArrayList<>();
		for(int i=0; i<carNames.size(); i++) {
			carList.add(new Car(carNames.get(i)));
		}
		return carList;
	}

위 방법은 지금까지 일반적으로 사용하던 방법이다. 위 코드를 자바 8에 추가된 stream과 map을 활용해 리팩토링한다면...

자바 8의 stream과 map을 사용하는 것보다 더 좋은 리팩토링 방법이 있을까?

2개의 의견 from FB

7개의 의견 from SLiPP

2017-10-18 11:25

자바 8의 스트림과 맵을 활용해 아래와 같이 바꿔봤습니다.

private static List<Car> parseCar(String fullString){
        List<String> carNames = Arrays.asList(fullString.split(","));
        List<Car> newCarList = carNames.stream().map(name -> new Car(name)).collect(Collectors.toList());
        return newCarList;
    }

이런 경우를 위해서라면 enhanced for를 사용하는 것도 충분히 나쁘지 않은 선택이라고 생각됩니다만, 이 구현을 통해 자바 8의 스트림에 대해 학습을 시작할 수 있는 계기가 된 것 같습니다.

2017-10-18 11:50

앞 댓글의 정휘준 친구가 학습한 내용을 공유해 봅니다.

처음 스트림을 통해 구현을 하면서, carNames.stream().forEach(name -> new Car(name)); 과 같이 접근하면 안되나? 라는 생각을 가져봤었습니다.

그러나 List<Car>carList = carNames.stream().map(name -> new Car(name).collect(Collectors.asList)); 와 같은 구현이 forEach에서 불가능하다는 걸 알게됐습니다.

책에 이런 표현이 있군요, 이럴 때는 스트림의 값 자체를 변환해 버리는 map()을 사용하자.

map()과 forEach()에 분명한 차이가 있군요. forEach()는 스트림을 통해 객체를 꺼내주긴 하지만, 그 값을 직접 변환하진 못합니다. 그래서 .collect() 메소드를 사용할 수 없네요.

2017-10-18 11:53
private static List<Car> parseCar(String fullString){
	List<String> carNames = Arrays.asList(fullString.split(","));
	return carNames.stream().map(car -> new Car(car)).collect(Collectors.toList());
}

이렇게 구현해봤습니다. java doc에 collect의 리턴 타입이 <R,A> R으로 되어있는데, 이렇게 바로 리턴해줘도 리스트 형태로 리턴이 되네요

<R,A> R 리턴 타입에 대해 찾아봤는데 이게 무슨 타입을 뜻하는건가요? 검색해봐도 잘 나오지 않아서 질문 올립니다.

2017-10-18 12:01

@김규남 이 부분 이해하려면 자바 generic을 학습해야 이해할 수 있습니다.

소스 코드에서 collect 메소드를 보면 다음과 같아요.

<R, A> R collect(Collector<? super T, A, R> collector);

이 코드에서 반환 값의 Type은 R이 됩니다. 앞의 <R, A>는 generice을 의미하는 겁니다.

Java Generic 정리 문서의 "Method Generic Type" 파트보면 도움이 될 것 같네요. 자바에서 generic은 저도 100% 이해못할 정도로 너무 복잡한 영역인 것 같아요.

2017-10-18 16:10

아..어렵네요.( stream foreach 는 void 라서 외부 선언이 필요 ) 근데 stream이 좋군요.


String fullString = "pobi,crong,honux"; -- 단일 생성자 (이경우는 생성자의 인자가 여러개있을 경우-가령 string이 연속 두개- 어느놈인지 갈라파고스에 빠질 수 있으므로 비추) List<Car> cars1 = Arrays.stream(fullString.split(",")).map(Car::new).collect(Collectors.toList()); --- 생성자 호출 List<Car> cars2 = Arrays.stream(fullString.split(",")).map(c -> new Car(c)).collect(Collectors.toList()); -- 그냥 loop List<Car> cars3 = new ArrayList<Car>(); Arrays.stream(fullString.split(",")).forEach(o->{ cars3.add(new Car(o)); });
2017-10-18 16:12

더 줄이려면, 이런 방법도 가능하네요. List<Car> carListMadeByColons = carNames.stream().map(Car::new).collect(Collectors.toList());

이 방법의 효용에 대해서는 스택 오버플로에서도 여러 의견이 있습니다.

New object instantiation when using Java 8 streams

가장 추천을 많이 얻은 의견에 따르면, 퍼포먼스 차이는 없다고 하는군요. 100개 이상의 객체를 만들면서 걸린 시간을 비교해보는 경험을 해 보는 것도 도움이 될 것 같습니다. 한 번 해봐야겠네요. ㅎㅎㅎ

의견 추가하기