yeoseon / tip-archive

트러블 슈팅 및 팁을 모아두는 레포 (Today I Learned)

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[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();
    }

Reference