Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
1f93118
[refactor] Lotto 멤버변수로 Set 사용하도록 변경하여 중복 제거, LottoNumber Comparable 구…
julee-0430 Nov 24, 2025
451d4f9
[refactor] Lottos 생성자 내부 로직 제거
julee-0430 Nov 24, 2025
8cafa4c
[feat] Lotto 보너스볼 추가 및 Prize enum 리팩토링
julee-0430 Nov 24, 2025
e3bef8b
[refactor] LottoResults Prize enum 을 key 로 갖는 Map으로 리팩토링
julee-0430 Nov 24, 2025
0403d84
[feat] 전체 애플리케이션에 input, output 기능 추가 반영
julee-0430 Nov 24, 2025
a5caacf
[docs] 로또 2단계 기능 요구사항 README.md 작성
julee-0430 Nov 24, 2025
e1bfb3b
[refactor] Lotto 에 Set을 받는 생성자 생성, 인스턴스 변수는 List로 변경
julee-0430 Nov 24, 2025
cd5f4b8
[refactor] 총 상금 계산 메서드 네이밍 리팩토링
julee-0430 Nov 24, 2025
030f472
[refactor] WinningLotto 객체 생성 후 로또 당첨 여부 확인
julee-0430 Nov 26, 2025
4be4d31
[refactor] Lotto 의 인스턴스 변수 Set 타입으로 변경
julee-0430 Nov 26, 2025
57ce83b
[refactor] LottoNumber 객체 재사용으로 메모리 사용량 고려
julee-0430 Nov 26, 2025
f75bfde
[refactor] LottoResultCalculator 에서 최종 결과 계산
julee-0430 Nov 26, 2025
ac909b4
[refactor] LottoNumber 내부 정적 팩토리 메서드로 변경
julee-0430 Nov 28, 2025
d148e86
[refactor] PurchaseAmount 에서 좀 더 범용적인 의미의 Money로 변경
julee-0430 Nov 28, 2025
17845eb
[refactor] LottoResultCalculator 대신 Lottos 내부에서 반복하도록 변경
julee-0430 Nov 28, 2025
73ec000
[refactor] WinningLotto 부생성자 추가
julee-0430 Nov 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions src/main/java/lotto/LottoApplication.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
package lotto;

import lotto.model.PurchaseAmount;
import lotto.model.Lotto;
import lotto.model.LottoResults;
import lotto.model.Lottos;
import lotto.model.*;
import lotto.view.InputView;
import lotto.view.OutputView;

public class LottoApplication {
public static void main(String[] args) {
PurchaseAmount purchaseAmount = InputView.readBudgetInput();
Lotto winningLotto = InputView.readWinningLottoInput();
WinningLotto winningLotto = InputView.readWinningLottoInput();

OutputView.printPurchaseCount(purchaseAmount.countLottoTickets());
Lottos lottos = purchaseAmount.buyLottos();
OutputView.printBoughtLottos(lottos);

LottoResults result = lottos.calculateResults(winningLotto);
LottoResults result = LottoResultCalculator.calculate(lottos, winningLotto);
OutputView.printResults(result, purchaseAmount);
}
}
2 changes: 2 additions & 0 deletions src/main/java/lotto/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
- 로또 구입 금액을 입력하면 구입 금액에 해당하는 로또를 발급한다. (로또 1장에 1,000원)
- 로또 번호는 1부터 45까지의 숫자 중 6개를 랜덤으로 선택한다.
- 당첨 번호 6개를 입력하면, 구입한 로또와 비교하여 당첨 결과를 알려준다.
- 보너스 번호를 뽑아 당첨 번호 5개와 보너스 번호 1개가 일치하는 로또는 2등상을 준다.
- 당첨 결과는 3개, 4개, 5개, 6개 일치에 따라 각각 5,000원, 50,000원, 1,500,000원, 2,000,000,000원의 상금을 지급한다.
- 2등은 30,000,000원을 지급한다.
- 수익률을 계산하여 출력한다. (수익률 = 총 당첨 금액 / 구입 금액)
- 수익률은 소수점 둘째 자리까지 표시한다.
- 1 이상인 경우 이익, 1 미만인 경우 손해로 표시한다.
62 changes: 24 additions & 38 deletions src/main/java/lotto/model/Lotto.java
Original file line number Diff line number Diff line change
@@ -1,68 +1,54 @@
package lotto.model;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Lotto {
private final List<LottoNumber> numbers;
private final static int LOTTO_NUMBER_SIZE = 6;
private final static List<Integer> rangedInts = IntStream.rangeClosed(1, 45).boxed().collect(Collectors.toList());
private final Set<LottoNumber> numbers;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


public Lotto() {
this(generateRandomNumbers());
}

public Lotto(int... numbers) {
this(Arrays.stream(numbers).boxed().collect(Collectors.toList()));
this(Arrays.stream(numbers).mapToObj(LottoNumberFactory::get).collect(Collectors.toSet()));
}

public Lotto(List<Integer> numbers) {
public Lotto(Set<LottoNumber> numbers) {
checkValidity(numbers);
Collections.sort(numbers);
this.numbers = numbers.stream().map(LottoNumber::new).collect(Collectors.toList());
}

public List<LottoNumber> value() {
return Collections.unmodifiableList(this.numbers);
this.numbers = numbers;
}

public int countMatchNumbers(Lotto lotto) {
int matchCount = 0;
for (LottoNumber number : this.numbers) {
matchCount += addMatchCount(number, lotto);
}
return matchCount;
return Math.toIntExact(this.numbers.stream().filter(lotto::contains).count());
}

private void checkValidity(List<Integer> numbers) {
if (numbers.size() != 6) {
throw new IllegalArgumentException("로또 번호는 6개여야 합니다.");
}
if (hasDuplicated(numbers)) {
throw new IllegalArgumentException("로또 번호는 중복될 수 없습니다.");
}
public boolean matchesBonusNumber(LottoNumber bonusNumber) {
return contains(bonusNumber);
}

private boolean hasDuplicated(List<Integer> numbers) {
long distinctCount = numbers.stream().distinct().count();
return distinctCount != numbers.size();
@Override
public String toString() {
List<LottoNumber> lottoNumbers = new ArrayList<>(this.numbers);
Collections.sort(lottoNumbers);
return lottoNumbers.toString();
}

private int addMatchCount(LottoNumber number, Lotto lotto) {
if (lotto.contains(number)) {
return 1;
}
return 0;
public boolean contains(LottoNumber number) {
return numbers.contains(number);
}

private boolean contains(LottoNumber number) {
return numbers.contains(number);
private void checkValidity(Set<LottoNumber> numbers) {
if (numbers.size() != LOTTO_NUMBER_SIZE) {
throw new IllegalArgumentException("로또 번호는 6개여야 합니다.");
}
}

private static List<Integer> generateRandomNumbers() {
List<Integer> array = IntStream.rangeClosed(1, 45).boxed().collect(Collectors.toList());
Collections.shuffle(array);
return array.subList(0, 6);
private static Set<LottoNumber> generateRandomNumbers() {
Collections.shuffle(rangedInts);
return rangedInts.subList(0, LOTTO_NUMBER_SIZE).stream().map(LottoNumberFactory::get).collect(Collectors.toSet());
}
}
7 changes: 6 additions & 1 deletion src/main/java/lotto/model/LottoNumber.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import java.util.Objects;

public class LottoNumber {
public class LottoNumber implements Comparable<LottoNumber> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

상황

  • 1만명의 사용자가 동시에 당첨 여부를 확인할 수 있어야 한다.
  • 1명의 사용자는 평균 5장의 로또를 구매한 상태이다.

위 요구사항을 서버에서 생성되는 LottoNumber의 인스턴스의 갯수는?
1 * 5장 * 6개 숫자/1장 * 1만명 = 30만개이다.

동시에 생성되는 인스턴스 갯수가 너무 많다.
인스턴스 갯수를 줄일 수 있는 방법은?
로또 숫자 값을 LottoNumber 객체로 래핑하는 경우 매번 인스턴스가 생성되기 때문에 인스턴스의 갯수가 너무 많아져 성능이 떨어질 수 있다.
LottoNumber 인스턴스를 생성한 후 재사용할 수 있도록 구현한다.

힌트 : Map과 같은 곳에 인스턴스를 생성한 후 재사용하는 방법을 찾아본다.

private final int number;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public LottoNumber(String number)

문자열을 받는 생성자를 추가할 경우 의미있을까?

public LottoNumber(int number) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

생성자를 public으로 구현하고 있다.
이렇게 구현할 경우 LottoNumberFactory 사용법을 모르는 개발자의 경우 LottoNumber를 직접 생성할 수 있다.
다른 개발자가 LottoNumber를 직접하지 못하도록 막고, LottoNumber는 45개의 인스턴스만 생성해 재사용할 수 있도록 할 수 없을까?

힌트: 정적 팩터리 메서드

Expand Down Expand Up @@ -36,4 +36,9 @@ public int hashCode() {
public String toString() {
return String.valueOf(number);
}

@Override
public int compareTo(LottoNumber another) {
return Integer.compare(this.number, another.number);
}
}
25 changes: 25 additions & 0 deletions src/main/java/lotto/model/LottoNumberFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package lotto.model;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.IntStream;

public class LottoNumberFactory {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

private final static Map<Integer, LottoNumber> CACHE;

static {
CACHE = new ConcurrentHashMap<>();
IntStream.rangeClosed(1, 45).forEach(i -> CACHE.put(i, new LottoNumber(i)));
}

public static LottoNumber get(String number) {
return get(Integer.parseInt(number));
}

public static LottoNumber get(Integer number) {
if (CACHE.get(number) == null) {
throw new IllegalArgumentException("로또 번호는 1부터 45 사이의 숫자여야 합니다.");
}
return CACHE.get(number);
}
}
11 changes: 11 additions & 0 deletions src/main/java/lotto/model/LottoResultCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package lotto.model;

public class LottoResultCalculator {
public static LottoResults calculate(Lottos lottos, WinningLotto winningLotto) {
LottoResults lottoResults = new LottoResults();
for (Lotto lotto : lottos.lottos()) {
lottoResults.update(winningLotto.calculatePrize(lotto));
}
return lottoResults;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굳이 이 객체가 필요한가?

Suggested change
LottoResults lottoResults = new LottoResults();
for (Lotto lotto : lottos.lottos()) {
lottoResults.update(winningLotto.calculatePrize(lotto));
}
return lottoResults;
LottoResults lottoResults = lottos.match(winningLotto);

이와 같이 Lottos에 메시지를 보내 위와 같이 구현해도 되지 않을까?

}
}
37 changes: 13 additions & 24 deletions src/main/java/lotto/model/LottoResults.java
Original file line number Diff line number Diff line change
@@ -1,59 +1,48 @@
package lotto.model;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class LottoResults {
private final Map<Integer, Integer> matchCounts;
private final Map<Prize, Integer> prizeCounts;

public LottoResults() {
this(new HashMap<>());
}

public LottoResults(Map<Integer, Integer> matchCounts) {
this.matchCounts = matchCounts;
public LottoResults(Map<Prize, Integer> prizeCounts) {
this.prizeCounts = prizeCounts;
}

public void updateMatchCount(int matchCount) {
if (matchCount < 3) {
return;
}
matchCounts.put(matchCount, matchCounts.getOrDefault(matchCount, 0) + 1);
public void update(Prize prize) {
prizeCounts.put(prize, prizeCounts.getOrDefault(prize, 0) + 1);
}

public long getPrizeValue() {
return matchCounts.entrySet().stream()
.mapToLong(entry -> (long) entry.getValue() * Prize.fromMatchCount(entry.getKey()).value())
public long getTotalPrizeValue() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반환값도 원시 값을 포장해 보며 어떨까?
객체 이름이 PurchaseAmount이라 재사용함에 거부감이 있다.
그런데 Money와 같은 객체로 이름을 바꾼 후 재사용하는 것은 어떨까?

Suggested change
public long getTotalPrizeValue() {
public Money getTotalPrizeValue() {

이와 같이 구현할 경우 수익률은 누가 계산하는 것이 맞을까?

return prizeCounts.entrySet().stream()
.mapToLong(entry -> (long) entry.getValue() * entry.getKey().value())
.sum();
}

public double getReturnRate(PurchaseAmount purchaseAmount) {
long totalPrize = getPrizeValue();
long totalPrize = getTotalPrizeValue();
return purchaseAmount.getReturnRate(totalPrize);
}

public Integer getMatchCount(int matchCount) {
return matchCounts.getOrDefault(matchCount, 0);
public Integer getPrizeCount(Prize prize) {
return prizeCounts.getOrDefault(prize, 0);
}

@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
LottoResults that = (LottoResults) o;
return Objects.equals(matchCounts, that.matchCounts);
return Objects.equals(prizeCounts, that.prizeCounts);
}

@Override
public int hashCode() {
return Objects.hashCode(matchCounts);
}

@Override
public String toString() {
return "LottoResults{" +
"matchCounts=" + matchCounts +
'}';
return Objects.hashCode(prizeCounts);
}
}
35 changes: 13 additions & 22 deletions src/main/java/lotto/model/Lottos.java
Original file line number Diff line number Diff line change
@@ -1,46 +1,37 @@
package lotto.model;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Lottos {
private final List<Lotto> lottos;

public Lottos(int count) {
this(new ArrayList<>());
for (int i = 0; i < count; i++) {
lottos.add(new Lotto());
}
this(createLottos(count));
}

public Lottos(List<Lotto> lottos) {
this.lottos = lottos;
}

public LottoResults calculateResults(Lotto winningLotto) {
LottoResults results = new LottoResults();

for (Lotto lotto : lottos) {
int matchCount = lotto.countMatchNumbers(winningLotto);
results.updateMatchCount(matchCount);
}

return results;
}

private void addMatchCount(int matchCount, List<Integer> results) {
if (matchCount >= 3 && matchCount <= 6) {
int idx = matchCount - 3;
results.set(idx, results.get(idx) + 1);
}
public List<Lotto> lottos() {
return Collections.unmodifiableList(lottos);
}

public String toString() {
StringBuilder sb = new StringBuilder();
for (Lotto lotto : lottos) {
sb.append(lotto.value().toString()).append("\n");
sb.append(lotto.toString()).append("\n");
}
return sb.toString();
}

private static List<Lotto> createLottos(int count) {
List<Lotto> lottos = new ArrayList<>();
for (int i = 0; i < count; i++) {
lottos.add(new Lotto());
}
return lottos;
}
}
25 changes: 18 additions & 7 deletions src/main/java/lotto/model/Prize.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
import java.util.Arrays;

public enum Prize {
THREE_MATCHES(3, 5_000),
FOUR_MATCHES(4, 50_000),
FIVE_MATCHES(5, 1_500_000),
SIX_MATCHES(6, 2_000_000_000);
MISS(0, 0),
FIFTH(3, 5_000),
FOURTH(4, 50_000),
THIRD(5, 1_500_000),
SECOND(5, 30_000_000),
FIRST(6, 2_000_000_000);

private final int matchCount;
private final int prizeValue;
Expand All @@ -23,13 +25,22 @@ public int value() {

@Override
public String toString() {
return String.format("%d개 일치 (%s원)", matchCount, prizeValue);
if (this.equals(SECOND)) {
return String.format("%d개 일치, 보너스 볼 일치(%s원)", matchCount, prizeValue);
}
return String.format("%d개 일치(%s원)", matchCount, prizeValue);
}

static Prize fromMatchCount(int matchCount) {
static Prize valueOf(int matchCount, boolean matchBonus) {
if (matchCount < 3) {
return MISS;
}
if (matchCount == 5 && matchBonus) {
return SECOND;
}
return Arrays.stream(Prize.values())
.filter(prize -> prize.matchCount == matchCount)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("유효하지 않은 일치 값입니다. " + matchCount));
.orElseThrow(() -> new IllegalArgumentException("유효하지 않은 일치 값입니다. " + matchCount + matchBonus));
}
}
20 changes: 20 additions & 0 deletions src/main/java/lotto/model/WinningLotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package lotto.model;

public class WinningLotto {
private final Lotto winningLotto;
private final LottoNumber bonusNumber;

public WinningLotto(Lotto lotto, LottoNumber bonusNumber) {
if (lotto.contains(bonusNumber)) {
throw new IllegalArgumentException("당첨 번호는 보너스볼의 번호를 포함할 수 없습니다.");
}
this.winningLotto = lotto;
this.bonusNumber = bonusNumber;
}

public Prize calculatePrize(Lotto lotto) {
int matchCount = lotto.countMatchNumbers(winningLotto);
boolean matchBonus = lotto.matchesBonusNumber(bonusNumber);
return Prize.valueOf(matchCount, matchBonus);
}
}
Loading