{{ label!='' ? 'Label : ' : (q!='' ? '검색 : ' : '전체 게시글') }} {{ label }} {{ q }} {{ ('('+(pubs|date:'yyyy-MM')+')') }}

[Java] 자바로 프로그래밍 입문하기: 3.1. 자료형 (4)

 그림 자료형

  Picture 자료형을 사용해봤을 때, 우리는 여러 개의 그림을 생성할 수 있었습니다. Picture에 정의된, Picture 객체를 다루는 연산들을 사용할 수 있었기 때문이죠. 따라서 StdDraw로 생성된 객체들을 다룰 수도 있으면 좋겠죠? Draw 자료형은 다음 API를 구현합니다.


  다른 자료형과 다르지 않게, new 키워드를 이용해 Draw 객체를 생성할 수 있습니다. 변수로 선언하고, 변수의 이름을 이용해 메소드를 호출할 수 있습니다. 다음 코드를 참고하세요.


Draw d = new Draw();
d.circle(.5, .5, .2);

  이 코드는 여러분의 화면 윈도우 가운데에 원을 그립니다. Picture와 같이, 각 그림은 각자의 윈도우에 그려지며, 따라서 수많은 그림들을 다룰 수 있죠.


참조형의 속성

  참조형에 대한 다양한 예제들을 알아보았고, 클라이언트 프로그램으로 그것들을 이용해봤으며, 이제는 참조형의 필수적인 속성들에 대해 알아보려고 합니다. 대부분의 경우에서 자바는 초보 프로그래머들이 이러한 세부사항에 대해 알지 못하도록 보호합니다. 숙련자 프로그래머들은 이러한 속성들을 이해하는 것이 객체지향 프로그래밍을 효과적이고 효율적으로 다루는데 도움이 됨을 알고 있죠. 그래서 여러분들에게도 소개해드리려고 합니다.

  참조라 함은, '무언가'와 '무언가의 이름' 사이의 차이를 말하는 것입니다. 


  한 객체가 여러 이름을 가질 수도 있습니다. 하지만 각 객체는 단 하나의 아이덴티티만을 지니고 있죠. 우리는 객체의 값을 변화시키지 않더라도, 객체의 새로운 이름을 무한하게 지어줄 수 있습니다.(대입문) 하지만 객체의 값이 변할 때,(인스턴스 메소드) 모든 객체의 이름들은 해당 객체가 바뀐 모습을 참조하게 되죠.

  여러분이 집을 리모델링하고 싶다 해보죠. 여러 리모델링 업자들에게 여러분의 집 주소를 종이에 연필로 적어 건내주기로 했습니다. 수많은 리모델링 업자들 중에 한 팀을 골라 계약했고, 집이 새롭게 변했습니다. 하지만 여러분이 집 주소를 적었던 종이들은 변하지 않습니다. 한 리모델링 업자가 여러분의 집 주소를 지우고 다른 집의 주소를 적더라도, 그 종이만 변할 뿐이지 다른 종이들은 변하지 않습니다.

  자바의 참조는 이런 종이들과 같습니다. 참조를 변경하는 것은 객체를 변경하는 것이 아닙니다. 하지만 객체를 변하게 하는 것은 해당하는 모든 참조에 영향을 미치게 됩니다.

르네 마그리트의 <이미지의 배반>

  벨기에의 화가 르네 마그리트는 이와 똑같은 개념을 그의 그림에 녹여냈습니다. 파이프를 그려놓고, 그 아래에 '이것은 파이프가 아닙니다'라고 설명을 적어놨죠. 보통은 단순히 그림이고, 실제 파이프가 아니라는 의미로 받아들이게 됩니다. 어쩌면 마그리트는 이것이 파이프도, 파이프의 그림도 아닌, 단순 '설명'에 불과하다는 의미로 썼을지도 모르죠! 어쨌거나, 이 그림을 통해 우리는 참조라는 것이 단순히 참조에 불과하다는 것을 명확히 해야 합니다. 객체 그 자체가 아니라는 것이죠.


Aliasing

  대입문에 참조형을 사용하는 것은 곧 참조의 복사본을 만드는 것입니다. 이 때 대입문은 새로운 객체를 만들지 않죠. 단순히 한 객체의 다른 참조를 만들어낸 것입니다. 이러한 상황을 aliasing이라고 합니다. 두 변수가 같은 객체를 참조하는 것이죠. aliasing의 영향은 예상치 못한 곳에서 일어납니다. 기본 자료형의 값과는 다른 이야기이기 때문이죠. 이 차이를 꼭 이해하세요.

  x와 y 변수들이 기본 자료형 변수라면, 대입문 x = y는 y값을 복사해 x에 대입하는 것입니다. 참조형 변수라면 참조가 복사될 뿐, 값이 복사되지는 않습니다. Aliasing은 흔한 자바 프로그램 버그의 원인이 됩니다. 다음 예제를 봐보죠.

Picture a = new Picture("mandrill.jpg");
Picture b= a;
a.set(i, j, color1);  // a is updated
b.set(i, j, color2);  // a is updated again

  a와 b는 같은 Picture 객체를 참조하게 됩니다. 객체 변경 시 그 영향은 그것을 참조하는 모든 변수에 미치게 되죠. 기본 자료형 변수였다면 이것이 독립적으로 움직였겠지만, 참조형은 그렇지 않다는 사실을 명심하세요. aliasing 버그는 참조형을 자주 다뤄보지 않은 사람들이 흔히 발생시키는 버그입니다. 그건 바로 여러분이죠. 주의하세요!

Aliasing



불변 자료형(Immutable types)

  바로 이러한 이유로, 절대 값이 변하지 않는 자료형을 선언하는 경우도 있습니다. 자료형에 해당 객체를 변경할 수 있는 메소드가 전혀 없을 때, 이를 불변 객체라고 합니다. 예를 들어, 자바의 Color와 String 객체는 불변입니다. 하지만 Picture 객체는 가변이죠. 3.3절에서 이를 심도있게 다뤄볼 것입니다.


객체 비교

  == 연산자를 이용해서 두 참조가 같은 객체를 참조하는지 확인할 수 있습니다. 하지만 이는 객체가 같은 값을 지녔는지 확인하는 것과 다른 이야기입니다. 다음 코드를 봐보죠.

Color a = new Color(142, 213, 87);
Color b = new Color(142, 213, 87);
Color c = b;

  여기서 (a == b)는 거짓이지만, (b == c)는 참입니다. 이러한 연산이 Color가 동일한지 검사하는 것이었다면, 아마 둘다 참이어야겠죠. 자바에는 각 객체의 동일성을 자동으로 확인해주는 메커니즘이 없습니다. 다만 여러분들이 equals() 메소드를 구현함으로써 동일성을 확인하는 방법을 제공할 수 있죠. 이 역시 3.3절에서 심도있게 다뤄볼 것입니다. 참고로 Color는 이러한 메소드를 구현하며, a.equals(c)는 참입니다. String 역시 동일성을 확인할 때에는 equals() 메소드를 이용해서 비교해야 함을 잊지 마세요.


Pass by value

  우리가 메소드를 인자값과 함께 호출할 때, 자바는 인자 값을 복사하여 메소드에 전달합니다. 이러한 방식을 pass by value라고 합니다. 이렇게 되면 메소드는 호출자의 변수 값을 변경할 수 없죠. main에 선언된 기본 자료형 변수 a를 f()라는 메소드에 인자로 넘겨주고, f()에서 그것을 아무리 변경하여도, 이는 복사된 값이므로 main의 a에 아무런 영향을 끼치지 않습니다.

  하지만 참조형을 인자로 사용할 때에는 조금 결과가 다릅니다. 이는 곧 alias를 만들어내는 것이기 때문이죠. 따라서 주의해야 합니다. 동일하게 참조형의 값을 복사하여 전달되지만, 이는 결국 객체의 참조를 전달하는 것과 다를 바 없습니다. 예를 들어, Picture 객체의 참조를 전달하면, 해당 메소드에서 원본 참조를 변경할 수는 없습니다. 하지만 객체의 값은 변경할 수 있죠. 이는 곧 원본 참조에 영향을 미칠 것입니다.


배열은 객체다.

  자바에서 기본 자료형이 아닌 값들은 전부 객체입니다. 배열 또한 객체죠. 문자열처럼 언어 차원의 특별 대우를 받고 있을 뿐입니다. 배열을 인자로 전달하거나 대입문을 사용하면, 다른 참조형들과 똑같은 결과가 이루어집니다. 배열의 복사가 아닌 참조의 복사가 이루어지죠.


객체의 배열

  배열에는 어떤 자료형이든 담을 수 있습니다. 우리가 이미 봐왔듯이, args[]에는 String 객체를 담고 있죠. 우리가 객체의 배열을 생성할 때에는 두 단계를 거쳐야 합니다.

  1. 배열을 생성한다.(객체들이 들어갈 방을 마련합니다)
  2. 배열의 각 방에 객체를 생성한다.(객체를 입주시킵니다)

Charge[] a = new Charge[2];
a[0] = new Charge(.51, .63, 21.3);
a[1] = new Charge(.13, .94, 85.9);

  당연히 객체의 배열 또한 배열의 참조입니다. 객체들 그 자체가 아니죠. 객체가 거대하다면, 그 객체들을 직접 움직이는 것보다는 참조를 이용하는 것이 좋겠죠. 반면 객체가 작다면, 값을 얻기 위해 계속 참조를 따라가야 하므로 상대적으로 효율이 떨어질 것입니다.


Safe pointers

  자료를 참조하는 메모리 주소를 생성할 수 있도록, 많은 프로그래밍 언어는 포인터라는 개념을 포함합니다. 자바의 참조와 비슷한 개념이죠. 포인터를 이용한 프로그래밍은 오류에 쉽게 노출되기로 악명이 높습니다. 따라서 포인터를 제공하는 연산들은 신중하게 설계돼야만 합니다. 

  자바는 이러한 관점을 극단적으로 받아들였습니다. 자바에서 참조를 생성하는 방법은 단 하나(new)이며, 참조를 변경하는 방법 역시 단 하나(대입문)입니다. 즉, 참조에 관해서 프로그래머가 할 수 있는 것은 오로지 '생성'과 '복사' 뿐이라는 의미입니다. 프로그래밍 언어 용어로, 이러한 자바의 참조를 safe pointers라고 말합니다. 자바는 각 참조가 특정 자료형의 객체만 가리킴을 보장할 수 있기 때문이죠.

  포인터를 직접 다루는 프로그래머들은 자바에 포인터 개념이 전혀 없다고 말하곤 합니다. 한편, 안전하지 않은 포인터가 진정 프로그래밍에서 바람직한 개념인지는 여전히 논란의 대상입니다. 자바에서는 직접 포인터 값에 접근하는 일은 없을 것이지만, 미래에 다른 언어를 통해 그러한 일을 하게 된다면, 조심하게 다루세요!


고아 객체(Orphaned objects)

  참조 변수에 새로운 값을 대입하면, 특정 객체가 더이상 참조되지 않을 가능성이 생깁니다. 예를 들어, 다음 그림처럼 세 대입문이 있다고 생각해보죠. 세 번째 대입문이 실행되면, a와 b는 같은 Color 객체를 참조할 뿐만 아니라 b에 초기화되었던 객체는 더이상 참조될 수 없습니다. 해당 객체를 참조할 방법이 전혀 없죠. 이러한 객체를 고아 객체라고 합니다. 자바는 이러한 고아 객체들에 신경을 써줘서, 고아 객체가 만들어지면 시스템이 자동적으로 메모리에서 삭제합니다.

고아 객체


메모리 관리

  프로그램은 보통 수많은 객체를 생성할 것이지만, 어느 시점에서는 그 중 일부만 다루게 될 때가 있을 것입니다. 프로그래밍 언어와 시스템은 메모리를 할당하였다면, 다시 소멸시켜줘야 할 의무가 있습니다. 기본 자료형들에 대한 메모리 관리는 굉장히 쉽습니다. 컴파일 시기 때 모든 메모리 할당에 대한 정보를 확인할 수 있기 때문이죠.

  자바(와 그 외 많은 시스템)에선 변수가 선언될 때가 돼서야 공간을 할당하고, 변수를 다 사용하면 공간을 소멸시킵니다. 따라서 객체에 대해서는 좀 더 복잡해지죠. 컴파일러는 객체가 언제 생성될 지는 알고 있지만, 언제 그 객체가 소멸되어야 하는지는 알 수 없습니다. 프로그램이 동적으로 그 시기를 결정할 수 있기 때문이죠.


메모리 누수

  많은 언어(특히 C나 C++)에서 프로그래머들은 메모리 할당과 소멸의 책임이 있습니다. 이는 굉장히 짜증날 뿐더러 오류의 원인으로 악명 높죠. 예를 들어, 어떤 프로그램이 특정 객체에 대한 메모리를 소멸시켰는데, 실수로 여전히 그 참조를 지니고 있다고 생각해보죠. 이후에, 시스템은 소멸된 해당 메모리를 다른 객체로 활용하기 위해 값들을 할당했을 것입니다. 만약 프로그램이 그 사실을 모르고 갖고 있던 참조로 그 값을 변경시킨다면, 이는 대혼란을 야기할 것입니다. 

  이런 혼란을 피하기 위해서, 프로그래머가 고아 객체들을 소멸시키는 행위를 신경쓰지 않는다고 생각해보죠. 고아 객체가 생겨나면 생겨날수록, 시스템에서 사용할 수 있는 메모리의 공간은 점점 줄어들 것입니다. 이러한 버그를 메모리 누수라고 말합니다. 이는 곧 성능 저하로 이어지죠. 컴퓨터를 사용하다가 점점 느려져서 이상함을 느끼고 재부팅을 해본 적 있나요? 아마 특정 응용프로그램이 메모리 누수를 일으켰을 가능성이 높습니다.


가비지 컬렉션(Garbage collection)

  자바의 특징 중 하나로 꼽히는 것은 바로 자동으로 메모리를 관리해준다는 것입니다. 이러한 개념은 프로그래머들이 지긋지긋한 메모리 관리로부터 벗어날 수 있게 해주죠. 이를 가비지 컬렉션이라고 말하며, 자바의 safe pointer 정책이 이를 가능하게 만들어주는 것입니다. 

  가비지 컬렉션은 굉장히 오래된 개념입니다. 프로그래밍 세계에서 재빠르게 적용되지 않았던 이유는 가비지 컬렉션으로 인해 시스템이 느려지는 것과 메모리 누수에 대한 걱정이 없어지는 것 사이에서 수많은 논쟁이 있었기 때문이죠. 어쨌거나 자바는 가비지 컬렉션 덕분에 메모리 관리에 신경쓸 필요가 없습니다. 다른 언어에서 메모리 관리를 해야 한다면, 꼭 조심하세요!


  "자료형은 값의 집합과 그 값에 정의된 연산의 집합입니다." 기본 자료형으로는, 작고 간단한 값의 집합으로서만 다루었죠. 색, 그림, 문자열, 입출력 스트림 등 높은 수준의 자료형들을 다룰 수 있었습니다. 

  "자료형을 사용하기 위해 구현된 방법을 알 필요는 없습니다." 각 자료형은 API를 통해 그 특성들과 사용법을 파악할 수 있습니다. 클라이언트 프로그램은 단순히 객체를 생성하고 인스턴스 메소드를 호출하여 값을 다루기만 하면 됩니다. 이러한 능력을 사용하는 것은 곧 프로그래밍에 대한 새로운 지평선을 여는 것입니다.

  자료형은 클라이언트 프로그램을 명료하게 만들고, 개발하기 쉽게 만들며, 유지보수하기 쉽게 만듭니다. 이번 절에서 다루었던 클라이언트 프로그램들은 이러한 주장의 근거입니다.

  나아가, 다음 절에서는 자료형을 직접 구현해볼 것입니다. 특히, 거대하고 복잡한 응용프로그램을 작성함으로써, 각 자료들과 필요한 연산들을 이해하고 그 이해를 프로그램으로서 나타내보는 시간을 가져볼 것입니다. 


끝.

댓글

이 블로그의 인기 게시물

[코딩의탑] 4층: 툰 쉐이딩

[코딩의탑] 3층: 바다 렌더링

[코딩의탑] 5층: 포탈(Portal), 더 나아가기