상속 (2)
1. 업캐스팅과 다운캐스팅
업캐스팅(upcasting)
- 서브 클래스의 레퍼런스를 슈퍼 클래스 레퍼런스에 대입
- 슈퍼 클래스 레퍼런스로 서브 클래스 객체를 가리키게 되는 현상
class Person { }
class Student extends Person { }
Person p;
Student s = new Student("Joe");
p = s; // 업캐스팅
class Person {
String name;
String id;
public Person(String name) {
this.name = name;
}
}
class Student extends Person {
String grade;
String department;
public Student(String name) {
super(name);
}
}
public class UpcastingEx {
public static void main(String[] args) {
Person p;
Student s = new Student("Joe"); p = s; // 업캐스팅 발생
System.out.println(p.name); // 오류 없음
p.grade = "A"; // 컴파일 오류
p.department = "Com"; // 컴파일 오류
}
}
다운캐스팅(downcasting)
- 슈퍼 클래스 레퍼런스를 서브 클래스 레퍼런스에 대입
- 업캐스팅된 것을 다시 원래대로 되돌리는 것
- 반드시 명시적 타입 변환 지정
class Person { }
class Student extends Person { }
Person p = new Student("Joe"); // 업캐스팅 Student s = (Student)p; // 다운캐스팅, 강제타입변환
class Person {
String name;
String id;
public Person(String name) {
this.name = name;
}
}
class Student extends Person {
String grade;
String department;
public Student(String name) {
super(name);
}
}
public class DowncastingEx {
public static void main(String[] args) {
Person p = new Student("Joe"); // 업캐스팅 Student s;
s = (Student)p; // 다운캐스팅
System.out.println(s.name); // 오류 없음
s.grade = "A"; // 오류 없음
}
}
result
Joe
업캐스팅 레퍼런스로 객체 구별의 어려움
슈퍼 클래스는 여러 서브 클래스에 상속되기 때문
graph BT
A[class Professor extends Researcher] --> B[class Researcher extends Person]
C[class Student extends Person] --> D[class Person]
B --> D
p가 가리키는 객체가 Person 객체인지, Student 객체인지, Professor 객체인지 구분하기 어려움
instanceof 연산자
- 레퍼런스가 가리키는 객체의 타입 식별
- 연산의 결과 : true/false의 불린 값
Person p = new Profsessor();
if(p instanceof Person) // true
if(p instanceof Student) // false. Student를 상속받지 않기 때문
if(p instanceof Researcher) // true
if(p instanceof Professor) // true
if("java" instanceof String) // true
if(3 instanceof int) // 문법 오류. instanceof는 객체에 대한 레퍼런스에만 사용
instanceof 연산자 활용 [예제 5-3]
class Person { }
class Student extends Person { }
class Researcher extends Person { }
class Professor extends Researcher { }
public class InstanceOfEx {
static void print(Person p) {
if(p instanceof Person) System.out.print("Person ");
if(p instanceof Student) System.out.print("Student ");
if(p instanceof Researcher) System.out.print("Researcher ");
if(p instanceof Professor) System.out.print("Professor ");
System.out.println();
}
public static void main(String[] args) {
System.out.print("new Student() -> "); print(new Student());
System.out.print("new Researcher() -> "); print(new Researcher());
System.out.print("new Professor() -> "); print(new Professor());
}
}
new Student() -> Person Student
new Researcher() -> Person Researcher
new Professor() -> Person Researcher Professor
이론문제 (8번) p.232
// instanceof에 관한 문제이다. 다음 클래스가 있을 때 물음에 답하라
class A { int i; }
class B extends A { int j; }
class C extends B { int k; }
class D extends B { int k; }
// 1) 다음 코드를 실행한 결과는?
A c = new C();
System.out.println(c instanceof D); // false
System.out.println(c instanceof A); // true
// 2) 다음 코드를 실행한 결과는?
System.out.println(new D() instanceof C); //에러
System.out.println(new D() instanceof A); // true
2. 메소드 오버라이딩
메소드 오버라이딩(Method Overriding)
- 서브 클래스에서 슈퍼 클래스의 메소드 중복 작성
- 슈퍼 클래스의 메소드 무력화, 항상 서브 클래스에 오버라이딩된 메소드가 실행되도록 보장됨
- 오버라이딩 조건
- 슈퍼 클래스 메소드의 원형(메소드 이름, 인자 타입 및 개수, 리턴 타입) 동일하게 작성
오버라이딩된 메소드, B의 f() 직접 호출
class A {
void f() {
System.out.println("A의 f() 호출");
}
}
class B extends A {
void f() { // 클래스 A의 f()를 오버라이딩
System.out.println("B의 f() 호출");
}
}
오버라이딩으로 다형성 구현
- 하나의 인터페이스(같은 이름)에 서로 다른 구현
- 슈퍼 클래스의 메소드를 서브 클래스에서 각각 목적에 맞게 다르게 구현
class Shape {
public void draw() {
system.out.println("Shape"); // 메소드 오버라이딩
}
}
class Line extends Shape {
public void draw() {
system.out.println("Line"); // 메소드 오버라이딩
}
}
class Rect extends Shape {
public void draw() {
system.out.println("Rect"); // 메소드 오버라이딩
}
}
class Circle extends Shape {
public void draw() {
system.out.println("Circle"); // 메소드 오버라이딩
}
}
public class MethodOverridingEx {
static void paint(Shape p) { // Shape을 상속받은 객체들이 매개 변수로 넘어올 수 있음
p.draw(); // p가 가리키는 객체에 오버라이딩된 draw() 호출. 동적바인딩
}
public static void main(String[] args) {
Line line = new Line();
paint(line); // Line의 draw() 실행. "Line" 출력
paint(new Shape()); // Shape의 draw() 실행. "Shape" 출력
paint(new Line()); // 오버라이딩된 메소드 Line의 draw() 실행
paint(new Rect()); // 오버라이딩된 메소드 Rect의 draw() 실행
paint(new Circle()); // 오버라이딩된 메소드 Circle의 draw() 실행
}
}
output
Line
Shape
Line
Rect
Circle
동적 바인딩
오버라이딩 메소드가 항상 호출된다.
public class SuperObject {
protected String name;
public void paint() { draw(); }
public void draw() {
System.out.println("Super Object");
}
public static void main(String [] args) {
SuperObject a = new SuperObject();
a.paint();
}
}
output
Super Object
class SuperObject {
protected String name;
public void paint() { draw(); }
public void draw() { System.out.println("Super Object"); }
}
public class SubObject extends SuperObject {
public void draw() {
System.out.println("Sub Object");
}
public static void main(String [] args) {
SuperObject b = new SubObject();
b.paint();
}
}
output
Sub Object
정적 바인딩 : super 키워드
- 슈퍼 클래스의 멤버를 접근할 때 사용되는 레퍼런스
- 서브 클래스에서만 사용
- 슈퍼 클래스의 필드 접근
- 슈퍼 클래스의 메소드 호출 시
- super로 이루어지는 메소드 호출 : 정적 바인딩
class SuperObject {
protected String name;
public void paint() {
draw();
}
public void draw() {
System.out.println(name);
}
}
public class SubObject extends SuperObject {
protected String name;
public void draw() {
name = "Sub";
super.name = "Super";
super.draw();
System.out.println(name);
}
public static void main(String [] args) {
SuperObject b = new SubObject(); b.paint();
}
}
output
Super
Sub
오버로딩과 오버라이딩 비교
비교요소 | 메소드 오버로딩 | 메소드 오버라이딩 |
---|---|---|
선언 | 같은 클래스나 상속 관계에서 동일한 이름의 메소등 중복작성 | 서브 클래스에서 슈퍼 클래스에 있는 메소드와 동일한 이름의 메소드 재작성 |
관계 | 동일한 클래스 내 혹은 상속 관계 | 상속관계 |
목적 | 이름이 같은 여러 개의 메소드를 중복 선언하여 사용의 편리성 향상 | 슈퍼 클래스이 구현된 메소드를 무시하고 서브 클래스에서 새로운 기능의 메소드를 재정의하고자 함 |
조건 | 메소드 이름은 반드시 동일함. 메소드 인자의 개수나 인자의 타입이 달라야 성림 | 메소드의 이름, 인자의 타입, 인자의 개수, 인자의 리턴 타입 등이 모두 동일하여야 성림 |
바인딩 | 정적 바인딩. 컴파일 시에 중복 메소드 중 호출되는 메소드 결정 | 동적 바인딘. 실행 시간에 오버라이딩된 메소드 찾아 호출 |
실습문제 (4번) p.237
// 2차원 상의 한 점을 표현하는 Point 클래스는 다음과 같다.
class Point {
private int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
public int getX() { return x; }
public int getY() { return y; }
protected void move(int x, int y) { this.x = x; this.y = y; }
}
// 다음 main()을 실행하여 결과가 그림과 같도록, Point를 상속받은 ColorPoint 클래스(main()포함)을 작성하라.
public class ColorPoint extends Point {
......
public static void main(String[] args) {
ColorPoint cp = new ColorPoint(5, 5,"YELLOW");
cp.setPoint(10, 20);
cp.setColor("GREEN");
cp.show();
}
}
결과
GREEN색으로(10,20)
풀이
class Point {
private int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
public int getX() { return x; }
public int getY() { return y; }
protected void move(int x, int y) { this.x = x; this.y = y; }
}
public class ColorPoint extends Point {
private String color;
ColorPoint(int x, int y, String color) {
super(x, y);
this.color = color;
}
void setPoint(int x, int y) { move(x, y); }
void setColor(String color) { this.color = color; }
void show() {
System.out.println(color + "색으로" + "(" + getX() + "," + getY() + ")");
}
public static void main(String[] args) {
ColorPoint cp = new ColorPoint(5, 5,"YELLOW");
cp.setPoint(10, 20);
cp.setColor("GREEN");
cp.show();
}
}
3. 추상 클래스와 인터페이스
추상 메소드
추상 메소드(abstract method)
- abstract로 선언된 메소드, 메소드의 코드는 없고 원형만 선언
추상 클래스
추상 클래스(abstract class)
- 추상 메소드를 가지며, abstract로 선언된 클래스
- 추상 메소드 없이, abstract로 선언한 클래스
- 추상 메소드를 가지고 있다면 반드시 추상 클래스로 선언되어야 한다.
// 추상 메소드를 가진 추상 클래스
abstract class Shape { Shape() { ... }
void edit() { ... }
// 추상 메소드
abstract public void draw();
}
// 추상 메소드 없는 추상 클래스
abstract class JComponent {
String name;
void load(String name ) {
this.name= name;
}
}
class fault { // 오류. 추상 메소드를 가지고 있으므로 abstract로 선언되어야 함
abstract void f(); // 추상 메소드
}
추상 클래스는 온전한 클래스가 아니기 때문에 인스턴스를 생성할 수 없음
JComponent p; // 오류 없음. 추상 클래스의 레퍼런스 선언
p = new JComponent(); // 컴파일 오류. 추상 클래스의 인스턴스 생성 불가
Shape obj = new Shape(); // 컴파일 오류. 추상 클래스의 인스턴스 생성 불가 (Unresolved compilation problem: Cannot instantiate the type Shape)
추상 클래스 구현
- 서브 클래스에서 슈퍼 클래스의 추상 메소드 구현(오버라이딩)
- 추상 클래스를 구현한 서브 클래스는 추상 클래스 아님
class C extends A{ // 추상클래스구현. C는정상클래스
int add(int x, int y) { return x+y; } // 추상 메소드 구현. 오버라이딩
void show() { System.out.println("C"); }
}
...
C c = new C(); // 정상
추상 클래스의 목적
- 상속을 위한 슈퍼 클래스로 활용하는 것
- 서브 클래스에서 추상 메소드 구현
- 다형성 실현
추상 클래스의 구현 [예제 5-5]
// 추상 클래스 Calculator를 상속받는 GoodCalc 클래스를 구현하라.
abstract class Calculator {
public abstract int add(int a, int b);
public abstract int subtract(int a, int b);
public abstract double average(int[] a);
}
public class GoodCalc extends Calculator {
public int add(int a, int b) { // 추상 메소드 구현
return a + b; }
public int subtract(int a, int b) { return a - b;}
public double average(int[] a) {
// 추상 메소드 구현 // 추상 메소드 구현
double sum = 0;
for (int i = 0; i < a.length; i++)
sum += a[i];
return sum/a.length;
}
public static void main(String [] args) {
GoodCalc c = new GoodCalc();
System.out.println(c.add(2, 3));
System.out.println(c.subtract(2, 3));
System.out.println(c.average(new int [] { 2, 3, 4 }));
}
}
output
5
-1
3.0
인터페이스
- 정해진 규격(인터페이스)에 맞기만 하면 연결 가능. 각 회사마다 구현 방법은 다름
- 정해진 규격(인터페이스)에 맞지 않으면 연결 불가 (예시 220v제품 코드를 110v 플러그에 꼽지 못함)
- 클래스가 구현해야 할 메소드들이 선언되는 추상형
- interface 키워드로 선언
- 인터페이스 구성 요소 : 필드(멤버 변수) 선언 불가
- 상수 필드(Java 7), 추상 메소드(Java7)
- 디폴트 메소드(Java 8)
- private 메소드(Java 9), static 메소드 (Java 9)
interface PhoneInterface { // 인터페이스 선언
public static final int TIMEOUT = 10000; // 상수 필드. public static final 생략 가능
public abstract void sendCall(); // 추상메소드. public abstract 생략가능
public abstract void receiveCall(); // 추상메소드. public abstract 생략가능
public abstract void printLogo( { // 디폴트 메소드는 public 생략가능
System.out.println("** Phone **");
};
}
자바 인터페이스의 특징
인터페이스의 객체 생성 불가
인터페이스 타입의 레퍼런스 변수 선언 가능
인터페이스 상속
인터페이스를 상속하여 확장된 인터페이스 작성 가능
- extends 키워드로 상속 선언
- 인터페이스 다중 상속 허용
인터페이스 구현
interface PhoneInterface {
int BUTTONS = 20;
void sendCall();
void receiveCall();
}
interface MobilePhoneInterface extends PhoneInterface {
void sendSMS();
void receiveSMS();
}
interface MP3Interface {
public void play();
public void stop();
}
class PDA {
public int calculate(int x, int y) {
return x + y;
}
}
// SmartPhone 클래스는 PDA를 상속받고,
// MobilePhoneInterface와 MP3Interface 인터페이스에 선언된
// 메소드를 모두 구현
class SmartPhone extends PDA implements MobilePhoneInterface, MP3Interface {
public void sendCall() { System.out.println("전화 걸기"); }
public void receiveCall() { System.out.println("전화 받기"); }
public void sendSMS() { System.out.println("SMS 보내기"); }
public void receiveSMS() { System.out.println("SMS 받기"); }
public void play() { System.out.println("음악 재생"); }
public void stop() { System.out.println("재생 중지"); }
public void schedule() { System.out.println("일정 관리"); }
}
public class InterfaceEx {
public static void main(String [] args) {
SmartPhone p = new SmartPhone();
p.sendCall();
p.play();
System.out.println(p.calculate(3, 5));
p.schedule();
}
}
output
전화 걸기
음악 재생
8
일정 관리
인터페이스 구현과 동시에 슈퍼 클래스 상속 [예제 5-7]
interface PhoneInterface {
// 인터페이스 선언
final int TIMEOUT = 10000; // 상수 필드
void sendCall(); // 추상 메소드
void receiveCall(); // 추상 메소드
default void printLogo() { // default 메소드
System.out.println("** Phone **");
}
}
class Calc { // 클래스 작성
public int calculate(int x, int y) {
return x + y;
}
}
// SmartPhone 클래스는 Calc를 상속받고,
// PhoneInterface 인터페이스의 추상 메소드 모두 구현
class SmartPhone extends Calc implements PhoneInterface {
// PhoneInterface의 추상 메소드 구현
@Override
public void sendCall(){
System.out.println("따르릉따르릉~~");
}
@Override
public void receiveCall() {
System.out.println("전화 왔어요.");
}
// 추가로 작성한 메소드
public void schedule(){
System.out.println("일정 관리합니다.");
}
}
public class InterfaceEx {
public static void main(String[] args) {
SmartPhone phone = new SmartPhone();
phone.printLogo();
phone.sendCall();
System.out.println("3과 5를 더하면 " + phone.calculate(3, 5));
phone.schedule();
}
}
output
** Phone **
따르릉따르릉~~
3과 5를 더하면 8
일정 관리합니다