7장 - 객체지향 프로그래밍 II

·2022년 12월 19일
0

자바의 정석

목록 보기
10/12
post-thumbnail

이번엔 드럼통에 몸을 담글까 말까 햇는데요

상속

1. 상속의 정의와 장점

두 클래스를 부모 - 자식 관계를 맺어주는 것 (extends 사용)
기존의 클래스를 재사용해 새로운 클래스를 작성하는 것

얻을 수 있는 장점

  1. 코드의 공통적인 관리 -> 코드의 추가 및 변경에 용이
  2. 재사용성 증가, 중복 제거 -> 프로그램의 생산성과 유지보수에 용이

extends를 이용한 상속

class Cild extends Parent {

}

이렇게 extends 명령어를 이용해 부모 - 자식 관계를 맺어줄 수 있으며, 뒤에 조상(부모)클래스가 오면 된다.

상속 관계도는 다음과 같다

요렇게 자손이 부모로부터 상속을 받으면, 조상 클래스의 모든 멤버를 상속받을 수 있다. (생성자, 초기화 블럭은 제외)

  • 자손 클래스에 새로운 코드가 추가되어도, 조상에는 아무런 영향을 주지 못한다.
  • 자손 클래스의 멤버 개수는 조상 클래스보다 항상 같거나 많다.
  • 부모와 자식의 관계(상속관계)만 존재한다. 형제관계는 없음.
  • 자손 클래스의 인스턴스를 생성하면, 조상 클래스의 멤버와 자손 클래스의 멤버가 합쳐진 하나의 인스턴스가 생성된다.

2.포함관계

한 클래스의 멤버변수로 다른 클래스 타입의 참조변수를 선언한다.

class Circle {
	int x;
    int	y;
    int	r;
}

class Point {
	int x;
    int	y;
}
이렇게 각자의 객체로 존재하던걸

class Circle {
	Point c = new Point();
    int r;
}
이렇게 클래스 안에 다른 인스턴스를 만들어버림

작은 단위의 클래스를 만들고, 다른 Type의 클래스를 참조(조합)해 클래스를 만든다.

3.클래스간의 관계 설정

상속관계 : is, ~은 ~이다
포함관계 : has, ~은 ~을 가지고 있다

보통 포함관계가 대부분이고, 상속은 꼭 필요할 때만 사용하도록 하자.

두 가지 예시를 보며 익숙해지자!!!

package test.객체지향2;

class DrawShape {
  public static void main(String[] args) {
    MyPoint[] p = {
            new MyPoint(100, 100),
            new MyPoint(140, 50),
            new MyPoint(200, 100)
    };
    Triangle t = new Triangle(p); // 생성자가 기본생성자 없이 매개변수를 필요로해서 바로 객체 배열을 넣어준다. + 위에서 선언된 객체배열의 내용을 필요로 함
    t.draw();

    Circle2 c = new Circle2();
    c.draw();
  }
}
class MyPoint { // x, y를 담은 객체
  int x;
  int y;

  MyPoint(int x, int y) {
    this.x = x;
    this.y = y;
  }

  MyPoint() {
    this(0, 0);
  }

  String getXY() {
    return "(" + x + ")" + "(" + y + ")";
  }
}

class Shape {
  String color = "black";
  void draw() {
    System.out.printf("[color=%s]%n", color);
  }
}

class Triangle extends Shape {
  MyPoint[] p = new MyPoint[3]; // 포함관계

  Triangle(MyPoint[] p) {
    this.p = p; // Triangle 객체에서 사용할 배열을 lv로 받아옴
  }

  @Override
  void draw() {
    System.out.printf("[p1=%s, p2=%s, p3=%s, color=%s]%n", p[0].getXY(), p[1].getXY(), p[2].getXY(), color);
  }
}

class Circle2 extends Shape {
  MyPoint center; // 포함관계 - 다른 참조변수를 선언해
  int r;

  Circle2() {
    this(new MyPoint(0, 0), 100); // 밑에 있는 매개변수를 받는 생성자를 호출
  }

  Circle2(MyPoint center, int r) {
    this.center = center;
    this.r = r;
  }

  @Override
  void draw() {
    System.out.printf("[center=(%d %d), r=%d, color=%s]%n", center.x, center.y, r, color);
  }
}
package test.객체지향2;

public class DeckTest {
  public static void main(String[] args) {
    Deck d = new Deck(); // deck 객체 생성
    Card3 c = d.pick(); // Card3 참조변수에 d.pick()메서드 반환값 - 랜덤하게 하나 뽑은거, 타입 일치하니까 가능
    System.out.println(c);

    d.shuffle();
    c = d.pick();
    System.out.println(c);
  }
}

class Deck {
  final int CARD_NUM = 52; // 총 카드는 52장임
  Card3 cardArr[] = new Card3[CARD_NUM]; // 포함관계, 객체배열 생성

  Deck() { // Deck의 카드 초기화
    int i = 0;

    for(int k = Card3.KIND_MAX; k > 0; k--) {
      for(int n = 0; n < Card3.NUM_MAX; n++) {
        cardArr[i++] = new Card3(k, n + 1); // 객체 배열에 무늬, 넘버 초기화해서 하나씩 넣어줌
      }
    }
  }

  Card3 pick(int index) { // Card 타입 반환 메서드 - Card3 랜덤한 넘버의 객체배열 주소를 반환
    return cardArr[index];
  }

  Card3 pick() {
    int index = (int)(Math.random() * CARD_NUM);
    return pick(index);
  }

  void shuffle() {
    for(int i = 0; i < cardArr.length; i++) {
      int r = (int)(Math.random() * CARD_NUM);

      Card3 tmp = cardArr[i];
      cardArr[i] = cardArr[r];
      cardArr[r] = tmp;
    }
  }
}

class Card3 {
  static final int KIND_MAX = 4; // 카드 무늬 수
  static final int NUM_MAX = 13; // 무늬별 카드의 수

  static final int SPADE = 4;
  static final int DIAMOND = 3;
  static final int HEART = 2;
  static final int CLOVER = 1;

  int kind;
  int number;

  Card3() {
    this(SPADE, 1);
  }

  Card3(int kind, int number) { // kind, number iv를 변경
    this.kind = kind;
    this.number = number;
  }

  public String toString() {
    String[] kinds = {"", "CLOVER", "HEART", "DIAMOND", "SPADE"};
    String numbers = "0123456789XJQK";
    return "kind : " + kinds[this.kind] + ", number : " + numbers.charAt(this.number);
  }
}

4.단일 상속

자바는 오직 단일 상속만을 허용한다. 둘 이상의 클래스로부터 상속을 받을 수 없다.

다중상속의 문제점

  • 클래스 간의 관계가 복잡해진다
  • 여러 클래스로부터 상속받은 멤버간의 이름이 같은 경우
  • 메서드가 같은 경우 충돌

근데 베리머치 복합적인 기능을 가진 클래스를 작업해야 할 때는?

  • interface를 사용한다
  • 비중 높은 클래스를 상속시키고, 나머지 클래스들은 포함관계로 만든다

+) 모든 클래스의 최상위 조상으로 Object클래스가 존재한다. 다른 클래스로부터 아무런 상속을 받지 않는 클래스가 있다고 하더라도, 컴파일 후에는 자동적으로 extends Object가 추가되어있다.

오버라이딩

조상 클래스로부터 상속받은 메서드 내용을 변경한다.

자손 클래스에서 오버라이딩 시, 조상 클래스의 메서드와
선언부와 반환값이 같아야 한다!

추가적인 제한 조건으로는

  1. 접근 제어자가 조상 클래스 메서드보다 좁은 범위 불가능 (public > protected > (default) > private)
  2. 조상 클래스의 메서드보다 많은 수의 예외 선언 불가
  3. 인스턴스 메서드를 static으로 변경하거나 그 반대의 경우 불가

1. 오버로딩 vs 오버라이딩

이름만 비슷하고 무관합니다. 벅벅

오버로딩(overloading) : 기존에 없는 새로운 메서드 정의
오버라이딩(overriding) : 상속받은 메서드의 내용을 변경

class Parent {
	void parentMethod() {
    }
}

class Child extends Parent {
	void parentMethod() {  // 오버라이딩 - 조상의 메서드 내용을 자손이 변경
    내용은 다를 수 있겠지,, 반환값은 같겠지..
    }
    void parentMethod(int i) {} // 오버로딩 - 이름 같은 메서드, 기존에 없던거임 매개변수 받음 얜
    
    void childMethod(){}
    void childMethod(int i) {} // 오버로딩
}

2. super

조상 클래스로부터 상속받은 멤버를 참조할 때 사용하는 참조변수

  • 객체 자신을 가리키는 참조변수. 인스턴스 메서드(생성자) 내에서만 존재한다 - static 메서드에선 당근 못씀
  • 조상의 멤버를 자신과 구별할 때 사용한다.

lv, iv를 구별할 때 사용한 this와 유사한 기능을 하는군아.. 하면 된다.

class SuperTest {
	public static void main(String args[]) {
    	Child c = new Child();
        c.method();
    }
}

class Parent {
	int x = 10;
}

class Child extends Parent {
	int x = 20;
    
	void method() {
    	System.out.println("x = " + x); // iv, 20
        System.out.println("x = " + this.x); // 가까운 쪽, 20 
        System.out.println("x = " + super.x); // 조상꺼 불러옴 
    }
}

3.super() - 조상 클래스의 생성자

조상의 생성자를 호출할 때 사용한다.
자손 클래스 멤버가 조상 클래스의 멤버를 사용할 수도 있으니, 꼭 생성자 첫 줄에서 써주셈

조상의 멤버는 조상의 생성자를 호출해 초기화 해야한다. 생성자와 초기화 블럭은 상속되지 않음!
Object클래스를 제외한 모든 클래스의 생성자 첫 줄에 생성자, this() 또는 super()를 호출해야한다. 안그러면 컴파일러가 super();를 생성자 첫줄에 삽입함.


class PointTest {
	public static void main(String[] args) {
    	Point3D p3 = new Point3D(1,2,3);
    }
}

class Point {
   	int x, y;
        
    Point(int x, int y) {
    	this.x = x;
        this.y = y;
    }
        
    String getLocation() {
        return "x :" + x + ", y:" + y;
    }
}

class Point3D extends Point {
	int z;
    
    Point3D(int x, int y, int z) { // 여기서 조상꺼 변수를 쓰는데 조상에 대한 생성자가 없다
    	this.x = x; // super(x, y)로 바꿔주면 됨
        this.y = y;
        this.z = z;
    }
    
    String getLocation() {
    	return "x : " + x + ", y :" + y + ", z:" + z; // 조상 PointRj dhqjfkdleld
    }
}

에러가 발생하는 예제임
조상 클래스의 멤버변수는 조상 생성자에 의해 초기화되도록 하자!!

제어자

1. 제어자란?

2. static - 클래스의, 공통적인

static변수(클래스 변수)는 모든 인스턴스가 공유하기 때문에, 인스턴스에 관계 없이 같은 값을 지닌다.

static이 붙은 iv, method, 초기화 블럭은 클래스에서 관리하기 때문에(iv + static = cv) 인스턴스를 생성하지 않아도 사용 가능.

인스턴스 멤버를 사용하지 않는다면, static 메서드를 생성하는 것을 고려해보자.
외 못써요?
얘가 먼저 메모리에 올라가니까 iv는 객체가 만들어져야 쓸 수 있음. 객체 유무에 대한 불확실성 때문.

3. final - 마지막의, 변경될 수 없는

상속, 변경, 오버라이딩 불가능, 상수.

이러한 특성을 이용한 초기화

class Card {
	final int NUMBER; // 선언만 되고 안쓰였슴
    final String KIND;
    static int width = 100;
    static int height = 250;
    
    Card(String kind, int num) {
    	KIND = kind;
        NUMBER = num;
    }
    
    Card() {
    	this("HEART", 1); // 생성자를 이용해 값 넣어줌
    }
    
    public String toString() {
    	return KIND + " " + NUMBER;
    }
}

class FinalCardTest{
	public static void main(String[] args) {
    	Card c = new Card("HEART", 10);
        
    }
}

4. abstract - 추상의, 미완성의

미완성 메서드를 포함한 미완성 설계도!

5. 접근 제어자

클래스에는 public, (default)
메서드, 멤버 변수에는 public protected, (default), private

6. 접근제어자를 이용한 캡슐화

외부로부터 데이터 보호
외부에는 불필요한, 내부적으로만 사용되는 부분을 감추기 위해.

public class Time {
	private int hour;
    private int minute;
    private int second;
    
    public int getHour() {
    	return hour;
    }
    
    public void setHour(int hour) {
    	if (hour < 0 || hour > 23) return ;
        this.hour = hour;
    }
    
    min, second도 유사하게 작성하믄 됨
}

위의 예시에서는 iv를 private로 선언해 외부의 접근을 막았고, getter, setter를 public으로 선언해 외부에서 method로 iv에 간접적으로 접근하도록 유도하였다.

package test.객체지향3;

public class TimeTest {
  public static void main(String[] args) {
    Time t = new Time(12, 35, 30);
    System.out.println(t);

    t.setHour(t.getHour() + 1);
    System.out.println(t);
  }
}

class Time {
  private int hour, min, sec;

  Time(int hour, int min, int sec) {
    setHour(hour);
    setMin(min);
    setSec(sec);
  }

  public int getHour() {
    return hour;
  }

  public void setHour(int hour) {
    if (hour < 0 || hour > 23) return;
    this.hour = hour;
  }

  public int getMin() {
    return min;
  }

  public void setMin(int min) {
    if (min < 0 || min > 59) return ;
    this.min = min;
  }

  public int getSec() {
    return sec;
  }

  public void setSec(int sec) {
    if (sec < 0 || sec > 59) return;
    this.sec = sec;
  }

  @Override
  public String toString() {
    return hour + ":" + min + ":" + sec;
  }
}

7. 제어자의 조합

절대 귀찮은게 아니다 갓교재가 넘 정리를 잘해놓음 ㅎㅎ;

와 양 줜나 많네 내가 못추리느건가

다형성

1. 다형성?

한 타입의 참조변수로 여러 타입의 객체를 참조한다.
조상클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조한다!

반대의 상황은 안댐 자손클래스 타입 참조변수로 조상 클래스 인스턴스 참조 안댐

이 차이라거 생각하면 된다

추가적으로 참조변수의 형변환은 인스턴스에 아무런 영향을 미치지 않음.

2. instanceof연산자

형변환 해도 되는지 확인

참조변수 instance 타입(클래스명)
boolean값이 리턴된당~

추상클래스

미완성 설계도를 상속을 통해 이어받아 자손클래스에서 완성시켜보자

이 추상클래스를 상속받아 구현을 해주려면, 갖고 있는 추상메서드를 모두 구현해줘야댐
몇개 하다 말거면 또 abstract를 붙여야한당
추상 메서드를 포함했다는 것 빼곤 일반 클래스와 다를게 없다.

예의상 예시를 좀 들어보자

abstract class Unit { // 추상메서드를 포함한 추상클래스
	int x, y;
    abstract void move(int x, int y); // 몸통 없으니 얘가 추상메서드
    void stop(){} // 몸통 있으니까 얜 ㄴㄴ
}

class Marin extends Unit{
	void move(int x, int y) {} // 몸통 있으니까 다 만들엇다
    void stimPack(){}
}

인터페이스

추상메서드 집합. 밑그림만 그려진 기본 설계도다.

인스턴스 멤버와 생성자를 가질 수 없다.

1. 인터페이스의 작성

interface Hello{
	public static final NUMBER = 99;
    public abstract test(int x); // 추상메서드
}

제약사항

  1. 모든 멤버변수는 public static final이어야 하며, 생략 가능.
  2. 모든 메서드는 public abstract이어야 하며, 생략 가능
  3. 이후 업데이트로 static, default 메서드도 생성 가능.

2. 인터페이스의 구현

인터페이스는 인터페이스로부터만 상속을 받을 수 있으며, 다중상속이 가능하다.

이제 상속 받은걸 implements를 붙여 몸통을 만들어 구현해보자
얘도 추상메서드와 마찬가지로 모두 구현한게 아니라면 abstract를 붙여 추상클래스로 선언해야한다.

profile
어?머지?

0개의 댓글