인프런/김영한 자바

[김영한의 자바 입문] - 5. 스코프 & 형변

sson-coding 2025. 8. 29. 00:18

본 글에 사용된 코드와 이미지의 일부는 김영한 님의 『김영한의 자바 입문』 강의를 참조하여 발췌·활용하였습니다.


스코프(Scope)

변수는 선언한 위치에 따라 지역 변수, 멤버 변수(클래스 변수, 인스턴스 변수) 와 같이 분류된다. 지역 변수 는 특정 지역에서만 사용할 수 있는 변수라는 뜻이고, 특정 지역을 벗어나면 사용할 수 없다.

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

코드를 통해 살펴보자.

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); //오류, 변수 x에 접근 불가
		System.out.println("main m = " + m);
	} //m 생존 종료
}

int m 은 main{} 코드 블록안에서 선언되었기 때문에 변수를 선언한 시점부터 main{} 의 코드 블록이 종료될 때 까지 생존한다. 또한 if{} 블록 내부에서도 외부 블록에서 선언된 m 에 접근할 수 있다.

int x 는 if{} 블록안에서 선언되었기 때문에 if{} 의 코드 블록이 종료될 때 까지 생존한다.

이렇게 변수의 접근 가능한 범위를 스코프(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);
	}
}

여기서 2배 증가한 값을 저장하기 위해 temp 를 사용했다. 이 변수는 임시로 사용하는 변수인데 main() 코드 블록에 선언되어 있다. 이렇게 되면 다음과 같은 문제가 발생한다.

  1. 비효율적인 메모리 사용
    • temp 는 if 코드 블록에서만 필요하지만, main() 코드 블록이 종료될 때 까지 메모리에 유지된다. 따라서 불필요한 메모리가 낭비된다.
  2. 코드 복잡성 증가
    • 만약 if 코드 블록 안에 temp 를 선언했다면 if 가 끝나고 temp 를 전혀 생각하지 않아도 된다. 누군가 이 코드를 유지보수 할 때 신경써야 한다.

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


형변환(Casting,캐스팅)

형변환

형변환의 특징을 살펴보자.

  1. 작은 범위에서 큰 범위로 값을 넣을 수 있다.
    • int → long → double
  2. 큰 범위에서 작은 범위는 문제가 발생한다.
    • 소수점 버림
    • 오버플로우

자동 형변환

작은 범위 숫자 타입에서 큰 범위 숫자 타입으로의 대입은 개발자가 직접 형변환을 하지 않아도 되는데, 이런 과정은 자동을 일어나기 때문에 이를 자동 형변환 , 묵시적 형변환 이라고 한다.

어떻게 자동으로 변환되는지 코드로 살펴보자.

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

이렇게 앞에 (double) 과 같이 적어주면 int 형이 double 형으로 변한다.

명시적 형변환

반대로 큰 범위에서 작은 범위 대입은 명시적 형변환이라고 한다.

코드를 통해 살펴보자.

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); //출력:1
 }
}

다음 코드의 앞부분에 있는 주석을 풀면 컴파일 오류가 발생한다. 왜 발생할까?

int 형은 double 형보다 숫자의 표현 범위가 작고 실수를 표현할 수도 없다. 따라서 이 경우 숫자가 손실되는 문제가 발생할 수 있다. 쉽게 큰 컵에 담긴 물을 작은 컵에 옮겨 담으려고 하니, 손실이 발생할 수 있다는 것이다.

하지만 이런 위험을 감수하고 값을 대입하고 싶다면 데이터 타입을 강제로 변경할 수 있다. 예를 들어, 소수점을 버리고 정수만 보고 싶을 수 있다. intValue = (int) doubleValue 이렇게 작성하면 된다.

명시적 형변환 과정을 코드로 살펴보자.

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

형변환과 오버플로우

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

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); //출력:2147483647
	 intValue = (int) maxIntOver; //형변환
	 System.out.println("maxIntOver casting=" + intValue); //출력:-2147483648
 }
}

---실행 결과
maxIntValue casting=2147483647
maxIntOver casting=-2147483648

long maxIntValue 는 int 로 표현할 수 있는 가장 큰 숫자를 입력했기 때문에 long -> int 로 형변환을 해도 아무런 문제가 없다.

long maxIntOver 를 보면 int 로 표현할 수 있는 가장 큰 숫자에 1 큰 숫자를 입력해, int 범위를 넘어가기 때문에 마지막에 L 을 붙여서 long 형을 사용해야 한다.

결과가 -2147483648 이 나오는 이유는 int 형은 2147483648L 를 표현할 수 있는 방법이 없다. 이렇게 기존 범위를 초과해서 표현하게 되면 전혀 다른 숫자가 표현되는데, 이런 현상을 오버플로우 라고 한다. 보통 오버플로우가 발생하면 시계가 한바퀴 돈 것 처럼 다시 처음부터 시작한다. 그래서 저렇게 출력이 된다.

중요한 것은 오버플로우가 발생하는 것을 막는것이다. 위 경우 intValue 의 타입을 int -> long 으로 변경하면 된다.

계산과 형변환

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

코드를 통해 살펴보자.

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

왜 저런 결과가 나올까?

이유는 다음과 같다.

  1. 같은 타입끼리의 계산은 같은 타입의 결과를 낸다.
  2. 서로 다른 타입의 계산은 큰 범위로 자동 형변환이 일어난다.

계산할 때 주의하도록 하자.