import java.util.HashSet;
import java.util.Set;
public class SetExample {
public static void main(String[] args) {
Set<String> fruits = new HashSet<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
// 중복 데이터 추가 시, 무시됨
fruits.add("Apple");
System.out.println(fruits);
// 출력 예시: [Orange, Banana, Apple] (순서는 랜덤)
// 포함 여부 확인
System.out.println("Banana가 포함되어 있나요? " + fruits.contains("Banana"));
// contains 메소드 포함 여부 있으면 T , else F
// 반복문으로 순회
for (String fruit : fruits) {
System.out.print(fruit + " ");
} // Orange Banana Apple (순서는 무관)
}
}
1. 학습내용 : 키오스크 과제 도전1단계, 알고리즘 Daily 풀기
★ 키오스크 도전 2단계 ★
| (Challenge) Lv 2. Enum, 람다 & 스트림을 활용한 주문 및 장바구니 관리 |
Kiosk, Main, Menu, MenuItem 클래스는 하나의 수정도 없이 동일함.
이번 단계 에서는 Enum을 활용하여 주문을 할때 할인율을 적용할 것인지 선택하게 구현 하고, 람다 & 스트림을 통해서 보다 간편하게 코드량을 줄여서 주문 및 장바구니 관리를 할 것이다. ( 해보는데까지 ! ! )
package com.example.kiosk;
public enum User {
// Enum을 이용해 사용자의 할인율을 관리함.
SOLDIER("군인 : 10%"){
@Override
public double discount() {
System.out.println("군인 할인율을 적용합니다.");
return 0.1;
}
},
EMPLOYEE("직원 : 7%"){
@Override
public double discount() {
System.out.println("직원 할인율을 적용합니다.");
return 0.07;
}
},
STUDENT("학생 : 3%"){
@Override
public double discount() {
System.out.println("학생 할인율을 적용합니다.");
return 0.03;
}
},
General("일반 : 0%") {
@Override
public double discount() {
System.out.println("일반인은 할인율이 없습니다.");
return 1;
}
};
public abstract double discount();
final String user;
User(String user) {
this.user = user;
}
public String getUser() {
return user;
}
}
User ( Enum 타입 )
package com.example.kiosk;
import java.util.*;
public class Cart {
// 장바구니 클래스를 담당한다.
private final List<MenuItem> menuItems = new ArrayList<>();
Scanner sc = new Scanner(System.in);
private Integer sum = 0; // 총 가격
// 장바구니 출력 및 금액 계산
boolean showInfo() {
if (!menuItems.isEmpty()) { // 장바구니가 비어 있지 않으면, 정보 출력
System.out.println("아래와 같이 주문 하시겠습니까?");
System.out.println("[ Orders ]");
for(int i=0; i<menuItems.size(); i++) {
MenuItem menuItem = menuItems.get(i); // menuItem 객체 접근
System.out.print((i+1)+ ". ");
menuItem.showInfo();
}
// 스트림화 ( 현재 코드에선 for문이 훨씬더 깔끔함. )
// IntStream.range(0, menuItems.size())
// .forEach(i -> {
// MenuItem menuItem = menuItems.get(i);
// System.out.print((i+1) + ". ");
// menuItem.showInfo();
// });
System.out.println("\n[ Total ]");
// sum = 0; // 총합 계산;
// for (MenuItem menuItem : menuItems) {
// sum += menuItem.getPrice();
// }
// 스트림화
sum = menuItems.stream().mapToInt(MenuItem::getPrice).sum();
System.out.println(sum + "원\n");
System.out.println("1. 주문 2. 메뉴판");
discountShow();
System.out.println("주문 하시겠습니까?(1 주문): ");
int choose = sc.nextInt();
if (choose == 1 && sum > 0) { // 주문시, 합계금액이 0원을 넘어야 주문가능
System.out.println("\n주문이 완료 되었습니다. 금액은 " + sum + "원 입니다.\n");
return true; // 튜터님 도움받아서 void 말고 boolean 으로 반환.
}
}
System.out.println("\n주문한 상품이 없습니다.\n");
return false;
}
// 장바구니 담기
void addCart(MenuItem menuItem) {
menuItem.showInfo();
System.out.println("위 메뉴를 장바구니에 추가하시겠습니까?");
System.out.println("1. 확인 2. 취소");
int choose = sc.nextInt();
if (choose == 1) {
menuItems.add(menuItem);
System.out.println("\n" + menuItem.getName() +"이(가) 장바구니에 추가 되었습니다.\n");
} else if (choose == 2) {
System.out.println("\n취소 되었습니다.\n");
} else {
System.out.println("\n잘못된 입력 값입니다.\n");
}
}
// 장바구니 초기화
void cartReset() { // 튜터님 도움 받아서 clear 메소드를 알게됨.
if(!menuItems.isEmpty()) { // 비어있지 않다면 초기화.
menuItems.clear();
System.out.println("\n진행중이던 주문을 초기화 하였습니다.\n");
} else {
System.out.println("\n주문한 상품이 없습니다.\n");
}
}
// 할인표 보여주기
void discountShow() {
System.out.println("\n할인 정보를 입력해주세요");
for(int i=0; i < User.values().length; i++) { // 튜터님 도움을통해 Enum 메소드 !
System.out.println((i+1) + ". " + User.values()[i].getUser());
}
// 스트림화 ( for문이 훨씬 더 깔끔 튜터님 조언 )
// IntStream.range(0, User.values().length)
// .forEach(i -> {
// System.out.println((i+1) + ". " + User.values()[i].getUser());
// });
try {
int choose = sc.nextInt();
if (choose == 1) {
sum = sum - (int) (sum * User.SOLDIER.discount());
} else if (choose == 2) {
sum = sum - (int) (sum * User.EMPLOYEE.discount());
} else if (choose == 3) {
sum = sum - (int) (sum * User.STUDENT.discount());
} else if (choose == 4) {
sum = sum - (int) (sum * User.General.discount());
}
else {
throw new Exception(); // throw문으로 예외처리를 해결 (튜터님 도움)!!
}
} catch (InputMismatchException e) {
System.out.println("\n해당 입력값은 유효하지 않습니다.\n");
} catch (Exception e) { // catch문은 여러개 작성가능하다.
System.out.println("\n유효 하지않은 번호입니다.\n");
}
}
public List<MenuItem> getMenuItems() {
return menuItems;
}
}
Cart 클래스
Enum 타입을 이용해서 구매자가 주문시 할인율을 받을 것인지 물어보고 입력한 값에 따라 할인율을 선택하게 된다.
try-catch문을 통해 예외적인 입력 값이 입력될 가능성을 고려해서 예외 처리를 하였다.
되도록 스트림화는 for문 위주로 스트림화를 시도해보았는데 그렇게 많이 해보지는 않았다. 오히려 for문이 가독성이 더 좋은 것들이 많았고, stream에 대해 개념은 이해가 되지만 적용하는것이 너무 어려웠다.
[트러블 슈팅]
- 배경 : Cart클래스에 스트림화 시켜서 주문 메뉴들을 이제 주문할때 가격을 합산 하려고 할때, 0원이 나오는 현상 발견.
- 발단 : 스트림화를 잘못 했었는가? 등 의문이 들기 시작함.
- 전개 : 스트림에 대해서는 잘 작동하지만 저장을 안시키고 있엇나? 확인함.
- 위기 : 결국에는 sum 필드에 저장을 하지 않고, 프로그램이 스트림만 읽는 거라고 판단함.
- 절정 : sum 에 직접 다시 그 스트림 코드를 대입 시켜주니까 해결됨
- 결말 : 단순히 대입을 하지 않아서, 0원 현상이 생긴것임. 컴파일러는 그대로 코드를 읽고 있엇을 뿐.
{ 자바 문법 특강 }
[배열(Array)]
같은 타입의 데이터를 연속된 메모리 공간에 저장하는 자료 구조
특징
- 고정 크기 (Static Size)
- 배열을 선언 할시 한번 크기를 정하면 다시는 되돌릴수 없음. ( 불변성 )
- 동일한 타입
- 배열은 하나의 타입으로만 데이터를 저장함.
String[] arr = {"apple", "lemon", "orange"};
- 인덱스(Index) 접근
- 인덱스는 0부터 시작, 배열길이 - 1 까지만 유효.
- 간단한 구조 & 메모리 효율
- 배열은 구조가 단순하고 연속된 메모리를 사용해 성능이 좋음.
- 크기가 고정이라는 큰 단점, 중간 데이터 삽입 / 삭제 어려움. ( 유연성 ↓ )
[컬렉션] ★ ★
특징
- 동적 크기
- 필요에 따라 크기가 자동으로 조절됨 ( 유연성 ↑ )
- add() 메소드로 추가
- remove() 메소드로 삭제
- clear() 전부 삭제
- 필요에 따라 크기가 자동으로 조절됨 ( 유연성 ↑ )
- 유연한 데이터 타입
- 제네릭을 사용해 다양한 타입 데이터를 저장
- 다양한 자료구조
- 순서가 있는 List, 순서가 없는 Set, 키-값 쌍으로 이루어진 Map등 다양한 구조 제공
- 풍부한 메소드
- 삽입, 삭제, 검색, 정렬 등의 기능을 손쉽게 수행할 수 있는 다양한 메소드를 제공
정리 하자면,
| 특징 | 배열 (Array) | 컬렉션 (Collection) |
| 크기 | 고정 ( 유연성 ↓ ) | 동적 ( 유연성 ↑ ) |
| 데이터 타입 | 동일한 타입만 | 제네릭으로 다양한 타입 가능 |
| 데이터 구조 | 단일 구조 | 다양한 구조 (List, Set, Map 등) |
| 사용법 | 단순 | 다양한 메소드와 구조로 인해 상대적으로 복잡하지만 유연함. |
| 성능 | 인덱스 접근이 빠름 | 데이터 구조마다 다름. |
| 메모리 사용 효율 | 고정 크기로 효율적 ( 빠름 ) | 필요에 따라 크기가 늘어나므로 비효율적 일수 있음. |
| 기본 기능 | 제한적 | 풍부한 메소드 ( 삽입, 삭제, 검색, 정렬 ) |
※ 주요 인터페이스 : List, Set, Map ★ ★
| 인터페이스 | 대표 구현 클래스 | 특징 |
| List | ArrayList, LinkedList | 순서가 있음, 중복 O |
| Set | HashSet, TreeSet | 순서가 없음, 중복 X |
| Map | HashMap, TreeMap | 키-값 쌍으로 이루어지며, 순서가 없음 중복은 키는 X ,값만 O |
ArrayList
import java.util.ArrayList;
import java.util.List;
public class ListExample {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
// 중복 데이터도 추가 가능
fruits.add("Apple");
System.out.println(fruits);
// 출력 예시: [Apple, Banana, Orange, Apple]
// 인덱스로 값 가져오기
System.out.println("인덱스 1의 값: " + fruits.get(1)); // Banana
// 반복문으로 순회
for (String fruit : fruits) {
System.out.println(fruit);
} // Apple
} // Banana
} // Orange
// Apple
HashSet
import java.util.HashSet;
import java.util.Set;
public class SetExample {
public static void main(String[] args) {
Set<String> fruits = new HashSet<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
// 중복 데이터 추가 시, 무시됨
fruits.add("Apple");
System.out.println(fruits);
// 출력 예시: [Orange, Banana, Apple] (순서는 랜덤)
// 포함 여부 확인
System.out.println("Banana가 포함되어 있나요? " + fruits.contains("Banana"));
// contains(값) 해당 값이 포함되어 있으면 T , 없으면 F
// 반복문으로 순회
for (String fruit : fruits) {
System.out.print(fruit);
} // Orange Banana Apple ( 순서는 무관 , 랜덤 )
}
}

※ 컬렉션에서 사용하는 변수 타입 ( 기본 타입 & 래퍼타입 ) ★ ★
기본 타입
- 정수형 : byte, short, int, long
- 실수형 : float, double
- 문자형 : char
- 논리형 : boolean
래퍼 타입
- 정수형 : Byte, Short, Integer, Long
- 실수형 : Float
- 문자형 : Character
- 논리형 : Boolean
컬렉션에서 래퍼 클래스를 사용하는 이유
1. 객체 지향 설계
- 자바의 컬렉션은 객체를 다루도록 설계됨. 원시타입은 객체가 아니므로, 컬렉션은 래퍼클래스 사용 권장
2. 기능 확장
- 래퍼 클래스는 원시 타입에 없는 여러 메소드를 제공함. ex) Integer.parseInt()
3. 제네릭 사용
- 자바의 제네릭은 객체 타입을 요구합니다. 원시 타입은 제네릭 타입 파라미터로 사용할수 없으므로 래퍼클래스 사용 권장함.
4. 값의 불변성
- 래퍼 클래스 불변 객체로, 멀티스레드 환경에서 안전하게 사용할 수 있습니다.
[스트림] ★ ★
배열이나 컬렉션의 데이터를 함수형(람다)로 다루기 위한 추상화된 데이터 처리 방식
반복문으로 하나씩 꺼내서 처리하던 방식을 벗어나, 중간 연산과 최종 연산을 체이닝 하여 선언적으로 데이터 처리.
※ 스트림과 I/O 스트림과는 다른 개념임 ! !
특징
- 함수형 스타일 : for문 대신 함수형 메소드로 연쇄적으로 적용해 가독성을 높임.
- 데이터 변경 X : 스트림은 원본 데이터를 변경하지 않음 ( 불변성 )
- 지연 연산 : 중간 연산을 결과를 바로 내지않고, 최종 연산이 실행될 때 한 번에 처리.
- 병렬 처리 : 스트림을 병렬로 처리해 멀티코어 환경에서 성능을 높일수 있음.★ 대표적인 스트림 쓰는이유.
스트림의 처리 단계
- 스트림 생성 (Create): 배열, 컬렉션, 혹은 기타 소스에서 스트림 객체를 생성
- 중간 연산 (Intermediate Operations): 맵핑, 필터링 등으로 데이터를 가공
- 최종 연산 (Terminal Operation): 결과를 집계하거나, 리스트로 다시 모으거나, 화면에 출력
이 과정을 체이닝(Chaining) 으로 연결하여 선언적으로 작성할 수 있습니다.
List<String> fruits = Arrays.asList("apple", "banana", "avocado", "grape");
fruits.stream() // 스트림 생성
.filter(s-> s.equals("apple")) // 중간연산 1 ( "apple"과 같으면 요소 조건)
.map(String::toUpperCase) // 중간 연산 2 ( 필터링된 요소 String타입에 , 모두다 대문자로 변환)
.forEach(System.out::println); // 최종연산
스트림 생성 방법
1. 컬렉션(Collection)에서 생성
List<String> list = Arrays.asList("a", "b", "c", "d");
Stream<String> stream = list.stream();
2. 배열에서 생성
String[] arr = {"a", "b", "c", "d"};
Stream<String> streamFromArray = Arrays.stream(arr);
3. Stream.of() 메서드 사용
Stream<String> streamOf = Stream.of("a", "b", "c", "d");
4. IntStream, LongStream, DoubleStream (기본형 특화 스트림)
- DoubleStream 과 Stream<Double>은 다름 ! !
IntStream intStream = IntStream.range(1, 5); // 1,2,3,4
Stream<int> X
중간 연산
중간 연산은 스트림을 변환하거나 필터링하여, 또 다른 스트림을 반환함.
중간 연산은 실제로 실행 되지 않고 최종 연산이 호출될 때 한꺼번에 실행됨.
필터링 - filter() ★
- 스트림 내 요소들을 조건에 따라 걸러냄.
- Predicate<T>는 boolean을 반환하는 람다식을 의미함.
List<String> list = Arrays.asList("apple", "banana", "avocado", "grape");
List<String> filteredList = list.stream() // Stream<String> , 최초 연산 ( 스트림 )
.filter(s -> s.startsWith("a")) // Stream<String> , 중간연산 (1)
.collect(Collectors.toList()); // List<String> , 최종연산 (리스트로 출력)
// 결과 : ["apple", "avocado"]
- map() ★
- 스트림 내 각 요소를 다른 형태로 변환합니다.
- Function<T, R>를 사용하여 요소를 매핑합니다.
List<String> list = Arrays.asList("apple", "banana", "orange");
List<String> mappedList = list.stream()
.map(String::toUpperCase) // 중간연산
.collect(Collectors.toList()); // 리스트로 반환. (최종연산)
// 결과: ["APPLE", "BANANA", "ORANGE"]
정렬 - sorted() ★
- 스트림 요소들을 정렬함.
- 기본적으로 오름차순으로 정렬하며, 커스텀 Comparator로 정렬 방식을 변경할 수 있음.
List<Integer> numbers = Arrays.asList(3, 1, 4, 2);
List<Integer> sortedList = numbers.stream()
.sorted() // 정렬 ( 중간연산 )
.collect(Collectors.toList()); 리스트로 반환 ( 최종연산 )
// 결과: [1, 2, 3, 4]
중복 제거 - distinct()
List<Integer> numlist = Arrays.asList(1, 2, 2, 3, 3, 3);
List<Integer> distinctList = numlist.Stream()
.distinct() // 중복 제거 (중간 연산)
.collect(Collectors.toList()); // 리스트로 반환 (최종 연산)
// 결과 : [1, 2, 3]
제한/건너뛰기
limit(n) - 스트림에서 최대 n개의 요소 까지만 추출
skip(p) - 스트림에서 처음 n개의 요소를 건너 뛰고 이후 요소만 가져옴.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> limited = numbers.stream()
.limit(3)
.collect(Collectors.toList());
// [1, 2, 3]
List<Integer> skipped = numbers.stream()
.skip(2)
.collect(Collectors.toList());
// [3, 4, 5]
최종 연산
최종 연산이 호출되면 스트림은 소모되며, 더 이상 재사용이 불가능함.
forEach()
- 스트림의 각 요소를 소비하며, 주어진 동작을 수행함.
list.stream()
.forEach(System.out::println);
collect() ★
- 스트림의 요소를 List, Set, Map 등으로 모아서 반환함.
List<String> collectedList = list.stream().collect(Collectors.toList());
reduce() ★
- 스트림의 요소를 하나씩 줄여가면서(계산) 하나의 결과를 생성.
그 외 최종연산 메소드
- count(): 스트림의 요소 개수 반환
- max(), min(): 최대값, 최소값 반환 (Comparator 필요)
- anyMatch(), allMatch(), noneMatch(): 특정 조건에 부합하는지 검사 후 boolean 결과 반환
- findFirst(), findAny(): 스트림의 첫 번째 혹은 임의 요소 반환 (Optional)
스트림의 지연 연산(Lazy Evaluation) 예시
중간 연산은 최종 연산이 일어나기 전까지 실제로 실행되지 않습니다. 이를 Lazy Evaluation(지연 연산) 이라고 함.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class LazyEvaluationExample {
public static void main(String[] args) {
List<String> list = Arrays.asList("apple", "banana", "orange", "grape", "melon");
// 중간 연산만 정의
Stream<String> stream = list.stream()
.filter(s -> {
System.out.println("filtering: " + s);
return s.startsWith("a");
})
.map(s -> {
System.out.println("mapping: " + s);
return s.toUpperCase();
});
System.out.println("스트림이 정의되었지만 아직 처리되지 않았습니다.");
// 최종 연산을 호출하는 순간, 그제야 모든 중간 연산이 실행됨
List<String> result = stream.collect(Collectors.toList());
System.out.println("결과: " + result);
}
}
실행 흐름
- filter()와 map()은 중간 연산으로, 즉시 실행되지 않음
- collect()(최종 연산)가 호출될 때 filter()와 map()이 실제 실행
※ IntStream.range() 와 IntStream.rangeClosed() 차이 ★\
System.out.println(IntStream.range(1,10).boxed().toList()); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
System.out.println(IntStream.rangeClosed(1,10).boxed().toList()); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// 인덱스 끝값이 포함되냐 안되냐의 차이임.
2. TMI - 오늘은 개인일정으로 인해 외출을 3~4시간정도 다녀와 복귀후 집중력이 좀 흐려져서 피로가 좀 쌓이고 피곤했다.
3. 내일 학습 할것 : 키오스크 과제 해설, 계산기 과제 피드백 자가점검 , 알고리즘
'[내일배움캠프-Sparta] > Spring 6기' 카테고리의 다른 글
| TIL 20 [ Spring( 네트워크, 용어, HTTP, Rest API ) ] (0) | 2025.03.17 |
|---|---|
| TIL 19 [ Java (스트림) , 미니세션 (클래스,객체) + 키오스크 과제 해설 ] (0) | 2025.03.14 |
| TIL 17 [ 키오스크 과제 (도전🔥 1단계) + Intellij 깃 사용법 ] (0) | 2025.03.12 |
| TIL 16 [ 키오스크 과제 5단계 , Java (Enum, 동일성 vs 동등성) ] (0) | 2025.03.11 |
| TIL 15 [ 키오스크 과제(3~4단계) ] (0) | 2025.03.10 |