Java의 정석: CHAPTER 06 객체지향 프로그래밍 I

mucha·2023년 4월 17일

Java의 정석

목록 보기
6/6
post-thumbnail

CHAPTER 06 객체지향 프로그래밍 I

1. 객체지향언어


1.1 객체지향언어의 역사

처음에는 FORTRAN이나 COBOL과 같은 절차적 언어들이 주류.
그러나 프로그램의 규모가 점점 커지고 사용자들의 요구가 빠르게 변화해가는 상황.
절차적 언어로는 극복하기 어렵다는 한계를 느끼고 객체지향언어를 이용한 개발방법론이 대안으로 떠오름.

1.2 객체지향언어

객체지향언어의 주요 특징
1. 코드의 재사용성이 높다.
- 새로운 코드를 작성할 때 기존의 코드를 이용하여 쉽게 작성할 수 있다.
2. 코드의 관리가 용이
- 코드간의 관계를 이용해서 적은 노력으로 쉽게 코드를 변경할 수 있다.
3. 신뢰성이 높은 프로그래밍을 가능하게 만듦.
- 제어자와 메서드를 이용해서 데이터를 보호하고 올바른 값을 유지하도록 한다.
- 코드의 중복을 제거하여 코드의 불일치로 인한 오동작을 방지한다.

객체지향개념을 학습할 때 재사용성과 유지보수 그리고 중복된 코드의 제거 세 가지 관점에서 보면 쉽게 이해 가능.
너무 객체지향개념에 얽매여서 고민하기 보다는 일단 프로그램을 기능적으로 완성한 다음 어떻게 하면 보다 객체지향적으로 코드를 개선할 수 있을지 고민하여 점차 개선해나가는 것이 좋음.

2. 클래스와 객체


2.1 클래스와 객체의 정의와 용도

클래스의 정의: 객체를 정의해 놓은 것.
클래스의 용도: 객체를 생성하는데 사용.

객체의 정의: 실제로 존재하는 것. 사물 또는 개념.
객체의 용도: 객체가 가지고 있는 기능과 속성에 따라 다름.
유형의 객체: 책상, 의자, 자동차, TV와 같은 사물.
무형의 객체: 수학공식, 프로그램 에러와 같은 논리나 개념.

클래스를 정의하고 클래스를 통해 객체를 생성하는 이유
설계도를 통해서 제품을 만드는 이유와 같음.
클래스를 한번만 잘 만들어 놓기만 하면 매번 객체를 생성할 때마다 어떻게 객체를 만들어야 할지 고민 안해도 됨.

2.2 객체와 인스턴스

클래스로부터 객체를 만드는 과정을 클래스의 인스턴스화(instantiate)라고 함.
어떤 클래스로부터 만들어진 객체를 그 클래스의 인스턴스(instance)라고 함.
책상은 객체, 책상은 책상클래스의 인스턴스.
클래스 —인스턴스화→ 인스턴스(객체)

2.3 객체의 구성요소 - 속성과 기능

속성(property): 멤버변수(member variable), 특성(attribute), 필드(field), 상태(state)
기능(function): 메서드(method), 함수(function), 행위(behavior)

class Tv{
	// 변수
	String color; // 색깔
	boolean power; // 전원상태
	int channel; // 채널

	// 메서드
	void power() {power = !power;}
	void channelUp() {channel ++;}
	void channelDown() {channel --;}
}

2.4 인스턴스의 생성과 사용

클래스로부터 인스턴스를 생성해야 사용할 수 있음.

클래스명 변수명;
변수명 = new 클래스명();

Tv t; // Tv 클래스 타입의 참조변수 t 선언
t = new Tv(); // Tv 인스턴스 생성한 후, 생성된 Tv 인스턴스의 주소를 t에 저장
class Tv { 
     // Tv의 속성(멤버변수) 
     String color;           	// 색상 
     boolean power;         	// 전원상태(on/off) 
     int channel;           	// 채널 

     // Tv의 기능(메서드) 
     void power()   { power = !power; }  // TV를 켜거나 끄는 기능을 하는 메서드  
     void channelUp()   {  ++channel; }  // TV의 채널을 높이는 기능을 하는 메서드 
     void channelDown() { --channel; }   // TV의 채널을 낮추는 기능을 하는 메서드  
}

class TvTest { 
      public static void main(String args[]) { 
            Tv t;                  // Tv인스턴스를 참조하기 위한 변수 t를 선언       
            t = new Tv();          // Tv인스턴스를 생성한다. 
            t.channel = 7;         // Tv인스턴스의 멤버변수 channel의 값을 7로 한다. 
            t.channelDown();       // Tv인스턴스의 메서드 channelDown()을 호출한다. 
            System.out.println("현재 채널은 " + t.channel + " 입니다."); 
      } 
}

참조변수에는 하나의 값(주소)만이 저장될 수 있음.
둘 이상의 참조변수가 하나의 인스턴스를 가리키는 것은 가능하지만 하나의 참조변수가 여러 개의 인스턴스를 가리키는 것은 불가.

2.5 객체 배열

많은 수의 객체를 다뤄야할 때, 배열로 다루면 편리.
객체 배열 안에 객체가 저장되는 것이 아니라, 객체의 주소가 저장.

class TvTest4 {
	public static void main(String args[]) {
		Tv[] tvArr = new Tv[3]; // 길이가 3인 Tv객체 배열

		// Tv객체를 생성해서 Tv객체 배열의 각 요소에 저장
		for(int i=0; i < tvArr.length;i++) {
			tvArr[i] = new Tv();
			tvArr[i].channel = i+10; // tvArr[i]의 channel에 i+10을 저장
		}

		for(int i=0; i < tvArr.length;i++) {
			tvArr[i].channelUp();  // tvArr[i]의 메서드를 호출. 채널이 1증가
			System.out.printf("tvArr[%d].channel=%d%n",i,tvArr[i].channel);
		}
	} // main의 끝
}

class Tv { 
     // Tv의 속성(멤버변수) 
     String color;           // 색상 
     boolean power;          // 전원상태(on/off) 
     int channel;          	 // 채널 

     // Tv의 기능(메서드) 
     void power()   { power = !power; }  // TV를 켜거나 끄는 기능을 하는 메서드 
     void channelUp()   {  ++channel; }  // TV의 채널을 높이는 기능을 하는 메서드 
     void channelDown() {  --channel; }  // TV의 채널을 낮추는 기능을 하는 메서드  
}

2.6 클래스의 또 다른 정의

객체지향이론 관점에서 내린 정의
클래스는 객체를 생성하기 위한 틀이며 클래스는 속성과 기능으로 정의되어 있다.

프로그래밍 관점

  • 클래스 - 데이터와 함수의 결합
    1. 변수: 하나의 데이터를 저장할 수 있는 공간.
    2. 배열: 같은 종류의 여러 데이터를 하나의 집합으로 저장할 수 있는 공간.
    3. 구조체: 서로 관련된 여러 데이터를 종류에 관계없이 하나의 집합으로 저장할 수 있는 공간.
    4. 클래스: 데이터와 함수의 결합(구조체 + 함수)
  • 클래스 - 사용자정의 타입(user-defined type)
    • 프로그래밍언어에서 제공하는 자료형외에 프로그래머가 서로 관련된 변수들을 묶어서 하나의 타입으로 새로 추가하는 것을 사용자정의 타입이라고 함.
    • 객체지향언어에서는 클래스가 곧 사용자 정의 타입이다.
// 비객체지향적 코드
int hour1, hour2, hour3;
int minute1, minute2, minute3;
float second1, second2, second3;

int[] hour = new int[3];
int[] minute = new int[3];
float[] second = new float[3];

// 객체지향적 코드
Time t1 = new Time();
Time t2 = new Time();
Time t3 = new Time();

Time[] t = new Time[3];
t[0] = new Time();
t[1] = new Time();
t[2] = new Time();

3. 변수와 메서드


3.1 선언위치에 따른 변수의 종류

class Variables{
	int iv; // 인스턴스 변수
	static int cv; // 클래스변수 (static변수, 공유변수)

	void method() {
		int lv; // 지역변수
	}
}
변수의 종류선언위치생성시기
클래스변수클래스 영역클래스가 메모리에 올라갈 때
인스턴스변수클래스 영역인스턴스가 생성되었을 때
지역변수클래스 영역 이외의 영역변수 선언문이 수행되었을 때

인스턴스 변수(instance variable)
클래스 영역에 선언. 클래스의 인스턴스를 생성할 때 만들어짐.
인스턴스 변수의 값을 읽어 오거나 저장하기 위해서는 먼저 인스턴스를 생성해야함.
인스턴스는 독립적인 저장공간을 가지므로 서로 다른 값을 가질 수 있음.
인스턴스마다 고유한 상태를 유지해야하는 경우, 인스턴스변수로 선언.

클래스 변수(class variable)
인스턴스변수 앞에 static을 붙이기만 하면 클래스 변수 생성 가능.
클래스변수는 모든 인스턴스가 공통된 저장공간(변수)를 공유.
한 클래스의 모든 인스턴스들이 공통적인 값을 유지해야하는 속성의 경우, 클래스 변수로 선언.
클래스이름.클래스변수와 같은 형식으로 사용됨. ex) Variables.cv
클래스가 메모리에 로딩될때 생성되어 프로그램이 종료될 때까지 유지.
public을 앞에 붙이면 같은 프로그램 내에서 어디서나 접근할 수 있는 전역변수(global variable)의 성격을 가짐.

지역 변수(local variable)
메서드 내에 선언, 메서드 내에서만 사용 가능, 메서드가 종료되면 소멸.
for문 또는 while문의 블럭 내에 선언된 지역변수는, 지역변수가 선언된 { } 블럭 내에서만 사용 가능.

3.2 클래스변수와 인스턴스변수

클래스변수와 인스턴스변수의 차이를 이해하기 위한 예로 카드 게임에 사용되는 카드를 클래스로 정의.
카드의 속성으로는 무늬, 숫자, 폭, 높이가 있음.
Card 인스턴스는 자신만의 무늬와 숫자를 유지하고 있어야 함 → 인스턴스변수
각 카드의 폭과 높이는 모든 인스턴스가 공통적으로 같은 값을 가져야함 → 클래스변수

class CardTest{
	public static void main(String args[]) {
		System.out.println("Card.width = "  + Card.width);
		System.out.println("Card.height = " + Card.height);

		Card c1 = new Card();
		c1.kind = "Heart";
		c1.number = 7;

		Card c2 = new Card();
		c2.kind = "Spade";
		c2.number = 4;

		System.out.println("c1은 " + c1.kind + ", " + c1.number + "이며, 크기는 (" + c1.width + ", " + c1.height + ")" );
		System.out.println("c2는 " + c2.kind + ", " + c2.number + "이며, 크기는 (" + c2.width + ", " + c2.height + ")" );		

		System.out.println("c1의 width와 height를 각각 50, 80으로 변경합니다.");
		c1.width = 50;
		c1.height = 80;

		System.out.println("c1은 " + c1.kind + ", " + c1.number + "이며, 크기는 (" + c1.width + ", " + c1.height + ")" );
		System.out.println("c2는 " + c2.kind + ", " + c2.number + "이며, 크기는 (" + c2.width + ", " + c2.height + ")" );
	}
 }

class Card {
	String kind ;				// 카드의 무늬 - 인스턴스 변수
	int number;				    // 카드의 숫자 - 인스턴스 변수
	static int width = 100;		// 카드의 폭  - 클래스 변수
	static int height = 250;	// 카드의 높이 - 클래스 변수
}

인스턴스변수는 인스턴스가 생성될 때 마다 생성되므로 인스턴스마다 각기 다른 값을 유지할 수 있지만, 클래스 변수는 모든 인스턴스가 하나의 저장공간을 공유하므로, 항상 공통된 값을 갖는다.

3.3 메서드

메서드: 특정 작업을 수행하는 일련의 문장들을 하나로 묶은 것.
메서드가 작업을 수항해는데 필요한 값만 넣고 원하는 결과를 얻으면 될 뿐, 메서드가 내부적으로 어떤 과정을 거쳐 결과를 만들어내는지 몰라도 됨.

메서드를 사용하는 이유
1. 높은 재사용성(reusability)
2. 중복된 코드의 제거
3. 프로그램의 구조화

3.4 메서드의 선언과 구현

선언부, 구현부로 이루어져있음.

메서드 선언부(method declaration, method header)
메서드의 이름, 매개변수 선언, 반환타입으로 구성.
int add (int x, int y)

매개변수 선언(parameter declaration)
메서드가 작업을 수행하는데 필요한 값들을 제공받기 위한 것.
필요한 값의 개수만큼 변수를 선언, 각 변수 간의 구분은 쉼표를 사용.
각 변수의 타입이 같아도 변수의 타입을 생략할 수 없음.

메서드의 이름(method name)
메서드는 특정 작업을 수행하므로 동사인 경우가 많음.
이름만으로도 메서드의 기능을 쉽게 알 수 있도록 함축적이면서도 의미있는 이름을 짓도록 노력해야함.

반환타입(return type)
메서드의 작업수행 결과인 반환값의 타입을 적는다.
반환값이 없는 경우 반환타입으로 void를 적어야함.

메서드의 구현부(method body, 메서드 몸통)
메서드 선언부 다음에 오는 괄호 { }를 메서드의 구현부라고 함.
메서드를 호출했을 때 수행될 문장들을 넣는다.

return문
반환타입이 void가 아닌 경우, 구현부 { }안에 return 반환값이 반드시 포함되어 있어야함.
이 문장은 작업을 수행한 결과인 반환값을 호출한 메서드로 전달.
이 값의 타입은 반환타입과 일치하거나 적어도 자동 형변환이 가능한 것이어야 함.
단 하나의 값만 반환할 수 있음.

지역변수(local variable)
메서드 내에 선언된 변수.

3.5 메서드의 호출

메서드이름(값1, 값2);
인자(argument)와 매개변수(parameter)
괄호( )안에 지정해준 값들을 인자 또는 인수라고 함.
인자의 개수와 순서는 호출된 메서드에 선언된 매개변수와 일치해야 함.
인자의 타입은 매개변수의 타입과 일치하거나 자동 형변환이 가능한 것이어야 함.

메서드의 실행흐름

class MyMathTest {
	public static void main(String args[]) {
		MyMath mm = new MyMath();
		long result1 = mm.add(5L, 3L);
		long result2 = mm.subtract(5L, 3L);
		long result3 = mm.multiply(5L, 3L);
		double result4 = mm.divide(5L, 3L);
		System.out.println("add(5L, 3L) = "      + result1);
		System.out.println("subtract(5L, 3L) = " + result2);
		System.out.println("multiply(5L, 3L) = " + result3);
		System.out.println("divide(5L, 3L) = "   + result4);
	}
}

class MyMath {
	long add(long a, long b) {
		long result = a+b;
		return result;
	//	return a + b;	// 위의 두 줄을 이와 같이 한 줄로 간단히 할 수 있다.
	}

	long subtract(long a, long b) {
		return a - b;
	}

	long multiply(long a, long b) {
		return a * b;
	}

	double divide(double a, double b) {
		return a / b;
	}
}

3.6 return 문

return문은 현재 실행중인 메서드를 종료하고 호출한 메서드로 되돌아감.
모든 메서드에는 적어도 하나의 return문이 있어야 함.

반환값(return value)
return문의 반환값으로 주로 변수가 오긴 하지만 항상 그런 것은 아님.
수식을 입력하면 수식을 계산한 결과가 반환.

매개변수의 유효성 검사
매개변수의 값이 적절한 것인지 확인.
적절하지 않은 값이 매개변수를 통해 넘어온다면 매개변수의 값을 보정하던가, 보정하는 것이 불가능하다면 return문을 사용해 작업을 중단해야 함.

3.7 JVM의 메모리 구조

  1. 메서드 영역(method area)
    • 프로그램 실행 중 어떤 클래스가 사용되면, JVM은 해당 클래스의 클래스파일을 읽어서 분석하여 클래스에 대한 정보를 이곳에 저장.
    • 클래스의 클래스변수도 이 영역에 함께 생성.
  2. 힙(heap)
    • 인스턴스가 생성되는 공간.
    • 프로그램 실행 중 생성되는 인스턴스는 모두 이곳에 생성.
  3. 호출스택(call stack, execution stack)
    • 메서드의 작업에 필요한 메모리 공간을 제공.
    • 메서드가 호출되면 호출스택에 호출된 메서드를 위한 메모리가 할당, 작업을 수행하는 동안 지역변수들과 연산의 중간결과 등을 저장하는데 사용.
    • 메서드가 작업을 마치면 할당되었던 메모리공간은 반환되어 비워짐.
    • 아래에 있는 메서드가 바로 위의 메서드를 호출한 메서드.
class CallStackTest {
      public static void main(String[] args) {
            firstMethod();
      }

      static void firstMethod() {
            secondMethod();
      }

      static void secondMethod() {
            System.out.println("secondMethod()");            
      }
}

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

기본형 매개변수: read only
참조형 매개변수: read & write

3.9 참조형 반환타입

class Data { int x; }

class ReferenceReturnEx {
	public static void main(String[] args) 
	{
		Data d = new Data();
		d.x = 10;

		Data d2 = copy(d); 
		System.out.println("d.x ="+d.x);
		System.out.println("d2.x="+d2.x);
	}

	static Data copy(Data d) {
		Data tmp = new Data();
		tmp.x = d.x;

		return tmp;
	}
}

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

3.10 재귀호출(recursive call)

메서드 내부에서 메서드 자신을 다시 호출하는 것.

void method() {
	method();
}
class FactorialTest {
	public static void main(String args[]) {
		System.out.println(factorial(4)); // FactorialTest.factorial(4)
	}

	static long factorial(int n) {
		long result=0;

		if (n == 1) return 1;		

		return n * factorial(n-1); // ¸Þ¼­µå ÀÚ½ÅÀ» È£ÃâÇÑ´Ù.
	}
}

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

인스턴스 메서드: 인스턴스 변수와 관련된 작업을 하는, 즉 메서드의 작업을 수행하는데 인스턴스를 필요로 하는 메서드.
클래스 메서드: 인스턴스와 관계없는(인스턴스 변수나 인스턴스 메서드를 사용하지 않는) 메서드

  1. 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙인다.
  2. 클래스 변수(static변수)는 인스턴스를 생성하지 않아도 사용할 수 있다.
  3. 클래스 메서드(static메서드)는 인스턴스 변수를 사용할 수 없다.
  4. 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다.
class MyMath2 {
	long a, b;
	
	// 인스턴스변수 a, b만을 이용해서 작업하므로 매개변수가 필요없다.
	long add() 	    { return a + b; }  // 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; } // 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());
	}
}

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

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

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();
	}
}

클래스멤버는 언제나 참조 또는 호출이 가능하기 때문에 인스턴스멤버가 클래스멤버를 사용하는 것은 아무런 문제가 안됨.
그러나 인스턴스멤버는 반드시 객체를 생성한 후에만 참조 또는 호출이 가능하기 때문에 클래스멤버가 인스턴스멤버를 참조, 호출하기 위해서는 객체를 생성해야 함.

4. 오버로딩(overloading)


4.1 오버로딩이란?

메서드 오버로딩: 한 클래스 내에 같은 이름의 메서드를 여러 개 정의하는 것.

4.2 오버로딩의 조건

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

4.3 오버로딩의 예

//println

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);

4.4 오버로딩의 장점

오버로딩을 통해 같은 기능을 메서드들이 하나의 이름으로 정의될 수 있음.
메서드의 이름을 절약 가능.

class OverloadingTest {
	public static void main(String args[]) {
		MyMath3 mm = new MyMath3();
		System.out.println("mm.add(3, 3) 결과:"    + mm.add(3,3));
		System.out.println("mm.add(3L, 3) 결과: "  + mm.add(3L,3));
		System.out.println("mm.add(3, 3L) 결과: "  + mm.add(3,3L));
		System.out.println("mm.add(3L, 3L) 결과: " + mm.add(3L,3L));

		int[] a = {100, 200, 300};
		System.out.println("mm.add(a) 결과: " + mm.add(a));
   }
}

class MyMath3 {
	int add(int a, int b) {
		System.out.print("int add(int a, int b) - ");
		return a+b;
	}
	
	long add(int a, long b) {
		System.out.print("long add(int a, long b) - ");
		return a+b;
	}
	
	long add(long a, int b) {
		System.out.print("long add(long a, int b) - ");
		return a+b;
	}

	long add(long a, long b) {
		System.out.print("long add(long a, long b) - ");
		return a+b;
	}

	int add(int[] a) {		// 배열의 모든 요소의 합을 결과로 돌려준다.
		System.out.print("int add(int[] a) - ");
		int result = 0;
		for(int i=0; i < a.length;i++) {
			result += a[i];
		}	
		return result;
	}
}

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

가변인자: 메서드의 매개변수 개수를 동적으로 지정해주는 것.

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... args) {
		return concatenate("",args);
	}
*/
} // class

가변인자를 선언한 메서드를 오버로딩하면, 메서드를 호출했을 때와 이와 같이 구별되지 못하는 경우가 쉽기 때문에 주의.
가능하면 가변인자를 사용한 메서드는 오버로딩하지 않는 것이 좋음.

5. 생성자(constructor)


5.1 생성자란?

생성자: 인스턴스가 생성될 때 호출되는 인스턴스 초기화 메서드. 인스턴스 변수의 초기화 작업에 주로 사용.

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

class Card() {
	Card() {
	}
	Card(String k, int num) {
	}
}

연산자 new가 인스턴스를 생성하는 것이지 생성자가 인스턴스를 생성하는 것이 아님.

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

5.2 기본 생성자(default constructor)

모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 함.
컴파일 할 때, 소스파일의 클래스에 생성자가 하나도 정의되지 않은 경우 컴파일러는 자동적으로 아래와 같은 내용의 기본 생성자를 추가하여 컴파일.

클래스이름() {}
Card() {}
class Data1 {
	int value;
}

class Data2 {
	int value;

	Data2(int x) { 	// 매개변수가 있는 생성자.
		value = x;
	}
}

class ConstructorTest {
	public static void main(String[] args) {
		Data1 d1 = new Data1();
		Data2 d2 = new Data2();		// compile error발생
	}
}

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

5.3 매개변수가 있는 생성자

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

class Car {
	String color;		// 색상
	String gearType;	// 변속기 종류 - auto(자동), manual(수동)
	int door;			// 문의 개수

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

class CarTest {
	public static void main(String[] args) {
		Car c1 = new Car();
		c1.color = "white";
		c1.gearType = "auto";
		c1.door = 4;

		Car c2 = new Car("white", "auto", 4);

		System.out.println("c1의 color=" + c1.color + ", gearType=" + c1.gearType+ ", door="+c1.door);
		System.out.println("c2의 color=" + c2.color + ", gearType=" + c2.gearType+ ", door="+c2.door);
	}
}

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

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

class Car {
	String color;		// 색상
	String gearType;	// 변속기 종류 - auto(자동), manual(수동)
	int door;			// 문의 개수

	Car() {
		this("white", "auto", 4);	
	}

	Car(String color) {
		this(color, "auto", 4);
	}
	Car(String color, String gearType, int door) {
		this.color    = color;
		this.gearType = gearType;
		this.door     = door;
	}
}

class CarTest2 {
	public static void main(String[] args) {
		Car c1 = new Car();	
		Car c2 = new Car("blue");

		System.out.println("c1의 color=" + c1.color + ", gearType=" + c1.gearType+ ", door="+c1.door);
		System.out.println("c2의 color=" + c2.color + ", gearType=" + c2.gearType+ ", door="+c2.door);
	}
}

this
인스턴스 자신을 가리키는 참조변수.
인스턴스의 주소가 저장되어 있음.
모든 인스턴스메서드에 지역변수로 숨겨진 채로 존재.

this(), this(매개변수)
생성자. 같은 클래스의 다른 생성자를 호출할 때 사용.

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

class Car {
	String color;		// 색상
	String gearType;    // 변속기 종류 - auto(자동), manual(수동)
	int door;			// 문의 개수

	Car() {
		this("white", "auto", 4);
	}

	Car(Car c) {	// 인스턴스의 복사를 위한 생성자.
		color    = c.color;
		gearType = c.gearType;
		door     = c.door;
	}

	Car(String color, String gearType, int door) {
		this.color    = color;
		this.gearType = gearType;
		this.door     = door;
	}
}
class CarTest3 {
	public static void main(String[] args) {
		Car c1 = new Car();
		Car c2 = new Car(c1);	// c1의 복사본 c2를 생성한다.
		System.out.println("c1의 color=" + c1.color + ", gearType=" + c1.gearType+ ", door="+c1.door);
		System.out.println("c2의 color=" + c2.color + ", gearType=" + c2.gearType+ ", door="+c2.door);

		c1.door=100;	// c1의 인스턴스변수 door의 값을 변경한다.
		System.out.println("c1.door=100; 수행 후");
		System.out.println("c1의 color=" + c1.color + ", gearType=" + c1.gearType+ ", door="+c1.door);
		System.out.println("c2의 color=" + c2.color + ", gearType=" + c2.gearType+ ", door="+c2.door);
	}
}

인스턴스를 생성할 때 결정해야하는 2가지
1. 클래스 - 어떤 클래스의 인스턴스를 생성할 것인가?
2. 생성자 - 선택한 클래스의 어던 생성자로 인스턴스를 생성할 것인가?

6. 변수의 초기화


6.1 변수의 초기화

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

멤버변수의 초기화 방법
1. 명시적 초기화
2. 생성자
3. 초기화 블럭

6.2 명시적 초기화(explicit initialization)

변수를 선언과 동시에 초기화하는 것.

class Car {
	int door = 4; // 기본형 변수의 초기화
	Engine e = new Engine(); // 참조형 변수의 초기화
}

명시적 초기화가 간단하고 명료하긴 하지만, 보다 복잡한 초기화 작업이 필요할 때는 초기화 블럭 또는 생성자를 사용.

6.3 초기화 블럭(initialization block)

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

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

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

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();
	}
}
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]);
	}
}

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

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

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

class Product {
	static int count = 0;   // 생성된 인스턴스의 수를 저장하기 위한 변수
	int serialNo;	        // 인스턴스 고유의 번호

	{
		++count;
		serialNo = count;
	}

	public Product() {}     // 기본생성자, 생략가능
}

class ProductTest {
	public static void main(String args[]) {
		Product p1 = new Product();
		Product p2 = new Product();
		Product p3 = new Product();

		System.out.println("p1의 제품번호(serial no)는 " + p1.serialNo);
		System.out.println("p2의 제품번호(serial no)는 " + p2.serialNo);
		System.out.println("p3의 제품번호(serial no)는 " + p3.serialNo);
		System.out.println("생산된 제품의 수는 모두 "+Product.count+"개 입니다.");  
	}
}
class Document {
	static int count = 0;
	String name;     // 문서명(Document name)

	Document() {     // 문서 생성 시 문서명을 지정하지 않았을 때
		this("제목없음" + ++count);
	}

	Document(String name) {
		this.name = name;
		System.out.println("문서 " + this.name + "가 생성되었습니다.");
	}
}

class DocumentTest {
	public static void main(String args[]) {
		Document d1 = new Document();
		Document d2 = new Document("자바.txt");
		Document d3 = new Document();
		Document d4 = new Document();
	}
}

0개의 댓글