필드는 클래스에 포함된 변수를 의미하고 객체의 속성을 정의하는 데 사용한다.
자바에서 변수는 크게 클래스 변수(cv, class variable), 인스턴스 변수(iv, instance variable), 그리고 지역 변수(lv, local variable)라는 세 가지로 구분된다.
이 중 필드에 해당하는 것은 클래스 변수와 인스턴스 변수이며, 이 둘은 다시 static 키워드의 유무로 구분할 수 있다.
static 키워드가 함께 선언된 것은 클래스 변수, 그렇지 않은 것은 인스턴스 변수이다. 이와 다르게 메서드 내에 포함된 모든 변수를 지역변수라고 한다.
이 세 가지 유형의 변수들은 주로 선언된 위치에 따라 그 종류가 결정되며 각각 다른 유효 범위(scope)를 가지게 된다.
1 class Example { // => 클래스 영역
2 int instanceVariable; // 인스턴스 변수
3 static int classVariable; // 클래스 변수(static 변수, 공유변수)
4
5 void method() { // => 메서드 영역
6 int localVariable = 0; // 지역 변수. {}블록 안에서만 유효
7 }
8 }
위의 코드 예제에서 Example 클래스 안에 앞서 언급한 세 가지 유형의 변수가 선언돼 있다. 이 중에 instanceVariable 과 classVariable 은 클래스 영역에 선언되었기 때문에 멤버 변수이다.
다시 static 키워드(다음 챕터에서 학습)의 유무에 따라 classVariable 변수가 클래스 변수, 그리고 키워드가 있지 않은 instanceVariable 변수가 인스턴스 변수가 된다.
마지막으로 메서드 내부의 블럭에 선언되어있는 지역변수 localVariable 이 있다.
이처럼 변수는 주로 그 선언 위치와 static 키워드의 유무에 따라 구분할 수 있다.
인스턴스 변수(iv)는 인스턴스가 가지는 각각의 고유한 속성을 저장하기 위한 변수로, new 생성자() 를 통해 인스턴스가 생성될 때 만들어진다.
static 키워드를 통해 선언하는 클래스 변수는 독립적인 저장 공간을 가지는 인스턴스 변수와 다르게, 공통된 저장공간을 공유한다. 따라서 한 클래스로부터 생성되는 모든 인스턴스들이 특정한 값을 공유해야하는 경우에 주로 static 키워드를 사용하여 클래스 변수를 선언한다.
또한, 클래스 변수는 인스턴스 변수와 달리 인스턴스를 따로 생성하지 않고도 클래스명.클래스변수명 을 통해 사용할 수 있다.
지역변수는 메서드 내에 선언되며 메서드 내({} 블록)에서만 사용가능한 변수이다. 멤버 변수와는 다르게 지역변수는 스택 메모리에 저장되어 메서드가 종료되는 것과 동시에 함께 소멸되어 더이상 사용할 수 없게 된다.
또한 힙 메모리에 저장되는 필드 변수는 객체가 없어지지 않는 한 절대로 삭제되는 않는 반면, 스택 메모리에 저장되는 지역변수는 한동안 사용되지 않는 경우 가상 머신에 의해 자동으로 삭제된다.
직접 초기화하지 않으면 값을 출력할 때에 오류가 발생하는 지역변수와는 다르게 필드 변수는 직접적으로 초기화를 실행하지 않더라도 강제로 초기화가 이뤄진다.
메모리의 저장 위치와 긴밀한 연관성을 가지는 부분인데 힙 메모리에는 빈 공간이 저장될 수 없기 때문에 이곳에 저장되는 필드는 강제로 초기화되지만, 스택 메모리는 강제로 초기화되지 않으므로 지역 변수는 선언시 반드시 초기화를 실행해주어야 한다.
클래스 변수와 인스턴스 변수를 구분하는 가장 큰 차이는 인스턴스를 생성한 후 멤버에 접근할 수 있느냐이다.
인스턴스 멤버는 반드시 객체를 생성한 이후에 변수와 메서드에 접근하여 해당 멤버를 사용가능한 반면, static 키워드로 정의되어 있는 클래스 멤버들은 인스턴스의 생성 없이도 클래스명.멤버명 만으로도 사용이 가능하다.
static 키워드를 사용하는 정적 멤버를 클래스명.멤버명 으로 사용할 수 있는 것 또한 앞에서 봤었던 메모리의 저장 위치와 관련이 있습니다. 앞서 확인했듯이 new 키워드를 통해 생성된 인스턴스는 힙 메모리에 생성되고 독립적인 저장공간을 가지게 됩니다.
public class StaticTest {
public static void main(String[] args) {
StaticExample staticExample = new StaticExample();
System.out.println("인스턴스 변수: " + staticExample.num1); // static 키워드가 없는 인스턴스 변수
System.out.println("클래스 변수: " + StaticExample.num2); //static 키워드가 있는 클래스 변수
}
}
class StaticExample {
int num1 = 10;
static int num2 = -10;
}
//Output
인스턴스 변수: 10
클래스 변수: -10
정적 필드는 객체 간 공유 변수의 성질이 있다는 점이다. 이것은 메서드에도 동일하게 적용된다. 일반적인 메서드 앞에 static 키워드를 사용하면 해당 메서드는 정적 메서드가 된다. 정적 메서드도 정적 필드와 마찬가지로 클래스명만으로 바로 접근이 가능하다.
정적 메서드의 경우 인스턴스 변수 또는 인스턴스 메서드를 사용할 수 없다는 것이다. 정적 메서드는 인스턴스 생성 없이 호출이 가능하기 때문에 정적 메서드가 호출되었을 때 인스턴스가 존재하지 않을 수 있기 때문이다.
public class Sta {
public static void main(String[] args) {
StaticField sta1 = new Sta();
StaticField sta2 = new Sta();
sta1.num1 = 30;
sta2.num1 = 300;
System.out.println(sta1.num1);
System.out.println(sta2.num1);
sta1.num2 = 500;
sta2.num2 = 5000;
System.out.println(sta1.num2);
System.out.println(sta2.num2);
}
}
class Sta {
int num1 = 3;
static int num2 = 5;
}
//출력값
30
300
5000
5000
num1는 인스턴스 각각의 변수가 독립적이기 때문에 30과 300으로 다르게 출력되지만, num2는 static을 이용해 클래스변수로 선언되었기 때문에 변수의 공유가 일어나 5000을 두 번 출력한다. static 키워드를 사용하면 모든 인스턴스에 공통적으로 사용하는 변수를 선언할 수 있다.
static 멤버는 인스턴스를 따로 생성하지 않고도 클래스명만으로 변수나 메서드 호출이 가능하다. 그리고 이는 메모리의 저장위치와 관련돼 있다.
독립적인 기능을 수행하는 일련의 명령문들의 집합을 의미한다.
자바제어자 반환타입 메서드명(매개 변수) { // 메서드 시그니처
메서드 내용 // 메서드 바디
}
public static int add(int x, int y) { // 메서드 시그니처
int result = x + y; // 메서드 바디
return result;
}
메서드명이 add, int 타입 2개의 값(x 와 y )을 받아 더한다음 int 타입의 결과값을 반환하는 메서드라 정리할 수 있다.
메서드는 return 문이 있는데 메서드를 호출한 후 결과값을 반환하기 위한 명령이다. 반환하기 위해서는 반환하는 값의 타입을 지정해야 하는데, 반환타입이 void가 아닌 경우에는 메서드 바디({} )안에 반드시 return 문이 존재해야 한다.
return 문은 메서드를 호출한 부분으로 전달한다. 여기서 결과값은 반드시 반환타입과 일치하거나 적어도 자동 형변환이 가능한 것이어야 한다.
메서드도 클래스의 멤버이므로 클래스 외부에서 메서드를 사용하기 위해서는 먼저 인스턴스를 생성해야한다. 인스턴스를 생성한 후에 앞서 보았던 것처럼 포인트 연산자(.)를 통해 메서드를 호출할 수 있다.
반면, 내부(이너)클래스끼리는 객체를 따로 생성하지 않아도 서로를 호출할 수 있다.
하나의 클래스 안에 같은 이름의 메서드를 여러 개 정의하는 것을 의미한다.
public class Overloading {
public static void main(String[] args) {
Shape s = new Shape(); // 객체 생성
s.area(); // 메서드 호출
s.area(5);
s.area(10,10);
s.area(6.0, 12.0);
}
}
class Shape {
public void area() { // 메서드 오버로딩. 같은 이름의 메서드 4개.
System.out.println("넓이");
}
public void area(int r) {
System.out.println("원 넓이 = " + 3.14 * r * r);
}
public void area(int w, int l) {
System.out.println("직사각형 넓이 = " + w * l);
}
public void area(double b, double h) {
System.out.println("삼각형 넓이 = " + 0.5 * b * h);
}
}
//출력값
넓이
원 넓이 = 78.5
직사각형 넓이 = 100
삼각형 넓이 = 36.0
Shape 클래스 안에 있는 모든 메서드들이 area()라는 메서드명을 가지고 있음에도 불구하고 각기 다른 출력값을 리턴한다. 메서드 오버로딩에는 조건이 있다.
메서드의 이름이 같아야 한다.
매개변수의 개수 또는 타입이 달라야 한다.
가장 큰 장점은 하나의 메서드로 여러 경우를 해결할 수 있다는 것이다.
메서드 오버로딩의 지원을 통해 개발자들이 같은 기능을 수행하는 메서드를 하나하나 모두 정의할 필요가 없어진 것이다.