본 글은 1편에서부터 이어진다.
기본형 매개변수
코드로 바로 알아보겠다.
public class Main {
public static void main(String[] args) {
Data d = new Data();
d.x = 10;
System.out.println("main() : x = " + d.x);
change(d.x);
System.out.println("After change(d.x)");
System.out.println("main() : x = " + d.x);
}
static void change(int x) {
x = 1000;
System.out.println("change() : x = " + x);
}
}
class Data {
int x;
}
해당 코드를 출력하면 어떻게 나오는지 확인해보자.
4번째 main() : x에서 1000이 나올줄 알았지만 10이 나왔다. 그림으로 이걸 풀어나가보자.
가장 먼저 main 함수가 실행되고 참조변수 d가 선언되어 d의 인스턴스 변수 x에 10을 초기화한다.
change 함수가 실행되어 매개변수로 d의 인스턴스 변수 값인 10을 넣는다. 다만 원시형이기 때문에 값의 주소를 알지 못한다.
그리고 change 함수 내의 지역 변수 x = 1000이 되어 10이었던 것이 1000이 된다. change 메소드는 내부의 System.out.println() 문을 실행시키고 더 이상 실행시킬 코드가 없어 소멸된다. 여기서 참조변수 d의 인스턴스 변수에 해당하는 값을 변경한 것이 아니라 change 함수 내부의 지역변수 값만 변경시키고 소멸되었기에 참조변수 d의 인스턴스 변수 x는 그대로 10을 의미하고 있다.
참조형 매개변수
위의 코드에서 조금만 바꿔보겠다.
public class Main {
public static void main(String[] args) {
Data d = new Data();
d.x = 10;
System.out.println("main() : x = " + d.x);
change(d);
System.out.println("After change(d.x)");
System.out.println("main() : x = " + d.x);
}
static void change(Data d) {
d.x = 1000;
System.out.println("change() : x = " + d.x);
}
}
class Data {
int x;
}
결과를 확인해보자.
아까와 다르게 참조변수 d의 인스턴스 변수 값이 변경되었다. 그 이유는 change 함수의 매개변수로 원시형이 아닌 참조형으로 매개변수를 받았기 때문에 해당 인스턴스의 주소값을 공유하게 되었기 때문이다.
한 눈에 정리하기
매개변수를 원시형으로 받을 경우에는 함수 내의 지역변수의 값만 변경하고 소멸하기 때문에 클래스에 있는 참조변수의 인스턴스가 변경되지 않는다. 하지만 매개변수를 참조형으로 받을 경우 인스턴스의 주소값을 공유해버리기 때문에 인스턴스가 변경되는 것이다.
static 메소드와 인스턴스 메소드
위치에 따라서 변수명이 변한다는 부분을 1편에서 기록했다. 클래스에 선언된 변수는 멤버변수, static이 붙어 주소를 공유하는 변수는 클래스 변수, 함수 내에서 선언되어 그 안에서만 동작하는 지역변수가 있다. 기억이 나지 않는다면 다시 공부한다. 메소드 또한 마찬가지이다. 메소드 앞에 static이 붙으면 클래스 메소드, 붙어있지 않으면 인스턴스 메소드이다. 코드로 알아보자.
public class Main {
public static void main(String[] args) {
System.out.println("static 메소드 출력");
System.out.println(MyMath.add(20, 10));
System.out.println(MyMath.subtract(20, 10));
System.out.println(MyMath.multiply(20, 10));
System.out.println(MyMath.divide(20, 10));
MyMath myMath = new MyMath();
myMath.a = 20;
myMath.b = 10;
System.out.println("인스턴스 메소드 출력");
System.out.println(myMath.add());
System.out.println(myMath.subtract());
System.out.println(myMath.multiply());
System.out.println(myMath.divide());
}
}
class MyMath {
int a, b;
int add() {
return a + b;
}
int subtract() {
return a - b;
}
int multiply() {
return a * b;
}
double divide() {
return (double) a / b;
}
static int add(int a, int b) {
return a + b;
}
static int subtract(int a, int b) {
return a - b;
}
static int multiply(int a, int b) {
return a * b;
}
static double divide(int a, int b) {
return (double) a / b;
}
}
스태틱 메소드와 인스턴스 메소드의 호출 방법이 다른 것을 알 수 있다. 스태틱 메소드의 경우 클래스명.메소드로 호출하지만 인스턴스 메소드의 경우에는 myMath 참조변수에 인스턴스를 생성하여 메소드를 호출했다. 하지만 인스턴스 메소드는 인스턴스를 생성했기 때문에 메소드 내에 매개변수를 넣지 않아도 값이 올바르게 리턴되지만 스태틱 메소드는 매개변수만으로 작업하기 때문에 넣어야 리턴된다.
출력해보면 모두 같은 값으로 출력된 것을 확인할 수 있다.
스태틱 메소드는 언제 붙여서 쓸까?
그렇다면 다음과 같은 의구심이 들 수 있다. 굳이 스태틱 메소드를 호출하지말고 인스턴스를 생성해 인스턴스 메소드를 호출해도 되는거 아니야? 라고 생각할 수 있다. 사용하는 때는 다음과 같다.
1. 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용되는 것에 static을 붙인다.
2. 스태틱 변수는 인스턴스를 생성하지 않아도 사용할 수 있다.
3. 스태틱 메소드는 인스턴스 변수를 사용할 수 없다.
4. 메소드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙일 것을 고려한다.
이론적인 건 어렵다고 느껴질 수 있다. 말을 풀어서 정리해보겠다.
1번의 경우, 호출되는 인스턴스들은 전부 독립적이기 때문에 모든 인스턴스에서 공통적으로 공유할 변수를 스태틱으로 지정한다. 2번의 경우, 스태틱 변수는 클래스가 생성될 때 메모리에 자동적으로 생성된다.
3번의 경우, 스태틱 메소드는 인스턴스 생성 없이 호출이 가능하므로 인스턴스가 존재하지 않을 수 있는데 그로 인해 금지한다.
4번의 경우, 인스턴스 변수가 필요없을 때는 스태틱을 붙여 메모리 활용을 통해 성능이 향상될 수 있다.
메소드 간의 호출과 참조
같은 클래스 내에 속하는 멤버들 간에는 별도의 인스턴스를 생성하지 않아도 서로 참조하거나 호출할 수 있다. 단, 클래스 멤버는 인스턴스 멤버를 참조 또는 호출할 때 인스턴스를 생성해주어야 한다.
class A {
void instanceMethod() {
}
static void staticMethod() {
}
void instanceMethod2() {
instanceMethod();
staticMethod();
}
static void staticMethod2() {
instanceMethod(); // 에러 발생
staticMethod();
}
}
스태틱 메소드에서 인스턴스 메소드를 실행시키려고 하니 에러가 발생한다.
class A {
void instanceMethod() {
}
static void staticMethod() {
}
void instanceMethod2() {
instanceMethod();
staticMethod();
}
static void staticMethod2() {
A a = new A();
a.instanceMethod();
staticMethod();
}
}
다음과 같이 참조변수를 통해 인스턴스를 생성해주고 인스턴스 메소드를 호출해야 에러가 발생하지 않는다.
내일 할 부분
오버로딩과 생성자 등 클래스 내에서 함수명을 같게 쓸 수 있는지 또한 쓸 수 없다면 어떻게 사용할지 등에 대해 학습할 예정이다.
'java > 자바의 정석' 카테고리의 다른 글
자바의 정석 - 객체지향 프로그래밍 6편 (0) | 2024.02.21 |
---|---|
자바의 정석 - 객체지향 프로그래밍 5편 (0) | 2024.02.20 |
자바의 정석 - 객체지향 프로그래밍 4편 (0) | 2024.02.19 |
자바의 정석 - 객체지향 프로그래밍 3편 (0) | 2024.02.17 |
자바의 정석 - 객체지향 프로그래밍 1편 (0) | 2024.02.14 |