Java - 열거형, 추상 메서드, 추상 클래스Override, toString

김지원·2022년 7월 30일
0

JAVA 총 정리

목록 보기
5/11

스타그래프트 프로젝트 생성.

게임에 대한 기초이해
마린은 유닛중에 하나이다. (마린이 〈 유닛을 상속한다.)
종족은 항상 언제나 3개이다. 

종족은 언제나 3개이기 때문에 열거형으로 만들면 좋을 것 같다.

어떠한 대상이 항상 정해져있고 프로그램 실행중에 추가되거나 삭제되지 않을 경우 열거형으로 작성한다.

열거형(Enumeration)

[접근 제한자] enum [이름] { ... }
  • 어떠한 대상이 프로그램 실행 중에 더 이상 추가되거나 삭제되지 않으며 그 종류나 개수가 상당히 고정되어있다고 판단되면 열거형으로 만드는 것이 좋다. (스타의 종족, 로그인 결과 등등)
  • 열거형의 원소는 UPper Snake Case(전체 대문자, 단어간 구분은 언더스코어로) 방식으로 명명하여 콤마로 구분하여 열거한다.

종족이라는 특성은 유닛만 가지는 것이 아닌 건물도 가진다.
만약 테란을 setName을 하는데 개발자들이 모두 똑같이 쓸 기대를 하면 안된다.
Terran / TERRAN / 테란 ... 이런식으로 다 다르게 작성할수도 있다.
그러한 일들을 막기위해 열거형을 많이 사용한다.

열거형을 사용하면 개발자들끼리 협엽을 할 때 오타가 날 확률이 줄어든다.

  • enum Race 열거형 생성

  • Unit class 유닛들의 이름과 종족을 만든다.
  • 어떤 유닛의 이름과 종족이 정해지면 바뀌지 않기 때문에 final로 한다.
  • 이름과 종족을 다시 정해줄 수 없기 때문에 setter은 깔끔하게 지워주면된다.

final 붙이니 왜 빨간줄이 그일까? 멤버상수이기 때문에 조건을 충족하지 않기 때문이다.

멤버상수

private final String name;
private final Race race;

반드시 아래 방법중 하나로 값 초기화가 일어나야한다.
1. 선언과 동시에 값 초기화

private final String name = Race.TERRAN; 으로 하는 방법인데 
모든 유닛의 이름이 TERRAN이 되기 때문에 지금은 사용하지 않는다. 
  1. 생성자 호출시 멤버 상수 값 지정.

생성자 호출을 하는 방법을 사용할 것이다.

public Unit(String name, Race race) {
     this.name = name; // 전달 받은 name
     this.race = race; // 전달 받은 race
}
  • 전달 인자로 값을 전달받아( () ) 위의 멤버 상수에 전달을 해준다.

상수는 setter를 통해서 값 지정을 못함으로 직접지정하거나 생성자를 통해서 지정한다.

=> 그랬더니 메인이 터졌다.

  • new Unit(); 은 생성자 호출이다. 호출할 수 있는 생성자는 Unit 밖에 없는데 Unit은 무조건 전달인자(name, race)가 있어야하는데 아무것도 전달해주는게 없어서 오류가 발생했다.

전달을 해줘야만 객체화를 할 수 있다.

  • 이렇게 전달인자로 전달을 해주면 객체화가 되며
    결과적으로 멤버 상수에 name : 마린 / race : TERRAN이 들어가게 된다.
  • 이렇식으로 들어가게 되는 것이다. (name만 참고)

-> 메인에서 전달받은 이름과 종족을 출력해보자.

  • marine객체에 get을 해서 값을 불러온다.

  • 그런데 이렇게 코드를 짜면 다른 개발자가 marine2를 만들어서 영어로 이름을 짓는거나 변경할 수 있는 가능성이 존재하기 때문에 별로 좋지 않은 코드이다.

유닛이 공격하는 방법(기능)이 다 다를 것이다.

유닛이 가진 기능을 구현하자.

  • 유닛이 가진 기능을 가진 Unit Class에 attack메서드를 만든다.

  • 메인에서 출력해보니 유닛마다 사운드가 다 달라야하는데 이렇게 한 메서드로 사용하면 다 똑같은 사운드를 낼 것이다.

그래서 사용하는게 추상 메서드 / 추상 클래스이다.

추상 메서드(Abstract Method)

[접근 제한자] abstract [반환 타입] ...
  • 메서드의 존재 자체만 명시하고 구현부의 구현을 본 클래스의 상속 받는 자식 클래스에게 맡기기위해 사용. (구현부가 없다.)
  • 단, 추상 메서드를 한 개 이상 가지는 클래스는 반드시 추상 클래스(Abstract Class)여야 한다.

  • 추상 메서드는 구현부가 없어야하기 때문에 빨간줄이 생긴다.

  • 추상 메서드를 가진 클래스는 추상 클래스여야 하기 때문에 빨간줄이 생긴다. 다 수정해주자.

추상클래스(Abstract Class)

[접근 제한자] abstranct class [이름] {...}
  • 반드시 추상 메서드를 가져야하는 것은 아니다. 주로 추상 메서드를 가질 때 클래스를 추상화한다.
  • 추상 클래스는 일반적으로 개발자가 직접 객체화하지 못(안)한다. (가능은 하다.)
  • 대신 추상클래스라고 해서 추상메서드를 꼭 가져야하는 것은 아니다.

  • 결론적으로 추상 메서드가 뭐냐면 Unit 클래스는 attack이라는 추상 메서드가 호출되었을 때 유닛이라는 클래스는 모른다. 상속을 받는 자식클래스가 구현을 직접해야한다는 의미이다.

메인으로 가보자.

  • 'Unit' is abstract; cannot be instantiated
    : 유닛은 추상적이고 객체화 될 수 없다.
  • 유닛은 추상클래스이기 때문에 객체화가 될 수 없다는 의미이다.

추상클래스는 일반적으로 직접 객체화하지 못한다.(방법은 있다.)

추상화를 했기 때문에 attack 메서드를 입맛에 맞게끔 사용 할 수 있고 유닛을 상속받았기 때문에 마린을 마음대로 질럿이라는 이름으로 바꾸는 것을 막을 수 있다.

  • (unists패키지안에 terran패키지에) 클래스를 만들고 유닛을 상속 받았다. (유닛에 있는 이름 / 종족 / 어택을 구현하기 위해서)
    그런데 Class 'Marine' must either be declared abstract or implement abstract method 'attack()' in 'Unit'
    : 추상적이거나 attack 이라는 추상 메서드를 구현해야한다. 이라는 오류를 해결하기 위해서는 두가지의 방법이 있다.
  1. Unit을 상속받은 Marine클래스는 attack이라는게 이미 잠재되어있다.
    attack은 추상적인데도 물구하고 Marine클래스가 추상적이지 않기에 오류가 뜨는 든다. Marine 클래스도 추상적이게 만들면 해결된다.
  2. 상속받는 부모 Unit클래스가 가진 메서드들을 다 구현해야한다.
    즉, attack메서드를 구현하면 된다.

attack메서드를 구현하자. Override로.

  • 원래 Marine 클래스의 기능이 아니고 부모로부터 물려받은 메서드를 Marine 클래스에서 재정의해줘야한다.

어노테이션

: 클래스, 메서드, 변수 등의 상태나 속성을 나타내기 위해 사용한다.

@Override

: 해당 메서드가 부모나 인터페이스가 가진 메서드를 재정의(Override) 한다는 의미

  • this.getName() Unit클래스가 가진 getName호출하는데 왜 this(나)일까 내가 가진게 아니라 부모가 가진 것인데 super가 아닐까? 라는 생각이 든다.

  • 부모가 가진 기능을 나(Marine)도 구현을 하고 있기 때문에 this도 되고 super도 된다.

  • name멤버변수가 있긴 하지만 name은 private이기 때문에 그냥 name으로 접근할 수 없다.

Marin 클래스에 기본생성자만 존재하고 따로 생성자를 만들어준적없다.

  • 기본생성자이다.
  • super누르면 이쪽으로 오게 된다. Unit의 생성자에 매개변수 2개를 받고 있다.
  • 여기서 super은 Unit을 의미하고 (Unit을 상속받으니) super(); 라고 되있으니 Unit(부모) 생성자를 호출하는 것이다.
  • Unit클래스(부모클래스)의 생성자를 호출할 때 매개변수 2개를 받고 있으니 적당한 값을 전달해줘야한다.

  • name, race 이렇게 값은 전달은 할 수 있지만 아까와 같은 마린타입에 질럿이라는 이름이 들어갈수 있는 문제가 발생하게 된다.

그래서 값을 받는 것이 아닌 단 한번만 여기에다 지정을 해놓으면 된다.
이렇게 하면 Marine 클래스를 객체화할때마다 이름은 무조건 "마린"이 될 것이고 종족은 TERRAN이 될 것이다.

  • 객체화할때마다 값을 전달해주는 것이 아닌 이렇게만 작성하면 된다. 마린이 필요할 때 마린타입을 가져다 쓰면 되는 것이다.

  • units패키지안 protoss패키지 Zealot 클래스 생성, Unit 상속받고 attack 메서드 재정의.
  • 마린과 다른 이름과 종족을 가지게 되었고 attack의 사운드마저 다르게 된다.

마린과 질럿의 부모는 유닛임으로 유닛 타입의 배열에 담을 수 있다.

  • (Main클래스)
  • 추상클래스인 Unit이란 타입으로 attack을 호출하는게 가능한가?
    정상적으로 작동을 한다. 그 이유는 뭘까?
    Unit이라는 클래스를 상속받아 만든 것은 마린과 질럿이 있다.
    마린과 질럿이라는 타입을 만들 때 attack을 할 때 어떻게 공격을 하라고 지정을 해주었다. (상속을 받아 오버라이드를 통해)
    마린과 질럿의 객체에 대해서 접근할 수 있는 범위를 Unit으로 제한한 것이다.

Marine marine = new Unit;

마린타입의 marine을 유닛으로 받게 되면 더 이상 new을 통해서는
마린이 가지고 있는 확장된 기능을 쓰지 못한다.
축소가 되긴하지만 attack을 했을 때 어떤 것을 해야된다 라는 것에 대해서는 날아간게 아니다.

Marine객체를 Unit타입으로 제한한다고 해서 Unit의 attack을 호출했을 때 추상적이라서 오류가 발생하는 것이 아닌 원래 할일을 하게 된다.

  • 제한시키더라도 기능구현은 가능하다.

println을 통해 이해를 빠삭하게 해보자.

  • 이렇게 적었다면 println에 마린과 질럿이 있어야 ( );안에 들어갈 수 있는게 아닐까? 매개변수의 타입과 맞지않는걸 넣으면 오류가 발생하는데 오류가 발생하지 않는다?

-> println에 들어가보자.

  • 모든 참조타입의 최상위 부모는 Object이다.
public void println(object x ) {...}

( ); 에 마린이란걸 넣으면 Object라는 변수에 Marine 객체가 들어가는데
즉, 부모 타입는 자식 객체을 받을 수 있기 때문에 가능한 것이다.

println에 "Hello"를 넣으면 String을 받는 것이고 5를 넣으면 int를 받는 것이다.

  • 출력을 해보았더니 이렇게 이상한 값이 출력이 된다.
[TERRAN] 마린
[PROTOSS] 질럿 

이렇게 출력이 되었으면 좋겠다.

  • String s = String.valueOf(x); 을 들여다보자.

  • toString() 을 호출하고 있다.

내가 println(여기에) 집어넣은 객체가 Object 로써 받아들여졌으며
이상하게 출력 내용을 보니 Object의 toString 메서드를 호출해서 출력된 값이 된 것이다. ( null이면 null이고 아니면 toString() )

  • toString 으로 가보니 출력된 결과와 같은 형태를 가진 것을 확인 할 수 있다. toString은 Object클래스에 속해있다.

결론적으로 이러한 형태가 되는 것이다.

println();
		↑ object 넣음 => toString 출력
Unit unit = marine;
unit.attack();
  • 이 attack이 가지고 있는 Unit이 아닌 Marine의 내용이다.

  • Object타입으로 마린과 질럿을 만들어봤다.

상속받고 있는 부모가 가진 메서드를 재정의하는 것 오버라이드라고 했다.
Object가 가진 toString을 재정의한다. (Unit은 상속받는게 없으니 부모가 Object이고 그래서 toString 오버라이드가 가능하다.)
toString을 Object의 toString을 두지않고 Unit의 toString으로 바꿀 수 있는 것이다.

정리하자면 Unit에게 너는 toString이 호출되면 반듯이 이 내용을 반환해라 라고 덮어씌워주는 것이다.
덮어씌우지 않으면 Unit에 toString 했을 때 Object가 가진 toString이 반환되는 것이고 재정의(덮어씌우는 것)를 하게 되면 Unit이 가진 toString이 반환이 되는 것이다.

object타입으로 변환한다고 해서 이상하게 나오지 않는다.
범위를 제한했을 뿐이고 원래 해야하는 일 (Marine의 toString을 호출하는 것)을 하는 것 뿐이라서 출력이 이렇게 되는 것이다.
만약 제한한 타입의 일을 했다면 Unit은 추상적이라서 호출할 수 없어서 오류가 터졌을 것이다.

object > toString : ~@~ 형식 출력
Unit extends Object
Unit을 통해 toString 재정의

profile
Software Developer : -)

0개의 댓글