NGMsoftware

NGMsoftware
로그인 회원가입
  • 매뉴얼
  • 학습
  • 매뉴얼

    학습


    Java 17-2. Java 클래스의 기본 생성자 (Class's constructor)

    페이지 정보

    본문

    안녕하세요. 소심비형입니다. 이전 글에서 클래스에 대해 아주 조금 알아봤습니다. 클래스에 대한 내용이 워낙 방대하다보니 앞으로도 계속 이어서 진행할 예정입니다. 오늘은 생성자(Constructor)와 생성자 오버로딩(Constructor overloading)에 대해서 알아보도록 하겠습니다. 그 외에 자잘하게 이것 저것 추가되어 있지만, 기본 맥락에서 벗어나는 내용들은 이 후에 좀 더 자세하게 알아보겠습니다.

     

    Java는 인스턴스 생성자, 전용 생성자와 복사 생성자를 지원하고 있습니다. 클래스나 구조체(사실 자바는 구조체를 지원하지 않습니다.)를 new 키워드로 생성할 때 자동으로 생성자를 호출합니다. 클래스는 메서드 오버로딩처럼 서로 다른 인수를 사용하는 여러 생성자를 만들 수 있고, 개발자는 생성자를 통해 기본값을 설정하고 인스턴스화를 제한하여 융통성 있고 읽기 쉬운 코드를 작성할 수 있습니다.

    u9ptbTm.jpg

     

     

    이전 글에서 Cat 클래스를 만들면서 생성자를 추가하지 않았습니다. 속성과 메소드만 추가했죠. 만약, 개발자가 작성한 클래스에 생성자를 제공하지 않으면 어떻게 될까요? C#과 동일하게 자바 컴파일러에서 개체를 인스턴스화하고 모든 맴버 변수에 기본 값을 설정하는 생성자를 자동으로 만듭니다.

     

    생성자는 클래스와 같은 이름으로 만드는 특수한 메소드입니다. 일반적으로 생성자에서 개체의 데이터 맴버를 초기화합니다. 다음 예제에서는 기본 생성자를 사용하여 Taxi라는 클래스를 정의합니다. 그런 다음 new 연산자를 사용하여 클래스를 인스턴스화 합니다. 새 개체에 대한 메모리를 할당한 직후에 new 연산자에 의해 Taxi 생성자가 호출됩니다.

    Taxi.java

    public class Taxi {
     public boolean isInitialized;
     
     public Taxi() {
      isInitialized = true;
     }
    }

     

     

    위 코드의 라인 4에서 기본 생성자를 사용하고 있습니다. 이처럼 매개 변수를 사용하지 않는 생성자를 기본 생성자라고 합니다.

    Default.java

    public class Default {
     public static void main(String[] args) {
      Taxi t = new Taxi();
      System.out.println(t.isInitialized);
     }
    }

     

     

    라인 3에서 new 키워드를 사용하여 클래스를 생성합니다. 이 때 이 클래스를 담는 t 변수는 클래스의 인스턴스입니다. 이런 과정을 인스턴스화라고 부릅니다. 아래 예제는 생성자 오버로딩을 통해 좀 더 효율적인 클래스를 구성하는 방법입니다. 이렇게하면 좀 더 융통성 있는 클래스의 인스턴스를 만들 수 있게 됩니다. 필요에 의해 여러개의 생성자를 오버로딩할 수 있습니다.

    Cat.java

    import java.awt.Color;
     
    class Cat {
     private String name;
     private Color color;
     
     public String getName() {
      return name;
     }
     
     public void setName(String _name) {
      name = _name;
     }
     
     public Color getColor() {
      return color;
     }
     
     public void setColor(Color _color) {
      color = _color;
     }
     
     public Cat() {
      name = "";
      color = Color.WHITE;
     }
     
     public Cat(String _Name, Color _Color) {
      name = _Name;
      color = _Color;
     }
     
     @Override
     protected void finalize() {
      System.out.printf("%s : 잘가%n", name);
     }
     
     public void Meow() {
      System.out.printf("%s : 야옹%n", name);
     }
    }

     

     

    위 코드의 라인 23 기본 생성자를 오버로딩하여 라인 28과 같은 생성자를 하나 더 만들었습니다. 이렇게 생성자도 오버로딩이 가능합니다. 참고로 Java는 C++, C# 처럼 소멸자를 가지고 있지 않습니다. 소멸자는 AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)와도 아주 밀접한 관계를 가지고 있기 때문에 언제 한번 심도있게 다루어 볼 주제이기도 하죠. 아무튼, 객체가 소멸될 때 어떤 처리를 해야 한다면 라인 34와 같이 처리하면 됩니다. 아래 코드를 통해 테스트 해보세요.

    Default.java

    import java.awt.Color;
     
    public class Default {
     public static void main(String[] args) {
      Cat kitty = new Cat("키티", Color.WHITE);
      kitty.Meow();
      System.out.printf("%s : %s%n", kitty.getName(), kitty.getColor());
      Cat nero = new Cat("네로", Color.BLACK);
      nero.Meow();
      System.out.printf("%s : %s%n", kitty.getName(), kitty.getColor());
      nero.finalize();
     }
    }
    

     

     

    라인 12와 같이 finalize를 호출하면 클래스가 소멸됩니다. 하지만 일반적인 방식은 개체가 더 이상 사용되지 않거나 null로 초기화하면 자동으로 GC에 등록되며, 순위가 오면 그 때 해제되도록 메커니즘이 구현되어 있죠. GC에 대한 자세한 내용도 다음에 알아볼께요-_-;

    위 12라인을 아래와 같이 변경하고, 실행 해보세요.

    Default.java

    nero = null;
    System.gc();

     

     

    아래는 이 코드를 실행한 결과입니다.

    wZ856Yd.png

     

     

    위의 예제에 기본 생성자와 오버로딩한 생성자가 포함되어 있습니다. 이렇게 하면 특정 초기 값이나 기본값으로 개체를 초기화 할 수 있으며, 좀 더 융통성 있는 클래스를 구성할 수 있게 됩니다. 아래 예제는 일반적인 클래스의 구조를 잘 보여주고 있습니다.

    Point3D

    public class Point3D {
     public int X;
     public int Y;
     public int Z;
     
     public Point3D(int X, int Y, int Z) {
      this.X = X;
      this.Y = Y;
      this.Z = Z;
     }
     
     @Override
     public String toString() {
      return String.format("%s, %s, %s", X, Y, Z);
     }
    }
    

     

     

    이 클래스는 구조체를 표현하고 있습니다. Java의 경우는 C의 construct나 C#의 struct처럼 별도로 구조체를 선언할 수 있는 키워드가 제공되지 않습니다. 그래서 클래스를 이용합니다. 라인 12는 toString 메소드를 제정의하여 개발자가 만든 클래스에서 처리하고 있습니다. 아직 오버라이딩에 대해서 학습하지 않았기에 의미가 무엇인지 궁금할겁니다. 하지만, 여기에서 내용이 방대하므로 클래스 단원의 오버라이딩 때 자세히 알아보도록 하겠습니다. 전체적인 클래스의 구조가 어떻게 구성되는지 간단하게 알 수 있습니다. 아직 캡슐화가 외어 있진 않지만 이런 내용은 차차 알아가야 할 부분입니다.

     

    다음은 private 접근 제한자를 이용하여 전용 생성자를 만드는 방법입니다. 전용 생성자는 특수한 인스턴스 생성자입니다. 정적 맴버만 포함하는 클래스에서 일반적으로 사용됩니다. 클래스에 전용 생성자만 한 개 이상 있고 공용 생성자는 없을 경우 중첩 클래스를 제외한 다른 클래스는 이 클래스의 인스턴스를 만들 수 없습니다.

    Statistics.java

    class Statistics { 
     // Private Constructor private Statistics() { } 
     public static double e = Math.E; 
     // 2.71828...
     }
    }

     

     

    빈 생성자를 선언하면 기본 생성자가 자동으로 생성되지 않습니다. 생성자에 접근 제한자를 사용하지 않을 경우 클래스를 생성할 때와 같이 전용 생성자가 됩니다. 하지만, 클래스와는 다르게 전용 생성자를 만들려면 private 제한자를 명시적으로 적어주는게 좋습니다. 전용 생성자는 Math 클래스처럼 인스턴스 필드나 메서드가 없는 경우 또는 클래스의 인스턴스를 가져오기 위해 메소드가 호출되는 경우에 클래스의 인스턴스가 생성되지 않도록 방지하는데 사용됩니다. 만약, 모든 필드가 정적이면 클래스를 정적으로 만드는게 더 좋습니다.

     

    마지막으로 Copy Constructor(복사 생성자)에 대한 예제입니다. 다음의 예제에서 Person 클래스는 해당 인수로 Person 인스턴스를 받아들이는 복사 생성자를 의미합니다. 인수의 속성 값은 Person의 새 인스턴스 속성에 할당됩니다.

    Person.java

    class Person {
     // Copy constructor.
     public Person(Person previousPerson) {
      name = previousPerson.getName();
      age = previousPerson.getAge();
     }
     
     // Instance constructor.
     public Person(String _name, int _age) {
      name = _name;
      age = _age;
     }
     
     private int age;
     private String name;
     
     public int getAge() {
      return age;
     }
     
     public void setAge(int _age) {
      age = _age;
     }
     
     public String getName() {
      return name;
     }
     
     public void setName(String _name) {
      name = _name;
     }
     
     public String Details() {
      return name + " is " + age;
     }
    }
    

     

     

    결과를 확인하기 위해 main 진입점의 코드를 아래와 같이 수정합니다.

    Default.java

    public class Default {
     public static void main(String[] args) {
      // 인스턴스 생성자를 사용하여 Person 객체를 생성합니다.
      Person person1 = new Person("George", 40);
      // person1 복사, 다른 사람 개체를 만듭니다.
      Person person2 = new Person(person1);
      // 각 사람의 나이를 변경합니다.
      person1.setAge(39);
      person2.setAge(41);
      // person2의 이름을 변경합니다.
      person2.setName("Charles");
      // 이름과 나이 필드가 다르게 설정되는지 확인합니다.
      System.out.printf("%s%n", person1.Details());
      System.out.printf("%s%n", person2.Details());
     }
    }
    

     

     

    위 예제의 결과입니다. 정상적으로 복사가 되었기 때문에 이름과 나이가 별개로 설정되어 있는 것을 확인 할 수 있습니다.

    WNM1Arx.png

     

     

    Java는 개체에 대한 복사 생성자를 별도로 제공하지 않습니다. 그러나 위와같이 사용자가 직접 작성할 수 있습니다. 하지만, 참조 형식에 대한 복사는 위와 같은 방식으로 작성하지 않습니다. Java에서 제공하는 Cloneable 클래스를 상속 받아서 명시적으로 구현하는게 일반적인 방식입니다. 여기에는 약간 이견이 있을수는 있으나 보통 구조와 데이터를 복사할 때는 clone을 사용하고, 개체의 구조만 복사할 때는 copy를 사용합니다.

    ScyiR41.png

     

     

    이 부분에 대해서는 나중에 진행할 얕은 복사(Shallow copy)와 깊은 복사(Deep copy)에서 좀 더 상세하게 다루도록 하겠습니다. 여기에서 배운 복사 생성자는 사실 실무에서 사용되지는 않고 있지만, 이렇게 처리할수도 있다는 것 정도만 알아두시면 될거 같습니다.

     

    참고로, C#은 Java와 다르게 정적 생성자를 만들 수 있습니다. 자세한 내용은 아래 링크를 통해 확인할 수 있습니다.

    17-2. C# 클래스의 기본 생성자 (Class's constructor) ]

     

    다음 시간에...

    • 네이버 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 카카오스토리 공유하기
    추천0 비추천0

    댓글목록

    등록된 댓글이 없습니다.