diff --git "a/9\354\243\274\354\260\250/racingcar/gradle/wrapper/gradle-wrapper.jar" "b/9\354\243\274\354\260\250/racingcar/gradle/wrapper/gradle-wrapper.jar" new file mode 100644 index 00000000..e708b1c0 Binary files /dev/null and "b/9\354\243\274\354\260\250/racingcar/gradle/wrapper/gradle-wrapper.jar" differ diff --git "a/9\354\243\274\354\260\250/racingcar/gradle/wrapper/gradle-wrapper.properties" "b/9\354\243\274\354\260\250/racingcar/gradle/wrapper/gradle-wrapper.properties" new file mode 100644 index 00000000..be52383e --- /dev/null +++ "b/9\354\243\274\354\260\250/racingcar/gradle/wrapper/gradle-wrapper.properties" @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git "a/9\354\243\274\354\260\250/racingcar/src/main/java/Application.java" "b/9\354\243\274\354\260\250/racingcar/src/main/java/Application.java" new file mode 100644 index 00000000..c99612fd --- /dev/null +++ "b/9\354\243\274\354\260\250/racingcar/src/main/java/Application.java" @@ -0,0 +1,8 @@ +import service.Game; + +public class Application { + public static void main(String[] args) { + Game game = new Game(); + game.play(); + } +} diff --git "a/9\354\243\274\354\260\250/racingcar/src/main/java/domain/Car.java" "b/9\354\243\274\354\260\250/racingcar/src/main/java/domain/Car.java" new file mode 100644 index 00000000..84825ac6 --- /dev/null +++ "b/9\354\243\274\354\260\250/racingcar/src/main/java/domain/Car.java" @@ -0,0 +1,50 @@ +package domain; + +public class Car { + private static final String BLANK = " "; + private static final int MAXIMAL_LENGTH = 5; + + private final String name; + private int position; + + public Car(String name) { + validate(name); + this.name = name; + this.position = 0; + } + + public Car(String name, int position) { + validate(name); + this.name = name; + this.position = position; + } + + private void validate(String name) { + if (name.length() > MAXIMAL_LENGTH) { + throw new IllegalArgumentException("[ERROR] 자동차 이름은 5자 이하여야 한다."); + } + if (name.contains(BLANK)) { + throw new IllegalArgumentException("[ERROR] 자동차 이름은 공백은 포함하지 않아야 한다."); + } + } + + public boolean isSamePosition(Car car) { + return car.position == this.position; + } + + public int compareTo(Car car) { + return this.position - car.position; + } + + public String getName() { + return name; + } + + public int getPosition() { + return position; + } + + public void go() { + position++; + } +} diff --git "a/9\354\243\274\354\260\250/racingcar/src/main/java/domain/Cars.java" "b/9\354\243\274\354\260\250/racingcar/src/main/java/domain/Cars.java" new file mode 100644 index 00000000..4f0d3cfc --- /dev/null +++ "b/9\354\243\274\354\260\250/racingcar/src/main/java/domain/Cars.java" @@ -0,0 +1,52 @@ +package domain; + +import java.util.List; +import java.util.stream.Collectors; + +import static view.OutputView.printGameStatus; + +public class Cars { + private final List cars; + + public Cars(List cars) { + validate(cars); + this.cars = cars + .stream() + .map(Car::new) + .collect(Collectors.toList()); + } + + private void validate(List cars) { + boolean checkName = cars.stream() + .distinct() + .count() != cars.size(); + if (checkName) { + throw new IllegalArgumentException("[ERROR] 중복된 이름입니다."); + } + } + + public void moveCars() { + cars.stream() + .filter(car -> Engine.isPower()) + .forEach(Car::go); + } + + public void printCars() { + cars.forEach(car -> + printGameStatus(car.getName(), car.getPosition())); + } + + public Winners findWinner() { + Car maxPositionCar = findMaxPositionCar(); + return new Winners(cars.stream() + .filter(maxPositionCar::isSamePosition) + .map(Winner::new) + .collect(Collectors.toList())); + } + + private Car findMaxPositionCar() { + return cars.stream() + .max(Car::compareTo) + .orElseThrow(() -> new IllegalArgumentException("[ERROR] 입력된 차량이 없습니다.")); + } +} diff --git "a/9\354\243\274\354\260\250/racingcar/src/main/java/domain/Engine.java" "b/9\354\243\274\354\260\250/racingcar/src/main/java/domain/Engine.java" new file mode 100644 index 00000000..0eb778ae --- /dev/null +++ "b/9\354\243\274\354\260\250/racingcar/src/main/java/domain/Engine.java" @@ -0,0 +1,17 @@ +package domain; + +import utils.RandomUtils; + +public class Engine { + private static final int START_INCLUSIVE = 0; + private static final int END_INCLUSIVE = 9; + private static final int GO_POINT = 4; + + private Engine() { + + } + + public static boolean isPower() { + return RandomUtils.nextInt(START_INCLUSIVE, END_INCLUSIVE) >= GO_POINT; + } +} diff --git "a/9\354\243\274\354\260\250/racingcar/src/main/java/domain/GameCounter.java" "b/9\354\243\274\354\260\250/racingcar/src/main/java/domain/GameCounter.java" new file mode 100644 index 00000000..3cab82d7 --- /dev/null +++ "b/9\354\243\274\354\260\250/racingcar/src/main/java/domain/GameCounter.java" @@ -0,0 +1,28 @@ +package domain; + +public class GameCounter { + private static final int ZERO = 0; + + private int counter; + + public GameCounter(String round) { + validate(round); + this.counter = Integer.parseInt(round); + } + + private void validate(String value) { + int result; + try { + result = Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("[ERROR] 시도 횟수는 숫자여야 한다."); + } + if (result <= ZERO) { + throw new IllegalArgumentException("[ERROR] 시도 횟수는 자연수여야 한다."); + } + } + + public int nextRound() { + return counter--; + } +} diff --git "a/9\354\243\274\354\260\250/racingcar/src/main/java/domain/Winner.java" "b/9\354\243\274\354\260\250/racingcar/src/main/java/domain/Winner.java" new file mode 100644 index 00000000..c4827905 --- /dev/null +++ "b/9\354\243\274\354\260\250/racingcar/src/main/java/domain/Winner.java" @@ -0,0 +1,13 @@ +package domain; + +public class Winner { + private final Car winner; + + public Winner(Car winner) { + this.winner = winner; + } + + public String getWinnerName() { + return winner.getName(); + } +} diff --git "a/9\354\243\274\354\260\250/racingcar/src/main/java/domain/Winners.java" "b/9\354\243\274\354\260\250/racingcar/src/main/java/domain/Winners.java" new file mode 100644 index 00000000..88920bc8 --- /dev/null +++ "b/9\354\243\274\354\260\250/racingcar/src/main/java/domain/Winners.java" @@ -0,0 +1,21 @@ +package domain; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class Winners { + private static final String DELIMITER = ", "; + + private List winners; + + public Winners(List winners) { + this.winners = new ArrayList<>(winners); + } + + public String winnersToString() { + return winners.stream() + .map(Winner::getWinnerName) + .collect(Collectors.joining(DELIMITER)); + } +} diff --git "a/9\354\243\274\354\260\250/racingcar/src/main/java/service/Game.java" "b/9\354\243\274\354\260\250/racingcar/src/main/java/service/Game.java" new file mode 100644 index 00000000..0c71e187 --- /dev/null +++ "b/9\354\243\274\354\260\250/racingcar/src/main/java/service/Game.java" @@ -0,0 +1,28 @@ +package service; + +import domain.Cars; +import domain.GameCounter; +import domain.Winners; +import view.OutputView; + +import java.util.stream.IntStream; + +import static view.InputView.inputCarNames; +import static view.InputView.inputGameCount; + +public class Game { + private static final int FINAL_ROUND = 0; + + public void play() { + Cars cars = new Cars(inputCarNames()); + GameCounter gameCounter = inputGameCount(); + OutputView.printGamePreview(); + while (gameCounter.nextRound() > FINAL_ROUND) { + cars.moveCars(); + cars.printCars(); + OutputView.nextLine(); + } + Winners winners = cars.findWinner(); + OutputView.printGameResult(winners); + } +} diff --git "a/9\354\243\274\354\260\250/racingcar/src/main/java/utils/RandomUtils.java" "b/9\354\243\274\354\260\250/racingcar/src/main/java/utils/RandomUtils.java" new file mode 100644 index 00000000..2220ecf9 --- /dev/null +++ "b/9\354\243\274\354\260\250/racingcar/src/main/java/utils/RandomUtils.java" @@ -0,0 +1,24 @@ +package utils; + +import java.util.Random; + +public class RandomUtils { + private static final Random RANDOM = new Random(); + private static final int ZERO = 0; + + private RandomUtils() { + + } + + public static int nextInt(final int startInclusive, final int endInclusive) { + if (startInclusive > endInclusive || startInclusive < ZERO) { + throw new IllegalArgumentException("[ERROR] 잘못된 랜덤값 설정"); + } + + if (startInclusive == endInclusive) { + return startInclusive; + } + + return startInclusive + RANDOM.nextInt(endInclusive - startInclusive + 1); + } +} diff --git "a/9\354\243\274\354\260\250/racingcar/src/main/java/view/InputView.java" "b/9\354\243\274\354\260\250/racingcar/src/main/java/view/InputView.java" new file mode 100644 index 00000000..77524f80 --- /dev/null +++ "b/9\354\243\274\354\260\250/racingcar/src/main/java/view/InputView.java" @@ -0,0 +1,52 @@ +package view; + +import domain.GameCounter; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.List; + +public class InputView { + private static final BufferedReader BUFFERED_READER = new BufferedReader(new InputStreamReader(System.in)); + private static final String DELIMITER = ","; + + private InputView() { + + } + + public static GameCounter inputGameCount() { + OutputView.printGameCounterInput(); + String inputNumber = nextLine(); + validateGameCount(inputNumber); + return new GameCounter(inputNumber); + } + + private static void validateGameCount(String input) { + if (input == null || input.isEmpty()) { + throw new IllegalArgumentException("[ERROR] 숫자를 입력해주세요."); + } + } + + public static List inputCarNames() { + OutputView.printCarInput(); + String input = nextLine(); + validateCarNames(input); + return Arrays.asList(input.split(DELIMITER)); + } + + private static void validateCarNames(String input) { + if (input == null || input.isEmpty()) { + throw new IllegalArgumentException("[ERROR] 이름을 입력해 주세요."); + } + } + + private static String nextLine() { + try { + return BUFFERED_READER.readLine(); + } catch (IOException e) { + throw new IllegalArgumentException("[ERROR] 잘못된 입력입니다."); + } + } +} diff --git "a/9\354\243\274\354\260\250/racingcar/src/main/java/view/OutputView.java" "b/9\354\243\274\354\260\250/racingcar/src/main/java/view/OutputView.java" new file mode 100644 index 00000000..cdfd0d5d --- /dev/null +++ "b/9\354\243\274\354\260\250/racingcar/src/main/java/view/OutputView.java" @@ -0,0 +1,50 @@ +package view; + +import domain.Winners; + +import java.util.stream.IntStream; + +public class OutputView { + private static final String POSITION_DISPLAY = "-"; + + private OutputView() { + + } + + private static void printCarName(final String carName) { + System.out.print(carName + " : "); + } + + private static void printCarPosition(final int carPosition) { + IntStream.range(0, carPosition) + .mapToObj(s -> POSITION_DISPLAY) + .forEach(System.out::print); + System.out.println(); + } + + public static void printGameStatus(final String carName, final int carPosition) { + printCarName(carName); + printCarPosition(carPosition); + } + + public static void printGameResult(final Winners winners) { + System.out.print("최종 우승자: "); + System.out.println(winners.winnersToString()); + } + + public static void printGameCounterInput() { + System.out.println("시도할 횟수는 몇회인가요? "); + } + + public static void printCarInput() { + System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); + } + + public static void printGamePreview() { + System.out.println("\n실행결과"); + } + + public static void nextLine() { + System.out.println(); + } +} diff --git "a/9\354\243\274\354\260\250/\354\236\220\353\260\224\352\270\260\354\264\210.md" "b/9\354\243\274\354\260\250/\354\236\220\353\260\224\352\270\260\354\264\210.md" new file mode 100644 index 00000000..dc0fbfbb --- /dev/null +++ "b/9\354\243\274\354\260\250/\354\236\220\353\260\224\352\270\260\354\264\210.md" @@ -0,0 +1,178 @@ +# [Java] 자바 기초(9) +## 1. 컬렉션 프레임워크(JCF, Java Collection Framework) +### 1-1. 컬렉션 프레임워크란 +컬렉션(Collection)은 여러 요소들을 담을 수 있는 자료구조다. 즉, 다수의 데이터 그룹이며 다른 말로 컨테이너(Container)라고도 부른다. 배열과 비슷하지만 크기가 고정된 배열을 보완하면 동적인 특성을 가진다. + +자바 1.2 이후 표준적인 방식으로 컬렉션을 다루기 위해 컬렉션 프레임워크(Collection Framework)가 등장했다. + +**컬렉션 프레임워크**란 널리 알려져 있는 자료구조를 바탕으로 객체나 데이터들을 효율적으로 관리(추가, 삭제, 검색, 저장)할 수 있도록 java.util 패키지에 컬렉션과 관련된 인터페이스와 클래스들을 포함시킨 것이다. + +![](https://images.velog.io/images/minide/post/dd44d14e-1efe-4817-aeeb-f4fa7a965e27/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-05-01%20%EC%98%A4%ED%9B%84%202.53.52.png) + +컬렉션 프레임워크 구성요소 +- 컬렉션 인터페이스 : 모든 컬렉션 인터페이스는 java.util 패키지에 있다. +- 컬렉션 클래스 : 모든 컬렉션 클래스는 java.util 또는 java.util.concurrent 패키지에 있다. +- 컬렉션 알고리즘 : 검색, 정렬, 셔플과 같은 기능을 제공한다. + +### 1-2. 컬렉션 인터페이스(Collection Interface) +컬렉션 인터페이스들은 제네릭(Generics)으로 표현되어 컴파일 시점에서 객체의 타입을 체크하기 때문에 런타임 에러를 줄이는 데 도움이 된다. + +컬렉션 프레임워크 대표적인 인터페이스 +- List 인터페이스 : 순서가 있는 데이터의 집합, 데이터의 중복을 허용 + - ArrayList, LinkedList, Stack, Vector 등 +- Set 인터페이스 : 순서를 유지하지 않는 데이터의 집합, 데이터의 중복을 허용하지 않음 + - HashSet, TreeSet, SortedSet 등 +- Map 인터페이스 : 키(key)와 값(value)의 쌍(pair)로 이뤄진 데이터의 집합, 순서는 유지되지 앟으며, 값의 중복은 허용되지만 키의 중복은 허용하지 않음 + - HashMap, TreeMay, Hashtable, LinkedHashMap, SortedMap, Properties 등 + +컬렉션 프레임워크의 모든 컬렉션 클래스들은 List, Set, Map 중 하나를 구현하고 있으며, 구현한 인터페이스의 이름이 클래스 이름에 포함되지만 Vector, Stack, Hashtable, Properties와 같은 클래스들은 컬렉션 프레임워크가 만들어지기 이전부터 존재하던 것이기 때문에 컬렉션 프레임워크의 명명법을 따르지 않는다. + +Vector나 Hashtable과 같은 기존의 컬렉션 클래스들은 호환을 위해 남겨진 것이므로 가급적 사용하지 않는 것이 좋다. 새로 추가된 **ArrayList**와 **HashMap**을 사용하면 된다. + +### 1-3. Collection 인터페이스 +List와 Set의 부모인 Collection 인터페이스에는 아래와 같은 메소드들이 정의되어 있다. + +![](https://images.velog.io/images/minide/post/a1e4ba5d-ddd4-44a6-962e-07c565f3f74f/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-05-01%20%EC%98%A4%ED%9B%84%203.23.01.png) + + +### 1-4. List 인터페이스 +List 인터페이스는 **중복을 허용하면서 저장 순서가 유지**되는 컬렉션을 구혀하는 데 사용된다. + +![](https://images.velog.io/images/minide/post/4c962eef-5d1c-43e7-831b-cdbd90e31896/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-05-01%20%EC%98%A4%ED%9B%84%203.23.33.png) + +### 1-5. Set 인터페이스 +Set 인터페이스는 **중복을 허용하지 않고 저장순서가 유지되지 않는** 컬렉션 클래스르 구현하는 데 사용된다. + +### 1-6. Map 인터페이스 +Map 인터페이스는 키(key)와 값(value)을 하나의 쌍으로 저장하는 컬렉션 클래스를 구현하는 데 사용된다. + +**값은 중복될 수 있지만 키의 중복은 허용하지 않는다.** +기존에 저장된 데이터와 중복된 키와 값을 저장하면 기존의 값은 없어지고 마지막에 저장된 값이 남게 된다. + +![](https://images.velog.io/images/minide/post/98c38744-eeda-482c-9c7f-0041eb1cea14/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-05-01%20%EC%98%A4%ED%9B%84%203.24.17.png) + +**Map 인터페이스에서 값은 중복을 허용하기 때문에 Collection 타입으로 반환하고, 키는 중복을 허용하지 않기 때문에 Set 타입으로 반환한다.** + +### 1-7. Map.Entry 인터페이스 +Map.Entry 인터페이스는 Map 인터페이스의 내부 인터페이스이다. +Map에 저장되는 key-value 쌍을 다루기 위해 내부적으로 Entry 인터페이스가 정의되어 있다. 보다 객체지향적인 설계를 하도록 유도한 것으로 Map 인터페이스 구현하는 클래스에서는 Map.Entry 인터페이스도 함께 구현해야 한다. + +![](https://images.velog.io/images/minide/post/a0cc64a8-9b2a-4b35-b02b-9b0db08ccdca/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-05-01%20%EC%98%A4%ED%9B%84%203.26.06.png) + + +### 1-8. 컬렉션 클래스 정리 +![](https://images.velog.io/images/minide/post/8deae5c8-8014-454d-83e7-32eeda44c0e5/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-05-01%20%EC%98%A4%ED%9B%84%203.32.34.png) + +### 1-9. 컬렉션 클래스의 특징 +![](https://images.velog.io/images/minide/post/67f81ba7-01e9-4861-9c31-611a727f5204/%EC%8A%A4%ED%81%AC%EB%A6%B0%EC%83%B7%202021-05-01%20%EC%98%A4%ED%9B%84%203.33.32.png) + +## 2. Stream의 특징 정리 +### 2-1. Stream이란 +java8에서 추가한 Stream은 람다를 활용할 수 있는 기술 중 하나이다. java8 이전에는 배열 또는 컬렉션 인스터스를 ```for``` 또는 ```foreach```문을 돌면서 요소 하나하나를 꺼내 다루었다면, java8 이후에는 Stream을 이용해 처리하였다. + +Stream은 한마디로 **데이터의 흐름**이다. 배열 또는 컬렉션 인스턴스에 함수 여러 개를 조합해서 원하는 결과를 필러팅하고 가공된 결과를 얻을 수 있다. 또한 람다를 이용해 코드의 양을 줄이고 간결하게 표현할 수 있다. + +즉, **배열과 컬렉션을 함수형으로 처리할 수 있다.** + +또 하나의 Stream의 장점은 간단하게 병렬처리(multi-threading)가 가능하다는 점이다. + +### 2-2. map +map은 요소들을 특정조건에 해당하는 값으로 변환해 변경된 요소를 포함하고 있는 새로운 스트림 객체이다. + +```java +list list = new ArrayList<>(); +list add(1); +list add(2); +list add(3); + +list.stream() + .map(num -> num * 10) + .forEach(System.out::println); +``` + +출력 결과 +``` +10 +20 +30 +``` + +### 2-3. sorted +sorted는 stream의 요소들을 정렬해 새로운 stream을 생성한다. +sorted()는 매개변수가 없기 때문에 이를 사용하려면 정렬하려는 객체에 비교가능한 인터페이스가 구현되어 있어야 한다. + +```java +list list = new ArrayList<>(); +list add("python"); +list add("java"); +list add("kotlin"); + +list.stream() + .sorted() + .forEach(System.out::println); +``` + +출력 결과 +``` +java +kotlin +python +``` + +### 2-4. distinct +stream에서 중복되는 요소들을 모두 제거해 새로운 stream을 반환한다. +동일한 객체인지 판단하는 기준은 ```Object.equals(Object)```의 결과 값이다. + +```java +list list = new ArrayList<>(); +list add("java"); +list add("python"); +list add("java"); +list add("kotlin"); +list add("python"); + +list.stream() + .distinct() + .forEach(System.out::println); +``` + +출력 결과 +``` +java +python +kotlin +``` + +### 2-5. limit +limit은 어떤 stream에서 일정 개수만큼만 가져와서 새로운 stream을 반환해준다. +```Stream.limit(숫자)```로 사용하며, 숫자만큼 요소를 취하여 stream을 생성해 반환한다. + +```java +list list = new ArrayList<>(); +list add("1"); +list add("2"); +list add("3"); +list add("4"); +list add("5"); + +list.stream() + .limit(2) + .forEach(System.out::println); +``` + +출력 결과 +``` +1 +2 +``` + +### 2-6. forEach +stream의 각각의 요소를 최종 처리할 명령문이다. +매개값으로 람다식 또는 메소드 참조를 대입할 수 있다. + +## 3. Collection.forEach와 Stream.forEach의 차이 +Collection.forEach는 따로 객체를 생성하지 않고 forEach 메소드를 호출한다. forEach 메소드는 Iterable 인터페이스의 default 메소드인데, Collection 인터페이스에서 Iterable 인터페이스를 상속하고 있기에 바로 호출할 수 있다. + +반면 Stream.forEach는 Collection 인터페이스의 default 메소드 stream()으로 Stream 객체를 생성해야만 forEach를 호출할 수 있다. + +단순 반복이 목적이라면 Stream.forEach는 stream()으로 생성된 Stream 객체가 버려지는 오버헤드가 있기 때문에 filter, map 등의 Stream 기능들과 함께 사용할 때만 Stream.forEach를 사용하고 나머지 경우엔 Collection.forEach를 쓰는 것이 좋다고 생각한다. \ No newline at end of file