[Java] 필드, static, 메서드, 생성자

kai6666·2022년 5월 11일
1

TIL. Java

목록 보기
8/21

필드 (Field)

필드는 클래스에 포함된 변수이자 객체의 속성을 정의한다. 자바에서 변수는 클래스 변수(cv, class variable), 인스턴스 변수(iv, instance variable), 그리고 지역 변수(lv, local variable) 이렇게 세 가지가 있다. 이중 클래스 변수와 인스턴스 변수가 필드이다. 클래스 변수와 인스턴스 변수는 static 유무로 구분할 수 있다.

class Cookie { // 클래스 영역
	String gender; // 인스턴스 변수
    static int age; // 클래스 변수(static 변수, 공유변수)
    
    void sleep() { // 메서드 영역
    	int hours = 4; // 지역 변수
        }
}

위 코드를 통해 보면 Cookie 클래스 안에 세 가지 유형의 변수가 선언되어 있다. 인스턴스 변수와 클래스 변수는 클래스 영역에 선언되어 있기 때문에 멤버 변수이다. 지역 변수는 메서드 영역 안에 선언되어 있다. 이처럼 변수는 선언 위치와 static 유무로 구분이 된다.

  • 인스턴스 변수
    인스턴스 변수는 new 생성자()를 통해 인스턴스가 생성될 때 만들어진다. 인스턴스는 힙 메모리에 독립적으로 저장되기 때문에, 인스턴스 변수는 사람마다 특징이 다 다르듯 고유한 특성을 정의하기 위해 사용된다.

  • 클래스 변수
    클래스 변수는 인스턴스 변수와 다르게 공통된 저장공간을 공유한다. 따라서 클래스 내에 인스턴스들이 공통으로 값을 공유해야 할 때 static 키워드를 통해 클래스 변수를 선언하게 된다.

  • 지역 변수
    지역 변수는 메서드 내에 {} 블록에서만 사용 가능한 변수다. 멤버 변수와 달리 지역 변수는 스택 메모리에 저장된다. 메서드가 종료됨과 동시에 소멸되기 때문에 재사용이 안 된다. 또한 스택 메모리에 저장되기 때문에 한동안 사용하지 않는다면 가상 머신에 의해 자동 삭제된다.

👉 필드 변수와 지역 변수의 차이점

지역 변수는 직접 초기화하지 않으면 값 출력시 오류가 발생한다. 반면 필드 변수는 직접 초기화를 하지 않아도 강제 초기화가 이뤄진다. 이것 또한 저장된 메모리가 다르기 때문이다. 힙 메모리는 빈 공간이 저장되지 않기 때문에 필드 저장시 강제 초기화가 된다. 스택 메모리는 초기화를 강제하지 않기 때문에 지역 변수는 선언시 반드시 초기화를 해주어야 한다.

static 키워드

앞선 내용에서 static 키워드로 클래스 변수와 인스턴스 변수를 구분할 수 있다고 설명했다. static은 클래스의 멤버(필드, 메서드, 이너클래스)에 쓰는 키워드로 이것이 붙은 멤버는 '정적 멤버(static member)'라 부른다.
인스턴스 변수는 반드시 객체의 생성이 우선되고, 그후 변수와 메서드에 접근한다. 이와 다르게 static 키워드로 정의된 클래스 멤버들은 인스턴스를 만들지 않아도 클래스명.멤버명만으로 사용이 가능하다. static 키워드로 선언한 정적 멤버는 힙 메모리가 아닌 클래스 내부 저장 공간을 쓰기 때문에 객체를 생성하지 않아도 되는 것이다.

public class StaticExercise {
    public static void main(String[] args) {
        StaticNumbers staticNumbers = new StaticNumbers();
        System.out.println(staticNumbers.num1); // 100
        System.out.println(StaticNumbers.num2); // 200

        staticNumbers.num1 = 500;
        staticNumbers.num2 = 600;
        System.out.println(staticNumbers.num1); // 500
        System.out.println(staticNumbers.num2); // 600

       // StaticNumbers.num1 = 300; // 에러 java: Cannot make a static reference to the non-static field
        StaticNumbers.num2 = 400;
      //  System.out.println(StaticNumbers.num1);
        System.out.println(StaticNumbers.num2); // 400
        System.out.println(staticNumbers.num2); // 400
    }
}

class StaticNumbers {
    int num1 = 100;
    static int num2 = 200;
}

위 코드를 보면, StaticNumbers 클래스에 num1이라는 인스턴스 필드와 num2라는 정적 필드가 선언되어있다.
StaticExercise 클래스에 1~2번째 println()에 100과 200이 출력되었다. num1의 경우 위에서 staticNumbers라는 인스턴스를 생성해 포인트 연산자를 통해 값을 불러왔다. num2의 경우 객체를 만들지 않고 클래스명.멤버명을 통해 바로 값을 불러왔다.
아래 3~4번째 println()는 500과 600이 출력되었다. 그 이유는 num1와 num2이 객체 안의 값이라 고유성을 가지기 때문이다. 반면 5~6번째 println()는 둘 다 400이 출력됐다. 이는 static 키워드가 가지는 '공유'라는 특성 때문이다. 클래스명.멤버명으로 호출하든, 인스턴스를 통해 호출하든 static 키워드가 붙어 값 공유가 일어났다. 값 공유가 일어나면 모든 인스턴스에 공통적으로 값을 적용할 수 있다.

메서드 (Method)

메서드는 클래스의 기능에 해당하는 명령문의 집합이자 함수이다. 메서드는 크게 메서드 시그니처와 메서드 바디로 구성된다.

public static int minus(int a, int b) { // 메서드 시그니처
	int result = a - b; // 메서드 바디
    return result
    }

메서드 시그니처(Method Signature)는 자바 제어자, 반환 타입, 메서드명 그리고 매개 변수(필요로 하는 재료)로 이뤄져있다. 메서드 바디(Method Body)는 메서드의 내용을 중괄호({}) 안에 담은 부분이다.
클래스명과 달리 메서드명은 통상적으로 소문자로 표시한다. 또한 반환타입이 void인 경우를 제외하면 메서드 바디 안에 반드시 return문이 있어야 한다. 반환 값은 호출한 메서드로 전달되는데, 이 값은 반환 타입과 같거나 자동 형변환이 가능해야 한다.

void sleep() {
	System.out.println("쿨쿨 잔다.")
    }

이 메소드의 경우 반환 타입이 void기 때문에 메서드가 호출되면 "쿨쿨 잔다."의 내용만 출력하고 종료된다.

String name() {
	return "쿠키"
    }

이 메서드의 경우 "쿠키"라는 이름만 반환하기 때문에 소괄호에 매개변수가 없다.

Double divide(int a, int b) {
	double result = a * b;
    return result;
    }

이 메서드의 경우 a와 b라는 매개변수를 받아 double 타입의 result를 반환한다.

메서드를 호출하는 방법은 아래와 같다.

void sleep(); // 쿨쿨 잔다.
String name(); // 쿠키
Double divide(4, 4); // 16

매서드이름(매개변수1, 매개변수2, ...);로 간단하게 호출할 수 있다. 소괄호 안에 매개변수를 인자(argument)라고도 하는데, 인자의 개수와 순서는 반드시 메서드를 선언할 때 넣은 매개변수와 일치해야 한다. 타입 또한 일치하거나 자동 형변환이 가능한 것이어야 한다. 또한, 메서드도 클래스 멤버이기 때문에 클래스 외부에서 쓰려면 인스턴스를 생성해야 한다. (생성 후 포인트 연산자 "."로 메서드 호출.) 클래스 내부의 메서드는 인스턴스를 만들지 않아도 서로 호출이 가능하다.

💁‍♀️ 메서드 오버로딩 (Method Overloading)

메서드 오버로딩이란 하나의 클래스 안에 같은 이름의 메서드여러 개 정의하는 것이다.

public class Overloading {
    public static void main(String[] args) {
        Computer computer = new Computer();

        computer.start(); // 메서드 호출
        computer.weight(5);
    }
}

class Computer {
    public void start() {
        System.out.println("시작");
    }
    public void weight(int x) {
        System.out.println("무게 = " + 1000 * x + "그램입니다.");
    }

}

// 출력값
// 시작
// 무게 = 5000그램입니다.

위 코드는 오버로딩에 대한 짧은 예시이다. Overloading이라는 클래스에 computer라는 동일한 이름의 메서드를 2번 사용했는데 각 다른 값이 나왔다. 이것이 오버로딩이다. 오버로딩이 성립되려면 2가지 조건이 충족되어야 한다.

  • 메서드의 이름이 동일할 것.
  • 매개변수의 개수 또는 타입이 다를 것.
    다시 보면 start() 메서드와 weight() 메서드는 둘 다 println() 명령이지만 매개변수의 개수와 타입이 다르다. 오버로딩을 쓰는 이유는 메서드를 일일이 정의할 필요없이 같은 메서드를 매번 조금씩 다르게 쓰면 되기 때문이다.

생성자 (Constructor)

생성자는 객체를 생성하는 역할을 하는 클래스의 구성요소이다. 객체를 생성할 때 인스턴스를 만드는 것은 엄밀히 말하면 new 키워드의 역할이다. 생성자는 인스턴스의 변수들을 초기화하는 메서드의 일종이다. 생성자는 메서드와 구조가 비슷하나 (1) 생성자 이름 = 클래스 이름, (2) 리턴 타입이 없다는 차이점이 있다. 보통 메서드에서 리턴 값이 없으면 void라는 말을 붙여준다. 그치만 생성자에서는 이 말을 하지 않는다. 리턴 타입 자체가 존재하지 않기 때문이다.

클래스명(매개변수) {
...
	}

생성자의 구조는 위와 같다. 생성자는 메서드이기 때문에 오버로딩이 가능하다. 따라서 한 클래스 내 여러 생성자가 존재할 수 있다.

public class Example {
    public static void main(String[] args) {
        Computer computer = new Computer();
        Computer computer2 = new Computer("This is Constructor");
        Computer computer3 = new Computer(5, 5, 5);
    }
}

class Computer {
        Computer () {
            System.out.println("1번 생성자");
        }
        Computer(String string) {
            System.out.println("2번 생성자");
        }
        Computer(int a, int b, int c){
            System.out.println("3번 생성자");
        }


}

오버로딩을 활용하여 같은 이름의 생성자를 여러 개 만든 코드이다. 생성자의 매개변수에 들어간 타입에 따라 객체를 생성하는 방법이 결정된다. 따라서 여기서는 computer2라는 객체가 생성되려면 문자열을 전달해줘야 하고, computer3이라는 객체가 생성되려면 int형 매개변수 3개를 줘야 한다.

👉 기본 생성자 vs. 매개변수가 있는 생성자

클래스에는 중요한 규칙이 있다. 바로 모든 클래스에 반드시 하나 이상의 생성자가 존재해야 한다는 규칙이다. 이 규칙 때문에 컴파일러는 생성자가 없는 클래스에 기본 생성자(Default Constructor)를 만들어준다.

클래스명() {}

기본 생성자는 위처럼 생겼다. 소괄호와 중괄호에 아무 내용이 없는 형태다.

클래스명(String str, int a, int b) {
	this.str = str;
    this.a = a;
    this.b = b;
    }

매개변수가 있는 생성자는 위처럼 생겼다. 호출시 매개변수로 값을 받아 인스턴스를 초기화한다. 클래스에 매개변수가 있는 생성자만 있는데 new 키워드로 객체를 생성할 때 호출이 되지 않는 경우가 있다. 이것은 컴파일러가 클래스에 이미 생성자가 있기 때문에 기본 생성자를 만들어주지 않았기 때문이다. 이 경우에 클래스명 () {} 형태의 기본생성자를 추가해주면 된다.

💁‍♀️ this vs. this()

  • this()
    this()는 형태를 보면 알 수 있듯이 메서드다. this() 메서드는 같은 클래스 안의 생성자끼리 상호 호출할 때 사용한다. 이 메서드는 생성자 내에서만 사용이 가능하고, 반드시 첫 줄에 위치해야 한다.
public class Example {
    public static void main(String[] args) {
        Computer computer = new Computer();
        Computer computer2 = new Computer("This is Constructor");

    }
}

class Computer {
        Computer () {
            System.out.println("1번 생성자"); // 1번 생성자
        }
        Computer(String string) {
            this();
            System.out.println("2번 생성자"); //1번 생성자 2번 생성자
        }

}

Computer 클래스에 첫번째 생성자는 기본 생성자다. 따라서 computer라는 객체를 생성할 때 우선 첫번째 생성자가 호출되어 "1번 생성자"라는 값이 출력된다. 아래의 두번째 생성자로 객체를 만들 때는 두번째 생성자의 this() 메서드로 다시 첫번째 기본 생성자가 호출되고, "2번 생성자"라는 문구가 출력된다.


- **this** this는 **인스턴스 변수(iv)와 지역 변수(lv)를 구분해주기 위해 사용**되는 참조변수다. ```java 클래스명(String str, int a, int b) { this.str = str; this.a = a; this.b = b; ``` 위에서 생성자의 예시로 사용했던 코드를 다시 가져와봤다. 여기서 인스턴스 변수는 this.뒤에 붙은 str, a, b이다. 소괄호의 내용과 대립 연산자 우측의 내용은 지역 변수다. 이것들이 같은 이름을 쓰기 때문에 구분해주기 위해 앞에 ```this.```를 붙여주는 것이다. 이름이 같지 않은 경우에도 관례적으로 이 키워드를 붙여준다. (많은 경우에는 메서드의 지역 변수명과 필드명이 동일하다.) 정리하자면, this 키워드는 인스턴스 자신을 가리키는 것이자 인스턴스 자신의 변수에 접근할 수 있게 해준다.
profile
성장 아카이브

0개의 댓글