JAVA

JAVA - 기본형과 참조형

jonghyeon6084 2024. 1. 2. 16:59
728x90

1. 기본형 vs 참조형 - 시작

자바에서 참조형을 제대로 이해하는 것은 정말 중요하다.

기본형 vs 참조형 - 기본

  • 기본형은 숫자 10, 20과 같이 실제 사용하는 값을 변수가 담을 수 있다. 그래서 해당 값을 바로 사용할 수 있다.
  • 참조형은 실제 사용하는 값을 변수에 담는 것이 아니다. 이름 그대로 실체 객체의 위치(참조, 주소)를 저장한다. 참조형에는 객체와 배열이 있다.
    • 객체는 .(dot)을 통해서 메모리 상에 생성된 객체를 찾아가야 사용할 수 있다.
    • 배열은 []을 통해서 메모리 상에 생성된 배열을 찾아가야 사용할 수 있다.

기본형 vs 참조형 - 계산

  • 기본형은 들어있는 값을 그대로 계산에 사용할 수 있다.
    • 예) 더하고 빼고, 사용하고 등등(숫자 같은 것들은 바로 계산할 수 있음)
  • 참조형은 들어있는 참조값을 그대로 사용할 수 없다. 주소지만 가지고는 할 수 있는 게 없다. 주소이제 가야 실체가 있다.
    • 예) 더하고 빼고 사용하고 못함. 참조값만 가지고는 계산할 수 있는 것이 없음.
// 기본형은 연산이 가능
int a = 10, b = 20;
int sum = a + b;
Student s1 = new Student();
Student s2 = new Student();
s1 + s2; // 오류 발생

 

참고

 - 자바에서 String은 매우 특별하다. String은 사실 클래스이다. 따라서 참조형이다. 그런데 기본형처럼 문자 값을 바로 대입할 수 있다. 문자는 매우 자주 다루기 때문에 자바에서 특별하게 편의 기능을 제공한다. 자세한 내용은 뒤에서 설명하기로.

2. 기본형 vs 참조형 - 변수 대입

대원칙 : 자바는 항상 변수의 값을 복사해서 대입한다.

기본형과 변수 대입

int a = 10;
int b = a;

System.out.println("a = " + a);
System.out.println("b = " + b);

//a 변경
a = 20;
System.out.println("변경 a = 20");
System.out.println("a : " + a);
System.out.println("b : " + b);

//b 변경
b = 30;
System.out.println("변경 b = 30");
System.out.println("a : " + a);
System.out.println("b : " + b);

 

이것은 앞에서 배웠다시피 쉽게 이해할 수 있다.

참조형과 변수 대입

Data dataA = new Data();
dataA.value = 10;
Data DataB = dataA;

System.out.println("dataA 참조값 = " + dataA);
System.out.println("DataB 참조값 = " + DataB);
System.out.println("dataA.value = " + dataA.value); // 10
System.out.println("DataB.value = " + DataB.value); // 10

//dataA 변경
dataA.value = 20;
System.out.println("변경 dataA.value = 20");
System.out.println("dataA.value = " + dataA.value); // 20
System.out.println("DataB.value = " + DataB.value); // 20

//dataB 변경
dataA.value = 30;
System.out.println("변경 dataB.value = 30");
System.out.println("dataA.value = " + dataA.value); // 30
System.out.println("DataB.value = " + DataB.value); // 30

여기서 차이가 보인다. 

 

Data dataA = new Data();
dataA.value = 10;
Data DataB = dataA;

 

이 부분에서 DataA와 DataB의 참조값이 일치하므로, 둘 중에 하나만 바꿔도 둘의 참조값은 일치하므로 둘 다 바뀌게 된다.

3. 기본형 vs 참조형 3 - 매서드 호출

기본형과 메서드 호출

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;
}

기본형은 말 그대로 그 값을 복사하는 것이기 때문에 

 

changePrimitive(a)를 하더라도 a의 값인 10을 복사해서 해당 메서드에 넣는 것이므로 변수 a와는 아무런 상관이 없다.

참조형과 메서드 호출

public static void main(String[] args) {
    Data dataA = new Data();
    dataA.value = 10;
    System.out.println("메서드 호출 전 : dataA.value = " + dataA.value);
    changeReference(dataA);
    System.out.println("메서드 호출 후 : dataA.value = " + dataA.value);
}

static void changeReference(Data dataX) {
    dataX.value = 20;
}

참조형은 변수에 저장된 참조값(주소)을 복사해서 가져가는 것이기 때문에 

 

changeReference 메서드를 통해 참조값을 통해 객체에 접근해서 그 안에 있는 value값을 20으로 변경했기 때문에 

 

호출 후 dataA.value는 20으로 바뀐다.

4. 참조형과 메서드 호출 - 활용

Student student1;
student1 = new Student();
student1.name = "학생1";
student1.age = 15;
student1.grade = 90;

Student student2;
student2 = new Student();
student2.name = "학생2";
student2.age = 16;
student2.grade = 80;

System.out.println("이름 : " + student1.name + " 나이 : " + student1.age + " 성적 : " + student1.grade);
System.out.println("이름 : " + student2.name + " 나이 : " + student2.age + " 성적 : " + student2.grade);

예전에 작성했던 코드인데, 여기서 보면 학생의 name, age, grade가 계속해서 중복된다.

 

메서드를 통해 중복코드를 제거할 수 있다.

public class Student {
    String name;
    int age;
    int grade;
}
public static void main(String[] args) {
    Student student1 = creatStudent("학생1", 15, 90);
    Student student2 = creatStudent("학생2", 16, 80);

    printStudent(student1);
    printStudent(student2);
}

static Student creatStudent(String name, int age, int grade) {
    Student student = new Student();
    student.name = name;
    student.age = age;
    student.grade = grade;

    return student;
}

static void initStudent(Student student, String name, int age, int grade) {
    student.name = name;
    student.age = age;
    student.grade = grade;
}

static void printStudent(Student student) {
    System.out.println("이름 : " + student.name + ", 나이 : " + student.age
            + ", 성적 : " + student.grade);
}

5. 변수와 초기화

변수의 종류

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

변수의 값 초기화

  • 멤버 변수 : 자동 초기화
    • 인스턴스의 멤버 변수는 인스턴스를 생성할 때 자동으로 초기화된다.
    • 숫자(int) = 0, boolean = false, 참조형 = null(null 값은 참조할 대상이 없다는 뜻으로 사용된다.)
    • 개발자가 초기값을 직접 지정할 수도 있다.
  • 지역 변수 : 수동 초기화
    • 지역 변수는 항상 직접 초기화해야 한다.
public class InitData {
    int value1; // 초기화 하지 않음
    int value2 = 10; // 10으로 초기화
}
InitData data = new InitData();
System.out.println("data.value1 = " + data.value1); // 0
System.out.println("data.value2 = " + data.value2); // 10

6. null

 -택배를 보낼 때 제품은 준비가 되었지만, 보낼 주소지가 아직 결정되지 않아서, 주소지가 결정될 때까지는 주소지를 비워두어야 할 수 있다. 참조형 변수에는 항상 객체가 있는 위치를 가리키는 참조값이 들어간다. 그런데 아직 가리키는 대상이 없거나, 가리키는 대상을 나중에 입력하고 싶다면 어떻게 해야 할까?? 참조형 변수에서 아직 가리키는 대상이 없다면 'null'이라는 특별한 값을 넣어둘 수 있다. 'null' 은 값이 존재하지 않는, 없다는 뜻이다.

null 값 할당

public class Data {
    int value;
}
Data data = null;
System.out.println("1. data = " + data); //null
data = new Data();
System.out.println("2. data = " + data); // ref.Data@30f39991
data = null;
System.out.println("3. data = " + data); // null

위처럼 처음에는 data의 참조값이 null이었다가, 새로운 객체 생성을 해주니 해당하는 참조값이 생기지만, 그 이후 null 처리를 해버리면 해당 메모리를 찾을 수 없는 상태가 되어 버린다.

 

그럼 여기서 찾을 수 없는 메모리를 처리하는 방법은??

 

GC - 아무도 참조하지 않는 인스턴스의 최후

data에 null을 할당했다. 따라서 앞서 생성한 'x001(예시 참조값)' 'Data' 인스턴스를 더는 아무도 참조하지 않는다. 이렇게 아무도 참조하지않게되면 'x001' 이라는 참조값을 다시 구할 방법이 없다. 따라서 해당 인스턴스에 다시 접근할 방법이 없다.
이렇게 아무도 참조하지 않는 인스턴스는 사용되지 않고 메모리 용량만 차지할 뿐이다.

C와 같은 과거 프로그래밍 언어는 개발자가 직접 명령어를 사용해서 인스턴스를 메모리에서 제거해야 했다. 만약 실수로 인스턴스 삭제를 누락하면 메모리에 사용하지 않는 객체가 가득해져서 메모리 부족 오류가 발생하게 된다.
자바는 이런 과정을 자동으로 처리해준다. 아무도 참조하지 않는 인스턴스가 있으면 JVMGC(가비지 컬렉션)가 더 이상 사용하지 않는 인스턴스라 판단하고 해당 인스턴스를 자동으로 메모리에서 제거해준다.

7. NullpointException

택배를 보낼 때 주소지 없이 택배를 발송하면 어떤 문제가 발생할까? 만약 참조값 없이 객체를 찾아가면 어떤 문제가 발생할까? 이 경우 'NullPointerException' 이라는 예외가 발생하는데, 개발자를 가장 많이 괴롭히는 예외이다. 'NullPointerException' 은 이름 그대로 'null' 을 가리키다(Pointer)인데, 이때 발생하는 예외(Exception). 'null' 은 없다는 뜻이므로 결국 주소가 없는 곳을 찾아갈 때 발생하는 예외이다.

public class Data {
    int value;
}
public class BigData {
    Data data; // null
    int count; // 0
}
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' 으로 초기화된다.