SsY/Class

014. 클래스와 인스턴스 실습, 생성자(Constructor)

planet-si 2023. 3. 2. 01:13
728x90

2023.02.13 (월)

클래스와 인스턴스
- 실습
  • Test072
    - 클래스와 인스턴스
더보기
/*=============================================
	 ■■■ 클래스와 인스턴스 ■■■
==============================================*/

// 사용자로부터 임의의 정수를 입력받아
// 1 부터 입력받은 수 까지의 합을 연산하여
// 결과값을 출력하는 프로그램을 구현한다.

// 단, 지금까지처럼 main()메소드에 모든 기능을 적용하는 것이 아니라
// 클래스와 인스턴스의 개념을 활용하여 처리할 수 있도록 한다.
// (→Hap 클래스 설계)
// 또한, 데이터 입력 처리 과정에서 BufferedReader 의 readLine() 을 사용하며,
// 입력 데이터가 1보다 작거나 1000보다 큰 경우
// 다시 입력받을 수 있는 처리를 포함하여 프로그램을 구현할 수 있도록 한다.

// 실행 예)
// 임의의 정수 입력(1 ~ 1000) : 1050
// 임의의 정수 입력(1 ~ 1000) : -45
// 임의의 정수 입력(1 ~ 1000) : 100
// >> 1 ~ 100 까지의 합 : 5050
// 계속하려면 아무 키나 누르세요...

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

class Hap
{
	int n, a, s;	// 임의의 정수, 1부터 증가해나갈 값, 합계를 담을 변수

	void input() throws IOException
	{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		do
		{
			System.out.print("임의의 정수 입력(1~1000) : ");
			n = Integer.parseInt(br.readLine());
		}
		while (n<1 || n>1000);
	}

	int cal()
	{
		s = 0;
		for (a=1; a<=n; a++)
			s += a;
		return s;
	}

	void print(int sum)
	{
		System.out.printf(">> 1 ~ %d 까지의 합 : %d\n", n, sum);
	}

}// Hap class end

public class Test072
{
	public static void main(String[] args) throws IOException
	{
		Hap h = new Hap();

		h.input();
		int ans = h.cal();
		h.print(ans);

	}//main end
}//class end

// 실행 결과 

/*
임의의 정수 입력(1~1000) : 1050
임의의 정수 입력(1~1000) : -50
임의의 정수 입력(1~1000) : 54
>> 1 ~ 54 까지의 합 : 1485
계속하려면 아무 키나 누르십시오 . . .
*/

  •  Test073
    - 클래스와 인스턴스
더보기
/*=============================================
	 ■■■ 클래스와 인스턴스 ■■■
==============================================*/

// 사용자로부터 임의의 두 정수와 연산자를 입력받아
// 해당 연산을 수행하는 프로그램을 구현한다.
// 단, 클래스와 인스턴스의 개념을 활용하여 작성할 수 있도록 한다.
// (→ Calculate 클래스 설계)

// 실행 예)
// 임의의 두 정수 입력 (공백 구분) : 10 5 
// 임의의 연산자 [+ - * /]		   : +
// 10 + 5 = 15
// 계속하려면 아무 키나 누르세요...

import java.util.Scanner;
import java.io.IOException;

class Calculate
{
	// 주요 변수 선언
	int su1, su2;               // 사용자로부터 입력받은 두 정수를 담아낼 변수
	char op;                    // 사용자로부터 입력받은 연산자를 담아낼 변수

	// 메소드 정의 (기능 : 입력)
	void input() throws IOException
	{
		Scanner sc = new Scanner(System.in);

		System.out.print("임의의 두 정수 입력(공백 구분) : ");
		su1 = sc.nextInt();
		su2 = sc.nextInt();

		System.out.print("임의의 연산자 [+ - * /] : ");
		op = (char)System.in.read();
	}
	
	// 메소드 정의 (기능 : 연산)
	// → 나눗셈 연산을 실수 기반으로 처리 → 반환 자료형 → double
	//int cal()
	//{
	//}
	
	// → 나눗셈 연산을 정수 기반으로 처리 → 반환 자료형 → int
	double cal()
	{
		double result = -1;
		
		switch (op)
		{
		case '+': result = su1 + su2; break;
		case '-': result = su1 - su2; break;
		case '*': result = su1 * su2; break;
		case '/': result = (double)su1 / su2; break;
		//default : result = -1     // result 값이 초기화 되어있기 때문에 생략 가능
		}
		return result;
	}

	// 메소드 정의 (기능 : 출력)
	void print(double s)
	{
		System.out.printf(">> %d %c %d = %.2f\n", su1, op, su2, s);
	}

}// Calculate class end

public class Test073
{
	public static void main(String[] args) throws IOException
	{
		Calculate cal = new Calculate();

		cal.input();                // 생성한 인스턴스를 통한 입력 메소드 호출
		double r = cal.cal();       // 생성한 인스턴스를 통한 연산 메소드 호출
		cal.print(r);               // 생성한 인스턴스를 통한 출력 메소드 호출
	}
}

// 실행결과 

/*
임의의 두 정수 입력 (공백 구분) : 50 3
임의의 연산자 [+ - * /] : +
50 + 3 = 53.0
계속하려면 아무 키나 누르십시오 . . .
*/
/*
임의의 두 정수 입력 (공백 구분) : 30 8
임의의 연산자 [+ - * /] : -
30 - 8 = 22.0
계속하려면 아무 키나 누르십시오 . . .
*/
/*
임의의 두 정수 입력 (공백 구분) : 31 132
임의의 연산자 [+ - * /] : *
31 * 132 = 4092.0
계속하려면 아무 키나 누르십시오 . . .
*/
/*
임의의 두 정수 입력 (공백 구분) : 654 54
임의의 연산자 [+ - * /] : /
654 / 54 = 12.1
계속하려면 아무 키나 누르십시오 . . .
*/

클래스와 인스턴스
- 생성자 (Constructor)
  • 생성자를 사용하는 이유
    ※ 서로 다른 인스턴스의 생성은, 인스턴스 변수의 초기화라는 문제를 고민하게 한다.
더보기

지금까지 작성해 왔던 방식으로 인스턴스 생성 후 변수나 메소드에 접근하는 방식을 이용한다면,

▶ 변수의 개수가 많아질수록 매우 비합리적인 행위를 반복해야만 한다.
→ 그렇다면, 사과장수의 속성값(변수)를 원하는 형태로 초기화 하는 메소드를 만들면?

▶ 속성을 한번에 초기화 하는 기능을 사용하여 비교적 구문이 간단해지긴 했으나, 바람직하지 않다!
▶ why?

    1) 사실은 이 과정 역시 비합리적으로 생성과 동시에 초기화를 할 수 있음
       - 생성자의 필요성

    2) 판매가격 등 변경이 되면 안되는 중요한 변수의 경우 값을 final 로 상수화 시켜야할 필요성이 있음
      - final이 붙은 변수는 상수가 되었기 때문에 인스턴스 생성 하여 값을 초기화시킬 수 없음.
        (문번 10번, 19번 참조)

→ 생성자를 사용하는 법!

※ 생성자의 이름은 항상 예외없이 클래스의 이름과 동일해야 하며
   필요할 경우 인수를 받아들이는 것도 가능하고,
   같은 이름의 메소드를 정의하는 중복정의(오버로딩)가 가능하지만,
   리턴값(반환값)은 가질 수 없다.

// ※ 생성자는 다른 일반 메소드처럼 호출될 수 없고,
//   『new』연산자를 이용하여 객체를 생성하기 위해 호출되며,
//   각 클래스의 인스턴스를 생성한 후에
//   생성된 객체의 멤버를 초기화 시키는 작업을 수행하게 된다.

○ 생성자(Constructor)의 역할
1. 인스턴스 생성 → 메모리 할당
2. 초기화

○ 생성자(Constructor)의 특징
1. 생성자는 메소드이지만, 일반적인 메소드처럼 호출될 수 없으며, 반환 자료형을 가질 수 없다.
     (『void』조차 가질 수 없으며, 값을 반환할 수도 없다.)
    +
     생성자가 반환자료형을 가지지 않는 특성으로 아무 것도 붙이지 않는 문법을 선점해갔기 때문에, 
     메소드에서 값을 반환하지 않을 때 붙이는 자료형을  void(공허의) 를 붙이게 됨.

2. 생성자는 클래스와 동일한 이름을 가져야 한다.
    (대소문자 명확히 구분 → 클래스의 명명법과 같은 규칙 적용)

3. 생성자는 객체를 생성하는 과정에서 『new 생성자();』의 형태로 호출된다.
    (인스턴스 생성 시 단 한 번만 호출) → final 변수(상수화된 변수) 초기화 가능

  • Test074
    - 생성자 (Constructor) 관찰
더보기
/*=============================================
           ■■■ 클래스와 인스턴스 ■■■
            - 생성자 (Constructor)
==============================================*/

class NumberTest
{
	int num;

	// ※ 클래스에... 사용자 정의 생성자를 정의하지 않았다면...
	//	  컴파일 과정에서 디폴트(default) 생성자가 자동으로 삽입된다.
	//	  즉, 클래스에... 사용자 정의 생성자가 정의되어 있다면...
	//	  컴파일 과정에서 디폴트(default) 생성자는 자동으로 삽입되지 않는다.

	// 디폴트(default) 생성자
	// NumberTest()
	//{
		// 텅 비어있는 상태
	//}

	// ※ 사용자 정의 생성자			// 사용자가 직접 정의하고 사용하게 되는 생성자
	NumberTest()
	{
    	// 아래와 같이 내용을 채울 수도 있고, 비워서 만들어두어도 사용자 정의 생성자이다.
		num = 10;
		System.out.println("사용자 정의 생성자 호출~");
	}

	int getNum()
	{
		return num;
	}
}

public class Test074
{
	public static void main(String[] args)
	{
		// NumberTest 클래스 기반의 인스턴스 생성
		NumberTest nt1 = new NumberTest();
		//--==>> 사용자 정의 생성자 호출~
		//-- 인스턴스가 생성되는 시점에서
		//	 이와 동시에 선택의 여지 없이 생성자 호출이 이루어진다.
		//                              ----------- NumberTest();

		/*
        // 사용자 정의 생성자 주석처리 후 확인
		int result = nt1.getNum();
		//			-------------- 생성자를 따로 메소드 정의하지 않았는데 에러가 안남!
		//						   다른 클래스에서 이미 정의 되어있기 때문에 실행 됨 - default 생성자 때문!
		System.out.println("result : " + result);
		*/

		//nt1.action();
		//--==>> 에러 발생(컴파일 에러)
		//		 can not find symbol		// NumberTest 클래스에 action이라는 메소드 존재X

		//nt1.NumberTest();
		//--==>> 에러 발생(컴파일 에러)
		//		 can not find symbol		// 생성자는 인스턴스 생성시 단 한 번만 호출 되기 때문!

		//int num1 = nt1.getNum();
		//System.out.println(num1);
		System.out.println(nt1.getNum());	// 상기 두 문장을 하나로 만들 수 있음
		//--==>> 10

		nt1.num = 200;
		System.out.println(nt1.getNum());
		//--==>> 200

		NumberTest nt2 = new NumberTest();
		//--==>> 사용자 정의 생성자 호출~

		System.out.println(nt2.num);
		//--==>> 10
	}
}

// 실행 결과

/*
사용자 정의 생성자 호출~
10
200
사용자 정의 생성자 호출~
10
계속하려면 아무 키나 누르십시오 . . .
*/

  • Test075
    - 생성자 (Constructor) 관찰
더보기
/*=============================================
           ■■■ 클래스와 인스턴스 ■■■
            - 생성자 (Constructor)
==============================================*/
class NumberTest2
{
	int num;
	
	// default 생성자
	/*
	NumberTest2()
 	{
 	}
	*/
	// ※ 사용자 정의 생성자가 정의되어 있으므로
	//	  default 생성자가 자동으로 삽입되지 않음~

	// 생성자 → 사용자 정의 생성자	
	NumberTest2(int n)
	{
		num = n;
		System.out.println("생성자 호출 시 매개변수 전달 : " + n);	
	}

	int getNum()
	{
		return num;
	}
}

public class Test075
{
	public static void main(String[] args)
	{
		// NumberTest2 클래스 기반의 인스턴스 생성
		//NumberTest2 nt1 = new NumberTest2();
		//--==>> 에러 발생 (컴파일 에러)
		//--NumberTest2 클래스에는
		//	사용자 정의 생성자가 존재하고 있는 상황이기 때문에
		//	『default 생성자』가 자동으로 삽입되지 않으며
		//	사용자가 정의한 생성자는 매개변수를 갖는 형태이기 때문에
		//	위와 같이 매개변수 없는 생성자를 호출하는 형태의 구문은
		//	문제를 발생시키게 된다.

		NumberTest2 nt1 = new NumberTest2(10);
		//--==>> 생성자 호출 시 매개변수 전달 : 10

		System.out.println("메소드 반환 값 : " + nt1.getNum());
		//--==>> 메소드 반환 값 : 10
		System.out.println("nt1.num		   : " + nt1.num);
		//--==>> nt1.num            : 10

		NumberTest2 nt2 = new NumberTest2(3654);
		//--==>> 생성자 호출 시 매개변수 전달 : 3654

		System.out.println("메소드 반환 값 : " + nt2.getNum());
		System.out.println("nt2.num		   : " + nt2.num);
		//--==>> 메소드 반환 값 : 3654
		//       nt2.num            : 3654
	}
}

  • Test076
    - 생성자 (Constructor)
    - this 키워드
더보기
/*=============================================
           ■■■ 클래스와 인스턴스 ■■■
            - 생성자 (Constructor)
==============================================*/

public class Test076
{
	int x;

	// 생성자 → 사용자 정의 생성자  → 매개변수 없는 생성자
	Test076()
	{	
		// ※ 생성자 내부에서 다른 생성자 호출하는 것은 가능하다. 다른 메소드들 처럼...
		//	  하지만, 생성자 내부에서 가장 먼저 실행되어야 한다. check!
		this(100);

		x = 10;
		// this.x = 10;

		System.out.println("인자 없는 생성자");

		System.out.println("Test076이 갖고있는 x : " + x);

		//Test076(100);
		//this(100);
		//--==>> 에러발생(컴파일 에러)
		//		 call to this must be first statement in constructor
	}

	// 생성자 → 사용자 정의 생성자  → 정수형 매개변수를 넘겨받는 생성자
	Test076(int x)
	{
		//x = num;
		//x = x;												//-- 둘 다 지역변수 x 

		// 『this』 키워드~!!!
		this.x = x;

		System.out.println("인자가 하나인 생성자");
		System.out.println("Test076이 갖고있는 x : " + this.x);
	}

	public static void main(String[] args)
	{
		// Test076 클래스 기반의 인스턴스 생성
		Test076 ob1 = new Test076();
		//--==>> 인자 없는 생성자
		//--==>> 인자가 하나인 생성자
		//       Test076이 갖고있는 x : 100
		//       인자 없는 생성자
		//       Test076이 갖고있는 x : 10

		System.out.println();

		// Test076 클래스 기반의 인스턴스 생성
		Test076 ob2 = new Test076(100);
		//--==>> 인자가 하나인 생성자
		//       Test076이 갖고있는 x : 100

		System.out.println();
		System.out.println("main 에서 ob1.x : " +ob1.x);
		System.out.println("main 에서 ob2.x : " +ob2.x);
		//--==>> main 에서 ob1.x : 10
		//	     main 에서 ob2.x : 100

	}
}

// 실행결과 
/*
인자가 하나인 생성자
Test076이 갖고있는 x : 100
인자 없는 생성자
Test076이 갖고있는 x : 10

인자가 하나인 생성자
Test076이 갖고있는 x : 100

main 에서 ob1.x : 10
main 에서 ob2.x : 100
계속하려면 아무 키나 누르십시오 . . .
*/
※ this 와 this( )
 1) this는 객체 자신을 가리키는 레퍼런스 변수로, 자신의 객체에 접근할 때 사용된다. 
    - 주로 멤버변수와 매개변수의 이름이 동일할 때, 이를 구분하기 위해 사용 
 2) this( )는 같은 클래스에서 생성자가 다른 생성자를 호출할 때 사용된다.
    - 주로 코드의 중복을 줄일 목적으로 사용됨
    - this( )는 생성자 코드에서만 사용할 수 있음
    - this( )는 생성자 코드안에서 사용될 때 가장 먼저 실행되어야 하므로 가장 윗줄에 위치한다.

* 클래스명.메소드명(또는 매개변수) 는 다른 문법에서 선점하였기 때문에 사용할 수 없다.

  • Test077
    - 생성자 (Constructor)
    - 오버 로딩
더보기
/*=============================================
           ■■■ 클래스와 인스턴스 ■■■
            - 생성자 (Constructor)
==============================================*/

public class Test077
{
	int val1;
	double val2;

	Test077()
	{
		val1=0;
		val2=0;
		System.out.println("매개변수 없는 생성자...");
	}
	
	Test077(int x)
	{
		val1=x;
		val2=0;
		System.out.println("int형 데이터를 매개변수로 받는 생성자...");
	}
	
	Test077(double y)
	{
		val1=0;
		val2=y;
		System.out.println("double형 데이터를 매개변수로 받는 생성자...");
	}
	
	Test077(int x, double y)
	{
		val1=x;
		val2=y;
		System.out.println("int형 변수와 double형 변수를 매개변수로 받는 생성자...");
	}


	public static void main(String[] args)
	{
		Test077 ob1 = new Test077();
		//--==>> 매개변수 없는 생성자...

		System.out.println(ob1.val1 + "," + ob1.val2);
		//--==>> 0,0.0

		Test077 ob2 = new Test077(4);
		//--==>> int형 데이터를 매개변수로 받는 생성자...
		System.out.println(ob2.val1 + "," + ob2.val2);
		//--==>> 4,0.0

		Test077 ob3 = new Test077(7.0);
		//--==>> double형 데이터를 매개변수로 받는 생성자...
		System.out.println(ob3.val1 + "," + ob3.val2);
		//--==>> 0,7.0

		Test077 ob4 = new Test077(4, 7.0);
		//--==>> int형 변수와 double형 변수를 매개변수로 받는 생성자...
		System.out.println(ob4.val1 + "," + ob4.val2);
		//--==>> 4,7.0
	}
}
// 실행 결과

/*
매개변수 없는 생성자...
0,0.0
int형 데이터를 매개변수로 받는 생성자...
4,0.0
double형 데이터를 매개변수로 받는 생성자...
0,7.0
int형 변수와 double형 변수를 매개변수로 받는 생성자...
4,7.0
계속하려면 아무 키나 누르십시오 . . .
*/
※ 오버로딩(또는 중복적용) (간략한 개념만)
- 클래스 내부에서 메소드는 "식별자" 역할 → 동일 클래스 내에 중복되는 이름은 없어야함
  그러나 메소드에 넘겨주는 매개변수의 개수나 매개변수의 타입이 다르면 동일한 이름을 사용할 수 있다.

- 허용하는 이유는 동일한 기능을 가진 메소드를 만드는데 매번 다른 이름을 만들려면 너무 힘들기 때문
  허용하지 않는다면 아래와 같이 선긋는 메소드를 만들때 번호를 붙여 1번은 점선 2번은 두줄 등.. 생성해야
  하고 이 모든 기능의 번호 외워 사용하기가 힘들기 때문.
  ex) 선긋기( ) { System.out.println("----------------");}
        선긋기2( ) { System.out.println("===========");}
  이렇게 하는 대신에
 ex) 선긋기() {System.out.println("----------------"); }
       선긋기(int n) {
            for (int i=0; i<n ; i++ )
                  System.out.println("----------------"); }
   이런 식으로 사용할 수 있게 해주는 것.

  • Test078
    - 생성자 (Constructor)와 초기화 블럭(Initialized Block)
※ 초기화 블럭 (Initialized Block)
 - 선언과 동시에 초기화 하지 않았을 때, 
   즉, 전역변수를 선언만 해두었을때 (-전역변수는 선언만 할 경우 자바에서 자동으로 초기화 하는 값이 담김)
   다시 다른 값으로 대입해서 전역변수의 값을 덮어쓰려고 할 때 사용하는 것.
- 대입연산만 있으면 오류가 나기 때문에 블레이스 안에 넣어둔 것 뿐.

- 초기화 블럭보다 생성자가 우선순위가 높다 
  초기화 블럭을 생성자 위에 놓든, 아래에 놓든 초기화 블럭이 먼저 실행 된 후 생성자가 실행됨
  → 초기화 블럭이 어디에 위치하든 먼저 생성 된 후 생성자가 생성되면,
      초기화 블럭이 대입해둔 값이 생성자의 값에 의해서 덮어씌워지면서 사라지게 됨
  → 초기화 블럭이 일한게 의미가 없음
  → 즉, 생성자가 더 중요함!
더보기
/*=====================================================
               ■■■ 클래스와 인스턴스 ■■■
- 생성자 (Constructor)와 초기화 블럭(Initialized Block)
======================================================*/

public class Test078
{
	// 수행할 수 없음
	/*
	int n;			// 선언
	int m;			// 선언
	n = 100;		// 대입 연산 	// 선언은 문제가 없으나 대입연산은 불가능 함.
	m = 200;		// 대입 연산	// 초기화 개념 check!
	*/

	// 수행 가능
	/*
	int n = 100;	// 선언과 동시에 초기화
	int m = 200;	// 선언과 동시에 초기화
	*/

	int n;			
	int m;			

	// 생성자 (Constructor)
	Test078()
	{
		n = 100;
		m = 200;
		System.out.println("생성자 실행...");
	}	
	
	// 초기화 블럭(Initialized Block)	// 생성자보다 하위에 있음.
	{
		n = 10;		
		m = 20;
		System.out.println("초기화 블럭 실행...");
	}

	// 생성자(Constructor)
	Test078(int n, int m)
	{
		this.n = n;
		this.m = m;
		System.out.println("매개변수 있는 생성자 실행...");
	}



	// 멤버 출력 메소드 정의
	void write()
	{
		System.out.println("n:" + n + ", m:" + m);
	}

	public static void main(String[] args)
	{
		//Test078 인스턴스 생성
		Test078 ob1 = new Test078();
		//--==>> 초기화 블럭 실행...
		//		 생성자 실행...

		ob1.write();
		//--==>> n:10, m:20
		//--==>> n:100, m:200

		// Test078 인스턴스 생성
		Test078 ob2 = new Test078(1234, 2345);
		//--==>> 초기화 블럭 실행...
		//       매개변수 있는 생성자 실행...

		ob2.write();
		//--==>> n:1234, m:2345
	}
}
// 실행결과

/*
초기화 블럭 실행...
생성자 실행...
n:100, m:200
초기화 블럭 실행...
매개변수 있는 생성자 실행...
n:1234, m:2345
계속하려면 아무 키나 누르십시오 . . .

*/

 

// 개인적 감상

더보기

클래스와 인스턴스의 개념을 이해하면서,
BufferedReader 나 Scanner 등 인스턴스 생성한다고 써왔던 부분을 실제로 어떤 방법으로 사용하는지 알게되었다. 
이를 복습하면서도, 중요한 개념임에도 불구하고 정리가 잘 되어있지 않아서 강의를 들으면서도 긴가민가 하면서 쓰던 부분들을 다시 확인할 수 있었다. 

쉬운 듯 쉽지않았던 개념이라, 어떻게하면 조금 더 쉽게 이해할 수 있을까 구체적으로 정리해보면서 강의의 흐름과 인스턴스 사용법을 이해할 수 있었다.

728x90