본문 바로가기

Java

[Java] 변수의 초기화, null

변수의 종류

  • 멤버 변수(필드): 클래스에 선언
  • 지역 변수: 메서드에 선언, 매개변수도 지역변수의 한 종류이다.

멤버 변수, 필드 예시

public class Student{
    String name;
    int age;
    int grade;
}

 

name, age, grade는 멤버 변수이다.

 

지역 변수 예시

public class ClassStart3 {

    public static void main(String[] args) {

        Student student1; 
        student1 = new Student();
        Student student2 = new Student();

 

student1, student2 는 지역 변수이다.

public class MethodChange1 {

    public static void main(String[] args) {
        int a = 10;
        System.out.println("메서드 호출 전: a = " + a);
        changePrimitive(a);
        System.out.println("메서드 호출 후: a = " + a);
    }

    static void changePrimitive(int x) {
        x = 20;
    }
}

 

a, x(매개변수)는 지역 변수이다.

지역 변수는 이름 그대로 특정 지역에서만 사용되는 변수라는 뜻이다.

예를 들어서 변수 x는 changePrimitive( ) 메서드의 블록에서만 사용된다.

changePrimitive( ) 메서드가 끝나면 제거된다. 

a변수도 마찬가지이다. main( ) 메서드가 끝나면 제거된다.

 

변수의 값 초기화

멤버 변수: 자동 초기화

  • 인스턴스의 멤버 변수는 인스턴스를 생성할 때 자동으로 초기화 된다.
  • 숫자(int) => 0,  boolean => false,  참조형 => null (null은 참조할 대상이 없다는 뜻으로 사용된다)
  • 개발자가 초기값을 직접 지정할 수 있다.

지역 변수: 수동 초기화

  • 지역 변수는 항상 직접 초기화 해야 한다.

멤버 변수

public class InitData {
    int value1; // 초기화 하지 않음
    int value2 = 10; // 10으로 초기화
}
public class InitMain {

    public static void main(String[] args) {
        
        InitData data = new InitData();
        System.out.println("value1 = " + data.value1);
        System.out.println("value2 = " + data.value2);
    }
}
value1 = 0
value2 = 10

 

value1은 초기값을 지정하지 않았지만 멤버 변수는 인스턴스를 생성할 때 자동으로 초기화 된다. (숫자는 0)

value2는 10으로 초기값을 지정해두었기 때문에 객체를 생성할 때 10으로 초기화 된다.

 

null

택배를 보낼 때 제품은 준비가 되었지만, 보낼 주소지가 아직 결정되지 않아서,

주소지가 결정될 때 까지는 주소지를 비워두어야 할 수 있다.

참조형 변수에는 항상 객체가 있는 위치를 가르키는 참조값이 들어간다.

그런데 만약 아직 가리키는 대상이 없거나, 가리키는 대상을 나중에 입력하고 싶다면 어떻게 해야 될까요?

 

public class Data {
    int value; // 멤버 변수(필드)
}
public class NullMain1 {

    public static void main(String[] args) {

        Data data = null;
        System.out.println("1. data = " + data);
        data = new Data();
        System.out.println("2. data = " + data);
        data = null;
        System.out.println("3. data = " + data);
    }
}
1. data = null
2. data = ref.Data@1d81eb93
3. data = null

 

 

Data data = null;

 

Data 타입을 받을 수 있는 참조형 변수 data를 만들었다.

그리고 여기에 null 값을 할당했다.

따라서 data 변수에는 아직 가리키는 객체가 없다는 뜻이다.

data = new Data();

 

이후에 새로운 Data 객체를 생성해서 그 참조값을 data 변수에 할당했다.

이제 data 변수가 참조할 객체가 존재한다.

data = null;

 

마지막에는 data에 다시 null값을 할당했다.

이렇게 하면 data 변수는 앞서 만든 Data 인스턴스를 더는 참조하지 않는다.

 

GC(가비지 컬렉션)

data
null을 할당했다. 따라서 앞서 생성한 x001 Data 인스턴스를 더는 아무도 참조하지 않는다.
이렇게 아무도 참조하지 않게 되면 x001 이라는 참조값을 다시 구할 방법이 없다.
따라서 해당 인스턴스에 다시 접근할 방법이 없다. 이렇게 아무도 참조하지 않는 인스턴스는 사용되지 않고
메모리 용량만 차지할 뿐이다. C언어 같은 프로그래밍 언어는 개발자가 직접 명령어를 사용해서 인스턴스를
메모리에서 제거해야 했다. 만약 인스턴스 삭제를 누락하면 메모리에 사용하지 않는 객체가 가득해져서
메모리 부족 오류가 발생하게 된다. 자바는 이런 과정을 자동으로 처리해준다.
아무도 참조하지 않는 인스턴스가 있다면 JVM의 GC(가비지 컬렉션)가 더 이상 사용하지 않는 인스턴스라 판단하고 해당 인스턴스를 자동으로 메모리에서 제거해준다.

객체는 해당 객체를 참조하는 곳이 있으면, JVM이 종료할 때 까지 계속 생존한다.
그런데 중간에 해당 객체를 참조하는 곳이 모두 사라지면 그 때 JVM은 필요 없는 객체라고 판단하고
GC(가비지 컬렉션)를 사용해서 제거한다.

 

 

NullPointerException

택배를 보낼 때 주소지 없이 택배를 발송하면 어떤 문제가 발생할까?

만약 참조값이 없이 객체를 찾아가면 어떤 문제가 발생할까?

이 경우 NullPointerException 이라는 예외가 발생하는데, 개발자들을 가장 많이 괴롭히는 예외이다.

NullPointerException 은 이름 그대로 null 을 가르키다(Pointer) 인데, 이때 발생하는 예외(Exception) 이다.

null 은 없다는 뜻이므로 결국 주소가 없는 곳을 찾아갈 때 발생하는 예외이다.

 

객체를 참조할 때는 .(점)을 사용해서 해당 객체를 찾아갈 수 있다.

그런데 참조값이 null 이라면 값이 없다는 뜻이므로, 찾아갈 수 있는 객체(인스턴스)가 없다.

NullPointerException 은 이처럼 null 에 ·(점)을 찍었을 때 발생한다.

public class NullMain2 {

    public static void main(String[] args) {

       Data data = null;
       data.value = 10; // NullPointerException 예외 발생
        System.out.println("data = " + data.value);
    }
}

 

data 참조형 변수에는 null값이 들어가 있다. 그런데 data.value = 10 이라고 하면 어떻게 될까?

data.value = 10
null.value = 10 // data에는 null 값이 들어있다.

 

결과적으로 null 값은 참조할 주소가 존재하지 않는다는 뜻이다. 따라서 참조할 객체 인스턴스가 존재하지 않으므로

다음과 같이 java.lang.NullPointerException이 발생하고, 프로그램이 종료된다. 

참고로 예외가 발생했기 때문에 그 다음 로직은 수행되지 않는다.

 

 

멤버변수와 null

앞선 예제와 같이 지역 변수의 경우에는 null 문제를 파악하는 것이 어렵지 않다.

다음과 같이 멤버 변수가 null인 경우에는 주의가 필요하다.

public class Data {
    int value; // 멤버 변수(필드)
}
public class BigData {
    Data data;
    int count;
}
public class NullMain3 {

    public static void main(String[] args) {

        BigData bigData = new BigData(); // 객체를 생성하며 초기화
        System.out.println("bigData.count = " + bigData.count);
        System.out.println("bigData.data = " + bigData.data);
        //NullPointerException
        System.out.println("bigData.data.value = " + bigData.data.value);
    }
}

 

BIgData를 생성하면 BigData의 인스턴스가 생성된다. 이때 BigData 인스턴스 멤버 변수에 초기화가 일어나는데,

BigData data 멤버 변수는 참조형이므로 null로 초기화 된다. count 멤버 변수는 숫자이므로 0으로 초기화 된다.

 

  • bigData.count을 출력하면 0이 출력된다.
  • bigData.data을 출력하면 null이 출력된다. 이 변수는 아직 아무것도 참조하고 있지 않다.
  • bigData.data.value를 출력하면 data의 값이 null이므로 null 에 .(점)을 찍게 되고, 따라서 참조할 곳이 없으므로 NullPointerException 예외가 발생한다.

예외 발생 과정

bigData.data.value
x001.data.value // bigdata는 x001의 참조값을 가진다.
null.data.value // x001.data는 null값을 가진다.
NullPointerException // null값에 .(점)을 찍으면 예외가 발생한다.

 

 

이 문제를 해결하려면 Data 인스턴스를 만들고 BigData.data 멤버 변수에 참조값을 할당하면 된다.

public class NullMain4 {

    public static void main(String[] args) {

        BigData bigData = new BigData();
        bigData.data = new Data(); // value 초기화
        System.out.println("bigData.count = " + bigData.count);
        System.out.println("bigData.data = " + bigData.data);
        System.out.println("bigData.data.value = " + bigData.data.value);
    }
}
bigData.count = 0
bigData.data = ref.Data@312b1dae
bigData.data.value = 0

 

실행 과정

bigData.data.value
x001.data.value // bigData는 x001 참조값을 가진다.
x002.value // x001.data는 x002 값을 가진다.
0 // 최종결과

 

정리

 

NullPointerException이 발생하면 null 값에 .(점)을 찍었다면 문제를 쉽게 찾을 수 있다.