[6] Java - Extends

9306 단어 Java-BasicJava-Basic

자바 상속의 특징

상속 개념

기존에 정의되어 있는 클래스( 부모 클래스 ) 의 모든 필드와 메소드를 자식 클래스가 물려받아 기능을 추가하거나 재정의하여 새로운 클래스를 정의하는 것

상속의 장점

- 기존에 작성된 클래스를 재활용 할 수 있음

- 자식 클래스 설계 시 중복되는 멤버를 미리 부모 클래스에 작성하면, 자식 클래스에서는 해당 멤버를 정의하지 않아도 됨

- 클래스 간의 계층적 관계를 구성함으로 다형성의 문법적 토대 마련

상속 사용 시 주의점

- 자식 클래스에는 부모 클래스의 필드와 메소드만이 상속되며, 생정자와 초기화 블록은 상속되지 않는다.

  또한 부모 클래스의 접근 제어가 private나 default로 설정된 멤버는 자식 클래스에서 상속받지만 접근할 수는 없다.

- 자바에서 클래스는 단 한개의 클래스만을 상속받는 단일 상속만이 가능하다.

부모 클래스와 자식 클래스 간의 포함 관계

- 부모 클래스는 자식 클래스에 포함된 것으로 볼 수 있음

- 부모 클래스에 새로운 필드를 하나 추가하면, 자식 클래스에도 자동으로 해당 필드가 추가된 것처럼 동작 함

- 자식 클래스에는 부모 클래스에 필드와 메소드만이 상속되며, 생성자와 초기화 블록은 상속되지 않음

- 부모 클래스의 접근 제어가 private 나 default로 설정된 멤버는 자식 클래스에서 상속받지만 접근할 수는 없음

부모 클래스와 자식 클래스 간의 포함 관계 예제

-자식 클래스에서 부모 클래스에서 상속받은 public 필드는 자식 클래스에서 따로 선언하지 않은 필드라도 해당 이름의 필드를 부모 클래스에서 상속 받았다면 문제없다.하지만 age 변수는 private 필드라 접근할 수 없으므로, 오류가 발생 됨, 또한 자식 클래스에서는 자신만의 필드나 메소드를 선언하여 사용할 수 있다.

-  자바에서 클래스는 단 한 개의 클래스만을 상속받는 단일 상속만이 가능하다. 

super 키워드

- 부모 클래스로부터 상속받은 필드나 메소드를 자식 클래스에서 참조하는 데 사용하는 참조 변수

- 인스턴스 변수의 이름과 지역 변수의 이름이 같을 경우 인스턴스 변수 앞에 this 키워드를 사용하여 구분

- 부모 클래스의 멤버와 자식 클래스의 멤버 이름이 같을 경우 super 키워드를 사용하여 구별

- super 참조 변수를 사용하여 부모 클래스의 멤버에 접근

- this와 마찬가지로 super 참조 변수를 사용할 수 있는 대상도 인스턴스 메소드뿐이며, 클래스 메소드에서는 사용할 수 없음

super 키워드 예제

실행 결과

int형 변수 num는 부모 클래스인 Parent 클래스에서만 선언되어 있음, 따라서 지역 변수와 this 참조 변수 그리고 super 참조 변수 모두 같은 값 출력 , 하지만 위 예제에서 int형 변수 num은 자식 클래스인 Child클래스에서도 선언되어 있음. 따라서 지역 변수와 this 참조 변수는 자식 클래스에서 대입된 값을 출력하며, super 참조 변수만이 부모 클래스에서 대입된 값을 출력하게 됩니다.

자식 클래스인 Child 클래스에서도 동일 한 변수 ( a ) 가 선언된 경우 예제

실행결과

지역 변수와 this 참조 변수는 자식 클래스에서 대입된 값을 출력하며, super 참조 변수만이 부모 클래스에서 대입 된 값을 출력하게 된다.

super() 메소드

- 부모 클래스의 생성자를 호출할 때 사용

- 자식 클래스의 인스턴스를 생성하면, 해당 인스턴스에는 자식 클래스의 고유 멤버뿐만 아니라 부모 클래스의 모든 멤버까지도 포함되어 있다. 따라서 부모 클래스의 멤버를 초기화하기 위해서는 자식 클래스의 생성자에서 부모 클래스의 생성자까지 호출해야만 한다. 이러한 부모 클래스의 생성자 호출은 모든 클래스의 부모 클래스인 Object 클래스의 생성자까지 거슬러 올라가며 수행 됨.

- 자바 컴파일러는 부모 클래스의 생성자를 명시적으로 호출하지 않는 모든 자식 클래스의 생성자 첫 줄에 자동으로 super() 라는 명령문을 

추가하여, 부모 클래스의 멤버를 초기화 할 수 있도록 해준다.

super() 메소드 예제

자바 컴파일러는 컴파일 시 클래스에 생성자가 하나도 정의되어 있지 않아야만, 자동으로 기본 생성자를 추가해 준다.

만약 다음 예제처럼 부모 클래스에 매개변수를 가지는 생성자를 하나라도 선언됐다면, 부모 클래스에는 기본 생성자가 자동으로 추가되지 않았을 것

Parent 클래스를 상속받은 자식 클래스에서 super() 메소드를 사용하여 부모 클래스의 기본 생성자를 호출하게 되면, 오류가 발생 됨 이유는, 부모 클래스인 Parent 클래스에는 기본 생성자를 선언해야 할 경우에는 되도록이면 다음 예제처럼 기본 생성자까지 명시적으로 선언하는 것이 좋다.

이렇게 기본 생성자까지 명시적으로 선언하는 것이 좋음

super() 메소드 호출 예제

실행 결과

위 예제를 그냥 실행하면 자바 컴파일러는 주석 처리된 super(40) 에 자동으로 super(); 구문을 삽입할 것이다. 따라서 변수 age는 10으로 초기화 됨, 하지만 super(40)의 주석을 해제하고 실행하면 부모 클래스인 Parent 클래스는 두번째 생성자에 의해 초기화 될 것.

따라서 변수 age는 40으로 초기화 됨.

메소드 오버라이딩

- 상속 관계에 있는 부모 클래스에서 이미 정의된 메소드를 자식 클래스에서 같은 시그니처를 갖는 메소드로 다시 정의하는 것

- 이 기법을 사용하면 상속받은 메소드들을 자기 자신의 필요에 맞추어서 변경할 수 있음 

Animal 클래스에 sound()라는 메소드가 선언되어 있다고 하면, Animal 클래스는 특정한 동물을 지정하지 않으므로 sound()의 몸체는 비어있음.

여기서 Animal class를 상속받은 Dog 클래스에 sound()메소드를 재정의하여 개가 짖는 소리를 출력하면 실행결과와 같이 Dog 클래스에 선언된 sound() 함수가 호출된다.

실행 결과

오버라이딩시 주의 할 점

- 메소드의 헤더는 그대로 두고 메소드의 몸체만 교체하는 것, 즉 메소드의 헤더 부분은 수퍼 클래스의 헤더와 동일하여야 한다. 한마디로 메소드의 이름, 반환형, 매개변수의 개수와 데이터 타입이 일치하여야 한다. 

- 수퍼 클래스의 메소드를 재정의 하려면 메소드가 public 으로 선언되어 있어야 한다. private 메소드는 재정의 할 수 없음.

- 접근 지정자 같은 경우, 수퍼 클래스의 메소드보다 더 좁은 범위로 변경할 수 없음. 예를들어 수퍼 클래스의 메소드가 protected로 선언되어 있는 경우 서브 클래스의 메소드는 protected나 public으로만 변경이 가능 함. 

오버라이딩 시 흔히하는 실수 예제

위 예제처럼 부모클래스의 메소드가 sound() 가 아닌 saund()로 오타가 났을 경우 메소드는 오버라이딩 되지 않는다, 하지만 컴파일러는 sanund()를 새로운 메소드로 인지하기 때문에 아무런 오류가 발생하지 않는다. 따라서 이 경우에는 @Override를 붙여서 사용해주면 컴파일러에게 수퍼 클래스의 메소드를 오버라이드하려고 한다는 것을 알려 줄 수 있다. 만약 컴파일러가 수퍼 클래스에 그러한 메소드가 존재하지 않는다는 것을 감지하면 오류를 발생하게 된다. 

@Override를 붙이면 컴파일러 Error 발생

오버로딩과 오버라이딩의 차이

- 오버로딩은 같은 이름의 메소드를 여러개 정의

- 오버라이딩은 수퍼 클래스에 있던 상속받은 메소드를 다시 정의해서 내용을 변경

추상 클래스

- 완전하게 구현되어 있지 않은 메소드를 가지고 있는 클래스를 의미

- 메소드가 미완성되어 있으므로 추상 클래스로는 객체를 생성할 수 없다.

- 추상 클래스는 주로 상속 계층에서 추상적인 개념을 나타내기 위한 용도로 사용

추상 클래스의 예

도형을 그리는 방법은 각각의 도형에 따라 달라진다. 따라서 draw()의 몸체는 Shape에서는 정의될 수 없지만 메소드의 이름과 매개변수는 정의될 수 있다. 이런 경우 추상 메소드가 사용되며, draw()는 추상 메소드로 정의되고 draw()의 몸체는 각각의 서브 클래스에서 작성된다.

Object 클래스

- 모든 클래스의 부모 클래스가 되는 클래스

- 자바의 모든 Class는 자동으로 Object 클래스의 모든 필드와 메소드를 상속받게 되며, 별도로 extends 키워드를 사용하여 Object 클래스의 상속을 명시하지 않아도 Object 클래스의 모든 멤버를 자유롭게 사용할 수 있다.

- 자바의 모든 객체에서 toString() 이나 clone() 과 같은 메소드를 바로 사용할 수 있는 이유가 해당 메소드들이 Object Class의 메소드이기 떄문이다. 

Java.lang.Object 클래스

- Java.lang 패키지의 클래스들은 import 문을 사용하지 않아도 클래스 이름만으로 바로 사용할 수 있고 그중에서도 가장 많이 사용되는 클래스는 Object 클래스이다. Object 클래스는 자바 클래스의 조상 클래스가 되는데, 자바의 모든 클래스는 Object클래스의 메소드를 바로 사용할 수 있다.

Object 클래스 메소드 종류

toString() 메소드

- Object 클래스의 메소드

- 인스턴스에 대한 정보를 문자열로 반환

- 반환되는 문자열은 이름과 함꼐 구분자로 '@'가 사용되며, 그 뒤로 16진수 해시코드가 추가 됨

- 16진수 해시 코드 값은 인스턴스의 주소를 가리키는 값, 인스턴스마다 모두 다르게 반환 됨

toString() 메소드 예제

Car car = new Car();
System.out.println( car.toString() );

// 실행결과
Car@15db9742

equals() 메소드

- 인스턴스를 매개변수로 전달받는 참조 변수와 비교하여, 그 결과를 반환

- 참조 변수가 가리키는 값을 비교, 서로 다른 두 객체는 언제나 false를 반환하게 됨

equals() 메소드 예제

Car car01 = new Car();
Car car02 = new Car();

System.out.println( car01.equals( car02 ) );
car01 = car02();	// 두 참조 변수가 같은 주소를 가리킴
System.out.println( car01.equals( car02 ) );

// 실행결과
false
true

clone() 메소드

- 인스턴스를 복제하여, 새로운 인스턴스를 생성해 반환함.

- 하지만 Object 클래스의 cloen() 메소드는 단지 필드의 값만 복사하므로, 필드의 값이 배열이나 인스턴스 제대로 복사할 수 없음 따라서 이러한 경우 clone() 메소드를 오버라이딩하여, 복제가 제대로 이루어지도록 재정의 해야 함.

- 이러한 clone() 메소드는 데이터 보호를 이유로 Clonable 인터페이스를 구현한 클래스의 인스턴스만이 사용할 수 있음.

clone() 메소드 예제

하지만, 여기서 이상한 점이 있다. car 인스턴스 변수를 carClone 인스턴스 변수로 clone하였는데 2번째 car.toString () , car.getModelName , car.getUserNameList() 을 출력하면 [hs, hs2]가 나온다.

즉, 이말은 원본 인스턴스인 car의 userNameList 필드에도 새로운 값이 추가되었음을 확인할 수 있다. 이처럼 단순히 부모 클래스의 clone()메소드를 호출하여 clone() 메소드를 재정의하면, 배열이나 인스턴스인 필드는 복제가 되는 것이 아닌 해당 배열이나 인스턴스를 가리키는 '주소값'만 복제되는 것

따라서 정확한 clone을 위해서는 배열이나 인스턴스인 필드에 대해서는 별도로 cloen() 메소드를 구현하여 호출해야 한다. 위에 주석된 걸 해제하고 결과를 다시 실행하면 정확한 실행결과가 출력 됨.

그 밖에 Object 메소드들

메소드설명
protected void finalize()해당 객체를 더는 아무도 참조하지 않아 가비지 컬렉터가 객체의 리소스를 정리하기 위해 호출
Class<T> getClass()해당 객체의 클래스 타입을 반환함.
int hashCode()해당 객체의 해시 코드값을 반환함.
void notify()해당 객체의 대기(wait)하고 있는 하나의 스레드를 다시 실행할 때 호출함.
void notifyAll()해당 객체의 대기(wait)하고 있는 모든 스레드를 다시 실행할 때 호출함.
void wait()해당 객체의 다른 스레드가 notify()나 notifyAll() 메소드를 실행할 때까지 현재 스레드를 일시적으로 대기(wait)시킬 때 호출함.
void wait(long timeout)해당 객체의 다른 스레드가 notify()나 notifyAll() 메소드를 실행하거나 전달받은 시간이 지날 때까지 현재 스레드를 일시적으로 대기(wait)시킬 때 호출함.
void wait(long timeout, int nanos)해당 객체의 다른 스레드가 notify()나 notifyAll() 메소드를 실행하거나 전달받은 시간이 지나거나 다른 스레드가 현재 스레드를 인터럽트(interrupt) 할 때까지 현재 스레드를 일시적으로 대기(wait)시킬 때 호출함.

좋은 웹페이지 즐겨찾기