java

Java_Chapter07_클래스의 관계

강용민 2022. 9. 15. 15:20

클래스 간의 관계를 고민 없이 프로그래밍하게 되면 엄청난 중복 코드의 사용과 지나치게 엄격한 관계 형성으로 유지보수성이 떨어질 수 있다.

그렇기에 상속, 데이터 은닉과 보호, 다형성의 개념을 통해 어떻게 클래스 간의 관계를 맺고 프로그램의 유지보수성을 향상시킬 수 있는지 알아야 한다.

 

상속

상속은 객체지향 프로그래밍의 4개 개념(Abstraction, Polymorphism, Inheritance, Encapsulation) 중 Inheritance에 해당하는 중요한 개념이다.

프로그램에서의 상속은 기존 클래스의 재산을 다른 클래스에서 재사용하기 위한 것이다.

여기서 말하는 재산이란 기존 클래스에 있던 멤버(변수와 메서드)를 이야기한다. 따라서 생성자와 초기화 블록은 상속의 대상이 아니다.

 

상속의 관계에 있어서 물려주는 클래스를 부모클래스라 하며, 상속받는 클래스를 자식 클래스라 한다.

상속받은 클래스는 물려받은 멤버들을 자신의 것처럼 사용할 수 있기 때문에 코드의 절감 효과를 가져오게 된다. 또한, 부모의 코드를 변경하면 모든 자식 클래스에게도 적용되므로 유지보수성 역시 향상된다.

프로그램 코드에서 상속을 적용할 떄는 자식 클래스의 선언부에서 extends 키워드와 함께 조상 클래스 이름을 표시한다.

public class Person{		//부모클래스인 Person클래스
	String name;
    
    public void eat(){
    	System.out.println("밥 먹기");
    }
    
    public void jump(){
    	System.out.println("뛰기");
    }
}

public class SpiderMan Extends Person{		//Person클래스를 상속받은 자식클래스 SpiderMan
	boolean isSpider;
    
    public void fireWeb(){
    	System.out.println("거미줄 발사");
    }
}

위의 코드를 예로 들었을때 Person을 상속받음으로써 SpiderMan이 가질 수 있는 멤버의 개수는 총 5개가 되었다.

즉, 부모 클래스인 Person에서 물려받은 멤버 3개와 자식인 SpiderMan에 선언된 고유 멤버 2개이다.

 

다양한 관계

단일 상속 지원

하나의 클래스를 여러 클래스가 상속받는 것을 전혀 문제가 되지 않지만 반대로 하나의 클래스가 여러 개의 클래스를 상속받을 수 없다.그 이유는 프로그램의 복잡도를 줄이기 위해서이다.

대신 이런 단점을 극복하기 위해 interface와 포함 관계라는 것을 사용한다.

 

포함관계

포함 관계는 연관 관계 또는 'has a'관계로 불리며 상속 이외에 클래스를 재활용하는 방법이다.

두 개 이상의 클래스에서 멤버를 물려받고 싶으나 단일 상속 지원 정책으로 하나만 상속할 수 있기 때문에 나머지 클래스들은 멤버 변수로 처리해서 사용하는 것이 포함관계다.

public class Person{		//부모클래스인 Person클래스
	String name;
    
    public void eat(){
    	System.out.println("밥 먹기");
    }
    
    public void jump(){
    	System.out.println("뛰기");
    }
}

public class Spider{
	public void jump(){
    	System.out.println("키*1000만큼 엄청난 점프");   
    }
    
    public void fireWeb(){
    	System.out.println("거미줄 발사");
    }
}

public class SpiderMan2 extends Person{	//상속관계
	Spider spider = new Spider();	//포함관계
    boolean isSpider;
    
    public void fireWeb(){
    	if(isSpider){
        	spider.fireWeb();
        }esle{
        	System.out.println("Person은 거미줄 발사 불가");
        }    
    }
}

 

메서드 오버라이딩

상속을 통해서 부모의 메서드들을 물려받았지만 부모 클래스에 정의된 기능을 자식 클래스에 적합하게 수정해서 재정의하는 것을 메서드 오버라이딩(overriding)이라 한다.

메서드 오버로딩(overloading)과는 다르다. 오버 로딩이 추가 적재(메서드 추가)라면 오버라이딩은 기존 메서드 재정의 개념으로 기존 메서드 위에 덮어쓴다.

 

메서드 오버라이딩은 총 5개의 규칙을 갖는다.

  • 메서드 이름은 조상 클래스의 메서드 이름과 같아야 한다.
  • 매개변수의 개수, 타입, 순서는 조상 클래스의 메서드와 같아야 한다.
  • 리턴 타입은 조상 클래스의 메서드와 같아야 한다.
  • 접근 제한자는 조상 클래스의 메서드보다 범위가 같거나 넓어야 한다.
  • 조상 클래스의 메서드보다 더 상위의 예외를 던질 수는 없다.

super 키워드

조상의 멤버를 참조하는 super

this를 통해 객체의 멤버에 접근할 수 있었듯이 super를 통해서 조상의 멤버에 접근할 수 있다.

this가 현재 객체를 참조한다면 super는 조상 객체를 참조한다.

public class Person{		//부모클래스인 Person클래스
	String name;
    
    public void eat(){
    	System.out.println("밥 먹기");
    }
    
    public void jump(){
    	System.out.println("뛰기");
    }
}

public class Spider{
	public void jump(){
    	System.out.println("키*1000만큼 엄청난 점프");   
    }
    
    public void fireWeb(){
    	System.out.println("거미줄 발사");
    }
}

public class SpiderMan2 extends Person{	//상속관계
	Spider spider = new Spider();	//포함관계
    boolean isSpider;
    
    public void fireWeb(){
    	if(isSpider){
        	spider.fireWeb();
        }esle{
        	System.out.println("Person은 거미줄 발사 불가");
        }    
    }
    
    @Override
    public void jump(){
    	if(isSpider){
        	spider.jump();
        }else{
        	super.jump();
        }
    }
}

위 코드를 보면 SpiderMan2클래스에서 Person클래스으로부터 상속받은 jump메서드를 오버라이드한것을 볼 수 있다.

Spider일 경우 포함관계로 생성된 Spider객체의 jump메서드를 실행시키지만, Person일 경우 super의 jump메서드 즉, Person의 jump메서드가 실행되게 오버라이드했다.

 

super() - 조상 클래스의 생성자

this()와 마찬카지로 super() 역시 생성자이다.

this()는 같은 클래스의 다른 생성자를 호출하는 데 사용되지만, super()는 조상 클래스의 생성자를 호출하는데 사용된다.

자손 클래스의 인스턴스를 생성하면, 자손의 멤버와 조상의 멤버가 모두 합쳐진 하나의 인스턴스가 생성된다.

그래서 자손 클래스의 인스턴스가 조상 클래스의 멤버들을 사요할 수 있는 것이다.

이 때 조상 클래스 멤버의 초기화 작업이 수행되어야 하기 떄문에 자손 클래스의 생성자에서 조상 클래스의 생성자가 호출되어야 한다.

Object클래스를 제외한 모든 클래스의 생성자는 첫 줄에 반드시 자신의 다른 생성자 또는 조상의 생성자를 호출해야 한다. 그렇지 않으면 컴파일러는 생성자의 첫 줄에 super();를 자동적으로 추가할 것이다.

public class PointTest {
    public static void main(String args[]){
        Point3D p3 = new Point3D(1,2,3);
    }
}

class Point{
    int x;
    int y;

    Point(int x, int y){
        this.x = x;
        this.y = y;
    }

    String getLocation() {
        return "x :" + x + ", y :" + y;
    }
}

class Point3D extends Point{
    int z;
    Point3D(int x, int y,int z) {
        super(x,y);
        this.x = x;
        this.y = y;
        this.z = z;
    }

    String getLocation(){
        return "x :"+x+", y :"+y+", z :" + z;
    }
}

 

제어자

제어자란 클래스, 변수, 메서드의 작성 시 함께 사용돼서 부가적인 의미를 부여해주는 키워드이다.

제어자의 종류는 크게 2가지로 나눌 수 있다.

  • 접근 제어자 : 접근 제어자는 멤버 변수 등을 사용할 수 있는 범위를 지정하는 키워드로 public, protected, (default = package), private의 네 가지 종류가 사용된다.
  • 그 외의 제어자 : static, final, abstract, synchronized 등으로 각각 제어자별로 특별한 목적이 있다.

하나의 대상에 여러 제어자를 조합해서 사용할 수 있으나 접근 제어자는 하나만 사용할 수 있다.

 

final

final 제어자는단어 뜻 그대로 마지막, 즉 더는 바뀔 수 없음을 말한다. final은 클래스, 메서드, 변수에 사용된다.

final이 선언된 클래스는 더는 확장할 수 없다. 즉, 상속받거나 오버라이드를 할 수 없다.

final class PerfectClass{}

//The type FinalClassTest cannot subclass the final class PerfectClass
public class FinalClassTest extends PerfectClass{}

class ParentClass{
	final int pi = 3.14;
	public final void finalMehod() {}
}

public class FinalClassTest2 extends ParentClass{
	//The final local variable pi cannot be assigned.
    super.pi = 2.34;
    
	//Cannot override the final method from ParentClass.
    @Override
    public void finalMethod(){}
}

 

abstract

메서드의 선언부만 작성하고 실제 수행내용은 구현하지 않은 추상 메서드를 선언하는데 사용된다.

대상은 클래스와 메서드로 나뉘어 사용할 수 있다.

추상 클래스는 아직 완성되지 않은 메서드가 존재하는 '미완성 설계도'이므로 인스턴스를 생성할 수 없다.

 

제어자(modifier)의 조합

지금까지 접근 제어자와 static, final, abstract에 대해서 학습했다.

제어자가 사용될 수 있는 대상을 중심으로 제어자를 정리해보았다. 제어자의 기본적인 의미와 그 대상에 따른 의미 변화를 다시 한 번 되새겨 보도록 하자.

대상 사용가능한 제어자
클래스 public, (default), final, abstract
메서드 모든 접근 제어자, final, abstract, static
멤버변수 모든 접근 제어자, final, static
지역변수 final

마지막으로 제어자를 조합해서 사용할 때 주의해야 할 사항에 대해 정리해 보았다.

  • 메서드에 static과 abstract를 함께 사용할 수 없다.
    • static메서드는 몸통이 있는 메서드에만 사용할 수 있기 떄문이다.
  • 클래스에 abscract와 final을 동시에 사용할 수 없다.
    • 클래스에 사용되는 final은 클래스를 확장할 수 없다는 의미이고 abstract는 상속을 통해서 완성되어야 한다는 의미이므로 서로 모순되기 때문이다.
  • abstract메서드의 접근 제어자가 private일 수 없다.
    • abstract메서드는 자손클래스에서 구현해주어야 하는데 접근 제어자가 private이면, 자손 클래스에서 접근할 수 없기 때문이다.
  • 메서드에 private과 final을 같이 사용할 필요는 없다.
    • 접근 제어자가 private인 메서드는 오버라이딩될 수 없기 때문이다. 이 둘 중 하나만 사용해도 의미가 충분하다.

 

'java' 카테고리의 다른 글

Java_Chapter08_객체 지향 4대 요소  (0) 2022.10.09
Java_Chapter?_builder pattern  (0) 2022.10.06
Java_Chapter06_클래스와 객체  (0) 2022.09.14
Java_Chapter05_배열  (0) 2022.09.06
Java_Chapter01~02_자바특징 및 변수  (0) 2022.09.05