[JAVA] 유효범위 개념 정리

LeeSeungEun·2023년 5월 9일
0

JAVA

목록 보기
7/28

1. 등장배경

  • 실행결과를 보면 알겠지만, 내부 변수의 값이 그 외부에 영향을 미치지 않는다는 것을 알 수 있다.
package org.opentutorials.javatutorials.scope;
 
public class ScopeDemo {
 
    static void a() {
        int i = 0;
    }
 
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            a();
            System.out.println(i); 
            /*
            0
            1
            2
            3
            4
            */
        }
    }
 
}

2. 전역변수, 지역변수

  • 4행의 변수 i는 위치적으로 어떠한 메소드의 소속도 아니다. 클래스 ScopeDemo2의 직접적인 소속인 클래스 변수다. 클래스 소속의 변수가 되면 모든 메소드에서 접근할 수 있게 된다. 그래서 7행의 변수 i는 클래스 맴버인 4행의 변수 i를 의미하게 된다. 마찬가지로 11행의 for문 안에 변수 i도 4행의 변수 i를 의미하게 된다. 다시 말해서 메소드 a의 변수 i와 for문의 변수 i가 동시에 클래스 변수 i를 사용하게 된다는 의미다. 그래서 반복문을 통해서 변수 i의 값을 아무리 바꿔도 메소드 a에 의해서 클래스 변수 i의 값이 0이 되기 때문에 반복문이 멈추지 않게 되는 것이다.
package org.opentutorials.javatutorials.scope;
 
public class ScopeDemo2 {
    static int i; //4행 , 선언 , 전역변수
     
    static void a() {
        i = 0; // 7행, 할당 , 지역변수
    }
 
    public static void main(String[] args) {
        for (i = 0; i < 5; i++) { // 11행, 할당 , 지역 변수
            a();
            System.out.println(i); // 무한 0 출력
        }
    }
 
}
  • 만약 위의 코드를 아래와 같이 바꾸면 위의 문제가 사라질 것이다.

  • 우선 메소드만 놓고 봤을 때 메소드 안에서 선언한 변수는 그 메소드가 실행될 때 만들어지고, 그 메소드가 종료되면 삭제된다. 만약 클래스 아래의 변수와 메소드 아래의 변수가 같은 이름을 가지고 있다면 메소드 아래의 변수가 우선하게 된다. 메소드 내의 변수가 존재하지 않을 때 클래스 아래의 변수를 사용하게 되는 것이다.

  • 즉 클래스 아래에서 선언된 변수는 클래스 전역에 영향을 미치지만 메소드 내에서 선언된 변수는 클래스 아래에서 선언된 변수보다 우선순위가 높다고 할 수 있다. 클래스 전역에서 접근 할 수 있는 변수를 전역변수, 메소드 내에서만 접근 할 수 있는 변수를 지역변수라고 한다.

package org.opentutorials.javatutorials.scope;
 
public class ScopeDemo3 {
    static int i;
     
    static void a() {
        int i = 0; // 아니면 for문에 int i = 0으로 바꿔도 동일한 결과 출력 (for문의 중괄호 안에서 유효)
    }
 
    public static void main(String[] args) {
        for (i = 0; i < 5; i++) {
            a();
            System.out.println(i); 
            /*
            0
            1
            2
            3
            4
            */
        }
    }
 
}

3. 활용

  • 지역변수는 메소드 내에서만 접근이 가능하다. 따라서 title은 메소드 a에서만 유효하기 때문에 에러가 발생한다.
package org.opentutorials.javatutorials.scope;
 
public class ScopeDemo4 {
    static void a(){
        String title = "coding everybody";
    }
    public static void main(String[] args) {
        a();
        //System.out.println(title);
    }
 
}
  • 반복문에서 정의한 변수도 반복문 밖에서는 유효하지 않다. 주석 처리된 8행의 주석을 제거하면 에러가 발생한다. 반복문에서 선언된 변수 i는 반복문 밖에서는 유효하지 않기 때문이다
package org.opentutorials.javatutorials.scope;
 
public class ScopeDemo5 {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            System.out.println(i);
        }
        // System.out.println(i);
    }
 
}
  • 결과는 5다. 위의 예제는 메소드 a가 메소드 b를 호출하고 있는데 메소드 b에는 변수 i의 값이 존재하지 않는다. 따라서 메소드 내(b)에서 지역변수가 존재하지 않기 때문에 그 메소드가 소속된 클래스의 전역변수를 사용하게 된다.

  • 이러한 방식을 정적 스코프(static scope) 혹은 렉시컬 스코프(lexical scope)라고도 부른다. 즉 사용되는 시점에서의 유효범위(메소드 a의 i)를 사용하는 것이 아니라 정의된 시점에서의 유효범위(i = 5)를 사용하는 것이다.

  • 동적 스코프라는 것도 있다. 만약 메소드 b의 결과가 10이라면 메소드 b는 메소드 a의 유효범위에 소속된 것이라고 할 수 있다. 하지만 자바는 동적 스코프를 채택하지 않고 있다. 대부분의 현대적인 언어들이 정적 스코프 방식을 선택하고 있다.

package org.opentutorials.javatutorials.scope;
 
public class ScopeDemo6 {
    static int i = 5;
 
    static void a() {
        int i = 10;
        b();
    }
 
    static void b() {
        // int i = 30; // 만약 이렇게 넣어주면 i=30출력됨
        System.out.println(i);
    }
 
    public static void main(String[] args) {
        int i = 1;
        a();
    }
 
}
  • 아래 경우 에러 발생함
package org.opentutorials.javatutorials.scope;
 
public class ScopeDemo6 {
    // static int i = 5; (삭제/0
 
    static void a() {
        int i = 10;
        b();
    }
 
    static void b() {
        // int i = 30; (삭제)
        System.out.println(i);
    }
 
    public static void main(String[] args) {
        int i = 1;
        a();
    }
 
}

4. this

  • 결과는 20이다. 즉 메소드 안에서 선언된 변수 v가 지역 변수가 되면서 인스턴스 전역에서 유효한 인스턴스 변수 v의 값보다 우선순위가 높아지면서 20이 출력된 것이다.
package org.opentutorials.javatutorials.scope;
 
class C2 {
    int v = 10;
 
    void m() {
        int v = 20;
        System.out.println(v);
    }
}
 
public class ScopeDemo8 {
 
    public static void main(String[] args) {
        C2 c1 = new C2();
        c1.m();
    }
 
}
  • 메소드 m에서 인스턴스 변수 v에 접근하려면 어떻게 해야할까? this를 사용하면 된다.
    그 결과 메소드 m 안에서 인스턴스 변수 v를 사용할 수 있게 되었다. this는 인스턴스 자신을 의미하는 키워드라고 할 수 있다.
package org.opentutorials.javatutorials.scope;
 
class C3 {
    int v = 10;
 
    void m() {
        int v = 20;
        System.out.println(v); // Result : 20
        System.out.println(this.v); // Result : 10
    }
}
 
public class ScopeDemo9 {
 
    public static void main(String[] args) {
        C3 c1 = new C3();
        c1.m();
    }
 
}

5. 결론

  • 유효범위란 변수를 전역변수, 지역변수 나눠서 좀 더 관리하기 편리하도록 한 것이다. 객체라는 개념이 존재하지 않는 절차지향 프로그래밍에서는 모든 메소드에서 접근이 가능한 변수의 사용을 죄악시하는 경향이 있다. 전역적인 사용의 효용이 분명한 데이터에 한해서 제한적으로 전역변수를 사용하도록 하고 있는 것이다. 객체지향 프로그래밍은 바로 이런 문제를 극복하기 위한 노력이라도고 볼 수 있다. 즉 연관된 변수와 메소드를 그룹핑 할 수 있도록 함으로서 좀 더 마음놓고 객체 안에서 전역변수를 사용할 수 있도록 한 것이다.

  • 어떤 메소드가 전역변수를 사용하고 있다는 것은 그 메소드는 그 전역변수에 의존한다는 의미다. 전역변수에 의존한다는 것은 이 메소드가 다른 완제품의 부품으로서 사용될 수 없다는 의미다. 객체지향 덕분에 좀 더 안심하고 전역변수를 사용하게 되었지만, 객체도 크기가 커지면 관리의 이슈가 생겨난다. 객체지향 프로그래밍에서도 가급적이면 전역변수의 사용을 자제하는 것이 좋고, 동시에 단일 객체가 너무 비대해지지 않도록 적절하게 규모를 쪼개는 것도 중요하다.

6. 출처

https://www.youtube.com/watch?v=7aDRGgIEgHY&t=890s

0개의 댓글