6-21 Tv클래스를 주어진 로직대로 완성하시오 완성한 후에 실행해서 주어진 실행결과와 일치하는지 확인하라.

class MyTv {
	boolean isPowerOn;
	int channel;
	int volume;
	final int MAX_VOLUME = 100;
	final int MIN_VOLUME = 0;
	final int MAX_CHANNEL = 100;
	final int MIN_CHANNEL = 1;

	void turnOnOff() {
		// (!) isPowerOn의 값이 true면 false로, false면 true로 바꾼다
	}

	void volumeUp() {
		// (2) voulum의 값이 MAX_VOLUME보다 작을 때만 값을 1증가시킨다/
	}

	void volumeDown() {
		// (3) volum의 값이 MIN_VOLUME보다 클 때만 값을 1감소시킨다.
	}

	void channelUp() {
		// (4) channel의 값을 1 증가시킨다.
		// 만일 channel이 MAX_CHANNEL이면, channel의 값을 MIN_CHANNEL로 다시 바꾼다.
	}

	void channelDown() {
		// (5)channel의 값을 1감소시킨다.
		// 만일 channel이 MIN_CHANNEL이면, channel의 값을 MAX_CHANNEL로 바꾼다.

	} // class MyTv

class Exercise6_21 {
	public static void main(String args[]) {
		MyTv t = new MyTv();
		t.channel = 100;
		t.volume = 0;
		System.out.println("CH:" + t.channel + ", VOL:" + t.volume);

		t.channelDown();
		t.volumeDown();
		System.out.println("CH:" + t.channel + ", VOL:" + t.volume);

		t.volume = 100;
		t.channelUp();
		t.volumeUp();
		System.out.println("CH:" + t.channel + ", VOL:" + t.volume);

	}
}
class MyTv {
	boolean isPowerOn;
	int channel;
	int volume;
	final int MAX_VOLUME = 100;
	final int MIN_VOLUME = 0;
	final int MAX_CHANNEL = 100;
	final int MIN_CHANNEL = 1;

	void turnOnOff() {
		// (!) isPowerOn의 값이 true면 false로, false면 true로 바꾼다
		isPowerOn = !isPowerOn;
	}

	void volumeUp() {
		// (2) voulume의 값이 MAX_VOLUME보다 작을 때만 값을 1증가시킨다/
		if (volume < MAX_VOLUME)
			volume += 1;
	}

	void volumeDown() {
		// (3) volume의 값이 MIN_VOLUME보다 클 때만 값을 1감소시킨다.
		if (volume > MIN_VOLUME)
			volume -= 1;
	}

	void channelUp() {
		// (4) channel의 값을 1 증가시킨다.
		// 만일 channel이 MAX_CHANNEL이면, channel의 값을 MIN_CHANNEL로 다시 바꾼다.
		if (channel == MAX_CHANNEL)
			channel = MIN_CHANNEL;
		else
			channel += 1;
	}

	void channelDown() {
		// (5)channel의 값을 1감소시킨다.
		// 만일 channel이 MIN_CHANNEL이면, channel의 값을 MAX_CHANNEL로 바꾼다.
		if (channel == MIN_CHANNEL)
			channel = MAX_CHANNEL;
		else
			channel -= 1;
	}
} // class MyTv

class Exercise6_21 {
	public static void main(String args[]) {
		MyTv t = new MyTv();
		t.channel = 100;
		t.volume = 0;
		System.out.println("CH:" + t.channel + ", VOL:" + t.volume);

		t.channelDown();
		t.volumeDown();
		System.out.println("CH:" + t.channel + ", VOL:" + t.volume);

		t.volume = 100;
		t.channelUp();
		t.volumeUp();
		System.out.println("CH:" + t.channel + ", VOL:" + t.volume);

	}
}
CH:100, VOL:0
CH:99, VOL:0
CH:100, VOL:100

 

 

 

 

6-22 다음과 같이 정의된 메서드를 작성하고 테스트하시오.

매서드명 : isNumber

기     능 : 주어진 문자열이 모두 숫자로만 이루어져있는지 확인한다.

모두 숫자로만 이루어져 있으면 true를 반환하고, 그렇지 않으면 false를 반환한다.

만일 주어진 문자열이 null이거나 빈문자열 ""이라면 false를 반환한다.

반환타입 : boolean

매개변수 : String str - 검사할 문자열

[Hint] String클래스의 charAt(int i)메서드를 사용하면 문자열의 i번째 위치한 문자를 얻을 수 있다.

public class Exercise6_22 {
	/*
	 * (1) isNumber . 메서드를 작성하시오
	 */
	public static void main(String[] args) {
	String str = "123";
	System.out.println(str+"는 숫자입니까? "+isNumber(str));
	str = "1234o";
	System.out.println(str+"는 숫자입니까? "+isNumber(str));
	}
}
public class Exercise6_22 {

	public static boolean isNumber(String str) {
		if (str == null || str == "") {
			return false;
		}
		for(int i=0; i<str.length();i++) {
			char ch = str.charAt(i);
			
			if (ch<'0' && ch>'9'){
				return false;
			}
		}
		return true;
	}
	public static void main(String[] args) {
		String str = "123";
		System.out.println(str + "는 숫자입니까? " + isNumber(str));
		str = "1234o";
		System.out.println(str + "는 숫자입니까? " + isNumber(str));
	}
}

 

 

 

 

6-23 다음과 같이 정의된 메서드를 작성하고 테스트하시오.

매서드명 : max

기     능 : 주어진 int형 배열의 값 중에서 제일 큰 값을 반환한다. 

만일 주어진 배열이 null이거나 크기가 0인 경우,  -999999를 반환한다.

반환타입 : int 

매개변수 : int[] arr - 최대값을 구할 배열

public class Exercise6_23 {
	/*
	 * (1) max메서드를 작성하시오.
	 */
			}

		}
		return max;
	}

	public static void main(String[] args) {
		int[] data = { 3, 2, 9, 4, 7 };
		System.out.println(java.util.Arrays.toString(data));
		System.out.println("최대값 :" + max(data));
		System.out.println("최대값 :" + max(null));
		System.out.println("최대값 :" + max(new int[] {})); // 0 최대값 크기가 인 배열
	}
}
public class Exercise6_23 {
	/*
	 * (1) max메서드를 작성하시오.
	 */
	static int max(int[] arr) {
		if (arr == null || arr.length == 0)
			return -999999;
		int max = arr[0];

		for (int i = 0; i < arr.length-1; i++) {
			if (arr[i + 1] > max) {
				max = arr[i + 1];
			}

		}
		return max;
	}

	public static void main(String[] args) {
		int[] data = { 3, 2, 9, 4, 7 };
		System.out.println(java.util.Arrays.toString(data));
		System.out.println("최대값 :" + max(data));
		System.out.println("최대값 :" + max(null));
		System.out.println("최대값 :" + max(new int[] {})); // 0 최대값 크기가 인 배열
	}
}
[3, 2, 9, 4, 7]
최대값 :9
최대값 :-999999
최대값 :-999999

 

 

 

6-24 다음과 같이 정의된 메서드를 작성하고 테스트하시오.

 

메서드명 : abs

기     능 : 주어진 값의 절대값을 반환한다.

반환타입 : int

매개변수 : int value

class Exercise6_24 {
		/*
		 * 	(1) abs메서드를 작성하시오.
		 */
	public static void main(String[] args)
	{
		int value = 5;
		System.out.println(value+"의 절대값:"+abs(value));
		value = -10;
		System.out.println(value+"의 절대값:"+abs(value));
	}
}
class Exercise6_24 {
	public static int abs(int value) {
		if (value > 0) {
			return value;
		} else {
			return -value;
		}
	}
	public static void main(String[] args) {
		int value = 5;
		System.out.println(value + "의 절대값:" + abs(value));
		value = -10;
		System.out.println(value + "의 절대값:" + abs(value));
	}
}
5의 절대값:5
-10의 절대값:10

6-11 다음 중 this에 대한 설명으로 맞지 않은 것은? (모두 고르시오)  b

a. 객체 자신을 가리키는 참조 변수이다.

b. 클래스 내에서라면 어디서든 사용할 수 있다.

c. 지역변수와 인스턴스 변수를 구별할 때 사용한다.

d. 클래스 메서드 내에서는 사용할 수 없다.

 

b. 인스턴스 메서드에서만 사용 가능하다.

 

 

6-12 다음 중 오버 로딩이 성립하기 위한 조건이 아닌 것은? (모두 고르시오) c, d

a. 매머드의 이름이 같아야 한다.

b. 매개변수의 개수나 타입이 달라야 한다.

c. 리턴 타입이 달라야 한다.

d. 매개변수의 이름이 달라야 한다

 

c. 리턴타입이 달라야 한다.

리턴 타입은 오버 로딩에 영향을 주지 못한다.

매개변수는 같고 리턴 타입이 다른 경우는 오버 로딩이 성립되지 않는다. 어떤 것을 호출해야 할지 모르기 때문이다.

 

d 매개변수의 이름이 달라야 한다

리턴 타입은 오버 로딩에 영향을 주지 못한다.

 

 

6-13 다음 중 아래의 add메서드를 올바르게 오버 로딩한 것은? (모두 고르시오) b, c, d

long add(int a, int b) { return a+b;}

a. long add(int x, int y) { return x+y;}

b. long add(long a, long b) { return a+b;}

c. int add(byte a, byte b) { return a+b;}

d. int add(long a, int b) { return (int)(a+b);}

 

b, c, d는 모두 메서드의 이름이 add로 같고 매개변수의 타입이 다르다.

 

 

6-14 다음 중 초기화에 대한 설명으로 옳지 않은 것은? ( 모두 고르시오 ) c, e

a. 멤버 변수는 자동 초기화되므로 초기화하지 않고도 값을 참조할 수 있다.

b. 지역변수는 사용하기 전에 반드시 초기화해야 한다.

c. 초기화 블록보다 생성자가 먼저 수행된다. 

d. 명시적 초기화를 제일 우선적으로 고려해야 한다.

e. 클래스 변수보다 인스턴스 변수가 먼저 초기화된다

 

c. 초기화 블록이 먼저 수행된다.

e. 클래스 변수가 먼저 초기화된다.

 

 

6-15 다음 중 인스턴스 변수의 초기화 순서가 올바른 것은? a

a. 기본값-명시적 초기화-초기화 블록-생성자

b. 기본값-명시적 초기화-생성자-초기화 블록

c. 기본값-초기화 블록-명시적 초기화-생성자

d. 기본값-초기화 블록-생성자-명시적 초기화

 

명시적 초기화가 가장 먼저 일어나고 생성자가 늦게 초기화된다.

 

 

6-16 다음 중 지역변수에 대한 설명으로 옳지 않은 것은? (모두 고르시오) a, e

a. 자동 초기화되므로 별도의 초기화가 필요 없다.

b. 지역변수가 선언된 메서드가 종료되면 지역변수도 함께 소멸된다.

c. 매머드의 매개변수로 선언된 변수도 지역변수이다.

d. 클래스 변수나 인스턴스 변수보다 메모리 부담이 적다.

e. 힙(heap) 영역에 생성되며 가비지 컬렉터에 의해 소멸된다.

 

a. 지역변수는 자동 초기화가 되지 않는다.

e 지역변수는 call stack에 생성된다.

 

 

 

6-17 호출 스택이 다음과 같은 상황일 때 옳지 않은 설명은? (모두 고르시오) b

 

a. main 제일 먼저 호출 스택에 저장된 것은 메서드이다.

b. println 메서드를 제외한 나머지 메서드들은 모두 종료된 상태이다. 

c. method2 메서드를 호출한 것은 main 메서드이다.

d. println 메서드가 종료되면 메서드가 method1 수행을 재개한다.

e. main-method2-method1-println. 의 순서로 호출되었다

f. 현재 실행 중인 메서드는 println 뿐이다.

 

b 다른 메서드들은 대기 중인 상태이다.

 

 

6-18 다음의 코드를 컴파일하면 에러가 발생한다 컴파일 에러가 발생하는 라인과 그 이유를 설명하시오.

class MemberCall {
	int iv = 10;
	static int cv = 20;
	int iv2 = cv;
	static int cv2 = iv; // A 라인

	static void staticMethod1() {
		System.out.println(cv);
		System.out.println(iv); // B 라인
	}

	void instanceMethod1() {
		System.out.println(cv);
		System.out.println(iv); // C 라인
	}

	static void staticMethod2() {
		staticMethod1();
		instanceMethod1(); // D 라인
	}

	void instanceMethod2() {
		staticMethod1(); // E 라인
		instanceMethod1();
	}
}

A라인 static변수의 초기화에 인스턴스를 사용할 수 없다.

B라인 static메서드에서는 인스턴스변수를 사용할 수 없다.

D라인 static메서드에서는 인스턴스메서드를 사용할 수 없다.

 

 

 

 

6-19 다음 코드의 실행 결과를 예측하여 적으시오.

public class Exercise6_19 {
	public static void change(String str) {
		str += "456";
	}

	public static void main(String[] args) {
		String str = "ABC123";
		System.out.println(str);
		change(str);
		System.out.println("After change:" + str);
	}
}
ABC123
After change:ABC123

 

 

6-20 다음과 같이 정의된 메서드를 작성하고 테스트하시오.

class Exercise6_20 {

	/*
		(1) shuffle . 메서드를 작성하시오
	*/

	}

	public static void main(String[] args) {
		int[] original = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
		System.out.println(java.util.Arrays.toString(original));

		int[] result = shuffle(original);
		System.out.println(java.util.Arrays.toString(result));
	}
}
	public static int[] shuffle(int[] arr) {
		if (arr == null || arr.length == 0) {
			return arr;
		}
		for (int i = 0; i < arr.length - 1; i++) {
			int j = (int) (Math.random() * arr.length);

			int tmp = arr[i];
			arr[i] = arr[j];
			arr[j] = tmp;
		}
		return arr;
	}

	public static void main(String[] args) {
		int[] original = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
		System.out.println(java.util.Arrays.toString(original));

		int[] result = shuffle(original);
		System.out.println(java.util.Arrays.toString(result));
	}
}
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[4, 5, 2, 9, 6, 3, 7, 8, 1]

일단 메모리를 공유해야하기 때문에 static으로 int형 shuffle 매서드를 만들고 배열의 자리를 바꾸는 코드를 짜주고 배열을 리턴하면 된다.

6-1 다음과 같은 멤버변수를 갖는 SutdaCard클래스를 정의하시오.

타 입 변수명 설 명
int num 카드의 숫자.(1~10사이의 정수)
boolean isKwang 광이면 true, 아니면 false
class SutdaCard {
	int num;
	boolean isKwang;
}

정의만 한 것이다.

 

6-2 문제 에서 정의한 클래스에 두 개의 생성자와 를 추가해서 실행결과와 같은 결과를 얻도록 하시오.

class Exercise6_2 {
	public static void main(String args[]) {
		SutdaCard card1 = new SutdaCard(3, false);
		SutdaCard card2 = new SutdaCard();
		System.out.println(card1.info()); // 3 . 이 출력된다
		System.out.println(card2.info()); // 1K . 가 출력된다
	}
}

class SutdaCard {
	int num;
	boolean isKwang;

	SutdaCard() {
		this(1, true); // SutdaCard(1, true) . 를 호출한다
	}

	SutdaCard(int num, boolean isKwang) {
		this.num = num;
		this.isKwang = isKwang;
	}

	String info() { // . ( ) K . 숫자를 문자열로 반환한다 광 인 경우 를 덧붙인다 光
		return num + (isKwang ? "K" : "");
	}
}

 

 

6-3 다음과 같은 멤버변수를 갖는  Student클래스를 정의하시오.

타입 변수명 설 명
String name 학생이름
int ban
int no 번호
int kor 국어점수
int eng 영어점수
int math 수학점수
public class Exercise06_3 {
}
class Student {
	String name;
	int ban;
	int no;
	int kor;
	int eng;
	int math;
}

 

 

6-4 문제 에서 정의한 클래스에 다음과 같이 정의된 두 개의 메서드getTotal()과 getAverage()를 추가하시오.

메서드명 : getTotal

기 능 : 국어 영어 수학 의 점수를 모두 더해서 반환한다.

반환타입 : int

매개변수 : 없음

2. : getAverage 메서드명 기 능 총점 국어점수 영어점수 수학점수 을 과목수로 나눈 평균을 구한다 소수점 둘째자리에서 반올림할 것.

반환타입 : float

매개변수 : 없음

class Exercise06_4 {
	public static void main(String args[]) {
		Student s = new Student();
		s.name = "홍길동 ";
		s.ban = 1;
		s.no = 1;
		s.kor = 100;
		s.eng = 60;
		s.math = 76;
		System.out.println("이름 :" + s.name);
		System.out.println("총점 :" + s.getTotal());
		System.out.println("평균 :" + s.getAverage());
	}
}
class Student {
	/*
	 * (1) . 알맞은 코드를 넣어 완성하시오
	 */
}

 

class Exercise06_4 {
	public static void main(String args[]) {
		Student s = new Student();
		s.name = "홍길동 ";
		s.ban = 1;
		s.no = 1;
		s.kor = 100;
		s.eng = 60;
		s.math = 76;
		System.out.println("이름 :" + s.name);
		System.out.println("총점 :" + s.getTotal());
		System.out.println("평균 :" + s.getAverage());
	}
}

class Student {
	String name;
	int ban;
	int no;
	int kor;
	int eng;
	int math;

	int getTotal() {
		return kor+math+eng;
	}
	float getAverage() {
		return (int)(getTotal() / 3f * 10 + 0.5f) / 10f;
	}
}
이름 :홍길동 
총점 :236
평균 :78.7

평균을 구하는식에서 int타입은 소수점이 버려지는것을 이용한 식이다.

 

 

 

6-6 두 점의 거리를 계산하는 를 완성하시오. getDistance()

[Hint] 제곱근 계산은 를 사용하면 된다. Math.sqrt(double a)

public class Exercise6_06 {
	static double getDistance(int x, int y, int x1, int y1) {
		/*
		 * (1) . 알맞은 코드를 넣어 완성하시오
		 */
	}
	public static void main(String args[]) {
		System.out.println(getDistance(1, 1, 2, 2));
	}
}
public class Exercise6_06 {
	static double getDistance(int x, int y, int x1, int y1) {

		return Math.sqrt((x-x1)*(x-x1)+(y-y1)*(y-y1));
	}
	public static void main(String args[]) {
		System.out.println(getDistance(1, 1, 2, 2));
	}
}
1.4142135623730951

제곱근을 구하면 된다.

Math.sqrt는 double타입을 반환한다.

 

 

 

6-7 문제 에서 작성한 클래스메서드getDistance()를 MyPoint클래스의 인스턴스메서드로 정의하시오.

class MyPoint{
	int x;
	int y;

	MyPoint(int x, int y) {
	this.x = x;
	this.y = y;
	}
	/*
	 * (1) getDistance . 인스턴스메서드 를 작성하시오
	 */
}
class Exercise6_07 {
	public static void main(String args[]) {
		MyPoint p = new MyPoint(1, 1);
		// p (2,2) . 와 의 거리를 구한다
		System.out.println(p.getDistance(2, 2));
	}
}
class MyPoint{
	int x;
	int y;

	MyPoint(int x, int y) {
	this.x = x;
	this.y = y;
	}
	double getDistance(int x1, int y1) {
		return Math.sqrt((x-x1)*(x-x1)+(y-y1)*(y-y1));
	}
}
class Exercise6_07 {
	public static void main(String args[]) {
		MyPoint p = new MyPoint(1, 1);
		// p (2,2) . 와 의 거리를 구한다
		System.out.println(p.getDistance(2, 2));
	}
}
1.4142135623730951

 

 

 

6-8 다음의 코드에 정의된 변수들을 종류별로 구분해서 적으시오.

public class PlayingCard {
	int kind;
	int num;
	static int width;
	static int height;

	PlayingCard(int k, int n) {
		kind = k;
		num = n;
	}

	public static void main(String args[]) {
		PlayingCard card = new PlayingCard(1, 1);
	}
}

- 클래스변수(static변수) : width, height

- 인스턴스변수 : kind, num

- 지역변수 : k, n, card, args

변수의 종류 선언위치 생성시기
클래스변수

클래스 영역 클래스가 메모리에 올라갈 때
인스턴스변수 인스턴스가 생성되었을 때
지역변수 클래스 영역 이외의 영역
(메서드, 생성자, 초기화 블럭 내부)
변수 선언문이수행되었을 때

 

6-9 다음은 컴퓨터 게임의 병사 를 클래스로 정의한 것이다 이 클래스의 멤버중에 static을 붙여야 하는 것은 어떤 것들이고 그 이유는 무엇인가?

(단, 모든 병사의 공격력과 방어력은 같아야 한다.)

class Marine {
	int x = 0, y = 0; // Marine (x,y) 의 위치좌표
	int hp = 60; // 현재 체력
	int weapon = 6; // 공격력
	int armor = 0; // 방어력

	void weaponUp() {
		weapon++;
	}
	void armorUp() {
		armor++;
	}
	void move(int x, int y) {
		this.x = x;
		this.y = y;
	}
}
class Marine {
	int x = 0, y = 0; // Marine (x,y) 의 위치좌표
	int hp = 60; // 현재 체력
	static int weapon = 6; // 공격력
	static int armor = 0; // 방어력

	static void weaponUp() {
		weapon++;
	}
	static void armorUp() {
		armor++;
	}
	void move(int x, int y) {
		this.x = x;
		this.y = y;
	}
}

공통적인 값을 가지는 변수들을 static변수로 선언해야한다.

공격력과 방어력이 같기 위해서는 기본 공격 방어력과 증감연산자가 들어간 메서드에 static을 넣으면 된다.

 

 

[6-10] 다음 중 생성자에 대한 설명으로 옳지 않은 것은 모두 고르시오 ? ( b,e )

a. 모든 생성자의 이름은 클래스의 이름과 동일해야한다.

b. 생성자는 객체를 생성하기 위한 것이다.

c. 클래스에는 생성자가 반드시 하나 이상 있어야 한다.

d. 생성자가 없는 클래스는 컴파일러가 기본 생성자를 추가한다.

e. 생성자는 오버로딩 할 수 없다.

 

b 객체를 생성하는것은 new 연산자이고 생성자는 초기화할 목적으로 사용된다.

e 생성자는 오버로딩이 가능하다.

 

추가적으로 생성자는 리턴값이 없다. 

생성자도 메서드이기 때문에 리턴값이 없다는 의미인 void를 적어야 하지만, 모든 생성자가 리턴값이 없으므로 생략이 가능하다.

변수를 선언하고 처음으로 값을 저장하는 것을 변수의 초기화라고 한다. 변수의 초기화는 경우에 따라서 필수적이기도 하고 선택적이기도 하지만, 가능하면 선언과 동시에 적절한 값으로 초기화 하는것이 바람직하다.

멤버변수는 초기화를 하지 않아도 자동적으로 변수의 자료형에 맞는 기본값으로 초기화가 이루어지므로 초기화하지 않고 사용해도 되지만, 지역변수는 사용하기 전에 반드시 초기화해야 한다.

class InitTest {
	int x;	//인스턴스변수
	int y = x;	//인스턴스변수

void method1() {
		int i;	//지역변수
		int j =i;	//에러 지역변수를 초기화하지 않고 사용
	}
}

x, y는 지역변수이고 i와 j는 지역변수이다 인스턴스 변수x는 초기화를 해주지 않아도 자동적으로 int형의 기본값인 0으로 초기화되므로 요류가 나지 않지만  method1()의 지역변수i는 자동적으로 초기화되지 않으므로, 초기화 되지 않은 상태에서 변수j를 초기화 하는데 사용될 수 없다.

멤버변수(클래스변수와 인스턴스변수)와 배열의 초기화는 선택적이지만, 지역변수의 초기화는 필수적이다.

각 타입의 기본값(default value)는 다음과 같다.

자료형 기본값
boolean false
char '\u0000'
byte, short, int 0
long 0L
float 0.0f
double 0.0d 또는 0.0
참조형변수 null

 

 

변수의 초기화에 대한 예를 몇 가지 더 살펴보자.

선언예 설 명
int i =10;
int j=10;
int형 변수 i를 선언하고 10으로 초기화 된다.
int형 변수 j를 선언하고 10으로 초기화 된다.
int i=10,  j=10; 같은 타입의 변수는 콤마(,)를 사용해서 함께 선언하거나 초기화 할 수 있다.
int i=10;, long j=10; 에러. 타입이 다른 변수는 함께 선언하거나 초기화할 수 없다.
int i=10;
int j=i;
변수 i에 저장된 값으로 변수 j를 초기화 한다.
변수 j는 i의 값인 10으로 초기화 된다.
int j=i;
int i=10;
에러. 변수 i가 선언되기 전에 i를 사용할 수 없다.

 

멤버변수의 초기화 방법
1. 명시적 초기화(explicit initialization)
2. 생성자(constructor)
3. 초기화 블럭(initialization block)
	- 인스턴스 초기화 블럭 :인스턴스변수를 초기화 하는데 사용.
	- 클래스 초기화 블럭 :클래스변수를 초기화 하는데 사용.

 

명시적 초기화(explicit initialization)

변수를 선언과 동시에 초기화하는 것을 명시적 초기화라고 한다.

 

초기화 블럭(initialization block)

초기화 블럭에는 클래스 초기화 블럭과 인스턴스 초기화 블럭 두 가지 종류가 있다.

클래스 초기화 블럭 클래스변수의 복잡한 초기화에 사용된다.
인스턴스 초기화 블럭 인스턴스변수의 복잡한 초기화에 사용된다.

초기화 블럭을 작성하려면, 인스턴스 초기화 블럭은 단순히 클래스 내에 블럭{}만들고 그안에 코드들을 작성하기만 하면 된다. 그리고 클래스 초기화 블럭은 인스턴스 초기화 블럭 앞에 단순히 static을 덧붙이기만 하면 된다.

 

초기화 블럭 내에는 매서드 내에서와 같이 조건문, 반복문, 예외처리구문 등을 자유롭게 사용할 수 있으므로, 초기화 작업이 복잡하여 명시적 초기화만으로는 부족한 경우 초기화 블럭을 사용한다.

class InitBlock {
	static {/* 클래스 초기화블럭 입니다. */}
	{/*인스턴스초기화블럭 입니다. */ }
}

클래스 초기화 블럭은 클래스가 메모리에 처음 로딩될 때 한번만 수행되며, 인스턴스 초기화블럭은 생성자와 같이 인스턴스를 생성할 때 마다 수행된다.

그리고 생성자보다 인스턴스 초기화 블럭이 먼저 수행된다는 것도 기억해두자.

클래스가 처음 로딩될 때 클래스변수들이 자동적으로 메모리에 만들어지고, 곧바로 클래스 초기화블럭이 클래스변수들을 초기화하게 되는 것이다.

 

인스턴스 변수의 초기화는 주로 생성자를 사용하고, 인스턴스 초기화 블럭은 모든 생성자에서 공통으로 수행돼야 하는 코드를 넣는데 사용한다.

Car() {
	count++;
	serialNo = count;
	color="White";
	gearType ="Auto";
}
Car(String color, String gearType) {
	count++;
	serialNo = count;
	this.color=color;
	this.gearType = gearType;
}

모든 생성자에 공통적으로 수행되어야 하는 문장들이 있을 때, 이 문장들을 각 생성자마다 써주기 보다는 아래와 같이 인스턴스 블럭에 넣어주면 코드가 보다 간결해진다.

{
	count++;
	serialNo = count;
}
Car() {
	clor = "White";
	gearType="Auto";
}

Car(String color, String gearType) {
	this.color=color;
	this.gearType = gearType;
}

코드의 중복을 제거하는 것은 코드의 신뢰성을 높여 주고, 오류의 발생가능성을 줄여 준다는 장점이 있다.

즉, 재사용성을 높이고 중복을 제거하는 것이 바로 객체 지향 프로그래밍이 추구하는 목표이다.

프로그래머는 이와 같은 객체지향언어의 요소들을 잘 이해하고 활용하여 코드의 중복을 최대한 제거해야 한다.

 

public class BlockTest {
	static {
		System.out.println("static { }");
	}
	
	{
		System.out.println("{ }");
	}
	
	public BlockTest() {
		System.out.println("생성자");
	}
	
	public static void main(String args[]) {
		System.out.println("BlockTest bt = new BlockTest(); ");
		BlockTest bt = new BlockTest();
		
		System.out.println("BlockTest bt2 = new BlockTest(); ");
		BlockTest bt2 = new BlockTest();
	}
}

예제가 실행되면서 BlockTest가 메모리에 로딩될 때, 클래스 초기화 블럭이 가장 먼저 수행되어 static {}이 화면에 출력된다. 그 다음에 main메서드가 수행되어 BlockTest인스턴스가 생성되면서 인스턴스 초기화 블럭이 먼저 수행되고, 끝으로 생성자가 수행된다.

클래스 초기화 블럭은 처음 메모리에 로딩될 때 한번만 수행되었지만, 인스턴스 초기화 블럭은 인스턴스가 생성될 때 마다 수행되었다.

public class StaticBlockTest {
	static int[] arr = new int[10];

	static {
		for(int i=0;i<arr.length;i++) {
			// 1과 10사이의 임의이 값을 배열 arr에 저장한다.
			arr[i] = (int)(Math.random()*10) + 1;
		}
	}
	public static void main(String args[]) {
		for(int i=0; i<arr.length;i++)
			System.out.println("arr["+i+"] :" + arr[i]);
	}
}
arr[0] :7
arr[1] :2
arr[2] :8
arr[3] :1
arr[4] :7
arr[5] :8
arr[6] :7
arr[7] :7
arr[8] :5
arr[9] :10

명시적 초기화를 통해 배열 arr을 생성하고, 클래스 초기화 블럭을 이용해서 배열의 각 요소들을 random()을 사용해서 임의의 값으로 채우도록 했다.

이처럼 배열이나 예외처리가 필요한 초기화에서는 명시적 초기화만으로는 복잡한 초기화작업을 할 수 없다.

이런 경우에 추가적으로 클래스 초기화 블럭을 사용하도록 한다.

인스턴스변수의 복잡한 초기화는 생성자 또는 인스턴스 초기화블럭을 사용한다.

 

멤버변수의 초기화 시기와 순서

클래스변수의 초기화시점	클래스가 처음 로딩될 때 단 한번 초기화 된다.
인스턴스변수의 초기화시점	인스턴스가 생성될 때마다 각 인스턴스별로 초기화가 이루어진다.

클래스변수의 초기화순서	기본값 > 명시적초기화 > 클래스초기화블럭
인스턴스변수의 초기화순서	기본값 > 명시적초기화 > 인스턴스 초기화 블럭 > 생성자

 

프로그램 실행도중 클래스에 대한 정보가 요구될 때, 클래스는 메모리에 로딩된다.

하지만, 해당 클래스가 이미 메모리에 로딩되어 있다면, 또다시 로딩하지 않는다. 초기화도 마찬가지로 수행하지 않는다.

클래스의 로딩 시기는 JVM의 종류에 따라 다를 수 있다.

필요할때 로딩하는lazyload와 실행효율을 높이기위해 미리 로딩하는preload가 있다.

 

class InitTest {
	static int cv = 1;
	int iv = 1;
	static { cv = 2; }
		{ iv = 2; }
	InitTest() {
		iv = 3;
	}
}

 

클래스 초기화 인스턴스 초기화
기본값 명시적
초기화
클래스
초기화블럭
기본값 명시적
초기화
인스턴스
초기화블럭
생성자
cv : 0

1

2

cv : 2
iv : 0
cv : 2
iv : 1
cv : 2
iv : 2
cv : 2
iv : 3
1 2 3 4 5 6 7

클래스변수 초기화 (1~3) :  클래스가 처음 메모리에 로딩될 때 차례대로 수행됨.

인스턴스변수 초기화(4~7) : 인스턴스를 생성할 때 차례대로 수행됨

※클래스변수는 항상 인스턴스변수보다 항상 먼저 생성되고 초기화 된다.

생성자란 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드이다.

인스턴스 변수의 초기화 작업에 주로 사용되며, 인스턴스 생성 시에 실행되어야 할 작업을 위해서도 사용된다.

인스턴스 초기화란, 인스턴스 변수들을 초기화하는 것을 뜻한다.

1. 생성자의 이름은 클래스의 이름과 같아야 한다.
2. 생성자는 리턴값이 없다.

생성자도 메서드이기 때문에 리턴값이리턴 값이 없다는 의미와 void를 붙어야 하지만, 모든 생성자가 리턴 값이 없으므로 void를 생략할 수 있다.

생성자도 오버로딩이 가능하므로 하나의 클래스에 여러 생성자가 존재할 수 있다.

 

연산자 new가 인스턴스를 생성하는 것이지 생성자(설계도면)가 인스턴스를 생성하는 것이 아니다.

생성자는 단순히 인스턴스변수들의 초기화에 사용되는 조금 특별한 메서드일 뿐이다.

 

Card클래스의 인스턴스를 생성하는 코드를 예로 들어, 수행되는 과정을 단계별로 나누어보면 다음과 같다.

	Card c = new Card();
    
1. 연산자 new에 의해서 메모리(heap)에 Card클래스의 인스턴스가 생성된다.
2. 생성자 Card()가 호출되어 수행된다.
3. 연산자 new의 결과로, 생성된 Card인스턴스의 주소가 반환되어 참조변수 c에 저장된다.

인스턴스를 생성하기위해 사용해왔던 클래스 이름()이 바로 생성자였다.

인스턴스를 생성할 때는 반드시 클래스 내에 정의된 생성자 중의 하나를 선택하여 지정해주어야 한다.

 

기본 생성자(default constructor)

모든 클래스에는 반드시 하나 이상의 생성자가 저의되어 있어야 한다.

컴파일할 때, 소스파일(*. java)의 클래스에 생성자가 하나도 정의되지 않은 경우 컴파일러는 자동적으로 아래와 같은 내용의 기본 생성자를 추가하여 컴파일한다.

클래스이름() { }
Card() { }

 컴파일러가 자동적으로 추가해주는 기본 생성자는 이와 같이 매개변수도 없고 아무런 내용도 없다.

특별히 인스턴스 초기화 작업이 요구되지 않는다면 생성자를 정의하지 않고 컴파일러가 제공하는 기본 생성자를 사용하는 것도 좋다.

클래스의 접근 제어자(Access Modifier)가 public인 경우에는 기본 생성자로 public 클래스 이름() {}이 추가된다.

class Data1 {
	int value;
}
class Data2 {
	int value;
	
	Data2(int x) {
		value = x;
	}
}
public class ConstructorTest {
	public static void main(String[] args) {
		Data1 d1 = new Data1();
		Data2 d2 = new Data2();
	}
}
Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
	The constructor Data2() is undefined

	at a210719.ConstructorTest.main(ConstructorTest.java:16)

이 예제를 컴파일하면 위와 같은 에러 메시지가 나타난다. 이것은 Data2에서 Data2()라는 생성자를 찾을 수 없다는 내용의 에러 메시지인데, Data2에 생성자 Data2()가 정의되어 있지 않기 때문에 에러가 발생한 것이다.

Data1에는 정의되어 있는 생성자가 하나도 없으므로 컴파일러가 기본 생성자를 추가해주었지만, Data2에는 이미 생성자 Data(int x)가 정의되어 있으므로 기본 생성자가 추가되지 않았기 때문이다.

기본 생성자가 컴파일러에 의해 추가되는 경우는 클래스에 정의된 생성자가 하나도 없을 때 뿐이다.

 

 

매개변수가 있는 생성자

생성자도 매개변수를 선언하여 호출 시 값을 넘겨받아서 인스턴스의 초기화 작업에 사용할 수 있다.

인스턴스마다 각기 다른 값으로 초기화(인스턴스 변수)되어야 하는 경우가 많기 때문에 매개변수를 사용한 초기화는 매우 유용하다.

class Car {
	String color;
	String gearType;
	int door;

	Car() {}
	Car(String c, String g, int d) {
		color = c;
		gearType = g;
		door = d;
	}
}

color, gearType, door 세 개의 인스턴스 변수와 두 개의 생성자만을 가지고 있다.

 

 

생성자에서 다른 생성자 호출하기 - this(), this

같은 클래스의 멤버들 간에 서로 호출할 수 있는 것처럼 생성자 간에도 서로 호출이 가능하다. 단, 다음의 두 조건을 만족시켜야 한다.

- 생성자의 이름으로 클래스이름 대신 this를 사용한다.
- 한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 호출이 가능하다.

생성자 내에서 다른 생성자를 호출할 때는 클래스 이름 대신에 this를 사용해야 한다.

생성자에서 다른 생성자를 첫 줄에서만 호출이 가능하도록 한 이유는 생성자 내에서 초기화 작업 도중에 다른 생성자를 호출하게 되면, 호출된 다른 생성자 내에서도 멤버 변수들의 값의 초기화를 할 것이므로 다른 생성자를 호출하기 이전의 초기화 작업이 무의미해질 수 있기 때문이다.

 

 

 

 

같은 클래스 내의 생성자들은 일반적으로 서로 관계가 깊은 경우가 많아서 이처럼 서로 호출하도록 하여 유기적으로 연결해주면 더 좋은 코드를 얻을 수 있다.

그리고 수정이 필요한 경우에도 보다 적은 코드만을 변경하면 되므로 유지보수가 쉬워진다.

 

1번코드
Car(Stromg c, String g, int d) {
	color = c;
	gearType = g;
	door = d;
}
2번 코드
Car(String color, String gearType, int door) {
	this.color = color;
	this.gearType = gearType;
	this.door = door;
}

1번 코드의 color = c는 생성자의 매개변수로 선언된 지역변수 c의 값을 인스턴스 변수 color에 저장한다.

이때 변수 colo와 c는 이름만으로도 구별되므로 아무런 문제가 없다.

하지만 2번 코드에서처럼 생성자의 매개변수로 선언된 변수의 이름이 colo로 인스턴스 변수 colo와 같을 경우에는 이름만으로는 두 변수가 서로 구별이 안 된다. 이런 경우에는 인스턴스변수 앞에 this를 사용하면 된다.

이렇게 하면 this, color은 인스턴스 변수이고, colo는 생성자의 매개변수로 정의된 지역변수로 서로 구별이 가능하다.

만일 2번 코드에서 this.color대신 color = color 같이하면 둘 다 지역변수로 간주된다.

이처럼 생성자의 매개변수로 인스턴스 변수들의 초기값을 제공받는 경우가 많기 때문에 매개변수와 인스턴스 변수의 이름이 일치하는 경우가 자주 있다. 이때는 1번 코드와 같이 매개변수 이름을 다르게 하는 것보다 this를 사용해서 서로 구별되도록 하는 것이 의미가 더 명확하고 이해하기 쉽다.

this는 참조 변수로 인스턴스 자신을 가리킨다. 참조 변수를 통해 인스턴스의 멤버에 접근할 수 있는 것처럼, this로 인스턴스 변수에 접근할 수 있다.

그러나 this를 사용할 수 있는 것은 인스턴스 멤버뿐이다. static메서드에서는 인스턴스 멤버들을 사용할 수 없는 것처럼, this 역시 사용할 수 없다. 왜냐하면, static메서드는 인스턴스를 생성하지 않고도 호출될 수 있으므로 static메서드가 호출된 시점에 인스턴스가 존재하지 않을 수도 있기 때문이다.

사실 생성자를 포함한 모든 인스턴스 메서드에는 자신이 관련된 인스턴스를 가리키는 참조 변수 this가 지역변수로 숨겨진 채로 존재한다.

일반적으로 인스턴스 메서드는 특정 인스턴스와 관련된 작업을 하기 때문에 자신과 관련된 인스턴스의 정보가 필요하지만, static메서드는 인스턴스와 관련 없는 작업을 하므로 인스턴스에 대한 정보가 필요 없기 때문이다.

 

생성자를 이용한 인스턴스의 복사

현재 사용하고 인스턴스와 같은 상태를 갖는 인스턴스를 하나 더 만들고자 할 때 생성자를 이용할 수 있다.

this 인스턴스 자신을 가리키는 참조변수, 인스턴스의 주소가 저장되어 있다.
	모든 인스턴스메서드에 지역변수로 숨겨진 채로 존재한다.
this(), this(매개변수) 생성자, 같은 클래스의 다른 생성자를 호출할 때 사용한다.

this와 this()는 비슷하게 생겼을 뿐 완전히 다른 것이다. this는 참조 변수고 this()는 생성자다.

 

생성자를 이용한 인스턴스의 복사

현재 사용하고 있는 인스턴스와 같은 상태를 갖는 인스턴스를 하나 더 만들고자 할 때 생성자를 이용할 수 있다.

두 인스턴스가 같은 상태를 갖는다는 것은 두 인스턴스의 모든 인스턴스 변수(상태)가 동일한 값을 갖고 있다는 것을 뜻한다.

하나의 클래스로부터 생성된 모든 인스턴스의 메서드와 클래스 변수는 서로 동일하기 때문에 인스턴스 간의 차이는, 인스턴스마다 각기 다른 값을 가질 수 있는 인스턴스 변수뿐이다.

Car(Car c) {
	color = c.color;
	gearType = c.gearType;
	door = c.door;
}

매개변수로 넘겨진 참조변수가 가리키는 Car인 인스턴스의 인스턴스변수인 color, gearType, door의 값을 인스턴스 자신으로 복사하는 것이다.

 어떤 인스턴스의 상태를 전혀 알지 못해도 똑같은 상태의 인스턴스를 추가로 생성할 수 있다. JavaAPI의 많은 클래스들이 인스턴스의 복사를 위한 생성자를 정의해놓고 있다.

Object클래스에 정의된 clone메서드를 이용하면 간단히 인스턴스를 복사할 수 있다.

 

인스턴스를 생성할 때는 다음의 2가지 사항을 결정해야 한다.
1. 클래스 - 어떤 클래스의 인스턴스를 생성할 것인가
2. 생성자 - 어떤 클래스의 어떤 생성자로 인스턴스를 생성할 것인가.

오버로딩의 의미

메서드도 변수와 마찬가지로 같은 클래스 내에서 서로 구별될 수 있어야 하기 때문에 각기 다른 이름을 가져야 한다.

자바에서는 한 클래스 내에 이미 사용하려는 이름과 같은 이름을 가진 메서드가 있더라도 매개변수의 개수 또는 타입이 다르면, 같은이름을 사용해서 메서드를 정의할 수 있다.

이 처럼, 한 클래스 내에서 같은 이름의 메서드를 여러 개 정의하는 것을 '메서드 오버로딩method overloading)또는 간단히 오버로딩(overloading)이라 한다.

 

오버로딩의 조건

같은 이름의 메서드를 정의한다고 해서 무조건 오버로딩인 것은 아니며, 다음조건을 만족해야한다.

1. 메서드 이름이 같아야 한다
2. 매개변수의 개수 또는 타입이 달라야 한다.

메서드의 이름이 같다 하더라도 매개변수가 다르면 서로 구별될 수 있기 때문에 오버로딩이 가능한 것이다.

오버로딩된 메서드들은 매개변수에 의해서만 구별될 수 있으므로 반환 타입은 오버로딩을 구현하는데 아무런 영향을 주지 못한다.

 

오버로딩의 예

오버로딩의 예로 가장 대표적인 것은 println메서드이다. println메서드를 호출할 때 매개변수로 지정하는 값의 타입에 따라서 호출되는 println메서드가 달라진다.

PrintStream클래스에는 어떤 종류의 매개변수를 지정해도 출력할 수 있도록 아래와 같이 10개의 오버로딩된 printlon메서드를 정의해놓고 있다.

void println()
void println(boolean x)
void println(char x)
void println(char[] x)
void println(double x)
void println(float x)
void println(int x)
void println(long x)
void println(Object x)
void println(String x)

println메서드를 호출할 때 매개변수로 넘겨주는 값의 타입에 따라서 위의 오버로딩된 메서드들 중의 하나가 선택되어 실행된다.

 

	int add(ina a, int b) { return a+b; }
	int add(int x, int y) { return x+y; }

이것은 매개변수의 이름만 다를 뿐 매개변수의 타입이 같기 때문에 오버로딩이 아니다.

컴파일 시 add(int,int) is already defined라는 에러가 난다.

 

	int add(int a, int b)	{ return a+b; }
	long add(int a, int b)	{return (long)(a + b); }

매개변수의 타입과 개수가 일치하기 때문에 어떤 메서드가 호출된 것인지 결정할 수 엇ㅂ기 때문에 오버로딩으로 간주되지 않고 add(int,int) is already defined라는 컴파일 에러가 난다.

 

	long add(int a, long b) { return a+b; }
	long add(long a, int b)	{ return a+b; }

두 메서드 모두 int형과 long형 매개변수가 하나씩 선언되어 있지만, 서로 순서가 다른 경우이다.

이 경우에 호출 시 매개변수의 값에 의해 호출될 메서드가 구분될 수 있으므로 중복된 메서드 정의가 아닌, 오버로딩으로 간주한다.

 

	int add(int a, itn b)
	long add(long a, long b)
	long add(int[] a) {
		long result = 0;

		for(int i = 0; i < a.length; i++) {
			result += a[i];
		}
		return result
}

위 메서드들은 모두 바르게 오버로딩되어 있다.

 

오버로딩의 장점

void println()
void printlnBoolean(boolean x_
void printlonChar(char x)
void printlnDouble(double x)
void printlnString(String x)

근본적으로 같은 기능을 하는 메서드들이지만 서로 다른 이름을 가져야 하기 때문에 메서드를 작성하는 쪽에서는 이름을 짓기도 어렵고, 메서드를 사용하는 쪽에서느 이름을 일일이 구분해서 기억하기때문에 부담이 되는데, 오버로딩을 사용하면 부담이 줄어든다.

기억하기도 쉽고 이름도 짧게 가능하고 이름만보고 메서드들의 기능이 같겠구나 하는 짐작도 쉽게 가능하며 메서드의 이름을 절약할 수 있다.

 

 

가변인자(varargs)와 오버로딩

매개변수의 개수를 유동적으로 지정하는것을 가변인자(variable arguments)라고한다.

가변인자는 '타입... 변수명'과 같은 형식으로 선언한다.

public PrintStream printf(String format, Object... args) {...}

위와 같이 가변인자 외에도 매개변수가 더 있다면, 가변인자를 매개변수 중에서 제일 마지막에 선언해야 한다.

가변인자인지 아닌지 구별할 방법이 없기 때문에 허용하지 않는 것이다.

가변인자는 내부적으로 배열을 이용하는것이다. 그래서 가변인자가 선언된 메서드를 호출할 때 마다 배열이 새로 생성된다.

C언어와는 달리 자바에서는 길이가 0인 배열을 생성하는 것이 허용된다.

public class VarArgsEx {
	public static void main(String[] args) {
		String[] strArr = { "100", "200", "300"	};
		
		System.out.println(concatenate("", "100", "200", "300"));
		System.out.println(concatenate("-", strArr));
		System.out.println(concatenate(",", new String[] {"1", "2", "3"}));
		System.out.println("["+concatenate(",", new String[0])+"]");
		System.out.println("["+concatenate(",")+"]");
	}
	
	static	String concatenate(String delim, String...args) {
		String result = "";
		
		for(String str : args) {
			result += str + delim;
		}
		
		return result;
	}
//	
//	static String concatenate(String...strings args) {
//		return concatenate("", args);
//	}
	
}
100200300
100-200-300-
1,2,3,
[]
[]

 

 

클래스 메서드(static메서드)와 인스턴스 메서드

변수에서처럼 메서드 앞에 static이 붙어 있으면 클래스 메서드이고 붙어있지 않으면 인스턴스 메서드이다.

클래스 메서드도 클래스 변수처럼, 객체를 생성하지 않고도, 클래스 이름. 메서드 이름(매개변수)과 같은 식으로 호출이 가능하다.

반면에 인스턴스 메서드는 반드시 객체를 생성해야만 호출할 수 있다.

인스턴스 메서드는 인스턴스 변수와 관련된 작업을 하는, 즉 메서드의 작업을 수행하는데 인스턴스 변수를 필요로 하는 메서드이다.

반면에 메서드 중에서 인스턴스와 관계없는 메서드를 클래스 메서드(static메서드)로 정의한다.

 

1. 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙인다.

- 생성된 각 인스턴스는 서로 독립적이기 때문에 각 인스턴스의 변수는 서로 다른 값을 유지한다.

그러나 모든 인스턴스에서 같은 값이 유지되어야 하는 변수는 static을 붙여서 클래스 변수로 정의해야 한다.

 

2. 클래스 변수(static변수)는 인스턴스를 생성하지 않아도 사용할 수 있다.

- static이 붙은 변수(클래스변수)는 클래스가 메모리에 올라갈 때 이미 자동적으로 생성되기 때문이다.

 

3. 클래스 메서드(static메서드)는 인스턴스 변수를 사용할 수 없다.

- 인스턴스변수는 인스턴스가 반드시 존재해야만 사용할 수 있는데, 클래스 메서드(static이 붙은 메서드)는 인스턴스 생성 없이 호출 가능하므로 클래스 메서드가 호출되었을 때 인스턴스가 존재하지 않을 수 있다. 그래서 클래스 메서드에서 인스턴스 변수의 사용을 금지한다.

반면에 인스턴스 변수나 인스턴스 메서드에서는 static이 붙은 멤버들을 사용하는 것이 언제나 가능하다. 인스턴스 변수가 존재한다는 것은 static변수가 이미 메모리에 존재한다는 것을 의미하기 때문이다.

 

4. 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다.

- 메서드의 작업내용 중에서 인스턴스 변수를 필요로 한다면, static을 붙일 수 없다. 반대로 인스턴스 변수를 필요로 하지 않는다면 static을 붙이자. 메서드 호출시간이 짧아지므로 성능이 향상된다. static을 안 붙인 메서드(인스턴스 메서드)는 실행 시 호출되어야 할 메서드를 찾는 과정이 추가적으로 필요하기 때문에 시간이 더 걸린다.

- 클래스의 멤버변수 중 모든 인스턴스에 공통된 값을 유지해야하는 것이 있는지 살펴보고 있으면, 
  static을 붙여준다.
- 작성한 메서드 중에서 인스턴스 변수나 인스턴스 메서드를 사용하지 않는 메서드에 
  static을 붙일 것을 고려한다

random()과 같은 Math클래스의 메서드는 모두 클래스 메서드이다.(인스턴스를 만들 수 없다) Math크래스에는 인스턴스 변수가 하나도 없거니와 작업을 수행하는데 필요한 값들을 모두 매개변수로 받아서 처리하기 때문이다.

 

class MyMath2 {
	long a, b;
	
	long add()		{ return a + b;	}
	long subtract()	{ return a - b;	}
	long multiply()	{ return a * b;	}
	double divide()	{ return a / b;	}
	
	static long		add(long a, long b)			{ return a + b;	}
	static long		subtract(long a, long b)	{ return a - b;	}
	static long		multiply(long a, long b)	{ return a * b;	}
	static double	divide(double a, double b)	{ return a / b;	}
}

class MyMathTest2 {
	public static void main(String[] args) {
		System.out.println(MyMath2.add(200L, 100L));
		System.out.println(MyMath2.subtract(200L, 100L));
		System.out.println(MyMath2.multiply(200L, 100L));
		System.out.println(MyMath2.divide(200.0, 100.0));
		
		MyMath2 mm = new MyMath2();
		mm.a = 200L;
		mm.b = 100L;
		
		System.out.println(mm.add());
		System.out.println(mm.subtract());
		System.out.println(mm.multiply());
		System.out.println(mm.divide());
	}
}
300
100
20000
2.0
300
100
20000
2.0

인스턴스 메서드인 add(), subtract(), multiply(), divide()는 인스턴스 변수인 a와 b만으로도 충분히 작업이 가능하기 때문에, 매개변수를 필요하지 않으므로 괄호()에 매개변수를 선언하지 않았다.

반면에 add(long a, long b), subtract(long a, long b)등은 인스턴스 변수 없이 매개 변수만으로 작업을 수행하기 때문에 static을 붙여서 클래스 메서드로 선언하였다.

MyMath2의 main메서드에서 보면, 클래스메서드는 객체 생성 없이 바로 호출이 가능했고, 인스턴스 메서드는 MyMath클래스의 인스턴스를 생성한 후에야 호출이 가능했다.

 

 

클래스 멤버와 인스턴스 멤버간의 참조와 호출

같은 클래스에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다. 단, 클래스 멤버가 인스턴스 멤버를 참조 또는 호출하고자 하는 경우에는 인스턴스를 생성해야 한다.

인스턴스 멤버가 존재하는 시점에 클래스 멤버는 항상 존재하지만, 클래스 멤버가 존재하는 시점에 인스턴스 멤버가 존재하지 않을 수도 있기 때문이다.

 

class MemberCall {
	int iv = 10;
	static int cv = 20;
	
	int iv2 = cv;
//	static int cv2 = iv;
	static int cv2 = new MemberCall().iv;
	
	static void staticMethod1() {
		System.out.println(cv);
//		System.out.println(iv);
		MemberCall c = new MemberCall();
		System.out.println(c.iv);
	}
	void instanceMethod1() {
		System.out.println(cv);	
		System.out.println(iv);	
	}
	
	static void staticMethod2() {
		staticMethod1();
//		instanceMethod1();
		MemberCall c = new MemberCall();
		c.instanceMethod1();
	}
	
	void instanceMethod2() {
		staticMethod1();
		instanceMethod1();
	}
}

//표시된 라인은 에러가 나는 라인이다.

클래스 멤버는 언제나 참조 또는 호출이 가능하기 때문에 인스턴스 멤버가 클래스 멤버를 사용하는 것은 아무런 문제가 안된다. 클래스 멤버 간의 참조 또는 호출 역시 아무런 문제가 없다.

그러나, 인스턴스멤버(인스턴스변수와 인스턴스 메서드)는 반드시 객체를 생성한 후에만 참조 또는 호출이 가능하기 때문에 클래스 멤버가 인스턴스 멤버를 참조, 호출하기 위해서는 객체를 생성하여야 한다.

 하지만, 인스턴스멤버간의 호출에는 아무런 문제가 없다. 하나의 인스턴스 멤버가 존재한다는 것은 인스턴스가 이미 생성되어있다는 것을 의미하며, 즉 다른 인스턴스 멤버들도 모두 존재하기 때문이다.

 

수학에서의 대입법처럼, c = new MemberCall()이므로 c.instanceMethod1();에서 c대신 new MemberCall()을 대입하여 사용할 수 있다.

 

	MemberCall c = new MemberCall();
	int result = c.instanceMetohd1();

이 코드를 요약하면

	int result = new MemberCall().instanceMetohd1();

대신 참조변수를 선언하지 않았기 때문에 생성된 MemberCall인스턴스는 더 이상 사용할 수 없다.

JVM의 메모리 구조 

응용프로그램이 실행되면 JVM은 시스템으로부터 프로그램을 수행하는데 필요한 메모리를 할당받고 JJVM은 이 메모리를 용도에 따라 여러 영역으로 나누어 관리한다.

 

1. 메서드 영역(method area)

프로그램 실행 중 어떤 클래스가 사용되면, JVM은 해당 클래스의 클래스 파일을 읽어서 분석하여 클래스에 대한 정보(클래스 데이터)를 이곳에 저장한다. 이때, 그 클래스의 클래스 변수(class variable)도 이 영역에 함께 생성된다.

 

2. 힙(heap)

인스턴스가 생성되는 공간, 프로그램 실행 중 생성되는 인스턴스는 모두 이곳에 생성된다.

즉, 인스턴스 변수(instance variable)들이 생성되는 공간이다.

 

3. 호출스택(call stack 또는 excution stack)

호출 스택은 메서드의 작업에 필요한 메모리 공간을 제공한다. 메서드가 호출되면, 호출 스택에 호출된 메서드를 위한 메모리가 할당되며, 이 메모리는 메서드가 작업을 수행하는 동안 지역변수(매개변수 포함)들과 연산의 중간결과 등을 저장하는 데 사용된다. 그리고 메서드가 작업을 마치면 할당되었던 메모리 공간은 반환되어 비워진다.

- 메서드가 호출되면 수행에 필요한 만큼의 메모리를 스택에 할당받는다.
- 메서드가 수행을 마치고나면 사용했던 메모리를 반환하고 스택에서 제거된다.
- 호출스택의 제일 위에 있는 메서드가 현재 실행 중인 메서드이다.
- 아래에 있는 메서드가 바로 위의 메서드를 호출한 메서드이다.

반환타입(return type)이 있는 메서드는 종료되면서 결괏값을 자신을 호출한 메서드(caller)에게 반환한다.

 

public class CallStackTest {
	public static void main(String[] args) {
		firstMethod();
	}
	static void firstMethod() {
		secondMethod();
	}
	static void secondMethod() {
		System.out.println("secondMethod()");
	}
}
secondMethod()

 

1~2. 위의 예제를 실행시키면, JVM에 의해서 main메서드가 호출됨으로써 프로그램이 시작된다. 이때, 호출 스택에는 main메서드를 위한 메모리 공간이 할당되고 main메서드의 코드가 수행되기 시작한다.

3.main메서드에서 firstMethod()를 호출한 상태이다. 아직 main메서드가 끝난 것은 아니므로 main메서드는 호출스택에 대기상태로 남아있고 firstMethod()의 수행이 시작된다.

4. firstMethod()에서 다시 secondeMethod()를 호출했다. firstMethod()는 secondMethod()가 수행을 마칠 때까지 대기상태에 있게 된다. secondMethod()가 수행을 마쳐야 firstMetohd()의 나머지 문장들을 수행할 수 있기 때문이다.

5. secondMethod()에서 println()을 호출했다. println메서드에 의해 secondMethod()가 화면에 출력된다.

6. println메서드의 수행이 완료되어 호출스택에서 사라지고 자신을 호출한 second Method()로 되돌아간다. 대기 중이던 secondMethod()는 println()을 호출한 이후부터 수행을 재개한다.

7. secondMethod()에 더 이상 수행할 코드가 없으므로 종료되고, 자신을 호출한 firstMetohd()로 돌아간다.

8. firstMethod()에 더 이상 수행할 코드가 없으므로 종료되고, 자신을 호출한 main메서드로 돌아간다.

9. main메서드에도 더 이상 수행할 코드가 없으므로 종료되어, 호출 스택은 완전히 비워지게 되고 프로그램은 종료된다.

 

 

 

기본형 매개변수와 참조형 매개변수

자바에서는 메서드를 호출할 때 매개변수로 지정된 값을 메서드의 매개변수에 복사해서 넘겨준다.

매개변수의 타입이 기본형(primitive type)일 때는 기본형 값이 복사되겠지만, 참조형(reference type)이면 인스턴스의 주소가 복사된다.

메서드의 매개변수를 기본형으로 선언하면 단순히 저장된 값만 얻지만, 참조형으로 선언하면 값이 저장된 곳의 주소를 알 수 있기 때문에 값을 읽어 오는 것은 물론 값을 변경하는 것도 가능하다.

기본형 매개변수 변수의 값을 읽기만 할 수 있다.(read only)
참조형 매개변수 변수의 값을 읽고 변경할 수 있다.(read & write)

 

 

public class PrimitiveParamEx_1 {
	public static void main(String[] args) {
		Data d = new Data();
		d.x = 10;
		System.out.println("main() : x = " + d.x);
		
		change(d.x);
		System.out.println("After change(d.x)");
		System.out.println("main() : x = " + d.x);
		
	}
	static void change(int x) {
		x = 1000;
		System.out.println("change1() : x = " + x);
	}
}
main() : x = 10
change1() : x = 1000
After change(d.x)
main() : x = 10

원본이 아니라 복사본이 변경된 것이라 원본에는 아무런 영향을 미치지 못한다.

 

 

public class ReferenceParamEx {
	public static void main(String[] args) {
		Data d = new Data();
		d.x = 10;
		System.out.println("main() : x = " + d.x);

	}

	static void change(Data d) {
		d.x = 1000;
		System.out.println("change() : x = " + d.x);

	}
}
main() : x = 10
change() : x = 1000
After change(x)
main() : x = 1000
public class ReferenceParamEx2 {
	public static void main(String[] args) {
		int[] x = {10};
		System.out.println("main() : x = "+ x[0]);
		
		change(x);
		System.out.println("After change(x)");
		System.out.println("main() : x = "+ x[0]);
	}
	
	static void change(int[] x) {
		x[0] = 1000;
		System.out.println("change() : x = " +x[0]);
	}
}
main() : x = 10
change() : x = 1000
After change(x)
main() : x = 1000

배열이 참조변수를 통해 데이터가 저장된 공간에 접근한다는 것을 배웠는데,

Data클래스의 인스턴스와 길이가 1인 배열을 선언해서 같은 객체를 가르키게 함으로써 값의 변경이 가능해진다.

 

 

참조형 반환타입

매개변수뿐만 아니라 반환타입도 참조형이 될 수 있다. 반환타입이 참조형이라는 것은 반환하는 값의 타입이 참조형이라는 얘기인데, 모든 참조형 타입의 값은 객체의 주소이므로 그저 정수값이 반환되는 것일 뿐 특별할 것이 없다.

반환타입이 참조형이라는 것은 메서드가 객체의 주소를 반환한다는 것을 의미한다.

 

 

재귀호출(recursive call)

메서드 내부에서 메서드 자신을 호출하는 것을 재귀호출(recursive call)이라 하고, 재귀호출을 하는 메서드를 재귀 메서드라한다.

void method() {
	method;	//재귀호출. 메서드 자신을 호출한다.
}

대부분의 재귀호출은 반복문으로 작성하는것이 가능하다. 반복문은 그저 같은 문장을 반복해서 수행하는 것이지만, 메서드를 호출하는 것은 반복문보다 몇 가지 과정, 예를 들면 매개변수 복사와 종류 후 복귀할 주소저장 등,이 추가로 필요하기 때문에 반복문보다 재귀호출의 수행시간이 더 오래 걸린다.

반면에 재귀호출이 주는 논리적 간결함 때문에 특정 상황에서는 단순한구조로 만들어서 알아보기 쉽게 작성하는 것이 논리적 오류가 발생할 확률도 줄어들고 나중에 수정하기 좋다.

 

+ Recent posts