본문 바로가기

개발/Java

자바를 배우고 싶은가?(2)

1장 변수, 데이터형

자바에서 사용할 수 있는 변수에는 몇가지 종류가 있는지 알고 있는가? 여러분은 당연히 알고 있을 것이다. 그렇다면 그 변수들의 유효 범위가 어떻게 되는지, 어떤 변수의 속도가 더 빠른지도 알고 있는가? 아마 시원스레 답하지 못하는 사람이 많을 것 같다. 이것이 필자만의 우려라면 좋겠지만 필자의 주변에는 코드를 작성하면서도 왜 그렇게 사용하는지에 대해서 생각해보지 않은 개발자가 많았으며, 알아야 할 필요성 자체도 느끼지 못한 경우도 많았다. 하지만 우리의 목적은 자바를 좀 더 자바답게 사용하기 위한 것이 아닌가? 좀 더 나은 코드를 작성할 수도 있도록 말이다. 이 장에서는 자바 프로그램을 하기 위해 필요한 가장 기초적인 변수와 데이터형에 대해서 살펴보도록 하겠다.
1. 가장 빠른 변수는 지역 변수이다.

변수는 선언되는 위치에 따라 지역 변수, 매개 변수, 인스턴스 변수, 클래스 변수로 구분된다. 먼저 지역 변수는 메소드 내에 선언되어 선언된 위치부터 메소드 끝까지 접근을 허용하며, 매개 변수는 메소드 선언부에 선언되어 메소드 전체에 접근을 허용한다. 그리고 인스턴스 변수는 클래스의 메소드 레벨에 선언되어 해당 인스턴스 내에서만 접근을 허용하며, 클래스 변수는 선언된 클래스의 모든 인스턴스로부터의 접근을 허용한다. 이렇듯 변수들은 선언된 위치에 따라 접근 허용 범위가 서로 다르다. 이러한 접근 허용 범위를 작은 순부터 큰 순으로 나열하면 다음과 같다.

그림 1-1 변수의 접근 허용 범위
접근 허용 범위가 작다는 것은 관리할 부분이 적다는 것을 의미한다. 예를 들어 메소드 내에 선언하는 지역 변수는 메소드 내에서만 사용하고 버리면 되지만, 클래스 변수는 유효한 인스턴스가 하나라도 존재한다면 계속해서 관리해야 한다. 이러한 이유로 인해 접근 범위가 작은 변수가 접근 범위가 큰 변수보다 더 나은 성능을 보인다. 따라서 지역 변수로 사용할 수 있는 것을 클래스 변수로 선언하여 성능을 저하시키는 일을 하지 말아야 한다.
2. 멤버 변수를 중복 초기화하지 않는다.

메소드와 같은 레벨에 선언하는 멤버 변수는 다음과 같이 초기값을 설정할 필요가 없다. 왜냐하면 멤버 변수는 객체가 생성되면서 디폴트 값으로 초기화되기 때문이다. 따라서 다음과 같은 코드는 초기화 부분을 제거하고 작성할 수 있다.
예외적으로 멤버 변수의 값이 디폴트 값이 아닌 다른 값으로 설정되어야 하는 경우라면 명시적으로 초기화해야 할 것이다. 하지만 그렇지 않은 경우에는 불필요하게 직접 초기화 코드를 작성하지 말고 자바가상머신이 알아서 초기화하도록 하는 것이 더 좋다.
3. 멤버 변수의 디폴트 값에 대해 알아야 한다.

멤버 변수는 자동 변수와는 달리 디폴트 값으로 초기화된다. 초기화되는 디폴트 값은 다음과 같다.
※ Reference type 은 String, Vector 와 같은 자바의 객체를 말한다.
4. 블록 내에서 사용할 변수는 지역 변수로 선언해야 한다.

메소드와 같은 블록 내에서만 사용할 변수를 멤버 변수로 선언하면 가독성도 떨어지고 지역 변수(자동 변수)로 선언할 때보다도 처리 속도가 떨어진다. 그렇기 때문에 블록 내에서 사용할 변수는 메소드의 스택 영역에 생성되어 빠르게 접근하여 사용할 수 있는 지역 변수로 선언해서 사용해야 한다.

멤버 변수로 선언된 sum 변수가 addValue() 메소드에서만 임시적으로 사용하는 변수라면 다음과 같이 코드를 작성할 필요는 없다.

public class Test {
    public int sum;
   
    public int addValue(int a, int b) {
        sum = a + b;
        return sum;
    }
}

다음과 같이 sum 변수를 addValue() 메소드 안에 선언하거나 sum 변수를 선언하지 않고 바로 a + b 를 반환하도록 코드를 작성하는 것이 더 나은 코드이다.

1) addValue() 메소드에 sum 변수 선언
public classTest {
    public int addValue(int a, int b) {
        int sum = a + b;
        return sum;
    }
}

2) a + b 결과를 바로 반환
public classTest {
    public int addValue(int a, int b) {
        return a + b;
    }
}

5. 자동 변수를 사용할 때는 항상 초기값을 설정해야 한다.

자동 변수는 멤버 변수와는 달리 디폴트 값으로 알아서 초기화되지 않기 때문에 선언할 때 초기값을 직접 설정해야 한다. 또는 해당 변수를 사용하기 전에 초기화를 먼저 해야 한다. 그렇지 않고 자동 변수를 사용하면 컴파일시 에러가 발생한다. 그렇기 때문에 자동 변수는 사용하기 전에 항상 초기값을 설정해야 한다.

다음 코드는 자동 변수에 초기값을 설정하지 않았기 때문에 컴파일시 에러가 발생한다.

public class Test {
    public static voidmain(String[] args) {
        int i; //초기값을 설정하지 않음
 
        System.out.println(i);
    }
}

Exception in thread "main" java.lang.Error: Unresolved compilation problem:
       The local variable i may not have been initialized
       at Test.main(Test.java:5)

이 코드를 에러가 발생하지 않도록 수정하면 다음과 같이 자동 변수인 i 에 초기값을 설정해야 한다. 여기서는 임의로 0을 설정하였다.

public class Test {
    public static voidmain(String[] args) {
        int i = 0; //초기값을 0으로 설정함
 
        System.out.println(i);
    }
}
프로그램을 작성하다 보면 변경되지 않는 고정 값인상수를 선언해야 하는 경우가 있다. 이때 상수의 의미를 쉽게 이해할 수 있게 한다는 목적으로 String 으로 선언하여 사용하는 경우가 있는데 이는 기본 데이터형을 사용하는 경우보다 비효율적이다. 왜냐하면 String 은 기본 데이터형과는 달리 내부적으로 복잡한 객체이기 때문이다.

아래는 각 지역을 표시하는 문자열 상수를 가진 인터페이스이다.

public interface Area {
    public staticfinal StringSEOUL= "SEOUL";
    public staticfinal StringPUSAN= "PUSAN";
    public staticfinal StringWONJU = "WONJU";
}
상수 이름으로 지역 이름을 파악할 수 있기 때문에 굳이 그 값을 문자열로 할 필요는 없다.

이 코드에서는 각 지역을 문자열로 표현하고 있다. 문자열을 비교할 경우에는 equals() 메소드를 사용해서 비교해야 하며 지역이 많아질 수록 많은 객체가 필요하다. 하지만 이 코드를 다음과 같이 기본 데이터형인 int 로 선언하면 객체를 사용하는 경우보다 좀 더 효율적인 코드가 된다.

public interface Area {
    public staticfinal intSEOUL= 0;
    public staticfinal intPUSAN= 1;
    public staticfinal intWONJU = 2;
}

이 코드는 각 지역을 모두 정수형으로 표현하고 있다. 첫 번째 방식과 마찬가지로 변수 이름으로 접근하여 사용할 수 있기 때문에 사용하는 입장에서는 첫 번째 방식과 다른 점이 없다. 단지 내부적으로 문자열이 아닌 숫자로 처리된다는 것만 다를 뿐이다. 그러나 이렇게 코드를 작성하면 문자열이 아닌 정수로 값을 표현하기 때문에 메모리를 절약할 수 있으며, 해당 지역을 equals() 메소드 대신 "==" 연산자를 사용해서 비교할 수 있어 좀 더 빠른 속도를 얻을 수 있다.
7. 상수는 사용시 주의해야 한다.

final 제한자를 변수에 선언하면 상수가 된다. 상수는 프로그램 실행 중에 절대로 변경할 수 없기 때문에 프로그램 동작 중에 변경되서는 안되는 변수들에 final 를 선언하여 상수로 만든다. 하지만 이 상수를 사용함에 있어서 주의할 점이 있다. 먼저 아래 Common 클래스를 보도록 하자.

class Common {
    public static final boolean DEBUG = true;
}

Common 클래스의 DEBUG 상수는 모든 클래스에서 참조하기 위한 상수로서 이 값이 true 일 경우에는 디버그 코드를 실행하고, 그렇지 않을 경우에는 디버그 코드를 실행하지 않도록 하기 위해서 사용하고자 한다. 이 값을 개발 중에는 true 로 사용하다가 개발이 끝난 뒤에 false 로 변경하고 Common 클래스만 컴파일하면 어떻게 될까? 다른 클래스에서는 Common 클래스의 DEBUG 상수가 false 로 설정된 것을 알지 못하기 때문에 여전히 디버그 코드를 실행할 것이다. 이렇게 되는 이유는 컴파일러가 상수를 사용하는 모든 클래스의 코드를 상수 참조가 아닌 상수 값으로 변경하기 때문이다.

다음과 같이 DEBUG 상수를 사용하는 코드가 있다고 했을 때 DEBUG 가 true 라면 화면에 지정된 문자열이 출력될 것이다.

if(DEBUG) System.out.println("지금은 디버그중입니다.");

하지만 컴파일러는 DEBUG 를 DEBUG 변수가 가지고 있는 값으로 치환하기 때문에 위의 코드는 다음과 같이 true 가 된다.

if (true) System.out.println("지금은 디버그중입니다.");

그렇기 때문에 Common 클래스의 상수를 false 로 변경하더라도 DEBUG 변수를 사용하는 다른 클래스의 코드는 변경하기 이전의 값인 true 를 그대로 갖고 있게 된다. 그래서 아무리 Common 클래스의 상수의 값을 변경하더라도 다른 클래스에는 제대로 적용되지 않는 것이다. 이 문제를 해결하기 위해서는 해당 상수에 선언된 final 을 제거하여 일반 변수로 선언하거나 상수를 사용하는 모든 클래스를 다시 컴파일해야 한다.
1장 8. 변수는 되도록 사용 시점에 선언해야 한다.

어떤 프로그래밍 언어는 변수를 필요한 시점에 선언하는 것이 아니라 반드시 최상단에 선언해야 하는 경우가 있다. 이는 소스 코드의 의미를 파악하기 위해서는 항상 최상단에 선언되어 있는 변수가 어떤 데이터형으로 선언되어 있는지 살펴봐야 한다는 것을 의미한다. 변수를 살펴보기 위해서 화면을 위 아래로 왔다갔다 하는 것은 짧은 소스 코드에서는 큰 문제가 없겠지만 소스 코드가 길 경우에는 매우 번거로운 일이다. 그렇기 때문에 자바를 이러한 언어처럼 작성하여 불편함을 가중시키기보다는 사용 시점에 변수를 선언해서 해당 변수의 데이터형을 바로 파악할 수 있도록 해야 한다.

모든 변수를 최상단에 선언하지 않고 필요할 때마다 변수를 선언해서 사용하면 코드를 파악하는 시간을 많이 줄일 수 있다. 그래서 자바로 코드를 작성하는 경우 많은 개발자들이 변수 선언은 필요한 시점에 하고 있다.

권장하지 않는 방법
int result = 0;
int a = 0;
int b = 0;
 
// 선언된 변수와 상관없는 소스 코드
 

result = a + b;


권장하는 방법
// 선언된 변수와 상관없는 소스 코드
 
int a = 0;
int b = 0;
int result = a + b;
1장 9. 객체보다는 기본 데이터형을 사용해야 한다.

객체 지향 언어인 자바에서 왜 기본 데이터형을 제거하지 않고 계속 유지하는 것일까? 그 이유는 기본 데이터형을 객체로 처리할 경우 많은 성능 저하를 일으킬 수 있기 때문이다. 그렇기 때문에 기본 데이터형으로 처리할 수 있는 데이터를 굳이 객체로 처리하여 불필요하게 처리 비용을 가중시킬 필요는 없다.

다음 소스 코드를 보자.
String num1 = "1";
String num2 = "2";

이 코드는 숫자 데이터를 처리하기 위해 String 객체를 사용하고 있다. 해당 데이터를 문자열 데이터로 처리해야 할 특별한 이유가 있는 경우가 아니라면 String 객체를 기본 데이터형으로 바꾸는 것이 현명한 선택이다. 왜냐하면 객체는 객체 생성, 초기화 등의 여러 작업을 거쳐야 하지만 기본 데이터형은 그러한 작업이 필요하지 않기 때문이다. 그렇기 때문에 이 코드를 다음과 같이 고치는 것이 좀 더 낫다.

int
 num1 = 1;
int num2 = 2;