상속과 캡슐화

주8·2023년 1월 9일
0

상속


상속(inheritance)의 필요성

아래의 클래스들을 보면 각 클래스마다 동일한 의미를 가지는 멤버(변수, 메소드)들이 존재한다.
name, address, salary 변수, receivesPay(), workd() 메서드
이런 공통의 의미를 가지는 멤버를 필요한 클래스마다 중복해서 정의를 해야 한다면?

Untitled


아래의 그림과 같이 공통의 멤버를 하나의 클래스로 정의하여 공유하도록 한다면 어떨까?
Menager와 CSR 클래스가 Employee를 클래스를 상속하여 Employee 클래스의 멤버를 공유한다.

Untitled


상속이란?

공통의 멤버들을 하나의 클래스에 정의하여, 다른 클래스들이 공유하도록 하는 것

  • 두 클래스를 조상과 자손의 관계로 맺어주는 것이다.
  • 상속해주는 클래스: super class, parent class, 상위 클래스라고 한다.
  • 상속받는 클래스: sub class, child class, 하위클래스, 파생클래스라고 한다.
  • 자손은 조상의 모든 멤버(변수, 메서드)를 상속 받는다(생성자, 초기화 블록은 제외).
  • 위의 용어처럼 부모가 가진 것을 자식이 물려받는다는 의미로 상속이라 할 수 있다.
  • 공통 부분은 조상이 관리하고 각 자손들은 자신들이 정의한 멤버만 관리한다.
  • 조상의 변경 사항은 자손에 영향을 미치지만, 자손의 변경은 조상에게 영향을 미치지 않는다.
  • Java의 상속 기본 개념은 기존 클래스를 기반으로 하는 새로운 클래스를 만들고, 상위 클래스의 메서드와 필드를 재사용할 수 있다는 것이다.

Untitled


is a 관계?

  • 상속을 하기 위해서는 is a 관계가 성립되어야 한다.
  • A is a B ⇒ manager is an employee

단일 상속(Single inheritance)

Java에서는 클래스간의 상속관계를 정의하기 위해 extends라는 키워드를 사용한다.
오직 하나의 클래스만 상속할 수 있다 하여 single inheritance라 한다.

  • 상속 외에 다른 클래스 사용이 필요한 경우 멤버변수로 다른 클래스를 선언하여 사용하는데 이것을 포함관계(Composition)라 한다(has a 관계).
  • Syntax
    <modifier> class <name> [extends <superclass>]{
    클래스 구현부
    }

상속불가와 제외 멤버

  • 상위클래스의 생성자는 하위클래스로 상속되지 않는다.
    • 상위클래스의 생성자는 명시적으로 호출하지 않을 땐 컴파일러가 자동으로 상위클래스의 생성자(예:super())를 호출하는 구문을 만들어서 컴파일 하게 된다.
    • 그렇기 때문에 모든 클래스는 초기화 전에 상위클래스의 생성자를 호출하게 된다.
  • 상속 제외 멤버
    • public, protected, default(동일 패키지내)로 정의된 멤버는 모두 하위클래스에 상속된다.
    • private 정의된 멤버는 정의된 클래스에서만 접근 가능하기 때문에 하위클래스 상속되지 않는다.
  • 접근 범위
ModifierSame ClassSame PackageSubclassUniverse
publicyesyesyesyes
protectedyesyesyes
defaultyesyes
privateyes

업캐스팅과 다운캐스팅

  • 상속관계에서 상위 및 하위 객체는 유형 변환이 가능하다.
  • 업캐스팅(Upcasting)
    • 하위 객체가 상위 클래스 객체로 형변환 되는 것
    • 업캐스팅으로 상위 크래스의 변수와 메서드에 쉽게 접근할 수 있다.
    • 암묵적 또는 명시적으로 수행할 수 있다.
    • 하위 객체에서 상위 객체의 변수와 메서드에 접근할 수 있다.
    • 예시) Parent p = new Child();
  • 다운캐스팅
    • 상위 객체의 참조가 하위 클래스로 전달된다.
    • 명시적으로 캐스탱해줘야 한다.
    • 다운캐스팅을 통해 상위, 하위 객체의 메서드와 변수에 접근할 수 있다.
    • 예시) Parent p = new Child();
      Child c = (Child)p;
class CastingParent{
	String name;
	void printData(){
		System.out.println("상위클래스 메서드");
	}
}
class CastingChild extends CastingParent{
	int age;
	void printData(){
		System.out.println("하위클래스 메서드");
	}
}
public class UpCastingEx{
	public static void main(String[] args){
		CastingChild child1 = new CastingChild();
		CastingParent parent1 = child; //업캐스팅
		System.out.println(parent1.name);
		//System.out.println(parent1.age); //업캐스팅되면 접근 불가
	
		//다운캐스팅되면 상위, 하위클래스 모두 접근 가능
		CastingParent parent2 = new CastingChild();
		CastingChild child2 = (CastingChild)parent2; //다운캐스팅
		System.out.println(child2.name);
		System.out.println(child2.age);
	}
}

class Parent{
	void parent(){
		System.out.print("부모");
	}
	void print(){
		System.out.println("입니다.");
	}
}

class Child extends Parent{
	void child(){
		System.out.print("자식");
	}
	@Override
	void print(){
		System.out.println("이에요.");
	}
}

public static void main(String[] args){
	Parent p = new Parent(); //부모 -> 자식에 접근 불가능
	p.parent();
	p.print();
	//결과: 부모입니다.
	p = new Child(); //업캐스팅 -> 자식에 접근 가능한 부모(오버라이딩된 자식 메서드 접근)
	p.parent();
	p.print();
	//결과: 부모이에요.
	Child c = (Child) p; //다운캐스팅 -> 자식(부모에 접근 가능)
	//Child c = (Child) new Parent();
	c.child();
	c.print();
	//결과: 자식이에요.
}
부모업캐스팅자식다운캐스팅
자식에 접근 x자식에 접근 가능한 부모부모에 접근 가능한 자식부모에 접근 가능한 자식
Parent p = new Parent();Parent p = new Child();Child c = new Child();Child c = (Child) p;
오버라이딩 상관 x오버라이딩된 자식 메서드 호출오버라이딩된 자식 메서드 호출오버라이딩된 자식 메서드 호출
오버라이딩 상관 x오버라이딩 안 된 부모 변수/메서드 호출오버라이딩 안 된 부모/자식 변수/메서드 호출오버라이딩 안 된 부모/자식 변수/메서드 호출
부모 변수/메서드만 호출동명이면 부모 변수 호출(메서드는 오버라이딩 됨)동명이면 자식 변수 호출동명이면 자식 변수 호출

java.lang.Object class

  • Object는 모든 클래스의 최상위 클래스다.
  • 하위 클래스에 물려줄 보편적인 특징이 있어야 한다.
  • API를 보면 java.lang 패키지에 있는 Object 클래스는 어떤 클래스도 상속하고 있지 않다.
    하지만 다른 모든 클래스들은 이 Object클래스를 상속 받고 있다.
  • extends라는 keyword를 사용하여 Object 클래스를 상속하지 않더라도 자동적으로 java.lang.Object 클래스가 상속되었다고 JVM이 인식하고 있으므로 생략해도 된다.
  • java.lang.* package는 따로 import문으로 코딩하지 않아도 JVM이 자동 인식한다.
  • Object type의 변수는 null, 클래스의 인스턴스, 배열 등 어떤 객체라도 참조할 수 있다.

Object class의 메서드

  • toString: 객체를 나타내는 String값을 리턴
  • equals: 객체의 동등성을 정의하며, 참조를 비교하는 것이 아니고 값을 비교하는 것을 의미
  • clone: 객체를 복제하기 위해서 사용
  • wait, notify, notifyAll: Thread를 사용하는 병행 프로그램에서 사용
  • finalize: 객체가 제거되기 직전에 수행

super keyword

  • super는 하위클래스에서 상위클래스를 참조할 때 사용하는 키워드이다.
  • super는 하위클래스에서 상위클래스의 변수 및 메소드 즉, 멤버를 참조할 때 이용한다.
    super.변수명; / super.메소드명(); / super()
    (상위클래스의 기본생성자를 호출)
  • super라는 키워드를 사용해도 상위클래스의 private 멤버는 접근이 불가능하다.
public class Employee{
	private String name;
	private double salary;
	private Date birthDate;

	public String getDetails(){
		return "Name: " + name + "\nSalary: " + salary;
	}
}

public class Manager extends Employee{
	private String department;

	public String getDetails(){
		//call parent method
		return super.getDetails() + //상위클래스의 메소드 호출(코드의 재사용)
					"\nDepartment: " + department;
	}
}
  • super(), super(argument list)로 상위클래스의 생성자를 호출할 수 있다.
public class Manager extends Employee{
	public String department;
	public Manager(String name, double salary, String dept){
		super(name, salary);
		department = dept;
	}
	public Manager(String name, String dept){
		super(name);
		department = dept;
	}
	public Manager(String dept){
		super();
		department = dept;
	}
	public Manager(){}
	public String getDetails(){
		return super.getDetails() +
					"\nDepartment: " + department;
	}
}
  • super.메서드명() 형식으로 상위클래스 메서드를 호출할 수 있다.
class Animal{
	public void display(){
		System.out.println("나는 동물이다");
	}
}

class Dog extends Animal{
	@Override
	public void display(){
		System.out.println("나느 개다");
	}
	public void printMessage(){
		display();
		super.display();
	}
}

class Main{
	public static void main(String[] args){
		Dog dog1 = new Dog();
		dog1.printMessage();
	}
}

상위 클래스의 생성자 호출

  • 하위클래스의 생성자의 첫 라인에는 상위클래스의 생성자를 호출하는 super() 구문이 자동으로 호출된다.
  • 만약 상위클래스의 기본생성자가 아닌 오버로딩한 상위클래스의 생성자를 호출할 경우 명시적으로 호출해주어야 한다.
public class SubClass{
	private int i;
	public SubClass(){
		super(); //묵시적으로 호출된다.
		this.i = 10;
	}
}
  • 만약 super 생성자의 위치를 생성자의 첫 라인이 아닌 곳에 위치시키면 오류가 발생한다.
public class SubClass{
	private int i;
	public SubClass(){
		this.i = 10;
		super(); //super의 constructor는 생성자의 첫 라인에 위치해야 함
	}
}

상속에서의 final keyword

  • final keyword를 클래스나 메소드 정의시 사용할 경우 상속 및 메소드 오버라이딩이 금지된다.
  • final keyword는 상속을 방지하거나 객체의 일관성을 유지하며, 오버라이딩 해서는 안 되는 클래스를 정의해야 할 경우 사용한다.
    예) 절대값을 구하는 수학함수, String 클래스 등
[class]-------------------------------------
final class A {...}
class B extends A {...} //Compile Error 발생

[method]-------------------------------------
class A {
	public final void aaa(){...}
}

class B extends A {
	public void aaa(){...} or public final void aaa(){...} //Compile Error 발생
}
  • 클래스, 메서드, 멤버변수, 지역변수에 사용할 수 있다.
선언 대상의미
클래스상속할 수 없는 클래스를 지정하는 것으로 상속되면 안 되는 클래스에 사용
메서드final로 지정된 메서드는 오버라이딩 불가
멤버/지역 변수상수로 정의돼서 값을 변경할 수 없다.

메서드 오버라이딩

  • 상속받은 클래스의 기능(행동)에 맞게 재정의 하는 것
  • 상위클래스의 메소드를 상속하는 하위클래스들의 메소드(동작)들은 이름은 동일하게 상속받지만 기능은 다르게 동작하도록 해야 하는 경우가 있다.
  • 예를 들자면 Pet과 Dog의 speak() 메소드의 경우 서로 다르게 소리내기 때문이다. 그래서 제공되는 기능이 메소드 오버라이딩이다.
  • 메소드 오버라이딩은 상속 받은 동일한 이름의 메소드를 상속 받는 클래스의 특징에 맞게 재정의하여 동작시키도록 하기 위해 사용한다.
  • Java에서는 상위클래스에서 상속받은 메소드를 하위클래스에서 재정의 했을 경우 상위클래스의 reference type으로 할당된 하위클래스의 object라 하더라도 메소드 호출시 상위 클래스의 메소드가 호출되는 것이 아니라 재정의된 메소드가 호출되도록 하여 다양한 동작을 가능하게 한다. (런타임 다형성)

  • Pet의 speak 메소드를 각 애완동물의 소리에 맞게 재정의하였다.
  • Pet은 일반적인 형태라 소리를 낼 수 없기 떄문에 상속받은 각 Pet에 맞게 소리를 낼 수 있도록 재정의 해주어야 한다.
public class Pet{
	public void speak(){
		System.out.println("애완동물 말하기");
	}
}

class Dog extends Pet{
	@Override
	public void speak(){
		System.out.println("멍멍");
	}
}

class Cat extends Pet{
	public void speak(){
		System.out.println("야옹");
	}
}

class Duck extends Pet{
	public void speak(){
		System.out.println("꽥꽥");
	}
}

메서드 오버라이딩 규칙

  • 인스턴스 메소드여야 한다.
  • 메소드의 이름이 같아야 한다.
  • 매개변수의 개수가 같아야 한다.
  • 매개변수 각각의 Data Type이 일치해야 한다.
  • 메소드의 Return Type이 일치해야 한다(java 5 이전 버전)
    • Java 5 버전부터는 subclass 타입을 return type으로 오버라이딩 가능하다 (Covariant return type: 공변 리턴 타입)
  • 매개변수의 개수나 Data Type이 일치하지 않을 경우, 메소드 오버로딩(method overloading)이다.
  • 상위클래스에 private, final, static으로 정의된 메소드는 오버라이드가 불가능하다.
  • final로 메서드를 선언하는 이유는 메서드의 내용이 외부에서 변경되지 않게 하기 위함이다.
    • (private도 setter를 만들지 않는 이상 외부에서 변경이 안 되지만, 내부에서는 변경이 가능한 반면 final은 (리셋값이 주어진 후에 변경이 안 된다. 그래서 일반적으로 공통으로 쓰는 절대 바뀌지 않는 값은 static final을 선언하고 쓴다)내부에서도 변경이 안 된다.)
  • 오버라이딩된 메서드의 호출은 런타임에 동적으로 결정되는데 이를 동적 다형성(Dynamic Polymorphism)이라고 한다.
class A {
	int m1(int i){}
	int m2(float f){}
	final int m3(int j){} //메서드 오버라이딩 불가
	static void m4(int k){} //메서드 오버라이딩 불가
	private int m5(int g){} //메서드 오버라이딩 불가
	A m6(){}
}

class B extends A{
	int m1(int i){} //메서드 오버라이딩
	int m2(float f1, float f2){} //메서드 오버로딩
	//Covariant return type
	B m6(){} //서로 리턴 타입은 다르지만 메서드 오버라이딩이다.
}

private / static / final

기본적으로 private은 접근제어자로, private이든 public이든 접근제어자를 적지 않으면 default 접근제어자를 쓰는 것이다.

private은 getter, setter를 설정해주지 않으면 외부에서 접근이 안 되지만 내부에서는 접근이 가능하다.

static은 공용으로, 동기화되는 멤버 변수/메서드라고 생각하면 쉽다.
static은 따로 인스턴스를 참조하지 않기 때문에 한 번 변경되면 해당 static 변수를 참조하던 값들이 다 변경된다.

class A{
	static int a;
}

class Test{
	static void main(String[] args){
		System.out.println(A.a); //0(초기값)
		A.a = 1;
		System.out.println(A.a); //1
	}
}

반면에 인스턴스 변수/메서드의 경우, 객체를 참조하기 때문에 (copy를 한다고 생각하면 쉽다) > 해당 객체에 대한 변수 값을 변경하지 않는 이상 다른 객체에 대한 인스턴스 변수값은 변경되지 않는다.

class A{
	int a;
}

class Test{
	static void main(String[] args){
		A a = new A();
		System.out.println(a.a); //0(초기값)
		a.a = 1;
		System.out.println(a.a); //1
		A a1 = new A();
		System.out.println(a1.a); //0
	}
}

final은 한 번 초기화된 값 외에는 내부에서도 변경에 불가하다.
변경하고자 할 때는 초기값을 바꿔야 한다.

class A{
	final int A = 0;

	void edit(){
		A = 1; //ERROR
	}
}

class Test{
	static void main(String[] args){
		A a = new A();
		System.out.println(a.A); //0(초기값)
		a.A = 1; //ERROR
	}
}

Covariant return type(공변 리턴 타입)

  • 공변 리턴 타입(함께 변하는 리턴 타입)은 메서드 오버라이드시 리턴 타입이 오버라이드 메서드 리턴 타입의 하위 타입이 되는 것을 허용하는 것이다.
  • Java 5 버전 이전에는 오버라이드하는 메서드에서 리턴 타입을 변경할 수 없었지만, 5버전 이상에서는 상위클래스의 하위 타입을 선언할 수 있도록 허용하고 있다.
  • 기본 데이터 타입이 아닌 객체 참조 타입만 가능하다.
  • 클래스 계층 구조에서 혼란스러운 타입 캐스팅을 피하는데 도움이 되므로 코드를 읽기 쉽게 유지 관리할 수 있도록 도와준다.
  • 메서드 오버라이드시 더 구체적인 리턴 타입을 가질 수 있는 자유도가 있다.
  • 공변 리턴 타입은 리턴시 런타임 ClassCastException을 방지하는 데 도움이 된다.
class SuperClass{
	SuperClass get(){
		System.out.println("상위클래스 get() 메서드 호출");
		return this;
	}
}

public class CovariantSubClass extends SuperClass{
	//Covariant Return type
	CovariantSubClass get(){
		System.out.println("하위클래스 get() 메서드 호출");
		return this;
	}
	public static void main(String[] args){
		SuperClass tester = new CovariantSubClass(); //업캐스팅
		tester.get();
	}
}

[결과]
하위클래스 get() 메서드 호출
  • Access Modifier를 상위클래스보다 낮은 범위로 변경할 수 없다.
    • 상위클래스가 protected라면, 범위가 같거나 높은 protected나 public으로만 변경 가능
  • 상위클래스보다 많은 수의 예외(Exception)을 선언할 수 없다.
class Parent{
	void parentMethod() throws IOException, SQLException{
		// ...
	}
}

class Child extends Parent{
	void parentMethod() throws IOException{
		// ...
	}
}

class Child2 extends Parent{
	void parentMethod() throws Exception{
		// ...
	}
}

@Override annotation

  • @Override annotation은 java에서 기본으로 제공하는 Built-in annotation이다.
  • 메서드가 오버라이드 되었는지 검증하며, 만약 상위클래스 또는 구현해야할 인터페이스의 메서드를 찾을 수 없다면 컴파일 오류가 발생하여 오류 상황을 대비할 수 있다.
  • Override annotation을 사용하지 않은 상태에서 상속 클래스에서 메서드를 변경하면 하위클래스의 오버라이드 메서드는 추가 메서드로 인식되어 컴파일 오류가 발생하지 않으며, 오타가 발생한 경우에도 컴파일 오류가 발생하지 않는다.
class OverrideAnnoatationParent{
	public int parentMethod(int n1, int n2){
		return n1 + n2;
	}
}

class OverrideAnnotationChild extends OverrideAnnotationParent{
	@Override
	public int parentMethod(int n1, int n2){
		return n1 + n2 + 1;
	}
}

// parentMethod의 메서드명을 parentMethodChange로 변경하면 컴파일 오류 발생
// java: method does not override or implement a method from a supertype

Virtual method invocation

  • 아래의 소스처럼 상위클래스 타입인 Pet의 레퍼런스 변수로 하위클래스의 인스턴스를 할당하여 speak() 메소드를 실행하면 아래와 같이 각 인스턴스의 재정의된 메소드가 호출된다.
public class PolymorphismPetTest{
	public static void main(String[] args){
		Pet pet = new Pet();
		pet.speak(); //애완동물 말하기

		pet = new Dog();
		pet.speak(); //멍멍

		pet = new Cat();
		pet.speak(); //야옹

		pet = new Duck();
		pet.speak(); //꽥꽥
	}
}
  • compile-time에는 어떤 메소드가 호출될지 컴파일러를 알지 못하지만 run-time에 JVM이 어떤 메소드를 호출할지를 결정하는데 이것을 Virtual method invocation 이라 한다.
  • 실행되는 메서드의 버전은 호출시 참조되는 객체의 타입에 따라 결정된다.
  • 다른 용어로는 다이나믹 메서드 디스패치(Dynamic Method Dispatch)라고 해서 재정의된 메서드 호출이 런타임시 동적으로 해결되는 기술로 유연성을 제공한다.
Employee e =new Manager();
Compile-time typeRun-time type

정적 바인딩과 동적 바인딩

  • 바인딩(Binding)이란 프로그램에서 사용되는 변수나 메서드 등 모든 것들이 결정되도록 연결해주는 것
  • 연결이 컴파일 타임에 수행되면 정적 바인딩, 런타임에 동적으로 수행되면 동적 바인딩이라고 한다.
  • 정적 바인딩은 클래스 및 필드의 데이터 타입을 사용하여 메서드 호출을 확인한다.
  • 동적 바인딩은 객체를 사용하여 메서드 호출을 수행한다.
  • 정적메서드는 컴파일 타임에 바인딩 된다.
  • 메서드 하이딩(Method hiding)
    • 하위클래스가 상위클래스의 정적 메서드(Static method)와 동일하게 정적 메서드를 정의하는 경우 하위클래스에 의해 상위클래스의 메서드가 숨겨지는 것을 의미한다.
    • 이는 컴파일 타임에 정적 메서드가 바인딩 되기 때문에 발생한다.
정적 바인딩(Static binding)동적 바인딩(Dynamic binding)
Compile time에 확인된 메서드 호출Run time에 확인된 메서드 호출
메서드 오버로딩메서드 오버라이딩
클래스, 필드 타입 사용객체 사용
private, final, static 엔티티 사용Virtual method(하위 클래스에서 재정의되는 메서드) 사용

메서드 하이딩

기본적으로 하위클래스는 오버라이딩된 하위클래스 메서드와 그 외 상위,하위 클래스의 변수,메서드로의 접근이 가능하다. 하지만 static은 오버라이딩이 되지 않음에도 (동명의 경우) 상위 클래스의 메서드는 숨겨져 하위 클래스의 메서드만 호출된다.

class Parent {
   void instanceMethod(){
       System.out.println("부모");
   }
   static void staticMethod(){
       System.out.println("부모");
   }
   static void staticMethod1(){
       System.out.println("부모");
   }
   void parent(){
       System.out.println("부모");
   }
}

class Child extends Parent{
   @Override
   void instanceMethod() {
       System.out.println("자식");
   }
   static void staticMethod(){
       System.out.println("자식");
   }
   static void staticMethod2(){
       System.out.println("자식");
   }
   void child(){
       System.out.println("자식");
   }
}

class Test{
   public static void main(String[] args){
       Parent p = new Child(); //업캐스팅 ->하위클래스(오버라이딩)접근 가능한 상위클래스
       p.instanceMethod(); //자식 ->오버라이딩
       p.staticMethod(); //부모
       p.parent(); //부모
       p.staticMethod1(); //부모
       System.out.println("--------------");
       Child c = (Child)p; //다운캐스팅 ->하위클래스
       c.instanceMethod(); //자식
       c.staticMethod(); //자식 ->메서드 하이딩
       c.parent(); //부모
       c.child(); //자식
       c.staticMethod1(); //부모
       c.staticMethod2(); //자식
   }
}

예시

int a=1;의 경우 데이터 타입으로 int가 바인딩되는 것은 프로그램을 컴파일할 때 메모리에 할당되므로 정적 바인딩이다.

a라는 변수명 또는 컴파일 할 때 메모리에 할당되므로 정적 바인딩이다.

하지만 1은 실행 시에 값으로 할당되므로 동적 바인딩이다.


특징

정적 바인딩

  • 정적 바인딩은 컴파일 시 이미 값이 확정되었기 때문에 실행시 효율이 높아진다. 값이 변하지 않아서 안정적이다.

동적 바인딩

  • 유연하고 값이 변할 수 있다.
  • 어떤 값이 들어올지 몰라서 들어올 값보다 많은 메모리 공간을 차지하고 있기 때문에 메모리 공간이 낭비될 수 있다.
    • 들어오는 값이 메모리 타입에 맞는지 확인하기 때문에 속도가 느려진다.

자바에서 메소드는 기본적으로 동적 바인딩하기 때문에 오버라이딩이 가능하다.
또한 static으로 선언하는 것은 메모리를 한 번밖에 할당하지 않기 때문에 컴파일 시에 메모리에 할당된다. 따라서 static으로 선언된 것은 모두 정적 바인딩이다.


상속에서의 생성과 초기화

  1. Memory 할당 후 기본값으로 초기화
  2. 명시적 초기화
  3. 생성자 구현부를 실행
public class Object{
	...
	public Object(){}
	...
}

public class Employee extends Object{
	private String name;
	private double salary = 15000.00;
	private Date birthDate;

	public Employee(String n, Date Dob){
		//implicit super();
		name = n;
		birthDate = DoB;
	}
	public Employee(String n){
		this(n, null);
	}
}

public class Manager extends Employee{
	private String department;

	public Manager(String n, String d){
		super(n);
		department = d;
	}
}
  1. Basic initialization
    1. Allocate memory for the complete Manager object
    2. Initialize all instance variables to their default values(0 or null)
  2. Call constructor: Manager(”Joe Smith”, “Sales”)
    1. Bind constructor parameters: n=”Joe Smith”, d=”Sales”
    2. No explicit this() call
    3. Call super(n) for Employee(String)
      1. Bind constructor parameters: n=”Joe Smith”
      2. Call this(n, null) for Employee(String, Date)
        1. Bind constructor parameters: n=”Joe Smith”, DoB=null
        2. Not explicit this() call
        3. Call super() for Object()
          1. No binding necessary
          2. Not this() call
          3. No super() call(Object is the root)
          4. No explicit variable initialization for Object
          5. No method body to call
        4. Initialize explicit Employee variables: salary=15000.00;
        5. Execute body: name=”Joe Smith”; date=null;
      3. Steps skipped
      4. Execute body: No body in Employee(String)
    4. No explicit initializers for Manager
    5. Execute body: department=”Sales”

캡슐화

캡슐화는 관련있는 데이터와 기능을 하나의 단위로 묶는 것이다.
예를 들어 프로그램의 어떤 동작에 문제가 생겼을 때 모든 프로그램을 살펴볼 필요 없이 그 동작을 제어하는 method만 확인하면 된다.

  • 클래스의 세부구현을 숨긴다. 세부 구현을 숨기는 캡슐화의 장점은 클래스의 숨겨진 구현부가 변경될지라도 외부에 노출된 인터페이스만 동일하다면 이 클래스를 사용하는 코드는 전혀 영향을 받지 않는다는 것이다. 이는 프로그램을 수정하거나 유지보수할 때 매우 큰 이점을 제공한다.

  • 대부분 또는 모든 변수들이 private으로 선언하여 외부 class의 접근을 막아 direct access를 차단한다.

  • method는 public으로 정의하여 외부 class의 접근을 허용한다. 즉, 외부에 인터페이스를 제공한다.

  • 변수는 method에 의해서 정보가 수정되어지거나 전달되게 된다.

  • 위의 접근권한을 이용하여 중요한 정보에 대한 보호를 할 수 있다.

    • 예를 들어 setter 메서드를 통해 값의 저장 룰을 정하여 관리할 수도 있다.
  • 위의 모듈화를 통해 정볼르 숨기는 것을 Information Hiding(정보은닉)이라고 한다.

  • Information Hiding을 통해 Object의 일부분이 변경되더라도 그 변화는 단지 Object 내에서만 영향을 끼치고, 이로 인하여 완전한 모듈화가 된다.

  • 모듈성: 작은 단위로 캡슐화를 할 수록 다른 객체를 위한 코드에서 독립적일 수 있다.


캡슐화의 이점의 예
자동차 운전자는 운전하는 방법만 알면 되며, 차의 내부내용을 운전자들이 알 수 없도록 숨기고 핸들, 브레이크, 기어라는 외부 인터페이스를 통한 메소드를 제공한다. → 잘못된 조작을 할 수 없다. → 내부구현이 바뀌더라도 외부 인터페이스는 동일하다.


잘못된 예
객체의 내부 데이터를 public 접근자로 제공한다.

정상
내부 데이터를 private 접근자로 선언하고 외부에 제공되는 인터페이스와 구현부를 분리한다.


  • 변수는 private으로 선언, method는 public으로 선언하여 외부에서는 method만을 통해 조작하도록 하여 오동작을 막는다.

  • get method(getter)와 set method(setter)

  • private instance variable의 접근을 public instance method에 의해 값을 얻을 때는 get method, 값을 할당할 때는 set method를 이용하도록 구현한다.

  • 예: get변수명(), set변수명()

  • 변수가 많은 경우엔 getter, setter 메서드를 다 선언하는 건 반복적인 코딩이다. 그래서 Lombok 라이브러리의 @Getter, @Setter 어노테이션을 이용하면 자동으로 메서드를 생성해주기 때문에 대부분의 개발자들은 Lombok 라이브러리를 사용한다.

@Getter @Setter
private String name;
profile
웹퍼블리셔의 백엔드 개발자 도전기

0개의 댓글