1. 지역 변수와 스코프

지역 변수(Local Variable)

특정 지역( { } )에서만 사용할 수 있는 변수

그 특정 지역을 벗어나면 사용할 수 없다.

지역 변수는 자신이 선언된 코드 블록 ( { } ) 안에서만 생존하고, 자신이 선언된 코드 블록을 벗어나면 제거됨

 

스코프(Scope)

변수의 접근 가능한 범위

int m 은 main{ } 전체에서 접근할 수 있기 때문에 스코프가 넓고, int x 는 if{ } 코드 블록 안에서만 접근할 수 있기 때문에 스코프가 좁다.

package scope;

public class Scope1 {
    public static void main(String[] args) {
        int m = 10; //m 생존 시작
        if (true){
            int x = 20; //x 생존 시작
            System.out.println("if m = " + m);
            System.out.println("if x = " + x);
        } //x 생존 종료
        //System.out.println("main x = " + x);
        System.out.println("main m = " + m);
    } //m 생존 종료
}

 

 

2. 스코프 존재 이유

[좋지 않은 코드]

package scope;

public class Scope3_1 {
    public static void main(String[] args) {
        int m = 10;
        int temp = 0;
        if (m > 0) {
            temp = m * 2;
            System.out.println("temp = " + temp);
        }
        System.out.println("temp = " + temp);
        System.out.println("m = " + m);
    }
}

임시 변수 temp는 if문에서 임시로 잠깐 사용하는 변수인데, main( ) 코드 블록에 선언되어 있다.

이렇게 되면 다음과 같은 문제가 발생한다.

  1. 비효율적인 메모리 사용: temp는 if 코드 블록에서만 필요하지만, main( ) 코드 블록이 종료될 때까지 메모리에 유지된다. 만약 if 코드 블록 안에 temp를 선언했다면 if 코드 블록의 종료 시점에 이 변수를 메모리에서 제거해서 더 효율적으로 메모리를 사용할 수 있다.
  2. 코드 복잡성 증가: 코드를 유지보수할 때 m은 물론이고 temp까지 계속 신경써야 한다. 스코프가 불필요하게 넓은 것이다.

[좋은 코드]

package scope;

public class Scope3_2 {
    public static void main(String[] args) {
        int m = 10;
        if (m > 0) {
            int temp = m * 2;
            System.out.println("temp = " + temp);
        }
        //System.out.println("temp = " + temp);
        System.out.println("m = " + m);
    }
}

temp를 if 코드 블록 안에서 선언했기 때문에 if 코드 블록 안으로 스코프가 줄어든다.

덕분에 temp 메모 리를 빨리 제거해서 메모리를 효율적으로 사용하고, temp 변수를 생각해야 하는 범위를 줄여서 더 유지보수 하기 좋은 코드를 만들었다.

 

[while문 vs for문 - 스코프 관점]

while문
package loop;

public class While2_3 {
    public static void main(String[] args) {
        int sum = 0;
        int i = 1;
        int endNum = 3;

        while (i <= endNum) {
            sum = sum + i;
            System.out.println("i=" + i + " sum=" + sum);
            i++;
        }
    }
}

 

변수 i의 스코프가 main( ) 메서드 전체가 된다.

 

for문
package loop;

public class For2 {
    public static void main(String[] args) {
        int sum = 0;
        int endNum = 3;

        //1+2+3 = 6

        for(int i = 1; i<=endNum; i++) {
            sum = sum + i;
            System.out.println("i=" + i + " sum=" + sum);
        }
    }
}

변수 i의 스코프가 for문 안으로 한정된다.

따라서 변수 i 와 같이 for 문 안에서만 사용되는 카운터 변수가 있다면 while 문 보다는 for 문을 사용해서 스코프의 범위를 제한하는 것이 메모리 사용과 유지보수 관점에서 더 좋다.

 

- 변수의 스코프는 꼭 필요한 곳으로 한정해서 사용하자.
- 좋은 프로그램은 무한한 자유가 있는 프로그램이 아니라 적절한 제약이 있는 프로그램이다.

 

 

3. 형변환

int < long < double
int → long → double // 작은 범위에서 큰 범위 → 자동 형변환(=묵시적 형변환)
double → long → int // 큰 범위에서 작은 범위 → 명시적 형변환

 

1) 자동 형변환

작은 범위에서 큰 범위로 대입은 허용한다

package casting;

public class Casting1 {
    public static void main(String[] args) {
        int intValue = 10;
        long longValue;
        double doubleValue;

        longValue = intValue; //int -> long
        System.out.println("longValue = " + longValue);
        
        doubleValue = intValue; //int -> double
        System.out.println("doubleValue = " + doubleValue);

        doubleValue = 20L; //long -> double
        System.out.println("doubleValue2 = " + doubleValue);
    }
}

실행 결과

 

자동 형변환 과정
//intValue = 10
doubleValue = intValue
doubleValue = (double) intValue //형 맞추기
doubleValue = (double) 10 //변수 값 읽기
doubleValue = 10.0 //형변환

(double): int형이 double형으로 변한다. => 형변환

작은 범위 숫자 타입에서 큰 범위 숫자 타입으로의 대입은 개발자가 이렇게 직접 형변환을 하지 않아도 된다.

이런 과정이 자동으로 일어나기 때문에 자동 형변환, 또는 묵시적 형변환이라 한다.

 

2) 명시적 형변환

큰 범위에서 작은 범위 대입은 명시적 형변환이 필요하다

package casting;

public class Casting2 {
    public static void main(String[] args) {
        double doubleValue = 1.5;
        int intValue = 0;

        //intValue = doubleValue; //double -> int; 컴파일 오류 발생
        intValue = (int) doubleValue; //형변환
        System.out.println("intValue = " + intValue);
    }
}

실행 결과

 

int는 double보다 숫자의 표현 범위가 작기 때문에 double → int를 하면 숫자가 손실될 수 있다.

하지만 이런 위험을 감수하고도 개발자가 값을 대입하고 싶다면 데이터 타입을 강제로 변경할 수 있다.

 

형변환(=캐스팅): (int) 와 같이 괄호를 사용해서 명시적으로 형을 변환 → 명시적 형변환

 

※ 캐스팅 용어 유래

"cast": 금속이나 다른 물질을 녹여서 특정한 형태나 모양으로 만드는 과정

 

명시적 형변환 과정
//doubleValue = 1.5
intValue = (int) doubleValue;
intValue = (int) 1.5; //doubleValue에 있는 값을 읽는다.
intValue = 1; //(int)로 형변환 한다. intValue에 int형인 숫자 1을 대입한다.

 

참고로 형변환을 해도 doubleValue 자체의 타입이 변경되거나 그 안에 있는 값이 변경되는 것은 아니다.

즉, doubleValue 안에 들어있는 값은 1.5로 그대로 유지된다.

 

 

형변환과 오버플로우

형변환을 할 때 만약 작은 숫자가 표현할 수 있는 범위를 넘어서면 어떻게 될까?

package casting;

public class Casting3 {
    public static void main(String[] args) {
        long maxIntValue = 2147483647; //int 최고값
        long maxIntOver = 2147483648L; //int 최고값 + 1(초과)
        int intValue = 0;

        intValue = (int) maxIntValue; //형변환
        System.out.println("maxIntValue casting = " + intValue);

        intValue = (int) maxIntOver; //형변환
        System.out.println("maxIntOver casting = " + intValue);
    }
}

실행 결과

  • int의 최고값인 2147483647을 입력한 경우 int로 포현할 수 있는 범위 안에 있기 때문에 long → int로 형변환을 해도 아무 문제가 없다.
  • 그러나 int의 최고값에서 +1 초과한 2147483648을 입력한 경우에는 in로 표현할 수 있는 범위를 넘어서기 때문에 long → int로 형변환을 하면 문제가 발생한다. ※ int 범위인 2147483647을 초과하면 L을 붙여 long형을 사용해야 함          이 경우의 결과는 -2147483648이라는 전혀 다른 숫자가 보인다. 이런 현상을 오버플로우라고 한다.
  • 오버플로우가 발생하면 마치 시계가 한 바퀴 돈 것처럼 다시 처음부터 시작한다.
  • 중요한 것은 오버플로우가 발생하는 것 자체가 문제라는 점이다! 오버플로우 자체가 발생하지 않도록 막아야 한다. 이 경우에는 intValue의 타입을 int → long으로 변경해서 사이즈를 늘리면 오버플로우 문제가 해결된다.

 

 

4. 계산과 형변환

형변환은 대입뿐만 아니라, 계산을 할 때에도 발생한다.

package casting;

public class Casting4 {
    public static void main(String[] args) {
        int div1 = 3 / 2; //1.5 → 1
        System.out.println("div1 = " + div1);
        
        double div2 = 3 / 2; //1.5 → 1.0
        System.out.println("div2 = " + div2);
        
        double div3 = 3.0 / 2; //1.5 → 1.5
        System.out.println("div3 = " + div3);

        double div4 = (double) 3 / 2; //1.5 → 1.5
        System.out.println("div4 = " + div4);
        
        int a = 3;
        int b = 2;
        double result = (double) a / b;
        System.out.println("result = " + result); //1.5 → 1.5
    }
}

실행 결과

 

자바 계산에서 두 가지 법칙

  1. 같은 타입끼리의 계산은 같은 타입의 결과를 낸다.
    int + int = int
    double + double     
                                                                                                        
  2. 서로 다른 타입의 계산은 큰 범위로 자동 형변환이 일어난다.
    int + long = long
    int + double = double
int div1 = 3 / 2; //int / int
int div1 = 1; //int / int이므로 int타입으로 결과가 나온다.
double div2 = 3 / 2; //int / int
double div2 = 1; //int / int이므로 int타입으로 결과가 나온다.
double div2 = (double) 1; //int -> double에 대입해야 한다. 자동 형변환 발생
double div2 = 1.0; // 1(int) -> 1.0(double)로 형변환 되었다.
double div3 = 3.0 / 2; //double / int
double div3 = 3.0 / (double) 2; //double / int이므로, double / double로 형변환 발생
double div3 = 3.0 / 2.0; //double / double -> double이 된다.
double div3 = 1.5;
double div4 = (double) 3 / 2; //명시적 형변환을 사용했다. (double) int / int
double div4 = (double) 3 / (double) 2; //double / int이므로, double / double로 형변
환 발생
double div4 = 3.0 / 2.0; //double / double -> double이 된다.
double div4 = 1.5;
double result = (double) a / b; //(double) int / int
double result = (double) 3 / 2; //변수 값 읽기
double result = (double) 3 / (double) 2; //double + int 이므로 더 큰 범위로 형변환
double result = 3.0 / 2.0; //(double / double) -> double이 된다.
double result = 1.5;

 

 

 

정리

형변환

int → long → double

  1. 작은 범위에서 큰 범위로는 대입할 수 있다 = 자동 형변환(=묵시적 형변환)
  2. 큰 범위에서 작은 범위의 대입은 '소수점 버림' or '오버플로우'와 같은 문제가 발생할 수 있다. 이런 위험을 감수하고도 값을 대입하고 싶다면 데이터 타입을 강제로 변경할 수 있다 = 명시적 형변환
  3. ① 같은 타입은 같은 결과를 낸다.    ② 서로 다른 타입의 계산은 큰 범위로 자동 형변환이 일어난다.

+ Recent posts