Backend/Java

JAVA 상속

가은파파 2021. 2. 20. 02:06

학습목표

  • 자바 상속의 특징
  • super 키워드
  • 메소드 오버라이딩
  • 다이나믹 메소드 디스패치 (Dynamic Method Dispatch)
  • 추상 클래스
  • final 키워드
  • Object 클래스

 

# 자바 상속의 특징

객체에 메소드(기능)를 추가하는 것을 하고 싶다. 그러나 아래 2가지 때문에 어렵다.

 

1. 만약 객체에 추가하는 메소드를 자신이 만들지 않았다. 그렇다면 원 소스를 업데이트 하면 추가한 메소드가 사라져서 이런 문제가 일어나지 않기 위해서는 지속적으로 코드를 관리해야 한다.

2. 해당 객체가 다양한 곳에서 사용되면 다른 곳에서 불필요한 기능이 포함될 수 있다.

 

그럼 기존의 객체를 그대로 유지하면서 어떤 기능을 추가하는 방법이 없을까? 이런 맥락에서 등장하는 것이 상속이다. 즉 기존의 객체를 수정하지 않으면서 새로운 객체가 기존의 객체를 기반으로 만들어지게 되는것이다. 그러면서도 새로운 객체가 이미 존재하는 객체의 기능을 가질 수 있다면 좋지 않을까? 

 

Java 모든 클래스는 Object 클래스의 자손이다.

 

부모 객체(클래스) : 이때 기존의 객체는 기능을 물려준다는 의미에서 부모 객체가 된다.

자식 객체(클래스) : 기존 객체의 기능을 물려받는 다는 의미.

상위(super) 클래스, 하위(sub) 클래스 관계라고도 한다. 또한 기초 클래스(base class), 유도 클래스(derived class) 관계라고도 한다.

 

아래와 같은 방법으로 상속을 할 수 있다.(extend)

class SubstractionableCalculator extends Calculator {
    public void substract() {
        System.out.println(this.left - this.right);
    }
}
 
public class CalculatorDemo1 {
 
    public static void main(String[] args) {
 
        SubstractionableCalculator c1 = new SubstractionableCalculator();
        c1.setOprands(10, 20);
        c1.sum();
        c1.avg();
        c1.substract();
    }
 
}

* Sub class 작성 상속 관련 내용

 

private 멤버는 상속되지 않는다. 그러나 public, protected 멤버는 상속된다.

super class에 있는 instance 메소드를 새롭게 작성할 수 있다. > Override

super class에 있는 static 메소드를 새롭게 작성할 수 있다. > Override 

 

* Casting 상속

 

아래와 같이 MoutainBike가 Bicycle과 Object의 자손 클래스이다. 

1. Object obj는 Object, MountainBike 클래스 가능.

2. MoutainBike myBike는 컴파일 에러가 날 거 같지만, 컴파일러가 (MountainBike)obj로 Casting하여 처리한다.

 > 위 cast가 런타임 체크를 obj에 넣어서 컴파일러가 안전하게 obj가 MountainBike을 될 수 있게 한다. 인스턴스 타입체크로 확인 가능하다.

//MountainBike is descended from Bicycle and Object
public MountainBike myBike = new MountainBike();

Object obj = new MountainBike();
//compile error? Yes.
MountainBike myBike = obj;
//assign a MountainBike to obj by explicit casting:
if (obj instanceof MountainBike) {
    MountainBike myBike = (MountainBike)obj;
}

 

# super 키워드

메소드 : super 키워드를 활용해서 superclass의 오버라이드된 메소드를 불러올 수 있다.

public class Superclass {

    public void printMethod() {
        System.out.println("Printed in Superclass.");
    }
}
public class Subclass extends Superclass {

    // overrides printMethod in Superclass
    public void printMethod() {
        super.printMethod();
        System.out.println("Printed in Subclass");
    }
    public static void main(String[] args) {
        Subclass s = new Subclass();
        s.printMethod();    
    }
}

//결과
//Printed in Superclass.
//Printed in Subclass

생성자 : 아래 방식으로 오버라이드된 superclass의 생성자를 불러올 수 있다.

 

super();

super(parameter list);

더불어 subclass에서 아래와 같이 superclass에서 생성자를 불러와서 추가하는 방식의 생성자 정의도 가능하다.

public MountainBike(int startHeight, 
                    int startCadence,
                    int startSpeed,
                    int startGear) {
    super(startCadence, startSpeed, startGear);
    seatHeight = startHeight;
}   

# 메소드 오버라이딩

* subclass의 메소드 override 은 
1. superclass의 '가장 근접한' 행위의 메소드를 상속 받게 하고,
2. 행위를 필요에 따라 수정할 수 있게 한다.
똑같은 이름, 숫자, 파라미터 타입, 리턴 타입을 가진다.

 

* @Override를 써서 컴파일러에 superclass 메소드를 override할 수 지시한다. 해당 메소스가 없으면 컴파일 에러로 발견해주게 한다.

 

* Static 메소드와 Instance 메소드

- 호출되는 숨겨진 static 메서드의 버전은 수퍼 클래스에 있는 버전을 호출하고, 재정의 된 인스턴스 메서드의 버전은 하위 클래스에 있는 버전입니다.

public class Animal {
    public static void testClassMethod() {
        System.out.println("The static method in Animal");
    }
    public void testInstanceMethod() {
        System.out.println("The instance method in Animal");
    }
}

public class Cat extends Animal {
    public static void testClassMethod() {
        System.out.println("The static method in Cat");
    }
    public void testInstanceMethod() {
        System.out.println("The instance method in Cat");
    }

    public static void main(String[] args) {
        Cat myCat = new Cat();
        Animal myAnimal = myCat;
        Animal.testClassMethod();
        myAnimal.testInstanceMethod();
    }
}
//result
//The static method in Animal
//The instance method in Cat

아래는 인터페이스의 다중상속을 해결하는 방법이다.

 

public interface OperateCar {
    default public int startEngine(EncryptedKey key) {
        // Implementation
    }
}
public interface FlyCar {
    default public int startEngine(EncryptedKey key) {
        // Implementation
    }
}
 
public class FlyingCar implements OperateCar, FlyCar {
    public int startEngine(EncryptedKey key) {
        FlyCar.super.startEngine(key);
        OperateCar.super.startEngine(key);
    }
}

Defining a Method with the Same Signature as a Superclass's Method 

  Superclass Instance Method Superclass Static Method
Subclass Instance Method Overrides Generates a compile-time error
Subclass Static Method Generates a compile-time error Hides

# 다이나믹 메소드 디스패치 (Dynamic Method Dispatch)

 

메소드 오버라이딩은 자바의 런타임 다형성에서 제공하는 방법중 하나이다.
다이나믹 메소드 디스패치는 이런 오버라이딩된 메소드가 '컴파일' 될 때보다 '런타임'될 때 결정되는 것을 말한다.

오버라이딩 메소드는 superclass의 레퍼런스를 통해 호출된다. 자바는 그 타이밍의 객체의 타입이 어떤 호출을 했는지에 따라 super클래스 or sub클래스의 메소드를 결정한다. 그러므로 '런타임'에 결정된다.

'런타임' 할 때 객체가 어떤 타입을 refer했느냐에 따라 버전이 결정된다.
이런 '런타임'시 오버라이딩 메소드를 호출되는 문제를 해결하기 위해 upcasting을 이용한다.

런타임 오버라이딩 메소드 해결방법 'upcasting'

 

// A Java program to illustrate Dynamic Method 
// Dispatch using hierarchical inheritance 
class A 
{ 
    void m1() 
    { 
        System.out.println("Inside A's m1 method"); 
    } 
} 
  
class B extends A 
{ 
    // overriding m1() 
    void m1() 
    { 
        System.out.println("Inside B's m1 method"); 
    } 
} 
  
class C extends A 
{ 
    // overriding m1() 
    void m1() 
    { 
        System.out.println("Inside C's m1 method"); 
    } 
} 
  
// Driver class 
class Dispatch 
{ 
    public static void main(String args[]) 
    { 
        // object of type A 
        A a = new A(); 
  
        // object of type B 
        B b = new B(); 
  
        // object of type C 
        C c = new C(); 
  
        // obtain a reference of type A 
        A ref; 
          
        // ref refers to an A object 
        ref = a; 
  
        // calling A's version of m1() 
        ref.m1(); 
  
        // now ref refers to a B object 
        ref = b; 
  
        // calling B's version of m1() 
        ref.m1(); 
  
        // now ref refers to a C object 
        ref = c; 
  
        // calling C's version of m1() 
        ref.m1(); 
    } 
} 

 

객체 A, B, C가 생성되었다. B,C는 A를 상속하였다.

 

A를 선언시엔 NULL을 가리킨다.

 

**중요 : 아래 a, b, c가 각각 A,B,C를 refer하고 런타임시 객체타입에 따라 어떤 m1()이 호출될지 결정된다. 

더불어 자바의 런타임 다형성은 오버라이딩 메소드만 적용되고 변수에는 호출할 수 없다.

 

Dynamic Method Dispatch의 장점

  1. Dynamic method dispatch는 런타임 다형성 중심으로 자바의 오버라이딩 메소드를 쓸 수 있게 한다. 
  2.  모든 파생된 클래스에게 공통으로 쓰이는 클래스의 메소드를 구체화한다. 그리고 모든 subclass에게 구체적인 실행을 정의할 수 있게한다. 
  3. subclass에게 구체적인 실행을 위해, 구체적인 메소드를 추가할 수 있게 한다.

 

 

# 추상 클래스

직접 사용할 수 없고, 반드시 사용하도록 강제하는 것이다. 일종의 '규제' 같은 것이다.

문법적인 부분은 아래 예제를 통해 정리했다.

abstract class A{
    public abstract int b();
    //본체가 있는 메소드는 abstract 키워드를 가질 수 없다.
    //public abstract int c(){System.out.println("hello");}
    //추상 클래스 내에는 추상 메소드가 아닌 메소드가 존재 하루 수 있다.
    public void d(){
        System.out.println("world");
    }
}
class B extends A{
    public int b(){
        return 1;
    }
}

public class AbstractDemo {
    public static void main(String[] args) {
        B b = new B();
        b.b();
        b.d();
    }
}


공통적인 부분은 클래스에서 정의하고 개별적으로 정의되야 하는 부분은 사용자가 정의해야 할 때 abstract 클래스를 사용한다.
실행시에 사용자가 직접구현한 메소드가 없는 부분은 부모클래스에 정의된 메소드가 호출된다.

abstract 클래스는 디자인 패턴에 활용할 수 있다.
디자인 패턴이라는 것은 건축의 공법같이 무엇을 만드는지 하나의 공법같은 것으로 사용자간의 약속을 할 수 있어 커뮤니케이션에 도움이 된다.

 

 

# final 키워드

final키워드를 메소드 선언 앞에 사용하면 해당 메소드는 오버라이드 될 수 없게 한다. 추상클래스와 반대되는 개념의 '규제' 같은 것이다. final 메소드가 된다는 뜻이다.

변경되지 말아야 할 메소드거나, 해당 object가 지속적인 상태가 유지되야 할 때 사용된다.

class ChessAlgorithm {
    enum ChessPlayer { WHITE, BLACK }
    ...
    final ChessPlayer getFirstPlayer() {
        return ChessPlayer.WHITE;
    }
    ...
}

생성자에 의해 호줄된 메소드는 일반적으로 final메소드이다. 만약 생성자가 non-final메소드를 호출했다면, subclass가 재정의할 것이다. 특정 상황에만 유용하다. 예를들어 String 클래스 같은 불변의 클래스에 활용할 수 있다.

 

# Object 클래스

Object 클래스는 모든 java.lang.package의 가장 상위 superclass이다. 모든 클래스가 직간접적으로 자손클래스(subclass)이다. 모든 클래스가 상속받아 사용할 수 있고, 사용안할 수도 있다. Object 클래스 메소드를 필요에 따라 오버라이드할 수 있기 때문에 하나씩 알아보자.

protected Object clone() throws CloneNotSupportedException
      Creates and returns a copy of this object.
public boolean equals(Object obj)
      Indicates whether some other object is "equal to" this one.
protected void finalize() throws Throwable
      Called by the garbage collector on an object when garbage
      collection determines that there are no more references to the object
public final Class getClass()
      Returns the runtime class of an object.
public int hashCode()
      Returns a hash code value for the object.
public String toString()
      Returns a string representation of the object.

//스레드 관련하여 동기화 시켜주는 메소드다.(멀티 스레드 관련 포스팅 참고.)
public final void notify()
public final void notifyAll()
public final void wait()
public final void wait(long timeout)
public final void wait(long timeout, int nanos)

The clone() Method

clone()메소드는 존재하는 객체를 복사하는 메소드이다. Cloneable 인터페이스를 실행하기 때문에 예외 처리를 꼭 해줘야 한다.

 

protected Object clone() throws CloneNotSupportedException

or:

public Object clone() throws CloneNotSupportedException

 

똑같은 클래스가 생성되면, 동일한 멤버변수를 가지고 있다. 대부분 clone()메소드가 잘 작동하지만 외부 클래스를 참조하는 객체는 적절한 오버라이딩으로 메서드를 정의해줘야 한다. 참조하는 객체 때문에 독립적으로 작동하지 않기 때문이다.

The equals() Method

기본적으로 operator(==)를 통해 기본형은 같음을 비교할 수 있다. 그러나 참조형 타입은 equals()를 통해 비교해야 한다.

 

public class Book {
    ...
    public boolean equals(Object obj) {
        if (obj instanceof Book)
            return ISBN.equals((Book)obj.getISBN()); 
        else
            return false;
    }
}

// Swing Tutorial, 2nd edition
Book firstBook  = new Book("0201914670");
Book secondBook = new Book("0201914670");
if (firstBook.equals(secondBook)) {
    System.out.println("objects are equal");
} else {
    System.out.println("objects are not equal");
}

두 객체를 정확하게 비교하기 위해서는 equals() 객체를 꼭 오버라이드하여야 해야한다.


Note: If you override equals(), you must override hashCode() as well.


The finalize() Method

Object클래스의 콜백 메소드로 해당하는 object가 garbage가 될 때 실행된다. 기본적으로 아무 것도 정의되지 않았다. 오버라이딩을 통해 cleanup이 가능하다. 그러나 시스템에 의해 자동으로 호출될지라도 명확하지 않다. 그러므로 finalize()에 의존하지 말자. 예를 들어, I / O를 수행 한 후 코드에서 파일 descriptors를 닫지 않고 finalize ()가이를 닫을 것으로 예상하면 파일 descriptors가 부족할 수 있다.

 

The getClass() Method

getClass()는 오버라이드 할 수 없다.

해당하는 클래스의 정보들을 getter할 수 있다. 50개가 넘는다. isAnnotation(), isInterface(), isEnum(), getFields(), getMethods()등이 있다

void printClassName(Object obj) {
    System.out.println("The object's" + " class is " +
        obj.getClass().getSimpleName());
}

The hashCode() Method

해당 클래스의 hashCode가 리턴된다. (해당 객체 메모리의 16진수 주소)

equals() 메소드와 함께 2개의 객체가 같다면 hash code도 반드시 같아야 한다. 그러므로 equals()메소드를 오버라이딩 한다면 hashCode()도 오버라이딩 해야한다.

The toString() Method

반드시 toString()메소드를 오버라이딩 하는 것을 고려해야 한다. 왜냐하면 디버깅하는데 상당히 유용하게 쓰인다.

 

출처 : 

1. docs.oracle.com/javase/tutorial/java

2. 생활코딩(www.opentutorials.org/)

3. www.geeksforgeeks.org/dynamic-method-dispatch-runtime-polymorphism-java/

'Backend > Java' 카테고리의 다른 글

Java NIO 알아보기  (0) 2021.03.01
Java I/O (Input / Output)  (0) 2021.02.21
annotations  (0) 2021.02.06
Enum 자바의 열거형  (0) 2021.01.29
[Java] 클래스  (0) 2021.01.20