[JAVA] 클래스와 객체를 붕어빵으로 알아보기🍞
클래스와 객체
김영한의 실전 자바 기본편을 보며 정리해보는 자바의 클래스와 객체 개념
클래스와 객체 개념을 설명할 때 어김없이 나오는 붕어빵과 붕어빵 틀! 오늘은 왜 다들 클래스를 붕어빵 틀에 비유하고, 붕어빵을 객체에 비유하는지 붕어빵을 잔뜩 만들어 보며 자세히 알아보도록 하자.
⚠️ 붕어빵과 붕어빵 틀은 엄밀히 말하면 정확한 비유가 아니다. 이 포스팅에서 그 이유까지 함께 다룰 예정이다.
클래스는 왜 필요할까?
공통된 속성을 가진 개념을 하나로 묶어서 관리하기 위해서
지금부터 우리는 다양한 방법으로 붕어빵을 만들어 볼 것이다. 물론 코드로 된 붕어빵 말이다.
변수를 사용해서 만든 붕어빵
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FishShapedBunMain1 {
public static void main(String[] args) {
String fishShapedBunName1 = "FishShapedBun1";
String ingredients1 = "팥";
String fishShapedBunName2 = "FishShapedBun2";
String ingredients2 = "슈크림";
String fishShapedBunName3 = "FishShapedBun3";
String ingredients3 = "초코";
System.out.println("첫 번째 붕어빵 이름: " + fishShapedBunName1 + ", 재료: " + ingredients1);
System.out.println("두 번째 붕어빵 이름: " + fishShapedBunName2 + ", 재료: " + ingredients2);
System.out.println("세 번째 붕어빵 이름: " + fishShapedBunName3 + ", 재료: " + ingredients3);
}
}
다음과 같이 각각 팥, 슈크림, 초코 붕어빵을 만들어냈다.
이렇게 만드는 방식에는 문제가 있다. 붕어빵이 늘어날 때마다 변수를 추가로 선언해야 하고, 출력하는 코드도 따로 만들어줘야 한다. 이런 문제를 조금 더 개선하기 위해 어떻게 해야 할까?
배열을 사용하면 조금 더 편리하게 사용할 수 있지 않을까?
배열을 사용해서 만든 붕어빵
1
2
3
4
5
6
7
8
9
10
public class FishShapedBunMain2 {
public static void main(String[] args) {
String[] fishShapedBunNames = {"FishShapedName1", "FishShapedName2", "FishShapedName3"};
String[] ingredients = {"팥", "슈크림", "초코"};
System.out.println("첫 번째 붕어빵 이름: " + fishShapedBunNames[0] + ", 재료: " + ingredients[0]);
System.out.println("두 번째 붕어빵 이름: " + fishShapedBunNames[1] + ", 재료: " + ingredients[1]);
System.out.println("세 번째 붕어빵 이름: " + fishShapedBunNames[2] + ", 재료: " + ingredients[2]);
}
}
코드는 훨씬 깔끔해졌지만, 각 붕어빵마다 인덱스를 맞춰주어야 하는 문제점이 존재한다. 붕어빵 제작자가 실수해서 순서를 다르게 만들어 버리면 예상하지 못한 붕어빵이 만들어지는 결과가 초래된다.
이런 문제점들을 해결하기 위해 고안된 방법이 클래스이다.
붕어빵이라는 개념을 하나로 묶고, 각각 붕어빵 별로 붕어빵의 이름과 재료들을 관리한다면 붕어빵을 만들기도, 관리하기도 쉬울 것이다.
클래스를 사용해서 만든 붕어빵
1
2
3
4
public class FishShapedBun {
String name;
String ingredients;
}
위 코드와 같이 붕어빵 클래스를 만들었다. 현재 붕어빵 클래스는 name
과 ingredients
라는 멤버 변수를 가지고 있다.
- 멤버 변수(Member Variable), 필드(Field): 클래스에 소속된 변수
이제 이 붕어빵의 설계도를 가지고 수많은 붕어빵을 찍어 낼 수 있게 되었다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class FishShapedBunMain3 {
public static void main(String[] args) {
FishShapedBun fishShapedBun1 = new FishShapedBun();
fishShapedBun1.name = "붕어빵 1번";
fishShapedBun1.ingredients = "팥 붕어빵";
FishShapedBun fishShapedBun2 = new FishShapedBun();
fishShapedBun2.name = "붕어빵 2번";
fishShapedBun2.ingredients = "슈크림 붕어빵";
FishShapedBun fishShapedBun3 = new FishShapedBun();
fishShapedBun3.name = "붕어빵 3번";
fishShapedBun3.ingredients = "초코 붕어빵";
System.out.println("첫 번째 붕어빵 이름: " + fishShapedBun1.name + ", 재료: " + fishShapedBun1.ingredients);
System.out.println("두 번째 붕어빵 이름: " + fishShapedBun2.name + ", 재료: " + fishShapedBun2.ingredients);
System.out.println("세 번째 붕어빵 이름: " + fishShapedBun3.name + ", 재료: " + fishShapedBun3.ingredients);
}
}
이렇게 붕어빵 설계도 FishShapedBun
을 사용해서 실제 메모리에 만들어진 실체인 fishShapedBun1, fishShapedBun2, fishShapedBun3
을 객체 또는 인스턴스라고 부른다.
객체와 인스턴스의 차이점
💡 객체와 인스턴스는 같은 의미지만, 인스턴스는 주로 객체가 어떤 클래스에 속해 있는지 강조할 때 사용된다.
ex.
rabbit
객체는Animal
클래스의 인스턴스다.
클래스란 객체를 만들기 위한 설계도지만..
즉, 클래스는 객체를 만들기 위한 설계도다. 그러나 쪼오끔 더 이해해야 할 부분이 있다. 이 부분은 스프링 입문을 위한 자바 객체 지향의 원리와 이해 책의 내용을 가볍게 인용했다.
이번 포스팅의 썸네일로 붕어빵과 붕어빵 틀을 그리긴 했지만 엄밀히 말하면 그 비유는 옳지 않다. 붕어빵틀로 붕어빵을 만드는 의사 코드를 보자.
1
붕어빵틀 붕어빵 = new 붕어빵틀();
붕어빵은 붕어빵이지, 붕어빵 틀이 아니다.
즉, 클래스는 무조건 설계도다! 라고 생각하는 것보다는 어떤 대분류를 지정하고 그 대분류 아래의 분류들이 가지고 있어야 할 특성을 지정해주는 것에 가깝다.
예를 들면 사람
이라는 분류를 생각해보자.
사람은 이름을 가지고 있고, 나이를 가지고 있다.
23세 홍길동 씨는 사람인가? 맞다. 47세 아무개 씨는 사람인가? 맞다.
동물
도 마찬가지다. 토끼, 다람쥐, 고양이, 강아지… 모두 동물이고 같은 속성을 가지고 있다. 그럼 다시 한번 생각해보자. 붕어빵틀은 붕어빵일까? 자동차의 설계도는 자동차일까?
1
2
3
4
5
6
7
사람 홍길동 = new 사람();
사람 아무개 = new 사람();
동물 토끼 = new 동물();
동물 고양이 = new 동물();
붕어빵틀 붕어빵 = new 붕어빵틀(); // ??
결론적으로 클래스는 분류, 객체는 실체에 대한 개념이다.
클래스는 각 객체들의 공통된 특징(변수)이나 행동(메서드)을 묶은 분류이고, 객체는 그에 대한 고유한 구현체라는 것. 하나의 분류로 서로 다른 다양한 객체를 만들어 낼 수 있으니 다형성을 구현하기 위해 사용하는 개념이라고 볼 수 있겠다.
객체 사용
다소 모호하게 느껴질 수 있는 클래스와 객체의 개념을 이해했다면 아래 내용은 조금 더 쉬울 것이다. 바로 객체의 사용 방법이다.
1
FishShapedBun fishShapedBun1 = new FishShapedBun();
자바에서는 new
키워드를 통해 메모리 상에 해당 객체를 적재시키고 참조 값을 반환한다.
new FishShpedBun()
코드는 해당 클래스가 담겨 있는 메모리 주소를 반환하고, fishShapedBun1
변수는 그 값을 저장하고 있는 참조 변수가 된다.
클래스를 배열을 이용해 관리할 때도 마찬가지다.
1
FishShapedBun[] fishShapedBuns = new FishShapedBun[2];
new FishShapedBun[2]
코드는 FishShapedBun
이 두 개 담길 수 있는 메모리 공간을 확보하고, 그 메모리 공간에 접근할 수 있는 참조 값을 fishShapedBuns
에 저장하게 된다.
이 참조 값과 .
키워드를 통해서 변수에 들어있는 참조값을 읽은 후 메모리에 존재하는 객체에 접근할 수 있다.
1
2
3
4
// 객체에 값 대입하기
fishShapedBun1.ingredients = "팥";
// 객체의 값 읽기
System.out.println(fishShapedBun1.ingredients); // 팥 출력
❗ 복사되는 방식에 주의
이때 변수에는 인스턴스 자체가 들어있는 것이 아니다. 인스턴스의 위치를 가리키는 참조값이 들어있을 뿐이다. 따라서 대입 시에 인스턴스가 복사되는 것이 아니라 참조값만 복사된다는 것을 잘 기억해두어야 한다.
정리
결론적으로 공통된 속성을 가진 개념을 하나로 묶어서 관리하기 위해서 클래스를 사용하게 되었고, 클래스는 분류, 객체는 실체에 대한 개념이라는 것을 알 수 있었다.