C++에서 배우는 자바-기초 문법(2)

전편에 이어 차이점들을 계속해서 정리해보겠다.

string

C++에서 string은 char의 배열과 동의어였다. 그렇기에 인덱스로 접근하여 값을 바꿔줄 수도, replace 등의 함수를 사용하여 값을 바꿀 수도 있고 여러모로 수정이 자유로웠다.
하지만 java에서 string은 수정할 수 없는 객체이다. 그렇기에 접근과 수정에 서브 메소드를 사용해야만 한다. 예를 들어

int main(void) {
    string s="hello";
    s[3]='c';
}  

c++에선 위와 같은 코드가 잘 작동한다. 인덱스를 통한 랜덤접근과 그의 수정이 대단히 자유롭다. 반면에 이 코드를 자바에 가지고 가면 에러가 난다. 같은 결과를 얻기 위해서

class cl {
    public static void main(String[] args) {
        String s="hello";
        String s1=s.replace("l","c");
    }
}

와 같이 코드를 짜고 실행시키면 우리는 hecco란 문자열을 얻게된다. 왜냐하면 replace 함수 또한 c++과는 달라 정해진 인덱스에서 얼마만큼을 대체하는 c++의 replace와 달리 java에서는 문자열에서 첫번째 매개변수를 모두 두번째 매개변수로 치환해준다.
또한 java에서 string은 수정 불가이기 때문에 기존 s 문자열이 바뀌는 것이 아닌 replace한 결과물을 s1이란 새로운 객체로 반환해준다.
따라서 원하는 결과를 얻기 위해선 stringbuilder, tochararray() 등의 특수한 방법을 사용하거나

class cl {
    public static void main(String[] args) {
        String s="hello";
        String s1=s.replaceFirst("l","c");
    }
}

replaceFirst 메소드로 바꿀 문자열 중 처음 것만 수정하도록 할 수 있다. c++과 비교하면 여러모로 복잡하다.

또한 string의 인덱스를 통한 랜덤접근이 불가능하기에 인덱스의 char값을 얻기 위해선 charAt() 함수를 사용해주어야 한다.

string의 비교 또한 다르다. c++에서는 ==을 사용하여 비교가 가능했지만 java에선 equals() 메소드를 사용하여 비교해야 한다는 것이 차이점이다.

별개로 java에선 new를 쓰지않고 리터럴로 string을 선언할 시 동일한 내용의 문자열이 존재했다면 그 문자열을 그대로 참조한다. 따라서 리터럴로 무분별하게 문자열을 선언 시 원치않는 결과가 나올 수 있음을 주의해야 한다.

이름 붙은 반복문

기본적으로 c++에서 break;를 사용한다면 반복문 하나만을 빠져나온다. 따라서 이중 반복문을 탈출하기 위해선 flag 등을 사용해야 하기에 코드가 살짝 복잡해지는데 java엔 반복문에 이름을 붙일 수 있다. 예를 들어

int main(void) {
    bool condition=false;
    for(int i=0; i<10; i++) {
        for(int j=0; j<10; j++) {
            //do something....
            if(condition==true) break;
        }
    }
}  

위 코드와 같이 이중 반복문에서 어떤 조건을 달성하고 반복문을 탈출하려고 할 때, 반복문 하나만을 탈출하기 때문에 바깥쪽 반복문은 따로 조건을 설정하여 탈출해주어야 한다. 그러나 java에선

class cl {
    public static void main(String[] args) {
        bool condition=false;
        Loop1 : for(int i=0; i<10; i++) {
            for(int j=0; j<10; j++) {
                //do something...
                if(condition==true) break Loop1;
            }
        }
    }
}

위의 코드와 같이 반복문에 이름을 붙일 수 있다. 이름을 붙인 반복문에 break, continue를 사용하여 중첩된 반복문도 편리하게 관리가 가능하다. 이 부분은 확실히 c++보다 편리한 장점인 것 같다.

배열

c++에서는 배열을 선언할 때 정해진 크기를 넣어 정적 할당을 해줬다. 하지만 java에선 static으로 선언되는 일부를 제외하곤 기본적으로 동적 할당을 하는 것 같다. 따라서 배열을 선언할 때에도 값을 넣어 초기화 하거나 new를 사용하여 동적 할당 해주어야 한다. 예를 들어

class cl {
    public static void main(String[] args) {
        int arr[]={1,2,3};
        int arr1[]=new int[30];
    }
}

위와 같이 원하는 값을 넣어 초기화 하거나 new를 사용하여 객체를 생성, 할당해야 한다.

그리고 배열 또한 위의 string과 클래스와 마찬가지로 참조 타입이므로 사용할 때 주의해야 한다. 예를 들어 배열을 복사할 때 c++에선

int main(void) {
    int arr[3]={1,2,3};
    int arr2=arr;
}  

위와 같이 작성하면 arr와 같은 값을 가진 arr2가 생성된다. 하지만 java에서 배열은 참조 타입이므로

class cl {
    public static void main(String[] args) {
        int arr[]={1,2,3};
        int arr1[]=arr;
    }
}

위와 같이 작성하게 되면 arr1은 arr와 같은 객체를 참조하는 참조 변수가 된다. 따라서 arr의 값을 변경하게 되면 arr1의 값도 변경된다. 이를 방지하기 위해서는 객체를 복사하는 Object의 하위메소드인 clone()을 사용하거나 Arrays.copyOf()를 사용하여 복사해줘야 한다.

class cl {
    public static void main(String[] args) {
        int arr[]={1,2,3};
        int arr1[]=arr.clone();
    }
}

위와 같이 작성하면 arr1은 arr의 값을 가진 새로운 객체를 참조하게 된다.

커맨드 라인

간단한 프로그램에선 따로 입출력 처리가 없어도 프로그램 실행시에 클래스 이름 뒤에 공백으로 구분하여 문자열을 입력함으로써 입력 처리를 할 수도 있다.
예를 들어

class cl {
    public static void main(String[] args) {
        System.out.println(args[0]);
    }
}

위와 같은 프로그램을 시스템에서 java main abc 를 입력하여 실행하면 args 배열에 abc가 입력되어 abc라는 출력을 얻을 수 있다.

가변 배열

이건 좀 신기했다. c++에선 기본적으로 정적 할당이기에 배열의 크기가 리터럴하게 정해져있다. 하지만 자바에선 2차원 배열 등에서 각 배열의 크기를 다르게 할 수가 있다. 예를 들어

class cl {
    public static void main(String[] args) {
        int arr[][]=new int[5][];
        arr[0]=new int[2];
        arr[1]=new int[3];
        arr[2]=new int[5];
    }
}

위와 같이 선언하면 가변 배열을 만들 수 있다. 어디에 사용할진 아직 잘 모르겠지만 아무튼 신기했다.

전역 변수가 없다

java에선 변수가 c++처럼 지역변수, 전역변수로 나뉘지 않는다. 대신 인스턴스 변수, 클래스 변수, 지역변수 세 가지로 나뉘게 되는데, 예를 들며 하나씩 알아보자.

class cl {
    int a=3;        //클래스
    static int b=4; //영역
    public static void main(String[] args) {
        int tmp=1;
    }
}

위의 코드를 보면, 클래스 내부는 클래스 영역으로 클래스 영역에서 선언되는 변수는 인스턴스 변수, 클래스 변수가 있다.
인스턴스 변수는 a가 예시인데, 클래스의 인스턴스가 생성될 때 만들어지는 변수이다.
클래스 변수는 클래스 영역 내에서 static을 붙여 선언한다. 모든 인스턴스가 공유하는 유일한 값이며, 인스턴스가 하나도 없더라도 사용 가능한 변수이다. 접근 지정자가 public일 시 전역변수와 같이 프로그램 전체에서 사용할 수 있다.
클래스 영역 이외에서 선언된 변수는 모두 지역 변수로 해당 메소드, 생성자 등이 종료되면 자동으로 소멸한다.

메소드

함수다. 정확히는 클래스, 구조체 등의 내부에 작성되는 함수를 메소드라고 하는데, java의 특성 상 모든 함수는 클래스 내부에 존재해야 하므로 자동으로 모든 함수는 메소드가 된다. 기능상의 차이는 없어보인다.

메모리 구조

c++에서 메모리 구조는 code, data, stack, heap으로 나뉜다. 하지만 java는 언어의 특성상 jvm을 무조건 거치게 되고, jvm은 OS로부터 할당받은 메모리를 heap, stack, static 세 부분으로 나누어 관리한다.
static 영역에서는 static을 붙여 선언된 것들, 메소드, 클래스의 정보 등이 저장되며 jvm 실행 시 클래스를 로딩하면서 메모리에 저장되고 프로그램 종료 시에 해제된다. 프로그램 어디에서든 접근이 가능하며 여러모로 static 변수의 특징과 동일하다.
heap 영역에는 new를 사용해 생성한 여러 객체들을 저장하며 참조형 변수들이 참조하는 데이터도 모두 heap에 저장되어 있다. 가비지 컬렉터에 의해 자동으로 해제되며 쓰레드가 여러 개여도 하나만 존재한다.
stack 영역에는 기본 자료형, 매개변수, 지역변수들이 저장되며 자료구조 stack의 형태를 하고 있다. 참조형 변수의 참조값도 stack에 저장되며 메소드가 호출 시 stack에 할당되었다가 종료 시 해제된다. 쓰레드마다 자신만의 stack을 하나씩 갖고있다.

나머지 내용들은 다음 포스팅에서 알아보도록 하겠다.