들어가며
클래스에 대한 지나치게
학문적인 접근은
오히려 처음 객체지향 언어를
공부하는 학습자에게 부담이
될 수 있다.
따라서 쉬운 접근을 통해서
클래스에 대한
첫 이해를 돕고자 한다.
클래스(Class) = 데이터(Data) + 메소드(Method)
작성된 프로그램의 코드를
관찰해보면
종류에 상관없이 모든 프로그램은
다음 2가지로 이뤄진다는
사실을 알 수 있다.
- 데이터 프로그램상에서 유지하고 관리해야 할 데이터
- 기능 데이터를 처리하고 조작하는 기능
이 중에서 데이터는 '변수의
선언'을 통해 유지 및 관리가 되고,
또 변수에 저장된 데이터는
'메소드의 호출'을 통해
처리가 된다.
이와 관련해서 다음 예제를
보자.
참고로 이 예제는 은행 계좌를
간단히 표현한 것이다.
public class temp {
static int balance = 0; // 예금 잔액
public static void main(String[] args){
deposit(10000); // 입금 진행
checkMyBalance(); // 잔액 확인
withdraw(3000); // 출금 진행
checkMyBalance(); // 잔액 확인
}
// 입금을 담당하는 메소드
public static int deposit(int amount){
balance += amount;
return balance;
}
// 출금을 담당하는 메소드
public static int withdraw(int amount){
balance -= amount;
return amount;
}
// 예금 조회를 담당하는 메소드
public static int checkMyBalance(){
System.out.println("잔액: " + balance);
return balance;
}
}
/*
잔액: 10000
잔액: 7000
*/
위 예제는 다음 위치에 변수를
선언하였다.
이 변수에 붙어있는 static 선언의
의미는
이후에 별도로 설명을 하니
지금은 신경 쓰지 말자.
public class temp {
static int balance = 0; // 예금 잔액
public static void main(String[] args){ . . .}
. . .
}
그리고 메소드 deposit, withdraw,
checkMyBalance 내에 접근하는
변수 balance는 2행에 선언된
바로 이 변수이다.
이러한 선언 및 접근 관계에
대해서도 이후에 설명하니
지금은 이러한 접근 관계를
단순히 받아들여서
실행 결과와 코드를 연결
짓는 정도만 하자.
위 예제를 제시한 목적은
다음 2가지 내용으로
프로그램이 이뤄진다는 것을
보이는데 있으니 말이다.
- 데이터 변수 balance는 프로그램상에서의 '데이터'이다.
- 기능 메소드 deposit, withdraw, checkMyBalance는 프로그램상에서의 '기능'이다.
더불어 다음 내용의 관찰도
위 예제를 제시한 목적에
포함된다.
변수 balance는 메소드 deposit, withdraw, checkMyBalance와
긴밀히 연관되어 있다.
긴밀히 연관되어(연결되어)
있다는 것은
다음 내용을 뜻한다.
메소드 deposit, withdraw, checkMyBalance는
변수 balance를 위한 메소드이다.
쉽게 말해서 메소드 deposit은
변수 balance와 뗄 수 없는
관계이다.
나머지 두 메소드도 마찬가지다.
그래서 이렇듯 연관 있는 변수와
메소드를 묶기 위해
'클래스'라는 것이 존재한다.
클래스를 이용하면 다음과
같이 변수 balance,
그리고 이와 연관 있는 모든
메소드를 하나로 묶을 수 있다.
class BankAccount {
int balance = 0; // 예금 잔액
// balance와 연관있는 메소드
public int deposit(int amount){
balance += amount;
return balance;
}
// balance와 연관있는 메소드
public int withdraw(int amount){
balance -= amount;
return amount;
}
// balance와 연관있는 메소드
public int checkMyBalance(){
System.out.println("잔액: " + balance);
return balance;
}
}
위 코드를 가리켜 '클래스의
정의'라 한다.
즉 이는 BankAccount 클래스의
정의이다.
그럼 이어서 이러한 클래스의
정의가
어떻게 사용이 되는지 설명하겠다.
클래스의 구성과 인스턴스화
위에서 정의한 클래스
BankAccount의 정의를
큰 틀에서 보면 다음과 같다.
인스턴스 변수와 인스턴스 메소드의 묶음이다.
public class BankAccount {
// 인스턴스 변수
static int balance = 0;
// 인스턴스 메소드
public static int deposit(int amount){ . . . }
public static int withdraw(int amount){ . . . }
public static int checkMyBalance(){ . . . }
}
위와 같이 클래스 내에
위치한 변수와
메소드를 가리켜 각각
다음과 같이 부른다.
- 인스턴스 변수 클래스 내에 선언된 변수
- 인스턴스 메소드 클래스 내에 정의된 메소드
인스턴스 변수는 앞서
chapter 06에서 설명한
'지역변수'가 아니다.
인스턴스 변수가 선언된 위치는
메소드 내부가 아니므로
이 둘은 성격이 다르다.
이러한 인스턴스 변수의
중요한 특징 중 하나는
다음과 같다.
인스턴스 변수는 같은 클래스 내에 위치한
메소드 내에서 접근이 가능하다.
때문에 앞서 정의한 클래스에
다음과 같은 메소드를 정의할
수 있었다.
public class BankAccount {
static int balance = 0;
public static int deposit(int amount){
balance += amount; // 인스턴스 변수 balance의 값 증가
return balance; // 인스턴스 변수 balance의 값 반환
}
. . .
}
필드(Fields)
'인스턴스 변수'는 다음과 같이
불리기도 한다.
- 멤버 변수
- 필드(Fields)
이 중에서 '필드'라는 표현은 생소할
수 있다.
하지만 이것도 '인스턴스 변수'의
다른 표현임을 기억하자.
클래스를 정의하였으니
이를 활용하는 예제를 제시할
차례인데,
그에 앞서 클래스의 정의의
본질을 설명하고자 한다.
클래스의 정의는 틀(Mold)을 구성하는 것과 같다.
클래스의 정의는 붕어빵과
같이
무언가를 찍어내는 '틀(Mold)'에
비유할 수 있다.
붕어빵 틀은 먹을 수 있는 대상은
아니다.
하지만 이 틀이 있으므로 빵을
찍어낼 수 있다.
마찬가지로 클래스가 정의되었다고
해서
그 안에 위치한 변수나 메소드를
사용할 수 있는 것은 아니다.
틀을 이용해서 다음과 같이
'인스턴스'라는 것을
찍어내야 사용이 가능하다.
new BankAccount(); // 클래스 BankAccount의 인스턴스화(Instantiation)
위의 문장을 실행하면
BankAccount에 정의된 변수와
메소드를 담고 있는
'인스턴스'라는 것이 만들어진다.
만들어져서 실제 메모리 공간에
존재하게 된다.
물론 다음과 같이 둘, 혹은 그
이상도 만들 수 있다.
new BankAccount(); // BankAccount 인스턴스1
new BankAccount(); // BankAccount 인스턴스2
그런데 이렇게 메모리상에
인스턴스를 만들기만 해서는
사용할 수가 없다.
만들어진 인스턴스를 참조할
수 있는(가리키고 있을 수 있는)
무언가가 필요하다.
그리고 이 무엇인가를 가리켜
'참조변수(Reference Variable)'
라 한다.
인스턴스의 다른 표현, '객체'
클래스를 정의하고 이를 기반으로
만들어진 '인스턴스'를 가리켜
'객체'라고도 한다.
예를 들어서 '인스턴스의 생성'과
'객체의 생성'은
그 의미가 완전히 동일하다.
본서에서는 인스턴스라는 표현을
선택해서 사용하고 있다.
클래스 BankAccount의 참조변수를
선언하는 방법은 다음과 같다.
선언 방식이 기본 자료형
변수의 선언 방식과 동일하다.
BankAccount myAcnt; // 참조변수 myAcnt의 선언
즉 다음과 같이 참조 변수를
선언하고
이를 통해서 새로 생성되는
인스턴스를(객체를) 가리키게
할 수 있다.
new BankAccount1(); // BankAccount 인스턴스1 선언
new BankAccount2(); // BankAccount 인스턴스2 선언
// 참조 변수 myAnct1이 새로 생성되는 인스턴스를 가리킴
myAcnt1 = new BankAccount1();
// 참조 변수 myAnct2rk 새로 생성되는 인스턴스를 가리킴
myAcnt2 = new BankAccount2();
키워드 new를 통해서 인스턴스를
생성하면
생성된 인스턴스 주솟값이
반환된다.
즉 참조변수에는 생성된
인스턴스의 주솟값이
저장되는 셈이다.
하지만 다음과 같이 표현하고
인식하자.
이것이 보다 일반적인 표현이다.
주솟값은 참조변수에 저장된 값이기 때문에 본서에서는 이 값을 '참조 값'이라 한다.
참조변수는 인스턴스를 참조한다.
참조변수는 인스턴스를 가리킨다.
그리고 다음과 같이 참조변수의
선언과 인스턴스의 생성을
한 문장으로 묶을 수도 있다.
BankAccount myAcnt1 = new BankAccount(); // 참조변수 myAcnt의 선언
BankAccount myAcnt2 = new BankAccount(); // 참조변수 myAcnt의 선언
그리고 참조변수를 통해서
해당 인스턴스의 메소드를
호출하는 방법은 다음과
같다.
인스턴스 변수에 접근하는 방법도 이와 동일하다. 하지만 지금은 접근을 시도하지 말자.
myAcnt1.deposit(1000); // myAcnt1이 참조하는 인스턴스의 deposit 호출
myAcnt2.deposit(2000); // myAcnt2가 참조하는 인스턴스의 deposit 호출
이어서 예제를 하나 제시
하겠다.
이 예제는 지금까지 설명한
내용을 바탕으로 작성되었으니,
이를 통해서 지금까지 설명한
내용을 확인하고 정리하기 바란다.
class BankAccount {
int balance = 0; // 예금 잔액
public int deposit(int amount){
balance += amount;
return balance;
}
public int withdraw(int amount){
balance -= amount;
return amount;
}
public int checkMyBalance(){
System.out.println("잔액: " + balance);
return balance;
}
}
public class temp {
public static void main(String[] args){
// 2개의 인스턴스 생성
BankAccount user1 = new BankAccount();
BankAccount user2 = new BankAccount();
// 각 인스턴스를 대상으로 예금을 진행
user1.deposit(5000);
user2.deposit(3000);
// 각 인스턴스를 대상으로 출금을 진행
user1.withdraw(2000);
user2.withdraw(2000);
// 각 인스턴스를 대상으로 잔액을 조회
user1.checkMyBalance();
user2.checkMyBalance();
}
}
/*
실행 결과
잔액: 3000
잔액: 1000
*/
코드와 실행 결과를 비교해
보면,
참조변수 user1과 user2가
가리키는 인스턴스가
서로 다른 인스턴스임을
알 수 있다.
참조변수(Reference Variable)의 특성
변수는 저장된 값을 바꿀
수 있다.
그래서 이름이 변수이다.
마찬가지로 참조변수도
변수이다.
따라서 참조변수도 다음과
같이
참조하는 인스턴스를 바꿀
수 있다.
BankAccount user1 = new BankAccount();
. . .
user1 = new BankAccount(); // user1이 새 인스턴스를 참조한다.
. . .
다음과 같이 참조변수가
지니고 있는 값을
다른 참조변수에 대입하여
하나의 인스턴스를
둘 이상의 참조변수가
동시에 참조하는 것도
가능하다.
BankAccount ref1 = new BankAccount();
BankAccount ref2 = ref1;
. . .
이 두 가지 상황 중에서
하나의 인스턴스를
2개의 참조변수가 함께
참조하는 상황을
다음 예제를 통해서 확인
해보자.
class BankAccount {
int balance = 0; // 예금 잔액
public int deposit(int amount){
balance += amount;
return balance;
}
public int withdraw(int amount){
balance -= amount;
return amount;
}
public int checkMyBalance(){
System.out.println("잔액: " + balance);
return balance;
}
}
public class temp {
public static void main(String[] args){
BankAccount ref1 = new BankAccount();
BankAccount ref2 = ref1; // ref1이 참조하는 대상을 ref2도 참조
ref1.deposit(3000);
ref2.deposit(2000);
ref1.withdraw(400);
ref2.withdraw(300);
ref1.checkMyBalance();
ref2.checkMyBalance();
}
}
/*
실행 결과
잔액: 4300
잔액: 4300
*/
위의 코드와 그 실행 결과는
2개의 참조변수
ref1과 ref2가 하나의 인스턴스를
참조하고 있음을 확인시켜준다.
참조변수(Reference Variable)의 매개변수 선언
메소드를 호출할 때 값을
전달할 수 있고,
이 값은 매개변수에 저장된다는
사실을 전에 설명하였다.
이와 유사하게 메소드를
호출하면서
인스턴스의 참조 값을
전달하는 것도 가능하다.
이와 관련해서 다음 예제를
보자.
class BankAccount {
int balance = 0; // 예금 잔액
public int deposit(int amount){
balance += amount;
return balance;
}
public int withdraw(int amount){
balance -= amount;
return amount;
}
public int checkMyBalance(){
System.out.println("잔액: " + balance);
return balance;
}
}
public class temp {
public static void main(String[] args){
BankAccount ref = new BankAccount();
ref.deposit(3000);
ref.withdraw(300);
check(ref); // '참조 값'의 전달
}
public static void check(BankAccount acc){
acc.checkMyBalance();
}
}
/*
실행 결과
잔액: 2700
*/
위 예제의 다음 메소드를
보자.
public static void check(BankAccount acc){
acc.checkMyBalance();
}
위 메소드의 매개변수로
BankAccount의 참조변수가
선언되었다.
즉 이 메소드는 인자로
인스턴스의 참조 값을
전달받는다.
따라서 메소드 내에서는
전달된 참조 값의
인스턴스를 대상으로
메소드를 호출할 수 있다.
참조변수에 null 대입
때로는 참조변수가 참조하는
(가리키는) 인스턴스와의
관계를 끊고
아무런 인스턴스도 참조하지
않도록 할 필요가 있다.
그리고 이때에는 다음과 같이
참조 변수에 null을 대입하면
된다.
BankAccount ref = new BankAccount();
. . .
ref = null; // ref가 참조하는 인스턴스와의 관계를 끊음
그리고 참조변수의 null
저장 유무를
다음과 같이 확인할 수
있다.
BankAccount ref = null;
. . .
if (ref == null) // ref가 참조하는 인스턴스가 없다면
. . .
생성되는 클래스 파일의 수
확장자가 class인 클래스 파일은
정의되는 클래스의 수 만큼
생성된다.
즉 '정의된 클래스의 수'와 '생성
되는 클래스의 파일 수'는 동일하다.
예를 들어서 앞서 소개한 예제를
컴파일 하면
2개의 클래스 파일이 생성된다.
참고 및 출처
|