원소시퀀스를 반환하는 메서드의 반환타입
** 원소시퀀스: 일반적으로 배열이나 리스트와 같은 데이터 구조에서 요소(element)들의 연속적인 나열을 의미
컬렉션 인터페이스
iterable 인터페이스: for-each 문에서만 쓰이거나 반환된 원소가 일부 collection 메서드를 구현할 수 없을 때
배열: 반환 원소들이 기본 타입이거나 성능에 민감한 상황
하지만 java 8이후 스트림이 등장하며 선택이 복잡해졌다.
🍑 본론
스트림이란 ?
java 8 API에 추가된 기능. 스트림을 이용하면 선언형으로 컬렉션 데이터를 처리할 수 있다.
또한 스트림을 이용하면 멀티스레드 코드를 구현하지 않아도 데이터를 투명하게 병렬로 처리할 수 있다.
java 7이전 3000만원 이하의 차량을 조회하는 코드
@ToString
@Getter
@AllArgsConstructor
public class Car {
private String name; // 자동차 이름
private Integer price; // 가격
}
// 차량 목록
List<Car> cars = Arrays.asList(
new Car("아반테", 20000000),
new Car("쏘나타", 30000000),
new Car("그랜저", 40000000)
);
// 3000만원 이하 차량 필터링
List<Car> lowPriceCars = new ArrayList<>();
for (Car car : cars) {
if (car.getPrice() <= 30000000) {
lowPriceCars.add(car);
}
}
// 비싼 차량부터 정렬
Collections.sort(lowPriceCars, new Comparator<Car>() {
@Override
public int compare(Car c1, Car c2) {
return Integer.compare(c2.getPrice(), c1.getPrice());
}
});
// 자동차 이름 필터링
List<String> lowPriceCarNames = new ArrayList<>();
for (Car lowPriceCar : lowPriceCars) {
lowPriceCarNames.add(lowPriceCar.getName());
}
System.out.println(lowPriceCarNames);
// 출력 결과 : [쏘나타, 아반테]
java 8 환경에서 스트림을 사용했을 때
// 차량 목록
List<Car> cars = Arrays.asList(
new Car("아반테", 20000000),
new Car("쏘나타", 30000000),
new Car("그랜저", 40000000)
);
List<String> lowPriceCarNames = cars.stream()
.filter(c -> c.getPrice() <= 30000000) // 3000만원 이하 차량 필터링
.sorted((c1, c2) -> c2.getPrice().compareTo(c1.getPrice())) // 비싼 차량부터 정렬
.map(Car::getName) // 자동차 이름 필터링
.collect(Collectors.toList());
System.out.println(lowPriceCarNames);
// 출력 결과 : [쏘나타, 아반테]
로직을 선언형으로 처리할 수 있고, 가독성 좋은 코드로 구현할 수 있다.
** 선언형 프로그래밍이란 원하는 결과를 묘사하는 방식으로 코드를 작성하는 프로그래밍 패러다임
stream을 parallelStream()으로 변경하면 멀티코어 아키텍쳐에서 병렬로 실행할 수 있다.
List<String> lowPriceCarNames = cars.parallelStream()
.filter(c -> c.getPrice() <= 30000000) // 3000만원 이하 차량 필터링
.sorted((c1, c2) -> c2.getPrice().compareTo(c1.getPrice())) // 비싼 차량부터 정렬
.map(Car::getName) // 자동차 이름 필터링
.collect(Collectors.toList());
✅ 원소를 반환할 때는 당연히 스트림을 사용해야 하지만, 스트림은 반복(iteration)을 지원하지 않는다.
=> 스트림과 반복을 알맞게 조합해야 한다.
✅ stream 인터페이스는 Iterable 인터페이스가 정의한 방식대로 동작하지만, Iterable을 확장(extend)하지 않아서 for-each로 스트림을 반복할 수 없다.
for (ProcessHandle ph : (Iterable<ProcessHandle>) ProcessHandle.allProcesses()::iterator) {
}
위의 경우 실전에 쓰기에 너무 난잡하고 직관성이 떨어지기 때문에, 어댑터 메서드를 사용하면 상황이 나아진다.
public static <E> Iterable<E> iterableOf(Stream<E> stream) {
return stream::iterator;
}
for (ProcessHandle p : iterableOf(ProcessHandle.allProcesses())) {
// 프로세스 처리
}
어댑터를 사용하면 어떤 스트림도 for-each문으로 반복할 수 있다.
Collection 인터페이스는 Iterable의 하위 타입이로 stream 메서드도 제공하니 반복과 스트림을 동시에 지원한다.
=> 원소 시퀀스를 반환하는 공개 API의 반환 타입에는 Collection이나 그 하위 타입을 쓰는게 일반적으로 최선이다.
반환하는 시퀀스의 크기가 메모리에 올려도 안전할 만큼 작다면 ArrayList나 HashSet 같은 표준 컬렉션 구현체를 반환하는 것이 최선일 수 있다.
하지만 단지 컬렉션을 반환한다는 이유로 덩치 큰 시퀀스를 메모리에 올려서는 안된다.
❗ 람다식 메소드::참조
메소드 참조 (Method Reference)는 말 그대로 메소드를 참조해서 매개 변수의 정보 및 리턴 타입을 알아내어, 람다식에서 굳이 선언이 불필요한 부분을 생략하는 것
(x, y) -> Math.max(x, y)
// 중복되는 매개변수를 없애고, 화살표를 없애고, 클래스가 메소드를 참조하는 기호인 `.`를 `::`로 변환하면 아래와 같이 표현 가능하다.
Math::max;
전용 컬렉션
반환할 시퀀스가 크지만 표현을 간결하게 할 수 있다면 전용 컬렉션을 구현하는 것이 좋다.
집합의 부분집합을 반환할 때 해당 집합의 원소 개수는 2^n개가 되며, 표준 컬렉션 구현체에 저장하는 것은 위험하다. 하지만 AbstractList를 사용하면 손쉽게 구현할 수 있다.
✅ 부분집합을 구성하는 각 원소의 인덱스를 비트 벡터로 사용하는 것
public class PowerSet {
public static final <E> Collection<Set<E>> of (Set<E> s) {
List<E> src = new ArrayList<>(s);
if (src.size() > 30)
throw new IllegalArgumentException("집합에원소가너무많습니다(최대30개).: " + s);
return new AbstractList<Set<E>>() {
@Override
public int size () {
// 부분집합의 크기는 2를 원래 집합의 원소 수만큼 거듭제곱 한것과같다.
return 1 << src.size();
}
@Override
public boolean contains (Obiect o) {
return o instanceof Set & src.containsAll((Set) o);
}
@Override
public Set<E> get (int index) {
Set<E> result = new HashSet<>();
for (int i = 0; index != 0; i++, index >>= 1)
if ((index & 1) == 1)
result.add(src.get(i));
return result;
}
};
}
}
👆 입력 집합의 원소 수가 30을 넘으면 PowerSet.of가 예외를 던진다. 다시 말해, Collection의 size 메서드가 intㄹ르 반환하므로 PowerSet.of가 반환되는 시퀀스의 최대 길이는 2^31 - 1로 제한된다.
Stream이나 Iterable이 아닌 Collection을 반환 타입으로 쓸 때의 단점을 보여준다.
AbstarctCollection을 활용해서 Collection 구현체 작성
Iterable용 메서드 외에 contains와 size를 구현할 것
반복이 시작되기 전에는 시퀀스의 내용을 확정할 수 없는 등의 사유로 contains와 size를 구현하는게 불가능할 때는 컬렉션보다는 스트림이나 Iterable을 반환하는 것이 좋다.
두 방식을 모두 제공해도 된다.
만약 부분리스트를 스트림으로 구현한다면?
for 반복문을 중첩해서 사용할 때
for (int start = 0; start < src.size(); start++) {
for (int end = start + 1; end <= src.size(); end++) {
System.out.println(src.subList(start, end));
}
}
Chapter : 7. 람다와 스트림
Item : 47. 반환 타입으로는 스트림보다 컬렉션이 낫다
Assignee : Lainlnya
🍑 서론
java 7이전
원소시퀀스를 반환하는 메서드의 반환타입 ** 원소시퀀스: 일반적으로 배열이나 리스트와 같은 데이터 구조에서 요소(element)들의 연속적인 나열을 의미
하지만 java 8이후 스트림이 등장하며 선택이 복잡해졌다.
🍑 본론
스트림이란 ?
java 8 API에 추가된 기능. 스트림을 이용하면 선언형으로 컬렉션 데이터를 처리할 수 있다.
또한 스트림을 이용하면 멀티스레드 코드를 구현하지 않아도 데이터를 투명하게 병렬로 처리할 수 있다.
java 7이전 3000만원 이하의 차량을 조회하는 코드
java 8 환경에서 스트림을 사용했을 때
로직을 선언형으로 처리할 수 있고, 가독성 좋은 코드로 구현할 수 있다. ** 선언형 프로그래밍이란 원하는 결과를 묘사하는 방식으로 코드를 작성하는 프로그래밍 패러다임
stream을 parallelStream()으로 변경하면 멀티코어 아키텍쳐에서 병렬로 실행할 수 있다.
✅ 원소를 반환할 때는 당연히 스트림을 사용해야 하지만, 스트림은 반복(iteration)을 지원하지 않는다. => 스트림과 반복을 알맞게 조합해야 한다.
✅ stream 인터페이스는 Iterable 인터페이스가 정의한 방식대로 동작하지만, Iterable을 확장(extend)하지 않아서 for-each로 스트림을 반복할 수 없다.
위의 경우 실전에 쓰기에 너무 난잡하고 직관성이 떨어지기 때문에, 어댑터 메서드를 사용하면 상황이 나아진다.
어댑터를 사용하면 어떤 스트림도 for-each문으로 반복할 수 있다.
Collection 인터페이스는 Iterable의 하위 타입이로 stream 메서드도 제공하니 반복과 스트림을 동시에 지원한다. => 원소 시퀀스를 반환하는 공개 API의 반환 타입에는 Collection이나 그 하위 타입을 쓰는게 일반적으로 최선이다.
반환하는 시퀀스의 크기가 메모리에 올려도 안전할 만큼 작다면 ArrayList나 HashSet 같은 표준 컬렉션 구현체를 반환하는 것이 최선일 수 있다.
하지만 단지 컬렉션을 반환한다는 이유로 덩치 큰 시퀀스를 메모리에 올려서는 안된다.
❗ 람다식 메소드::참조 메소드 참조 (Method Reference)는 말 그대로 메소드를 참조해서 매개 변수의 정보 및 리턴 타입을 알아내어, 람다식에서 굳이 선언이 불필요한 부분을 생략하는 것
전용 컬렉션
반환할 시퀀스가 크지만 표현을 간결하게 할 수 있다면 전용 컬렉션을 구현하는 것이 좋다.
집합의 부분집합을 반환할 때 해당 집합의 원소 개수는 2^n개가 되며, 표준 컬렉션 구현체에 저장하는 것은 위험하다. 하지만 AbstractList를 사용하면 손쉽게 구현할 수 있다.
✅ 부분집합을 구성하는 각 원소의 인덱스를 비트 벡터로 사용하는 것
👆 입력 집합의 원소 수가 30을 넘으면 PowerSet.of가 예외를 던진다. 다시 말해, Collection의 size 메서드가 intㄹ르 반환하므로 PowerSet.of가 반환되는 시퀀스의 최대 길이는 2^31 - 1로 제한된다. Stream이나 Iterable이 아닌 Collection을 반환 타입으로 쓸 때의 단점을 보여준다.
AbstarctCollection을 활용해서 Collection 구현체 작성
만약 부분리스트를 스트림으로 구현한다면?
입력 리스트의 모든 부분리스트를 스트림으로 반환
=> 반복을 사용하는게 더 자연스러운 상황에서도 사용자는 스트림을 쓰거나 Stream을 Iterable로 변환해주는 어댑터를 이용해야 한다. => collection을 사용하면 스트림을 활용한 구현보다 1.4배 빨랐다.
🍑 결론
원소를 반환하는 메서드의 반환타입
Referenced by