본문 바로가기

Java

맨날 헷갈리는 자바 1편 - 변수(인스턴스, 클래스(static))편

안녕하세요! 그동안 공모전이랑 알바 등으로 바빠서 오래간만에 글을 써 보는 거 같네요!

얼마 전에 아버지 회사 홈페이지 리뉴얼을 담당하게 되었어요! 드디어 자바서버를 실제 서비스에 배포하게 되는 아주 커다란 일을 당담하게 되었네요ㅎㅎ 부담스럽기도 하면서 걱정도 되고 한편으로는 기대가 되네요! DB 아키택쳐부터 모든 걸 비즈니스 로지컬 하게 짜고 싶어서 고민하던 중 제 자바 실력이 너무 부족하다는 결론이 들어서 이렇게 블로그로 자바 정리를 하게 되었어요!

컬랙션, 객체 활용 등 부족하고 어려운 건 한두 가지가 아니었지만 그중에서 가장 먼저 잡아야겠다 싶었던 건 어설프게 아는 거가 아닐까 싶었습니다..

 

static을 쓰면 new로 안 만들고 main 메서드에서 바로 쓸 수 있지!
this는 생성자 만들 때 그 위에 있는 변수에 주입해줄 때 쓰지!

 

이렇게.. 너무 어설프게.. 물론 뜻은 전달이 되지만 Java CS 면접에서 저렇게 말하면 안 봐도 탈락이죠ㅠ '어설프게 아는 건 모르느니만 못하다'와 상황이 너무 똑같아서 제대로 정리해서 블로그로도 정리하고, GitHub로도 남겨서 내가 필요할 때 쓸 수 있는 나만에 바이블을 만들기로 다짐했습니다! 그중에서 오늘 알아볼 건 변수와 메서드입니다!

변수의 종류 - 클래스 변수, 인스턴스 변수, 지역변수

변수는 크게 다음과 같이 세 종류로 분류가 가능합니다! 이 세 가지를 구분시 가장 중요한 건 바로 변수의 선언 위치입니다!

멤버 변수를 제외한 나머지 변수들은 모두 지역변수이며, 멤버변수 중 static이 붙은 것은 클래스 변수, 붙지 않은 것은 인스턴스 변수입니다.

여기서 멤버 변수는 선언 위치가 클래스 영역(메서드 밖)인걸 말한답니다! 그럼 지역변수는? 메서드나 생성자 내부에서 사용하는 변수겠죠!

참고로 자바에는 전역 변수라는 개념이 존재하지 않습니다! 전역 변수는 멤버 변수와 비슷한 C언어에 개념입니다.(사실 이렇게 설명하면 안 되긴 하지만 간단히 읽고만 넘어가세요..)

 

public class VariableAndMethod {
    int num = 10;    // 인스턴스 변수
    static int sNum = 100;    // 클래스 변수, static 변수, 공유변수
    // method() 영역!
    public void method() {
        int mNum = 0;   // 지역변수
    }
}

 

1. 인스턴스 변수

정수형으로 선언된 변수 num이 바로 인스턴스 변수입니다!

class 영역에 선언되었으며, 인스턴스를 생성할 때 만들어집니다. 여기서 객체지향에서 인스턴스란, 클래스에 구현된 오브젝트(객체)를 뜻합니다. 인스턴스가 생성되어서 사용될 때 비로소 메모리에 할당됩니다. 인스턴스마다 고유한 상태를 유지해야 하는 경우에 선언됩니다.

2. 클래스 변수

클래스 변수를 선언하고 싶으면 간단합니다! static만 붙여주세요! 클래스 변수는 모두 공통된 저장공간을 공유하게 됩니다. 모든 인스턴스들이 공통적인 값을 유지해야 하면 클래스 변수로 선언합니다. 가장 큰 특징은 '클래스명. 클래스 변수명'으로 사용이 가능하다는 것이겠죠!

클래스가 로딩될 때 생성되어 프로그램이 종료될 때까지 유지됩니다. 즉, static을 남발하면 시스템 성능에 좋지 않다는 말이 되기도 합니다.

3. 지역 변수

메서드 내에 선언되어 메서드 안에서만 사용 가능하며, 메서드가 끝나면 그 즉시 사라집니다. for문이나 while문 안에 선언된 지역변수는 반복문 블록{} 안에서만 사용 가능하며, 반복문이 종료되면 그 즉시 소멸됩니다.

생긴 것도 알겠고 대충 어떻게 쓰는지도 알겠는데 이렇게만 하면 까먹겠죠? 코드를 직접 봅시다!

 

public static void main(String[] args) {
    VariableAndMethod vam1 = new VariableAndMethod();
    VariableAndMethod vam2 = new VariableAndMethod();

    System.out.println("instance vam1 > " + vam1.num + " class vam1 > " + VariableAndMethod.sNum);

    System.out.println("값을 재할당합니다.");
    vam1.num = 50;
    vam1.sNum = 11;

    System.out.println("instance vam1 > " + vam1.num + " class vam1 > " + vam1.sNum);
    System.out.println("instance vam2 > " + vam2.num + " class vam2 > " + vam2.sNum);
}

 

출력결과

 

다음과 같이 인스턴스를 생성해주고 출력해줬습니다. 첫째줄의 경우, vam1으로 인스턴스화 시킨 클래스를 참조하여 vam1.num()을 출력하였고, 클래스 변수는 그럴 필요가 없으므로 클래스명. sNum()을 출력했습니다.

>>> instance vam1 > 10 class vam1 > 100

 

그리고 아래줄에 값을 재할당해줍니다. vam1의 num은 50으로, vam1의 snum은 11로 할당해줬습니다. vam1.snum이 아닌 snum = 11;로 작성해도 되지만, 구분을 쉽게 하기 위해 vam1을 붙여줬습니다.

>>> instance vam1 > 50 class vam1 > 11

변경해준 값으로 제대로 출력된 걸 확인할 수 있습니다.

 

이번엔 vam2로 인스턴스화 시킨 클래스를 참조하여 출력해보겠습니다.

>>> instance vam2 > 10 class vam2 > 11

우잉? 이거 맞나요? vam2.num이 10이 나온 건 어찌 보면 당연합니다. 우리는 VariableAndMethod라는 클래스를 복사해서 vam2라는 변수명으로 참조시켜주니 위에서 재할당한 vam1.num에 값은 안 나오는 게 당연하죠. 완전히 다른 메모리를 참조하니깐요.

근데 이상합니다. vam2.sNum은 왜 그대로 11이 나올까요? vam2로 복사해서 참조한 건데 원래 값인 100이 나와야 하는 거 아닌가요?

아닙니다! 클래스 변수는 한번 생성되면 클래스 내에서 공유되는 성질을 가지고 있습니다!  같은 메모리를 참조하니 값을 변경된 대로 나오는 거죠! 그리고 우리는 구분을 쉽게 하기 위해 vam1.sNum, vam2.sNum을 사용했지만 실제로는 VariableAndMethod.sNum 이렇게 사용하니 구분이 더 쉽겠죠!

 

클래스 변수와 인스턴스 변수는 언제 써야 할까?

결론부터 말하면 모든 인스턴스가 공통적으로 같은 값을 유지해야 할 때 클래스 변수를 써야 합니다!

실무에서 쓰이는 코드를 한번 볼까요? 다음은 ArrayList를 연습하기 위해 만든 MemberDTO 코드입니다.(List, Set, Map 등에 컬랙션도 추후에 포스팅해보겠습니다! 지금은 그냥 이런 상황에서 쓰는구나 이해만 하시길 바랍니다! :) 모든 코드는 하단 깃 헙 주소를 참고 바랍니다!

 

public class Member {
    public static final int MALE = 0;
    public static final int FEMALE = 1;

    private String name;
    private int gender;
    private int age;

    public Member(String name, int gender, int age) {
        this.name = name;
        this.gender = gender;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getGender() {
        return gender;
    }

    public int getAge() {
        return age;
    }
}

 

아까 공부한 걸 실전 코드로 익혀볼까요? 

public static final인 클래스 변수로 MALE과 FEMALE을 선언했습니다. final은 한번 선언하면 다시 바꿀 수 없습니다. main 메서드에서 Member.MALE = 1; 혹은 Member.FEMALE = 0;으로 변경이 불가합니다.

name, gender, age는 private로 매개변수를 받는 생성자로만 값을 지정 가능하게 해 두었습니다.

생성자의 역할은 인스턴스를 초기화할 때 사용됩니다. 인스턴스화 시킬 때 값을 지정해주면 됩니다!

Getter만 생성하였는데, 이유는 값을 가져올 때만 사용하기 위해서입니다! 자세한 건 Getter/Setter 편에서 다뤄볼게요

 

import java.util.Arrays;
import java.util.List;

public class ExpressionMember {
    public static void main(String[] args) {
        List<Member> member = (List<Member>) Arrays.asList(
                new Member("빵부장", Member.MALE, 20),
                new Member("개발자", Member.MALE, 24)
        );

        int count = 0;
        double sum = 0;

        for (int i = 0; i < member.size(); i++) {
            if (member.get(i).getGender() == Member.MALE) {
                sum += member.get(i).getAge();
                count++;
            }
        }

        double ageAvg = sum/count;

        System.out.println("남자 회원의 평균나이: " + ageAvg);
    }
}

 

위 코드는 남자 회원의 평균 나이를 구하는 코드입니다.

ArrayList로 값을 받을 거고, member라는 변수명으로 선언했습니다. new Member(String name, int gender, int age) 값을 할당해주고, 그 값을 ArrayList에 넣었습니다. i로 member리스트에 Index를 가져와 Gender값 중 MALE에 해당하는 값만 가져와줬고, for문을 돌 때마다 한 번씩 count값을 증가시켰습니다. ageAvg는 나이의 합계와 count를 나눠서 평균값을 구했습니다.

위 코드를 이해하려고 하지 마시고, 이럴 때 클래스변수가 이럴때 인스턴스 변수가 쓰이는구나! 만 이해하셨으면 좋겠습니다! 참고로 위 코드에 출력 값은 다음과 같습니다.

>>> 남자 회원의 평균 나이: 22

 

https://github.com/babydev-by03KOREA/newJavaBasic