도서 대여 시스템 - 상세 설명
📋 프로그램 개요
ArrayList를 활용하여 도서 정보를 관리하고, 대출/반납 기능을 제공하는 도서 대여 시스템입니다.
객체 지향 프로그래밍의 기본 개념(클래스, 객체, 캡슐화, 상속)을 실제 프로젝트에 적용한 예제입니다.
🏗 프로그램 구조
도서 대여 시스템
├── Library.java (도메인 클래스 - 도서 정보)
├── LibraryManager.java (관리 클래스 - CRUD 기능)
└── Search.java (실행 클래스 - 메뉴 시스템)클래스 관계도
Search (실행)
│
└── LibraryManager (관리)
│
├── ArrayList<Library> librarys
└── ArrayList<Library> booklocation
│
└── Library (도메인)
├── String title
├── String author
├── String location
├── String isbn
└── boolean available📦 1. Library 클래스 (도메인 클래스)
역할
도서의 정보를 담는 데이터 클래스입니다. 하나의 Library 객체가 하나의 도서 정보를 나타냅니다.
주요 구성
필드 (Fields)
private String title; // 책 제목
private String author; // 저자
private String location; // 책 위치 (예: "sectionA")
private String isbn; // ISBN 번호
private boolean available; // 대출 가능 여부
캡슐화 원칙:
- 모든 필드를
private로 선언하여 외부에서 직접 접근 불가 - Getter/Setter 메서드를 통해서만 접근 가능
생성자 (Constructors)
1. 기본 생성자
public Library() {
}
- 매개변수가 없는 기본 생성자
- 모든 필드를 기본값(
null,false)으로 초기화 - 용도: 객체를 먼저 생성하고 나중에 setter로 값을 설정할 때 사용
2. 매개변수 생성자
public Library(String title, String author, String location, String isbn) {
this.title = title;
this.author = author;
this.location = location;
this.isbn = isbn;
this.available = true; // 새 도서는 항상 대출 가능
}
- 도서 정보를 받아서 초기화하는 생성자
- 중요:
available = true로 자동 설정 (새 도서는 항상 대출 가능)
생성자 오버로딩:
- 같은 이름의 생성자를 매개변수만 다르게 여러 개 정의
- 기본 생성자와 매개변수 생성자를 함께 제공하면 유연하게 객체 생성 가능
Getter/Setter 메서드
Getter 메서드 (읽기)
public String getTitle() {
return title;
}
public String getAuthor() {
return author;
}
public boolean isAvailable() {
return available;
}
Setter 메서드 (쓰기)
public void setTitle(String title) {
this.title = title;
}
public void setAvailable(boolean available) {
this.available = available;
}
주의사항:
- Boolean 타입의 getter는
is접두사 사용:isAvailable()✅ getAvailable()❌ (문법적으로는 가능하지만 관례상is사용)
this 키워드:
public void setTitle(String title) {
this.title = title; // this.title = 인스턴스 변수, title = 매개변수
}
- 매개변수 이름과 필드 이름이 같을 때 구분하기 위해 사용
this는 현재 객체를 가리킴
주요 메서드
1. void book() - 도서 대출 처리
public void book() {
this.available = false;
}
- 도서를 대출 처리하는 메서드
available을false로 변경하여 대출 불가능 상태로 만듦- 메서드 이름:
rent()또는borrow()가 더 적절할 수 있지만,book()도 가능
2. String toString() - 객체 정보 문자열 반환
@Override
public String toString() {
return "책 제목 : " + title + ", 저자 : " + author + ", 책 위치 : " + location
+ ", ISBN : " + isbn + ", 대출여부 : " + (available ? "대출가능" : "대출불가능");
}
@Override 어노테이션:
- 부모 클래스(
Object)의toString()메서드를 재정의(오버라이딩) - 컴파일러에게 명시적으로 알려주는 역할
- 오타나 시그니처 불일치 시 컴파일 에러 발생
삼항 연산자:
(available ? "대출가능" : "대출불가능")
조건 ? 참일때값 : 거짓일때값- if-else문의 축약형
🗂 2. LibraryManager 클래스 (관리 클래스)
역할
도서 리스트를 관리하고, CRUD(생성, 읽기, 수정, 삭제) 기능을 제공하는 클래스입니다.
주요 구성
필드
private ArrayList<Library> librarys; // 전체 도서 리스트
private ArrayList<Library> booklocation; // 대출된 도서 리스트
두 개의 ArrayList 사용 이유:
librarys: 전체 도서를 관리하는 리스트booklocation: 대출된 도서만 따로 관리하는 리스트- 같은
Library객체를 두 리스트에서 공유 (객체 참조 개념)
생성자
public LibraryManager() {
librarys = new ArrayList<>();
booklocation = new ArrayList<>();
// 더미 데이터 추가
librarys.add(new Library("this is java", "shin", "sectionA", "979-11-691-229-8"));
librarys.add(new Library("First Encounter with React", "Lee Inje", "Section B", "979-11-6921-169-7"));
librarys.add(new Library("The Principles of Web Standards", "Ko Kyunghee", "Section C", "979-11-6303-622-7"));
}
더미 데이터:
- 프로그램 테스트를 위한 샘플 데이터
- 프로그램 실행 시 자동으로 추가됨
메서드 상세 설명
1. void allLibrary() - 대출 가능한 도서 보기
public void allLibrary() {
System.out.println("대출 가능한 도서보기");
for(int i = 0; i < librarys.size(); i++) {
Library library = librarys.get(i);
if(library.isAvailable()) {
System.out.println(library);
}
}
}
동작 원리:
librarys리스트를 인덱스 기반으로 순회- 각 도서의
available이true인지 확인 - 대출 가능한 도서만 출력
System.out.println(library)는 자동으로library.toString()호출
향상된 for문으로 개선 가능:
for(Library library : librarys) {
if(library.isAvailable()) {
System.out.println(library);
}
}
2. boolean booklocations(String libraryName) - 도서 대출하기
public boolean booklocations(String libraryName) {
for(Library library : librarys) {
if(library.getTitle().equalsIgnoreCase(libraryName) && library.isAvailable()) {
library.book(); // 대출 처리
booklocation.add(library); // 대출 목록에 추가
return true;
}
}
return false;
}
동작 원리:
librarys리스트를 순회- 도서 제목이 일치하고 (
equalsIgnoreCase) 대출 가능한 도서 찾기 - 찾으면:
book()호출하여available = false로 변경booklocation리스트에 추가true반환
- 못 찾으면
false반환
equalsIgnoreCase() 설명:
"this is java".equalsIgnoreCase("THIS IS JAVA") // true (대소문자 무시)
"this is java".equalsIgnoreCase("This Is Java") // true
- 문자열 비교 시 대소문자를 구분하지 않음
- 사용자 입력 오류 방지에 유용
논리 연산자 &&:
library.getTitle().equalsIgnoreCase(libraryName) && library.isAvailable()
- 두 조건이 모두
true여야 함 - AND 연산
객체 참조 개념:
library.book(); // library 객체의 available을 false로 변경
booklocation.add(library); // 같은 객체를 booklocation에도 추가
- 두 리스트가 같은 객체를 참조
- 한 리스트에서 객체를 수정하면 다른 리스트에도 영향
3. void booklocations() - 대출한 도서 보기 (오버로딩)
public void booklocations() {
System.out.println("대출한 도서보기");
for(Library location : booklocation) {
System.out.println(location);
}
}
메서드 오버로딩:
- 같은 이름의 메서드를 매개변수만 다르게 여러 개 정의
booklocations(String libraryName): 도서 대출booklocations(): 대출한 도서 보기
4. void addLibrary(...) - 도서 추가하기
public void addLibrary(String newTitle, String newAuthor, String newLocation, String newIsbn) {
librarys.add(new Library(newTitle, newAuthor, newLocation, newIsbn));
}
동작 원리:
- 매개변수로 받은 정보로 새로운
Library객체 생성 vehicles리스트에 추가- 생성 시
available = true로 자동 설정됨
한 줄 작성:
// 방법 1: 객체를 변수에 저장 후 추가 (주석 처리된 코드)
Library abc = new Library(newTitle, newAuthor, newLocation, newIsbn);
librarys.add(abc);
// 방법 2: 바로 추가 (현재 코드)
librarys.add(new Library(newTitle, newAuthor, newLocation, newIsbn));
5. void delLibrary(String dname) - 도서 삭제하기
public void delLibrary(String dname) {
boolean result = false;
for(Library library : librarys) {
if(library.getTitle().equalsIgnoreCase(dname)) {
if(library.isAvailable()) { // 대출 중이 아닐 때만 삭제 가능
librarys.remove(library);
result = true;
break;
} else {
result = false;
break;
}
}
}
if(result) {
System.out.println("삭제됨");
} else {
System.out.println("삭제 안됨");
}
}
동작 원리:
- 도서 제목이 일치하는 도서 찾기
- 찾은 도서가 대출 가능(
available == true)한지 확인 - 대출 가능하면 삭제, 대출 중이면 삭제 불가
- 결과 출력
주의사항:
- 향상된 for문에서 직접
remove()를 사용하면ConcurrentModificationException발생 가능 - 안전한 방법: Iterator 사용 또는 인덱스 기반 반복문
break 문:
- 반복문을 즉시 종료
- 조건에 맞는 첫 번째 항목만 처리하고 종료
6. void updateLibrary(String uname) - 도서 정보 수정하기
public void updateLibrary(String uname) {
int i = 0;
int index = -1;
int menu = -1;
boolean flag = true;
Scanner sc = new Scanner(System.in);
Library newA = new Library();
// 수정할 도서 찾기
for(Library a : librarys) {
i++;
if(a.getTitle().equals(uname)) {
index = i - 1; // 실제 인덱스 (i는 1부터 시작)
newA = a; // 찾은 도서 객체를 newA에 참조
}
System.out.println(a.getTitle().equals(uname) + " " + a.getTitle() + " " + uname);
}
if(index != -1) {
System.out.print("뭘 수정할건데?\n 1.도서 이름 \t 2.도서 저자 \t 3.도서 위치 \t 4.도서ISBN \n >>");
menu = sc.nextInt();
sc.nextLine(); // 버퍼 초기화
while (flag) {
switch (menu) {
case 1:
System.out.println("수정할 이름");
newA.setTitle(sc.nextLine());
librarys.set(index, newA);
flag = false;
break;
// ... 다른 케이스들
}
}
} else {
System.out.println("찾는 도서가 없어서 업데이트할 수 없습니다.");
}
}
동작 원리:
- 수정할 도서를 이름으로 찾기
- 인덱스와 객체 참조 저장
- 수정할 항목 선택 (메뉴)
- 해당 필드만 setter로 수정
librarys.set(index, newA)로 리스트에 반영
코드 분석:
1) 인덱스 찾기:
int i = 0;
for(Library a : librarys) {
i++;
if(a.getTitle().equals(uname)) {
index = i - 1; // i는 1부터 시작하므로 -1
}
}
i를 1부터 카운트하므로 실제 인덱스는i - 1
개선 방법:
for(int i = 0; i < librarys.size(); i++) {
if(librarys.get(i).getTitle().equals(uname)) {
index = i; // 바로 인덱스 사용
break;
}
}
2) 객체 참조:
Library newA = new Library(); // 빈 객체 생성
newA = a; // 찾은 도서 객체를 참조 (같은 객체를 가리킴)
newA는librarys의 실제 객체를 참조- setter로 수정하면 원본 객체가 변경됨
librarys.set(index, newA)는 사실 불필요할 수 있지만, 명시적으로 반영
3) Scanner 버퍼 처리:
menu = sc.nextInt(); // 숫자만 읽음, 엔터는 버퍼에 남음
sc.nextLine(); // 버퍼 초기화 (엔터 제거)
4) 디버깅 출력:
System.out.println(a.getTitle().equals(uname) + " " + a.getTitle() + " " + uname);
- 비교 결과를 출력 (디버깅용)
- 실제 운영 시 제거 권장
개선된 코드:
public void updateLibrary(String uname) {
int index = -1;
Vehicle targetLibrary = null;
Scanner sc = new Scanner(System.in);
// 수정할 도서 찾기 (인덱스 기반)
for (int i = 0; i < librarys.size(); i++) {
if (librarys.get(i).getTitle().equalsIgnoreCase(uname)) {
index = i;
targetLibrary = librarys.get(i);
break;
}
}
if (index != -1) {
System.out.print("무엇을 수정할까요?\n 1.도서 이름 \t 2.도서 저자 \t 3.도서 위치 \t 4.도서ISBN \n >> ");
int menu = sc.nextInt();
sc.nextLine();
switch (menu) {
case 1:
System.out.print("수정할 이름: ");
targetLibrary.setTitle(sc.nextLine());
// librarys.set(index, targetLibrary); // 불필요 (이미 같은 객체 참조)
break;
// ... 다른 케이스들
}
} else {
System.out.println("찾는 도서가 없어서 업데이트할 수 없습니다.");
}
}
7. void showLibrary(String sname) - 도서 상세 정보 조회
public void showLibrary(String sname) {
for(Library a : librarys) {
if(a.getTitle().equalsIgnoreCase(sname)) {
System.out.println(a.toString());
}
}
}
동작 원리:
- 도서 제목이 일치하는 도서의 정보 출력
🖥 3. Search 클래스 (실행 클래스)
역할
사용자와 상호작용하는 메인 프로그램입니다. 메뉴를 제공하고 사용자 입력을 처리합니다.
주요 구성
메인 메서드 구조
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
LibraryManager manager = new LibraryManager();
boolean flag = true;
while (flag) {
// 메뉴 출력
// 사용자 입력 받기
// switch-case로 메뉴 처리
// 8번 선택 시 flag = false로 종료
}
sc.close();
System.exit(0);
}
메뉴 시스템
메뉴 출력:
System.out.println("\n도서검색 시스템에 오신 것을 환영합니다.");
System.out.println("1.대출 가능한 도서 보기");
System.out.println("2.도서 대출하기");
System.out.println("3.대출한 도서 보기");
System.out.println("4.도서 추가하기");
System.out.println("5.도서 삭제하기");
System.out.println("6.도서 정보 수정하기");
System.out.println("7.도서 내용 보기");
System.out.println("8.종료");
System.out.print("원하는 작업을 선택하세요 >> ");
입력 처리:
int choice = sc.nextInt();
sc.nextLine(); // 버퍼 초기화 (매우 중요!)
Scanner 버퍼 문제:
int choice = sc.nextInt(); // 숫자만 읽음, 엔터(\n)는 버퍼에 남음
String name = sc.nextLine(); // 버퍼에 남은 엔터를 읽어서 빈 문자열이 됨
해결 방법:
int choice = sc.nextInt();
sc.nextLine(); // 엔터를 버퍼에서 제거
String name = sc.nextLine(); // 이제 정상적으로 입력 받음
메뉴별 처리
메뉴 1: 대출 가능한 도서 보기
case 1:
System.out.println("대출 가능한 도서");
manager.allLibrary();
break;
메뉴 2: 도서 대출하기
case 2:
System.out.println("도서 대출하기");
System.out.print("대출 하려는 도서의 이름을 입력 하세요 : ");
String libraryName = sc.nextLine();
if(manager.booklocations(libraryName)) {
System.out.println("도서가 성공적으로 대출되었습니다.");
} else {
System.out.println("도서가 존재하지 않거나 대출 불가능합니다.");
}
break;
반환값 처리:
booklocations()는boolean반환true: 대출 성공false: 대출 실패
메뉴 3: 대출한 도서 보기
case 3:
System.out.println("대출한 도서 보기");
manager.booklocations();
break;
메뉴 4: 도서 추가하기
case 4:
System.out.println("도서 추가하기");
System.out.print("추가 도서이름 : ");
String newTitle = sc.nextLine();
System.out.print("추가 도서저자 : ");
String newAuthor = sc.nextLine();
System.out.print("도서 위치 : ");
String newLocation = sc.nextLine();
System.out.print("도서의 ISBN : ");
String newIsbn = sc.nextLine();
manager.addLibrary(newTitle, newAuthor, newLocation, newIsbn);
System.out.println("도서 추가 완료되었습니다.");
break;
메뉴 5, 6, 7: 빈 문자열 체크
case 5:
System.out.println("삭제 시작");
System.out.println("삭제하는 도서 이름을 입력하세요 : ");
String dname = sc.nextLine();
if(dname.equals("")) {
System.out.println("삭제 하려는 도서 이름을 다시 입력 해주세요");
dname = sc.nextLine();
}
manager.delLibrary(dname);
System.out.println("삭제 완료");
break;
빈 문자열 체크:
- 사용자가 엔터만 누르는 경우 방지
- 한 번만 재입력 요청 (더 강력한 예외 처리는 반복문 사용)
개선 방법:
String dname = "";
while(dname.equals("")) {
System.out.print("삭제하는 도서 이름을 입력하세요: ");
dname = sc.nextLine();
if(dname.equals("")) {
System.out.println("도서 이름을 입력해주세요.");
}
}
메뉴 8: 종료
case 8:
System.out.println("프로그램 종료합니다.");
flag = false;
break;
프로그램 종료:
sc.close(); // Scanner 닫기
System.exit(0); // 프로그램 종료 (0: 정상 종료)
🔍 핵심 개념 설명
1. 객체 참조 (Reference) 이해
ArrayList<Library> librarys = new ArrayList<>();
ArrayList<Library> booklocation = new ArrayList<>();
Library book = new Library("Java", "Author", "A", "123");
librarys.add(book); // librarys 리스트에 추가
booklocation.add(book); // booklocation 리스트에도 같은 객체 추가
// 같은 객체를 두 리스트에서 공유
book.setAvailable(false); // 두 리스트의 같은 객체가 변경됨
시각적 표현:
librarys booklocation
│ │
└─── Library ───────┘
(Java)
available = false2. ArrayList 메서드
| 메서드 | 설명 | 예시 |
|---|---|---|
add(E e) |
요소 추가 | librarys.add(new Library(...)) |
get(int index) |
요소 조회 | Library lib = librarys.get(0) |
set(int index, E e) |
요소 수정 | librarys.set(0, newLib) |
remove(Object o) |
요소 삭제 (객체) | librarys.remove(library) |
remove(int index) |
요소 삭제 (인덱스) | librarys.remove(0) |
size() |
리스트 크기 | int count = librarys.size() |
isEmpty() |
비어있는지 확인 | if(librarys.isEmpty()) |
3. 문자열 비교
// ❌ 잘못된 방법
if (str1 == str2) { ... } // 주소 비교 (의도한 대로 동작 안 함)
// ✅ 올바른 방법
if (str1.equals(str2)) { ... } // 대소문자 구분
if (str1.equalsIgnoreCase(str2)) { ... } // 대소문자 무시
4. 향상된 for문 vs 일반 for문
향상된 for문 (Enhanced for loop)
for (Library library : librarys) {
// library는 librarys의 각 요소
// 인덱스 접근 불가
// 삭제 시 주의 필요
}
일반 for문 (Index-based)
for (int i = 0; i < librarys.size(); i++) {
Library library = librarys.get(i);
// 인덱스 접근 가능 (i)
// 삭제 시 인덱스 조정 필요
}
5. 메서드 오버로딩
// 매개변수가 다른 같은 이름의 메서드
public boolean booklocations(String libraryName) { ... }
public void booklocations() { ... }
오버로딩 조건:
- 메서드 이름이 같아야 함
- 매개변수의 개수 또는 타입이 달라야 함
- 반환 타입은 오버로딩에 영향 없음
🎯 학습 포인트 정리
객체 지향 프로그래밍
- 클래스와 객체의 개념
- 캡슐화 (private 필드, public 메서드)
- 생성자 오버로딩
- Getter/Setter 패턴
-
toString()메서드 오버라이딩 - 메서드 오버로딩
ArrayList 활용
- ArrayList 생성 및 초기화
- 요소 추가 (
add()) - 요소 조회 (
get()) - 요소 수정 (
set()) - 요소 삭제 (
remove()) - 리스트 순회 (for문, 향상된 for문)
문자열 처리
-
equals()vs== -
equalsIgnoreCase()사용 - 빈 문자열 체크
제어문
- while 루프 (메뉴 시스템)
- switch-case문
- if-else문
- break 문
Scanner 사용
-
nextInt()vsnextLine()버퍼 문제 -
sc.nextLine()으로 버퍼 초기화
💡 코드 개선 제안
1. 예외 처리 강화
// 현재 코드
int choice = sc.nextInt();
// 개선된 코드
int choice = -1;
try {
choice = sc.nextInt();
sc.nextLine();
} catch (InputMismatchException e) {
System.out.println("숫자를 입력하세요.");
sc.nextLine(); // 버퍼 초기화
}
2. 반복 입력 처리
// 현재 코드
if(dname.equals("")) {
dname = sc.nextLine();
}
// 개선된 코드
String dname = "";
while(dname.trim().isEmpty()) {
System.out.print("도서 이름을 입력하세요: ");
dname = sc.nextLine();
if(dname.trim().isEmpty()) {
System.out.println("도서 이름을 입력해주세요.");
}
}
3. 메서드 이름 개선
// 현재 코드
public void booklocations(String libraryName) { ... }
public void booklocations() { ... }
// 개선된 코드
public boolean rentBook(String libraryName) { ... }
public void showRentedBooks() { ... }
4. Iterator를 사용한 안전한 삭제
public void delLibrary(String dname) {
Iterator<Library> iterator = librarys.iterator();
boolean result = false;
while (iterator.hasNext()) {
Library library = iterator.next();
if (library.getTitle().equalsIgnoreCase(dname)) {
if (library.isAvailable()) {
iterator.remove(); // 안전한 삭제
result = true;
break;
}
}
}
if (result) {
System.out.println("삭제됨");
} else {
System.out.println("삭제 안됨 (대출 중이거나 존재하지 않음)");
}
}
5. 중복 체크 추가
public boolean addLibrary(...) {
// ISBN 중복 체크
for (Library lib : librarys) {
if (lib.getIsbn().equals(newIsbn)) {
System.out.println("이미 존재하는 ISBN입니다.");
return false;
}
}
librarys.add(new Library(...));
return true;
}
📚 관련 개념
- ArrayList: 배열-vs-ArrayList-비교.md
- 생성자/Getter/Setter: 생성자-Getter-Setter-오버로딩-오버라이딩-toString-완전정리.md
- 예외 처리: 10장_예외_처리.md
🎓 실습 과제
- 반납 기능 추가: 대출된 도서를 반납하는 기능 구현
- 검색 기능 강화: 저자, ISBN, 위치별로 검색하는 기능 추가
- 통계 기능: 전체 도서 수, 대출 가능한 도서 수, 대출 중인 도서 수 출력
- 파일 저장/불러오기: 도서 정보를 파일에 저장하고 불러오기
- 예외 처리 강화: 모든 입력에 대해 예외 처리 추가
'BackEnd > Java' 카테고리의 다른 글
| 9_1장 HashMap을 활용한 커피 메뉴 관리 예제 (0) | 2026.01.23 |
|---|---|
| 8_3. 도서 관리시스템 (0) | 2026.01.20 |
| 배열-vs-ArrayList-비교 (0) | 2026.01.16 |
| 8_2 .학생 관리 시스템 실습 문제 (0) | 2026.01.15 |
| 8_1. 회차 은행 어플리케이션 프로그램 (0) | 2026.01.14 |