2022.07.26 화요일/자바 정리/클래스 사용법

Jimin·2022년 7월 26일
0

비트캠프

목록 보기
9/60

클래스 사용법

  • 상속을 통한 기능 확장법
  • UML: 클래스 간의 5가지 관계
  • 기타 기능 확장법

기능을 확장하는 방법

기존 클래스 변경

  1. 코드에 계속 기능을 덧붙이다보면 누더기 코드가 될 수 있다.
  2. 기존 코드를 사용하는 프로젝트에 영향을 끼친다.

기존 소스를 복제해서 기능 확장

  1. 특징: 기존 코드를 손대지 않는다.
    ⇒ 따라서, 기존 프로젝트 내 영향을 끼치지 않는다.
  2. 단점: 복제할 때마다, 중복코드가 생성된다.
    → 같은 일을 하는 여러 클래스(코드 중복)가 존재하면 관리하기가 어렵다.
    → 원본 코드에 버그가 존재할 때, 복제하는 동시에 버그도 존재하게 되어, 버그 수정시, 원본, 복제파일들 전체를 수정해야한다.
    ⇒ 따라서, 유지 보수가 힘들다.

상속

- 상속 문법을 이용한 기능 추가

  • 특징
    1. 기존 코드를 손대지 않는다.
      ⇒ 이전 프로젝트에 영향을 미치지 않는다.
    2. 기존 코드를 재사용한다.
      ⇒ 개발 비용 절감
      ⇒ 오류 가능성을 줄인다. (버그 추가 가능성 감소)
      ⇒ 코드를 재사용 함으로써, 코드 중복을 없앤다.
      ⇒ 버그 수정이 쉽다. (원본에서 버그가 발생할 시, 원본만 수정하면 된다.)
  • 단점:
    여러 단계를 거치다 보면, 필요없는 기능도 강제로 상속받는 경우가 있다.
  • Car 클래스를 상속받은 Sedan 클래스를 통해서,
    Car 클래스의 기능을 사용할 수 있다.

- 상속과 메서드 호출

  • B클래스가 A클래스를 상속받을 때, A클래스의 코드를 B클래스 코드인 것 처럼 사용할 수는 있지만!
    A 클래스의 코드는 B클래스에 존재하지 않는다!
  • 단지 B클래스는 A클래스의 코드를 사용할 수 있는 사용권만 갖는 것이다!
  • 즉, 상속을 받고 A클래스가 사라지면 B는 더 이상 A클래스를 사용할 수 없다! 코드가 복사된 것이 아니기 때문이다!
  • extend = 확장한다.

- 상속과 인스턴스 필드(변수)

-- 클래스 로딩 관점에서 보자

  • 서브 클래스 실행시, 수퍼 클래스부터 로딩되고 그 다음 서브 클래스가 로딩된다.
  • 인스턴스 필드 생성 전에 스태틱 필드와 블럭부터 실행됨을 명심하자.
  • 인스턴스 필드 생성: 수퍼 클래스의 인스턴스 변수부터 생성한다.
B obj = new B();
  • 위의 인스턴스를 생성해보자.
    → B 클래스의 인스턴스 생성 과정
  1. B의 수퍼 클래스가 로딩되어 있지 않다면, 수퍼 클래스(A 클래스)를 먼저 로딩한다.
    • 스태틱 필드 생성(변수 선언)
    • 스태틱 블록 실행
  2. 서브 클래스인 B 클래스 로딩
    • 스태틱 필드 생성(변수 선언)
    • 스태틱 블록 실행
  3. 인스턴스 필드 생성
    • 수퍼 클래스의 인스턴스 필드부터 생성한다.
    • 즉, A의 v1 필드 생성 후, B의 v2필드 생성
    • 각 필드를 기본값이 0으로 설정
    • 이후 넣어주는 값 설정
    • 즉, 서브 클래스 B 클래스의 인스턴스는 수퍼 클래스 A클래스의 인스턴스 필드도 포함한다.
    • v1라는 변수의 코드는 A것이지만, 인스턴스 자체는 B에 저장되어 B의것이 된다. 즉 obj를 통해 v1을 접근할 때는 B의 레퍼런스를 통해 접근하게 된다.
  • 수퍼클래스의 변수가 인스턴스가 서브 클래스 로딩할 때 함꼐 생성되지만, 결과는 결국 서브 클래스의 인스턴스에 저장되는 것이다.
    f, exam01 참고

- 생성자 호출 순서

스태틱 필드와 블럭이 생성자보다 먼저 실행된다.
즉, 서브 클래스가 로딩되기 전에 수퍼 클래스부터 로딩되는데, 클래스가 로딩될 때 스태틱도 함께 로딩되기 때문에 수퍼 클래스의 스태틱 부터 로딩되게 된다.

  • 모든 클래스 생성자의 첫 문장은 반드시 수퍼클래스의 생성자 호출 문장이어야 한다.
    또는, 같은 클래스의 다른 생성자를 호출하는 문장이어야 한다.
  • 즉, super()나 this()로 생성자의 첫문장이 시작되어야 한다.

⇒ 그러나, 밑에와 같이 둘 다 동시에 호출할 수는 없다.
그럼 둘 중 하나는 첫 문장이 아니게 되기 때문이다!

super()
this()
this()
super()
  • 수퍼 클래스가 현재 클래스에 extends형태로 나타나 있지 않다면, object클래스가 호출된다.
  • 수퍼 클래스가 현재 클래스에 extends형태로 나타나 있더라도, 수퍼 클래스의 생성자 호출을 생략하면,
    컴파일러가 자동으로 수퍼 클래스의 기본 생성자를 호출하라는 명령을 생성자의 첫 줄에 추가한다.
    super()가 생략되어 있다면, 컴파일러가 현재 클래스 생성자에 super()를 자동으로 붙여버린다.
  • 하위 클래스부터 생성자가 실행된다. 수퍼 클래스부터 생성자가 실행되는 것이 아니다! 단지, 서브 클래스의 생성자의 첫 문장이 super()라 타고 올라가다보니, 최상위 클래스의 생성자부터 먼저 출력되고 그 뒤, 상위부터 리턴되며 차근차근 하위 클래스의 생성자가 실행되는 것이다!

  • 예시

  • A.java

public class A /*extends Object*/ {
  int v1;

  A() {
    // 수퍼 클래스의 어떤 생성자를 호출할지 지정하지 않으면 컴파일러는
    // 다음과 같이 수퍼 클래스의 기본 생성자를 호출하라는 명령을
    // 생성자의 첫 줄에 추가한다.
    super(); // 즉 개발자가 붙이지 않으면 자동으로 붙인다.

    // 헐.. 강사님, A 클래스의 수퍼 클래스는 없는데요?
    // => 클래스를 정의할 때 수퍼 클래스를 지정하지 않으면,
    //    컴파일러는 자동으로 수퍼 클래스를 java.lang.Object 클래스로 지정한다.
    // => 그래서 자바의 모든 클래스는 반드시 수퍼 클래스가 있으며,
    //    자바의 모든 클래스는 java.lang.Object의 자손 클래스가 된다.

    System.out.println("A() 생성자!");
    this.v1 = 100;
  }
  static {
    System.out.println("static A");
  }
}
  • B.java
public class B extends A {
  int v2;

  B() {
    // 수퍼 클래스의 어떤 생성자를 호출할지 지정하지 않으면 컴파일러는
    // 다음과 같이 수퍼 클래스의 기본 생성자를 호출하라는 명령을
    // 생성자의 첫 줄에 추가한다.
    super(); // 즉 개발자가 붙이지 않으면 자동으로 붙인다.

    System.out.println("B() 생성자!");
    this.v2 = 200;
  }
  static {
    System.out.println("static B");
  }
}
  • C.java
public class C extends B {
  int v3;

  C() {
    // 수퍼 클래스의 어떤 생성자를 호출할지 지정하지 않으면 컴파일러는
    // 다음과 같이 수퍼 클래스의 기본 생성자를 호출하라는 명령을
    // 생성자의 첫 줄에 추가한다.
    //    super(); // 즉 개발자가 붙이지 않으면 자동으로 붙인다.

    System.out.println("C() 생성자!");
    this.v3 = 300;

    // 만약 개발자가 수퍼 클래스의 생성자를 호출하는 명령을 작성할 때
    // 그 전에 다른 코드가 있다면 컴파일러는 오류를 발생시킨다.
    //    super(); // 따라서 수퍼 클래스 생성자를 호출하는 명령은 반드시 첫 문장으로 와야 한다.
  }
  static {
    System.out.println("static C");
  }
}
  • Exam01.java(main)
public class Exam01 {
  public static void main(String[] args) {
    C obj = new C();
    System.out.printf("v1=%d, v2=%d, v3=%d\n", obj.v1, obj.v2, obj.v3);

    // 생성자 호출 순서
    // 1) C 클래스의 생성자를 호출하면,
    //    그 생성자의 첫 번째 문장이 수퍼 클래스의 생성자를 호출하는 명령이다.
    //    그래서 수퍼 클래스인 B 클래스의 생성자를 호출한다.
    // 2) B 클래스의 생성자를 호출하면,
    //    그 생성자의 첫 번째 문장이 수퍼 클래스의 생성자를 호출하는 명령이다.
    //    그래서 수퍼 클래스 A의 생성자를 호출한다.
    // 3) A 클래스의 생성자를 호출하면,
    //    그 생성자의 첫 번째 문장이 수퍼 클래스의 생성자를 호출하는 명령이다.
    //    그래서 수퍼 클래스 Object의 생성자를 호출한다.
    // 4) Object 클래스의 생성자를 호출하면,
    //    더이상 수퍼 클래스가 없기 때문에 Object() 생성자를 실행한다.
    //    그리고 이 생성자를 호출한 A 클래스의 생성자로 리턴한다.
    // 5) A 클래스의 생성자를 실행한 후
    //    이 생성자를 호출한 B 클래스의 생성자로 리턴한다.
    // 6) B 클래스의 생성자를 실행한 후
    //    이 생성자를 호출한 C 클래스의 생성자로 리턴한다.
    // 7) C 클래스의 생성자를 실행한다.

  }
}
  • 위의 코드 모식도

  • 다음 코드를 메인에서 실행할 때 순서도

C obj = new C();
  1. A 클래스 로딩
  2. A 클래스 Static 필드 선언, Static block 실행
  3. B 클래스 로딩
  4. B 클래스 Static 필드 선언, Static block 실행
  5. C 클래스 로딩
  6. C 클래스 Static 필드 선언, Static block 실행
  7. C 클래스 생성자 실행
  8. C 클래스 생성자의 첫줄, super() 실행
  9. B 클래스 생성자 실행
  10. B 클래스 생성자의 첫줄, super() 실행
  11. A 클래스 생성자 실행
  12. A 클래스 생성자의 첫줄, super() 실행
  13. Object 클래스 실행 (최상위 클래스)
  14. B 클래스 생성자로 리턴
  15. A 클래스 생성자로 리턴
  16. C의 인스턴스 변수 생성 -> v1, v2, v3
  • 상위 클래스 순서로 클래스 로딩 + 스태틱 실행

  • C 클래스의 생성자부터 super()를 통해 상위 생성자를 타고 올라가며 실행

  • 다시 하위 클래스로 내려가며 생성자 코드 실행하기

  • C클래스의 인스턴스 생성

    • 각각의 A, B, C 클래스의 this의 주소값은 C 레퍼런스 변수 값인 200이 들어가게 된다.

⇒ 즉, A클래스의 생성자의 코드가 먼저 출력된다고 A 클래스의 생성자가 먼저 실행되는 것이 아니라, C클래스의 생성자부터 실행이 되지만, super()를 통해 타고 올라가서 결국 A클래스의 생성자가 실행이 되어 A 생성자의 코드가 먼저 출력되는 것이다!

최하위 클래스 인스턴스를 new를 통해 생성 명령어 실행 ->수퍼 클래스 로딩 -> 수퍼 클래스의 static부터 실행
-> 서브 클래스생성자 실행 -> super()를 통해 수퍼 클래스의 생성자까지 올라가서 실행 -> 수퍼 클래스의 인스턴스부터 만들기

생성자 호출 2

  • B클래스에서 A클래스를 상속받았는데, A클래스에 기본 생성자가 없고 파라미터를 받는 생성자가 있다고 가정했을 때, B클래스의 생성자에서 super(파라미터값)으로 첫 문장에 적어주지 않으면 자동으로 super()를 컴파일러가 만들어줘도, A클래스에 기본생성자가 존재하지 않기 때문에 오류가 나게 된다.

다중 상속

  • 자바는 다중 상속을 지원하지 않는다!
  • 위의 그림에서의 obj.v2에 3을 대입하고 싶은데 v2가 A 클래스의 변수인지 B 클래스의 변수인지 구분할 수가 없다.
  • 구분하려면, 문법을 추가해야하는데, 이렇게되면 언어가 복잡해진다.
  • 언어가 복잡해지면, 컴파일러가 복잡해지는데 자바는 다중상속을 허락하지 않는다.
  • 따라서 이 때문에, 자바는 다중 상속을 허락하지 않는다!
  • 그렇다면, 클래스 C가 A만을 상속 받지만, 클래스C와 A 둘 다 이름이 같은 변수가 있는 경우, 변수가 오버라이딩된 형태라서 결국 가장 가까운 변수를 가져오게 된다.
    하지만, 이렇게 되면 헷갈리게 되므로 이런식으로 코딩을 하면 안된다!

상속

SPECIALIZAITNO(전문화)

  • 위의 그림에서 수퍼 클래스인 Car클래스는,
    처음에 직접 사용하려고 만든 클래스이다!
  • Sedan과 Truck 클래스는 Car 클래스를 만든 이후,
    필요에 의해 Car클래스 기능에, 세부 기능을 추가하여 정의한 클래스들이다.

GERNERALIZATION(일반화)

  • Geralization을 수행하는 이유:
    일반화된 Car 클래스는 다른 클래스를 정의할 때 재사용이 가능하다!
  • 공통 코드가 발견되어 유지보수를 쉽게하기 위하여 일반화를 진행한다. -> 수퍼 클래스 정의
  • 이렇게 되면, 두 클래스의 사용법이 크게 바뀌지는 않는다.
  • 이 수퍼 클래스의 목적은 소스코드를 보다 쉽게 관리하기 위해 만든 클래스이지, 직접 사용하려고 만든 클래스가 아니다.
    → 즉, 이 수퍼클래스의 객체를 만들어서 사용할 의도로 만든 클래스가 아니다.
  • 그럼에도, 이 클래스를 어떤 개발자가 사용할 때, 막을 수가 없다.
  • 이것을 막는 문법이 바로 추상클래스이다!!

상속과 추상메서드

  • 위의 수퍼 클래스(car)를 따로 쓰려고 만든 클래스가 아니라, 소스코드를 관리하기 쉽게 하려고 만든 클래스이므로, 목적 그대로 사용하기 위하여 마음대로 직접 사용하는 것을 막기 위하여 추상 클래스로 만들어 사용한다!
    ==> 추상 클래스를 인스턴스로 만들려고 하다면 오류가 나게 될 것이다!
  • Car 클래스는 소스코드를 유지보수하기 쉽도록 공통 코드를 추출해서 만든 수퍼 클래스이다.
  • Car 클래스를 직접 사용하려고(객체를 만들어서) 만든 것이 아니다.
  • 서브 클래스에서 재정의할 메서드라면 굳이 수퍼 클래스에서 구현하지 마라!
  • 서브 클래스에서 구현하도록 강제하고 싶다면 그때 해당 메서드를 추상 메서드로 선언한다.
  • 추상 메서드를 상속 받는 서브클래스는 반드시 구현해야한다.
    만약 구현하지 않으면, 추상 메서드인채로 남아있기 때문에 서브 클래스도 추상 클래스가 되어야 한다.
  • 객체를 생성하여 이용하지 못하게 하고 싶은 클래스라면 추상 클래스로 만들어주면 된다.
  • 추상 메서드는 주로 공개 범위뒤, public 뒤에 둔다.
  • 추상 클래스는 클래스명에 abstract가 들어가는 것이 좋다.

추상 메서드

  • 메서드 body를 만들지 않는다.
  • run()메서드 처럼 어차피 sub 클래스에서 재정의 해야할 메서드라면, 수퍼 클래스에서 정의하지 말자!
    ⇒ 메서드 선언, 메서드 형식만 정의해 놓자!
    Method signature(function prototype)
    → 메서드명, 파라미터 타입, 리턴 타입만 설정
  • abstruct class는 인스턴스를 만들 수 없다!
// 추상 클래스
// => 서브클래스에게 공통 기능을 상속해주는 목적으로 만든 클래스이다.
// => 직접 사용하지 않는 클래스이다.
// => 즉 개발자에게 이 클래스를 상속 받아 새 클래스를 만들어 쓰라는 의미다!
public abstract class Car {

  public Car() {
    super();
  }

  public void start() {
    System.out.println("시동 건다!");
  }

  public void shutdown() {
    System.out.println("시동 끈다!");
  }

  // 추상 메서드
  // => 서브 클래스에서 재정의할 메서드라면 굳이 수퍼 클래스에서 구현하지 말라!
  // => 또는 서브 클래에서 구현하도록 강제하고 싶다면 그때 해당 메서드를 추상 메서드로 선언한다.
  // => 추상 메서드를 상속 받는 서브클래스는 반드시 구현해야 한다.
  //    만약 구현하지 않으면 추상 메서드인채로 남아 있기 때문에 
  //    서브클래스도 추상클래스가 되어야 한다.
  //    일반 클래스는 인스턴스를 생성하여 메서드를 호출하기 때문에 
  //    구현되지 않은 메서드를 갖는 것은 오류이다.
  //    그래서 일반 클래스는 추상 메서드를 가질 수 없다.
  // => 왜? 추상 메서드가 있다는 것은 해당 메서드를 실행할 수 없다는 것이고
  //       실행할 수 없는 메서드를 갖는 클래스는 
  //       인스턴스를 생성해서는 안되기 때문에
  //       추상메서드를 갖는 클래스는 반드시 추상클래스여야 한다.
  //       일반 클래스는 추상메서드를 가질 수 없다.
  // 
  public abstract void run();

}
  • Car()를 상속 받은 Sedan 클래스
public class Sedan extends Car {

  @Override
  public void run() {
    System.out.println("쌩쌩 달린다.");
  }

  public void doSunroof(boolean open) {
    if (open) {
      System.out.println("썬루프를 연다.");
    } else {
      System.out.println("썬루프를 닫는다.");
    }
  }
}

기능 확장법 sheet

상속 안 받고 다른 클래스 기능 사용하기!!!

  • 상속은 내가 필요없는 기능까지 상속 받게 된다.
    → 이는, 위임으로 해결할 수 있다.
  • 기능 확장법
  1. 기존 코드 변경
  2. 코드 복제 후 기능 추가
  3. 상속
  4. 위임

    위임

  • 원래라면 상속할 클래스를 객체로 받고, 그 객체를 이용해서 메소드를 만든다.
  • 객체를 새 클래스 내부에 만듦
  • 예시
  • Calculator.java (원래라면 상속할 클래스)
public class Calculator {
  public int result;

  public void plus(int value) {
    this.result += value;
  }

  public void minus(int value) {
    this.result -= value;
  } 
}
  • Calculator2.java(CalculatorTest클래스를 위임받는 클래스)
public class Calculator2 {

  // plus(), minus()는 기존의 Calculator 클래스에게 위임한다.
  com.eomcs.oop.ex05.x1.Calculator origin = new com.eomcs.oop.ex05.x1.Calculator();

  public void plus(int value) {
    // 이 클래스가 포함하고 있는 객체에게 실행을 위임한다.
    origin.plus(value);
  }

  public void minus(int value) {
    // 이 기능은 기존의 클래스가 처리하도록 기존 객체에게 위임한다.
    origin.minus(value);
  }

  // 새 기능 또한 기존 객체의 필드를 사용하여 처리한다.
  public void multiple(int value) {
    origin.result *= value;
  }

  public int getResult() {
    return origin.result;
  }
}
  • CalculatorTest.java(메인)
public class CalculatorTest {
  public static void main(String[] args) {
    Calculator2 c = new Calculator2(); 
    // Calculator 객체를 내장하고 있다. 
    //Calculator2와 Calculator의 생명주기가 같다.

    c.plus(100);
    c.minus(200);
    c.multiple(2);

    System.out.println(c.getResult());
  }
}

  1. 위임

    위임

  • 원래라면 상속할 클래스를 객체로 받고, 그 객체를 이용해서 메소드를 만든다.
  • 객체를 새 클래스 외부에 만듦
  • 예시
  • origin이 인스턴스 변수라 생성자에서 실행된다.
  • 외부에서 넘겨 받은 클래스의 객체를 생성자에서 초기화 해준다.
  • Calculator2
public class Calculator2 {

  // 의존 객체를 외부에서 주입받는다.
  com.eomcs.oop.ex05.x1.Calculator origin;

  public Calculator2(com.eomcs.oop.ex05.x1.Calculator origin) {
    this.origin = origin;
  }

  public void plus(int value) {
    // 이 클래스가 포함하고 있는 객체에게 실행을 위임한다.
    origin.plus(value);
  }

  public void minus(int value) {
    // 이 기능은 기존의 클래스가 처리하도록 기존 객체에게 위임한다.
    origin.minus(value);
  }

  // 새 기능 또한 기존 객체의 필드를 사용하여 처리한다.
  public void multiple(int value) {
    origin.result *= value;
  }

  public int getResult() {
    return origin.result;
  }
}
  • CalculatorTest
// 5) 계산기 기능 확장 방법5 - 의존 객체 주입 방식을 적용하여 기능 확장
//    - 곱하기 계산 기능 추가
//    - 기존의 Calculator 객체를 포함한 후 새 기능을 추가하기
//
public class CalculatorTest {
  public static void main(String[] args) {

    com.eomcs.oop.ex05.x1.Calculator calculator = new com.eomcs.oop.ex05.x1.Calculator(); 

    Calculator2 upgradeCalculator = new Calculator2(calculator);

    upgradeCalculator.plus(100);
    upgradeCalculator.minus(200);
    upgradeCalculator.multiple(2);

    System.out.println(upgradeCalculator.getResult());
  }
}



클래스 관계 Cheet sheet

  1. 상속(inheritance)
class B extends A{

}
  1. 연관(association)
  • 지속적인 사용
  • 사람 / 핸드폰, 컴퓨터
class B {
	A obj;
}
  1. 집합(aggregation)
  • B가 A를 포함하는 관계
  • 컴퓨터 / 키보드, 마우스, 모니터
  • B와 A의 life cycle이 다르다.
    ⇒ B가 사라져도 A는 계속 이용될 수 있다.
  • 예를 들어 이미지 파일을 소스 파일이 포함하는 경우, 이미지 파일을 소스 파일이 포함하는데, 소스 파일이 사라져도 이미지 파일은 다른 곳에서 사용될 수 있다.
class B {
	A obj;
}
  1. 합성(composition)
  • B가 A를 강하게 포함
  • 컴퓨터 / 그래픽카드, CPU, 램
  • B와 A의 life cycle이 같다.
  • B가 사라지면, A도 사라진다.
class B {
	A obj;
}
  1. 의존(depending)
  • 참조가 이 경우에 속한다.
  • 특정 메소드를 일시적으로 사용
  • 핸드폰 / 충전지
class B {
	void m(A obj){
    
    }
}

UML 참고:
https://www.nextree.co.kr/p6753/

  • 웬만하면 애매~ 해서 다 연관으로 나타내고 강조하고 싶을 때 나머지를 이용해서 강조한다.
profile
https://github.com/Dingadung

0개의 댓글