백기선님 자바스터디 6주차 : 상속

학습 1) 자바 상속의 특징

개념

  • 한 클래스에서 파생된 클래스를 subclass라고 한다. ( == derived class, extended class, child class )
  • 그 대상이 된 클래스를 super class ( == base class, parent class)라고 한다.
  • is -a 관계가 성립한다.
    • 하위클래스 is a 상위클래스 )
  • 키워드 extends를 사용한다.
class Vehicle {
	..
}

class Car extends Vehicle {
	.. 
}
  • 장점 : 코드 중복 막고, 공통적인 코드 변경에 용이
  • 단일상속 : Object을 제외하고 모든 클래스는 단 하나의 슈퍼 클래스가 있다. ( extends A, B 불가), ( c++은 다중상속 허용 )
    • 명시적으로 슈퍼클래스가 표시되어있지 않은 클래스는 Object의 하위 클래스다. (extends Object)
    • implements를 활용해서 다중 상속을 흉내낼 수 있다. 자바의 정석 참고.

부모클래스의 모든 것을 가져온다?

  • 부모 클래스의 public, protected 멤버변수 모든 것을 가져온다. (패키지에 상관없이)
  • 하위 클래스가 부모와 같은 패키지에 있다면 package-private 멤버변수도 다 가져올수도 있다. (replace도 가능)
  • 생성자는 멤버가 아니므로 상속되지 않는다.
    • super() 등을 통해 하위클래스에 생성자를 적용할 수 있다. (아래 super() 설명)
  • superclass의 private 멤버는 상속되지 않는다. 다만 superclass의 public, protected 메소드가 private 멤버에 접근하고 있다면 이것은 subclass에서도 사용할 수 있다.
public class Practice {
    public static void main(String[] args) {
        Car c = new Car();
        c.printPrivateN();
    }
}

class Vehicle {
    private int n = 1;

    public int getPrivateN() {
        return this.n;
    }
}

class Car extends Vehicle {
    public void printPrivateN() {
        int n = super.getPrivateN();
        System.out.println(n); // 1출력
    }
}
  • 오버라이딩 메소드는 조상 클래스의 메소드의 하위 타입을 반환할 수 있다. (아래 covariant return type)

학습 2) super 키워드

부모클래스의 멤버에 접근 할 때

  • 자식 클래스에서 부모 클래스를 가리킬 때 사용
  • 만약 부모클래스의 메소드를 오버라이딩하고 있다면 super 키워드 통해서 부모클래스의 메소드에 접근 가능 하다
  • static 메소드에서 사용 불가
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");
    }
}

자식클래스의 생성자에서 사용할 때

  • 부모클래스의 생성자를 가리킬 때 사용
  • super() or super(parameter list)
public MountainBike(int startHeight, 
                    int startCadence,
                    int startSpeed,
                    int startGear) {
    super(startCadence, startSpeed, startGear);
    seatHeight = startHeight;
}   
  • 자식클래스에서 명시적으로 부모클래스의 생성자를 호출하지 않으면, 자바 컴파일러가 자동적으로 인자 없는 부모클래스의 생성자를 호출한다. ( super() )
    • 이 때, 만약 부모클래스에 인자 없는 생성자가 없으면 컴파일 에러가 날 것이다.
    • 모든 클래스의 조상인 Object는 인자없는 생성자가 있기 때문에, 부모클래스가 Object라면 이런 문제는 일어나지 않는다.
class Vehicle2 {
    int num = 1;

    Vehicle2(int num) {
        this.num = num;
    }
}

class Car2 extends Vehicle2 {
    int num;
    String name;

    Car2(int num, String name) { //컴파일 에러. 
        this.num = num;
        this.name = name;
    }
}
  • 위에서 Car2의 생성자에서 super에 대한 명시가 없기 때문에 자동적으로 super()를 호출한다. Vehicle2() 와 같은 인자 없는 생성자가 Vehicle에 없기 때문에 컴파일 에러가 발생한다.
class Vehicle2 {
    int num = 1;

    Vehicle2(int num) {
        this.num = num;
    }
}

class Car2 extends Vehicle2 {
    int num;
    String name;

    Car2(int num, String name) { //컴파일 ok.
        super(num);
        this.name = name;
    }
}
  • 존재하는 super()를 작성해 줘야 한다.

학습 3) 메소드 오버라이딩

  • 상속받은 조상의 메소드를 오버라이딩 하는 것.
  • 주의. 오버로딩과 헷갈리지 말기
  • 주의. hiding
    • static 메서드 오버라이딩이 아니라 hiding
      • 오버라이딩은 런타임에 객체를 호출하는데 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");
  }

  @Override
  public void testInstanceMethod() {
    System.out.println("The instance method in Cat");
  }

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

오버라이딩 조건

  1. 메소드의 선언부 ( 메소드의 시그니처 : 메소드 이름, 매개변수 타입과 수, 리턴타입) 이 같아야 한다.
class Vehicle3 {
    int oil = 100;

    public void run() {
        oil--;
    }
}

class Car3 extends Vehicle3 {
    public void run() {
        oil -= 2;
    }
}
  1. 접근제어자는 부모 클래스의 접근제어자 보다 좁은 범위로 변경할 수 없다.
class Vehicle3 {
    int oil = 100;

    public void run() {
        oil--;
    }
}

class Car3 extends Vehicle3 {
    protected void run() { // 컴파일 에러 
        oil -= 2;
    }
}
  1. 조상 클래스의 메소드보다 많은 수의 예외를 선언할 수 없다.
class Vehicle3 {
    int oil = 100;

    public void run() throws IndexOutOfBoundsException{
        oil--;
    }
}

class Car3 extends Vehicle3 {
    public void run() throws IndexOutOfBoundsException, IOException { // 컴파일 에러 
        oil -= 2;
    }
}

covariant return type

  • 오버라이딩 하면 그 오버라이딩한 조상클래의 메소드의 리턴타입의 하위 클래스를 리턴할 수 있다.
class B extends A {

}

class C {
    
}

class Vehicle3 {
    int oil = 100;

    public void run() throws IndexOutOfBoundsException{
        oil--;
    }

    public A getObject() {
        return new A();
    }
}

class Car3 extends Vehicle3 {
    public void run() throws IndexOutOfBoundsException {
        oil -= 2;
    }
    
    public C getObject() { //컴파일 에러
        return new C();
    }
class Car3 extends Vehicle3 {
    public void run() throws IndexOutOfBoundsException {
        oil -= 2;
    }

    public B getObject() { //이상무 
        return new B();
    }

}

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

  • 오버라이딩된 메소드 호출할 때 어떤 메소드를 호출할지를 (컴파일 할 때가 아니라) 런타임 때 결정하는 매커니즘
public class DynamicMethodDispatch {
    public static void main(String[] args) {
        Fruit fruit = new Fruit();
        Fruit peach = new Peach();

        fruit.eat(); //"과일은 맛있어"
        peach.eat(); //"복숭아는 맛있어"
    }
}

class Fruit {
    public void eat() {
        System.out.println("과일은 맛있어");
    }
}

class Peach extends Fruit {
    @Override
    public void eat() {
        System.out.println("복숭아는 맛있어");
    }
}

만약 컴파일 할 때 결정하면(참조타입에 의해 결정되므로) peach.eat() 도 "과일은 맛있어"가 되어야 한다.

런타임에 결정하면 JVM이 참조변수에 저장된 객체를 확인하므로 위와 같은 결과가 나온다.

이에 따라 다형성이 지원된다

학습 5) 추상 클래스

  • abstract로 선언된 클래스

추상 클래스 개념 이해

  • 클래스가 설계도라면 추상 클래스는 미완성 설계도. 구체적인 행동 내용은 부모 입장에서 정의할 수 없으니 자식 클래스에서 이를 구현해야함을 명시하는 것
  • 해당 부분 잘 설명해주신 (출처 속 댓글) 출처

    안녕하세요,
    추상 클래스와 메서드의 개념은 객체지향설계 개념을 좀 더 익히면 이해하기 쉬워질 거라 생각합니다.
    객체지향설계에서는 구현해야할 것들을 객체들로 바라본 후, 객체들 간의 연관 관계를 상속(Is-a), 포함(Has-a)로 풀어낸다는 것이 핵심입니다.
    추상 개념은, 객체들 간의 관계를 상속 관계로 정립하면서 생기는, 점차 상위의 클래스를 정의하게 되면서 발생하는 "구체적인 행동 내용은 부모 입장에서 정의할 수 없는 것"을 묘사하기 위해 이용하는 것이라 생각하면 될 것 같습니다.
    글에서 설명하신 것과 같이 isRelevant()는 자식 시점에서는 그 행동을 구체적으로 정의할 수 있지만 부모
    입장에서는 그럴 수 없지요. 게다가 모든 자식 클래스가 반드시 구현해야할 행동입니다. 그렇다면 이 메소드를 부모 단계에서 추상화하여 "내 시점에선 행동을 특정할 수 없지만 자식 시점에선 반드시 구현되어야 하고 구현될 수 있는 행동이다" 라는 것을 설계 시점에서 명시하는 겁니다.
    학교나 여느 책에선 해당 이야기를 이런 예를 들어서 설명합니다. 만일 갖가지 도형 객체를 구현해야 하고, 넓이나 부피, 중심 좌표 등을 제공해줄 수 있게 해야한다고 합시다. 그럼 "원", "사각형", "삼각형" 등의 클래스는 추상화하여 "평면도형"이라는 부모 클래스를 정의하고 상속받게 할 수 있을겁니다. "구", "직육면체", "삼각뿔" 등의 클래스는 "입체도형"이라는 부모 클래스를 정의하고 상속받게 할 수 있을거고요.
    입체도형과 평면도형 클래스는 다시 추상화하여 "도형"이라는 부모 클래스를 정의하고 상속 받게 설계하면 추상화를 완료하게 됩니다. 도형->평면도형->원 과 같은 형태로요.
    넓이()나 부피() 같은 메소드는 최종 자식 클래스에선 구체적으로 구현해줄 수 있지만 평면도형, 입체도형 클래스 시점에선 이를 구현하기 힘들겁니다. 따라서 이들을 추상 메소드로 선언해두고, 자식 클래스에서 실제로 구현해주는거죠. 또 도형 입장에서는 무게중심 혹은 질량중심의 좌표를 담아두기 힘들죠. 하지만 평면도형 클래스에선 무게중심의 좌표를, 입체도형 클래스에선 질량중심의 좌표를 담아둘 수 있을 것입니다.
    이런 일련의 과정을 이해하고 몇 번 연습과 실습을 거치게 되면 본인이 맡은 기능을 구현해나가는 도중에 본인이 정의한 클래스들 간의 공통점을 찾아 추상화한 부모 클래스를 만들고 추상 메소드를 추출해나갈 수 있게 될겁니다. 그리고 이게 객체지향설계의 절반 정도 되는 것 같습니다. 그만큼 간단하지만 중요한 개념입니다.
    안드로이드가 그런 관점에서는 참고하기 좋은 운영체제입니다. 자바 자체가 객체지향 개념에 잘 기반해 만든 언어인데 안드로이드 역시 자바를 이용하여 각종 API와 기능들을 객체지향에 기반해 잘 설계했거든요.

추상 클래스 특징

  • 추상 메소드가 있을 수도 있고 없을 수도 있다.

    • 추상메소드는 구현부가 없는 메소드다
    abstract void moveTo(double deltaX, double deltaY);
  • 추상메소드가 있는 클래스는 반드시 추상클래스어야만 한다.

  • 추상클래스의 인스턴스는 만들 수 없다.

    • 상속해서 추상 메소드를 구현해야 한다.
  • 추상클래스는 하위클래스를 가질 수 있다.

  • 보통 abstract의 하위 클래스는 모든 추상 메소드를 구현한다. 그렇지 않으면 그 하위클래스는 abstract로 선언되어야 한다.
  • 추상 클래스의 특징을 생각할 때 추상클래스는 일반 클래스인데 추상메소드를 갖고 있는 것이라고 생각하면 편하다.
  • 참고. 인터페이스의 메소드는 디폴트나 static으로 선언되지 않으면 전부 abstract이다. 그래서 따로 abstract라고 명시할 필요가 없다.

학습 6) final 키워드

public class Base{
    public       void m1() {...}
    public final void m2() {...}

    public static       void m3() {...}
    public static final void m4() {...}
}

public class Derived extends Base
{
    public void m1() {...}  // OK, overriding Base#m1()
    public void m2() {...}  // forbidden

    public static void m3() {...}  // OK, hiding Base#m3()
    public static void m4() {...}  // forbidden
}

  • Final 변수 : 오직 한번만 초기화될 수 있다. 꼭 선언할 때 초기화될 필요는 없다.
    - 변수를 final로 선언한다는 것은 어떤 순간에도 그것이 같은 객체를 가리킨다는 의미다.

    • 반대로 객체는 자신을 가리키는 변수에 대해 어떤 영향도 받지 않는다.
    • 그렇기 때문에 변수가 어떤 객체를 가리킨다고 했을 때 그 안의 멤버 변수 등을 자유롭게 변경될 수 있다.
    public class FinalKeyWord {
        public static final int[] ints = new int[5];
        public static final List<Integer> integerList = new ArrayList<>();
        public static void main(String[] args) {
            ints[1] = 5;
            integerList.add(1);
    
            System.out.println(Arrays.toString(ints)); //[0, 5, 0, 0, 0]
            System.out.println(integerList); //[1]
        }
    }    
    • 만약 이 또한 막고 싶다면 객체의 모든 필드가 final로 선언되어야 한다.

학습 7) Object 클래스

  • 모든 클래스의 조상이다.

    • java는 단일상속이므로, 부모 클래스가 명시되있지 않으면 extends Object
  • 다양한 메서드를 가지고 있다.

  • 메서드1) clone() : 특정 클래스를 복제해서 인스턴스를 생성할 때 사용

    • 객체를 복제해서 사용하고 싶을 때, 참조 변수를 그대로 가져온다면 객체의 참조값이 같아, 원래 객체를 가져오는 것과 다름 없다. 이 때 clone을 사용한다.
    public class Clone {
        public static void main(String[] args) {
            Student s = new Student("Chars");
            Student m = s;
            m.changeName("Kim");
            System.out.println(s.name); // Kim 
        }
    }
    
    class Student {
        String name;
        Student(String name) {
            this.name = name;
        }
    
        public void changeName(String name) {
            this.name = name;
        }
    }
    • 사용방법

      • Cloneable을 implements 해서 해당 클래스가 복제 가능한 클래스라는 사실을 JVM에게 알려준다.
      • 그리고 예외처리 해줘야 한다. throws CloneNotSupportedException
      public class Clone {
          public static void main(String[] args) {
              Student student = new Student("Cheol");
              student.toString(); // 가능, 접근제어자 public
              student.clone() ; // 불가능, 접근제어자 protected
          }
      }
      
      class Student implements Cloneable {
          String name;
          Student(String name) {
              this.name = name;
          }
      }
      • 접근제어자가 protected 이므로 Object와 다른 패키지에서 쓰고 싶다면 (일반적으로 이에 해당한다. 우리가 정의한 클래스에서 사용하고 싶다면) clone 다시 써준다.
class Test {
	int x, y;
}

class Test2 implements Cloneable {
	int a;
	int b;
	Test c = new Test();
	public Object clone() throws CloneNotSupportedException
	{
		return super.clone();
	}
}


public class Main {
	public static void main(String args[])
		throws CloneNotSupportedException
	{
		Test2 t1 = new Test2();
		t1.a = 10;
		t1.b = 20;
		t1.c.x = 30;
		t1.c.y = 40;

		Test2 t2 = (Test2)t1.clone();


		t2.a = 100;
		t2.c.x = 300;

		System.out.println(t1.a + " " + t1.b + " " + t1.c.x
						+ " " + t1.c.y); // 10 20 300 40
		System.out.println(t2.a + " " + t2.b + " " + t2.c.x
						+ " " + t2.c.y); // 100 20 300 40
	}
}
  • 메서드2) equals() : 오버라이딩해서 동등성 정의

    • equals 오버라이딩 할 때 hashCode() 도 같이 오버라이딩 해줘야 한다. : 해시 값을 사용하는 hashMap이나 hashSet에서 우리는 같은 객체로 인식하길 바라는 상황에서도 해시 값이 달라 다른 객체로 인식 할 수도 있기 때문 https://hee96-story.tistory.com/48
  • 메서드3) finalize() : 객체가 소멸될 때 호출되기로 약속된 메소드. 오버라이딩 해서 객체가 소멸될 때 일어나야 할 일을 작성 할 수 있다.

    • 많은 자바의 전문가가 이 메소드의 사용을 지양한다. 사용될 일 거의 없다.
  • 메서드4) getClass() : 클래스 정보를 얻기 위해 사용한다. (getSimpleName(), getSuperclass(), getInterfaces())

    • 오버라이드 할 수 없다
        void printClassName(Object obj) {
        System.out.println("The object's" + " class is " +
            obj.getClass().getSimpleName());
    }
    
  • 메서드5) hashCode()

  • 메서드6) toString()


출처

좋은 웹페이지 즐겨찾기