필드
클래스를 구성하는 요소 중 하나로 클래스 멤버.
클래스에 포함된 변수, 객체 속성을 정의할 때 사용된다
어떠한 데이터를 저장하기위한 역할을 담당하고
클래스 내부 생성자와 메소드 바깥 쪽에 정의된다
멤버 변수라고도 불리고 클래스 내부 전역에서 사용하기 때문에 전역 변수라고도 불린다
클래스 안에서 독립적으로 선언되는 변수를 의미한다고도 볼 수 있다
자바에서 변수는 클래스, 인스턴스, 지역 3개의 변수로 구분되고
이중에 필드라고 부르는 것은 클래스와 인스턴스 변수,
이 2개를 다시 static으로 구분할 수 있다
static 과 함께 선언된 건 클래스 변수,
그렇지 않은 건 인스턴스 변수다
그리고 이 2가지 변수 유형에 포함되지 않고 메소드 내 포함된 모든 변수를 지역변수 라고 한다
이 3가지 유형의 변수들은 주로 선언된 위치에 따라 그 종류가 결정되며 다른 유효 범위(scope)를 가진다
class Example { // 클래스 영역
int instanceVariable; // 인스턴스 변수
static int classVariable; // 클래스 변수(static 변수, 공유변수)
void method() { // 메소드 영역
int localVariable = 0;
// 지역 변수. {}블록 안에서만 유효
}
}
여기서 인스턴스 변수와 클래스 변수는 클래스 영역에 선언되었기 때문에 "멤버 변수" 다
다시 static 에 유무에 따라 classVariable 변수가 클래스 변수,
static이 없는 instanceVariable 변수가 인스턴스 변수가 된다
그리고 메소드 내부에 선언되어 있는 지역변수가 있다
이렇게 변수는, 선언 위치와 static의 유무에 따라 구분할 수 있다
인스턴스 변수
인스턴스가 가지는 각 고유한 속성을 저장하기 위한 변수로 new 생성자() 를 통해
인스턴스가 생성될 때 만들어진다
클래스를 통해 만들어진 인스턴스는 힙 메모리의 독립적 공간에 저장되고 동일한 클래스로부터 생성되었지만
객체의 고유한 개별성을 가진다
한마디로 정리하면, 인스턴스 변수는 그 고유한 특성을 정의하기 위한 용도로써 사용된다
static 을 통해 선언하는 클래스 변수
독립적인 저장공간을 가지는 인스턴스 변수와는 다르게 공통된 저장공간을 공유한다
그래서 한 클래스로부터 생성되는 모든 인스턴스들이 특정 값을 공유해야하는 경우에
주로 static를 사용해서 클래스 변수를 선언하게 된다
인간을 예시로 들면 팔 다리 개수 같은 모든 사람이 보편적으로 공유하는 특성을 저장하는데 사용된다고 할 수 있다
클래스 변수의 또 다른 특징으로는 인스턴스 변수와는 달리 인스턴스를 따로 생성하지 않고도
언제라도 클래스명.클래스변수명 을 통해 사용이 가능하다
저 위에 코드를 예시로 들면 Example.classVariable로 클래스 변수를 사용할 수 있는 것
지역변수
메소드 내에 선언되고 메소드 블럭 안에서만 사용 가능한 변수
멤버 변수와는 다르게 지역 변수는 스택 메모리에 저장되어
메소드가 종료되는 것과 동시에 함께 소멸되고
더이상 사용할 수 없게 된다
그리고 힙 메모리에 저장되는 필드 변수(클래스, 인스턴스 변수)는
객체가 없어지지 않는 한 절대 삭제되는 일이 없는 반면,
스택 메모리에 저장되는 지역변수는 한동안 사용하지 않는 경우 JVM에 의해 자동으로 삭제된다
필드 변수(클래스, 인스턴스 변수)와 지역 변수의 주된 차이점은 초기 값에 있다
직접 초기화 하지 않으면 값을 출력할 때 오류가 발생하는 지역변수와는 다르게
필드 변수는 직접적으로 초기화하지 않더라도 강제로 초기화가 이뤄진다
이 또한 메모리의 저장 위치와 긴밀한 연관성을 지니는데,
힙 메모리에는 빈 공간이 저장될 수 없기 때문에 여기에 저장되는 필드는 강제로 초기화 될 수 밖에 없지만
스택 메모리는 강제로 초기화 되지 않기 때문에 지역 변수는 선언할 때 반드시 초기화가 필요하다
(그렇지 않으면 값 출력 할 때 오류가 발생)
Static
클래스의 멤버 (필드, 메소드, 이너 클래스) 에 사용한다
static이 붙어있는 멤버를 정적 멤버라고 부르고 static이 붙어있지 않은 인스턴스 변수와 구분한다
정적 멤버(필드, 메소드, 이너클래스) 와 인스턴스 변수를 구분하는 가장 큰 차이는
인스턴스 멤버는 기존에 배웠던 내용처럼 무조건 객체를 생성한 이후에 변수와 메소드에 접근해서
해당 멤버를 사용가능한 반면,
static으로 정의되어 있는 클래스 멤버들은 인스턴스의 생성 없이도
클래스명.멤버명 만으로 사용이 가능하다는 점이다
물론 정적 멤버도 객체를 생성한 이후 참조변수를 통해 사용이 가능하긴 하겠지만
애초에 정적 멤버임을 표시하기 위해 클래스명.멤버명 형태로 사용할 것을 권장하고 있다
static을 사용하는 정적 멤버를 클래스명.멤버명 으로 사용할 수 있는 것 또한
전에 봤던 메모리 저장 위치와 관련이 있다
new를 통해 생성된 인스턴스는 힙메모리에 생성되고 독립적 저장공간을 가지게 된다고 말했다
static으로 선언된 정적 멤버는 클래스 내부에 저장공간을 가지고 있기 때문에 객체 생성 없이 바로 사용 가능하다
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의 유무에 따라 달라지는 차이가 명확하게 보인다
int num1 = 10; // static 없이 그냥 num1이라는 int형 변수에 10을 할당했다
static int num2 = -10; // static을 집어넣고 int형 변수 num2에 -10을 할당했다
StaticExample라는 클래스에서 객체를
new StaticExample(); 해서 힙 메모리에 할당하고
객체를 "인스턴스화" 한다
staticExample.num1 << static 키워드 없는 num1 객체 생성후에
포인트 연산자를 사용해서 값을 가져왔다
그리고 static가 있는 num2는
StaticExample.num2 << 객체 생성 없이 클래스명(대문자)을 사용해서 값을 불러왔다
이게 무슨 차이인지 이해가 가는가 ?
전에도 써놓았지만
관례상 클래스는 대문자로 쓰고 객체는 소문자로 쓴다고 했다
그러니 StaticExample 과 staticExample은 다른 거라고 보면 된다
그러니까 static 요소가 없는 num1은 생성된 객체를 사용해서
staticExample.num1 포인트 연산자로 인스턴스 변수로써 값을 불러온거고
static 요소가 있는 num2는 StaticExample.num2 클래스명.num2 로 "객체 생성 없이"
클래스명을 사용해서 값을 가져온 것이다
이렇듯, 정적 필드는 객체끼리 공유 변수의 성질이 있다는 점을 알아야 한다
메소드에도 동일하게 적용되는 점인데,
일반적인 메소드 앞에 static을 사용하면 해당 메소드는 "정적" 메소드가 된다
정적 메소드도 정적 필드와 동일하게 "클래스명" 만으로도 접근이 가능하다
그런데 정적 메소드는 인스턴스 변수 or 인스턴스 메소드를 사용할 수 없다
이게 무슨 말이냐면
정적 메소드. static을 사용한 메소드는 인스턴스 생성 없이 호출이 가능하기 때문에
정적 메소드가 호출되었을 때, 인스턴스가 존재하지 않는 것
정적 필드 간에 값 공유가 일어나는 걸 코드로 살펴보자
public class StaticFieldTest {
public static void main(String[] args) {
StaticField staticField1 = new StaticField(); // 객체 생성
StaticField staticField2 = new StaticField();
staticField1.num1 = 100;
staticField2.num1 = 1000;
System.out.println(staticField1.num1);
System.out.println(staticField2.num1);
staticField1.num2 = 150;
staticField2.num2 = 1500;
System.out.println(staticField1.num2);
System.out.println(staticField2.num2);
}
}
class StaticField {
int num1 = 10;
static int num2 = 15;
}
//출력값
100
1000
1500
1500
위의 코드를 보면 StaticField 클래스에 인스턴스 필드 num1, 정적필드 num2를 각각 선언하고
대조해서 보기 위해 staticField1, staticField2 객체를 생성했다
num1은 각 변수가 고유성을 가지기 때문에 (힙 메모리에 저장된 인스턴스를 포인트 연산자로 불러온다는 점에서)
100, 1000으로 따로 출력되는 모습이고
static을 사용한 num2는 값 공유가 일어나 (동일한 클래스가 적용되니까)
1500이 2번 반복 출력되고 있다
static을 사용하면 모든 인스턴스에 공통적으로 적용되는 값을 공유할 수 있는 것이다
이걸로 알 수 있는 사실은
static은 클래스 멤버(필드, 메소드, 이너 클래스) 앞에 붙일 수 있다
이 정적 멤버의 가장 큰 특징이라면 인스턴스를 따로 생성하지 않아도
클래스 명만으로 변수나 메소드를 호출이 가능하다는 점이고
메모리 저장위치와 관련이 있다는 사실을 알 수 있다
메소드 (Method)
특정 작업을 수행하는 기능을 가진 일련의 명령문 집합
머리 부분에 해당하는 메소드 시그니쳐와 몸통에 해당하는 메소드 바디로 구분할 수 있다
자바제어자 반환타입 메소드명(매개 변수) { // 메소드 시그니처
메소드 내용 // 메소드 바디
}
메소드 시그니쳐는 자바 제어자, 반환타입, 메소드명, 매개변수로 이뤄져 있다
아직 뭔지 모를 자바 제어자를 빼놓고 보면
메소드 시그니쳐는 "순서대로" 해당 메소드가 어떤 타입을 반환하는지(반환 타입), 메소드 이름이 무엇이며
이 작업을 수행하기 위해 어떤 것들이 필요한지(매개변수)에 대한 정보를 포함하고 있다
메소드 바디는 {} 중괄호 안에 해당 메소드가 호출 되었을 때 수행되어야 하는 일련의 작업들을 표시하게 된다
관례적으로 메소드명은 소문자로 표시한다 (클래스는 대문자인걸 참고하자)
public static int add(int x, int y) { // 메서드 시그니처
int result = x + y; // 메서드 바디
return result;
}
public 과 static 을 빼놓고 본다면 메소드명이 add, int타입 2개 x, y를 받아서 더한 다음에
int 타입의 결과 값을 반환하는 메소드라고 말할 수 있다
근데 만약 메소드 반환타입이 void가 아닌 경우에는 메소드 바디에 안에 return 이 존재해야 한다
이 리턴문은 작업을 수행한 결과값을 호출한 메소드로 전달하는데
여기 결과값은 반드시 반환타입과 일치하거나
적어도 자동 형변환이 가능한 것이어야 한다
그러니까 어느 메소드 반환 타입이 void 라면 반환 값이 없는 메소드를 의미하는 것이다
매개변수가 존재하지 않는 메소드도 있다
getNumSeven()이라는 메소드는 매개변수가 따로 존재하지 않는다
int getNumSeven() {
return 7;
}
해당 메소드가 호출되었을 때 숫자 7을 반환하면 되기 때문에 굳이 필요하지 않는 것
메소드의 호출
메소드를 아무리 잘 정의해놨다고 해도 호출하지 않으면 하등 쓸모가 없게 된다
메소드가 클래스 멤버이기 때문에 "클래스 외부에서 메소드를 사용하기 위해서는"
인스턴스를 생성할 필요가 있다
인스턴스를 생성한 후에 앞에서 봤던대로 포인트 연산자를 통해 메소드를 호출 할 수 있다
반면, 클래스 내부에 있는 메소드끼리는 따로 객체를 생성하지 않고도 서로를 호출할 수 있다
메소드이름(매개변수1, 매개변수2, ...); // 메소드 호출방법. 매개 변수가 없을 수도 있다
메소드 호출할 때 소괄호() 안에 넣어주는 입력 값을 우리는 argument (인자) 라고 하는데
개수와 순서는 무조건 메소드를 정의할 때 선언된 매개 변수와 일치해야 한다
안그러면 실행할 때 에러가 발생하니까 말이다
이 인자 타입 또한 매개변수의 그것과 일치하거나 자동 형변환이 가능한 것이어야만 한다
메소드 오버로딩
하나의 클래스 안에 같은 이름의 메소드를 여러 개 정의하는 것을 의미
overload는 과적 이라는 의미니까 어느 정도 일맥상통한다고 볼 수 있다
보통은 하나의 메소드에 하나의 기능만 구현해야 하는데 같은 이름의 메소드를 여러 기능을 구현하기 때문에
오버로딩이라는 용어를 사용한 것이라고 봐도 무방하다
이걸 이해하기 위해서는 메소드 시그니쳐에 대한 이해도 필요하다
메소드 시그니쳐는 메소드명과 매개변수의 자료형을 의미하는데
각 메소드를 구분하는 용도로도 사용한다
메소드 이름이나 매개변수 타입이 다르면 다른 메소드라고 인식하는 JVM 기능과 관계가 있다
public class Overloading {
public static void main(String[] args) {
Shape s = new Shape(); // 객체 생성
s.area(); // 메소드 호출
s.area(3);
s.area(5, 5);
s.area(5.5, 12.5);
}
}
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);
}
}
// output
넓이
원 넓이 = 28.26
직사각형 넓이 = 25
삼각형 넓이 = 34.375
Shape 클래스 안 모든 메소드들이 area()라는 메소드명을 가지고 있는데도
전부 다른 출력값을 리턴하는 것을 볼 수 있다
그렇지만 같은 메소드명(area)을 사용한다고 해서 오버로딩이 되는 게 아니라는 점이 중요한 부분이며
매개변수의 개수나 타입도 다르게 정의되어야 한다는 점을 확인 할 수 있듯이 중요한 부분이다
그러니까
오버로딩이 되려면 2가지 조건이 성립되어야 한다
1. 메소드의 이름이 같다
2. 매개변수 개수나 타입이 달라야 한다
area()
area(int r)
area(int w, int l)
area(double b, double h)
매개변수 개수, 타입이 다른 것을 알 수 있다
두 가지 조건이 동시에 만족되지 않으면 중복 정의로 간주돼서 컴파일 에러가 발생한다
반환 타입은 오버로딩에 영향을 주지 못하는데, 다른 반환타입을 지정했다고해도
JVM이 다른 메소드라 인식하지 못하기 때문이다
이러한 오버로딩은 하나의 메소드로 여러 경우를 해결할 수 있다는 장점이 있다
println() 메소드 같은 경우엔 아무 값이든 괄호()에 인자로 넣어서 사용하는데 문제가 없었지만
사실은 그 내부를 보면 매개변수 타입에 따라서 호출되는 println() 메소드가 달라진다는 것을 알 수 있다
오버로딩이 없다면 일일이 메소드를 정의해줘야 하는 번거로움이 발생했을 것이고
같은 기능을 하는 메소드들의 이름을 계속 반복적으로 지어줘야 했을 것이다
'Java' 카테고리의 다른 글
[Java OOP 심화] 상속( Inheritance) (0) | 2022.05.25 |
---|---|
[Java OOP 기초] 생성자 (0) | 2022.05.25 |
[Java OOP 기초] 클래스와 객체 (0) | 2022.05.25 |
[Java OOP 기초] 객체 지향 프로그래밍 (0) | 2022.05.25 |
[Java 기초] 계산기 (0) | 2022.05.23 |
댓글