스코프

이상민·2024년 7월 12일

지역 변수와 스코프

지역 변수는 이름 그대로 특정 지역에서만 사용할 수 있는 변수라는 뜻이다. 그 특정 지역을 벗어나면 사용할 수 없다.
지역 변수는 자신이 선언한 코드 블록{}안에서만 생존하고, 자신이 선언된 코드블록을 벗어나면 제거된다.

Scope1

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 x = " + m);
    } //m 생존 종료
}

지역 변수는 본인의 코드 블록 안에서만 생존한다.
자신의 코드 블록을 벗어나면 제거되기 때문에 접근할 수 없다.

변수의 접근 가능한 범위를 스코프라고 한다.
int mmain{}전체에서 접근할 수 있기 때문에 스코프가 넓고, int xif{} 코드 블록 안에서만 접근할 수 있기 때문에 스코프가 짧다.

Scope2

package scope;

public class Scope2 {

    public static void main(String[] args) {
        int m = 10;
        for (int i = 0; i < 2; i++) { //블록 내부, for문 내
            System.out.println("for m = " + m); //블록 내부에서 외부는 접근 가능
            System.out.println("for i = " + i);
        } //i 생존 종료

        //System.out.println("main i = " + i); //오류, i에 접근 불가
        System.out.println("main m = " + m);
    }
}

스코프 존재 이유

왜 스코프라는 개념을 만들었을까?

Scope3_1

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("m = " + m);
    }
}

위 코드는 좋은 코드라고 보기 어렵다.

  • 비효율적인 메모리 사용: tempif코드 블록에서만 필요하지만, main()코드 블록이 종료될 때 까지 메모리에 유지되므로, 불필요한 메모리가 낭비된다.

  • 코드 복잡성 증가: 좋은 코드는 군더더기 없는 단순한 코드이다. 만약 if코드 블록 안에 temp를 선언했다면 if가 끝나고 나면 temp를 전혀 생각하지 않아도 된다.

Scope3_2

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("m = " + m);
    }
}

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

while VS for문

package loop;

public class While2_3 {

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

        //i=2, endNum=3
        while (i <= endNum) {
            sum = sum + i;
            System.out.println("i=" + i + " sum=" + sum);
            i++;
        }
    }
}
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를 비교해보자.

  • while문의 경우 변수i의 스코프가 main() 메서드 전체가 된다. 반면에 for문의 경우 변수 i의 스코프가 for문 안으로 한정된다.

메모

for문은 while문과 달리 범위안에서 변수를 선언할 수 있다.

정리

  • 변수는 꼭 필요한 범위로 한정해서 사용하는 것이 좋으니 변수의 스코프는 꼭 필요한 곳으로 한정해서 사용하자.
    -> 메모리를 효율적으로 사용하고 더 유지보수하기 좋은 코드를 만들 수 있다.

좋은 프로그램은 무한한 자유가 있는 프로그램이 아니라 적절한 제약이 있는 프로그램이다.

형변환 - 자동 형변환

형변환

  • 작은 범위에서 큰 범위로는 당연히 값을 넣을 수 있다.
  • 큰 범위에서 작은 범위는 다음과 같은 문제가 발생할 수 있다
    • 소수점 버림
    • 오버플로우

작은 범위에서 큰 범위로 대입은 허용한다
int < long < double
int보다는 ling이,long보다는double이 더 큰 범위를 표현할 수 있다.

Casting1

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);
    }
}

실행 결과

longValue = 10
doubleValue = 10.0
doubleValue2 = 20.0
  • 자바는 기본적으로 같은 타입에 값을 대입할 수 있다.
  • 작은 범위에서 큰 범위로의 대입은 자바 언어에서 허용된다.

쉽게 말하면 큰 그릇에서 작은 그릇에 담긴 내용물을 담을 수 있다고 보면 된다.

자동 형변환

결국 대입하는 형(타입)을 맞추어야 하기 때문에 개념적으로는 다음과 같이 작동한다.

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

이렇게 (double)과 같이 적어주면 int형이 double형으로 형이 변한다. 이렇게 형이 변경되는 것을 형변환이라한다.

  • 이런 과정이 자동으로 일어나기 때문에 자동 형변환이라 한다.

명시적 형변환

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

double -> int로 대입

Casting2

package casting;

public class Casting2 {

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

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

이러한 오류가 발생한다.
intValue = doubleValue; //컴파일 오류 발생

메모

int형은 double형보다 숫자의 표현 범위가 작아서 숫자가 손실되는 문제가 발생할 수 있다. 그래서 컴파일 오류가 발생한다.

형변환

형변환은 변경하고 싶은 데이터 타입을 (int)와 같이 괄호를 사용해서 명시적으로 입력하면 된다.

intValue = (int) doubleValue; //형변환
영어로는 캐스팅이라 하고 개발자가 직접 형변환 코드를 입력한다고 해서 명시적 형변환이라 한다.

명시적 형변환 과정

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

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

형변환과 오버플로우

만약 범위를 넘어선다면?

Casting3

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) 2147483648L; //형변환
        System.out.println("maxIntValue casting= " + intValue);
    }
}

실행 결과

maxIntValue casting= 2147483647
maxIntValue casting= -2147483648

정상 범위

maxIntValue = 2147483647; //int 최고값
intValue = (int) maxIntValue; //변수 값 읽기
intValue = (int) 2147483647L; //형변환
intValue = 2147483647;

초과 범위

int로 표현할 수 있는 가장 큰 숫자보다 더 크기때문에 마지막에 L을 붙여서 long형을 사용해야 한다.

int로 표현할 수 있는 범위를 넘기 때문에 다음과 같이 long -> int로 형변환 하면 문제가 발생한다.

maxIntOver = 2147483648L; //int 최고값 + 1
intValue = (int) maxIntOver; //변수 값 읽기
intValue = (int) 2147483648L; //형변환 시도
intValue = -2147483648;
  • 결과를 보면 -2147483648라는 전혀 다른 숫자가 나온다. int형은 이숫자를 표현할 수 있는 방법이 없기때문에, 전혀 다른 숫자가 표현된다. 이것을 오버플로우라 한다.

  • 보통 오버플로우가 발생하면 마치 시계가 한바퀴 돈 것 처럼 다시 처음부터 시작한다.

중요한 것은 오버플로우가 발생하는 것 자체가 문제이다.
오버플로우 자체가 발생하지 않도록 막아야한다.

메모
int -> long으로 변경해서 사이즈를 늘리면 오버플로우 문제가 해결된다.

계산과 형변환

Casting4

public class Casting4 {

    public static void main(String[] args) {
        int div1 = 3 / 2;
        System.out.println("div1 = " + div1); //1

        double div2 = 3/ 2;
        System.out.println("div2 = " + div2); //1.0

        double div3 = 3.0 / 2;
        System.out.println("div3 = " + div3); //1.5

        double div4 = (double) 3 / 2;
        System.out.println("div4 = " + div4); //1.5

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

출력 결과

div1 = 1
div2 = 1.0
div3 = 1.5
div4 = 1.5
result = 1.5

자바에서 계산은 다음 2가지를 기억하자.

  1. 같은 타입끼리의 계산은 같은 타입의 결과를 낸다.

    • int + intint를, double +doubledouble의 결과가 나온다.
  2. 서로 다른 타입의 계산은 큰 범위로 자동 형변환이 일어난다.

    • int + longlong + long으로 자동 형변환이 일어난다.
    • int + doubledouble + 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;

3 / 2 와 같이 int 형끼리 나눗샘을 해서 소수까지 구하고 싶다면 div4 의 예제처럼 명시적 형변환을 사용하면 된다.

물론 변수를 사용하는 경우에도 다음과 같이 형변환을 할 수 있다.


int a = 3;
int b = 2;
double result = (double) a / b;

처리 과정

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;

마무리

오늘은 범위(스코프)에 관해서 정리해보았는데 쉽게 말해서 변수의 생존 범위이다. {]코드 블록으로 선언하는데 앞에서 공부하면서 자연스럽게 개념을 느끼고 있었던 것 같다. 그래서 이해하는데 그렇게 어렵지 않았다. 중요하다고 생각한 부분은 꼭 필요한 범위에 변수를 넣어주어서 내가 코드를 작성하면서 신경써야할 변수를 최대한 줄이는 것이 중요하다고 느꼈다. 그리고 계산을 할 때 먼저 명시적 형변환을 시킨후 계산해야 숫자가 손실되는 문제를 막을 수 있으므로, 신경써야 겠다.


출처 https://www.youtube.com/watch?v=JEzBDk0E9Rw

profile
안녕하세요

0개의 댓글