프로그래밍 언어/[ Java ]

[ Java ] 13. Java의 자료형 (Primitive type & Reference type)

kim.svadoz 2021. 6. 18. 17:43
반응형

Primitive type & Reference type


  • Primitive type에 대해 설명하라
  • Reference type에 대해 설명하라

자바에는 기본형(Privitive type)과 참조형(Reference type)이 있다.

일반적으로 다음처럼 분류가 된다.

Java Data Type 
ㄴ Primitive Type
    ㄴ Boolean Type(boolean)
    ㄴ Numeric Type
        ㄴ Integral Type
            ㄴ Integer Type(short, int, long)
            ㄴ Floating Point Type(float, double)
        ㄴ Character Type(char)
ㄴ Reference Type
    ㄴ Class Type
    ㄴ Interface Type
    ㄴ Array Type
    ㄴ Enum Type
    ㄴ etc.

 

Primitive type

기본형 타입

  • JAVA에서는 총 8가지의 Primitive type을 미리 정의하고 제공합니다.
  • JAVA에서 기본 자료형은 반드시 사용하기 전에 선언(Declared) 되어야 합니다.
  • OS에 따라 자료형의 길이가 변하지 않습니다.
  • 비객체 타입으로, NULL값을 가질 수 없습니다.
  • 만약 Primitive typeNULL값을 넣고 싶다면 Wrapper Class를 활용해야 합니다.
  • 스택(Stack) 메모리에 저장됩니다.

img

  • boolean
    • 논리형인 boolean의 기본값은 false이며 참과 거짓을 저장하는 타입입니다.
    • 주로 yes/no, on/off 등의 논리 구현에 사용되며 두 가지 값만 표현하므로 가장 크기가 작습니다.
    • boolean은 실제로 1bit면 충분하지만, 데이터를 다루는 최소 단위가 1byte 이므로 메모리 크기가 1byte입니다.
  • byte
    • byte는 주로 이진데이터를 다루는데 사용되는 타입입니다.
  • short
    • short는 C언어와의 호환을 위해 사용되는 타입으로 잘 사용되지 않는 타입입닏.ㅏ
  • int
    • int형은 자바에서 정수 연산을 하기 위한 기본 타입입니다.
    • 즉, byte 혹은 short의 변수가 연산을 하면 연산의 결과는 int형이 됩니다.
  • long
    • 수치가 큰 데이터를 다루를 프로그램(은행 및 우주와 관련된 프로그램)에서 주로 사용합니다.
    • long 타입의 변수를 초기화 할 때에는 정수값 뒤에 알파벳 L을 붙여서 long 타입(즉, 8byte)의 정수 데이터임을 알려주어야 합니다
    • 만일 정수값이 int의 값의 저장범위를 넘는 중시에서 L을 붙이지 않는다면 컴파일 에러가 발생합니다.
    • long l = 2147483648; // 컴파일 에러 발생 long l = 2147483648L;
  • float, double
    • 실수를 가수와 지수 형식으로 저장하는 부동소수점 방식으로 저장됩니다.
    • 가수를 표현하는 데 있어 double 형이 float형보다 표현 가능 범위가 더 크므로 보다 정밀하게 표현가능합니다.
    • 자바에서 실수의 기본 타입은 double 형이므로 float형에는 알파벳 F를 붙여서 float형임을 명시해주어야 합니다.
    • float f = 1234.567; // 무조건 double 타입으로 이해하려고 하므로 컴파일 에러가 발생합니다. float f = 1234.567F; // float type이라는 것을 표시해야 합니다.

Reference type

참조형 타입

  • JAVA에서 Primitive type을 제외한 타입들이 모두 Reference type입니다.
  • Reference type은 JAVA에서 최상인 java.lang.Object 클래스를 상속하는 모든 클래스들을 말합니다.
    • 물론 new로 인하여 생성하는 것들은 메모리 영역인 Heap 영역에 생성을 하게 되고, Garbage Collector가 돌면서 메모리를 해제합니다.
  • 클래스 타입(class type), 인터페이스 타입(interface type), 배열 타입(array type), 열거 타입(enum type)이 있습니다.
  • 빈 객체를 의미하는 NULL이 존재합니다.
  • 문법상으로는 에러가 없지만 실행시켰을 때 에러가 나는 런타임 에러가 발생합니다. 예를 들어 객체나 배열을 NULL값으로 받으면 NullPointException(NPE)이 발생하므로 변수 값을 넣어야 합니다.
  • Heap 메모리에 생성된 인스턴스는 메소드나 각종 인터페이스에서 접근하기 위해 JVM의 Stack 영역에 존재하는 Frame에 일종의 포인터(C의 포인터와는 다름!)인 참조값을 가지고 있어 이를 통해 인스턴스를 핸들링합니다.

img

 

String Class

클래스형 중에서도 String Class는 조금 특별합니다.

이 클래스는 참조형에 속하지만 기본적인 사용은 기본형처럼 사용합니다.

그리고 불변(immutable)하는 객체입니다.

 

String 클래스에는 값을 변경해주는 메소드들이 존재하지만 해당 메소드를 통해 데이터를 바꾼다 해도 새로운 String Class 객체를 만들어내는 것입니다.

일반적으로 기본형 비교는 == 연산자를 사용하지만 String 객체간의 비교는 .equals() 메소드를 사용해야 합니다.

String Classequals()가 오버라이딩 되어있고, StringBuilder(or StringBuffer) Classequals()가 오버라이딩되어 있지않습니다.

 

때문에, StringBuilder에서 sb1, sb2 두 인스턴스를 생성하게 되고 둘다 "abc"값을 주고 equals를 해보면 false가 나오고==를 사용하면 true가 나오게 됩니다.

StringBuilder sb1 = new StringBuilder("abc");
StringBuilder sb2 = new StringBuilder("abc");

boolean check = sb1.eqauls(sb2); // false // (주소값을 비교 (동등성))
boolean check = sb1 == sb2;        // true // (실제 값 비교(동일성))

String에서는 equals가 오버라이딩 되어있기 때문에, String str1.equals(str2)를 하게 되어도 true가 나오는 것입니다.

오버라이딩 하지 않은 equals는 주소를 비교하는거고 String에서는 equals를 오버라이딩 하여 값을 비교하게 만들어 준 것이죠.

String str1 = "abc";
String str2 = "abc";

boolean check = str1.equals(str2); // true // String class에서 equals가 오버라이딩 되었기 때문에 true
boolean check = str1 == str2;    // true //

 

++ 2021-11-29 추가내용

 

> String Constant Pool

동일하다(==)는 두 개의 실제 인스턴스가 완전히 같을 경우(메모리 주소값이 같음)이고
동등하다(eqauls)는 두 개의 값이 같다라는 의미이다.

String s1 = new String("aaa");
String s2 = new String("aaa");

System.out.println(s1 == s2); // false
System.out.println(s1.equlas(s2)); // true

위의 예에서 s1과 s2는 각각 new 연산자로 새로운 오브젝트 메모리에 생성되었기 때문에 두 주소는 당연히 동일하지 않습니다.

하지만 String 클래스는 eqauls 메소드가 오버라이딩 되어있으므로 같은 문자열이기 때문에 true가 출력됩니다.

 

하지만?

String s1 = "aaa";
String s2 = "aaa";

System.out.println(s1 == s2); // true
System.out.println(s1.equals(s2)); // true

자바에서는 String에게 new 연산자가 아니라 primitive 타입 변수를 선언하듯이 문법적으로 허락하고 있습니다.

그리고 실제로 값을 비교해보면 primitive 타입 변수와 같이 비교가 가능합니다.

자바에서는 이처럼 문자열 상수에 대해서 문자열이 동일한 경우 하나의 인스턴스만 생성하고 이를 공유하도록 한다.

이 때 문자열이 저장되는 곳이 바로 String Constant Pool입니다. 이렇게 생성된 String 값은 Heap 영역 내에 있는 String Constant Pool에 저장되어서 재사용됩니다.

 

하지만, new 연산자로 생성하면 같은 내영이라도 여러 개의 객체가 각각 Heap 영역을 차지하기 때문에 new 연산자로 생성하지 않는 것이 효율적입니다.

primitive type 처럼 생성하는 것을 String literal로 생성한다고 하는데, 이렇게 생성한 객체의 값(ex. "aaa")이 이미 String pool에 존재한다면 해당 객체는 String pool의 reference를 참조하기 때문에 s1과 s2는 같은 곳을 가리키고 있는 것입니다.

 

String Constant Pool의 위치는 Java6까지는 Perm 영역 이었지만 Java7에서 Heap영역으로 변경되었습니다. 그 덕분에 바로 String Constant Pool의 모든 문자열도 GC의 대상이 될 수 있게 되어 성능이 높아졌다고 할 수 있습니다.

Perm영역에 있었던 시절에는 String Pool에 문자열 객체가 많이 생성된다거나 이 영역이 가득 차게 되면 런타임 환경에서는 메모리를 동적으로 늘리지 못해 OutofMemory에러가 발생했습니다. 하지만 Java7 이후 부터는 OOM가 발생 위험을 줄였다고 합니다.

 

(new로 생성해도 일반 객체들과 마찬가지로 String Pool이 아닌 Heap의 영역에 생성된다.)

결론은 String 객체를 new 연산자로 생성하면 같은 값이라 할지라도 Heap영역에 매번 새로운 객체가 생성됩니다. 따라서 String이 갖는 불변성이라는 장점을 누리지 못해요!

따라서 메모리를 효율적으로 사용하기 위해서는 String literal(큰 따옴표)로 생성하는 것이 좋습니다!

 

Wrapper Class

기본 자료형(Primitive data type)에 대한 클래스 표현을 Wrapper Class라고 한다.

Integer, Float, Boolean 등이 있다.

int를 Integer라는 객체로 감싸서 저장해야 하는 이유가 있을까?

  • 일단 컬렉션에서 제네릭을 사용하기 위해서는 Wrapper Class를 사용해야 한다.
  • 또한, NULL값을 반환해야만 하는 경우에는 return type을 Wrapper Class로 지정하여 NULL을 반환할 수 있도록 할 수 있다.

하지만, 이러한 상황을 제외하고 일반적인 상황에서 Wrapper Class를 사용해야 하는 이유는 객체지향적인 프로그램을 위한 프로그래밍이 아니고서야 없다.

일단 해당 값을 비교할 때, Primitive data type인 경우에는 ==로 바로 비교할 수 있다.

하지만, Wrapper Class인 경우에는 .intValue() 메소드를 통해 해당 Wrapper class의 값을 가져와 비교해줘야 한다.

반응형