1. set 계열
- 중복, 순서 X
- 하나의 집합
set이라는 인터프리터를 이용해 만든 클래스들
- HashSet : 순서를 유지하지 않음, 가장 일반적인 Set
- LinkedHashSet : 입력 순서를 유지, 메모리를 연결-연결 해서 쓰는 거라서 순서 유지함
- TreeSet : 자동 정렬 (오름차순), 내부적으로 트리 구조 사용 (이진 트리 기본)
- 트리구조 : 검색이 빠름
배열구조로 저장이 되면 인덱스를 모를 경우 다 뒤져봐야해서 속도가 느리다.
insert 하거나 sort 할 땐 느릴 수 있지만 검색할 땐 속도가 반으로 줄어든다.
예제 : HashSet
package lesson07;
import java.util.HashSet;
public class Ex01_Main {
public static void main(String[] args) {
HashSet<String> set = new HashSet<String>(); // 고정된 길이를 갖는다. hash 알고리즘을 사용한다. set 중복되지 않음
set.add("김사과");
set.add("반하나");
set.add("오렌지");
set.add("이메론");
set.add("배애리");
// 중복 데이터 : 추가되지 않음 : [반하나, 이메론, 김사과, 오렌지, 배애리]
set.add("김사과");
set.add("배애리");
System.out.println(set);
// [반하나, 이메론, 김사과, 오렌지, 배애리] : 순서가 없는 모습
// 내부적으로 순서가 없더라도 반복문은 돌릴 수 있는 자료구조임
System.out.println("출석한 학생 명단 ");
for(String name : set) {
System.out.println("- " + name);
}
// 출석한 학생 명단
//- 반하나
//- 이메론
//- 김사과
//- 오렌지
//- 배애리
// 데이터 있는 지 찾아보기 : contains
System.out.println("오렌지 출석 여부 : "+ set.contains("오렌지")); //오렌지 출석 여부 : true
System.out.println("박수박 출석 여부 : "+ set.contains("박수박")); //박수박 출석 여부 : false
// 삭제
set.remove("이메론");
for(String name : set) {
System.out.println("- " + name);
}
//- 반하나
//- 김사과
//- 오렌지
//- 배애리
// 자바스크립트, json은 key : value가 있다. set은 순서가 없기 때문에 인덱스도 없고, key도 없다. 그래서 value를 꺼낼 방법이 없어서 value 자체로 찾아낸다. (오렌지, 박수박 직접 입력)
}
}
예제 : TreeSet
package lesson07;
import java.util.TreeSet;
public class Ex02_Main {
public static void main(String[] args) {
TreeSet<String> set = new TreeSet<String>();
// 저장과 삭제는 느릴 수 있지만, 검색이 빠르다. 정렬도 함.
set.add("김사과");
set.add("반하나");
set.add("오렌지");
set.add("이메론");
set.add("배애리");
set.add("김사과");
System.out.println(set);
// [김사과, 반하나, 배애리, 오렌지, 이메론] : 중복데이터를 넣었지만, 추가 X , 오름차순으로 정렬
// 정렬되는 상황? add 될 때 정렬을 하고 들어감 (그래서 insert 될 때 느린 것)
for (String name : set) {
System.out.println("- " + name);
//- 김사과
//- 반하나
//- 배애리
//- 오렌지
//- 이메론
}
// 앞에 오는 값 : lower, 뒤에오는 값 : higher
System.out.println("오렌지보다 앞에 있는 이름 찾기 : " + set.lower("오렌지")); // 배애리
System.out.println("오렌지보다 뒤에 있는 이름 찾기 : " + set.higher("오렌지")); // 이메론
// key:value가 없기 때문에 value 자체로 찾아야 함
// 만약 내림차순을 하고 싶다면? Comparable을 오버라이딩 하면 됨
// Comparable이란?
}
}
* Comparble이란?
자바에서 객체를 정렬 가능하게 만들기 위한 인터페이스
public interface Comparable<T> {
int compareTo(T o);
}
- 핵심 목적 : sort(), priorityQueue, TreeSet 같은 자료 구조에서 객체끼리 순서를 비교할 수 있게 하기 위해 쓰임
예제 :
public class Student implements Comparable<Student> {
String name;
int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
// 오름차순 정렬 (score 기준)
@Override
public int compareTo(Student other) {
return this.score - other.score; // 낮은 점수부터 정렬됨
}
@Override
public int compareTo(Student other) {
return other.score - this.score; // 높은 점수부터 정렬됨
}
}
* 빅오 표기법이란?
알고리즘의 효율성을 표기해주는 표기법
알고리즘의 효율성은 데이터 개수(n)가 주어졌을 때 덧셈, 뺄셈, 곱셈 같은 기본 연산의 횟수를 의미
시간복잡도, 공간 복잡도를 나타내는데 주로 사용됨
n이 상수에 가까울 수록 빠르고 좋은 것.
예제 : 이름으로 내림차순
package lesson07;
import java.util.Comparator;
import java.util.TreeSet;
class Ex04_Student {
String name;
int score;
// 생성자 만들기
public Ex04_Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return name + "(" + score + "점)";
}
}
public class Ex04_MAin {
public static void main(String[] args) {
Comparator<Ex04_Student> comparator= (a,b) -> b.name.compareTo(a.name);
TreeSet<Ex04_Student> students = new TreeSet<>(comparator);
// <> 안에 들어갈 수 있는 건 레퍼 클래스 등을 넣었는데, Student객체를 넣으면 트리셋에 쌓일 것임. Student에는 이름,점수 2개가 들어있음 그렇다면 뭐로 정렬을 시킬까?
students.add(new Ex04_Student("김사과", 90));
students.add(new Ex04_Student("반하나", 85));
students.add(new Ex04_Student("오렌지", 95));
students.add(new Ex04_Student("이메론", 92));
students.add(new Ex04_Student("배애리", 85));
// 어떻게 들어가있는지 확인해보자
for (Ex04_Student student : students) {
System.out.println("- " + student);
}
}
}
2. Map 계열 (많이 사용함)
- 데이터를 key-value 쌍으로 저장
-Key는 중복될 수 없음, Value는 중복 가능
- 값을 검색할 땐 key를 사용 (map.get("name"))
- userid라는 키를 만들어서 아이디를 저장하거나, userpw를 만들어서 pw를 저장할 때 사용한다.
package lesson07;
import java.util.HashMap;
import java.util.Map;
public class Ex05_Main {
public static void main(String[] args) {
HashMap<String, Integer> scoreMap = new HashMap<>();
scoreMap.put("김사과", 90);
scoreMap.put("반하나", 85);
scoreMap.put("오렌지", 95);
scoreMap.put("이메론", 88);
scoreMap.put("배애리", 92);
System.out.println(scoreMap.entrySet());
// [반하나=85, 이메론=88, 김사과=90, 오렌지=95, 배애리=92] : key=value 형태로 저장하고 있음
for(Map.Entry<String, Integer> entry : scoreMap.entrySet()){
// entry 저장 방식 key=value
System.out.println(entry.getKey() + ": " + entry.getValue()+"점"); // key, value를 따로 갖고 나옴
//반하나: 85점
//이메론: 88점
//김사과: 90점
//오렌지: 95점
//배애리: 92점
}
String name = "오렌지";
System.out.println(name + "의 점수: " + scoreMap.get(name));
// get 안에 오렌지를 넣어주게 되면, 점수만 출력됨
// 만약, map에 key를 동일한 걸 집어 넣게 되면? update 된다.
scoreMap.put("김사과",100);
System.out.println("김사과의 점수 수정 후 : " + scoreMap.get("김사과"));
scoreMap.remove("반하나");
for(Map.Entry<String, Integer> entry : scoreMap.entrySet()) {
// entry 저장 방식 key=value
System.out.println(entry.getKey() + ": " + entry.getValue() + "점"); // key, value를 따로 갖고 나옴
// 반하나 없어짐
}
System.out.println("전체 학생 수: " + scoreMap.size() + "명");
}
}
3. DTO = Data Transfer Object
데이터를 담아서 전달하는 용도의 객체
=> 데이터만 들고 다니는 택배상자 같은 것
- 로직(기능)은 없음
- 데이터 필드만 다님 (필드+getter/setter + 생성자)
왜 쓰는가?
1. 계층 간 데이터 전달용
- controller -> service -> Repository
이런 구조에서 중간중간 필요한 데이터만 뽑아서 전달
2. DB나 외부 API에서 받은 데이터를 포장해서 사용
- DB에서 받은 Entity를 그대로 노출하지않음 (보안/성능 문제)
필요한 정보만 담아서 DTO로 전달함
예 : 회원정보 DTO
public class MemberDTO {
private String name;
private int age;
private String email;
// 생성자, getter, setter
}
- 이 객체는 그냥 name, age, email을 담아서 넘기기만 하는 용도 (비즈니스 로직, 계산, 검증 같은 건 안함)
예 : DTO가 아닌 예 (비교)
public class Member {
private String name;
private int age;
public boolean isAdult() {
return this.age >= 20;
}
}
- 데이터 + 로직이 있기 때문에 DTO가 아님
예제 : 단어장
package lesson07;
/*
[사용자 입력] > controller > service > Repo > DTO 저장
*/
import java.time.LocalDate;
import java.util.*;
// 데이터 상자 역할, 전달 전용 객체, DB처럼 값을 담고 주고받을 때 사용함.
class WordDTO {
// 데이터 베이스에 저장할 내용이 있다면 그거랑 동일하게 필드를 구성하는 객체 이면서, 이걸 서비스 단으로 주고받고 할겨
private String word; //영단어
private String meaning; // 한국어
private String level;
private LocalDate regDate;
// private로 막아뒀기 때문에 getter, setter로 가져와서 써야함.
// 생성자 만들기 (마우스 우클릭으로 다 선택해서 만들어버령)
public WordDTO(String word, String meaning, String level) {
this.word = word;
this.meaning = meaning;
this.level = level;
this.regDate = LocalDate.now();
}
// getter/setter도 마우스 우클릭으로 다 만들기
public String getWord() {
return word;
}
public void setWord(String word) {
this.word = word;
}
public String getMeaning() {
return meaning;
}
public void setMeaning(String meaning) {
this.meaning = meaning;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public LocalDate getRegDate() {
return regDate;
}
public void setRegDate(LocalDate regDate) {
this.regDate = regDate;
}
@Override
public String toString() {
return "[단어] : " + word + " | 뜻 : " + meaning + " | 레벨 : " + level + " | 등록일 : " + regDate;
}
}
// 사용자 요청을 처리하는 역할 (등록을 받으면, wordService에 전달, 2번의 요청도 여기서 받음)
class WordController {
private final WordService service = new WordService();
// 단어 등록
public void register(String word, String meaning, int levelNum) {
String level = ConvertLevel(levelNum);
// 레벨부터 필터링
if(level == null) {
System.out.println("잘못된 레벨입니다.(1: 초급, 2: 중급, 3: 고급)");
return;
}
try{
WordDTO dto = new WordDTO(word, meaning, level);
service.registerWord(dto);
} catch(IllegalArgumentException e) { //파라미터가 문제 있다면
System.out.println("등록 실패 : " + e.getMessage());
}
}
// 전체 단어 조회
public void printAllWords() {
List<WordDTO> list = service.getAllwords();
if(list.isEmpty()) {
System.out.println("등록된 단어가 없습니다");
} else {
System.out.println("등록된 단어 목록");
for(WordDTO dto : list) {
System.out.println("- " + dto);
}
}
}
// 단어 상세 조회
public void getWord(String word) {
WordDTO dto = service.getWord(word);
if(dto == null) {
System.out.println(word + " 단어는 등록되어있지 않습니다.");
} else {
System.out.println("- " + dto);
}
}
// 레벨에 대한 함수 (그냥 만듦)
private String ConvertLevel(int levelNum) {
return switch (levelNum) {
case 1 -> "초급";
case 2 -> "중급";
case 3 -> "고급";
default -> null;
};
}
}
// controller와 Repository 사이에서 중간 다리 역할
class WordService {
private final WordRepository repository = new WordRepository();
// 단어 등록 : 유효성 검사 하고 Repo로 전달
public void registerWord(WordDTO dto) {
if(dto.getWord().isBlank() || dto.getMeaning().isBlank()) {throw new IllegalArgumentException("단어와 뜻은 반드시 입력해야 합니다.");}
repository.save(dto);
}
// 전체 단어 조회 : controller에서 요청 오면 바로 Repo에 요청
public List<WordDTO> getAllwords() {
return repository.findAll();
}
// 단어 상세 조회
public WordDTO getWord(String word) {
return repository.findWord(word);
}
}
// 임시 DB 역할 : 나중에 DB 연결 시 이 부분만 바꾸면 됨. 실제로 단어를 저장하고 불러오는 역할
class WordRepository {
private final Map<String, WordDTO> wordMap = new HashMap<>();
// 단어 저장
public void save(WordDTO dto) {
wordMap.put(dto.getWord(), dto);
System.out.println("저장 완료: " + dto.getWord());
}
// 단어 전체 조회하는 것 (value들만 뽑아서 array에 담아줄게)
public List<WordDTO> findAll() {
return new ArrayList<>(wordMap.values());
}
public WordDTO findWord(String word) {
return wordMap.get(word);
}
}
// 프로그램 시작점 + 사용자 입력 받는 콘솔 UI
public class Ex06_Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
WordController controller = new WordController();
while(true) {
System.out.println("영어 단어 사전");
System.out.println("1. 단어 등록");
System.out.println("2. 전체 단어 목록 보기");
System.out.println("3. 단어 상세 조회하기");
System.out.println("0. 종료하기");
System.out.print("선택 : ");
int choice = Integer.parseInt(sc.nextLine());
switch(choice) {
case 0 -> {
System.out.println("종료");
sc.close();
return; //메인 메소드 빠져나가기
}
case 1 -> {
System.out.print("영단어 입력: ");
String word = sc.nextLine();
System.out.print("뜻 입력: ");
String meaning = sc.nextLine();
System.out.print("레벨 선택 (1: 초급, 2: 중급, 3: 고급): ");
int level = Integer.parseInt(sc.nextLine());
controller.register(word, meaning, level);
}
case 2 -> {
controller.printAllWords();
}
case 3 -> {
System.out.println("조회할 영단어 입력 :");
String word = sc.nextLine();
controller.getWord(word);
// 입력한 단어만 출력하는 것
}
default -> System.out.println("잘못된 메뉴 입니다");
}
}
}
}
4. VO : 값을 표현하기 위한 객체
- 변하지 않는 고정된 정보
- 비교 기준이 '주소'가 아닌 '값'
특징 3가지
- 불변성 : 한 번 만들면 값을 바꿀 수 없음 (setter 없음)
- 값 기반 비교 : 주소(==)가 아닌 값(.equals())으로 비교
- 재사용 가능 : 같은 값이면 다른 객체로 만들어도 같다고 침
예제 : Money라는 VO
public class Money {
private final int amount;
public Money(int amount) {
this.amount = amount;
}
public int getAmount() {
return amount;
}
// 값 기반 비교를 위해 equals & hashCode 구현
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Money)) return false;
Money money = (Money) o;
return amount == money.amount;
}
@Override
public int hashCode() {
return Objects.hash(amount);
}
}
--------------------------------------------------------------------------------------
Money m1 = new Money(1000);
Money m2 = new Money(1000);
System.out.println(m1 == m2); // false (주소 다름)
System.out.println(m1.equals(m2)); // true (값 같음)
이걸 왜 만들었나?
- 돈이라는 값을 안전하고 명확하게 다루기 위해서
🤔 왜 이렇게까지 만들어야 하나요?
단순히 숫자만 사용하면 다음과 같은 문제가 발생할 수 있습니다
반면 Money 객체는 다음처럼 의도가 명확해집니다.
예제 : 배달 가능 지역 확인 시스템 - VO 먼저 만들기
package lesson07;
import java.util.Objects;
class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public double distance(Point other) {
// 현재 내가 저장하고 있는 x값과 받은 x값을 빼서 dx가 나옴
int dx = this.x - other.x;
int dy = this.y - other.y;
return Math.sqrt(dx * dx + dy * dy); // 거리 구하는 피타고라스 공식
}
public int getX() {return x;}
public int getY() {return y;}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true; //메모리 위치가 같으면 true
if (!(obj instanceof Point)) return false; // 클래스 자체가 다른 걸로 만들어졌다면 비교할 것도 없어요! 같은 클래스거나 얘를 상속받아 만든거면 true가 나오게 되어있는데 그게 아니라면 false라고 나옴
Point other = (Point) obj; // 다운 캐스팅
return x == other.x && y == other.y; //x값이 other.x와 같고.... 둘다 완벽하게 맞았을 때만 true, 아니면 false
}
@Override
public String toString() {
return "(" + x + ", " + y + ")";
}
}
public class Ex07_Main {
}
- StoreService, main 만들기
class StoreService{
private final Point storeLocation = new Point(0, 0);
public boolean canDeliver(Point customerLocation) {
double distance = storeLocation.distance(customerLocation);
System.out.println("거리계산 : " + distance);
return distance <= 5.0;
}
}
public class Ex07_Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
StoreService service = new StoreService();
System.out.println("가게 기준점은 (0,0)입니다.");
System.out.print("고객 위치 x 좌표를 입력 : ");
int x = Integer.parseInt(sc.nextLine());
System.out.print("고객 위치 y 좌표를 입력 : ");
int y = Integer.parseInt(sc.nextLine());
Point customer = new Point(x, y);
System.out.println("고객 위치 : " + customer);
if(service.canDeliver(customer)) {
System.out.println("배달 가능 지역입니다.");
} else {
System.out.println("배달 불가능 지역입니다.");
}
sc.close();
}
}
+ ) DTO와 VO의 차이점
항목 DTO (Data Transfer Object) VO (Value Object)
목적 | 데이터 전달용 (입·출력) | 값 표현용 (의미 있는 단위) |
변경 가능성 | 있음 (setter 사용) | ❌ 없음 (Immutable) |
비교 기준 | 객체 자체 (주소 기준) | 값 기준 (equals()) |
예시 | UserDTO, OrderDTO | Money, Coordinate, Email, Address |
사용하는 곳 | Controller ↔ Service ↔ 외부 | Entity, Domain 내부 |
DTO가 택배 박스라면, VO는 신분증 (이게 누구인지 증명하는 고유 정보) 이라고 할 수 있다.
'Programming > Java' 카테고리의 다른 글
메서드, return, 객체지향 프로그래밍, 객체, 생성자, 오버로딩 Null, 가비지컬렉터, 패키지, 상속, 메서드 오버라이딩, final (1) | 2025.06.02 |
---|---|
조건문 (if, else-if, switch, yield), 반복문 (while, do-while, for), Random 클래스, Break문, Continue, 향상된 for 문, 중첩 반복문 (4) | 2025.06.02 |
인터페이스, 예외처리 (0) | 2025.06.02 |
자바, 변수, 데이터 타입, 형 변환, 연산자, Scanner API, System.in, 배열, 2차원 배열, 얕은 복사와 깊은 복사 (0) | 2025.06.02 |
recode (0) | 2025.05.28 |