[Naver Cloud Camp 7] 교육 정리

네이버 클라우드 캠프 26일차 230531

우기37 2023. 5. 31. 21:04

#1 교육정리

1) 레퍼런스(reference) & 인스턴스(instance)

2) 가비지(garbage)

3) 형변환

 

 

 

#2 레퍼런스(Reference) & 인스턴스(Instance)

자바에서 레퍼런스란?)

레퍼런스는 객체를 가리키는 변수입니다. 자바에서 객체는 힙(heap) 메모리에 할당되고, 그 객체를 조작하기 위해서는 객체를 가리키는 레퍼런스를 사용합니다. 레퍼런스는 객체의 주소를 저장하고, 이 주소를 통해 객체에 접근하고 조작할 수 있게 해줍니다. 레퍼런스는 객체의 타입에 따라 선언되며, 해당 객체를 가리키기 위해 사용됩니다.

 

 

자바에서 인스턴스란?)

인스턴스는 클래스를 기반으로 생선된 객체를 의미합니다. 클래스는 객체의 설계도이며, 인스턴스는 그 설계도를 기반으로 생성된 실제 객체입니다. 인스턴스는 힙 메모리에 할당되며, 클래스의 멤버 변수와 메서드에 대한 실제 데이터와 동작을 갖게 됩니다.

 

 

 

 

 

#3 가비지(garbage)

자바에서 가비지란?)

자동 메모리 관리 기능을 제공하는 메커니즘입니다. 가비지 컬렉션은 동적으로 할당된 메모리 영역에서 더 이상 사용되지 않는 객체들을 자동으로 탐지하고 제거하여 프로그래머가 메모리 관리에 대한 직접적인 책임을 덜어줍니다.

 

가비지 컬렉션은 영어로 Garbeage Collection으로 줄여서 GC라고도 부릅니다. 가비지 컬렉션은 자바의 메모리 관리 방법 중의 하나로 JVM의 Heap 영역에서 동적으로 할당했던 메모리 영역 중 필요 없게 된 메모리 영역을 주기적으로 삭제하는 프로세스를 말합니다.

 

자바의 가비지 컬렉션은 메모리 누수(memory leaks)나 더블 포인터(double pointers)와 같은 일반적인 메모리 관련 오류를 방지하는데 도움을 줍니다. 그러나 가비지 컬렉션의 작동 방식과 주기는 JVM(Java Virtual Machine) 구현에 따라 다를 수 있으므로, 특정 상황에서의 가비지 컬렉션 동작을 정확히 이해하고 최적화하는 것이 중요합니다.

 

 

레퍼런스 카운팅 방식의 GC에서 각 객체는 참조당하는 횟수를 표시해둡니다.

이때 참조 횟수가 0이라면 Garbage라고 할 수 있습니다.
참조가 생기면 count는 증가하고, 참조가 사라지면 감소합니다.
count가 0이 되었을때, 객체를 해제합니다.

장점 :
객체가 접근 불가능해지는 즉시 메모리가 해제되므로, 프로그래머가 객체의 해제 시점을 어느정도 예측 가능합니다.
객체가 사용된 직후에 메모리를 해제하므로, 메모리 해제 시점에 해당 객체는 캐시에 저장되어 있을 확률이 높기 때문에 메모리 해제가 빠르게 이루어집니다.


단점 :
Cycles: 두개 이상의 객체가 서로 참조를 하고있다면, 사이클(순환참조)이 생겨서 참조 횟수가 영원히 0이 될 수 없습니다. 이를 순환 참조라고 하며, 메모리 누수의 원인이 됩니다. CPython은 이 문제를 해결하기 위해 순환 참조를 감지하는 알고리즘을 사용합니다.


Space overhead (reference count): 레퍼런스 카운팅 방식은 각 객체마다 카운트를 저장할 공간이 필요합니다. 이것은 객체의 메모리 영역에서 가까운 곳에 저장될 것인데, 객체마다 32에서 64비트의 공간이 추가적으로 필요로 하게 됩니다. 일부 시스템에서는 태그가 지정된 포인터를 사용하여 개체 메모리의 사용되지 않는 영역에 참조 횟수를 저장하여 이러한 오버헤드를 완화할 수 있습니다.


Speed overhead (increment/decrement): 참조의 할당이 그 범위를 넘어서면 하나 이상의 reference counter를 수정해야 할 수도 있습니다. 그러나 참조가 외부 범위 변수에서 내부 범위 변수로 복사되어 내부 변수의 수명이 외부 변수의 수명으로 제한되는 일반적인 경우에는 참조 증가가 제거될 수 있습니다. 외부변수는 참조를 '소유'합니다. C++에서는 const reference를 사용하여 이러한 내용을 쉽게 구현할 수 있습니다. C++에서 레퍼런스 카운팅은 일반적으로 생성자, 소멸자 및 할당 연산자가 참조를 관리하는 '스마트포인터' 를 사용하여 구현됩니다.


Requires atomicity: 멀티스레드 환경에서, 이러한 수정(increment/decrement)은 compare-and-swap과 같은 atomic operation이어야 합니다. 멀티 프로세서 환경에서의 atomic operation은 비용이 많이 듭니다.


Not real-time: 일반적으로 reference counting은 구현할 때 real-time 속성의 동작을 지원하지 않습니다. 이는 재귀적인 메모리 해제가 필요한 object가 다른 object 또한 가리키고 있고 (=any pointer assignment), 이 object를 수행중인 스레드는 다른 task를 수행하고 있어 메모리 해제 수행이 불가능한 경우 실시간 처리가 보장되지 않기 때문입니다. 이 이슈는 추가적인 연산 오버헤드를 감수하면서 참조되지 않은 object의 메모리 해제를 다른 스레드에 위임하여 회피할 수 있습니다.

 

 

 

가비지 컬렉션 동작 원리)

1.객체의 생성: 객체는 new 키워드를 사용하여 힙(heap) 메모리에 동적으로 할당됩니다.

 

2. 객체의 참조: 객체를 사용하기 위해 레퍼런스(참조 변수)를 통해 접근합니다. 레퍼런스는 객체의 주소를 저장하고, 객체에 접근하기 위해 사용됩니다.

 

3. 객체의 참조 유지: 객체를 참조하는 레퍼런스가 있으면 객체는 유효하고 사용 가능한 상태로 유지됩니다. 다른 객체가 해당 객체를 참조하면, 해당 객체는 여전히 유효하며 가비지 컬렉션의 대상이 되지 않습니다.

 

4. 객체의 불필요한 참조: 객체를 더 이상 사용하지 않는 경우, 해당 객체를 참조하는 레퍼런스를 명시적으로 해제하거나 레퍼런스가 유효하지 않게 되는 경우에는 더 이상 객체에 접근할 수 없게 됩니다.

 

5. 가비지 컬렉션 수행: 가비지 컬렉션은 주기적으로 또는 필요에 따라 자동으로 실행됩니다. 이 과정에서 가비지 컬렉터는 더 이상 사용되지 않는 객체들을 식별하고 메모리에서 해제합니다. 이렇게 제거된 객체들은 힙 메모리에서 자동으로 해제되며, 해당 메모리 공간은 다른 객체의 할당에 사용될 수 있습니다

 

 

가비지 컬렉션의 단점)

1. 개발자가 메모리가 언제 해제되는지 정확하게 알 수 없다.

2. 가비지 컬렉션(GC)이 동작하는 동안에는 다른 동작을 멈추기 때문에 오버헤드가 발생한다.

 

가비지 컬렉션에는 장점만 있는 것이 아니라 단점도 존재합니다. 대표적으로는 위의 2가지 단점이 존재하는데요.

이 중에서 경우에 따라서 문제가 발생할 수 있는 부분은 GC가 동작하는 동안에는 JVM의 다른 동작들은 잠깐 멈추기 때문에 오버헤드가 발생한다는 점입니다.

이로 인해 GC가 너무 자주 실행되면 소프트웨어 성능 하락의 문제가 되기도 합니다. 이러한 특성으로 인해 실시간으로 계속 동작해주어야 하는 시스템들 예를 들자면 열추적 미사일의 경우에는 잠깐의 소프트웨어 일시정지로도 목표한 결과가 달라질 수 있기 때문에 GC의 사용이 적합하지 않을 수 있습니다.

 

 

프로그램이 메모리 부족으로 죽는 경우)

OutOfMemoryError 를 빨리내고, GC 를 확인하기 위해서 jvm 옵션으로 -Xmx16m -verbose:gc 를 주고 시작하자. -Xmx 는 힙영역의 최대 사이즈 를 설정하는 것이다. 16MB 로 설정했다.

코드는 아래와 같다.

public class ListGCTest {
    public static void main(String[] args) throws Exception {
        List<Integer> li = IntStream.range(1, 100).boxed().collect(Collectors.toList());
        for (int i=1; true; i++) {
            if (i % 100 == 0) {
                Thread.sleep(100);
            }
            IntStream.range(0, 100).forEach(li::add);
        }
    }
}

실행결과

 

실행결과를 보면 가비지 컬렉션 작업을 몇번 반복하다가 결국 OutOfMemoryError 를 뱉으며 프로그램이 죽어버린다.

위에서, 가비지 컬렉션의 대상이 되는 오브젝트는 Unreachable 오브젝트라고 했다.

그런데 무한루프의 외부에서 선언한 ArrayList 는 무한루프가 도는 동안에도 계속해서 Reachable 하기 때문에 (레퍼런스가 끊이지 않기 때문에), 가비지 컬렉션 작업이 진행되어도 힙에 모든 데이터가 계속 남아있게 된다.

즉, 무한루프를 돌기 때문에 프로그램이 죽은 것이 아니라, (Unreachable 오브젝트가 없으므로) 가비지 컬렉션이 일어나도 모든 오브젝트가 살아있기 때문에 OutOfMemoryError 가 발생한 것이다.

똑같이 무한루프를 돌지만, Unreachable 오브젝트를 만들어 내는 코드를 살펴보자.

 

 

가비지 컬렉터가 열심히 일하여 프로그램이 죽지 않는 경우)

JVM 옵션으로 똑같이 -Xmx16m -verbose:gc 를 주고 실행했다.

Thread.sleep() 하는 부분에서 li 변수에 새로운 ArrayList 를 생성하도록 해보자. 그리고 몇번째 루프에서 가비지 컬렉션이 수행되는지 확인하기 위해 프린트도 하나 찍어보자.

무한루프를 돌면서 중간중간에 List 를 가비지가 되도록 만들어서 가비지 컬렉션이 수행되면 프로그램은 죽지않고 계속해서 돌아갈 것이다. 코드는 아래와 같다.

public class ListGCTest {
    public static void main(String[] args)throws Exception {
        List<Integer> li = IntStream.range(1, 100).boxed().collect(Collectors.toList());
        for (int i=1; true; i++) {
            if (i % 100 == 0) {
                li = new ArrayList<>();
                Thread.sleep(100);
            }
            IntStream.range(0, 100).forEach(li::add);
        }
    }
}

실행결과는 아래와 같다. 루프 횟수는 .... 으로 표시했다.

if (i % 100 == 0) 구문으로 100 번째 단위로 루프를 돌때마다 (새로운 리스트를 할당하여) 기존에 있던 리스트를 가비지로 만들어주니 프로그램이 죽지 않고 계속 돌아가는 것을 보면, 가비지 컬렉터가 열일하고 있다는 것을 알 수 있다.

 

첫 번째 코드예제에서, 스택에 한개의 리스트 레퍼런스 변수를 두고 같은 리스트에 계속해서 데이터를 추가하면, 가비지 컬렉션이 이루어져도 가비지로 분류되는 Unreachable 오브젝트가 없기 때문에 프로그램이 죽는 것을 확인했다.

 

두 번째 코드예제에서는, 똑같이 스택에 한개의 리스트 레퍼런스 변수를 두더라도, 주기적으로 새로운 리스트를 생성해서 새롭게 생성한 리스트를 레퍼런스 하도록 만들었다. 그 결과, Unreachable 오브젝트 가 되어버린 기존 리스트들을 가비지 컬렉터가 메모리에서 제거함으로써 프로그램이 죽지않고 돌아가는 것을 확인했다.

 

글,그림,코드 참조 : https://yaboong.github.io/java/2018/06/09/java-garbage-collection/

 

 

 

#4 형변환

정수와 실수는 컴퓨터 내부에서 표현되는 방식이 전혀 다릅니다. 따라서 정수와 실수를 더한다고 할 때 그대로 연산을 수행할 수 없고 하나의 자료형으로 통일한 후 연산을 해야 합니다. 이 때 형변환(type conversion)이 이루어집니다.

 

크게 3가지의 예시로 형변환을 알아보겠습니다.

 

 

정수 변수 => 부동소수점 변수)

정수 변수를 부동소수점 변수로 형변환시에 주의할 점은 유효자릿수를 넘어가는 정수 값인 경우 부동소수점 메모리에 저장될 때 짤릴 수 있으며, 그럼에도 컴파일 오류가 발생하지 않아 정확한 값을 놓치는 경우가 많습니다.

 

public class Exam0920 {
  public static void main(String[] args) {
    byte b = 100; // 1byte
    short s = 100;  // 2byte
    int i = 98765678; // 4byte
    long l = 98765678;  // 8byte
    char c = 100; // 2byte

    float f;  // 4byte
    double d; // 8byte

    // 정수 변수의 값을 부동소수점 변수에 저장할 때 값이 짤릴 수 있다.
    //
    f = b; // byte(1) ==> float(4). 값을 그대로 저장.
    System.out.println(f); // => 100.0

    f = s; // short(2) ==> float(4). 값을 그대로 저장. 
    System.out.println(f); // => 100.0

    f = c; // char(2) ==> float(4). 값을 그대로 저장.
    System.out.println(f); // => 100.0

    f = i; // int(4) ==> float(4). 
    // 유효자릿수를 넘는 정수는 짤린다.
    // 주의! 컴파일 오류가 발생하지 않는다.
    System.out.println(f); // => 9.876568E7 => 소수점이하 6자리까지만 나타내며, 그 이후는 자동으로 짤리거나 반올림한다. 

    f = l; // long(8) ==> float(4)
    // 유효자릿수를 넘는 정수는 짤린다.
    // 주의! 컴파일 오류가 발생하지 않는다.
    System.out.println(f); // => 9.876568E7 => 위와 마찬가지로 자동으로 자르거나 반올림 한다.

    d = i; // int(4) ==> double(8)
    // 유효자릿수 범위의 정수 값이므로 int(32비트) 값을 그대로 저장할 수 있다.
    System.out.println(d);	// => 9.8765678E7

    l = 18_2345_3456_4567_5678L;
    d = l; 
    // 유효 범위를 넘어가는 정수인 경우 짤린다.
    // 주의! 컴파일 오류가 발생하지 않는다.
    System.out.println(d);	// => 1.8234534564567568E17 => ~ 6756까지 나오고 반올림하거나 짜른다.
  }
}

 

 

부동소수점 변수 => 정수 변수)

부동소수점 메모리의 값을 정수 메모리에 저장할 수 없습니다. 그 이유는 정수 메모리는 소수점 이하의 값을 저장할 수 없기 때문에 자바에서는 부동소수점 값을 정수 메모리에 저장하는 것을 문법에서 막고 있습니다. 그래서 아래와 같은 형변환 코드를 실행해보면 컴파일 오류가 발생합니다.

 

public class Exam0921 {
  public static void main(String[] args) {
    float f = 3.14f;
    double d = 9876.56789;

    // 부동소수점 메모리의 값을 정수 메모리에 저장할 수 없다.
    // 왜?
    // - 정수 메모리는 소수점 이하의 값을 저장할 수 없기 때문에
    // 자바에서는 부동소수점 값을 정수 메모리에 저장하는 것을
    // 문법에서 막는다!
    int i = f; // 컴파일 오류!
    long l = d; // 컴파일 오류!
  }
}

컴파일 오류!

 

 

명시적 형변환)

byte형은 1바이트로 int형보다 크기가 작습니다. 그래서 자료 손실이 발생할 수 있기 때문에 프로그래머가 변환할 자료형을 명시적으로 써 주어야 하며 이를 강제 형변환이라고 합니다.

 

위에서 확인한 것과 같이 부동소수점 메모리의 값은 정수 메모리에 저장할 수 없습니다. 그럼에도 저장하려고 한다면 명시적 형변환 문법을 사용해야 합니다.

 

명시적 형변환을 통해 부동소수점의 값을 정수 메모리에 저장할 때 소수점 이하의 값이 짤립니다.

 

정리하자면 다음과 같습니다.

- 명시적 형변환은 컴파일러에세 강제로 값을 넣을 것을 명령하는 것

- 큰 메모리의 값을 작은 메모리로 변환 할 때

- 부동소수점을 정수로 변환 할 때

- 문법 : 변수 = (바꾸고자하는타입) 변수 또는 값;

 

public class Exam0930 {
  public static void main(String[] args) {

    // float ==> int 
    float f = 3.14f;
    int i = (int)f;  // 소수점 이하가 제거된다.
    System.out.println(i); // => 3

    // double ==> long
    double d = 9876.56789;
    long l = (long)d; // 소수점 이하가 제거된다.
    System.out.println(l); // => 9876
  }
}

 

명시적 형변환 : 큰 정수 변수의 값을 작은 정수 변수에 저장)

큰 메모리의 정수 값을 작은 메모리에 저장하는 경우 컴파일 오류가 발생합니다.

큰 값을 작은 메모리에 넣는 것은 아무런 의미가 없고 해서도 안되지만, 큰 메모리의 값을 바이트 단위로 쪼개고 싶을 때 유용합니다. 이러한 경우를 명시적 형변환을 통해 저장해보도록 하겠습니다.

 

public class Exam0940 {
  public static void main(String[] args) {

    byte b = 100; // 1byte
    short s = 100; // 2byte
    int i = 100; // 4byte
    long l = 100; // 8byte

    // 1) 큰 메모리의 값이 작은 메모리에 충분히 들어가는 경우
    // short(2byte) ==> byte(1byte)
    byte b2 = (byte) s; // 명시적 형변환
    System.out.println(b2); // => 100

    // int(4byte) ==> byte(1byte)
    b2 = (byte) i; // 명시적 형변환
    System.out.println(b2); // => 100

    // long(8byte) ==> byte(1byte)
    b2 = (byte) l; // 명시적 형변환
    System.out.println(b2); // => 100

    // 2) 큰 메모리의 값이 작은 메모리에 들어 갈 수 없는 경우
    // => 앞 쪽 바이트의 값이 짤려서 들어간다.
    int i2 = 0b0000_0000_0000_0000_0000_0001_0010_1100; // = 300(10진수)
    b2 = (byte) i2; //
    System.out.println(b2); // 0b0010_1100 => 44

    l = 400_0000_0000L; // 0x00_00_00_09_50_2f_90_00
    i = (int) l; // 큰 메모리의 값이 작은 메모리에 들어가지 못하는 경우
    System.out.println(l); // => 400_0000_0000
    System.out.println(i); // => 앞 쪽 바이트가 짤린다. => 50_2f_90_00만 출력 => 1345294336
    System.out.println(0x502f9000); // => 1345294336
  }
}

 

 

명시적 형변환 : 명시적 형변환이 불가능한 경우)

1) 정수 메모리끼리 형변환이 가능하다.

2) 부동소수점을 정수로 형변환이 가능하다.

3) 현변환 없이 정수는 부동소수점 메모리에 저장할 수 있다.

4) 숫자를 문자 코드로 형변환 가능하다.

이 외에는 형변환 불가! 예) 정수, 부동소수점 ==> boolean

 

public class Exam0950 {
  public static void main(String[] args) {

    boolean bool;
    bool = (boolean) 1; // 컴파일 오류!

    예) 문자열 ==/==> 정수, 부동소수점, 문자, boolean
    boolean v1 = (boolean) "true"; // 컴파일 오류!
    char v2 = (char) "가"; // 컴파일 오류!
    int v3 = (int) "123"; // 컴파일 오류!
    float v4 = (int) "3.14f"; // 컴파일 오류!
  }
}

 

 

명시적 형변환 : 명시적 형변환이 불가능한 경우, 특별한 메서드를 사용하면 가능)

- valueOf : 자바의 string 클래스에 있는 정적 메서드로, 인자로 전달된 값을 문자열로 변환하여 반환합니다. 다양한 타입의 인자를 받아서 해당 타입을 문자열로 변환합니다.

 

valueOf 메서드의 주요 형태와 각각의 설명입니다.

  • public static String valueOf(Object obj): 인자로 전달된 객체의 문자열 표현을 반환합니다. 인자가 null인 경우 "null" 문자열을 반환하며, 그 외의 경우에는 obj.toString()의 결과를 반환합니다.
  • public static String valueOf(char[] data): 문자 배열의 문자들을 포함하는 문자열을 반환합니다. 인자로 전달된 문자 배열의 내용이 복사되므로 이후 문자 배열의 변경은 반환된 문자열에 영향을 주지 않습니다.
  • public static String valueOf(char[] data, int offset, int count): 특정 하위 배열의 문자들을 포함하는 문자열을 반환합니다. offset은 하위 배열의 첫 번째 문자의 인덱스를, count는 하위 배열의 길이를 나타냅니다.
  • public static String valueOf(boolean b): 불리언 값을 문자열로 변환하여 반환합니다. true인 경우 "true" 문자열을, false인 경우 "false" 문자열을 반환합니다.
  • public static String valueOf(char c): 문자를 문자열로 변환하여 반환합니다. 반환되는 문자열은 길이가 1이고 인자로 전달된 문자 하나를 포함합니다.
  • public static String valueOf(int i): 정수 값을 문자열로 변환하여 반환합니다. 반환되는 문자열은 Integer.toString 메서드와 동일한 표현을 갖습니다.
  • public static String valueOf(long l): long 타입의 값을 문자열로 변환하여 반환합니다. 반환되는 문자열은 Long.toString 메서드와 동일한 표현을 갖습니다.
  • public static String valueOf(float f): float 타입의 값을 문자열로 변환하여 반환합니다. 반환되는 문자열은 Float.toString 메서드와 동일한 표현을 갖습니다.
  • public static String valueOf(double d): double 타입의 값을 문자열로 변환하여 반환합니다. 반환되는 문자열은 Double.toString 메서드와 동일한 표현을 갖습니다.

 

valueOf 메서드를 사용하여 다양한 타입의 값을 문자열로 변환할 수 있으며, 이를 활용하여 프로그램에서 값들을 문자열로 표현하거나 문자열 연산 등에 활용할 수 있습니다.

 

 

- parse : 다양한 데이터 유형의 문자열 표현을 해당 유형의 값으로 변환하는데 사용됩니다. 'parse' 명령어는 여러 클래스에서 정적 메서드로 제공되며, 각 메서드는 특정 데이터 유형을 대상으로 문자열을 해석하여 해당 유형의 값을 생성합니다.

 

다음은 parse 메서드와 각각의 설명입니다.

  • Integer.parseInt(String s): 문자열을 정수 값으로 변환합니다. s 매개변수는 변환할 문자열을 나타냅니다. 예를 들어, Integer.parseInt("123")은 문자열 "123"을 정수 값 123으로 변환합니다.
  • Double.parseDouble(String s): 문자열을 부동 소수점 값으로 변환합니다. s 매개변수는 변환할 문자열을 나타냅니다. 예를 들어, Double.parseDouble("3.14")는 문자열 "3.14"을 부동 소수점 값 3.14로 변환합니다.
  • Boolean.parseBoolean(String s): 문자열을 불리언 값으로 변환합니다. s 매개변수는 변환할 문자열을 나타냅니다. 예를 들어, Boolean.parseBoolean("true")는 문자열 "true"를 불리언 값 true로 변환합니다.
  • Byte.parseByte(String s): 문자열을 바이트 값으로 변환합니다. s 매개변수는 변환할 문자열을 나타냅니다. 예를 들어, Byte.parseByte("10")은 문자열 "10"을 바이트 값 10으로 변환합니다.
  • Short.parseShort(String s): 문자열을 short 값으로 변환합니다. s 매개변수는 변환할 문자열을 나타냅니다. 예를 들어, Short.parseShort("100")은 문자열 "100"을 short 값 100으로 변환합니다.
  • Long.parseLong(String s): 문자열을 long 값으로 변환합니다. s 매개변수는 변환할 문자열을 나타냅니다. 예를 들어, Long.parseLong("1000")은 문자열 "1000"을 long 값 1000으로 변환합니다.

 

위와 같은 parse 메서드를 사용하여 문자열을 해당 데이터 유형으로 변환할 수 있습니다. 다만, 변환 과정에서 문자열이 유효한 형식인지 확인해야 합니다.

예를 들어, Integer.parseInt를 사용하여 정수로 변환할 때, 문자열이 숫자로 구성되어 있지 않으면 NumberFormatException이 발생합니다. 따라서 예외 처리를 통해 이러한 오류를 처리하는 것이 중요합니다.

또한, 자바에서는 다른 데이터 유형에 대한 parse 메서드들도 제공됩니다. 예를 어, Float.parseFloat, Character.parseChar, BigDecimal.parse, BigInteger.parse 등이 있으며, 각각의 메서드는 해당 데이터 유형으로의 변환을 수행합니다.

 

public class Exam0951 {
  public static void main(String[] args) {

    byte b = Byte.valueOf("100");
    short s = Short.valueOf("32767");
    int i1 = Integer.valueOf("2122223333"); // 문자열 ==> int
    int i2 = Integer.parseInt("2122223333"); // 문자열 ==> int
    long l = Long.valueOf("9221111222233334444"); 
    float f1 = Float.valueOf("3.14f");
    float f2 = Float.parseFloat("3.14f");
    double d = Double.valueOf("9876.54321");
    boolean bool1 = Boolean.valueOf("TRUE");   // 문자열 ==> boolean
    boolean bool2 = Boolean.parseBoolean("TRUE");   // 문자열 ==> boolean
    char c = "가나다".charAt(1); // => 나

    System.out.println(b); // => 100
    System.out.println(s); // => 32767
    System.out.println(i1); // => 2122223333
    System.out.println(i2); // => 2122223333
    System.out.println(l); // => 9221111222233334444
    System.out.println(f1); // => 3.14
    System.out.println(f2); // => 3.14
    System.out.println(d); // => 9876.54321
    System.out.println(bool1); // => true
    System.out.println(bool2); // => true
    System.out.println(c); // => 나
  }
}

 

 

암시적 형변환 - implicit type conversion; type casting : 산술연산자)

연산은 항상 같은 타입끼리만 가능하며, 다른 타입끼리 연산을 할 때는 둘 중 한개의 타입을 다른 타입으로 바꿔야 합니다.

타입을 바꾸는 것을 내부적인 규칙에 따라 자동으로 처리한다고 해서 "암시적 형변환" 이라고 부릅니다.

이러한 형변환은 더 작은 데이터 유형에서 더 큰 데이터 유형으로의 변환에 대해서만 발생하며, 데이터의 손실 없이 안전하게 수행됩니다.

 

규칙으로는 다음과 같이 오른쪽 타입의 값으로 자동 변환시킵니다.

byte,short,char => int => long => float => double

 

정수와 부동소수점에 대해서만 암시적 형변환이 일어나며, 그 외에 다른 타입은 불가능합니다.

 

정리하자면 다음과 같습니다.

 

1. 작은 데이터 유형에서 큰 데이터 유형으로의 변환: 크기가 더 작은 데이터 유형에서 크기가 더 큰 데이터 유형으로의 변환은 자동으로 이루어집니다. 예를 들어, byte를 int로, short를 long으로, float를 double로 변환하는 것은 암시적 형변환이 발생합니다.

2. 정수와 부동 소수점 간의 변환: 정수 유형인 byte, short, int, long은 부동 소수점 유형인 float 또는 double로 암시적으로 형변환될 수 있습니다. 이때는 정수가 부동 소수점 값으로 변환됩니다.

3. 문자와 정수 간의 변환: char는 내부적으로 정수로 표현되기 때문에, char를 정수 유형으로 암시적 형변환할 수 있습니다.

4. 논리 값의 변환: boolean은 다른 데이터 유형으로 암시적 형변환될 수 없습니다.

 


암시적 형변환은 주로 다음과 같은 상황에서 사용됩니다:

- 연산자를 사용할 때: 서로 다른 데이터 유형을 사용하는 연산에서 암시적 형변환을 통해 표현 범위가 더 큰 데이터 유형으로 변환됩니다. 예를 들어, int와 double을 더할 때 int 값은 double로 암시적 형변환됩니다.

- 메서드 호출과 반환: 메서드가 특정 데이터 유형을 반환하거나 인자로 받을 때, 암시적 형변환을 통해 호환되는 데이터 유형으로 자동 변환됩니다.

암시적 형변환은 프로그래밍에서 편의를 제공하며, 일부 형변환이 자동으로 처리되므로 코드 작성이 더 간편해집니다. 그러나 형변환이 자동으로 수행되기 때문에 데이터 손실을 조심해야 합니다. 암시적 형변환이 필요한 경우에는 데이터 손실이 발생하지 않도록 주의해야 합니다.

 

다음은 코드를 통해서 알아보겠습니다.

public class Exam0150 {
  public static void main(String[] args) {
    byte b = 1;
    short s = 2;
    int i = 3;
    long l = 4;
    float f = 5.5f;
    double d = 6.6;
    boolean bool = true;
    char c = 7;

    // byte + byte = int
    // => 연산을 하기 전에 byte 값이 int로 암시적 형변환 된다.
    // byte r1 = b + b; // 컴파일 오류!

    // short + short = int
    // => 연산을 하기 전에 short 값이 int로 암시적 형변환 된다.
    // short r2 = s + s; // 컴파일 오류!

    // byte + short = int
    // => byte와 short 값은 int로 암시적 형변환 된다.
    // short r3 = b + s; // 컴파일 오류!

    // byte + int = int
    // => byte가 int로 암시적 형변환 한 이후 연산을 수행한다.
    int r4 = b + i; // OK
    System.out.println(r4); // => 4

    // short + int = int
    // => short가 int로 암시적 형변환 한 이후 연산을 수행한다.
    int r5 = s + i; // OK
    System.out.println(r5); // => 5

    // int + long = long
    // => int가 long으로 암시적 형변환 한 이후에 연산을 수행한다.
    // int r6 = i + l; // 컴파일 오류!

    // long + float = float
    // => long이 float으로 암시적 형변환 한 후에 연산을 수행한다.
    // long r7 = l + f; // 컴파일 오류!

    // int + float = float
    // => 정수 타입의 값과 부동소수점 타입의 값을 연산하면
    // 정수 타입의 값이 암시적 형변환을 통해 부동소수점으로 바뀐다.
    // int r8 = i + f; // 컴파일 오류!

    // float + double = double
    // float r9 = f + d; // 컴파일 오류!

    // byte + short + int + long + float + double = double
    // long r10 = b + s + i + l + f + d; // 컴파일 오류!

    // float + int + long = float
    // long r11 = f + i + l; // 컴파일 오류!

    // boolean + int = 컴파일 오류!
    // => 산술 연산자는 정수 타입(byte, short, char, int, long)과
    // 부동소수점 타입(float, double)에 대해서만
    // 실행할 수 있다.
    // int r12 = bool + i; // 컴파일 오류!
  }
}