본문 바로가기
Back-end/Java_T.I.L

6. 상속, 오버라이딩, 추상클래스, Object 클래스

by 사장님나빠여 2022. 1. 3.

목표

자바의 상속에 대해 학습하세요.

학습할 것 (필수)

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

 

#자바 상속의 특징

상속(inheritance)이란?

부모가 자식에게 물려주는 행위

자식(클래스)가 상속받고 싶은 부모(클래스)를 선택해서 물려받는다.

이 때 상속받는 클래스를 자식 클래스, 하위 클래스 또는 서브 클래스라고 한다.

상속을 해주는 클래스를 부모 클래스, 상위 클래스 또는 슈퍼 클래스라고 한다.

 

자식 클래스가 부모 클래스로부터 상속을 받게 되면 부모 클래스의 필드와 메서드를 물려받게 된다.

단, 제어 접근자가 private을 갖는 필드나 메서드는 상속이 불가능하고, 패키지가 다를 경우 default인 경우도 상속이 불가하다.

 

상속의 장점은 중복된 코드를 줄일 수 있고, 유지보수가 편리하며, 통일성이 있고 다형성을 구현할 수 있다는 점

상속을 통해 소스코드의 재사용이 보장되며 가독성이 높아진다는 점이다.

 

 

 

상속의 선언 - extends

상속을 받는 방법은 자식 클래스 뒤에 extends 키워드를 사용하고 부모 클래스를 적어주면 된다

   ex) class A extends B{...}

자바에서는 자식 클래스가 여러 부모로부터 다중 상속을 받는 것은 불가능하다.

즉, 1개의 부모 클래스로부터의 단일 상속만 허용된다. 하지만 부모 클래스는 여러 개의 자식 클래스에게 상속에 가능하다.

class (자식)클래스 extends (부모)클래스{....}  //부모 클래스는 2개이상 사용 불가.

class (자식)클래스1 extends (부모)클래스{....}  //가능
class (자식)클래스2 extends (부모)클래스{....}  //가능

class (부모)클래스 extends (조부모)클래스{....}  //가능
class (자식)클래스 extends (부모)클래스{....}  //가능

#super 키워드

super 란?

super는 자식 클래스가 부모 클래스로부터 상송받은 멤버를 참조할 때 사용하는 참조 변수다.

클래스 내의 멤버 변수와 지역 번수의 이름이 같을 경우 구분을 위해 this를 사용하듯이

부모 클래스와 자식 클래스의 멤버의 이름이 같을 경우 super를 사용한다.

this와 super는 인스턴스의 주소 값을 저장하는데 static 메서드(클래스 메서드)와는 무관하게 사용된다.

class Paent{
	int x = 10;
}
class Child extends Parent{	
	int x = 20;
    
    void childMethod(){
    	System.out.println("x=" +x);
        System.out.println("this.x"+thix.x);
        System.out.println("super.x"+super.x);
    }
}

 

Child 클래스의 childMethod()를 실행하면 

x=20

this.x=20

super.x=10 

출력된다.

 

super()

super()는 부모 클래스의 생성자를 호출하는 메서드다.

상송받은 자식 클래스가 부모 클래스의 멤버를 사용할 경우가 있을 수도 있으므로 부모 클래스를 우선적으로 초기화해줘야 한다. 부모 클래스의 생성자는 자식 클래스의 생성자 첫 줄에서 호출해준다.

이러한 부모 클래스에 대한 생서자 호출은 상속관계에 따라 Object 클래스까지 올라가서 마무리된다.

 

Object 클래스를 제외한 모든 클래스의 생성자의 첫 줄에는 반드시 자신의 클래스의 또다른 생성자 this() 또는 부모 클래스의 생성자 super()를 호출해줘야 한다. 이렇게 하지 않으면 컴파일러가 자동으로 super()를 생성자의 첫줄에 호출한다.

class Point{
	int x = 10;
    int y = 20;
    
    Point(int x, int y){
    //생성자의 첫줄에 다른 생성자를 호출하지 않았기 때문에,
   	//컴파일러가 이 부분에 super()를 호출한다.
    //부모 클래스이므로 Object 클래스의 super()가 호출된다.
    this.x=x;
    this.y=y;
    }
}

class Point3D extends Point{
	int z = 30;
    
    Point3D(){
    	this(100, 200, 300);
    }
    
    Point3d(int x, int y, int z){
    	super(x, y);
        this.z=z;
    }
}

위의 Point3D 생성자에 인수 입력 후 실행시 다음의 결과가 출력된다.

point3d.x = 100

point3d.y = 200

point3d.z = 300

 


#메소드 오버라이딩

오버라이딩(overriding)이란?

오버라이드(override)는 '무시하다' '...보다 더 중요하다'와 같은 사전적 의미를 갖는다

즉, 상속 관계에 있는 부모 클래스에서 이미 정의된 메서드를 자식 클래스에서 같은 시그니쳐를 갖는 메서드로 다시 정의하는 것을 의미한다.

자바에서 자식 클래스는 부모 클래스의 private 멤버를 제외한 모든 메서드를 상속받는다

이렇게 상속받은 메서드는 그대로 사용해도 되고, 필요한 동작을 위해 재정의하요 사용할 수도 있다.

즉, 메서드 오버 라이딩이란 상속받은 부모 클래스의 메서드를 재정의하여 사용하는 것을 의미한다.

 

오버라이딩의 조건

1. 오버라이딩이란 메서드의 동작만을 재정의하는 것이므로, 메서드의 선언부(반환타입, 메서드이름, 매게변수)는 기존 메서드와 완전히 같아야 한다.

    하지만 메서드의 반환 타입은 부모 클래스의 반환 타입으로 타입 변환할 수 있는 타입이라면 변경할 수 있다.

2. 부모 클래스의 메서드보다 접근 제어자를 더 좁은 범위로 변경할 수 있다.3. 부모 클래스의 메서드보다 더 큰 범위의 예외를 선언할 수 없다.

 

오버로딩과 오버라이딩

오버로딩과 오버라이딩의 개념은 확실히 다르며, 그 차이점을 아는 것이 중요하다.

 

오버로딩(overloading)은 새로운 메서드를 정의하는것.오버라이딩(overriding)은 상속받는 기존의 메서드를 재정의하는 것이다

 


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

메서드 디스패치(Method Dispatch)

메서드 디스패치란 어떤 메서드를 호출할지 결정하여 실제로 실행시키는 과정이다

자바는 런타임 시 객체를 생성하고, 컴파일 시에는 생성할 객체 타입에 대한 정보만 보유한다.

이 과정은 static(정적)과 dynamic(동적)이 있다.

 

Static Dispatch : 컴파일 시점에서, 컴파일러가 특정 메서드를 호출할 것이라고 명확하게 알고있는 경우(정적)

컴파일 시 생성된 바이트 코드에도 이 정보가 그대로 남아있다.

런타임(실행 시점)이 되지 않아도 미리 결정하는 개념이다.

함수를 오버로딩하여 사용하는 경우 인자의 타입이나 리턴 타입 등에 따라 어떤 메서드를 호출할지 알 수 있는 경우

public class ACar{
	public void print(){
    	System.out.println("A");
    }
}

public class BCar extends AcAR {  //메서드 오버라이딩 - ACar상속 후 함수 재정의
	public void print(){
    	System.out.println("BCar");
    }
}

public static void main(String[] args){
	BCar bcar = new BCar();
    System.out.println(bcar.print());  //BCar를 출력
}

Dynamic Dispatch : 정적 디스패치와 반대로 컴파일러가 어떤 메서드를 호출하는지 모르는 경우이다.

동적 디스패치는 호출할 메서드 런타임 시점에서 결정한다.

 

인터페이스나 추상 클래스에 정의된 추상 메서드를 호출하는 경우

인터페이스 또는 추상 클래스로 선언하고 구현/상속받은 하위 클래스의 인스턴스를 생성.

컴파일러가 알고 있는 타입에 대한 정보를 토대로 런타임 시 해당 타입의 객체를 생성하고 메서드 호출.

public abstract class Job{
	abstract void printJob();
}

public class Student extends Job{
	@Override
    public void printJob(){
    	System.out.println("Student");
    }
}

public class Teacher extends Job{
	@Override
    public void printJob(){
    	System.out.println("Teacher");
    }
}

public class DynamicDispatch{
	public static void main(String[] args){
   		Job student = new Student();
        Job teacher = new Teacher();
        
        student.printJob();
        teacher.printJob();
        
    }
}

Job 클래스를 상속받는 두 클래스 Student, Teacher 가 있다.

이 두 클래스를 Job타입의 객체로 만들었다.

그리고 두 객체의 printJob()을 실행시킨다.

이때, main메서드 안의 student.printJob(), teacher.printJob()은 어떤 클래스의 printJob()을 실행하는지 알까?

애초에 Job이라는 abstract 클래스의 메서드로 접근하는데 컴파일 타임에 하위 타입의 printJob()의 구현을 고려할까?

위 코드의 바이트코드 // 출처 : alkhwa.tistory.com

아래의 Job.printJob이 같은 것으로 알 수 있듯이, 바이트코드 상에서는 Job타입의 printJob()을 실행함을 알 뿐

어떤 클래스의 메서드인지는 모른다.

 

어떤 메서드를 실행시킬까는 런타임에 결정하게 되는 것이다. 이를 동적 디스패치라 한다.

 

Job teacher = new Teacher(); 라고 객체를 생성하면 클래스에서 this라는 자기 자신 객체의 정보를 담은 참조자를

가지는 것처럼 receiver parameter라는 자기 객체의 정보를 담은 인자를 같이 가지고 Job이라는 타입에 저장하게 된다.

 

런타임에 메서드를 호출할 대 이 reciver parameter를 보고 어떤 객체인지 판단 후 그 클래스의 메서드를 호출한다.

 

/*

Double Dispatch : Dynamic dispatch를 두 번 하는 것을 의미

방문자 패턴(Visitor Pattern) - 여러 객체에 대해 각 객체의 동작들을 지정하는 패턴(N:N)

 일반적으로 OOP는 객체가 스스로 행위를 수행하게 하지만, 경우에 따라(ex.확장성고려, OCP위배) 객체의 

행위 수행을 외부 크래스에 위임, 이때 사용하는 디자인 패턴 종류는 전략패턴, 커맨드 패턴, 방문자 패턴

*/ 여기 개념은 좀 이해가 안돼서 추가 공부 더 필요...

 


#추상 클래스

추상 메서드(abstract method)란?

자식 클래스에서 반드시 오버 라이딩해야만 사용할 수 있는 메서드를 의미

자바에서 추상 메서드를 선언하여 사용하는 목적은 추상 메서드가 포함된 클래스를 상속받는

자식 클래스가 반드시 추상 메서드를 구현하도록 하기 위함이다.

 

예를 들면 모듈처럼 중복되는 부분이나 공통적인 부분은 미리 다 만들어진 것을 사용하고, 이를 받아 사용하는 쪽에서는

자신에게 필요한 부분만을 재정의하여 사용함으로써 생산성이 향상되고 배로 등이 쉬워지기 때문

 

추상 메서드는 선언 부만이 존재하며, 구현부는 작성되어 있지 않다.

이 작성되어 있지 않은 구현부를 자식 클래스에서 오버라이딩하여 사용하는 것이다.

추상 메서드는 함수의 선언부에 'abstract'라는 키워드를 붙인다.

 

더보기

abstract 반환타입 메서드이름();

위와 같이 선언부만 있고 구현부가 없다는 의미로 선언부 끝에 바로 세미콜론을 추가한다.

 

추상클래스(abstract class)

자바에서는 하나 이상의 추상 메서드를 포함하는 클래스를 가리켜 추상 클래스(abstract class)라고 한다.

이러한 추상 클래스는 객체 지향 프로그래밍에서 중요한 특징인 다형성을 가지는 메서드의 집합을 정의할 수 있도록 해준다.

즉, 반드시 사용되어야 하는 메서드를 추상 클래스에 추상 메서드로 선언해 놓으면, 이 클래스를 상속받는 모든 클래스에서는

이 추상 메서드를 반드시 재정의해야 한다.

 

추상메서드의 접근 지정자로 private는 지정할 수 없는데 이는 자식 클래스에서 받아서 구현되어야 하므로 당연하다.

다른 접근 지정자(public, protected)는 사용할 수 있고 생략되면 default(즉, 같은 패키지 안에서만 접근 가능)인 것은 일반 클래스와 동일하다.

 

더보기

abstract class 클래스 이름{

     ...

     abstract 반환타입 메서드이름();

     .....

}

추상 클래스는 동작이 정의되어 있지 않은 추상 메서드를 포함하고 있으므로, 인스턴스를 생성할 수 없다.

추상 클래스는 먼저 상속을 통해 자식 클래스를 만들고, 만든 자식 클래스에서 추상 클래스의 모든 추상 메서드를

오버라이딩 하고 나서야 비로소 자식 클래스의 인스턴스를 생성할 수 있게 된다.

 

@추상 클래스는 추상 메서드를 포함하고 있다는 점을 제외하면, 일반 클래스와 모든 점이 같다.

   즉, 생성자와 필드, 일반 메서드도 포함 가능하다.

 

추상 메서드의 사용 목적

자바에서 추상 메서드를 선언하여 사용하는 목적은 추상 메서드가 포함된 클래스를 상속받는 자식 클래스가

반드시 추상메서드를 구현하도록 하기 위함이다.

만약 일반 메서드로 구현한다면 사용자에 따라 해당 메서드를 구현할 수도 있고, 안 할수도 있다.

하지만 추상 메서드가 포함된 추상 클래스를 상속받은 모든 자식 클래스는 추상 메서드를 구현해야만

인스턴스를 생성할 수 있으므로, 반드시 구현하게 된다.(Override와의 차이점)


 

#final 키워드

자바에서 final은 상수를 표현하기 위한 예약어다.

마지막이라는 단어의 뜻 처럼 선언한 그대로 사용하라는 의미고 변수, 메서드, 클래스에서 모두 이용한다.

 

-final 변수

상수라고도 불리며 변수를 선언과 동시에 초기화하며 이후에 값을 수정할 수 없다.

오직 get만 가능

일반적으로 final 변수는 프로그램 전체에 걸쳐 사용되는 경우가 많아서

특정 메서드 내부에서 선언하기보다 클래스에 static 키워드와 함께 정의되어 사용된다.모든 변수 타입(int, double, String 등)에 적용할 수 있으며 폴더 / 파일 이름, DB컬럼명, 사이즈 등의 정보를 표현하는데 유용하다.

 

-final 메서드

오버라이딩이 불가능하다 = 상속 받은 그대로 사용해야 한다.

public class Fruit{
	public String name;
    
    public void setName(String name){
    	this.name = name;
    }
    
    public final String getName(){
    	return name;
    }
}

public class Banana extends Fruit{
	
    @Override
    public void setName(String name){
    	this.name = "Fruit Name : " + name;
    }
    
    public String getName(){
   		return name;
    }
    
    public static void main(String[] args){
   	  // TODO
   	}
}

Fruit클래스에는 name에 대한 set / get메서드가 존재하는데 getName() 메서드 앞에는 final 키워드가 붙었다.

Banana 클래스는 Fruit 클래스를 상속받아 setName() 메서드를 원하는 형태로 오버라이딩 했다.

하지만 getName()을 오버라이딩 한 경우는 에러가 발생한다.

즉, 오버라이딩이 불가능하다는 뜻이다.

 

-final 클래스

상속이 불가능하다 = subclass를 만들 수 없다

public class Fruit{
	public String name;
    
    public void setName(String name){
    	this.name = name;
    }
    
    public final String getName(){
    	return name;
    }
}

public class Banana extends Fruit{
	public static void main(String[] args){
      //TODO
    }
}

final 메서드와 마찬가지로 Banana클래스에서 Fruit 클래스를 상속하려고 하는데

Fruit 클래스는 final 클래스이기 때문에 상속이 불가능하다는 에러가 나타난다.

 

final 메서드와 클래스는 주로 라이브러리의 형태의 프로젝트를 작성할 때 사용된다.

(라이브러리를 완전히 이해하지 못한 상태에서 재정의 한다면 에러가 발생할 확률이 높아지기 때문에

원천적으로 수정이 불가능하도록 막아놓는 것이다)

자신이 작성한 메서드와 클래스를 다른 사람이 상속받아서 사용하지 못하게 금지하고 싶을 때 이용하면 좋다.


#Object 클래스

java.lang 패키지

java.lang 패키지는 자바에서 가장 기본적인 동작을 수행하는 클래스들의 집합이다.

따라서 자바에서는 java.lang 패키지의 클래스들은 import 문을 사용하지 않아도

클래스 이름만으로 바로 사용할 수 있도록 한다.(import java.lang.*;)

 

java.lang.Object 클래스

java.lang 패키지 중에서도 가장 많이 사용되는 클래스는 바로 Object 클래스다.

Object 클래스는 모든 자바 클래스의 최고 조상 클래스다.

(모든 클래스는 Object 클래스를 상속함. 컴파일 과정에서 자동으로 extends Object됨)

따라서 자바의 모든 클래스는 Object클래스의 모든 메서드를 바로 사용할 수 있다.

이러한 Object 클래스는 필드를 가지지 않으며, 총 11개의 메서드만으로 구성되어 있다.

Object 클래스에는 native라고 선언된 것이 있는데, native란 c,c++기능(라이브러리)을 쓰고 싶어서 만들어 진 것이다.

 

toString() 메서드

Object 클래스에서 기본으로 제공하며 toString() 메서드는 해당 인스턴스에 대한 정보를 문자열로 반환한다.

이때 반환되는 문자열은 클래스 이름과 함께 구분자로 '@'가 사용되며, 그 뒤로 16진수 해시코드(hash code)가 추가된다.

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

 

자바에서 toString() 메서드는 기본적으로 각 API 클래스마다 자체적으로 오버라이딩을 통해 재정의 되어있다.

 

toString()메서드는 자동으로 호출된다.

public class Example{
	public static void main(String[] args){
    	String str = "Tjd silver";
        System.out.println(str);
    }
}

위 코드를 보면 "str"이라는 String 클래스의 객체가 있는데 객체임에도 str독단적으로 출력이 가능하다

이 지점에서 toString이 자동으로 호출되는 것을 확인할 수 있다.

 

equals() 메서드

equals() 메서드는 해당 인스턴스를 매개변수로 전달받는 참조 변수와 비교하여, 그 결과를 반환한다.

equals()는 '=='연산자와 동일한 기능을 한다. 같은 객체일 경우에는 true를 리턴하고 객체가 다를 경우 false를 리턴한다.

 

자바에서 equals() 메서드는 기본적으로 각 API 클래스마다 자체적으로 오버라이딩을 통해 재정의 되어있다.

public class ObjectEquals{
	public static void main(String[] args){
    	Object ob1 = new Object();
        Object ob2 = new Object();
        
        System.out.println(ob1 == ob2);
        System.out.println(ob1.equals(ob2));
    }
}

두 출력의 내용은 같으며 클래스는 같지만, 두 인스턴스의 주소 값이 다르기 때문에 실행 결과는 둘 다 false가 출력된다.

 

 

close() 메서드

clone() 메서드는 해당 인스턴스를 복제하여 새로운 인스턴스를 생성해 반환한다. 하지만 Objet 클리스의 clone()메서드는 단지 필드의 값만을 복사하므로, 필드의 값이 배열이나 인스턴스면 제대로 복제할 수 없다.

이러한 경우에는 해당 클래스에서 clone() 메서드를 오버라이딩 하여, 복제가 제대로 이루어지도록 재정의 해야 한다.

 

 

Object 메서드

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

 

 

 

'Back-end > Java_T.I.L' 카테고리의 다른 글

8. 인터페이스  (0) 2022.01.05
7.패키지, import, 클래스패스(classpath)  (0) 2022.01.05
5. 클래스  (2) 2022.01.02
4. 제어문_반복문(for, while, do-while)  (0) 2021.12.31
4. 제어문_선택문(if, else-if, switch)  (0) 2021.12.31

댓글