콘텐츠로 이동

상속 (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
Person p = new Person();
Person p = new Student(); // 업캐스팅 Person 
p = new Professor();      // 업캐스팅

p가 가리키는 객체가 Person 객체인지, Student 객체인지, Professor 객체인지 구분하기 어려움

instanceof 연산자

  • 레퍼런스가 가리키는 객체의 타입 식별
  • 연산의 결과 : true/false의 불린 값
객체레퍼런스 instanceof 클래스타입
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.슈퍼클래스의 멤버
    
  • 서브 클래스에서만 사용
  • 슈퍼 클래스의 필드 접근
  • 슈퍼 클래스의 메소드 호출 시
  • 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 **");
  };
}

자바 인터페이스의 특징

인터페이스의 객체 생성 불가

new PhoneInterface(); // 오류. 인터페이스의 객체를 생성할 수 없다.

인터페이스 타입의 레퍼런스 변수 선언 가능

PhoneInterface galaxy; // galaxy는 인터페이스에 대한 레퍼런스 변수

인터페이스 상속

인터페이스를 상속하여 확장된 인터페이스 작성 가능

  • extends 키워드로 상속 선언
    interface MobilePhoneInterface extends PhoneInterface { 
      void sendSMS(); // 새로운 추상 메소드 추가 
      void receiveSMS(); // 새로운 추상 메소드 추가
    }
    
  • 인터페이스 다중 상속 허용
    interface MusicPhoneInterface extends PhoneInterface, MP3Interface { 
      ......
    }
    

인터페이스 구현

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
일정 관리합니다