티스토리 뷰

PL/JAVA

[JAVA] 자바 - 상속 2

poopooreum 2024. 5. 26. 21:52
반응형

✏️ 업캐스팅

자바에서 서브 클래스는 슈퍼 클래스의 속성을 상속받기 때문에, 서브 클래스의 객체는 슈퍼 클래스의 멤버를 모두 가진다. 그러므로 서브 클래스의 객체를 슈퍼 클래스의 객체로 취급할 수 있다. 서브 클래스의 객체에 대한 레퍼런스를 슈퍼 클래스 타입으로 변환하는 것을 업캐스팅(upcasting)이라고 하며, 업캐스팅은 슈퍼 클래스의 래퍼런스로 서브 클래스의 객체를 가리키게 한다. 예시를 통해서 알아보자.
Student 클래스 Person 클래스 상속받았고 p=s를 통해 업캐스팅을 하였다. 이렇게 되면 Person 타입의 객체 p는 Student 객체를 가리키게 된다. 그러나 레퍼런스 p로는 Person 클래스의 멤버만 접근할 수 없다. p는 Person타입이기 때문이고 gradedepartStudent의 멤버이므로 아래에서 주석처리를 지우면 컴파일 오류가 발생한다.

class Person{
    String name;
    String id;
    public Person(String name){
        this.name=name;
    }
}

class Student extends Person{
    String grade;
    String depart;
    public Student(String name){
        super(name);
    }
}
public class UpcastingEx {
    public static void main(String[] args) {
        Person p;
        Student s = new Student("홍길동");
        p=s; // 업캐스팅

        System.out.println(p.name);
        // p.grade = "a"; 컴파일 오류
        // p.depart = "com"; 컴파일 오류
    }
}

 

 

✏️ 다운캐스팅

업캐스팅과 반대로 캐스팅하는 것을 다운캐스팅(downcasting)이라고 한다. 이것도 예시를 통해서 알아보자
먼저 Student 객체가 Person타입으로 업캐스팅이 되었고 s=(Student)p;를 통해 다운캐스팅을 하여서 s는 Student 객체 전체를 접근할 수 있게 된다. 그리고 다운캐스팅은 명시적으로 타입 변환을 지정해주어야 한다.

class Person{
    String name;
    String id;
    public Person(String name){
        this.name=name;
    }
}

class Student extends Person{
    String grade;
    String depart;
    public Student(String name){
        super(name);
    }
}

public class DowncastingEx {
    public static void main(String[] args) {
        Person p = new Student("홍길동"); // 업캐스팅
        Student s;
        s = (Student)p; // 다운캐스팅
        System.out.println(s.name);
        s.grade = "A";
     
    }
}

 

 

✏️ instance of 연산자

업캐스팅을 한 경우에는 레퍼런스가 가리키는 객체의 진짜 클래스 타입을 구분하기 어려운데, 이때 instance of 연산자를 이용할 수 있다. 사용방법은 "레퍼런스 instance of 클래스명"이다. 주의할 점으로는 instance of 연산자는 클래스에만 적용되므로 "3 instance of int"와 같은 문장은 오류로 인식된다. 단 문자열은 String 객체이므로 예외이다.

class Person{...}
class Student extends Person{...}
class Researcher extends Person{...}
class Professor extends Researcher{...}

Person p = new Person();
Person p = new Student(); // 업캐스팅
Person p = new Researcher(); // 업캐스팅
Person p = new Professor(); // 업캐스팅

print(p) // 만약 이 문장을 실행한다면
void print(Person person){
	이 메소드는 person이 가리키는 객체를 알기 어렵고 오직
    Person을 상속받은 객체가 업캐스팅되어 넘어왔다는 사실만 알고 있다.
}
// 그래서 instacne of 연산자를 이용하게 된다
Person jee = new Student();
Person kim = new Professor();
Person lee = new Researcher();
if(jee instance of Person) // jee는 Person을 상속받았으므로 true
if(jee instance of Student) // jee는 Student 타입이므로 true
if(kim instance of Student) // kim은 Student 타입이 아니므로 false
if(kim instance of Professor) // kim은 Professor 타입이므로 true
if(kim instance of Researcher) // kim은 Researcher을 상속받았으므로 true
if(lee instance of Professor) // lee는 Professor 타입이 아니므로 false

 

 

✏️ 메소드 오버라이딩

메소드 오버라이딩은 슈퍼 클래스에 선언된 메소드를 서브 클래스에서 재작성하는 것이다. 이때 주의할 점으로는 슈퍼 클래스에서 선언된 메소드와 같은 이름, 같은 리턴 타입, 같은 매개 변수 리스트를 가져야 한다. 슈퍼 클래스에 있는 메소드로 목적을 이룰 수 없을 때 서브 클래스에서 메소드 오버라이딩을 통해 그 목적을 실현할 수 있다. 메소드 오버라이딩은 슈퍼 클래스의 메소드를 무시하고 서브 클래스에서 오버라이딩된 메소드가 무조건 실행되도록 하는데, 이런 처리를 동적 바인딩이라고 부른다. 메소드 오버라이딩의 제약사항은 아래와 같다.

  • 슈퍼 클래스의 메소드와 동일한 원형(이름, 매개변수, 리턴 타입)으로 작성한다.
  • 슈퍼 클래스 메소드의 접근 지정자보다 접근의 범위를 좁혀 오버라이딩 할 수 없다.
  • static이나 private 또는 final로 선언된 메소드는 서브 클래스에서 오버라이딩 할 수 없다.

super 키워드를 통해 오버라이딩을 하였어도 슈퍼 클래스의 메소드를 호출할 수 있다. 아래 경우에는 super키워드를 사용하여서 슈퍼 클래스의 draw() 메소드를 호출하였고 출력 결과는 Shape/ Circle이다.

class Shape{
    protected String name;
    public void paint(){
        draw();
    }
    public void draw(){
        System.out.println(name);
    }
}
public class Circle extends Shape {
    protected String name;
    @Override
    public void draw(){
        name = "Circle";
        super.name="Shape";
        super.draw();
        System.out.println(name);
    }
    public static void main(String[] args) {
        Shape b = new Circle();
        b.paint();
    }
}

 

 

✏️ 동적 바인딩

위에서 동적 바인딩에 대해서 간단히 설명하였으니 코드를 통해 접근하고자 한다.
먼저, line.draw()의 출력결과는 "Line"이다. 그리고 shape.draw()의 출력결과는 shape가 Shape 타입이므로 "Shape"라고 생각할 수 있지만 결과는 "Line"이다. shape.draw()를 컴파일할 대, 컴파일러는 Shape 클래스에 draw()멤버가 있는지 확인하고 Shape의 draw()를 호출하도록 컴파일한다. 하지만, shape.draw()실행 시 shape이 가리키는 객체에는 Shape의 draw()를 오버라이딩한 Line의 draw()가 존재하기 때문에 Line의 draw()가 실행되고 이 과정을 동적 바인딩이라고 한다.

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");
    }
}

Line line = new Line();
line.draw();
Shape shape = new Line(); // 업캐스팅
shpae.draw();

 

 

✏️ 메소드 오버로딩 VS 메소드 오버라이딩

비교 요소 메소드 오버로딩 메소드 오버라이딩
선언 같은 클래스나 상속 관계에서
동일한 이름의 메소드 중복 작성
서브 클래스에서 슈퍼 클래스에 있는 메소드와
동일한 이름으로 메소드 재작성
관계 동일한 클래스 내 혹은 상속 관계 상속 관계
목적 이름이 같은 여러 개의 메소드를
중복 작성하여 사용의 편리성 향상
, 다형성 실현
슈퍼 클래스에 구현된 메소드를 무시하고 서브 클래스에서
새로운 기능의 메소드를 재정의하고자 함
, 다형성 실현
조건 메소드 이름은 반드시 동일하고,
매개변수 타입이나 개수가 달라야 함
메소드 이름, 매개변수 타입과 개수,
리턴 타입이 모두 동일하여야 함
바인딩 정적 바인딩, 호출될 메소드는 컴파일 시에 결정 동적바인딩, 실행 시간에 오버라이딩 된 메소드를 찾아 호출

 

 

✏️ 추상 메소드

추상 메소드(abstract method)는 선언은 되어 있으나 코드가 구현되어 있지 않은, 껍데기만 있는 메소드이다. 추상 메소드를 작성하기 위해서는 abstract 키워드로 원형만 선언하고 코드는 작성하지 않는다.

public abstract String getName(); => 컴파일 오류 나지 않음
public abstract String fall(){ return "Hello"} => 컴파일 오류

 

 

✏️ 추상 클래스

추상 클래스(abstract class)가 되는 경우는 총 2가지이며, 모두 abstract 키워드로 선언해야 한다.

  • 추상 메소드를 포함하는 클래스
  • 추상 메소드가 없지만 abstract로 선언한 클래스
abstract class Shape{ // 추상 메소드를 포함하는 클래스
    public Shape();
    public void paint(){ draw();}
    abstract public void draw();
}
abstract class Mycom{ // 추상 메소드는 없고 abstract로 선언한 클래스
    String name;
    public void load(String name){
    	this.name=name;
    }
}

 

 

주의점

class Fault{
    abstract public void f(); 
    // 추상 클래스로 선언하지 않고 추상 메소드를 사용할 수 없다.
}
public class AbstractError{ // Shape은 추상 클래스이다
	public static void main(String [] args){
    	Shape shape; // 단, 추상 클래스의 레퍼런스 변수를 선언하는 자체는 문제가 없음
        shape = new Shape(); // 추상 클래스의 객체를 생성할 수 없음
    }
}

 

 

✏️ 추상 클래스의 상속

추상 클래스를 단순히 상속받는 서브 클래스는 추상 클래스가 된다. 슈퍼 클래스가 추상 클래스이므로 당연히 서브 클래스도 추상 클래스가 되므로 서브 클래스도 abstract 키워드를 사용해야 한다. 그러나 서브 클래스를 추상 클래스로 선언하지 않고도 작성할 수 있는 방법이 있다. 바로 슈퍼 클래스의 추상 메소드를 서브 클래스에서 모두 오버라이딩하는 것이다. 즉, 서브 클래스에서 슈퍼 클래스의 추상 메소드를 모두 오버라이딩하면 서브 클래스는 추상 클래스를 상속받을 필요가 없다.

abstract class Shape{
    public abstract void draw();
}
// 추상 클래스로 상속받지 않는 방법
class Line extends Shape{
    @Override
    public void draw(){ // 오버라이딩
    	System.out.println("Line");
    }
}

class Rect extends Shape{
    @Override
    public void draw(){ // 오버라이딩
    	System.out.println("Rect");
    }
}

class Circle extends Shape{
    @Override
    public void draw(){ // 오버라이딩
    	System.out.println("Circle");
    }
}

 

 

✏️ 인터페이스

인터페이스는 interface 키워드를 사용하며 5종류의 멤버로 구성되며, 필드는 만들 수 없다.

  1. 상수와 추상 메소드
  2. default 메소드
  3. private 메소드
  4. static 메소드

추상 메소드는 public abstract로 정해져 있으며, 생략될 수 있고, 다른 접근으로 지정될 수 없다. default, private, static 메소드들은 모두 인터페이스 내에 코드가 작성되어 있어야 한다. default 메소드의 접근 지정은 public으로 고정되어 있고 private 메소드는 인터페이스 내에서만 호출 가능하다. static 메소드의 경우 접근 지정이 생략되면 public이며, private로 지정 불가능하다. 또한 인터페이스도 객체를 생성할 수는 없지만 인터페이스 타입의 레퍼런스 변수는 선언이 가능하다. 그리고 인터페이스끼리는 상속이 가능하며 인터페이스를 상속받아 클래스를 작성하면 인터페이스의 모든 추상 메소드를 구현해야 한다.

 

 

✏️ 인터페이스 구현

인터페이스 구현은 implements 키워드를 사용하여 인터페이스의 모든 추상 메소드를 구현한 클래스를 작성하는 것을 말한다. 아래 코드를 통해서 알아보자 두 개의 추상 메소드를 가진 PhoneInterface라는 인터페이스를 만들었고 InterfaceEx 클래스에서 PhoneInterface의 모든 추상 메소드를 구현하였다. 그리고 원래 가지고 있던 default 메소드는 그대로 물려받는다.

interface PhoneInterface{
    final int TIMEOUT = 10000; // 상수
    void sendCall(); // 추상 메소드
    void receiveCall(); // 추상 메소드
    default void printLogo(){ // default 메소드
        System.out.println("**phone**");
    }
}

class InterfaceEx implements PhoneInterface{
    // PhoneInterface의 모든 추상 메소드를 구현하기
    @Override
    public void sendCall(){
        System.out.println("띠리리링");
    }
    @Override
    public void receiveCall(){
        System.out.println("전화가 왔습니당");
    }
    public void flash(){
        System.out.println("전화기에 불이 켜졌습니당");
    }
}

public class SamsungPhone {
    public static void main(String[] args) {
        InterfaceEx phone = new InterfaceEx();
        phone.printLogo();
        phone.sendCall();
        phone.receiveCall();
        phone.flash();
    }
}

 

 

✏️ 인터페이스 상속

클래스는 인터페이스를 상속받을 수 없고, 인터페이스끼리만 상속이 가능하다. 상속을 통해 기존 인터페이스에 새로운 규격을 추가한 새로운 인터페이스를 만들 수 있으며 extends 키워드를 사용하여 인터페이스끼리 상속을 한다.

interface MobilePhone extends PhoneInterface{
    //PhoneInterface는 인터페이스이다
    void sendSMS(); // 추상 메소드
    void receiveSMS(); // 추상 메소드
    // 이 결과, MobilePhone 인터페이스는 Timeout, sendCall(), receiveCall()
    // printLogo(), sendSMS(), receiveSMS() 6개의 멤버를 가진다
}

 

또한 인터페이스는 다중 상속이 가능하다.

interface MP3Interface{
    void play();
    void stop();
}

interface MusicPhone extends MobilPhone, MP3Interface{
    void playMP3(); // 추상 메소드
    // 이 결과, MusicPhone 인터페이스는 9개의 멤버를 가진다.
}

클래스는 하나 이상의 인터페이스를 구현할 수 있는데, 이 경우에는 각 인터페이스에 선언된 모든 추상 메소드를 구현하여야 한다. 그리고 클래스를 상속 받으면서 동시에 인터페이스를 구현할 수 있다.

interface PhoneInterface{
    final int TIMEOUT = 10000;
    void sendCall();
    void receiveCall();
    default void printLogo(){
        System.out.println("**phone**");
    }
}
interface MobilPhoneInterface 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;
    }
}

class SmartPhone extends PDA implements MobilPhoneInterface, MP3Interface{
    // 클래스 PDA를 상속받고 MobileInterface, MP3Interface 인터페이스에 선언된 모든 추상메소드를 구현

    // MobilePhone 인터페이스의 추상 메소드 구현
    @Override
    public void sendCall(){
        System.out.println("따르르를");
    }
    @Override
    public void receiveCall(){
        System.out.println("전화 왔어요");
    }
    @Override
    public void sendSMS(){
        System.out.println("문자가는중");
    }
    @Override
    public void receiveSMS(){
        System.out.println("문자받는중");
    }

    // MP3 인터페이스의 추상 메소드 구현
    @Override
    public void play(){
        System.out.println("음악 트는 중");
    }
    @Override
    public void stop(){
        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();
        phone.play();
        System.out.println(phone.calculate(3,5));
        phone.schedule();
    }
}

 

 

✏️ 인터페이스 VS 추상 클래스

비교 목적 구성
추상 클래스 추상 클래스는 서브 클래스에서 필요로 하는 대부분의 기능을 구현하여 두고 서브 클래스가 상속받아 활용할 수 있도록 한다. 또한, 서브 클래스에서
구현할 수밖에 없는 기능만을 추상 메소드로 선언하여 서브 클래스에서
구현하도록 한다
(다형성)
추상 메소드와 일반 메소드 모두 포함
상수, 변수 필드 모두 포함
인터
페이스
인터페이스는 객체의 기능을 모두 공개한 표준화 문서와
같은 것으로
, 개발자에게 인터페이스를 상속받는 클래스의
목적에 따라 인터페이스의 모든 추상 메소드를
만들도록 하는 목적
(다형성)
변수 필드는 포함하지 않는다.
상수, 추상 메소드, 일반 메소드, default 메소드, static 메소드 모두 포함
protected 접근 지정 선언 불가
다중 상속 지원
반응형

'PL > JAVA' 카테고리의 다른 글

[JAVA] 자바 - 모듈과 패키지 2  (2) 2024.05.28
[JAVA] 자바 - 모듈과 패키지  (0) 2024.05.27
[JAVA] 자바 - 상속  (0) 2024.05.25
[JAVA] 자바 - 클래스와 객체  (0) 2024.05.23
[JAVA] 자바 - 배열와 예외 처리  (0) 2024.05.22
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함