유효범위, 초기화 및 생성자

muz·2021년 3월 31일
0

Java

목록 보기
8/21
post-thumbnail

🙆🏻‍♀️ 유효범위

클래스 중심으로 알아보는 유효범위

유효범위(Scope)는 점차 커져가는 프로그램의 이름 충돌을 막기 위한 개념이라고 생각하면 쉽다.

public class Scope {
        static void a() {
            int i = 5;
        }
     
        public static void main(String[] args) {
            for (int i = 0; i < 5; i++) {
                a();
                System.out.println(i);
            }
        }
}

실행 결과는 0 1 2 3 4가 나온다. main()에서 메소드 a()를 호출하고 있어 i의 값이 계속 5가 출력되어야 할 것 같지만, a()에서 선언된 내부 변수의 값이 외부에서는 영향을 미치지 않음을 알수 있다.

스코프를 디렉터리에 비유해서 생각하면 쉽다. 디렉터리는 파일을 그룹핑해서 그룹별로 파일을 격리한다. 디렉터리 내에서 파일명이 중복되면 안되지만, 디렉터리 밖의 파일명과는 중복이 되어도 상관없다.

위의 코드에서 메소드a에 선언된 변수 i는 메소드a에 소속된 변수이다. 변수 i의 값을 계속 바꿔주어도 메소드a 내에서만 영향을 미칠 뿐, 메소드a를 벗어나는 밖의 영역에는 아무런 영향도 주지 않는다.

public class Scope2 {
        static int i;
         
        static void a() {
            i = 0;
        }
     
        public static void main(String[] args) {
            for (i = 0; i < 5; i++) {
                a();
                System.out.println(i);
            }
        }
}

하지만 위의 코드를 실행해보면 i값이 계속 0으로 무한 출력된다. 이는 static int i;가 클래스 변수i로 선언되었기 때문이다. 메소드 a의 변수 i와 for문의 변수 i가 동시에 클래스 변수i 를 사용하게 되어, 반복문으로 i의 값을 바꾸어도 메소드 a로 인해 클래스 변수i 값이 계속 0이 되므로 무한 반복이 되는 것이다.

public class Scope3 {
    static int i;
     
    static void a() {
        int i = 0;
    }
 
    public static void main(String[] args) {
        for (i = 0; i < 5; i++) {
            a();
            System.out.println(i);
        }
    }
}

그러나 위의 코드에서 메소드a에서 int i = 0;라고 새롭게 선언 및 대입이 주어지면, 메소드a의 변수i는 클래스 변수i와 다르게 인식되어 main()의 i에 영향을 미치지 못한다. 이로써 알 수 있는 2가지 사실이 있다.

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

즉, 클래스 아래에서 선언된 변수는 클래스 전역에 영향을 미치지만 메소드 내에서 선언된 변수는 클래스 아래에서 선언된 변수보다 우선순위가 높다. 그러므로 지역적인 것 > 전역적인 것이 되어 전역적으로 기본 값을 설정하고, 필요에 따라서 지역 값을 다르게 사용하는 것이 더 효율적이게 된다.

  • 전역 변수 : 클래스 전역에서 접근할 수 있는 변수
  • 지역 변수 : 메소드 내에서만 접근 가능한 변수
public class Scope2 {
        static int i = 5;
     
        static void a() {
            int i = 10;
            System.out.println("메소드a의 변수 i값은 : " + i);            
            b();
        }
     
        static void b() {
            System.out.println("메소드b에서 읽는 변수 i의 값은 : " + i);
        }
     
        public static void main(String[] args) {
            a();
        }
}

위의 코드를 실행해보자. 결과는 10과 5가 나온다. 메소드a가 메소드b를 호출하고 있지만 메소드b에는 변수 i의 값이 존재하지 않는다. 이 상태에서 메소드a를 호출하면 우선 메소드a에서 i값이 10이므로 10이 출력되고, b를 호출하려고 메소드b에 가보니 i값이 따로 없어서 클래스 변수i의 값 5를 가져와서 출력한다.

이러한 방식을 정적 스코프(static scope) 또는 렉시컬 스코프(lexical scope)라고 부른다. 이는 사용되는 시점에서 유효범위(메소드 a의 i)를 사용하는 것이 아니라, 정의된 시점에서 유효범위(i=5)를 사용하는 것이다. (동적스코프도 있지만, 이는 자바가 채택하지 않는 방식이다.)

인스턴스에서의 유효범위

인스턴스에서의 유효범위는 클래스와 거의 동일하나 결정적인 차이점은 this에 있다.

class C {
    int v = 10;
 
    void m() {
    	// int v = 20;
        System.out.println(v);
    }
}
 
public class Scope3 {
 
    public static void main(String[] args) {
        C c1 = new C();
        c1.m();
    }
 
}

위의 코드를 컴파일하면 10이 출력된다.
이 코드에서 클래스C의 메소드m에 int v = 20;을 추가한 후 다시 컴파일하면 20이 출력된다. 이는 메소드 안에서 선언된 변수 v가 지역 변수가 되면서 인스턴스 전역에서 유효한 인스턴스 변수v 값보다 우선순위가 높아졌기 때문이다.

이런 상황 속 메소드 m에서 인스턴스 변수 v에 접근하려면 this를 사용하면 된다.

class C3 {
    int v = 10;
 
    void m() {
        int v = 20;
        System.out.println(this.v);
    }
}
 
public class Scope4 {
 
    public static void main(String[] args) {
        C3 c1 = new C3();
        c1.m();
    }
 
}

메소드m에서 v를 출력하는 것이 아닌 this.v를 출력했으므로 10이 출력된다. 이렇게 작성해줌으로써 메소드m안에서 인스턴스 변수 v를 사용할 수 있게 되었다.

this는 인스턴스 자신을 의미하는 키워드

🙋🏻‍♀️ 초기화

일을 시작하기 전에 준비를 하는 것을 초기화라고 할 수 있다. 객체 지향 프로그래밍도 초기화에 해당하는 기능들이 있다. 이것을 생성자(constructor)라고 한다.

객체를 이용하기 위한 로직을 살펴보자.

Calculator c1 = new Calculator();
c1.setOprands(10,20);
c1.sum();
c1.avg();

객체를 이용하기 위해서는 setOprands를 먼저 호출해야만 sum과 avg를 호출하여 원하는 결과를 얻을 수 있다. 이러한 호출 절차를 기억해야 한다는 것은 많은 불편함을 초래하기에 생성자(Constructor)를 사용하는 것이다.

🙋🏻‍♀️ 생성자

인스턴스가 생성될 때 left, right 값을 입력하도록 강제하면 어떨까?

Calculator c1 = new Calculator(10,20);
c1.sum();
c1.avg();

위처럼 Calculator 사용방법을 변경하려면 코드를 약간 수정해주어야 한다.

class Calculator {
    int left, right;
 
    public Calculator(int left, int right) {
        this.left = left;
        this.right = right;
    }
 
    public void sum() {
        System.out.println(this.left + this.right);
    }
 
    public void avg() {
        System.out.println((this.left + this.right) / 2);
    }
}
 
public class CalculatorDemo1 {
 
    public static void main(String[] args) {
 
        Calculator c1 = new Calculator(10, 20);
        c1.sum();
        c1.avg();
 
        Calculator c2 = new Calculator(20, 40);
        c2.sum();
        c2.avg();
    }
 
}

위의 코드 중, 생성자가 바로 이 부분이다.

public Calculator(int left, int right) {
        this.left = left;
        this.right = right;
    }

생성자는 객체를 생성할 때 호출된다. 생성자를 이용해서 객체를 생성하려면 Calculator c1 = new Calculator(10,20); 이렇게 입력해주면 된다.
생성자를 추가하면 left와 right에 값을 설정하는 과정을 축약할 수 있다.

🎃 생성자의 특징

  • 값을 반환하지 않음
    : 생성자는 인스턴스를 생성해주는 역할을 하는 특수한 메소드와 같다. 반환 값이 없으므로 return도 사용하지 않고, void 형태로 작성한다.
  • 생성자의 이름은 클래스의 이름과 동일함
    : 자바에서 클래스의 이름과 동일한 메소드는 생성자로 사용하기로 약속되어 있다!

Reference

profile
Life is what i make up it 💨

0개의 댓글