Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 4 additions & 7 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();
Money purchaseAmount = InputView.readPurchaseAmountInput();
WinningLotto winningLotto = InputView.readWinningLottoInput();

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

LottoResults result = lottos.calculateResults(winningLotto);
LottoResults result = lottos.match(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(LottoNumber::of).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(LottoNumber::of).collect(Collectors.toSet());
}
}
41 changes: 32 additions & 9 deletions src/main/java/lotto/model/LottoNumber.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
package lotto.model;

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

public class LottoNumber {
private final int number;
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 static Map<Integer, LottoNumber> CACHE;

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) {
if (!isValid(number)) {
throw new IllegalArgumentException("로또 번호는 1부터 45 사이의 숫자여야 합니다.");
}
this.number = number;
static {
CACHE = new ConcurrentHashMap<>();
IntStream.rangeClosed(1, 45).forEach(i -> CACHE.put(i, new LottoNumber(i)));
}
Comment on lines +11 to 14
Copy link
Contributor

Choose a reason for hiding this comment

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

👍


private boolean isValid(int number) {
return number >= 1 && number <= 45;
private final int number;

private LottoNumber(int number) {
this.number = number;
}

public int getNumber() {
Expand All @@ -36,4 +39,24 @@ public int hashCode() {
public String toString() {
return String.valueOf(number);
}

@Override
public int compareTo(LottoNumber another) {
return Integer.compare(this.number, another.number);
}

private static boolean isValid(int number) {
return CACHE.containsKey(number);
}

public static LottoNumber of(int number) {
if (!isValid(number)) {
throw new IllegalArgumentException("로또 번호는 1부터 45 사이의 숫자여야 합니다.");
}
return CACHE.get(number);
}

public static LottoNumber of(String input) {
return of(Integer.parseInt(input));
}
}
51 changes: 24 additions & 27 deletions src/main/java/lotto/model/LottoResults.java
Original file line number Diff line number Diff line change
@@ -1,59 +1,56 @@
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 Money getTotalPrizeValue() {
return new Money(prizeCounts.entrySet().stream()
.mapToLong(entry -> (long) entry.getValue() * entry.getKey().value())
.sum());
}

public long getPrizeValue() {
return matchCounts.entrySet().stream()
.mapToLong(entry -> (long) entry.getValue() * Prize.fromMatchCount(entry.getKey()).value())
.sum();
public double getReturnRate(Money purchaseAmount) {
Money totalPrize = getTotalPrizeValue();
return calculateReturnRate(purchaseAmount, totalPrize);
}

public double getReturnRate(PurchaseAmount purchaseAmount) {
long totalPrize = getPrizeValue();
return purchaseAmount.getReturnRate(totalPrize);
private double calculateReturnRate(Money purchaseAmount, Money totalPrize) {
if (purchaseAmount.isZero()) {
return 0;
}
double rate = totalPrize.divideBy(purchaseAmount);
return Math.floor(rate * 100) / 100.0;
}

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);
}
}
37 changes: 16 additions & 21 deletions src/main/java/lotto/model/Lottos.java
Original file line number Diff line number Diff line change
@@ -1,46 +1,41 @@
package lotto.model;

import java.util.ArrayList;
import java.util.Arrays;
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 LottoResults match(WinningLotto winningLotto) {
LottoResults lottoResults = new LottoResults();
for (Lotto lotto : this.lottos) {
lottoResults.update(winningLotto.calculatePrize(lotto));
}
return lottoResults;
}

@Override
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,45 @@

import java.util.Objects;

public class PurchaseAmount {
private final int amount;
public class Money {
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 long amount;

public PurchaseAmount(int amount) {
public Money(int amount) {
this((long) amount);
}

public Money(long amount) {
if (!isValid(amount)) {
throw new IllegalArgumentException("예산은 0 이상이며 1000원 단위로만 설정 가능합니다.");
}
this.amount = amount;
}

public int countLottoTickets() {
return amount / 1_000;
return Math.toIntExact(amount / 1_000L);
}

public Lottos buyLottos() {
return new Lottos(countLottoTickets());
}

public double getReturnRate(Long totalPrize) {
if (amount == 0) {
return 0;
}
double rate = (double) totalPrize / amount;
return Math.floor(rate * 100) / 100.0;
public boolean isZero() {
return amount == 0;
}

public double divideBy(Money money) {
return (double) this.amount / money.amount;
}

private boolean isValid(int amount) {
private boolean isValid(Long amount) {
return amount >= 0 && amount % 1000 == 0;
}

@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
PurchaseAmount purchaseAmount = (PurchaseAmount) o;
return amount == purchaseAmount.amount;
Money money = (Money) o;
return amount == money.amount;
}

@Override
Expand Down
Loading