Java의 정석 챕터7 객체지향 프로그래밍2
상속이란 기존 클래스를 재사용 해 새로운 클래스를 작성하는 것.
클래스 이름 뒤에 상속받고자 하는 클래스의 이름을 'extends'와 함께 써서 구현한다
class child extends Parent {
//...
}
상속 해주는 클래스 = 부모, 상위 클래스
상속 받는 클래스 = 자손, 자식, 하위 클래스
- 자손 클래스는 조상 클래스의 모든 멤버를 상속받는다
= 자손 클래스는 조상 클래스를 포함한다
= 생성자, 초기화블럭은 상속되지 않는다- 멤버개수 : 자손클래스 >= 조상클래스
- 형제 관계는 존재하지 않는다. 같은 부모를 상속받는 클래스 간에는 아무 관계가 성립하지 않는다.
- 같은 코드를 한 곳에서 관리함으로써 중복이 줄어드는 것에 의미가 있다.
= 공통되는 부분을 조상클래스에서 관리, 자손 클래스들은 자신에 정의된 멤버들만 관리하면 됨
포함관계 : 한 클래스의 멤버변수로 다른 클래스타입의 참조변수를 선언하는 것
-> 단위 별로 여러 개의 클래스를 작성하고, 이 단위 별 클래스들을 포함관계로 재사용.
class Point { int x; int y; } class Circle { Point c = new Point(); int rad; }
-> Circle이 Point를 포함
- is : ~은 ~이다 : 상속관계
: Shape - Circle : 원은 도형이다- has : ~은 ~을 가지고있다 : 포함관계
: Circle - Point : 원은 점을 가지고있다
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 클래스에 정의되어 있는 멤버 ( 인스턴스가 가져야 할 기본적 메소드 :toString, equals, ... )를 사용할 수 있다.
조상클래스로부터 상속받은 메소드를 자손클래스에서 변경하는 것
메소드의 선언부는 조상클래스와 완전히 동일하다
= 이름, 매개변수, 리턴타입은 동일
- 접근 제어자는 조상클래스보다 넓은 범위여야 함
: 대부분 같은 범위로 사용- 조상 클래스보다 많은 수의 예외를 선언할 수 없음
- 인스턴스 메소드 <-> static메소드 변환 불가
오버로딩 : 새로운 메소드를 정의하는 것 ( new )
: 이름만 같고 매개변수, 리턴타입을 다르게 지정 가능
오버라이딩 : 상속받은 메소드를 변경하는 것 ( modify )
: 선언부(이름, 매개변수, 리턴타입)가 완전히 동일, 내용만 변경
- 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; }
서로 관련된 클래스들의 묶음. 물리적으로 하나의 디렉토리이다.
- 하나의 소스파일은 첫번째 문장에 단 한번의 패키지 선언만 가능하다.
- 모든 클래스는 하나의 패키지에 속해야 한다.
=> 지정하지 않은 클래스는 자동으로 unnamed 패키지에 속한다.- 패키지는 물리적으로 .class 파일을 포함하는 하나의 디렉토리이다.
package 패키지명;
으로 선언한다.
-> 해당 소스코드에 포함된 모든 클래스, 인터페이스가 이 패키지에 속하게 된다.
다른 패키지의 클래스를 사용할 때, 패키지를 미리 import하면 클래스명에서 패키지명은 생략하고 사용할 수 있다.
-> import가 컴파일러에게 클래스의 패키지에 대한 정보를 제공하는 역할
- 접근제어자 : public, protected, default, private
- 그 외 : abstract, final, static, ...
접근제어자는 한 변수당 하나만 지정 가능, 그 외는 여러 개 가능
클래스 변수 앞에 붙는 제어자.
모든 인스턴스가 클래스변수를 공유하기 때문에 인스턴스와 관계없이 같은 값을 유지한다.
-> 인스턴스를 생성하지 않고도 사용할 수 있다.
static이 사용될 수 있는 곳 : 멤버변수, 메소드, 초기화블럭
-> 공통점 : 인스턴스를 생성하지 않고도 사용할 수 있다
- static 멤버변수 : 모든 인스턴스가 값 공유
클래스가 메모리에 로드 될 때 생성된다.- static 메소드 : static메소드 내부에서는 인스턴스 멤버를 사용할 수 없다.
다만 호출속도가 더 빠르므로 인스턴스멤버를 사용하지 않으면 static을 붙일 것을 고려해보자
클래스, 메소드, 멤버,지역변수에 사용가능
- 클래스 => 확장할 수 없는 클래스가 된다 : 다른 클래스가 상속받지 못함
- 메소드 => 오버라이딩이 불가능하다
- 지역, 멤버변수 => 값을 변경할 수 없는 상수
※ 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 : 제한없이 전부
※ 멤버변수의 유효성 검사, 내부처리, ...을 위해
변수는 private (상속이 예상되는 경우 private)로 설정하고
이 변수의 값 반환, 변경, ... 을 수행하는 메소드를 public으로 제공하자
private hour;
→public getHour(), public setHour(int hour)
생성자의 접근제어를 통해 인스턴스의 생성을 제한할 수 있다.
다른 클래스의 조상이 될 수 없다
-> 자손클래스에서 super() 호출이 불가능하기때문
-> class 선언 시 final 붙이는게 좋음
외부에서 직접 인스턴스 생성 불가능
인스턴스 생성하는 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 메소드 : 대신 인스턴스를 생성해서 반환하는 메소드
한 타입의 참조변수로 다른 타입의 객체를 참조할 수 있게함으로써 구현
= 조상클래스 타입의 참조변수로 자손클래스의 인스턴스를 참조할 수 있다.
조상참조변수 -> 자손인스턴스 가능
: 참조변수가 사용할 수 있는 멤버개수는 인스턴스의 멤버 개수 이하여야 함
( 따라서 자손참조변수 -> 조상인스턴스 불가능 )※ 같은 인스턴스라도 참조변수의 타입에 따라 사용할 수 있는 멤버변수가 달라짐
: 조상 참조변수의 인스턴스는 자손클래스에서만 존재하는 변수 사용불가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 클래스명(타입)
-> 일치여부 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로 가질 수 잇음
: 추상메소드를 포함하고 있는 미완성 클래스
1. 상속을 통해 자손 클래스에 의해서만 완성될 수 있다
2. 추상클래스로는 인스턴스를 생성할 수 없다.
abstract 리턴타입 메소드이름();
선언부만 작성하고 구현부는 남겨둔 메소드
- 목적
- 상속받는 클래스에 따라 메소드 내용이 달라지는 경우를 위함
- 자손클래스에서 추상메소드를 반드시 구현하도록 강제하기 위함
-> 자손클래스에서 목적에 맞게 오버라이딩 해 구현해야 함
+자손클래스에서 추상메소드를 반드시 구현하도록 강제하기 위함
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 리턴타입 메소드이름(); }
- 제약사항 (생략가능 -> 자동추가해줌)
- 모든 멤버변수는 public static final
- 모든 메소드는 public abstract
- 상속
- 인터페이스로부터만 상속받을 수 있다.
- 다중상속이 가능하다.
추상클래스와 마찬가지로 자신의 추상메소드를 구현할 클래스가 있어야 한다.
class 클래스이름 implements 인터페이스이름 {
//추상메소드 구현
}
abstract class 클래스이름 implements 인터페이스이름 {
//추상메소드 일부구현
}
: 인터페이스의 메소드 중 일부만 구현한다면 해당 클래스에도 abstract 붙여야 함
- 인터페이스를 구현할 때
메소드의 접근제어자를 반드시 public으로 해야한다.: 인터페이스의 추상메소드는 전부 public abstract + 오버라이딩 시 조상메소드보다 넓은 범위의 접근제어자 사용해야 하기 때문
자바는 클래스의 다중상속은 불가능하지만 인터페이스의 다중상속은 가능
- 멤버변수는 전부 static이므로 클래스 이름을 붙여 구별가능
- 메소드는 구현내용 없으므로 조상클래스의 메소드를 상속받으면 됨
→ 멤버의 충돌을 피할 수 있다.