[210504화] 다형성, 추상메소드, 인터페이스
[복습] toString()은 조상격인 Object로부터 상속받아 오버라이딩한 것!
@Override
public String toString() {
return super.toString() + ", + displacement=" + displacement;
}
: super.toString()는 부모의 toString()을 호출하겠다.
· 콘크리트 클래스: 객체를 생성할 수 있는 클래스 ↔ 객체를 생성할 수 없는 클래스
▶ 다형성 : 하나의 메소드를 다양한 형태로 메소드를 호출할 수 있다. 부모클래스가 자식클래스를 갖도록, 가리키는 것이다.
ex) Vehicle v1 = new Car(4000); 부모클래스인 Vehicle v1가 자식클래스인 Car()를 갖게 된다. 부모클래스인 Vehicle은 멤버변수로 brand만 가지고 있기때문에 배기량을 나타낼 수 없지만, 다형성을 통해 자식클래스를 갖도록 하여 차브랜드와 배기량을 출력할 수 있다.
▶ 상속과 함께 알아야할 연산자 instanceof
- 상속관계의 여부를 확인할 때 사용한다.
- 이항 연산자
- 레퍼런스변수 instanceof 객체 : true/false를 리턴
ex) Vehicle v1 = new Car; : 좌변의 변수가 우변에 위치한 클래스(Car)의 객체를 가리키고 있는지 확인한다.
v1 instanceof Car :
객체에 레퍼런스변수가 속하니?를 묻는 것이고 그에 대한 대답으로 true와 false로 출력값을 받을 수 있다.
[오버로딩과 오버라이딩의 차이]
오버로딩은 메소드의 이름이 같고 매개변수의 개수, 타입이 달라 다른 메소드처럼 사용하는 것.
오버라이딩은 부모로부터 상속받은 것을 확장해서 사용하는 것.
[상속 요약해보기]
부모클래스의 기능을 상속받은 자식클래스가 있고, 자식클래스는 부모클래스로부터 상속받은 기능을 확장해서 사용하는 것을 오버라이드라고 하고 자식클래스가 확장한 기능을 부모클래스가 갖고 싶을 때 자식클래스를 가리켜 사용하는 문법이 다형성이다?
부모클래스에서 기능을 상속받은 자식클래스의 메소드를 부모는 전부 다 사용할 수 있고, 자식은 부모의 모든 것(멤버변수 및 메소드)을 가져다 사용할 수 없다.?
[다형성의 예]
다형성의 시작
1. Polygon이라는 클래스를 생성
Polygon[] = new Polygon[10]; 방이 10개인 객체배열 생성
poly라는 각각의 하나의 방 안에 Polygon 타입이면 다 저장할 수 있다.
[부모클래스의 메소드 outpout를 자식클래스가 오버라이드]
Alt + S → Override/Implement Methods 클릭
슈퍼클래스(부모)로부터 기능을 물려받은 서브클래스(자식) MyCircle, MyRectangle 클래스 생성
현재 poly는 총 3개! is -a관계이기 때문에 MyCircle(자식), MyRectangle(자식)는 부모클래스로부터 상속받았기때문에 Polygon 안에 속한다. 부모클래스 Polygon의 객체인 poly 안에는 총 3개의 Polygon(본인), MyCircle(자식), MyRectangle(자식)이 존재한다.
[출력값으로 확인해보기]
public static void printAll : void인 것을 보아 리턴 타입이 없는 것을 알 수 있다!
public class PolyTest {
public static void main(String[] args) {
Polygon[] poly = new Polygon[10];
poly[0] = new Polygon();
poly[1] = new MyCircle();
poly[2] = new MyRectangle();
printAll(poly[0]);
printAll(poly[1]);
printAll(poly[2]);
}
printAll(poly[0]);를 넘길 때 주소를 넘긴다.
주소를 넘긴다는 말이 무슨 말이지?
▶ public static void printAll(Polygon poly)에서 poly라는 것은 어떤 것을 받느냐하면
poly[0] = new Polygon();에서 poly[0]는 Polygon()을 가리킴, Polygon()의 주소가 100번지라면 poly[0]도 100번지를 가리킨다.
poly[1] = new MyCircle();에서 poly[1]은 MyCircle()을 가리킴
poly[2] = new MyRectangle();에서 poly[2]는 MyRectangle()을 가리킴
poly[i] 안에 있는 값은 주소를 나타내는 것!
public static void printAll(Polygon poly)에서 Polygon poly는 주소를 가리킴
poly[0]를 public static void printAll(Polygon poly) 안에 넘기면 (100번지 -> 100번지), 같은 객체!
· call by reference : 주소가 넘어가는 것, 주소값, 참조값이 넘어가는 경우, 같은 주소를 받는 것
· call by value : 값이 넘어가는 것
public static void printAll(Polygon poly)
Polygon poly : Polygon타입의 poly가 뭐 인지는 모르겠지만 Polygon(본인), MyCircle(자식), MyRectangle(자식)는 모두 OK!
만약에 다형성이 없다고 하면 각각의 변수가 따로 필요하다.
하나의 output을 잘 만들어 놓으면 여러 개의 메소드를 생성한 것과 같이 효율적!이다.
Polygon[] poly = new Polygon[10];
poly[0] = new Polygon(); 부모는 설계를 하고
poly[1] = new MyCircle(); 자식이 구현하는 경우가 많다!
poly[2] = new MyRectangle();
[인터페이스 Interface]
: 객체를 생성할 수 없는 타입
: 생성자를 가질 수 없다.
: 주로 자식들로 하여금 오버라이딩을 강제화 하기 위해 사용됨
: 멤버변수를 가질 수 없다. 변수를 선언할 경우 public static final이 됨
=> 즉, static이 의미하는 우리멤버가 아니다라는 것을 알 수 있다.
: 메소드는 구현되지 않은 상태로 제공 => 우리는 overriding 해서 사용해야 한다.
: 일종의 메소드 설계도로써의 의미가 강함
: 선언 방법 ↓
public interface 인터페이스명 {
구현되지 않은 메소드(추상메소드)
public void output(); <- 메소드의 바디, 몸통이 없는 상태
}
public class 클래스명 implements 인터페이스명 { ← 인터페이스 구현
@override
public void output(){
구현된 코드; 즉, 추상메소드의 몸체를 만들어줌
}
}
public class 클래스명 implements 인터페이스명
: 클래스명으로 인터페이스명을 구현하게 된다. → 클래스는 인터페이스가 가지고 있는 필드(상수)와 메소드 이름을 물려받게 된다.
생성자는 기능이 아니다.
이름바꾸기 refactor-> rename
[인터페이스 구현하는 방법]
구현한다하고 구현하지 않아 오류 발생
SampleImpl은 상속된 추상메소드를 구현해야 한다. → Add unimplemented methods (구현되지 않은 메소드를 추가하겠다!) 선택
class SampleImpl implements Sample, Tempolary{}에서 SampleImpl의 입장에서는 구현한 것이 총 2개, Sample, Tempolary이다.
Sample, Tempolary가 SampleImpl의 부모의 역할(상위개념)을 한다.
▶ Sample, Tempolary가 Implement하겠다는 것은 ↓ 여기를 의미한다.
interface Sample {
public double calc();
public int add(int a, int b);
public int multiply(int a, int b);
}
구현한 이후로는 class SampleImpl를 new해서 객체로 사용할 수 있다.
★개발자들은 개발을 하기 전에 인터페이스 먼저 설계를 하고, 인터페이스에 따라 구현을 하는 것이 순서이다.
우리는 클래스부터 배웠기 때문에 역으로 클래스에서 인터페이스로~!
[전날에 공부한 PhoneService 불러오기]
refactor를 통해 Rename 이름을 먼저 PhoneServiceImpl로 바꿔주세요.
class명이 변경된 것을 확인할 수 있다.
여기서 기능하는 메소드를 고른다. 생성자, setter, getter는 기능이라 할 수 없다.
기능을 하는 메소드를 골라 Interface에 넣어줄 것!
PhoneService라는 명의 Interface 만들어주기
PhoneService 함수들을 떼어온다.
★ 만약 우리가 순서대로 인터페이스를 설계하고 클래스를 코딩했다면
public class PhoneServiceImpl implements PhoneService 이런 모습이었을 것!
interface는 저렇게 로고?가 약간 다르다.
public class PhoneServiceImpl implements PhoneService
인터페이스인 PhoneService를 구현한 public class PhoneServiceImpl 이라는 뜻
interface PhoneService에서 주석을 떼면 이런 모습 ↓
<방명록의 명사적, 동사적 요소>
명사적 요소
- 글번호, 제목, 내용, 날짜, 글쓴이, 비밀번호
동사적 요소(CRUD)
- 글쓰기(write), 삭제(delete), 수정(modify), 조회(retrieve)
public interface GuestbookService {
public boolean or int write(GuestVO VO); //글이 써졌으면 t/f or 숫자로
public int delete(int guestbookNo) //삭제가 되었으면 1 or
public int modify(GuestVO VO);
public GuestVO VO retrieve(int guestbookNo); //GuestVO VO
}
[방명록을 구현해보자!]
패키지 생성 후
1.VO에는 명사적인 요소만 따다가! 만들기
VO에 기본적으로 있어야 하는 멤버변수, 기본생성자, 일반생성자, setter, getter, toString
여기서 주의할 점!
비밀번호, 통장비밀번호와 같이 아주 개인정보의 경우에는 getter가 없다.
설계시 private으로 하거나 아예 만들지 않는다.
2. 인터페이스 생성
동사적인 요소의 설계는 Interface!
인터페이스의 접근지정자는 public, 인터페이스명은 동사적인 의미가 담긴 것으로 하는 것이 좋다.
[Interface 설계 방법]
지금 이 패턴은 게시판 인터페이스와 거의 유사하다.
① 글 작성 write
public int write(GuestbookVO vo)
글을 작성할 때는 명사적 요소(글번호, 제목, 내용, 날짜, 글쓴이, 비밀번호)가 다 필요한데 (GuestbookVO vo)를 달라고 하면 되고, 이 글은 데이터베이스에 저장이 된다. 글이 써질 수도 있고, 어떤 요인으로 인해 글이 안써질 수도 있기 때문에 확인을 하기위해 int/ boolean 타입을
선택해 사용한다. int의 경우, 1이 return되면 글이 잘 써졌다 혹은 boolean의 경우, true가 return되면 글이 잘 써졌다라고 확인할 수 있다.
② 글 삭제 delete
public int delete(int guestbokNo)
삭제할 경우, 내가 쓴 글 1개만 삭제할 것이기 때문에 글번호가 필요하다. public int delete(int guestbookNo); 글 번호에 해당하는 것을 삭제하고 삭제되었으면 1, 삭제되지 않았으면 0을 리턴한다.
③ 글 수정 update
public int update(GuestbookVO vo)
모든 데이터를 가지고 있는 (GuestbookVO vo)달라고 한다.
④ 글 조회 retrieve
public GuestbookVO retrieve(int guestbookNo)
외부에서 내가 쓴 글을 조회할 경우, public int retrieve(int guestbookNo); 글 번호를 받아서 예를 들어 글 번호가 72번이면 화면에 나타내야할 것이 제목, 내용, 날짜, 글쓴이이다. 그렇기 때문에 리턴되는 것은 정수 int가 아닌 GuestbookVO인 것이다.
int guestbookNo 이 글 번호가 조회가 되어 리턴되는 것은 그 전체 GuestbookVO라는 것이다.
GuestbookServiceImpl이 클래스를 구현할 것이고 구현할 인터페이스가 있으면 추가할 수 있고, 1개 이상 추가 가능하다.
public class GuestbookServiceImpl implements GuestbookService
GuestbookServiceImpl 하위 GuestbookService 상위
보통 설계할 때 동사적 타입, 명사적 타입을 분리해 만드는 것이 일반적이다.
API Array List는 동사와 명사적 타입이 섞여있다.
위의 파일은 구현하지는 않고 설계까지만, 구현은 이후에 진도 더 나가서 할 예정이다.
interface끼리 상속 가능
자바에서 다중 상속이 안되는 이유 : 구현된 메소드때문에
인터페이스끼리는 구현이 되지 않은 상태이기 때문에 interface I2 extends I1 가능
[Abstract class] : 상속 전용 클래스
Abstract :
- 객체를 생성할 수 없다.
- 선언할 때는 class이지만 객체를 생성할 수 없다는 것이 interface의 특징과 class의 특징을 전부 다 가지고 있다.
- 클래스와 인터페이스의 중간에 있는 것이다.
- 어떤 때 사용하느냐 하면, 멤버는 가지고 싶은데 객체 생성은 할 필요없을 때 사용한다.
- 멤버변수가 있기 때문에 생성자도 가지고 있지만 구현되지 않은 메소드가 포함되어 있다.
- 잘 사용하지 않기 때문에 이런게 있다 정도로만 알고 넘어갈 것!
abstract이랑 final을 동시에 체크하는 것은 좋지않다.
추상클래스는 객체생성할 수 없지만 왜 하려고 하냐면, abstract class MyAbstract{} 이 안에 멤버와 생성자를 가질 수 있어 상속을 하기위해서이다. 추상클래스 MyAbstract{} 안에 생성자가 있는 이유는 자식클래스를 위해서이다.
interface에는 그들은 이미 abstract이기 때문에 abstract를 붙이지 않지만, abstract class에서 구현되지 않는 메소드에는 반드시 abstract를 붙여줘야 한다. abstract를 안쓰면 반드시 오류가 발생한다.
추상클래스 MyAbstract{}를 상속받고 싶을 경우에는 extends로 상속! class Concreate를 생성하면 에러가 발생하는데 구현되지 않았기 때문이므로 위의 사진처럼 Add unimplemented methods를 눌러준다. 그럼 아래와 같이 오버라이딩된다.(강제 오버라이딩) 여기서 생성자는 상속되지 않는다.
추상클래스 MyAbstract는 ConcreateClass의 아버지 역할, 상위 클래스가 된다.
뒤섞인 코드를 만들어보겠다.
interface MyInterface를 생성하고 몸통이 없는 메소드를 1개 가지고 있다.
class ConcreateClass가 MyInterface를 상속받고, MyInterface는 구현해야 한다.
class ConcreateClass에 implements MyInterface를 입력해주면 또 에러가 발생한다. MyInterface에 있는 몸통업는 메소드를 구현하라는 것임!
오류난 부분에 마우스를 가져가 Add umimplement method를 눌러주면 오류 해결
자동으로 public void method3()가 오버라이드된다.
그러면 class ConcreateClass에는 상위클래스가 2개인셈이다. MyInterface도 상위이고, MyAbstract도 상위이다.
class ConcreateClass는 2개의 메소드를 상속받았고 구현을 했기 때문에 객체생성을 할 수 있다.
MyAbstract은 추상클래스이기 때문에 객체를 생성할 수 없지만 자식클래스인 ConcreateClass을 통해 아버지가 자식클래스를 가리킴을 통해(다형성) 객체를 생성할 수 있다. 상위가 하위를 가리키는 것이 가능하다. 상위가 무조건 아래쪽 클래스를 가리키게 하는 것! 가능하다.
MyAbstract tmp = new ConcreateClass();는 MyAbstract에 만들어 놓은 클래스를 중점적으로 사용하겠다 할 때,
MyInterface tin = new ConcreateClass();는 MyInterface에 만들어 놓은 클래스를 중점적으로 사용할 때!
MyAbstract or MyInterface를 상위로 사용하면 된다.
위의 그림을 보고 전체 관계를 파악할 수 있어야 한다.
실선은 상위클래스를 의미, 점선은 상위인터페이스를 의미한다.
항상 그러한 것은 아닌데 인터페이스가 여러 개 있을 경우, (인터페이스들끼리도 상속할 수 있다!)
같은 타입일 경우, 상속할 때 interface I2 extends I1
클래스, 인터페이스를 상속할 때는 extends!
자바에서는 다중 상속이 안된다고 했는데 구현된 메소드때문이다.
하지만 인터페이스는 구현이 되지 않은 메소드이기때문에 다중 상속 가능하다.
위의 내용 다 암기할 것들 ^^
[예제]
추상클래스를 만들 때는 멤버가 필요한 경우, 인터페이스를 만드는 경우는 멤버는 필요없고 동사만 필요한 경우이다.
추상메소드는 구현도 가능하다.
<에러나는 이유 메소드를 통해 접근하지 않았기 떄문 >
MyPoint는 현재 자식이 둘, MyCircle, MyTriangle
MyPoint는 자식인 MyCircle, MyTriangle를 가리킬 수 있는 상황이다.
MyPoint[] list = new MyPoint[10];
메소드는 접근 가능하지만, 변수에는 접근불가
list는 MyPoint();를 가리키기 때문에 MyPoint()의 변수 x, y 모두 사용할 수 있고 list[0]이 new Circle을 가리키기 때문에 변수 radius도 사용할 수 있다.
list[0].radius = 5;에서 에러가 나는 이유는?
list는 MyPoint() 타입, list[0].radius는 포인트 타입에 없음!
list는 MyPoint타입이라서, list[0]의 실제 객체는 MyCircle이지만 만들 때는 MyPoint()라서 그렇다.
객체배열 list의 아버지가 MyPoint 타입이기 때문에 접근할 수 있는 멤버는 MyPoint 타입에만 해당한다.
list[0] = MyCircle();이라서 MyCircle()의 메소드에는 접근할 수 있지만 멤버에는 접근할 수 없다.
왜? 메소드는 오픈되어 있지만, 변수는 감춰지는 것이 일반적이기 때문에 setter가 필요하다. 혹은 하향캐스
변수 x, y를 다룰 때는 x, y를 멤버로 가지고 있는 부모클래스를 호출하고 난 다음 자신들의 클래스에 가서 호출한다.
부모클래스의 타입을 강제로 자식클래스로 변환 (하향캐스팅)
list[0].radius = 5; => ((MyCircle)list[0]).setRadius(5);
3시간 수업인데 이해하기는 12시간 넘게 걸리네..
그렇다고 이해가 된 것도 아니야.. 속상하다.