Type

ymk·2023년 6월 8일
0
post-thumbnail

💾 기본형(primitive type)과 참조형(reference type)

자료형은 크게 기본형(primitive type)과 참조형(reference type)으로 나눌 수 있는데, 기본형 변수는 실제 값(data)를 저장하고, 참조형 변수는 어떤 값이 저장되어 있는 주소(memory address)를 값으로 갖는다.

  • 기본형(Primitive type)
    논리형(boolean), 문자형(char), 정수형(byte, short, int, long), 실수형(float, double) - 8개, 계산을 위해 실제 값을 저장한다.

  • 참조형(reference type)
    객체의 주소를 저장한다. 8개의 기본형을 제외한 나머지 타입.

참조변수를 선언하는 방법은 다음과 같다. 기본형 변수와 같이 변수 이름 앞에 타입을 적어주는데 참조변수의 타입은 클래스의 이름이다.
클래스이름 변수이름; // 변수의 타입이 기본형이 아니면 모두 참조변수이다.

예를 들어 다음은 Date 클래스 타입의 참조변수 today를 선언한 것이다. 참조변수는 null 또는 객체의 주소를 값으로 갖으며 참조변수의 초기화는 다음과 같이 한다.
Date today = new Date(); // Date 객체를 생성하고 그 주소를 today에 저장
-> 연산자 new를 통해서 객체를 생성하고 그 주소를 반환한다.


📧 Wrapper Class

객체지향 개념에서 모든 것은 객체로 다루어져야 하는데 자바의 8개 기본형은 객체로 다루지 않는다. 대신 보다 높은 성능을 얻을 수 있었다. 하지만 기본형변수도 객체로 다루어야할 경우가 생긴다. 이때는 기본형 값들을 객체로 변환하여 작업을 수행하여야 한다.
이때 사용하는 것이 래퍼(Wrapper)클래스이다.

래퍼 클래스의 생성자는 매개변수로 문자열이나 각 자료형의 값들을 인자로 받는다. 주의해야 할 점은 문자열을 사용할때는 각 자료형에 알맞은 문자열을 사용해야하는 것이다. new Integer("3.0"); 와 같이 하면 NumberFormatException이 발생한다.

래퍼 클래스들은 모두 equals()가 오버라이딩되어 있어서 주소값이 아닌 객체가 가지고 있는 값을 비교한다. 또한 toString()도 오버라이딩되어 있어서 객체가 가지고 있는 값을 문자열로 변환해 줄 수도 있다. 이 외에도 래퍼 클래스들은 MAX_VALUE, MIN_VALUE, SIZE, BYTES, TYPE등의 static 상수를 공통적으로 가지고 있다.

오토박싱, 언박싱 (autoboxing, unboxing)

자바 1.5 이전에는 기본형과 참조형간의 연산이 불가능했다.

int i = 5;
Integer iObj = new Integer(10);

int sum = i + iObj; // -> 자바 1.5이전은 에러

하지만 이제는 가능해졌다. 컴파일러가 자동으로 코드를 넣어주기 때문이다. 예를 들어, 위의 코드를 컴파일 하면 Integer 객체를 int 타입으로 변환해주는 intValue()를 추가해준다.

int i = 5;
Integer iObj = new Integer(10);

int sum = i + iObj.intValue(); // 컴파일 후

기본형 값을 래퍼 클래스의 객체로 자동 변환해주는 것을 오토박싱(autoboxing) 이라고 하며, 그 반대로 변환하는 것을 언박싱(unboxing) 이라고 한다.


📢 Call by Reference VS Call by Value

언어들마다 메서드 매개변수 호출 방식에 차이가 있다. Call by reference는 C++에서 참조 개념이 도입된 이후 가능하게 되었다.
Call by value는 함수의 인자를 전달할때 값을 전달하는 방식이며,
Call by reference주소를 전달하는 방식이다.

자바에서는 항상 Call by Value 방식을 사용한다.

Call by Value 예시

void calculate(int a) {
	a += 10;
}

void main() {
	int a = 10;
    calculate(a);
    printf("a = &d", a); // a = 10
    return 0;
}

calculate 함수에서 새로운 변수가 생성되었기 때문에 main함수의 a와는 완전히 다른 변수다. 따라서 함수 호출 이후에 실인자 a의 값은 변하지 않는다.


Call by Reference 예시

void plus(int &a) {
	a += 10;
}

int main() {
	int a = 10;
	plus(a);
    printf("a = &d", a); // a = 20

	return 0;
}

C 언어가 공식적으로는 Call by Reference를 지원하는 것은 아니지만 의미적, 결과적으로 Call by Address를 이용하여 Call by Reference와 같이 사용할 수 있다. 그래서 이렇게 포인터를 넘겨주는 방식으로 설명할 수 있다.

☕ 자바는 항상 Call by Value?

위에서 말했지만 자바는 항상 Call by Value를 사용한다. 자바로 프로그래밍을 하다보면 객체를 넘기고 그 객체를 수정하면 원본도 수정되어 Call by reference로 착각하는 경우가 많다. 아래의 코드는 Stack overflow에서 가져온 예시 코드들이다.

public static void main(String[] args) {
        Dog aDog = new Dog("Max");
        Dog oldDog = aDog;

        foo(aDog);
        // dog의 이름이 "Fifi"로 변경
        
        aDog.getName().equals("Fifi"); // true
        // 계속 같은 dog를 가리킨다
        aDog == oldDog; // true
    }

    public static void foo(Dog d) {
        d.getName().equals("Max"); // true
        d.setName("Fifi");
    }

하지만 foo함수에서 new를 통해 변수 d에 새로운 객체를 향하게하면 어떻게 될까? Call by Reference라면 d는 aDog의 주소값이므로 aDog는 새로운 객체를 가리켜야 할 것이다.

public static void main(String[] args) {
        Dog aDog = new Dog("Max");
        Dog oldDog = aDog;

        foo(aDog);
        // foo(...)이후 aDog 변수는 아직 "Max"라는 Dog를 향하고 있다.

        aDog.getName().equals("Max"); // true
        aDog.getName().equals("Fifi"); // false -> Call by Reference가 아니기 때문이다.
        System.out.println(aDog == oldDog); // true
    }

    public static void foo(Dog d) {
        d.getName().equals("Max"); // true
        d = new Dog("Fifi");
        d.getName().equals("Fifi"); // true
    }

결과를 보면 객체 자체의 교체는 일어나지 않는다. 결국 인자로 넘어가는 것은 실제 주소값이 아닌 주소값에 대한 복사본이 넘어가는 것이므로 자바는 Call by Value를 사용하는 것이다.


Reference

Java의 정석 - 3rd Edition by. 남궁 성

https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value

https://deveric.tistory.com/92

https://m.blog.naver.com/PostView.naver?blogId=heartflow89&logNo=220975218499&proxyReferer=

profile
개발 공부 일지

0개의 댓글