기본형 매개변수와 참조형 매개변수
자바에서는 메소드를 호출할 때 매개변수로 지정한 값을 메소드의 매개변수에 복사해서 넘겨준다.
매개변수의 타입이 기본형일 때는 기본형 값이 복사되겠지만,
참조형이면 인스턴스의 주소가 복사된다.
기본형 매개변수 : 변수의 값을 읽기만 할 수 있다.
참조형 매개변수 : 변수의 값을 읽고 변경할 수 있다.
다음 실습프로그램을 보면서 자세히 살펴보자.
change메소드에서 main메소드로부터 넘겨받은 d.x의 값을 1000으로 변경했는데도
main메소드에서는 d.x의 값이 그대로이다. 왜냐하면 다음과 같이 실행되었기 때문이다.
- change메소드가 호출되면서 d.x가 change메소드의 매개변수 x에 복사됨.
- change메소드에서 x의 값을 1000으로 변경
- change메소드가 종료되면서 매개변수 x는 스택에서 제거됨
즉, d.x의 값이 변경된 것이 아니라, change메소드의 매개변수 x의 값이 변경된 것이다.
원본이 아닌 복사본이 변경된 것이라 원본에는 아무런 영향을 미치지 못한다.
이처럼 기본형 매개변수는 변수에 저장된 값만 읽을수만 있을뿐 변경할 수는 없다.
다음 실습프로그램을 보면 참조형 매개변수와 기본형 매개변수의 차이를 잘 알 수 있을 것이다.
이전 예제와 달리 change메소드를 호출한 후에 d.x의 값이 변경되었다.
change메소드의 매개변수가 참조형이라서 값이 아니라 '값이 저장된 주소'를 change메소드에게 넘겨주었기 때문에
값을 읽어오는 것 뿐만 아니라 변경하는 것도 가능하다.
- change메소드가 호출되면서 참조변수 d의 값(주소)이 매개변수 d에 복사됨.
- change메소드에서 매개변수 d로 x의 값을 1000으로 변경
- change메소드가 종료되면서 매개변수 d는 스택에서 제거됨
이 두 예제를 잘 비교해서 차이를 완전히 이해해야 한다.
참조형 반환타입
매개변수뿐만 아니라 반환타입도 참조형이 될 수 있다.
반환타입이 참조형이라는 것은 반환하는 값의 타입이 참조형이라는 얘긴데,
모든 참조형 타입의 값은 '객체의 주소'이므로 그저 정수값이 반환되는 것일 뿐 특별한 것이 없다.
다음 실습프로그램을 살펴보자.
copy메소드는 새로운 객체를 생성한 다음에, 매개변수로 넘겨받은 객체에 저장된 값을 복사해서 반환한다.
반환하는 값이 Data객체의 주소이므로 반환타입이 'Data'인 것이다.
다시 정리하자면 다음과 같은 단계를 거친다.
- copy메소드를 호출하면서 참조변수 d의 값이 매개변수 d에 복사된다.
- 새로운 객체를 생성한 다음, d.x에 저장된 값을 tmp.x에 복사한다.
- copy메소드가 종료되면서 반환한 tmp의 값은 참조변수 d2에 저장된다.
- copy메소드가 종료되어 tmp가 사라졌지만, d2로 새로운 객체를 다룰 수 있다.
반환타입이 '참조형'이라는 것은 메소드가 '객체의 주소'를 반환한다는 것을 의미한다.
재귀호출
메소드의 내부에서 메소드 자신을 다시 호출하는 것을 '재귀호출'이라 하고,
재귀호출을 하는 메소드를 '재귀메소드'라 한다.
예를들어,
void method() {
method() ;
}
이러한 문장이 있다고 가정해보자.
이렇게 메소드 자신을 다시 호출하는 것이 재귀호출이다.
하지만 무한히 자기 자신을 호출하기 때문에 무한반복에 빠지게된다.
무한반복문이 조건문과 함께 사용되어야 하는 것처럼, 재귀호출도 조건문이 필수적으로 따라다닌다.
재귀호출은 반복문과 유사한 점이 많으며, 대부분의 재귀호출은 반복문으로 작성하는 것이 가능하다.
하지만, 메소드를 호출하는 것은 반복문보다 매개변수 복사와 종료, 복귀할 주소 저장 등 추가로
몇 가지 과정이 필요하기 때문에 재귀호출의 수행시간이 더 오래걸린다.
그럼에도 재귀호출을 사용하는 이유는
몇겹의 반복문과 조건문으로 복잡하게 작성된 코드가 재귀호출로 작성하다보면 단순한 구조로 바뀔 수도 있다.
따라서 다소 비효율적이더라도 알아보기 쉽게 작성하는 것이 논리적 오류가 발생할 확률이 줄어들기 때문이다.
재귀호출의 대표적인 예시 팩토리얼을 구하는 프로그램을 살펴보자.
* fact메소드가 static메소드이므로 인스턴스를 생성하지 않고 직접 호출할 수 있다.
그리고 main메소드와 같은 클래스에 있기 때문에 static메소드를 호출할 때 클래스이름을 생략하는것이 가능하다.
만일 fact메소드에 1을 집어넣으면 if문에 의하여 1을 반환한다.
만일 2를 집어넣으면 2 * fact(1) 을 반환하여 2 * 1 을 반환하게 된다.
만일 3을 집어넣으면 3 * fact(2) 가 되고, fact(2)는 2 * fact(1) 이기 때문에 3 * 2 * 1을 반환하게 된다.
그런데 만일 n의 값이 0이거나 100,000처럼 큰 수 이면 어떻게 될까?
먼저 n의 값이 0이라면 0 * fact(-1) ... -1 * fact(-2) .. 이렇게 계속 n의 값이 내려가며
if문의 조건식이 절대 참이 될 수 없기 때문에 계속해서 재귀호출만 일어나게 된다.
100,000처럼 큰 수도 마찬가지로 스택오버플로우 에러가 발생한다.
따라서 다음과 같이 '매개변수의 유효성검사'를 해주어야 한다.
이전 예제에 매개변수의 유효성을 검사하는 코드를 추가해서
메소드 fact의 매개변수 n이 음수거나 20보다 크면 -1을 반환하도록 하였다.
클래스 메소드와 인스턴스 메소드
변수에서 그랬던 것과 같이, 메소드 앞에 static이 붙어 있으면 클래스메소드이고 붙어있지 않으면 인스턴스 메소드이다.
클래스 메소드도 클래스 변수처럼, 객체를 생성하지 않고도 클래스이름.메소드이름(매개변수)와 같이 호출이 가능하다.
클래스메소드는 다음과 같은 특징과 고려해야할 점이 있다.
1. 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙인다.
2. 클래스변수(static 변수)는 인스턴스를 생성하지 않아도 사용할 수 있다.
-static이 붙은 변수는 클래스가 메모리에 올라갈 때 이미 자동적으로 생성되기 때문이다.
3. 클래스메소드는 인스턴스 변수를 사용할 수 없다.
-인스턴스변수는 인스턴스가 반드시 존재해야만 사용할 수 있는데, 클래스메소드는 인스턴스 생성 없이
호출가능하므로 클래스 메소드가 호출되었을 때 인스턴스가 존재하지 않을 수도 있다.
그래서 클래스 메소드에서 인스턴스 변수의 사용을 금지한다.
4. 메소드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다.
*참고
random()과 같은 Math클래스의 메소드는 모두 클래스 메소드이다. Math클래스에는 인스턴스변수가
하나도 없거니와 작업을 수행하는데 필요한 값들을 모두 매개변수로 받아서 처리하기 때문이다.
인스턴스메소드인 add(), subtract(), multiply() 는 인스턴스 변수인 a와 b만 으로도 충분히 작업이 가능하기 때문에,
매개변수를 필요로 하지 않으므로 괄호()에 매개변수를 선언하지 않았다.
반면에 add(long a, long b) 등은 인스턴스변수 없이 매개변수만으로 작업을 수행하기 때문에
static을 붙여서 클래스메소드로 선언하였다.
그래서 메인메소드를 보면 객체생성없이 바로 호출이 가능했고,
인스턴스메소드는 인스턴스를 생성한 후에야 호출이 가능했다.
클래스 멤버와 인스턴스 멤버간의 참조와 호출
같은 클래스에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다.
단, 클래스멤버가 인스턴스 멤버를 참조 또는 호출하고자 하는 경우에는 인스턴스를 생성해야한다.
그 이유는
인스턴스 멤버가 존재하는 시점에 클래스 멤버는 항상 존재하지만,
클래스멤버가 존재하는 시점에 인스턴스 멤버가 존재하지 않을 수도 있기 때문이다.
void instanceMethod() {} //인스턴스메소드
static void classMethod() {} //static메소드
void a() { //인스턴스메소드
instanceMethod(); //다른 인스턴스메소드를 호출
classMethod() //static메소드를 호출
}
static void b() { //static메소드
instanceMethod(); //에러발생. 인스턴스메소드를 호출할 수 없다.
classMethod() //static메소드를 호출
}
int iv; //인스턴스 변수
static int cv; //클래스변수
void a() { //인스턴스메소드
System.out.println(iv); //인스턴스 변수를 사용할 수 있다.
System.out.println(cv); //클래스변수를 사용할 수 있다.
}
static void b() { //static메소드
System.out.println(iv); //에러발생. 인스턴스 변수를 사용할 수 없다.
System.out.println(cv); //클래스변수는 사용할 수 있다.
}
위의 예시와 같이, 클래스멤버는 언제나 참조 또는 호출이 가능하기 때문에
인스턴스멤버가 클래스멤버를 사용하는 것은 아무런 문제가 안 된다.
클래스 멤버간의 참조 또는 호출 역시 아무런 문제가 없다.
그러나, 인스턴스멤버는 반드시 객체를 생성한 후에만 참조 또는 호출이 가능하기 때문에
클래스멤버가 인스턴스멤버를 참조, 호출하기 위해서는 객체를 생성하여야 한다.
하지만, 인스턴스멤버간의 호출에는 아무런 문제가 없다.
하나의 인스턴스멤버가 존재한다는 것은 인스턴스가 이미 생성되어 있다는 것을 의미하며,
즉 다른 인스턴스멤버들도 모두 존재하기 때문이다.
'JAVA > Basic' 카테고리의 다른 글
[JAVA-basic] 변수의 초기화 (0) | 2021.08.15 |
---|---|
[JAVA-basic] 생성자 (0) | 2021.08.13 |
[JAVA-basic] 변수와 메소드 (0) | 2021.08.11 |
[JAVA-basic] 클래스와 객체 (0) | 2021.08.11 |
[JAVA-basic] 다차원 배열 (0) | 2021.08.09 |