JavaStudy Chap. 7

Jieun·2022년 4월 5일
0

자바

목록 보기
4/4

Java의 정석 챕터7 객체지향 프로그래밍2

- 상속

● 상속의 정의와 장점

상속이란 기존 클래스를 재사용 해 새로운 클래스를 작성하는 것.

클래스 이름 뒤에 상속받고자 하는 클래스의 이름을 'extends'와 함께 써서 구현한다

class child extends Parent {
 		//...
    }

상속 해주는 클래스 = 부모, 상위 클래스
상속 받는 클래스 = 자손, 자식, 하위 클래스

  1. 자손 클래스는 조상 클래스의 모든 멤버를 상속받는다
    = 자손 클래스는 조상 클래스를 포함한다
    = 생성자, 초기화블럭은 상속되지 않는다
  2. 멤버개수 : 자손클래스 >= 조상클래스
  3. 형제 관계는 존재하지 않는다. 같은 부모를 상속받는 클래스 간에는 아무 관계가 성립하지 않는다.
  • 같은 코드를 한 곳에서 관리함으로써 중복이 줄어드는 것에 의미가 있다.
    = 공통되는 부분을 조상클래스에서 관리, 자손 클래스들은 자신에 정의된 멤버들만 관리하면 됨

● 클래스 간의 관계 - 포함관계

포함관계 : 한 클래스의 멤버변수로 다른 클래스타입의 참조변수를 선언하는 것
-> 단위 별로 여러 개의 클래스를 작성하고, 이 단위 별 클래스들을 포함관계로 재사용.

class Point {
	int x;
    int y;
}
class Circle {
	Point c = new Point();
    int rad;
}

-> Circle이 Point를 포함

● 클래스 간 관계 결정하기

  • is : ~은 ~이다 : 상속관계
    : Shape - Circle : 원은 도형이다
  • has : ~은 ~을 가지고있다 : 포함관계
    : Circle - Point : 원은 점을 가지고있다
  • Deck - Card관계 예시
public class VarEx1 {
    public static void main(String args[]) {
        Deck d = new Deck();
        Card c = d.pick(0);
        System.out.println(c);

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

    }
}
class Deck {
    final int CARD_NUM = 52; //카드개수
    Card[] cardArr = new Card[CARD_NUM]; //카드개수만큼 카드배열

    Deck() { //덱 초기화
        int i = 0;
        for (int k = Card.KIND_MAX; k > 0; k--) {
            for (int n = 0; n < Card.NUM_MAX; n++)
                cardArr[i++] = new Card(k, n + 1);
        }
    }

    Card pick(int index) { //특정 인덱스의 카드 뽑기
        return cardArr[index];
    }

    Card 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);  //랜덤위치
            //swap
            Card tmp = cardArr[i];
            cardArr[i] = cardArr[r];
            cardArr[r] = tmp;
        }
    }
}

class Card {
    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, number;

    Card() { this(SPADE,1); }  //디폴트 생성값
    Card(int kind, int number) { this.kind = kind; this.number = number; }

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

여기서
System.out.println(c) 은 System.out.println(c.toString()) 과 같다.
즉, toString은 인스턴스의 정보를 문자열로 변환할 목적으로 정의된 것이다

<= 참조변수출력, 참조변수와 문자열의 결합에는 toString()을 자동으로 호출해 참조변수를 문자열로 대치해 처리한다.

toString은 Object클래스에 정의되어 있으므로 어떤 객체에도 사용이 가능하다.


● Object 클래스

모든 클래스의 최상위에 있는 조상클래스.
<= 다른 클래스의 상속을 받지 않는 모든 클래스는 자동적으로 Object 클래스를 상속받기 때문

모든 클래스의 최상위에 Object가 있으므로 모든 클래스는 Object 클래스에 정의되어 있는 멤버 ( 인스턴스가 가져야 할 기본적 메소드 :toString, equals, ... )를 사용할 수 있다.


- 오버라이딩

● 오버라이딩의 조건

조상클래스로부터 상속받은 메소드를 자손클래스에서 변경하는 것

메소드의 선언부는 조상클래스와 완전히 동일하다
= 이름, 매개변수, 리턴타입은 동일

  1. 접근 제어자는 조상클래스보다 넓은 범위여야 함
    : 대부분 같은 범위로 사용
  2. 조상 클래스보다 많은 수의 예외를 선언할 수 없음
  3. 인스턴스 메소드 <-> static메소드 변환 불가

● 오버로딩 vs 오버라이딩

  • 오버로딩 : 새로운 메소드를 정의하는 것 ( new )
    : 이름만 같고 매개변수, 리턴타입을 다르게 지정 가능

  • 오버라이딩 : 상속받은 메소드를 변경하는 것 ( modify )
    : 선언부(이름, 매개변수, 리턴타입)가 완전히 동일, 내용만 변경


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

  • this() : 내 클래스의 생성자
  • super() : 부모클래스의 생성자

자손 인스턴스 생성 시, 조상클래스의 멤버를 사용할 수 있으므로
자손클래스의 생성자에서 조상클래스의 생성자(super())가 호출된다.
-> 이 조상클래스 생성자의 호출은 상속관계를 거슬러 올라가 반복, Object()를 만나면 종료된다.
따라서 컴파일러가 자동으로 모든 클래스의 생성자 첫줄에 super();를 추가한다.

컴파일러가 자동으로 모든 클래스의 생성자 첫줄에 super();를 추가한다.
-> ※ 주의할 점 발생
1. 부모클래스에서 기본생성자를 정의하지 않고 다른 생성자를 정의해둔 경우, super()에 해당하는 기본생성자가 없기때문에 오류가 발생한다.
2. 조상클래스의 멤버변수는 조상의 생성자에 의해 초기화 되도록 해야한다

class Point {
	int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }
}
Class Point3D extends Point {
	int z;
    Point3D(int x, int y, int z) {
    	///컴파일러가 super();삽입 -> 오류발생
        super(x,y); //조상클래스의 멤버는 조상의 생성자가 초기화
        this.z = z;
}

- package & import

● package

서로 관련된 클래스들의 묶음. 물리적으로 하나의 디렉토리이다.

  1. 하나의 소스파일은 첫번째 문장에 단 한번의 패키지 선언만 가능하다.
  2. 모든 클래스는 하나의 패키지에 속해야 한다.
    => 지정하지 않은 클래스는 자동으로 unnamed 패키지에 속한다.
  3. 패키지는 물리적으로 .class 파일을 포함하는 하나의 디렉토리이다.

package 패키지명; 으로 선언한다.
-> 해당 소스코드에 포함된 모든 클래스, 인터페이스가 이 패키지에 속하게 된다.


● import

다른 패키지의 클래스를 사용할 때, 패키지를 미리 import하면 클래스명에서 패키지명은 생략하고 사용할 수 있다.
-> import가 컴파일러에게 클래스의 패키지에 대한 정보를 제공하는 역할


- 제어자

  • 접근제어자 : public, protected, default, private
  • 그 외 : abstract, final, static, ...
    접근제어자는 한 변수당 하나만 지정 가능, 그 외는 여러 개 가능

● static

클래스 변수 앞에 붙는 제어자.
모든 인스턴스가 클래스변수를 공유하기 때문에 인스턴스와 관계없이 같은 값을 유지한다.
-> 인스턴스를 생성하지 않고도 사용할 수 있다.

static이 사용될 수 있는 곳 : 멤버변수, 메소드, 초기화블럭
-> 공통점 : 인스턴스를 생성하지 않고도 사용할 수 있다

  • static 멤버변수 : 모든 인스턴스가 값 공유
    클래스가 메모리에 로드 될 때 생성된다.
  • static 메소드 : static메소드 내부에서는 인스턴스 멤버를 사용할 수 없다.
    다만 호출속도가 더 빠르므로 인스턴스멤버를 사용하지 않으면 static을 붙일 것을 고려해보자

● final

클래스, 메소드, 멤버,지역변수에 사용가능

  • 클래스 => 확장할 수 없는 클래스가 된다 : 다른 클래스가 상속받지 못함
  • 메소드 => 오버라이딩이 불가능하다
  • 지역, 멤버변수 => 값을 변경할 수 없는 상수

final 멤버변수의 초기화
=> 생성자를 통해 초기화 하자
생성자의 매개변수로 값을 받아 초기화 하면 인스턴스마다 다른 값을 갖게 할 수 있다.

class Card {
	final int NUMBER;	//바로 초기화 하지 않고
    final String KIND;	//생성자에서 초기화 한다.
    Card(String kind, int number) {  //매개변수로 받은 값으로 초기화
    	NUMBER = number;
        KIND = kind;
    }

- 접근제어자

멤버, 클래스를 외부에서 접근하는 정도를 제어한다

  • 사용 될 수 있는 곳
    클래스 : public, default
    멤버변수, 메소드 : public, protected, default, private

  • 같은 클래스 < 같은 패키지 < 자손클래스 < 전체

  • private < (default) < protected < public

private : 같은 클래스 내에서만
default : 같은 패키지 내의 클래스만
protected : 패키지 관계 없이 상속관계에 있는 클래스
public : 제한없이 전부

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

  1. 외부로부터 데이터 보호 목적
  2. 외부에는 불필요한 내부적으로 사용되는 부분 감추기 위함
    -> 내부작업을 위한 멤버변수, 메소드를 클래스 내부에 감춤

※ 멤버변수의 유효성 검사, 내부처리, ...을 위해
변수는 private (상속이 예상되는 경우 private)로 설정하고
이 변수의 값 반환, 변경, ... 을 수행하는 메소드를 public으로 제공하자

private hour;public getHour(), public setHour(int hour)


- 생성자의 접근제어자

생성자의 접근제어를 통해 인스턴스의 생성을 제한할 수 있다.

  • 생성자를 private으로 설정하는 경우
  1. 다른 클래스의 조상이 될 수 없다
    -> 자손클래스에서 super() 호출이 불가능하기때문
    -> class 선언 시 final 붙이는게 좋음

  2. 외부에서 직접 인스턴스 생성 불가능
    인스턴스 생성하는 public static 메소드가 따로 필요

※ 예제

class SingleTon {
	private static SingleTon s = new SingleTon();
    //private한 생성자
    private SingleTon() { ///.. }
    //인스턴스를 생성하지 않고도 호출할 수 있어야함 -> public static
    public static SingleTon getInstatnce() { return s; }
}

-private 생성자 : 외부에서 접근x, 외부에서 직접 인스턴스 생성x
-public static 메소드 : 대신 인스턴스를 생성해서 반환하는 메소드


- 제어자의 조합

  1. 메소드 static - abstract x
  2. 메소드 private - abstract x : 상속으로 완성해야하는데 상속이 불가
  3. 메소드 fianl - private 둘 중 하나만 : 어차피 둘 다 오버라이딩 불가하다는 의미
  4. 클래스 abstract - final x : 상속으로 완성해야 하는데 상속이 불가능

- 다형성

한 타입의 참조변수로 다른 타입의 객체를 참조할 수 있게함으로써 구현

= 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있다.

조상참조변수 -> 자손인스턴스 가능
: 참조변수가 사용할 수 있는 멤버개수는 인스턴스의 멤버 개수 이하여야 함
( 따라서 자손참조변수 -> 조상인스턴스 불가능 )

※ 같은 인스턴스라도 참조변수의 타입에 따라 사용할 수 있는 멤버변수가 달라짐
: 조상 참조변수의 인스턴스는 자손클래스에서만 존재하는 변수 사용불가

class Tv {
	void power() { ... }
}
class CaptionTv extends Tv {
	String text;
	void caption() {...}
}
Tv t = new CaptionTv();
CaptionTv c = new CaptionTv();
//t.caption(); 과 t.text; 사용 x

● 참조변수의 타입캐스팅

상속관계의 클래스의 참조변수 간 타입캐스팅 가능

  • 자손 -> 조상 : 형변환 생략가능
    : 자손의 멤버변수 > 조상의 멤버변수 이기 때문
  • 조상 -> 자손 : 생략 불가
  • 같은 부모를 상속받은 클래스 : 형변환 불가

참조변수 변환은 인스턴스 자체에는 아무 영향을 주지 않음
참조하는 인스턴스에서 사용할 수 있는 멤버의 범위를 조절하는 용도!!

!!주의할 점!!
"참조변수"의 타입캐스팅일뿐, 인스턴스의 형변환이 아님을 명심

Car car  = new Car();
FireEngine f = null;
f = (FireEngine)car; //런타임에러발생!!

참조변수를 아무리 타입캐스팅 해도
해당 참조변수가 참조하는 인스턴스가 조상클래스타입임
-> 컴파일은 되지만 실행 시 에러 발생함

" 참조변수의 인스턴스를 자손타입으로 형변환하는 것은 불가능!"

- instanceof연산

참조변수 instanceof 클래스명(타입) -> 일치여부 bool값 반환
: 참조변수가 참조하는 인스턴스의 실제 타입 알기위해 사용

true를 반환하는 기준 : 검사한 타입으로 형변환이 가능하다
== 조상타입에도 true를 반환다.


● 참조변수 - 인스턴스 연결

멤버가 조상, 자손클래스에 중복으로 정의된 경우
1. 메소드 : 인스턴스의 타입에 맞는 메소드가 호출
2. 멤버변수 : 참조변수의 타입에 따라 갈림


● 매개변수의 다형성

"메소드의 매개변수를 조상클래스 타입의 참조변수로 사용"하면
자손클래스 타입으로 받는 메소드를 여러개 정의할 필요 없어짐

<= 조상클래스 참조변수가 자손클래스를 참조할 수 있는 다형성의 활용이다.

class Product { int price; String code; }
class Tv extends Product { ... }
class Compouter extends Product { ... }

class Buyer {
	int money =1000;
    void buy(Product p) {  // Product 참조변수로 Tv, Computer 인스턴스를 받을 수 있음
    	money-=p.price;
    }
}

● 여러종류의 객체를 배열로 다루기

조상타입의 참조변수로 자손타입 인스턴스 참조 가능
조상타입 참조변수 배열공통의 조상 가진 객체를 배열로 묶을 수 있다!

Product p[] = new Product[3];  //조상타입 배열
p[0] = new Tv(); //공통의 조상 가진
p[1] = new Computer(); //객체들을 element로 가질 수 잇음
  • 객체배열 유동적 관리 -> Vector 클래스 사용
    Vector 클래스는 동적으로 크기가 관리되는 객체배열
    기본생성자 -> 10개 크기의 인스턴스 생성, 더 커지면 자동으로 크기 증가
    add, remove, isEmpty, get, ...의 연산 가능

- 추상클래스

● 추상클래스

: 추상메소드를 포함하고 있는 미완성 클래스
1. 상속을 통해 자손 클래스에 의해서만 완성될 수 있다
2. 추상클래스로는 인스턴스를 생성할 수 없다.

●추상메소드

abstract 리턴타입 메소드이름();

선언부만 작성하고 구현부는 남겨둔 메소드

  • 목적
  1. 상속받는 클래스에 따라 메소드 내용이 달라지는 경우를 위함
  2. 자손클래스에서 추상메소드를 반드시 구현하도록 강제하기 위함
    -> 자손클래스에서 목적에 맞게 오버라이딩 해 구현해야 함

+자손클래스에서 추상메소드를 반드시 구현하도록 강제하기 위함

abstract class Unit {
	int x, y;
    abstract void move(int x, int y);
    void stop() {...}
}

class Tank extends Unit {
	void move(int x, int y) { //탱크의 이동 }
    void stop() { //stop 오버라이딩 }
    void changeMode() {...}
}
class Marine extends Unit {
	void move(int x, int y) { //보병의 이동 }
    void stop() { //stop 오버라이딩 }
    voids stimpack() {...}
}

전부 이동하는 move 기능이 필요하지만 보병과 탱크이므로 작동방식은 다름

Unit grop[] = new Unit[2];
group[0] = new Marine();
group[1] = new Tank();
for(int i=0;i<group.length;i++) group[i].move();

추상메소드를 공유하는 객체들로 배열을 생성
-> 객체배열의 element들로 구현한 추상메소드 사용


- 인터페이스

일종의 추상클래스, 추상클래스보다 추상화정도가 높아
오직 추상메소드와 상수만 가진 클래스

  • 작성
interface 인터페이스이름 {
	public static final 타입 상수이름 =;
    public abstract 리턴타입 메소드이름();
}
  • 제약사항 (생략가능 -> 자동추가해줌)
  1. 모든 멤버변수는 public static final
  2. 모든 메소드는 public abstract
  • 상속
  1. 인터페이스로부터만 상속받을 수 있다.
  2. 다중상속이 가능하다.

● 인터페이스의 구현

추상클래스와 마찬가지로 자신의 추상메소드를 구현할 클래스가 있어야 한다.

class 클래스이름 implements 인터페이스이름 {
	//추상메소드 구현
}
abstract class 클래스이름 implements 인터페이스이름 {
	//추상메소드 일부구현
}

: 인터페이스의 메소드 중 일부만 구현한다면 해당 클래스에도 abstract 붙여야 함

  • 인터페이스를 구현할 때
    메소드의 접근제어자를 반드시 public으로 해야한다.

: 인터페이스의 추상메소드는 전부 public abstract + 오버라이딩 시 조상메소드보다 넓은 범위의 접근제어자 사용해야 하기 때문


● 인터페이스 이용한 다중상속

자바는 클래스의 다중상속은 불가능하지만 인터페이스의 다중상속은 가능

  • 멤버변수는 전부 static이므로 클래스 이름을 붙여 구별가능
  • 메소드는 구현내용 없으므로 조상클래스의 메소드를 상속받으면 됨
    → 멤버의 충돌을 피할 수 있다.

0개의 댓글

관련 채용 정보