Java/Java

객체지향 프로그래밍 심화(상속, 캡슐화)

마손리 2023. 2. 28. 11:23

 

상속화

상속화란 기존의 클래스를 재활용하여 새로운 클래스를 작성하는 자바의 문법요소이다. 보통 부모(상위), 자식(하위) 클래스로 나누어 부모클래스의 멤버(필드, 메서드, 이너클래스)를 자식 클래스와 공유하는 것을 의미힌다. 이때 이 두 클래스를 상속 관계라고 하며, 자식 클래스는 부모 클래스가 가진 모든 멤버를 상속 받는 상태라고 한다.

 

또한, 자식 클래스는 하나의 부모 클래스만 가질수 있지만 부모 클래스는 여러 자식 클래스를 가질수 있다. 상속 방법으로는 자식 클래스 이름 옆에  extends  키워드를 사용 하여 부모 클래스로부터 멤버들을 상속 받는다.

 

class Person {
    String name;
    int age;

    void learn(){
        System.out.println("공부를 합니다.");
    };
}

class Programmer extends Person { // Person 클래스로부터 상속. extends 키워드 사용 
    String companyName;

    void coding(){
        System.out.println("코딩을 합니다.");
    };
    void learn(){
        System.out.println("자바를 공부를 합니다.");
    };    
}

class Dancer extends Person { // Person 클래스로부터 상속
    String groupName;

    void dancing(){
		System.out.println("춤을 춥니다.");
	};
}

위의 예제와 같이 부모 클래스인 Person을 자식 클래스인 Programmer와 Dancer가  extends  키워드를 사용하여 상속받았다. 이후 Programmer 또는 Dancer인스턴스화 하면 부모 클래스의 멤버들에 접근이 가능하다.

 

 

메서드 오버라이딩(Method Overriding)

위의 예제를 보면 클래스 PersonProgrammer 클래스안에 동일한 메소드learn()선언되었는데 이처럼 부모와 자식 클래스안에 같은 메소드가 있을경우 자식 클래스의 메소드가 실행되며 이것을 메서드 오버라이딩(Method Overriding)이라 한다.

 

상위 클래스의 메서드를 오버라이딩 하려면 세가지 조건반드시 만족 시켜야 한다.

  1. 메서드의 선언부(메서드 이름, 매개변수, 반환타입)이 상위 클래스의 메서드와 완전히 일치해야 한다.
  2. 접근 제어자의 범위가 상위 클래스의 메서드보다 같거나 넓어야 한다.
  3. 예외는 상위 클래스의 메서드보다 많이 선언할 수없다.

 

 

포함 관계

포함(composite)이란 상속처럼 클래스를 재사용할 수 있는 방법으로, 클래스의 멤버로 다른 클래스 타입의 참조변수를 선언하는 것 이다.

public class Main {
    public static void main(String[] args){
        Address address = new Address("서울","한국");
        Employee employee = new Employee(1,"Mason", address);
        System.out.println(employee.address.city);
    }
}

class Employee {
    int id;
    String name;
    Address address;

    public Employee(int id, String name, Address address) {
        this.id = id;
        this.name = name;
        this.address = address;
    }
    
}

class Address {
    String city, country;

    public Address(String city, String country) {
        this.city = city;
        this.country = country;
    }
}

상속과는 다르게 Employee 클래스의 참조변수에 Address라는 클래스를 선언해주고 미리 생성된 Address 인스턴스를 이용하여 Employee의 인스턴스의 address 값을 초기화 해줄수 있다. 또한 Employee 인스턴스를 이용하여 자신에게 할당된 address 인스턴스에 접근할수 있다.

 

보통 클래스 간의 관계를 설정하는 데 있어서 상속관계를 맺어 줄 것 인지 포함 관계를 맺어 줄 것인지를 판별 할 기준은 클래스 간의 관계가 "~은 ~이다(IS-A)"관계일 경우 상속관계를, "~은 ~을 가지고 있다.(HAS-A)"관계 일경우 포함관계가 적합하다.

 

 

super와 super()

"super와 super()" "this와 this()" 와 마찮가지로 super 키워드는 상위클래스의 멤버에 접근할때, super() 메소드는 상위클래스의 생성자를 호출할때 사용된다.

 

public class Main {
    public static void main(String[] args) {
        SubClass subClassInstance = new SubClass(10);	//인자값을 자식클래스의 생성자로 전달한뒤
        						//super()를 통해 부모클래스의 생성자로 전달
        subClassInstance.callNum();			
    }
}

class SuperClass {
    int count;	//super.count
    SuperClass(int count){	//자식 클래스에서 super()를 이용하여 인자값을 받음
        this.count = count;	//여기서 this는 부모클래스인 자신의 인스턴스를 의미함
    }
}

class SubClass extends SuperClass {
    int count = 15; // this.count
    SubClass(int count) {
        super(count);	//super()를 이용하여 부모클래스의 생성자로 인자값 전달
    }

    void callNum() {
        System.out.println("count = " + count);
        System.out.println("this.count = " + this.count);
        //여기서 this는 자식클래스의 인스턴스를 의미
        System.out.println("super.count = " + super.count);
        //super 키워드를 통해 부모클래스의 멤버에 접근할수 있다.
    }
}
//출력값
count = 15
this.count = 15
super.count = 10

this()와 마찬가지로 super()메서드 또한 모든 생성자의 첫줄에 선언되어야 하며 이 경우 this() 즉, 생성자 오버로드는 불가능 하다.

 

  멤버에 접근 생성자에 접근
자신의 클래스 this this()
부모의 클래스 super super()

 

 

Object 클래스

오브젝트 클래스는 자바의 클래스 상속 계층도에서 최상위에 위치한 부모클래스이며 자바의 모든 클래스는 오브젝트 클래스로부터 확장된다. 

기존에 다른 클래스로부터 아무런 상속을 받지 않은 클래스는 자동적으로 extends Object를 상속받게 된다.

class ParentEx {  //  컴파일러가 "extends Object" 자동 추가한 것임
}

class ChildEx extends ParentEx {
}

이로인해 우리는 자동적으로 Object 클래스의 메서드들을 사용할수 있으며 아래의 표는 Object 클래스의 대표적인 메서드들이다.

 

메서드명 반환타입 주요내용
toString() String 객체 정보를 문자열로 출력
equals(Object obj) boolean 등가 비교 연산(==)과 동일하게 스택 메모리값을 비교
hashCode() int 객체의 위치정보 관련. Hashtable 또는 HashMap에서 동일 객체여부 판단
wait() void 현재 쓰레드 일시정지
notify() void 일시정지 중인 쓰레드 재동작

 

 

캡슐화

캡슐화특정 객체 안에 관련된 속성과 기능을 하나의 캡슐(capsule)로 만들어 데이터를 외부로부터 보호하는 것이다.

캡슐화의 이유로는 첫번째로 데이터 보호의 목적이고, 두번째로 내부적으로만 사용되는 데이터에 대한 불필요한 외부 노출을 방지 하기 위해서 이다.

 

즉, 캡슐화의 가장 큰 장점정보 은닉이라 할수 있으며 이로 인해서 외부로부터 인스턴스의 필드와 메서드를 함부로 변경하지 못하게 막고, 데이터가 변경되더라도 다른 객체에 영향을 주지 않게하여 객체간의 독립성을 보존하게 해줍니다.

 


Package와 Import

패키지(package)

패키지란 물리적인 하나의 디렉토리(directory)이며 이 하나의 패키지에 안의 클래스나 인터페이스 파일은 모두 해당 패키지에 속해 있다.

 

또한 이 디렉토리는 트리구조의 형태를 가지고 있는데 각 계층 간 구분은 점(.)으로 표현한다.

 

패키지가 있는 경우 소스코드의 첫 번째 줄에는 반드시  package 패키지명  이 표시되어야 하며, 만약 패키지 선언이 없으면 이름 없는 패키지에 속하게 된다. 예를 들면 src 폴더가 이 이름없는 패키지에 속하며 다른 패키지에서 import가 불가능하다.

package test.test2;
// 최종목적지는 해당 패키지의 directory이다.

패키지 사용의 장점으로는 같은 패키지에 속한 클래스들은 서로 연결이 용이하며 다른 패키지의 클래스들간의 충돌을 방지해 준다.

예를 들어, 같은 이름의 클래스를 가지고 있더라도 각각 다른 패키지에 속해 있다면 이름명으로 인한 충돌이 발생하지 않는다.

 

임포트(import)

임포트는 다른 패키지 내의 클래스를 사용하기 위해 사용하며 일반적으로 패키지 구문 다음에 작성한다.

import testPackage.testClass;
//혹은
import testPackage.*;

// import 패키지명.클래스명; 또는 import 패키지명.*;
// 임포트의 경우 최종 목적지는 클래스가 된다.

사용법은 패키지와 비슷하지만 경로의 마지막은 패키지가 아닌 클래스가 온다. 또한 (*)를 사용할경우 해당 패키지의 모든 클래스를 임포트해온다.

 

참고로 임포트문은 컴파일 시에 처리가 되므로 프로그램의 성능에는 영향을 주지 않는다.

 

 

접근 제어자

제어자

제어자는 클래스 및 클래스의 멤버들 그리고 생성자 등에 부가적인 의미를 부여하는 "키워드"이다.

 

자바에서 사용하는 제어자접근 제어자기타 제어자로 구분되며 아래와 같은 제어자들을 사용할수 있다.

접근 제어자 public, protected, (default), private
기타 제어자 static, final, abstract, native, transient, synchronized 등

기타 제어자의 경우 여러 제어자들이 사용가능하지만 접근 제어자의 경우 단 하나의 제어자만을 사용할 수 있다.

 

접근 제어자

접근 제어자는 4가지로 구성되있으며, 그 이름 그대로 접근에 제한을 두어 클래스 외부로 부터 데이터를 보호 할수 있다.

접근 제어자 접근 제한 범위
private 동일 클래스에서만 접근 가능
default 동일 패키지 내에서만 접근 가능
protected 동일 패키지 + 다른 패키지의 하위 클래스에서 접근 가능
public 접근 제한 없음
접근 제어자 클래스 내  패키지 내 다른 패키지의
하위 클래스
패키지 외
private O X X X
default O O X X
protected O O O X
public O O O O

public(접근 제한 없음) > protected(동일 패키지 + 하위클래스) > default(동일 패키지) > private(동일 클래스) 순으로 정리 할수 있다. 

 

default의 경우 아무 접근 제어자가 없는 경우 자동으로 default 접근 제어자가 설정된다.

 

 

getter와 setter 메서드

접근 제어자를 사용하여 클래스 안의 데이터를 보호하면서 그 데이터를 변경하기 위해 사용되는것이 gettersetter 메서드 이다. 

package PT;

public class Member {
    private String name;
    private int age;
    private PersonalTraining pt;

    public Member(String name, int age, PersonalTraining pt) {
        this.name = name;
        this.age = age;
        this.pt = pt;
    }

    void dailyRoutine(){
        //pt.pushUp; private 접근 제어자로 인해 사용불가
        System.out.printf(
                "팔굽혀펴기와 턱걸이를 각각 &d, %d회 씩 총 %d세트 반복합니다.",
                pt.getPushUp(),
                pt.getChinUp(),
                pt.getNumberOfSet()
        );
    }
}
package PT;

public class PersonalTraining {
    private int pushUp;
    private int chinUp;
    private int numberOfSet;

    public PersonalTraining(int pushUp, int chinUp, int set){
        this.pushUp = pushUp;
        this.chinUp = chinUp;
        this.numberOfSet = set;
    }

    void setNumberOfSet(int numberOfSet) {
        this.numberOfSet = numberOfSet;
    }

    int getNumberOfSet() {
        return numberOfSet;
    }

    int getChinUp() {
        return chinUp;
    }

    int getPushUp() {
        return pushUp;
    }
}

같은 패키지 내에 MemberPersonalTraining이라는 클래스들을 만들어 준후 Member 클래스에 PersonalTraining 클래스를 포함 시켜 주었다.

 

이때 PersonalTraining 클래스의 전역 변수들의 접근제어자가 본인 클래스만 접근가능한 private으로 설정 되어 있으므로 Member클래스에서 해당 데이터들을 사용하기 위해 gettersetter 메소드들을 이용하여 PersonalTraining 클래스의 전역 변수에 접근이 가능하도록 설정해주었다. (Windows, Intellij사용시, Alt+Insert로 getter와 setter 메소드 생성)

 

위의 코드처럼 PersonalTraining의 필드를 수정해 주게되면 Member 클래스의 메소드또한 수정해 주어야되는데 이런경우를 캡슐화를 위반한다 라고한다.

 

이를 해결하기 위해 다음과 같이 코드를 변경해준다.

package PT;

public class Member {
    private String name;
    private int age;
    private PersonalTraining pt;

    public Member(String name, int age, PersonalTraining pt) {
        this.name = name;
        this.age = age;
        this.pt = pt;
    }

    void dailyRoutine(){
        pt.dailyRoutine();
    }
}
package PT;

public class PersonalTraining {
    private int pushUp;
    private int chinUp;
    private int numberOfSet;

    public PersonalTraining(int pushUp, int chinUp, int set){
        this.pushUp = pushUp;
        this.chinUp = chinUp;
        this.numberOfSet = set;
    }

    void setNumberOfSet(int numberOfSet) {
        this.numberOfSet = numberOfSet;
    }

    private int getNumberOfSet() {
        return numberOfSet;
    }

    private int getChinUp() {
        return chinUp;
    }

    private int getPushUp() {
        return pushUp;
    }
    void dailyRoutine(){
        System.out.printf(
                "팔굽혀펴기와 턱걸이를 각각 &d, %d회 씩 총 %d세트 반복합니다.",
                getPushUp(),
                getChinUp(),
                getNumberOfSet()
        );
    }
}

위와 같이 PersonalTraining클래스의 gettersetter 메소드들에 private 접근 제어자를 적용 시킨뒤 해당클래스 다른 메소드(default)에 코드를 작성한뒤 Member클래스에서 생성된 메소드만을 호출해주게되면 추후 데이터가 변경이 되더라도 PersonalTraining클래스의 코드들만 수정해주면 되게 된다.