[Java] Java 8 스트림
yeoseon opened this issue · comments
Stream
Stream vs for ??
next-step/java-ladder#327 (comment) 참고
judgeHasLine를 사용하고 있는 createLine 로직이 꼭 Stream일 필요가 없을 것 같아요!! Stream이 강점을 발휘하는 부분은 만들어져 있는 컬렉션을 가공, 변형하는 부분이라고 생각하기 때문이지요 ㅎㅎ 저 역시 객체를 만들어낼 때는 for문을 훨씬 선호하고 있습니다. 특히 지금과 같이 전에 만들었던 Point가 다음에 만들 Point에 영향을 주고 있기 때문에 for문 사용을 적절하게 하셨다고 생각합니다 ㅎㅎ
예제
예제 1 (next-step/java-lotto#404 (comment)) 참고
public static LottoTier getTier(int matchedNumberCount) {
for (LottoTier lottoTier : LottoTier.values()) {
if(lottoTier.matchedNumberCount == matchedNumberCount) {
return lottoTier; //TODO: indent 2 이하 어떻게 풀면 좋을 지
}
}
return LottoTier.NONE;
}
LottoTier라는 Enum을 하나 만들어, Enum이 가지고 있는 Count와 matchedNumberCount가 같으면 해당 LottoTier 객체를 반환하는 로직이다.
values를 활용하여 다음과 같이 구현했으나, 클린코드 규칙인 indent 2 이하에 위배된다.
이것을 Stream으로 풀면 다음과 같이 해결할 수 있다.
Arrays.stream.(values())
.filter(tier-> tier.matchedNumberCount == matchedNumberCount)
.findFirst()
.orElse(NONE);
- Refactoring 팁
** Stream을 쓰지 싫다면, 다음과 같은 방법으로도 접근하면 좋겠다. !
Map<Integer, LottoTier> cache 형태로 matchedNumberCount 로 캐싱한다면
cache.get(matchedNumberCount) 와 같은 형태로도 사용 할 수 있을 것 같습니다.
예제 2 (next-step/java-lotto#404 (comment) 참고)
1 - 45 사이의 LottoNumber 객체를 상수로 생성하여 가지고 싶을 때
- 나쁜 코드
- 상수를 선언할 때는 대문자 컨벤션을 사용한다. 또한 상수이므로 굳이 선언 후에 초기화 해줄 필요 없다.
private static final List<LottoNumber> wholeNumber = new ArrayList<>();
...
private static List<LottoNumber> getWholeNumber() {
if(wholeNumber.isEmpty()) {
for(int i = LottoNumber.MIN_NUMBER; i <= LottoNumber.MAX_NUMBER; i++) {
wholeNumber.add(new LottoNumber(i));
}
}
return wholeNumber;
}
다음과 같이 IntStream을 이용해 개선할 수 있다.
private static final List<LottoNumber> LOTTO_NUMBER_POOL = IntStream.rangeClosed(LottoNumber.MIN_NUMBER, LottoNumber.MAX_NUMBER)
.mapToObj(LottoNumber::new)
.collect(Collectors.toList());
예제 3 (합 구하기)
int total = 0;
for (int number : numbers) {
if(c.isSumTarget(number)) {
total += number;
}
}
return total;
이것을 Stream으로 (reduce 이용)
total = numbers.stream()
.filter(number -> c.isSumTarget(number))
.reduce(0, Integer::sum);
예제 4
12자를 넘는 문자를 긴 순서대로, 중복 허용하지 않고 출력한다.
public static void printLongestWordTop100() throws IOException {
String contents = new String(Files.readAllBytes(Paths
.get("src/main/resources/fp/war-and-peace.txt")), StandardCharsets.UTF_8);
List<String> words = Arrays.asList(contents.split("[\\P{L}]+"));
List<String> resultWords = words.stream()
.filter(word -> word.length() > 12)
.map(String::toLowerCase)
.sorted((o1, o2) -> Integer.compare(o2.length(), o1.length()))
.distinct()
.collect(Collectors.toList());
resultWords.forEach(word -> System.out.println(word));
}
예제 5 (객체가 아닌 int i = 0;...의 형태를 Stream으로)
List<Line> lines = new ArrayList<>();
for(int i = 0; i < height; i++) {
lines.add(createLine(persons));
}
List<Line> lines = Stream
.generate(() -> createLine(persons))
.limit(height)
.collect(Collectors.toList());
예제 6 (for 내부의 if문)
for(Round round : roundHistory) {
if(round.isRound(roundNumber)) {
return round.getDistance();
}
}
throw new IllegalStateException(NO_ROUND_EXCEPTION_MESSAGE);
return roundHistory.stream()
.filter(round -> round.isRound(roundNumber))
.map(Round::getDistance)
.findFirst()
.orElseThrow(() -> {
throw new IllegalStateException(NO_ROUND_EXCEPTION_MESSAGE);
});
예제 7 (void 문 중간에 사용하기)
map을 쓸 경우 무조건 리턴값이 있어야 하기떄문에, void 메소드를 값을 return 하도록 했다.. 다른방법도 있지 않을까?
for(String string : strings) {
Integer number = Integer.parseInt(string);
assertNegative(number);
numbers.add(number);
}
...
private void assertNegative(Integer number) {
if(number < 0) {
throw new RuntimeException(NEGATIVE_NUMBER_EXCEPTION_MESSAGE);
}
}
List<Integer> newNumbers = Arrays.stream(strings)
.map(this::assertNegative)
.collect(Collectors.toList());
numbers.addAll(newNumbers);
...
private int assertNegative(String string) {
Integer number = Integer.parseInt(string);
if(number < 0) {
throw new RuntimeException(NEGATIVE_NUMBER_EXCEPTION_MESSAGE);
}
return number;
}
예제 8 (generate 이용하기)
public Cars(int carCount) {
for(int i = 0; i < carCount; i++) {
cars.add(new Car());
}
}
public Cars(int carCount) {
cars.addAll(Stream.generate(Car::new)
.limit(carCount)
.collect(Collectors.toList()));
}
예제 9 (여러가지 숫자들을 String으로 쭊 이어 만들기)
로또번호 목록이 주어지면 이를 [1, 2, 3, 4, 5, 6] 형태로 만드는 것을 Stream으로 풀 수 있다!
private static final String LOTTO_NUMBER_DELIMITER = ", ";
private static final String LOTTO_NUMBER_PREFIX = "[";
private static final String LOTTO_NUMBER_POSTFIX = "]";
...
private String getLottoNumbersString(Set<LottoNumber> lottoNumbers) {
return lottoNumbers
.stream()
.sorted()
.map(LottoNumber::getValue)
.map(String::valueOf)
.collect(Collectors.joining(LOTTO_NUMBER_DELIMITER, LOTTO_NUMBER_PREFIX, LOTTO_NUMBER_POSTFIX));
}
예제 10 (generate를 파라미터와 함께 수행하기, 불변 Collection 유지하기)
public Cars(String[] carNames) {
for(int i = 0; i < carNames.length; i++) {
cars.add(createCar(carNames[i]));
}
}
cars = Arrays.stream(carNames)
.map(carName -> new Car(carName))
.collect(collectingAndThen(toList(), Collections::unmodifiableList));
예제 11 (불변 Collection 유지하기)
next-step/java-racingcar#1785 (comment)
next-step/java-racingcar#1785 (comment)
public List<String> getWinnerNames() {
List<String> winnerNames = new ArrayList<String>();
for(Car car: cars) {
addWinnerName(winnerNames, car);
}
return winnerNames;
}
private List<String> addWinnerName(List<String> winnerNames, Car car) {
if(isWinner(car.getDistance())) {
winnerNames.add(car.getName());
}
return winnerNames;
}
private boolean isWinner(int distance) {
int winnerDistance = getMaxDistance(getFinalDistanceList());
if(winnerDistance == distance) {
return true;
}
return false;
}
private static int getMaxDistance(List<Integer> finalDistances) {
return Collections.max(finalDistances);
}
private List<Integer> getFinalDistanceList() {
List<Integer> finalDistanceList = new ArrayList<>();
for(Car car: cars) {
finalDistanceList.add(car.getDistance());
}
return finalDistanceList;
}
public List<String> getWinnerNames() {
int winnerDistance = getMaxDistance();
return cars.stream()
.filter(car -> car.getDistance() == winnerDistance)
.map(car -> car.getName())
.collect(collectingAndThen(toList(), Collections::unmodifiableList));
}
private int getMaxDistance() {
return cars.stream()
.map(car -> car.getDistance())
.max(Integer::compareTo)
.get();
}