2011년 1월 12일 수요일

추상클래스는 객체생성이 안될까?

사람들은 흔히들 추상클래스는 객체 생성이 불가능하다고 알고 있습니다.
하지만 정말 그럴까요?

public  abstract class AAA {

 {
  System.out.println("aaaa");
 }

 private String name;
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }


}

우선 부모 클래스  AAA 라고 만들었습니다(네이밍룰은 레드썬!)
자세히 보면 기본블럭(default block) 을 이용해서 객체가 생성되면 자동적으로 aaaa 라는 문자열을 찍도록 해 두었습니다.

자식 클래스는 당연히 AAA클래스를 상속해서 만들었습니다.

public class BBB extends AAA {
 {
  System.out.println("BBB");
 }


 public static void main(String[] args) {

  BBB obj1 = new BBB();
  System.out.println(obj1);

  BBB obj2 = new BBB();
  System.out.println(obj2);

 }
}
별다른 내용은 없고 메인 메소드를 작성해서 객체를 두개 만들어 보았습니다. 다만 객체 생성을 확인하기 위해서 BBB클래스 역시 기본 블럭으로 BBB라는 문자열을 출력하도록 수정했습니다. 이 코드의 컴파일 결과 부터 살펴봅니다.
[parsing started BBB.java]
[parsing completed 32ms]
[search path for source files: .]

역시 BBB.java 파일을 메모리로 파싱합니다.
그런데 BBB클래스는 AAA를 상속하기 때문에 AAA역시 필요하게 됩니다.

[loading .\AAA.java]
[parsing started .\AAA.java]
[parsing completed 0ms]
[loading java\lang\Object.class(java\lang:Object.class)]
[loading java\lang\String.class(java\lang:String.class)]
[checking BBB]
[loading java\lang\System.class(java\lang:System.class)]
[loading java\io\PrintStream.class(java\io:PrintStream.class)]
[loading java\io\FilterOutputStream.class(java\io:FilterOutputStream.class)]
[loading java\io\OutputStream.class(java\io:OutputStream.class)]
[checking AAA]
[wrote .\AAA.class]
[checking BBB]
[wrote BBB.class]
[total 281ms]

이 코드를 가지고 실행하게 될 코드는 어떻게 될까요?
javap 명령을 이용해서 역컴파일 시킨 결과는 다음과 같습니다.


Compiled from "BBB.java"
public class BBB extends AAA
  SourceFile: "BBB.java"
  minor version: 0
  major version: 50
  Constant pool:
const #1 = Method       #8.#17; //  AAA."<init>":()V
const #2 = Field        #18.#19;        //  java/lang/System.out:Ljava/io/PrintS
tream;
const #3 = String       #20;    //  BBB
const #4 = Method       #21.#22;        //  java/io/PrintStream.println:(Ljava/l
ang/String;)V
const #5 = class        #20;    //  BBB
const #6 = Method       #5.#17; //  BBB."<init>":()V
const #7 = Method       #21.#23;        //  java/io/PrintStream.println:(Ljava/l
ang/Object;)V
const #8 = class        #24;    //  AAA
const #9 = Asciz        <init>;
const #10 = Asciz       ()V;
const #11 = Asciz       Code;
const #12 = Asciz       LineNumberTable;
const #13 = Asciz       main;
const #14 = Asciz       ([Ljava/lang/String;)V;
const #15 = Asciz       SourceFile;
const #16 = Asciz       BBB.java;
const #17 = NameAndType #9:#10;//  "<init>":()V
const #18 = class       #25;    //  java/lang/System
const #19 = NameAndType #26:#27;//  out:Ljava/io/PrintStream;
const #20 = Asciz       BBB;
const #21 = class       #28;    //  java/io/PrintStream
const #22 = NameAndType #29:#30;//  println:(Ljava/lang/String;)V
const #23 = NameAndType #29:#31;//  println:(Ljava/lang/Object;)V
const #24 = Asciz       AAA;
const #25 = Asciz       java/lang/System;
const #26 = Asciz       out;
const #27 = Asciz       Ljava/io/PrintStream;;
const #28 = Asciz       java/io/PrintStream;
const #29 = Asciz       println;
const #30 = Asciz       (Ljava/lang/String;)V;
const #31 = Asciz       (Ljava/lang/Object;)V;
{
public BBB();
  Code:
   Stack=2, Locals=1, Args_size=1
   0:   aload_0
   1:   invokespecial   #1; //Method AAA."<init>":()V
   4:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   7:   ldc     #3; //String BBB
   9:   invokevirtual   #4; //Method java/io/PrintStream.println:(Ljava/lang/Str
ing;)V
   12:  return
  LineNumberTable:
   line 2: 0
   line 5: 4
   line 6: 12

public static void main(java.lang.String[]);
  Code:
   Stack=2, Locals=3, Args_size=1
   0:   new     #5; //class BBB
   3:   dup
   4:   invokespecial   #6; //Method "<init>":()V
   7:   astore_1
   8:   getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   11:  aload_1
   12:  invokevirtual   #7; //Method java/io/PrintStream.println:(Ljava/lang/Obj
ect;)V
   15:  new     #5; //class BBB
   18:  dup
   19:  invokespecial   #6; //Method "<init>":()V
   22:  astore_2
   23:  getstatic       #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   26:  aload_2
   27:  invokevirtual   #7; //Method java/io/PrintStream.println:(Ljava/lang/Obj
ect;)V
   30:  return
  LineNumberTable:
   line 12: 0
   line 13: 8
   line 15: 15
   line 16: 23
   line 18: 30

}

코드를 보면 내부적으로  AAA클래스 역시 로딩하는 것을 확인할 수 있습니다.
실행된 결과는 더욱 재밌습니다.

aaaa
BBB
BBB@c17164
aaaa
BBB
BBB@1fb8ee3

객체를 두 개 만들었더니 AAA안의 기본 블럭 역시 두번(즉 AAA클래스의 객체도 두번 만들어진다) 실행되는 것을 볼 수 있습니다.

Java에서 추상 클래스는 문법적으는 객체 생성을 제약하는 것이 맞습니다. 즉 직접 객체를 생성할 수 없도록 하는 장치라는 겁니다.

하지만 상속이라는 것이 실제로 객체를 한번에 부모 클래스까지 만들어 내기 때문에 추상클래스 역시 그 예외가 아니라는 겁니다.

댓글 2개: