아래 다이어그램을 참고해 Account 인스턴스가 마이너스 금액의 입금을 요청받는 경우 작업을 거부하고 예외를 던지는 코드를 작성하라.
Account
balance: int
deposit(int amount): void
class Account {
// 계좌 잔액
int balance = 0; // 정수형 변수는 값을 할당안하면 자동으로 0
// 입금처리
void deposit (int amount) { // amount: 입금액
if (amount < 0) { // 입금액이 0보다 커야함
// 예외를 던진다
// IllegalStateException (잘못된 인자 예외)
// 메서드가 잘못된 인자를 받았을 때 발생
throw new IllegalStateException
("유효하지 않은 금액");
// 생성자에 전달한 값은 예외 메시지가 됨
}
this.balance += amount;
}
}
public class Main {
public static void main(String[] args) {
Account account = new Account();
try{
// 예외가 발생할 수 있는 부분
account.deposit(100);
System.out.println("입금이 완료되었습니다.");
} catch (Exception e) { // 던진 예외를 잡습니다(catch)
System.out.println("예외 발생! " + e.getMessage());
}
System.out.println("잔액은 " + account.balance + " 입니다");
}
}
Account 클래스를 만들어 balace(계좌 잔액)을 0으로 설정하였다.
그 다음 deposit 메서드를 만들었고 amount는 입금할 금액이다.
만약 amount(입금 금액)가 0보다 작은지 확인한다.
음수는 입금할 수 없으므로 예외를 발생시킨다.
throw를 통해 예외 객체를 생성한 후 던진다.
프로그램은 즉시 메서드 실행을 중단하고 catch 블록으로 이동한다.
this.balance += amount; -> 잔액에 입금액을 더한다.
그 다음 Account 객체를 생성하였다.
try를 통해 예외가 발생할 수 있는 코드를 실행하는 영역을 만들었다.
100원을 입금하면 100은 음수가 아니므로 예외가 발생하지 않는다.
하지만 -100을 입금하면 음수이므로 예외메시지가 나오게 된다.
현재 코드는 예외가 발생하지 않았으므로 정상 실행이 된다.
예외 발생 여부와 관계없이 추가적으로 필요한 작업이 있을 때 finally문을 활용할 수 있다.
대표적인 사용 사례로 자원 해제가 있다.
catch문 뒤에 연결해서 작성한다.
Scanner를 활용해 사용자로부터 나이를 입력받는 코드를 작성하라.
단 사용자가 문자를 입력하는 등의 예외 상황이 발생하더라도 스캐너가 닫혀야 한다.
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int age;
try {
System.out.println("몇 살이니?");
// nextInt: 입력값이 정수가 아니면 예외를 던진다
age = scanner.nextInt();
System.out.println("나이 " + age);
} catch (Exception e) {
// getClass: 클래스의 이름을 반환
System.out.println("예외 발생! " + e.getClass());
} finally { // 예외 발생여부와 상관없이 항상 실행된다
scanner.close();
}
}
}
이 코드는 사용자로부터 나이를 입력받고 잘못된 입력이 들어왔을때 예외를 처리하는 예제이다.
먼저 Scanner 객체를 생성해 키보드 입력을 받을 준비를 한다.
그 다음 try 블럭에서 "몇 살이니?"를 출력하고 scanner.nextInt()를 사용해 정수를 입력받는다.
만약 사용자가 20처럼 정상적인 정수를 입력하면 변수 age에 저장이 되고 "나이 20"과 같은 결과가 출력된다.
하지만 nextInt()는 정수만 입력받을 수 있기 때문에 사용자가 "hello"같은 문자열을 입력하면 예외가 발생한다.
이 경우 프로그램의 흐름은 catch 블록으로 이동한다.
catch 블록에서는 사용한 예외 객체를 받아서 처리한다.
e.getClass()를 사용하면 어떤 종류의 예외가 발생했는지 확인할 수 있다.
예를 들어 문자열을 입력했다면 InputMismatchException 관련 정보가 출력된다.
마지막으로 finally 블록이 실행된다.
finally는 예외 발생 여부와 상관없이 항상 실행되는 영역이다.
여기서는 scanner.close()를 호출해 Scanner 자원을 정리한다.
예외 처리를 해당 메서드를 호출하는 대상에게 전가하는 방법이다.
체크 예외(Check Exception)는 내부에서 예외처리를 하지 않으면 반드시 throws를 추가해야 한다.(언체크 예외는 선택사항)
주로 일괄적으로 예외를 처리하고 싶을 때 사용할 수 있다.
메서드_이름(매개변수) throws 예외 타입
File 인스턴스의 createFile()을 호출하는 메서드를 작성하라.
단 메서드 내에서 예외처리를 하지 말고 이 메서드를 호출하는 상위 객체에게 예외 처리를 전가하라.
import java.io.File;
import java.io.IOException;
public class Main {
// throws를 사용해 IOException의 처리를
// 이 메스드를 호출하는 대상에게 전가합니다
static boolean createFile(String name) throws IOException{
File file = new File(name);
// createFile: 체크 예외를 던지는 메스드
// 즉 반드시 예외처리를 해야 한다. 컴파일 자체가 안됨
// try-catch 또는 throws를 사용해 예외처리를 해야 됨
if (file.createNewFile()) {
return true;
}
return false;
}
public static void main(String[] args) {
// 이 try-catch에서 예외를 한 번에 처리하겠다
try {
createFile("foo");
// 예외를 발생시키는 다른 메서드들
// foo()
// bar()
// baz()
} catch (Exception e) {
System.out.println("예외 발생" + e.getMessage());
}
}
}
이 코드는 Checked Exception(체크 예외)을 throws로 위임하고 최종적으로 try-catch에서 처리하는 예제이다.
먼저 creatFile()메서드는 전달받은 이름으로 파일을 생성하는 역할을 한다.
여기서 중요한 부분은 throws IOException이다.
File 클래스의 createNewFile() 메서드는 IOException을 발생시킬 수 있다.
IOException은 체크 예외이므로 컴파일러가 반드시 예외처리를 요구한다.
그래서 creatFile() 메서드는 직접 try-catch로 처리하지 않고 throws IOException를 사용하여 이 예외는 나를 호출한 쪽에서 처리하세요. 라고 책임을 넘긴다.
그 다음 메서드 내부에서 파일 객체를 생성 후 file.createNewFile()을 호출해 실제 파일 생성 작업을 수행한다.
파일 생성에 성공하면 return true;를 반환하고 이미 파일이 존재하거나 생성하지 못하면 return false;를 반환한다.
main에서는 createfile("foo")를 호출한다.
하지만 creatFile()이 IOException을 던질 수 있으므로 호출하는 쪽에서 반드시 처리해야 한다.
그래서 try-catch로 감싸서 예외를 처리한다.
만약 파일 생성 과정에서 문제가 생기면 catch 블록이 실행되고 print를 통해 예외 메시지를 출력한다.
배열, 연결 리스트, 스택, 큐, 해시 테이블 등의 자료 구조(Data Structure)를 제공하는 자바 라이브러리이다.
쉽게 말해 자바에서 다수의 데이터(객체)를 효율적으로 저장, 관리, 조작하기 위해 사용하는 동적인 자료형을 의미한다.

순서가 있고, 중복을 허용한다. 일반적으로 가장 많이 쓰는 구조이다.
ArrayList(배열 리스트): 데이터의 추가/ 삭제보다 조회가 많을 때 유리하다.
LinkedList(연결 리스트): 데이터 조회보다 데이터의 추가/삭제가 빈번할 때 더 적합하다.
수학의 집합(Set) 개념이다. 순서가 없고 중복을 허용하지 않는다.
HashSet: 빠른 조회가 특징인 대표적인 Set이다.
TreeSet: 데이터가 들어오면 자동으로 정렬(오름차순)해 준다.
각 요소가 키-값의 쌍으로 구성된 자료형이다.
고유한 키를 통해 값에 접근하는 방식이다. (값은 중복 가능)
키(Key): 값(Value)
List 인터페이스를 구현한 대표적인 클래스이다.
일반 배열과 달리 크기를 동적으로 조절할 수 있다.

문자열 배열리스트 Persons를 생성하고 아래의 값들을 추가해보세요
첫번째 요소: John Doe
두번째 요소: Jane Doe
세번째 요소: Mary Doe
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
// 배열 리스트
// 지네릭: 요소의 타입
ArrayList<String> persons = new ArrayList<>();
// 빈 배열리스트를 생성합니다
// 배열리스트에 요소를 추가하는 메서드
persons.add("John Doe");
persons.add("Jane Doe");
persons.add("Mary Doe");
}
}
이 문제는 ArrayList를 생성하고 문자열 데이터를 저장하는 가장 기본적인 예제이다.
먼저 ArrayListMString>을 사용하여 문자열만 저장할 수 있는 ArrayList를 생성한다.
여기서 < String >은 지네릭이다.
즉 이 ArrayList에는 문자열만 저장할 수 있다는 의미이다.
만약 정수나 다른 객체를 추가하려고 하면 컴파일 오류가 발생한다.
그 다음 add() 메서드를 사용해 데이터를 추가한다.
add()는 ArrayList의 가장 뒤에 새로운 요소를 추가하는 메서드이다.
실행이 끝나면 ArrayList 안에는 다음과 같은 데이터가 저장된다.
이 예제의 핵심은 ArrayList라는 데이터를 동적으로 저장할 수 있는 컬렉션이라는 점이다.
배열은 처음에 크기를 정해야 하지만 String[] persons = new String[3];처럼 크기가 고정된다.
반면 ArrayList는 persons.add(...)를 호출할 때마다 크기가 자동으로 늘어난다.
따라서 저장할 데이터의 개수가 계속 변할 수 있는 경우 ArrayList를 사용하는 것이 훨씬 편리하다.
Persons 리스트의 각각의 요소에 접근하라
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
// 배열 생성
ArrayList<String> persons = new ArrayList<>();
// 요소 추가
persons.add("John Doe");
persons.add("Jane Doe");
persons.add("Mary Doe");
// 각 요소에 접근하기 - 인덱스로 접근
System.out.println(persons.get(0));
System.out.println(persons.get(1));
System.out.println(persons.get(2));
}
}
먼저 ArrayLst< String >을 생성해 문자열만 저장할 수 있는 리스트를 만든다.
그 다음 add() 메서드를 사용해 리스트 내용을 저장한다.
get() 메서드는 저장한 인덱스의 요소를 반환한다.
따라서 각 위치에 저장된 문자열이 출력된다.
이 코드의 핵심은 ArrayList에 데이터를 저장할 때는 add()를 사용하고 저장된 데이터에 접근할 때는 get(인덱스)를 사용한다는 점이다.
배열처럼 0부터 시작하는 인덱스로 요소를 조회할 수 있다.
Persons 리스트에서 첫번째 요소를 Steve Jobs로 바꾸어라.
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> persons = new ArrayList<String>();
persons.add("John Doe");
persons.add("Jane Doe");
persons.add("Mary Doe");
// 바뀌기 전
System.out.println(persons);
// set(바꿀 위치, 새 값)
persons.set(0, "Steve Jobs");
System.out.println(persons.get(0));
}
}
set() 메서드는 특정 위치의 값을 새로운 값으로 변경시키는 메서드이다.
첫번째 매개변수 -> 변경할 인덱스
두번째 매개번수 -> 새로 저장할 값
따라서 인덱스 0에 있던 John Doe가 Steve Jobs로 변경된다.
persons.get(0)을 통해 0번 인덱스의 값을 출력한다.
persons 리스트에서 첫번째 요소를 제거해보세요
public class Main {
public static void main(String[] args) {
ArrayList<String> persons = new ArrayList<String>();
persons.add("John Doe");
persons.add("Jane Doe");
persons.add("Mary Doe");
// 제거 전
System.out.println(persons);
// 제거하기
persons.remove(0); // John Doe
// 제거 확인
System.out.println(persons);
}
}
remove() 메서드는 지정한 인덱스의 요소를 삭제하는 메서드이다.
여기서는 인덱스 0에 있는 John Doe가 삭제된다.
삭제가 이루어지면 뒤에 있던 요소들이 한 칸씩 앞으로 이동한다.
마지막으로 System.out.println(persons);를 통해 삭제 후의 리스트를 출력한다.
persons 리스트의 크기를 구하라
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> persons = new ArrayList<String>();
persons.add("John Doe");
persons.add("Jane Doe");
persons.add("Mary Doe");
// 크기 구하기: size 메서드
System.out.println(persons.size());
}
}
size() 메서드는 ArrayList에 저장된 요소의 개수를 반환하는 메서드이다.
따라서 현재 리스트에는 3개의 데이터가 있으므로 출력결과는 3이 나온다.
현실 세계의 '집합'을 프로그램으로 구현한 구조이다.
순서가 없고 중복을 허용하지 않는다.

Apple, Banana, Orange로 구성된 집합 fruits를 생성하라.
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
// 빈 집합을 생성
Set<String> fruits = new HashSet<>();
// 요소 추가
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
System.out.println(fruits);
}
}
먼저 Set< String >을 선언하고 HashSet 객체를 생성한다.
Set은 중복된 데이터를 허용하지 않는 자료구조이다.
그리고 HashSet은 Set 인터페이스를 구현한 대표적인 클래스이다.
그 다음 add() 메서드를 사용해 과일 이름을 추가한다.
이렇게 하면 Set 안에 3개의 문자열이 저장된다.
다만 HashSet은 저장 순서를 보장하지 않는다.
그래서 상황에 따라 달느 순서로 출력될 수도 있다.
fruits가 특정 과일을 포함하는지 여부를 검사하라.
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<String> fruits = new HashSet<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
System.out.println(fruits.contains("Apple"));
System.out.println(fruits.contains("Mango"));
}
}
contains()는 지정한 요소가 Set 안에 존재하는지 확인하는 메서드이다.
Set 안에 "Apple"이 있으므로 결과는 true가 출력된다.
두번째로는 "Mango"가 있는지 확인한다
Set 안에 "Mango"가 없으므로 결과는 false가 출력된다.
fruits 집합으로부터 특정 과일을 제거해보세요
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<String> fruits = new HashSet<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
System.out.println(fruits);
fruits.remove("Orange");
System.out.println(fruits);
}
}
remove() 메서드는 지정한 요소를 Set에서 삭제하는 메서드이다.
여기서는 "Orange"를 삭제하므로 Set에는 "Apple"과 "Banana"만 남게 된다.
마지막으로 삭제 후의 Set을 출력한다.
실제 출력 순서는 HashSet의 특성상 달라질 수 있다.
fruits의 크기를 구하라.
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Set<String> fruits = new HashSet<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
System.out.println(fruits.size());
}
}
size() 메서드는 Set에 저장된 요소의 개수를 반환하는 메서드이다.
현재 Set에는 총 3개의 데이터만이 들어있으므로 결과는 3이 출력된다.
각 요소가 키-값의 쌍으로 구성된 자료형이다.
고유한 키를 통해 값에 접근하는 방식이다. (값은 중복 가능)
(Key) : 값(Value)

아래와 같이 국가-수도 쌍의 요소로 이루어진 맵 capitalCities를 생성하라.
국가(키) : 수도(값)
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
// Map <키 자료형, 값 자료형>
Map<String, String> capitalCities = new HashMap<>();
capitalCities.put("S.Korea", "Seoul");
capitalCities.put("Japan", "Tokyo");
capitalCities.put("USA", "Washington D.C");
capitalCities.put("Germany", "Berlin");
System.out.println(capitalCities);
}
}
이 코드는 HashMap에 키와 값을 저장하는 예제이다.
먼저 Map< String, String >을 선언하고 HashMap 객체를 생성한다.
Map은 키와 값을 한 쌍으로 저장하는 자료구조이다.
여기서는 Key는 국가이름, Value는 수도 이름을 저장하도록 만들었다.
그 다음 put() 메서드를 사용해 데이터를 추가한다.
put(키, 값)은 Map에 데이터를 저장하는 메서드이다.
예를 들어 capitalCities.put("Japan", "Tokyo");는 "Japan"이라는 키에 "Tokyo" 라는 값을 저장한다는 의미이다.
마지막으로 capitalCities를 실행하면 Map에 저장된 데이터가 출력된다.
HashMap은 저장 순서를 보장하지 않기 때문에 실행할 때마다 출력 순서가 달라질 수 있다.
capitalCities의 각 요소에 접근해보세요
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<String, String> capitalCities =
new HashMap<String, String>();
capitalCities.put("S.Korea", "Seoul");
capitalCities.put("Japan", "Tokyo");
capitalCities.put("USA", "Washington D.C");
capitalCities.put("Germany", "Berlin");
// 키로 값에 접근한다
System.out.println(capitalCities.get("S.Korea"));
System.out.println(capitalCities.get("Japan"));
System.out.println(capitalCities.get("USA"));
System.out.println(capitalCities.get("Germany"));
}
}
get(키)는 전달한 키에 해당하는 값을 반환하는 메서드이다.
따라서 "S.Korea"라는 키에 연결된 "Seoul"이 출력된다.
Japan, USA, Germany 모두 마찬가지이다.
이 코드의 핵심은 HashMap은 인덱스가 아닌 키(Key)를 사용해 값을 조회한다는 점이다. get() 메서드에 키를 전달하면 해당 키와 연결된 값을 반환한다. 만약 존재하지 않는 키를 조회하면 null이 반환된다.
capitalCities의 요소를 수정하라.
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<String, String> capitalCities =
new HashMap<String, String>();
capitalCities.put("S.Korea", "Seoul");
capitalCities.put("Japan", "Tokyo");
capitalCities.put("USA", "Washington D.C");
capitalCities.put("Germany", "Berlin");
// 수정 전
System.out.println(capitalCities);
// 한국의 수도 -> 인천
// 키가 같으면 값을 덮어쓴다
capitalCities.put("S.Korea", "Incheon");
// 확인하기
System.out.println(capitalCities.get("S.Korea"));
}
}
수정 전의 Map을 출력해보고 이후 put() 메서드를 사용하였다.
put()메스드를 통해 S.Korea에 Incheon이라는 값을 덮어쓴다.
여기서 중요한 점은 put()은 데이터를 추가할 때도 사용하지만 이미 존재하는 키가 있으면 값을 수정하는 역할도 한다는 것이다.
기존 데이터는 S.Koreak -> Seoul이었지만 capitalCities.put("S.Korea", "Incheon");를 실행하면 S.Korea -> Incheon으로 변경된다.
즉 S.koreak라는 키는 그대로 유지되고 값만 Seoul에서 Incheon으로 바뀐다.
마지막으로 System.out.println(capitalCities.get("S.Korea"));를 실행하면 S.Korea 키에 저장된 값을 조회한다.
capitalCities에서 특정 요소를 삭제하라.
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<String, String> capitalCities =
new HashMap<String, String>();
capitalCities.put("S.Korea", "Seoul");
capitalCities.put("Japan", "Tokyo");
capitalCities.put("USA", "Washington D.C");
capitalCities.put("Germany", "Berlin");
// 삭제 전
System.out.println(capitalCities);
// 키로 삭제할 요소에 접근
capitalCities.remove("Japan");
// 없는 키로 접근을 시도했을 때: null
System.out.println(capitalCities.get("Japan"));
}
}
먼저 HashMap을 생성한 뒤 국가와 수도 정보를 저장한다.
이 시점의 Map에는 총 4개의 데이터가 저장되어 있다.
그 다음 remove()를 실행한다.
remove()키는 전달한 키에 해당하는 데이터를 삭제하는 메서드이다.
여기서는 "Japan" 키와 연결된 데이터가 삭제된다.
마지막으로 System.out.println(capitalCities.get("Japan"));를 실행해보면 "Japan"키에 저장된 값이 삭제되었기 때문에 결과로 null이 반환된다.
capitalCities의 크기를 구하라.
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<String, String> capitalCities =
new HashMap<String, String>();
capitalCities.put("S.Korea", "Seoul");
capitalCities.put("Japan", "Tokyo");
capitalCities.put("USA", "Washington D.C");
capitalCities.put("Germany", "Berlin");
System.out.println(capitalCities.size());
}
}
size() 메서드는 Map에 저장된 키-값 쌍의 개수를 반환하는 메서드이다.
현재 Map에는 4개의 데이터가 저장되어 있으므로 결과는 4이다.
이 코드의 핵심은 size()를 사용하면 HashMap에 저장된 데이터의 개수를 확인할 수 있다는 점이다. Map에서는 키와 값이 한 쌍으로 저장되므로, size()는 저장된 키-값 쌍의 개수를 반환한다.
다음과 같이 구성된 리스트 ages 로부터 성인들만 추출해 새로운 리스트 adults를 생성해보세요
[12, 18, 22, 34]
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> ages = new ArrayList<>();
ages.add(12);
ages.add(18);
ages.add(22);
ages.add(34);
System.out.println(ages);
}
}
먼저 List< Integer >를 생성한 뒤 나이데이터를 추가한다.
이 시점의 리스트는 [12, 18, 22, 34]이다.
그 다음 Stream을 사용하여 성인만 추출한다.
여기서 ages.stream()은 ages 리스트를 Stream으로 반환한다.
.filter(age -> age >= 18)은 나이가 18 이상인 데이터만 남긴다.
마지막으로 .collect(Collector.toList());를 사용해 필터링된 결과를 다시 List로 변환한다.
결과적으로 adults에는 [18, 22, 34]의 데이터가 저장이 된다.
다음과 같이 구성된 리스트 persons로부터 이름에 J가 들어가는 사람들로 구성된 새로운 리스트 personsWithJ를 생성해보세요.
[John Doe, Jane Doe, Mary Doe, Steve Jobs]
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("John Doe");
names.add("Jane Doe");
names.add("Mary Doe");
names.add("Steve Jobs");
System.out.println(names);
// J를 포함한 사람들로 구성된 리스트
List<String> personsWithJ = names.stream()
// .filter(person -> person.toLowerCase().indexOf("j") > -1)
.filter(name -> name.contains("J"))
.collect(Collectors.toList());
System.out.println(personsWithJ);
}
}
먼저 리스트를 생성하고 이름을 저장한다.
그 다음 Stream을 생성하고 names리스트를 Stream으로 변환한다.
이후 fillter를 통해 이름에 "J"가 포함된 데이터만 남긴다.
마지막으로 collect를 사용해 결과를 새로운 List로 변환한다.
결과적으로 personWithJ에는 [John Doe, Jane Doe, Steve Jobs]만 저장된다.
데이터베이스로부터 가져온 User 리스트로부터 UserSearchDto 리스트를 생성하는 코드를 작성해보세요

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
class User {
private int id;
private String username;
private String password;
private String photoUrl;
public User(int id, String username, String password, String photoUrl) {
this.id = id;
this.username = username;
this.password = password;
this.photoUrl = photoUrl;
}
public int getId() {
return id;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public String getPhotoUrl() {
return photoUrl;
}
}
// Dto - 클라이언트에게 전송할 사용자 정보
class UserSearchDto {
private String username;
private String photoUrl;
public UserSearchDto(String username, String photoUrl) {
this.username = username;
this.photoUrl = photoUrl; // 프로필사진
}
@Override
public String toString() {
return String.format
("UserSearchDto{Username=%s, photoUrl=%s",
username, photoUrl);
}
}
public class Main {
public static void main(String[] args) {
// 데이터베이스로부터 가져온 사용자 데이터들
List<User> users = new ArrayList<>();
users.add(new User(1, "johndoe", "xxx", "/johndoe.jpg"));
users.add(new User(2, "janedoe", "xxx", "/janedoe.jpg"));
users.add(new User(3, "marydoe", "xxx", "/marydoe.jpg"));
// 검색용 사용자 정보를 생성
List<UserSearchDto> userSearchDtos = users.stream()
// users를 활용해 dto를 생성
.map(user -> new UserSearchDto(user.getUsername(), user.getPhotoUrl()))
.collect(Collectors.toList());
// 확인하기
for (UserSearchDto userSearchDto : userSearchDtos) {
System.out.println(userSearchDto);
}
}
}
이 코드는 사용자(User) 객체를 검색용 DTO(UserSearchDto)로 변환하는 예제이다.
먼저 User 클래스는 데이터 베이스에 저장된 사용자 정보를 표현한다.
사용자의 번호, 아이디, 비밀번호, 프로필 사진 경로를 가지고 있다.
그 다음 UserSearchDto 클래스는 클라이언트에게 전달할 데이터를 담는다.
여기서는 아이디와 프로플 사진만 필요하므로 이 두 정보만 저장한다.
main에서는 List를 사용해 사용자 데이터를 생성한다.
그리고 사용자 정보를 추가한다.
users.add 부분은 데이터베이스에서 가져온 사용자 목록이라고 생각하면 된다.
그 다음 Stream을 사용해 DTO를 생성한다.
Users 리스트를 Stream으로 반환한다.
map()은 데이터를 다른 형태로 변환하는 메서드이다.
여기서는 User -> UserSearchDto로 변환한다.
그 후 변환된 DTO들을 다시 List로 만들어 저장한다.
결과적으로 List< UserSearchDto >에는 검색용 DTO들이 저장된다.
마지막으로 for문을 활용해 DTO를 하나씩 출력한다.
이 때 toString()이 자동 호출되어 DTO 정보가 출력된다.
이 코드의 핵심은 데이터베이스의 User 객체 전체를 그대로 전달하지 않고, 필요한 정보만 담은 UserSearchDto로 변환하여 사용한다. 실제 스프링 프로젝트에서도 비밀번호 같은 민감한 정보는 숨기고 필요한 데이터만 전달하기 위해 DTO를 많이 사용한다.
오늘은 Stream, Optional, 내부 클래스, 예외 처리, 그리고 컬렉션(List, Set, Map)까지 다양한 자바 기능을 학습했다.
특히 Stream을 활용해 데이터를 필터링하고 가공하는 방법을 익히면서 기존의 반복문보다 더 간결하게 데이터를 처리할 수 있다는 점을 알게 되었다. 또한 Optional을 통해 null을 안전하게 다루는 방법을 배우며 NPE를 예방하는 이유를 이해할 수 있었다.
내부 클래스와 DTO를 활용해 데이터를 목적에 맞게 분리하는 방법도 학습했고, 예외 처리와 throws를 통해 프로그램의 안정성을 높이는 방법을 익혔다. 마지막으로 ArrayList, Set, Map을 사용해 데이터를 저장하고 조회, 수정, 삭제하는 방법을 실습하며 컬렉션의 기본 사용법을 이해할 수 있었다.
이번 학습을 통해 단순히 코드를 작성하는 것을 넘어 데이터를 효율적으로 관리하고 안전하게 처리하는 자바의 다양한 기능들을 경험할 수 있었으며, 앞으로 더 복잡한 프로그램을 작성하는 데 도움이 될 것 같다.