JAVA 웹개발 과정 국비 11일차 정리

11일차

※ 배우는 과정이므로 정확하지 않을 수 있습니다.
잘못된 점은 알려주시면 수정하도록 하겠습니다!


1. Constructor (생성자 메소드)

클래스를 구성하는 4가지 요소

  • Member Field
  • Member Method
  • Constructor
  • Nested Class

이 중 Constructor를 배워본다.
Nested Class는 잘 쓰이지 않는다고 한다.

1) 특이한 메소드 Constructor

보통 메소드랑은 다른 특성을 가진다.

  • 반환형을 가질 수 없다.
  • 이름이 고정되어 있다. (반드시 클래스의 이름과 동일해야 한다.)
  • 호출 시점이 정해져있다. (인스턴스가 생성되는 시점에 단 한번)
  • 그 외에 특징은 일반 메소드와 모드 동일하다.
public class Book() {
	private int id;
	private String title;
	private int price;
	
	public Book() { // 클래스와 같은 이름을 가지고 반환부가 없다면
	} 				// 생성자인 것을 바로 알아차려야 함
}

2) 그래서 왜 쓰는건데?

현재 Book 클래스는 멤버필드를 3개 가지고 있다.
인스턴스를 처음 생성하면 멤버필드는 값이 0으로 아무것도 들어가있지 않은 상태이다.
따라서 우리는 이 멤버필드에 값을 각각 넣어줬었다.

public static void main(Stringp[] args) {
	
	Book b1 = new Book();
	b1.setId(1001);
	b1.setTitle("로미오와 줄리엣");
	b1.setPrice(14000);
}

여기서 Book()을 생성자 콜이라고하고, 클래스 이름 + 소괄호 형식으로 나타낸다.
new 옆에 클래스명과 소괄호가 오는 것은 자바의 문법으로, new가 있으면 생성자 콜이 나올 수 밖에 없다.

그런데 멤버필드가 많아질수록 코드 길이도 길어지고 하나씩 값을 넣어주는게 너무 귀찮다.
그래서 인스턴스를 만들면서 한꺼번에 값을 넣어주면 좋을 것 같다는 생각을 하게 된다.
이것이 생성자의 존재 이유이다.

인스턴스가 생성될 때 초기값을 한번에 미리 넣어주기 위한 목적.

public class Book() {
	private int id;
	private String title;
	private int price;
	
	public Book(int id, String title, int price) { 
		this.id = id;
		this.title = title;
		this.price = price;
	} 				
}
public static void main(Stringp[] args) {
	
	Book b1 = new Book(1001, "로미와 줄리엣", 14000);
}

생성자는 필수가 아니다. setter를 써서 값을 넣어준다면 굳이 생성자 메소드를 쓸 필요는 없다.
단지 편해서 생성자를 통해 초기값을 넣어주는 것이다.

3) default 생성자

클래스를 생성하면 생성자는 무조건 1개는 존재한다. (default 생성자)
내가 생성자를 명시적으로 작성하는 순간 default 생성자는 사라진다.

익숙해지기 전까지는 default 생성자와 생성자 오버로딩은 의무적으로 만들자!

4) 인스턴스에 값을 넣는 방법

이제 인스턴스에 값을 넣는 두가지 방법을 배웠다.

② 생성자 메소드로 값 넣기
③ setter 로 값 넣기

왜 1번이 비어있냐면 이 두가지 방법보다 빠르게 값을 넣는 방법이 존재하기 때문이다.

① 멤버필드에 직접 값을 넣기 (default value)

대부분 클래스는 private으로 멤버필드를 가지기 때문에 default value는 클래스 개발자만 사용 가능한 방법이다.
만약 사용자가 ②, ③번을 둘다 사용하지 않는다면 ? 기본값을 넣어주는 것이다.

2. 예제 - 비상연락망 프로그램

1) Contact 클래스 생성

  • id, name, contact 멤버필드
  • getter / setter
  • default 생성자, overloading 생성자

연락망이라는 클래스로, 필수요소를 다 갖춘 클래스를 만들어준다.

2) 인스턴스를 생성.

3개의 인스턴스를 생성해보자.

public static void main(String[] args) {
	Contact c1 = new Contact(1001, "Susan", "01011111111");
	Contact c2 = new Contact(1002, "Tom", "01022222222");
	Contact c3 = new Contact(1003, "Jack", "01033333333");
}

3) 출력을 해보자.

이 3개의 인스턴스의 내용을 출력하려면, 아래와 같이 println함수를 3번이나 써줘야하고 번거롭다.

public static void main(String[] args) {
	Contact c1 = new Contact(1001, "Susan", "01011111111");
	Contact c2 = new Contact(1002, "Tom", "01022222222");
	Contact c3 = new Contact(1003, "Jack", "01033333333");
	
	System.out.println(c1.getId()+":"+c1.getName()+":"+c1.getContact());
}

여러 인스턴스의 데이터를 한꺼번에 관리하고 싶은데..
이럴 때 배열을 사용한다.

4) 배열은 어떤 자료 형의 몇 칸 짜리여야 할까?

단순하게 생각하면, 인스턴스들을 배열에 저장해서 꺼내쓰면 편리할 것이다.
그러나 Contact 인스턴스를 배열에 저장하는 것이 아니다.
배열은 동일한 타입을 가지는 변수들의 집합이다.
변수는 데이터 또는 자료를 저장하기 위한 메모리 공간 이다.
따라서 배열은 메모리 공간의 집합이다.
Contact 인스턴스는 Heap 메모리에 생성되고, 주소값을 가진다.

결국, Contact 인스턴스(데이터)를 저장한 주소(메모리 공간)값의 집합(배열)을 만들어야 한다는 것이다.

5) 변수는 값이 아니다.

이 부분에서 계속 이해를 못해서 몇시간을 강의를 돌려봤는데 이유를 찾았다.
나는 지금까지 변수를 값이라고 생각하고 있었다.
보통 a, b, x, y 변수에 값을 집어넣었고(수학에서도) a, b, x, y와 같은 알파벳 값을 변수라고 이해하고 있었다.
수학처럼 변수를 실제 존재하는 알파벳에 대입하고 있었던 것이다.
그러나 변수는 값이 아니다. 메모리 공간 그 자체를 나타낸다.
비어있는 공간을 연상하니 이해가 빠르게 됐다.

Contact 자료형을 가지는 변수들을 저장하는 Contact 배열 3칸을 만든다.

그냥 쉽게 넘어갈 내용이라고 생각했는데, 강사님이 강조하신 이유가 있었다.
복습하지 않았다면 영원히 이해 못할뻔했다……

Contact[] contacts = new Contact[3];

3. 예제 - 학생 관리 시스템

예제 코드는 매우 길지만, 정리할 부분만 뽑아서 코드 블록에 담는 것이므로 많은 코드가 생략되어 있다.

int id = 1001; // 학생들에게 순차적으로 부여될 고유 ID 값

if(menu == 1) { // 만약 '1. 학생 정보 입력 '을 누른다면

		// 이름, 국어, 영어, 수학점수를 멤버필드로 가지는 Student 클래스
		System.out.print("이 름 : ");
		String name = sc.nextLine();
		int kor = inputValidInt("국 어 : ");
		int eng = inputValidInt("영 어 : ");
		int math = inputValidInt("수 학 : ");

1) 입력받은 모든 값과 ID를 Student 인스턴스에 저장하는 코드 작성

new를 이용하여 새 Student 인스턴스를 만들어 변수 std에 담는다.
id 값은 새 인스턴스가 생길 때마다 1씩 증가한 값을 가져야 하므로 ++를 해준다.

		Student std = new Student(id++, name, kor, eng, math);

2) 여러 인스턴스를 담으려면?

변수 std는 1명의 정보만 가지는 인스턴스인데, 이 프로그램은 여러명의 정보를 각각 저장하는 기능이 필요하다.
즉, 여러 인스턴스를 저장해야 하므로 Student형 배열이 필요하다.
배열의 크기는 일단 10명 정도로 해볼까?

Student[] stds = new Student[10];

3) 작성된 Studnet 인스턴스를 소멸하지 않고 순차적으로 저장되게 하기

배열 stds에 순차적으로 인스턴스를 담는다고 생각하자.
처음에 stds[0], 그 다음 stds[1] … 이런식으로 담아야 한다.
배열의 인덱스가 0부터 시작하여 1씩 증가해야 하므로 index 변수를 만들어 ++을 사용한다.
그러면 입력을 받을 때 마다 index가 1씩 증가하여 순차적으로 인스턴스를 배열에 저장할 수 있다.
바깥쪽에 변수를 선언해야 소멸되지 않으므로 주의.

int id = 1001; 
int index = 0;  // 밖에다 변수 생성

if(menu == 1) { 

		System.out.print("이 름 : ");
		String name = sc.nextLine();
		int kor = inputValidInt("국 어 : ");
		int eng = inputValidInt("영 어 : ");
		int math = inputValidInt("수 학 : ");
		
		stds[index++] = new Student(id++, name, kor, eng, math); 

4) 저장된 모든 학생의 정보를 출력하기

2번 메뉴인 학생 목록 출력하기 기능을 만들어보자.
기존에 하던대로 stds 배열을 처음부터 끝까지 반복하면 간단하게 끝낼 수 있다.
그리고 1명의 학생의 정보를 입력하고 출력을 하면 1명에 대한 정보가 나올 것이다.

}else if(menu == 2) {

	for(int i = 0; i < stds.length; i++) {
		System.out.println(stds[i].getId()+"\t"+stds[i].getName()
							+"\t"+stds[i].getKor()+"\t"+stds[i].getEng()+"\t"
							stds[i].getMath()+"\t"+stds[i].getSum()+"\t"+stds[i].getAvg());
	}

라고 생각하고 실행을 했는데, NullPointerException 오류가 나온다.
왜 이런 오류가 나는 것일까?

10칸 짜리 배열에 현재 값이 들어있는 칸은 맨 첫 칸 뿐이다.
첫번째 칸에 .을 찍으면 주소로 잘 찾아갈 것이다.
그러나 2번째 칸부터 10번째 칸까지는 값이 없는 null 값으로 존재한다. (입력하지 않았으므로)
따라서 오류를 해석해보자면, 비어있는 칸(null)에 .을 찍어서 오류가 났음!!

5) NullPointException

null값이 들어있어서 에러가 나는 것이 아니다.
null값을 참조할 때 나는 에러.

6) 해결 방법은?

① if (stds[i] != null)

stds의 i번째 인덱스 값이 null이 아니면 출력하게 만든다.
그렇다면 null 값이면 주소를 참조하지 않을 것이므로 에러가 나지 않는다.

if(stds[i] != null) {
	System.out.println(stds[i].getId()+"\t"+stds[i].getName()
						+"\t"+stds[i].getKor()+"\t"+stds[i].getEng()+"\t"
						stds[i].getMath()+"\t"+stds[i].getSum()+"\t"+stds[i].getAvg());
}

그러나 이 방법은 for문이 처음부터 끝까지 모두 동작하기 때문에 좋은 코드는 아니다.
null 값이 아닌 것들만 출력한다는 말은, 다른 말로 하면 null 값인지 아닌지 모두 체크를 해야하기 때문이다.

② if (stds[i] == null)

null 값이면 반복문을 종료시키는 if문으로 바꾸면, 조금 더 효율적인 코드를 짤 수 있다.

if(stds[i] == null) {break;}
	System.out.println(stds[i].getId()+"\t"+stds[i].getName()
						+"\t"+stds[i].getKor()+"\t"+stds[i].getEng()+"\t"
						+stds[i].getMath()+"\t"+stds[i].getSum()+"\t"+stds[i].getAvg());

③ i < std

배열이 몇 칸 짜리인지와는 상관 없이, 정보가 저장된 학생 수가 몇명인지를 알아내는 방법이다.
바로 index 변수를 이용하는 것이다.
index 변수는 학생 수 만큼 증가하고 있기 때문이다.
따라서 아래와 같이 코드를 바꿔주면, 반복문 자체가 학생 수 만큼만 반복된다.

}else if(menu == 2) {

	for(int i = 0; i < index; i++) {  // 학생 수 만큼 반복
		System.out.println(stds[i].getId()+"\t"+stds[i].getName()
							+"\t"+stds[i].getKor()+"\t"+stds[i].getEng()+"\t"
							stds[i].getMath()+"\t"+stds[i].getSum()+"\t"+stds[i].getAvg());
	}

7) 학생 정보 검색 기능 만들기

나의 코드는 아래와 같다.
Student형 변수를 하나 생성하고, for문으로 배열의 처음부터 index까지 반복시켜 같은 이름을 가진 인스턴스를 찾으면 변수에 넣어준다.
만약 반복문을 다 돌았는데도 변수가 null 값 그대로라면 같은 이름을 가진 인스턴스가 없다는 뜻이므로 찾지 못했다는 안내문을 출력해준다.
이 방법이 틀린 것은 아니지만, 앞서 말했듯이 효율적인 코드는 아니다.

}else if(menu == 3) {
    
	// 이름으로 검색
	Student search = null;
	System.out.print("검색할 이름을 입력하세요. >> ");
	String srchName = sc.nextLine();
	for(int i=0;i<index;i++) {	
		if(stds[i].getName().equals(srchName)) {
			search = stds[i];
		}
	}
	if(search!=null) {
		System.out.println(search.getId()+"\t"+search.getName()
							+"\t"+search.getKor()+"\t"+search.getEng()+"\t"
							+search.getMath()+"\t"+search.getSum()+"\t"+search.getAvg());
	}else {
		System.out.println("정보를 찾을 수 없습니다.");
	}

이런 기능을 만들 때 가장 먼저 생각해 주어야 할 것은, 검색하는 값이 없다면? 이다.

① 검색 메소드 만들기

미리 학생의 이름이 있는지 없는지 검색하는 메소드를 만드는 방법.
실무에서 가장 많이 쓰는 방법이다.
아직은 어렵다고 생각하셨는지 쉬운 방법을 설명해주시고 넘어가셨다.
다음 수업 때 질문하고 추가해야겠다.

② boolean existFlag

1번보다 쉬운 방법.
for문이 실행이 됐는지 안됐는지를 확인하는 방법이다.
for문이 1번이라도 실행된다면 boolean값을 true로 바꿔 정보를 찾을 수 없을 때의 if문을 실행시키지 않는다.

}else if(menu == 3) {
	// 이름으로 검색
	
	System.out.print("검색할 이름을 입력하세요. >> ");
	String srchName = sc.nextLine();
	
	boolean existFlag = false;
	for(int i=0;i<index;i++) {
		if(stds[i].getName().equals(srchName)) {
			System.out.println(stds[i].getId()+"\t"+stds[i].getName()
					+"\t"+stds[i].getKor()+"\t"+stds[i].getEng()+"\t"
					+stds[i].getMath()+"\t"+stds[i].getSum()+"\t"+stds[i].getAvg());
			existFlag = true;
		}
	}
	if(!existFlag) {
		System.out.println("정보를 찾을 수 없습니다.");
	}

③ for문을 다 돌았을 때도 찾지 못한다면

나의 코드와 개념적으로는 비슷한 방법이라고 생각한다.
else if로 i가 인덱스-1, 즉, 가장 마지막 인덱스까지 돌았을 때 if문을 실행시키지 않는다면 else if문을 무조건 실행시켜야 하기 때문에 코드흐름을 잘 읽은 방법이라고 할 수있다고 강사님께서 말씀하셨다.
나중에 도움이 될 것 같아서 정리해둔다.

}else if(menu == 3) {
	// 이름으로 검색

	System.out.print("검색할 이름을 입력하세요. >> ");
	String srchName = sc.nextLine();

	for(int i=0;i<index;i++) {
		if(stds[i].getName().equals(srchName)) {
			System.out.println(stds[i].getId()+"\t"+stds[i].getName()
					+"\t"+stds[i].getKor()+"\t"+stds[i].getEng()+"\t"
					+stds[i].getMath()+"\t"+stds[i].getSum()+"\t"+stds[i].getAvg());
		}else if(i==index-1){
			System.out.println("정보를 찾을 수 없습니다.");
		}
	}

④ 변수 count

searchCount라는 변수를 만들어서 if문이 한번도 실행되지 않았을 때, 즉, searchCount가 0일 때 정보가 없다는 알림을 출력해준다.
비슷한 방법이다.

}else if(menu == 3) {
	// 이름으로 검색

	System.out.print("검색할 이름을 입력하세요. >> ");
	String srchName = sc.nextLine();
	
	int searchCount = 0;
	for(int i=0;i<index;i++) {
		if(stds[i].getName().equals(srchName)) {
			System.out.println(stds[i].getId()+"\t"+stds[i].getName()
					+"\t"+stds[i].getKor()+"\t"+stds[i].getEng()+"\t"
					+stds[i].getMath()+"\t"+stds[i].getSum()+"\t"+stds[i].getAvg());
			searchCount++;
		}
	}
	if(searchCount == 0) {
		System.out.println("정보를 찾을 수 없습니다.");
	}

4. 0과 null

예제 문제를 풀면서 혼란이 왔던 개념 두개 0null을 정리해본다.

  • 0
    • 숫자 0
    • 0을 int형으로 표현한 값
  • null
    • 주소값 0
    • 0을 참조형으로 표현한 값

heap 메모리에 데이터가 생성되면 처음은 0 값을 가지고 있다.
라고 배워서 약간 혼란이 왔었는데, 다시 정리해보면 간단하다.
0과 null은 같은 개념이다.
stds는 참조형 변수를 가지는 배열이다.
따라서 stds는 0값을 가지는 것이 아니라 null 값을 가지고 있었다.
그래서 if (stds[i] == null) 은 되지만, if (stds[i] == 0) 은 되지 않는다!
반대로 int형 배열인 a가 있다면, if (a[i] == 0) 은 되지만, if (a[i] == null) 은 오류가 난다.

5. 라이브러리 사용하기

간단한 음악 플레이어를 만들어보면서 라이브러리 사용법을 익혀보자.
엄청나게 멋있는 기능을 쓰기에는 아직 실력이 부족하지만, 어떤 식으로 라이브러리를 사용하고 적용시키는지 개념을 알아보는 시간이었다.

1) 구글에 검색하기

콩글리쉬도 OK! 일단 검색해보자.
java mp3 player library를 검색해보고 여러 글을 읽어보자.
안나오는 것 같으면 패스.
java recommended mp3 library 로도 검색해보자.
검색하다가 그 중 jlayer 라는 것을 알아냈다.
jlayer download 를 검색해서 파일을 다운받는다.

  • jar 파일
    • 자바 라이브러리 확장자 이름
    • 클래스 파일들을 모아둔 파일이다. (압축처럼)

2) 다운로드 받은 파일 경로 지정

편한 위치에 새폴더를 하나 만들어서 ‘lib’라고 이름을 지은 뒤 압축다운로드 파일을 압축을 풀어 jar 파일로 만들어놓는다. (.zip 파일로 두면 안됨!)

3) 매뉴얼을 확인한다

보통 라이브러리를 배포할 때는 사용자가 편하도록 매뉴얼 파일을 함께 배포하는데, 구할 수 없는 라이브러리인 경우도 있다.
jlayer도 매뉴얼을 구할 순 있지만 우선 구할 수 없는 경우라고 치고, 진행한다.

4) 매뉴얼을 구할 수 없다면?

구글에 jlayer example 이라고 검색해보자.
그러면 jlayer를 사용했던 다른 사람들의 샘플 코드를 볼 수 있다.
stact overflow에서 샘플 코드를 하나 찾았다!
무슨 코드인지는 몰라도 일단 써보도록 하자.

import javazoom.jl.player.Player;

try{
    FileInputStream fis = new FileInputStream("mysong.mp3");
    Player playMP3 = new Player(fis);
    playMP3.play();
}
catch(Exception exc){
    exc.printStackTrace();
    System.out.println("Failed to play the file.");
}

출처 : stackoverflow

5) 코드를 작동시켜보자

위 코드에서 catch문은 에러가 생겼을 때 작동하는 코드니 일단 패스.
try문을 봐보자.
FileInputStream에는 빨간 줄으로 에러가 떠있다. 아직 import를 하지 않아서이다.
자동 import 기능인 ctri+shift+o 를 눌러 에러를 제거해주자.
자동 import를 했는데 에러가 사라진다면, oracle에서 제공해주는 클래스라는 의미이다.
따라서 FileInputStream 클래스는 자동으로 제공되는 도구임을 알 수 있다.

import java.io.fileInputStream;

그러나 Player는 빨간 줄이 사라지지 않았다.
에러 메세지를 살펴보면, Player cannot be resolved to a type로 떠있는데 뜻은 Player는 자료형이 아닌데? Player가 뭐임? 이라고 하는 것을 볼 수 있다.
그 이유는 Player는 oracle에서 제공해주는 애가 아니라서이다.

Player 클래스는 다운 받은 파일 jlayer-1.0.1.jar 파일에 있다.
jlayer 라이브러리를 현재 프로젝트에 넣어서 사용해야 한다.

6) 라이브러리 파일 추가하기

  1. 프로젝트 우클릭
  2. Properties 클릭
  3. Java Build Path 클릭
  4. 상단 Libaries 탭 클릭
    이 프로젝트에 포함되어있는 라이브러리들의 목록을 보여준다.
  5. Classpath 클릭
  6. Add External JARs... 클릭
    oracle 제공 라이브러리가 아닌 외부 라이브러리 추가이기 때문
  7. 다운 받은 라이브러리 경로에 찾아가서 jar파일 선택 후 열기
  8. Apply and Close 클릭

7) 추가한 라이브러리 import

추가한 라이브러리를 alt + shift + o 로 다시 자동 import 해준다.

import javazoom.jl.player.Player;

eclipse 상 빨간 줄 에러는 모두 사라졌다.
실행을 해보자.

8) 코드 다듬기

FileNotFoundException 에러가 발생한다.
사실 당연한 이치이다. 내가 실행시키고 싶은 음악 파일을 선택하지도 않았는데 실행이 되는 것도 이상한 거다.
코드를 잘 살펴보면 "mysong.mp3"라는 누가봐도 실행시키려는 파일 제목이 담긴 코드 일부분을 볼 수 있다.
실행하고 싶은 음악 파일 아무거나 프로젝트 폴더에 드래그하여 추가한다.
나는 piano.mp3를 추가했다.
그리고 "mysong.mp3" 부분을 "piano.mp3" 로 바꿔준다.

댓글남기기