[java] 7. 클래스와 객체


객체지향언어



자바는 객체지향언어라고 하였는데요~

이제부터는 객체지향언어에 대해 알아보겠습니다.

객체지향이론의 기본 개념은 실제 세계는 사물(객체)로 이루어져 있으며,

발생하는 모든 사건들은 사물간의 상호작용이라는 것입니다.

실제 사물의 속성과 기능을 분석한 다음, 데이터(변수)와 함수로 정의함으로써

실제 세계를 컴퓨터 속에 옮겨 놓은 것과 같은 가상 세계를 구현하고 이 가상세계에서 모의실험을 함으로써

많은 시간과 비용을 절약할 수 있다는 것입니다.

즉, 실제세계의 객체들과 객체들의 상호작용을 컴퓨터 세계에서 그대로 표현하는 것이 객체지향언어입니다.

객체지향언어는 상속, 캡슐화, 추상화, 다형성이라는 특성이 있는데요~

차차 알아보겠습니다.

객체지향언어의 장점은 다음과 같습니다.


1. 코드의 재사용이 높다.

새로운 코드를 작성할 때 기존의 코드를 이용하여 쉽게 작성할 수 있다.


2. 코드의 관리가 용이하다.

코드간의 관계를 이용해서 적은 노력으로 쉽게 코드를 변경할 수 있다.


3. 신뢰성이 높은 프로그래밍을 가능하게 한다.

제어자와 메서드를 이용해서 데이터를 보호하고 올바른 값을 유지하도록 하며,

코드의 중복을 제거하여 코드의 불일치로 인한 오동작을 방지할 수 있다.



클래스와 객체



자바에서 가장 중요한 개념은 바로 클래스와 객체입니다~!


클래스의 정의 : 클래스란 객체를 정의해 놓은 것이다.

클래스의 용도 : 클래스는 객체를 생성하는데 사용된다.




객체의 정의: 실제로 존재하는 것, 사물 또는 개념

객체의 용도: 객체가 가지고 있는 기능과 속성에 따라 다름


유형의 객체: 책상, 의자, 자동차, TV

무형의 객체: 수학공식, 프로그램 에러와 같은 논리나 개념



처음 배우시는 사람들은 잘 와닿지가 않을텐데요~

한 번 예를 들어보겠습니다.

우리가 먹는 붕어빵은 붕어빵 틀이라는 곳에서 무한히 만들어집니다.

이 때 붕어빵을 객체라고 한다면, 붕어빵 틀을 클래스라고 할 수 있습니다.

실제로 우리가 먹는 것은 붕어빵이지 붕어빵 틀이 아닙니다.

즉 클래스 또한, 객체를 생성하는데 사용될 뿐 객체 그 자체가 아닙니다.

프로그래밍에서는 먼저 클래스를 작성하여 큰 틀을 정의한다음,

그 후에 클래스로부터 객체를 생성하여 메모리에 할당합니다.

따라서, 클래스로 인해 객체가 생성되는 만큼 클래스를 잘 정의하는 것이 중요합니다!



객체와 인스턴스



클래스로부터 객체를 만드는 과정을 클래스의 인스턴스화라고 하며,

어떤 클래스로부터 만들어진 객체를 그 클래스의 인스턴스라고 합니다.

예를 들면 붕어빵 틀에서 만들어진 붕어빵을 바로 붕어빵 틀 클래스의 인스턴스라고 합니다.

어떻게 보면 객체와 인스턴스는 같은 의미이지만, 객체는 모든 인스턴스를 대표하는 포괄적인 의미를 갖고 있으며,

인스턴스는 어떤 클래스로부터 만들어진 것인지를 강조하는 보다 구체적인 의미를 갖고 있습니다.

예를 들어 ‘책상은 인스턴스이다’라고 하는 것보다는 ‘책상은 객체다’라고 하는 것이 자연스럽고,

‘책상은 책상 클래스의 객체다’라고 하는 것보다는

책상은 책상 클래스의 인스턴스다.’라고 하는 것이 자연스럽습니다.

문맥에 따라 둘의 의미를 구별하는 것이 좋습니다!



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



객체는 속성과 기능, 두 종류의 구성요소로 이루어져 있으며,

일반적으로 객체는 다수의 속성과 다수의 기능을 갖습니다.


속성: 멤버변수, 특성, 필드, 상태

기능: 메서드, 함수, 행위



예를 들어보겠습니다.

TV라는 객체가 있다고 하면.


<TV객체>

멤버변수(속성): 크기, 길이, 높이, 색상, 볼륨, 채널 등

메서드(기능): 켜기, 끄기, 볼륨 높이기, 볼륨 낮추기, 채널 변경하기 등



클래스 정의



클래스와 객체에 대한 개념을 어느 정도 배웠으니 직접 자바를 이용하여 구현해보겠습니다.

아까 들었던 TV 를 예로 들겠습니다.

TV 클래스는 다음과 같이 정의합니다.


class Tv{
	//멤버변수(속성)
	String color; //색깔
	boolean power; //전원상태
	int channel; //채널
	
	//메서드(기능)
	void power() {power=!power;} //전원을 키고 끄는 기능
	void channelUp(){channel++;} //채널 증가
	void channelDown(){channel--;} //채널 감소
}



C에서 배운 구조체를 정의하는 것과 비슷합니다. (stucture에서 Class로 바뀜)

우리가 실세계에서 본 사물(객체)를 표현하고 싶은대로 프로그래밍에서 클래스로 정의하면 됩니다.

이 때 멤버 변수를 정의할 때 자료형을 속성의 알맞은 것으로 선택해야 합니다.

인스턴스의 생성과 사용



위에서 Tv클래스를 정의했다고 해서 메모리에 Tv 객체가 생성된 것이 아닙니다.

아까도 말했지만, 클래스는 객체를 만드는 틀일 뿐입니다.

클래스를 인스턴스화 시켜야 비로소 메모리에 객체가 생성됩니다.

클래스로부터 인스턴스를 생성하는 방법은 다음과 같습니다.


클래스명 변수명; //클래스의 객체를 참조하기 위한 참조변수 선언(메모리 할당 전)

변수명=new 클래스명(); //객체 생성(메모리 할당)


ex) Tv t; //Tv클래스 타입의 참조변수 t를 선언

t=new Tv(); //Tv 인스턴스를 생성한 후, 생성된 Tv 인스턴스의 주소를 t에 저장



다음과 같이 한꺼번에 할 수도 있습니다.


Tv t=new Tv();



객체를 생성할 때 new라는 연산자를 사용하는데,

어디서 많이 보시지 않았나요?


Scanner sc=new Scanner(System.in);



바로 화면으로부터 입력을 받을 때 사용하는 Scanner 클래스 입니다.

우리는 이미 클래스로부터 객체를 생성하여 사용하고 있었습니다~!

다음은 Tv클래스를 정의하여 객체를 생성한 예입니다.


image



위에서 보듯이 인스턴스의 멤버변수에 접근할 때는 접근 연산자 .을 사용합니다.

또, Tv클래스의 참조변수 t에는 Tv 인스턴스의 주소가 저장됩니다.

다시 말해서, Tv 인스턴스가 메모리 어딘가에 생성되고, 그 저장된 메모리 주소를 t가 가리키고 있습니다.


인스턴스는 참조변수를 통해서만 다룰 수 있으며, 참조변수의 타입은 인스턴스의 타입과 일치해야한다.



다음은 Tv클래스로부터 두 개의 인스턴스를 생성한 예입니다.


image



주의할 것은 참조형 변수는 값이 아닌 주소가 저장된 것이기에 대입연산을 실행하면 같은 인스턴스를 가리킵니다.



객체 배열



많은 수의 객체를 다뤄야할 때, 배열로 다루면 편리하겠죠?

참조형 변수도 배열 선언이 가능합니다!


Tv tv1,tv2,tv3; //관리 불편




Tv[] tvArr=new Tv[3]; //배열 선언



단, 주의할 것이 위의 코드를 실행했다고 해서 인스턴스가 생성된 것이 아닙니다.

다음과 같이 배열의 각 요소에 객채를 생성해서 저장해야합니다.


Tv[] tvArr=new Tv[3]; //참조변수 배열을 생성

//객체를 생성해서 배열의 각 요소에 저장
tvArr[0]=new Tv();
tvArr[1]=new Tv();
tvArr[2]=new Tv();



다뤄야할 객체의 수가 많을 때는 for문을 사용하면 됩니다.


Tv[] tvArr=new Tv[100];

for(int i=0;i<tvArr.length;i++){
	tvArr[i]=new Tv();
}



다음은 객체배열을 사용한 예제입니다.


image



변수



자바에서 사용하는 변수는 클래스 변수, 인스턴스 변수, 지역변수 모두 세 종류가 있습니다.

변수의 종류를 결정짓는 중요한 요소는 변수의 선언된 위치이므로 변수의 종류를 파악하기 위해서는

변수가 어느 영역에 선언되었는지를 확인하는 것이 중요합니다.

멤버변수를 제외한 나머지 변수들은 모두 지역변수이며, 멤버변수 중 static이 붙은 것은 클래스 변수,

붙지 않은 것은 인스턴스변수입니다.


class Variables
{
	int iv; //인스턴스 변수
	static int cv; //클래스 변수(static 변수, 공유변수)
	
	void method()
	{
		int lv=0; //지역 변수
	}
}




<인스턴스 변수>

클래스 영역에 선언되며, 클래스의 인스턴스를 생성할 때 만들어진다.


그렇기 때문에 인스턴스 변수의 값을 읽어 오거나 저장하기 위해서는 먼저 인스턴스를 생성해야한다.


인스턴스는 독립적인 저장공간을 가지므로 서로 다른 값을 가질 수 있다.


인스턴스마다 고유한 상태를 유지해야하는 속성의 경우, 인스턴스변수로 선언한다.




<클래스 변수>


클래스 변수를 선언하는 방법은 인스턴스변수 앞에 static을 붙이기만 하면 된다.


인스턴스마다 독립적인 저장공간을 갖는 인스턴스변수와는 달리,


클래스변수는 모든 인스턴스가 공통된 저장공간을 공유하게 된다.


한 클래스의 모든 인스턴스들이 공통적인 값을 유지해야하는 속성의 경우,


클래스 변수로 선언해야한다.


클래스 변수는 인스턴스변수와 달리 인스턴스를 생성하지 않고도 언제라도 바로 사용할 수 있으며 '클래스이름.클래스변수' 형식으로 사용된다.




<지역변수>


매서드 내에 선언되어 메서드 내에서만 사용 가능하며, 메서드가 종료되면 소멸되어 사용할 수 없게 된다.


for문 또는 while 문의 블럭 내에 선언된 지역변수는, 지역변수가 선언된 블록 {} 내에서만 사용 가능하며,


블럭 {}을 벗어나면 소멸되어 사용할 수 없게 된다.



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



클래스 변수와 인스턴스 변수의 차이점을 이해하기 위하여 예제를 들겠습니다.

Card 클래스를 정의할 것인데, Card 전체의 폭과 높이는 일정하므로 클래스 변수로 선언하고,

무늬와 숫자는 인스턴스마다 다르므로 인스턴스 변수로 선언하겠습니다.


image

image



출력 결과입니다.


image



클래스 변수와 인스턴스 변수를 다시 정리하면,


인스턴스변수는 인스턴스가 생성될 때 마다 생성되므로 인스턴스마다 각기 다른 값을 유지할 수 있지만,


클래스 변수는 모든 인스턴스가 하나의 저장공간을 공유하므로, 항상 공통된 값을 갖는다.



메서드



메서드는 특정 작업을 수행하는 일련의 문장들을 하나로 묶은 것입니다.

C에서 배운 함수와 동일하지만 클래스 내에 정의되어 있다는 점에서 다릅니다.

메서드는 어떤 값을 입력하면 이 값으로 작업을 수행해서 결과를 반환하는데,

예를 들어 제곱근을 구하는 메서드 Math.sqrt()는 4.0을 입력하면 2.0을 결과로 반환합니다.

그저 메서드가 작업을 수행하는데 필요한 값만 넣고 원하는 결과를 얻으면 될 뿐,

이 메서드가 내부적으로 어떤 과정을 거쳐 결과를 만들어내는지 몰라도 됩니다.

즉, 메서드에 넣을 값(입력)과 반환하는 결과(출력)만 알면 되는 것입니다.

그래서 메서드를 내부가 보이지 않는 블랙박스라고도 합니다.

여태 저희가 사용해온 System 클래스의 println()이나 Scanner 클래스의nextInt() 등이 메서드에 해당합니다.

위에서 말했듯이 메서드는 내부적으로 알 필요 없이 어떻게 동작하는지 알면 쉽게 사용할 수 있습니다.

그로 인해 높은 재사용성과 중복된 코드를 제거시키는 등의 장점을 가집니다.

물론 메서드를 작성할 때도 남들이 사용할 것을 고려하여 작성해야 합니다.



메서드의 선언과 구현



메서드는 크게 두 부분, 선언부(header)와 구현부(body)로 이루어져 있습니다.

메서드는 다음과 같이 정의합니다.

반환타입 메서드이름 (타입 변수명, 타입 변수명,...)
{
	//메서드 호출시 수행할 코드
}



예를 들어 MathExample이라는 클래스 내에 sum이라는 메서드를 정의하면,


class MathExample{
	int sum(int x,int y){
		return x+y;
	}
}



항상 메서드에서 반환 값이 있을 때 메서드의 반환타입과 return으로 반환하는 값의 타입이 일치해야합니다.

그리고 위의 코드에서 메서드로 입력되는 값 x,y를 매개변수라고 하며 이는 지역변수입니다.

즉, 값을 반환하고 나면 지역변수들은 소멸하게 됩니다.

main함수에서 MathExample의 sum을 호출하고 싶다면 다음과 같이합니다.


image



return 문



return문은 현재 실행중인 메서드를 종료하고 호출한 메서드로 되돌아갑니다.

원래는 반환 값의 유무에 관계없이 모든 메서드에는 적어도 하나의 return문이 있어야하지만

반환형이 void인 경우에는 컴파일러가 자동적으로 return;을 추가해주기에 생략 가능합니다.

return 문에는 다음과 같이 변수 뿐만아니라 수식이 와도 됩니다.


return result; //변수 반환 가능

return x+y; //수식 가능

return x>=0?x:-x; //조건 연산자 가능



매개변수의 유효성 검사



메서드의 구현부 {}를 작성할 때, 제일 먼저 해야하는 일은 매개변수의 값이 적절한 것인지 확인하는 것입니다.

호출하는 쪽에서 알아서 적절한 값을 넘겨주겠지라는 생각을 절대로 가져서는 안 됩니다.

나눗셈을 하는 메서드인 divide가 있다고 합시다.


float divide(int x,int y){
	return x/(float)y;
}

위의 코드의 문제점은 다음과 같이 0으로 나누는 것을 생각 안한것입니다.


divide(5,0); //오류



따라서 메서드를 정의할 때 매개변수의 값이 적절한지 꼭 확인해야합니다.


float divide(int x,int y){
	if(y==0){
		System.out.println("0으로 나눌 수 없습니다.");
		return;
	}
	
	return x/(float)y;
}