Starbucks Caramel Frappuccino
본문 바로가기
  • 그래 그렇게 조금씩
JAVA/Java Intermediate(OOP)

2. 기본형과 참조형 (Primitive, Reference), NullPointerException

by Toughie 2024. 2. 12.

 

 

기본형(Primitive Type)

변수에 사용할 값을 직접 넣을 수 있는 데이터 타입(리터럴 값 or 복사한 값)

 

정수형: byte, short, int, long

실수형: float, double

논리형: boolean

문자형: char

 

* String은 대문자 카멜케이스를 사용하는 것에서 유추할 수 있겠지만, Class이기 때문에 참조형이지만,

java에서 new 연산자 없이 기본 데이터 타입처럼 사용하도록 해주는 특별한 클래스이다. 

그렇기 때문에 내부 메서드가 다양한 것이다.

(new 키워드를 통해 문자열을 생성하면 힙에 각각 생성된다 -> 문자열 상수 풀을 활용하지 못함!)

 

연산

- 변수 자체에 값이 있기 때문에 연산이 가능하다.

변수 대입

- 기본형의 경우 변수에 들어 있는 실제 값 자체를 복사해서 대입한다.

새로운 인스턴스를 찍어내는 것이 아니면, 참조값만 복사하면 동일한 객체를 가리킨다는 것이다.

(힙에 있는 객체에 대한 주소를 가진 변수가 여러개 있을 수 있다. 접근 경로만 다양해 진다는 뜻)

 

메서드 호출

메서드 파라미터로 값이 전달될 때도 당연히 값이 복사되어서 들어간다.

원본 변수에 return값을 재할당 하지 않는 이상, 메서드 내부에서 파라미터의 변화는 원본과는 상관이 없다!


참조형(Reference Type)

기본형을 제외한 모든 타입, 객체가 저장된 메모리의 위치를 가리키는(참조하는) 참조값(주소)을 넣을 수 있는 타입

대문자 카멜케이스를 사용한다. (Class)

연산

- 변수에는 값이 아닌 주소가 있기 때문에 직접 연산은 불가능하다.

-> .(dot)문법을 통해 데이터에 접근하면 연산이 가능하다.

변수 대입

- 참조형의 경우 실제 객체가 아니라 객체의 주소인 참조값이 복사돼서 대입된다.

MyClass sampleA = new MyClass(); //@30f39991
MyClass sampleB = sampleA; //@30f39991

 

메서드 호출

https://all-young.tistory.com/17

 

    public static void main(String[] args) {
        Data locationA = new Data();
        locationA.value = 10;

        System.out.println(locationA); //@30f39991
        System.out.println("locationA의 Value: " + locationA.value); //10
        changeValue(locationA);
        System.out.println("locationA의 Value: " + locationA.value);//100
    }
    
    public static void changeValue(Data x) {
        System.out.println(x); //@30f39991
        x.value = 100;
    }

 

Data 클래스의 인스턴스를 생성하고, 해당 참조값이 locationA에 들어있다.

이후 locationA가 가리키는 Data 클래스 인스턴스의 멤버변수인 value에 10을 할당해줬다.

changeValue 메서드는 Data타입의 파라미터를 받는다.

 

changeValue 호출 시 locationA를 전달하고 이는 heap 영역의 Data 클래스 인스턴스의

주소가 복사돼서 전달되는 것이다. (스택 프레임이 형성된다)

메서드 내부에서 참조값을 통해 heap 영역의 Data 클래스 인스턴스를 찾아가서 값을 변경한다.

따라서 메서드 호출이 끝나 스택프레임이 소멸하면 locationA.value가 100으로 변경되는 것이다.

 

참조값과 메서드 활용 예시

    static void printStudent(Student student) {
        System.out.printf("이름: %s, 나이: %d, 점수: %d", student.name, student.age, student.score);
    }
    
    static void initStudent(Student student, String name, int age, int score) {
        //객체 생성 이후 멤버 변수 초기화 작업 (값 할당)
        student.name = name;
        student.age = age;
        student.score = score;
    }
    
    static Student newStudent(String name, int age, int score) {
        Student newStudent = new Student(); // heap에 객체 생성후 참조값을 지역변수에 초기화
        initStudent(newStudent, name, age, score); // 객체 멤버 값 할당
        return newStudent; //객체 참조값 반환
    }

 

멤버 변수의 자동 초기화

인스턴스의 멤버 변수는 생성 시 자동으로 초기화된다.

int: 0 / boolean: false / char: 빈문자 / 참조형: null

 

* 지역변수(메서드 내부 변수)는 직접 초기화 해줘야한다.

 

public class AutoInit {
    int value1; double value2; char value3; boolean value4; String value5; int value6 = 7;
}
    public static void main(String[] args) {
        AutoInit auto = new AutoInit();
        System.out.println(
                "value1: " + auto.value1 + " value2: " + auto.value2 + " value3: " + auto.value3 +
                        " value4: " + auto.value4 + " value5: " + auto.value5 + " value6: " + auto.value6);
        //value1: 0 value2: 0.0 value3:  value4: false value5: null value6: 7
    }

 


Null

값이 존재하지 않는것을 나타낸다. (Swift의 nil, python의 none 등)

값이나 참조값을 나중에 할당하고 싶은 경우에 사용할 수 있다. 

다만 java에서는 참조형에서만 해당된다. 즉 가리키는 대상이 없다!

 

    public static void main(String[] args) {
        Data data = null;
        System.out.println("data = " + data); //null
        data = new Data();
        System.out.println("data = " + data); //@30f39991
        data = null;
    }

 

위 코드대로면 data객체는 힙에 남아있는데 참조할 수단이 사라졌다. 즉 힙에 둥둥 떠다니며 메모리 누수를 일으킬 것이다..

이를 방지하기 위해 자바에는 GC (Garbage Collection)이 존재한다.

 

Swift에서 ARC와 동일한 개념이라고 보면 될 것 같다.

https://toughie-ios.tistory.com/218

 

ARC, weak self, 캡쳐리스트

스위프에서 메모리 관리는 'ARC'를 통해서 이루어진다. ARC(Automatic Reference Counting) ARC는 객체에 대한 강한 참조(Strong Reference) 횟수를 추적하여 메모리에서 해당 객체를 해제하는 데 사용된다. 객체

toughie-ios.tistory.com

즉 인스턴스를 아무도 참조하지 않으면 (Reference Counting이 0이면) JVM의 GC가 해당 인스턴스를 메모리에서 해제시켜준다.

물론 강한순환참조(Strong reference) 로 인한 메모리 누수는 여전히 신경써야 한다. GC가 해제를 못할테니.

(C에서는 인스턴스를 꼭 직접 해제시켜줘야 한다 ㅎㅎ 곧 포인터 배우면서 자세히 다뤄볼 예정)

 

NullPointerException

null을 가리켜서 발생하는 예외! 

Swift에서 이를 방지하기 위해 if let, guard let 바인딩, 닐 코얼레싱 등을 했던 것이 떠오른다.

쉽게는 null에 .(dot)을 통해 접근하려 할 때 발생하는 예외이다.

    public static void main(String[] args) {
        Data data = null;
        //Exception in thread "main" java.lang.NullPointerException:
        // Cannot assign field "value" because "data" is null
        data.value = 10;
    }

 * 메인 메서드는 일반적으로 메인 스레드에서 실행되며, 이 스레드는 JVM에 의해 실행되고 프로그램의 진입점 역할을 한다.

 

 

 


학습 출처

김영한의 실전 자바 - 기본편

'JAVA > Java Intermediate(OOP)' 카테고리의 다른 글

5. 접근 제어자와 캡슐화  (0) 2024.03.03
4. 생성자 Construct  (0) 2024.03.02
3. 절차 지향 vs 객체 지향(OOP)  (0) 2024.02.23
1. 클래스 (Class)  (0) 2024.02.10
0. OOP의 세계로  (0) 2024.02.10