Sablog Models/언어

왜 abstract static은 금지되어 있는가?

어­리 2013. 8. 5. 03:28

자바(Java)로 객체지향을 처음 접하는 사람이 꽤 흔하다. 이들이 가장 많이 하는 실수이면서 잘 풀리지 않는 의문 중 하나가 stackoverflow에 차고 넘치는 'abstract static'이다. abstract static으로 규격을 만들어 놓으면 인스턴스화할 필요는 없으면서 상속될 수는 있으므로, 프로그램 내에서 문맥의 영향을 받지 않는 메서드를 모아 쓰기 좋은 구조일 것이다. 이런 패턴은 상당히 많은 프로그램에 적용될 수 있을 것이다. 그런데 왜 그런 좋은 게 금지되는 걸까?

답은 먼 곳에 있지 않다. 자바는 객체지향 언어라는 것. 그리고 그 중에서도 클래스-인스턴스 관계를 매우 엄밀하게 따지는 언어라는 것이다. 일견 자바의 static은 C++의 static과도, Python의 classmethod와도 닮아 보이지만, Python을 배운 사람이라면 staticmethod와 classmethod가 어떻게 다른지 찾을 수 있을 것이다. 자바에서도 클래스 그 자체가 객체로 취급되기는 한다. 하지만 그것이 우리가 알고 있는 역할을 하는 객체, 즉 인스턴스처럼 능동적으로 움직이는 것은 아니다. 그렇기 때문에 static 메서드가 상속되어서는 안 되는 것이다. 비록 하위 클래스가 상위 클래스의 static 내부 메서드의 이름을 가져올 수는 있지만, 이것은 오버라이딩이 아니다. 클래스는 인스턴스를 위한 생성구조(construct)일 뿐이며, 클래스가 인스턴스의 다형성을 지원한다. 이것이 전통적인 클래스-인스턴스 관계이다.

다 끝난 얘기지만, 좀 더 자바 자체를 두고 살펴 보자. 자바에는 자기 반영성(reflective)의 기반이 되는 java.reflect 패키지가 있다. 이 패키지에는 클래스를 나타내는 java.lang.Class에서 꺼내지는 java.reflect.Field와 java.reflect.method가 있고, 이들은 임의의 인스턴스 객체를 가리키는 java.lang.Object를 자동으로 상속하는 클래스이지만, java.lang.Class는 임의의 클래스를 그 이름만으로 받지 않는다. 이를테면 이런 식이다.

private Class<? extends EntryActivity> mHostActivityClass = MainActivity.class;

여기서 MainActivity는 EntryActivity를 상속한다고 가정하자. 우리는 Class<? extends EntryActivity> 타입의 값이 MainActivity라는 이름이 아니라 MainActivity.class로 표현된다는 것에 주목할 필요가 있다. Class 타입은 그 값의 클래스적 특징만을 보여주며, 클래스가 할 수 있는 일들만을 제공한다. 따라서 mHostedActivityClass는 우리가 알고 있는 객체로서의 클래스와는 본질적으로 다르다. mHostedActivityClass는 오직 EntryActivity 클래스를 상속한 어떤 클래스 자체이며, 인스턴스를 만들고, 하위 클래스를 생성구조로 하는 인스턴스의 필드를 읽고 쓰거나 메서드를 발현시키는(invoke) 기능만을 갖고 있다. 이는 만약 EntryActivity에 어떤 static 메서드가 있더라도 마찬가지인데, java.reflect.Method에는 invoke(obj, ... args) 메서드가 있어서 static 메서드의 경우 인스턴스를 나타내는 첫 인자를 무시하는 방식이 된다. 이는 JVM 위에 올려졌을 때 완전히 동등한 바이트코드로 해석된다.

중요한 것은 Class<? extends EntryActivity> 타입의 값이 MainActivity가 아니라 MainActivity.class라는 점에서, mHostActivityClass 자체에서 어떤 static 클래스를 꺼내서 쓸 방법이 없다는 것이다. EntryActivity.DoStaticWork();이지, EntryActivity.class.DoStaticWork();가 아니지 않은가? 이는 mHostActivityClass가 하나의 오브젝트같지만, 실제로는 JVM이 객체지향을 구현하는 고전적인 방식에 완전히 의존적인 일종의 데이터 구조일 뿐이며, 이는 메서드를 가져다 쓸 때에도 같은 방식을 취해야 한다는 것을 의미한다. 따라서 static 메서드는 오버라이드될 수 없다. 오버라이드는 인스턴스가 자기 자신을 알고 있을 때 다형성을 만드는 방법이기 때문이다. MainActivity.class가 자기 자신을 알 리가 없지 않은가! 그렇다고 Class<? extends EntryActivity>같은 무언가가 MainActivity의 이름을 받게 언어를 구현하는 것이 가능할까? 적어도 자바에서는 그럴 리가 없다.

내가 말하고자 하는 것은 자바의 방식이 전적으로 맞다는 것이 아니다. 세상은 디자인 패턴으로 이루어져 있지 않다. 실제로 일을 나눌 때에는 static과 non-static을 구분할 때 자바의 고전적인 객체지향 논리를 적용해야 한다는 것이 정말로 방해가 된다. 그나마 그것을 헤쳐나가는 방식이 수많은 패턴으로 굳혀져 왔고, 자바는 지금 패턴을 바탕으로 업계에서 가장 선호되는 언어 중 하나의 입지를 놓치지 않고 있다. 그러나 패턴을 어떻게 썼느냐로 코드를 판단할 수 있는 만큼, 그 패턴들이 실제로 적용된 곳은 이래도 그만 저래도 그만인 억지가 있는 것도 사실이다. 중요한 것은 컴퓨터에게 일을 어떻게 시키느냐와, 사람이 그것을 언제 어떻게 다루느냐에 있다.

하지만 abstract와 static은 모두 객체지향의 키워드인 만큼 abstract static은 금지되는 것이 맞다. 자기 반영성이 훨씬 유연하고 견고한 언어인 C#에서도, 자바와 같은 원리가 작용해서 abstract static은 불가능한 것이 된다. 또한 자바에서 invoke()를 남용하면 큰 코 다칠 수 있다는 사실도 유념해야 할 것이다. 사실 이 문제는 객체지향에서만 발생한 것은 아니지만, 잘 보면 C에서는 대충 void *로 다루고 예의 상 규약을 지켜 줬기 때문에 어떻게든 설계가 가능했다. 어딜 가나 type system이 문제가 되는 것이다. 만약 함수형 언어가 지금처럼 상승세를 타면서, F#이나 하스켈(Haskell)같은 언어가 미래에 지금의 naive한 함수론을 버린다면, 언젠가는 프로그래밍 언어에서 고계(high-order) 함수와 고계 컴파일러 매크로를 포함하는 견고한 현대적 타입 시스템의 입지를 구축할 수 있지 않을까. 물론 먼 미래의 이야기다. abstract static으로 일을 나누는 것도 그만큼이나 먼 꿈이다.