객체 지향은 인간 지향, 직관적

프로그래밍 언어의 발전사를 보면 개발자를 더욱 편하고 이롭게 하기 위해 발전했다.
로우 레벨의 기계가 아닌 하이 레벨의 인간을 배려하기 위한 과정이었다.
포인터 개념은 기계 수준으로 눈높이를 낮추지 않으면 이해하기 힘든 부분이였고,
"왜 우리가 기계 종속적인 개발을 해야 하는가?"라는 의문이 생긴 개발자들은
"우리가 눈으로 보고, 느끼고, 생활하는 현실 세계처럼 프로그래밍 할 수 없을까?"라는
고민 속에서 객체 지향의 개념이 탄생 했다.

논리적인 단위 블록, 함수

객체 지향의 증거는 무엇인가? #객체 이다.
기존의 구조적 프로그래밍 언어에서 가장 중요한 것은 #함수 였다.

  • D&C (Divide and Conquer, 분할 정복)
    아무리 복잡한 문제라도 작은 문제로 분할해서 하나씩 정복하다 보면 결국 해결된다는 전략이다.
  • 이 논리적인 단위 블록을 함수라고 한다

객체 Object

"우리가 주변에서 사물을 인지하는 방식대로 프로그래밍할 수 있지 않겠는가"하는 것이 객체 지향의 출발이다. 객체 지향은 직관적이다.

  • 세상에 존재하는 모든 것은 사물(객체 Object)이다.
  • 각각의 사물은 고유하다.
  • 사물은 속성을 갖는다.
  • 사물은 행위를 한다.

그리고 사물을 분류(Class)해서 이해하는 것이 인간의 인지법이다.

  • 직립보행을 하며 말을 하는 존재를 사람이라고 분류한다.
  • 연미복, 짧은 다리, 날지 못하는 새를 펭귄이라고 분류한다.
  • 밤하늘에 반짝이는 사물들을 별이라고 분류한다.
Class Object property method
사람 김종민, 한효주, 김연아, … 나이, 몸무게, 키 먹다,자다,울다,싸다
객체 지향 이전에는 속성과 메서드를 객체라는 단위로 묶지 않고 따로 분리된 형태로 프로그램을 작성했었다. 객체 지향에서는 우리가 주변에서 실제 사물을 인지 및 사고하는 방식대로 객체 단위의 프로그래밍이 가능하다.

객체지향은 인간적이다. 객체지향은 직관적이다.

객체 지향의 4대 특성

  • 캡슐화(Encapsulation) : 정보은닉(information hiding)
  • 상속 : 재사용
  • 추상화(Abstraction) : 모델링
  • 다형성(Polymorphism) : 사용 편의

클래스와 객체

객체 지향의 클래스와 객체를 구분하는 간단한 방법은 나이를 물어보는 것이다.

  • 사람의 나이는 몇 살인가?
  • 김연아의 나이는 몇 살인가?
  • 뽀로로의 나이는 몇 살인가?
  • 펭귄의 나이는 몇 살인가?

클래스 분류는 개념이지 실체가 아니다.
객체는 실체다.
Pasted image 20231212000340.png

추상화 : 모델링

추상 : 여러 가지 사물이나 개념에서 공통되는 특성이나 속성 따위를 추출하여 파악하는 작용
객체 : 세상에 존재하는 유일무이한 사물
클래스 : 분류. 집합. 같은 속성과 기능을 가진 객체를 총칭하는 개념

추상화란 구체적인 것을 분해해서 관찰자가 관심 있는 특성만 가지고 재조합하는 것
객체 지향의 4대 특성은 클래스를 통해 구현된다.

객체 지향의 추상화는 곧 모델링이다

  • 객체는 유일무이한 사물이다
  • 클래스는 같은 특성을 지닌 여러 객체를 총칭하는 집합의 개념

예를 들어 새로운 사람인 홍길동이 태어났다고 했을 때 자바로 표현하면 다음과 같다
사람 홍길동 = new 사람();

새로운 사람인 줄리엣이 태어났다면 다음과 같다.
사람 줄리엣 = new 사람();

사람이라는 클래스를 이용해 새로운 하나의 사람(객체)를 만들어 홍길동(객체 참조 변수)라는 이름을 지어준 것이다.

클래스를 이용해 객체(object)를 생성한 경우 클래스의 인스턴스(instance) 라는 표현을 쓴다.
객체(object) = 클래스의 인스턴스

객체 지향 프로그래밍을 할 때 클래스를 먼저 설계하게 된다.
만약 사람이라는 클래스를 설계한다고 해보면, 사람 클래스를 만들기 위해 실체들, 사람 객체들이 가진 공통된 특성을 찾게 된다.
Pasted image 20231212001731.png

명사로 표현되는 특성을 속성이라 하고, 속성은 값을 가질 수 있다.
동사로 표현되는 특성을 기능/행위라 하고, 기능/행위는 절차 또는 로직이라는 것을 갖게 된다. 이것을 메서드라 한다.

그리고 애플리케이션 경계를 컨텍스트(Context)라고 부른다. 애플리케이션의 경계를 알기 위해서는 다음과 같은 질문을 던져보면 된다.

내가 만들고자 하는 애플리케이션은 어디에서 사용될 것인가?

병원 애플리케이션을 만들고자 한다면, 사람은 환자를 의미하는 이름으로 바꿀 수 있고 클래스 설계도 달라질 것이다. 은행 애플리케이션을 만들고자 한다면, 사람은 고객이라는 이름으로 바꿀 수 있고 클래스 설계도 달라질 것이다.

추상화란 구체적인 것을 분해해서 관심 영역(애플리케이션 경계, Application Boundary)에 있는 특성만 가지고 재 조합하는 것 = 모델링

모델은 실제 사물을 정확히 복제하는 게 아니라 목적에 맞게 관심 있는 특성만 추출해서 표현하는 것.
바로 모델은 추상화를 통해 실제 사물을 단순하게 묘사하는 것이다.

이런 모델링(추상화)은 객체 지향에서 클래스를 설계할 때 필요한 기법이고 또한 데이터베이스의 테이블을 설계할 때 필요한 기법이다.

추상화는 모델링이다

  • OOP의 추상화는 모델링이다
  • 클래스 설계에서 추상화가 사용된다
  • 클래스 설계를 위해서는 애플리케이션 경계부터 정해야 한다
  • 객체 지향의 추상화의 결과는 클래스이다

추상화 = 모델링 = 자바의 Class 키워드

  • 자바 표현 > 클래스 객체_참조_변수 = new 클래스();

추상화와 T 메모리

애플리케이션 경계를 먼저 설정해야 한다. 애니메이션의 쥐 캐릭터 관리 프로그램을 만든다고 가정한다.

  1. 클래스 객체로부터 공통 특성을 뽑아내자.
객체명 미키마우스 제리
속성 성명:미키마우스
국적:미국
나이:87
종교:무교
신장:70cm
체중:11.5kg
애완동물:플루토
여자친구:미니마우스
꼬리:1개
성명:제리
국적:미국
나이:75
종교:기독교
친구:톰
여자친구:null
꼬리:1개
  1. 쥐 캐릭터 객체를 관찰하여 쥐 클래스 설계를 한다.
    추상화를 통해 애플리케이션 경계 내에서 관심있는 특성들만 뽑는다. 추상화는 주관적이다.
    Pasted image 20231212005143.png
    코드로 표현하기 위해 논리적 설계를 물리적 설계로 바꿔야 한다.
  • Class Mouse
  • property name:String, age:int, countOfTail:int
  • Method sing():void
package abstraction01;

public class Mouse {
	public String name;
	public int age;
	public int countOfTail;

	public void sing() {
		System.out.println(name + " 찍찍!!!");
	}
}
  • 위 UML 클래스 표기법으로 구현한 java 코드이다.
package abstraction01;

public class MouseDriver {
	public static void main(String[] args) {
		Mouse mickey = new Mouse();

		mickey.name = "미키";
		mickey.age = 85;
		mickey.countOfTail = 1;

		mickey.sing();

		mickey = null;

		Mouse jerry = new Mouse();

		jerry.name = "제리";
		jerry.age = 73;
		jerry.countOfTail = 1;

		jerry.sing();
	}
}
  • Mouse 클래스를 통해 객체를 생성한다.
  • 객체의 특성을 활용하여 main()메서드를 가진 별도의 테스트용 클래스를 만든다.

Pasted image 20231212005722.png

  • MouseDriver.main() 메서드의 T메모리 스냅샷

java.lang 패키지와 모든 클래스들(Mouse, MouseDriver)이 T 메모리의 스태틱 영역에 배치 됨
Mouse에서 name, age, countOfTail에는 변수 저장 공간이 안 보인다.

  • Mouse 클래스에 속한 속성이 아닌 Mouse 객체에 속한 속성이기 때문이다.
  • 객체가 생성돼야만 속성의 값을 저장하기 위해 힙 영역에 메모리 공간이 할당 된다.

UML 표기법에 따르면…

  • MouseDriver 클래스의 main() 메서드에 밑줄이 그어져 있는 이유는 main() 메서드는 클래스의 멤버 메서드이다.
  • sing()은 객체의 멤버 메서드이기 때문에 밑줄이 없다.

클래스 멤버와 객체 멤버

static 키워드는 클래스 멤버와 객체 멤버를 구분하는 키워드이다.

  • 5번째 줄 코드라인
    Mouse mickey Mouse 객체에 대한 참조 변수 mickey를 생성
    new Mouse() Mouse 클래스의 인스턴스를 하나 만들어 힙에 배치
    대입문 Mouse 객체에 대한 주소(포인터)를 참조 변수 mickey에 할당함

Pasted image 20231212235659.png

  • Mouse mickey에 의해 Mouse 타입의 객체를 참조할 수 있는 객체 참조 변수 mickey가 스택 영역의 main() 스택 프레임 안에 생성 된다.

new Mouse() 는 객체 생성자를 호출하는 구문이다.
Pasted image 20231212235824.png

  • 생성된 객체는 힙 영역에 배치
  • Mouse 클래스의 인스턴스의 시작 주소가 100번지라고 가정하면 대입문에 의해 mickey 객체 참조 변수에 할당되는 값은 얼마일까??
    Pasted image 20231213000036.png
    객체 참조 변수 mickey가 Mouse 객체에 대한 참조 변수이다.

Mouse mickey = new Mouse();
한 마리 쥐가 태어났다. 이름은 mickey

  • 쥐: 클래스
  • 한 마리 쥐 : 클래스의 인스턴스 : 객체
  • mickey : 객체 참조 변수

객체 참조 변수 mickey와 참조 연산자(.)을 이용해 실제 힙 상의 객체에 접근해 속성에 값을 할당한다.
Pasted image 20231213000343.png

  • 11번째 코드 라인의 mickey.sing() 이 실행되면, T 메모리 상의 변화는 없다. Mouse 객체의 sing() 메서드가 코드 실행 영역에서 실행되어 화면에 "미키 찍찍!!!"을 출력한다.

  • 13번째 코드 라인의 mickey = null 은 객체 참조 변수 mickey가 더 이상 힙 영역에 존재하는 Mouse 객체를 참조하지 않는다.

    • 가비지 컬렉터(Garbage Collector)가 제거
      Pasted image 20231213000731.png
  • 15번째 코드 라인이 실행 되면 mickey와 같은 과정을 거쳐서 새로운 Mouse 객체가 생성되고 객체 참조 변수 jerry에 주소 값이 할당될 것이다.
    Pasted image 20231213000831.png
    Pasted image 20231213000848.png

  • 마지막 라인까지 실행되고 나면 프로그램은 완전히 종료 된다
    Pasted image 20231213000923.png

스태틱은 “고정된” 이라는 뜻을 가지고 있다. 스태틱 영역에 올라간 정보는 main() 메서드가 시작되기 전에 올라가서 main() 메서드가 종료된 후에 내려올 정도로 스태틱 영역에 단단히 고정되어 있기 때문에 스태틱 영역이라 한다.

스택은 자료구조 스택과 같다. 선입후출구조, LIFO, FILO…

힙은 대용량 자료를 저장할 수 있는 메모리를 사용하는 방식이다.

클래스 멤버 vs 객체 멤버 = static 멤버 vs 인스턴스 멤버

클래스와 객체를 간단히 구분하는 방법을 다음과 같이 제시한다.

  • 생물인 경우 “나이가 어떻게 되는가?”
  • 무생물인 경우 “제조일자가 어떻게 되는가?”

객체는 유일무이하게 존재하는 실체이며, 속성에 값을 가진다.
클래스는 개념이면서 분류 체계일 뿐 속성에 값을 가질 수 없다.

  • 미키마우스의 꼬리는 몇 개 인가요?
  • 제리의 꼬리는 몇 개 인가요?
  • 쥐의 꼬리는 몇 개 인가요?

위 질문의 답은 모두 한 개이다. 쥐는 클래스인데 꼬리 개수에 답이 있는 것이다.
꼬리 개수는 객체의 속성이지만 모든 객체가 같은 값을 가지고 있기 때문에 클래스를 통해 질문해도 하나라는 답을 알 수 있다.

Pasted image 20231214003908.png

  • Mouse 객체가 모두 같은 값을 갖는 꼬리 개수(countOfTail) 속성이 있다. 나라면 이 coutOfTail 속성은 값을 변하게 하지 않고 읽기만 가능하도록 할 것 같다.

Pasted image 20231214004046.png

  • static 키워드 속성을 앞에 붙이면 된다.
package abstraction01;

public class Mouse {
	public String name;
	public int age;
	public static int countOfTail;

	public void sing() {
		System.out.println(name + " 찍찍!!!");
	}
}
  • countOfTail 속성은 T 메모리의 스태틱 영역에 단 하나의 저장 공간을 갖게 된다.
  • 객체참조변수.countOfTail 또는 클래스명.countOfTail로 접근할 수 있다.
package abstraction02;

public class MouseDriver {
	public static void main(String[] args) {
		// 클래스명.countOfTail
		Mouse.countOfTail = 1;

		Mouse mickey = new Mouse();
		Mouse jerry = new Mouse();
		Mouse mightyMouse = new Mouse();

		// 객체명.countOfTail
		System.out.println(mickey.countOfTail);
		System.out.println(jerry.countOfTail);
		System.out.println(mightyMouse.countOfTail);

		// 클래스명.countOfTail
		System.out.println(Mouse.countOfTail);
	}
}
  • 6번째와 18번째 줄은 클래스명.속성명 형식으로 접근했다.
  • 13번째, 14번째, 15번째 줄은 객체참조변수.속성명 형식으로 접근했다.

static 키워드가 붙은 속성을 클래스 멤버 속성이라 한다.
static 키워드 안 붙은 속성은 객체 멤버 속성이라고 한다.
속성뿐만 아니라 메서드도 static 키워드에 따라 클래스 멤버 메서드, 객체 멤버 메서드로 분류한다.

클래스 설계 시 클래스 멤버와 객체 멤버

추상화를 통해 모델링을 하게 되면 다음과 같이 4가지 요소를 설계하는 것이다.

클래스 멤버 static 클래스 멤버 속성
클래스 멤버 메서드
객체 멤버 객체 멤버 속성
객체 멤버 메서드
  • 클래스 멤버 = static 멤버 = 정적 멤버
  • 객체 멤버 = 인스턴스 멤버

정적 속성은 해당 클래스의 모든 객체가 같은 값을 가질 때 사용하는 것이 기본이다.
정적 메서드는 객체들의 존재 여부에 관계없이 쓸 수 있는 메서드이다. JVM 구동 시 T 메모리의 스태틱 영역에 바로 배치되기 때문이다.
정적 멤버들은 객체가 아닌 클래스에 속해 있다.

  • 정적 메서드를 사용하는 경우
    • main() 메서드의 논리를 함수로 분할해서 사용하는 경우
    • 정적 변수에 대한 getter와 setter로 사용하는 용도
      실무에서는 클래스의 인스턴스를 만들지 않고 사용하게 되는 유틸리성 메서드를 주로 정적 메서드로 구성한다.

객체 속성은 속성명만 있고 실제 메모리 공간은 확보하지 않는다.

  • 힙 영역에 객체가 생성되면 바로 그때 각 객체 안에 멤버 속성을 위한 메모리 공간이 할당됨
변수이름 다른 이름 T메모리
static 변수 클래스 [멤버] 속성, 정적 변수, 정적 속성 등 스태틱 영역
인스턴스 변수 객체 [멤버] 속성, 객체 변수 등 힙 영역
local 변수 지역 변수 스택 영역(스택 프레임 내부)

상속 : 재사용 + 확장

Pasted image 20231217144153.png
객체 지향의 상속은 재사용과 확장으로 이해하는 것이다.

동물은 포유류의 부모가 아니다. 동물 중 분류를 조금 더 세분화해서 포유류와 조류가 나오고 포유류를 조금 더 분류해서 고래, 박쥐가 나오는 것이다.

객체 지향에서의 상속은 상위 클래스의 특성을 하위 클래스에서 상속(특성 상속)하고 거기에 더해 필요한 특성을 추가, 즉 확장해서 사용할 수 있다.

  • 상위 클래스 - 하위클래스
  • 슈퍼 클래스 - 서브 클래스

Pasted image 20231217144507.png
상위 클래스 쪽으로 갈수록 추상화, 일반화되고, 하위 클래스 쪽으로 갈수록 구체화, 특수화 된다.

Pasted image 20231217144608.png

  • 객체 지향의 상속 - 집합 벤다이어그램

상속 관계에서 반드시 만족해야 할 문장은 “하위 클래스는 상위 클래스다.” 이다.

  • 포유류는 동물이다.
  • 고래는 포유류다.
  • 고래는 동물이다.

로버트 C. 마틴이 "하위 클래스는 상위 클래스다."라는 것을 주장하고, 객체 지향 설꼐 5원칙 가운데 LSP(리스코프 치환 원칙)를 나타내는 말이다.

자바 언어에서 inheritance(상속) 라는 키워드는 존재하지 않는다.
대신 extends(확장)가 존재한다.

상속의 강력함

package inheritance01;

public class 동물 {
	String myClass;

	동물() {
		myClass = "동물";
	}

	void showMe() {
		System.out.println(myClass);
	}
}
package inheritance01;

public class 포유류 extends 동물 {
	포유류() {
		myClass = "포유류";
	}
}
package inheritance01;

public class 조류 extends 동물 {
	조류() {
		myClass = "조류";
	}
}
package inheritance01;

public class 고래 extends 포유류 {
	고래() {
		myClass = "고래";
	}
}
package inheritance01;

public class 박쥐 extends 포유류 {
	박쥐() {
		myClass = "박쥐";
	}
}
package inheritance01;

public class 참새 extends 조류 {
	참새() {
		myClass = "참새";
	}
}
package inheritance01;

public class 펭귄 extends 조류 {
	펭귄() {
		myClass = "펭귄";
	}
}

객체 참조 변수명은 객체스럽게, 클래스명은 클래스명 답게 정하는 습관을 들여야 한다.
조류 bird = new 조류(); 이 같이 짓는 것은 잘못됐다.
bird라는 단어는 객체보다는 클래스에 가깝기 때문이다.
#클래스와 객체 에서 설명했듯이 클래스는 나이를 답할 수 없다.
프로그램 적으로는 허용되는 코드이지만 인간의 논리에는 맞지 않다.
작은 논리들이 모여 결국 큰 프로그램을 만들게 된다.

package inheritance01;

public class Driver01 {
	public static void main(String[] args) {
		동물 animal = new 동물();
		포유류 mammalia = new 포유류();
		조류 bird = new 조류();
		고래 whale = new 고래();
		박쥐 bat = new 박쥐();
		참새 sparrow = new 참새();
		펭귄 penguin = new 펭귄();

		animal.showMe();
		mammalia.showMe();
		bird.showMe();
		whale.showMe();
		bat.showMe();
		sparrow.showMe();
		penguin.showMe();
	}
}

상위 클래스인 동물에서만 showMe() 메서드를 구현했지만 모든 하위 클래스의 객체에서 showMe() 메서드를 사용할 수 있다.
하위 클래스에서 showMe()메서드를 다시 작성하지 않고도 재사용 할 수 있는 것이다.

package inheritance01;

public class Driver02 {
	public static void main(String[] args) {
		동물 animal = new 동물();
		동물 mammalia = new 포유류();
		동물 bird = new 조류();
		동물 whale = new 고래();
		동물 bat = new 박쥐();
		동물 sparrow = new 참새();
		동물 penguin = new 펭귄();

		animal.showMe();
		mammalia.showMe();
		bird.showMe();
		whale.showMe();
		bat.showMe();
		sparrow.showMe();
		penguin.showMe();
	}
}
  • 실행결과는 Driver1과 같다.

위 코드는 "하위 클래스는 상위 클래스다."라는 말이 표현되는 코드이다.

package inheritance01;

public class Driver03 {
	public static void main(String[] args) {
		동물[] animals = new 동물[7];

		animals[0] = new 동물();
		animals[1] = new 포유류();
		animals[2] = new 조류();
		animals[3] = new 고래();
		animals[4] = new 박쥐();
		animals[5] = new 참새();
		animals[6] = new 펭귄();

		for (int index = 0; index < animals.length; index++) {
			animals[index].showMe();
		}
	}
}

반복문 하나로 모든 동물들을 불러낼 수 있다.

객체 지향은 현실 세계를, 인간의 논리를 그대로 코드로 옮길 수 있는 힘이 있다.

상속은 is a 관계를 만족해야 한다?

  • 펭귄 is a 동물

“펭귄은 한 마리 동물이다.” 번역도 되고 논리도 맞지만, 펭귄과 동물은 클래스다. "한 마리 동물"은 객체이다.

상속을 표현하는 is a를 더 명확히 표현하면 a kind of 라는 표현이다.

  • 하위 클래스 is a kind of 상위 클래스
  • 펭귄 is a kind of 동물 -> 펭귄은 동물의 한 분류다.

객체지향의 상속

  • 상위 클래스의 특성을 재사용하는 것
  • 상위 클래스의 특성을 확장하는 것
  • is a kind of 관계를 만족하는 것

다중 상속과 자바

Pasted image 20231217151016.png

  • 인어는 사람과 물고기를 상속한다고 생각해보자.

사람도 수영할 수 있고, 물고기도 수용할 수 있는데 인어에게 "수영해!"라고 한다면 사람처럼 팔과 다리를 저어 수영해야 할까? 아니면 물고기처럼 가슴, 등, 꼬리 지느러미로 헤엄쳐야 할까?

이와 같은 문제를 다중 상속의 다이아몬드 문제라고 함.
자바와 C#은 과감히 다중 상속을 포기했다. 대신 자바에서는 C++에는 없는 인터페이스를 도입해 다중 상속의 득은 취하고 실은 과감히 버렸다.

상속과 인터페이스

  • 상속 관계 : 하위 클래스 is a kind of 상위 클래스
  • 해석 : 하위 클래스는 상위 클래스의 한 분류다
  • 예제 : 고래는 동물의 한 분류다.

인터페이스를 도입한 자바에서 인터페이스는 어떤 관계를 나타내는 것일까?

  • 인터페이스 : 구현 클래스 is able to 인터페이스
  • 해석 : 구현 클래스는 인터페이스할 수 있다.
  • 예제 : 고래는 헤엄칠 수 있다.

Pasted image 20231217152005.png

  • 인터페이스는 be able to, "무엇을 할 수 있는"이라는 표현 형태로 만드는 것 이 좋다.

자바 API에서도 이러한 be able to 형식의 인터페이스를 많이 볼 수 있다.

  • Seriaizeble 인터페이스 : 직렬화 할 수 있는
  • Cloneable 인터페이스 : 복제할 수 있는
  • Comparable 인터페이스 : 비교할 수 있는
  • Runnable 인터페이스 : 실행할 수 있는

상위 클래스는 하위 클래스에게 특성(속성과 메서드)를 상속해 주고, 인터페이스는 클래스가 '무엇을 할 수 있다’라고 하는 기능을 구현하도록 강제하게 된다.

상위 클래스는 물려줄 특성이 풍성할 수록 좋고, 인터페이스는 구현을 강제할 메서드의 개수가 적을 수록 좋다.

  • 상위 클래스가 풍부할 수록 좋은 이유는 LSP(리스코프 치환 원칙)에 따른 이유이다.
  • 인터페이스에 메서드가 적을 수록 좋은 이유는 ISP(인터페이스 분할 원칙)에 따른 이유이다.
package inheritance02;

public class 동물 {
	String myClass;

	동물() {
		myClass = "동물";
	}

	void showMe() {
		System.out.println(myClass);
	}
}
package inheritance02;

public interface 날수있는 {
	void fly();
}
package inheritance02;

public interface 헤엄칠수있는 {
	void swim();
}
package inheritance02;

public class 포유류 extends 동물 {
	포유류() {
		myClass = "포유류";
	}
}
package inheritance02;

public class 조류 extends 동물 {
	조류() {
		myClass = "조류";
	}
}
package inheritance02;

public class 고래 extends 포유류 implements 헤엄칠수있는 {
	고래() {
		myClass = "고래";

	}

	@Override
	public void swim() {
		System.out.println(myClass + " 수영 중. 어프!!! 어프!!!");
	}
}
package inheritance02;

public class 박쥐 extends 포유류 implements 날수있는 {
	박쥐() {
		myClass = "박쥐";

	}

	@Override
	public void fly() {
		System.out.println(myClass + " 날고 있삼.. 슈웅!!! 슈웅!!!");
	}
}
package inheritance02;

public class 참새 extends 조류 implements 날수있는 {
	참새() {
		myClass = "참새";
	}

	@Override
	public void fly() {
		System.out.println(myClass + " 날고 있삼.. 허우적!!! 허우적!!!");
	}
}
package inheritance02;

public class 펭귄 extends 조류 implements 헤엄칠수있는 {
	펭귄() {
		myClass = "펭귄";
		
	}

	@Override
	public void swim() {
		System.out.println(myClass + " 수영 중. 푸악!!! 푸악!!!");
	}
}
package inheritance02;

public class Driver {
	public static void main(String[] args) {
		날수있는 날라리1 = new 박쥐();
		날라리1.fly();

		날수있는 날라리2 = new 참새();
		날라리2.fly();

		헤엄칠수있는[] 맥주병들 = new 헤엄칠수있는[2];

		맥주병들[0] = new 고래();
		맥주병들[1] = new 펭귄();

		for (헤엄칠수있는 맥주병 : 맥주병들) {
			맥주병.swim();
		}
	}
}

실행 결과는 다음과 같다

박쥐 날고 있삼… 슈웅!!! 슈웅!!!
참새 날고 있삼… 허우적!!! 허우적!!!
고래 수영 중. 어프!!! 어프!!!
펭귄 수영 중. 푸악!!! 푸악!!!

상속과 UML 표기법

Pasted image 20231217152847.png
UML 표기법에서 상속과 인터페이스 표기법이다.

상속을 표현하기 위해 하위 클래스에서 상위 클래스 쪽으로 화살표를 그린다.
클래스가 인터페이스를 구현한 경우에는 인터페이스를 구현하는 클래스에서 인터페이스 쪽으로 화살표를 그린다.
인터페이스 구현에 대한 약식 표현으로 막대 사탕을 사용하기도 한다.

상속과 T 메모리

예제 코드

package inheritance03;

public class Driver {
	public static void main(String[] args) {
		Penguin pororo = new Penguin(); 
		//펭귄 한 마리가 태어났다. 이 펭귄을 pororo라 할 것이다.

		pororo.name = "뽀로로"; 
		// pororo의 name은 "뽀로로"
		pororo.habitat = "남극";
		// pororo의 habitat(서식지)는 "남극"

		pororo.showName();
		// pororo의 name 보여줘
		pororo.showHabitat();
		// pororo의 habitat 보여줘

		Animal pingu = new Penguin();

		pingu.name = "핑구";
		// pingu.habitat = "EBS";

		pingu.showName();
		// pingu.showHabitat();

		// Penguin happyfeet = new Animal();
	}
}

Penguin 클래스의 인스턴스와 그를 상속하는 Animal 클래스의 인스턴스도 힙 영역이 생성된다.

  • 하위 클래스의 인스턴스가 생성될 때 상위 클래스의 인스턴스도 함께 생성된다.

객체 참조 변수인 pingu는 Animal 인스턴스를 가리킨다.

  • Penguin pororo = new Penguin();
  • Animal pingu = new Penguin();

pingu는 Penguin() 클래스를 불렀지만, 앞에 클래스가 Animal이 되면서 형변환이 되었다.

  • 명시적 형변환(Casting)과 암묵적 형변환(Promotion)

코드를 보며 인간의 언어로 번역하여 잘 읽히는 것이 논리적으로 이해하기 쉬운 코드가 된다.
객체 지향이 인간 지향이기 때문이다.

다형성 : 사용편의성

  • 다형성 - 오버라이딩(overriding)과 오버로딩(overloading)
    • ride 올라타다
    • load 적재하다

Pasted image 20231218145227.png
오버라이딩과 오버로딩을 구분하는 법은 위로 쌓을 것인가? 옆으로 쌓을 것인가? 이다.

다형성과 T 메모리

예제 코드

Pasted image 20231218145817.png

  • Penguin 클래스가 상위 클래스인 Animal 클래스의 showName() 메서드를 오버라이딩(재정의)
  • showName(yuserName : String) 메서드를 오버로딩(중복 정의)

Animal 객체에 있는 showName() 메서드는 Penguin 객체에 있는 showName() 메서드에 의해 재정의 되어 어머 내 이름은 알아서 뭐하게요?가 출력된다.

다형성이 지원되지 않는 언어

간단히 두 정수를 더해서 반환하는 함수는 add(int, int) 형식 일 것이다.
다형성이 지원되지 않는 언어는 double과 int를 더하는 함수를 만드려면 중복된 이름으로 된 함수로 만들 수 없기 때문에 다른 이름으로 함수를 새로 추가해야 한다.
오버로딩은 함수명 하나를 가지고 인자 목록만 달리하면 되므로 사용성이 편리하다.
자바 5에서 추가된 제네릭을 이용하면 하나의 함수만 구현해도 다수의 함수를 구현한 효과를 낼 수 있다.

오버라이딩의 경우에도 하위 클래스가 재정의한 메서드를 알아서 호출해 줌으로써 형변환이나 isntanceof 연산사를 써서 하위 클래스가 무엇인지 신경쓰지 않아도 된다.

상위 클래스인 동물 클래스에서 울어보세요() 라는 메서드를 정의해 두고 하위 클래스인 쥐, 고양이, 강아지 클래스에서 각각 울어보세요() 메서드를 오버라이딩 했다면, 각 동물들이 하위 클래스 타입에 맞게 울게 된다.

캡슐화 : 정보 은닉

Pasted image 20231218152526.png

  • 정보 은닉(information hiding) 접근 제어자
    • private
    • [default]
    • protected
    • public

객체 멤버의 접근 제어자

예제 코드

상위 클래스인 Class A에서는 다음과 같이 속성을 정의했다.

  • 객체 멤버
    • private int pri;
    • int def;
    • protected int pro;
    • public int pub;
  • 클래스 멤버(정적 멤버)
    • static private int priStatic;
    • static int defStatic;
    • static protected int proStatic;
    • static public int pubStatic;

클래스 A에서 객체 멤버 메서드 runSomething()에서

  • 객체 멤버에 접근을 한다면
    • 객체 멤버 모두 접근이 가능하다.
    • 또는 this.객체 멤버로 접근 할 수 있다.
  • 정적 멤버에 접근을 한다면,
    • 클래스명.정적멤버 형태의 접근을 하는 것을 권장하지만,
    • 정적 멤버 모두 그대로 접근 가능
    • 또는 this.정적 멤버로 접근할 수 있다.

클래스 A에서 정적 멤버 메서드 runStaticThing()에서

  • 객체를 생성하지 않고 객체 멤버 접근은 불가능
  • 정적 멤버는 클래스명.정적멤버 형태로 접근할 수 있음
  • 객체 멤버를 생성하여 객체 참조 변수를 통해 접근 가능

같은 패키지 내에서 Class A를 상속한 ClassAA 하위 클래스에서는

  • private 키워드 속성을 제외한 객체 멤버와 정적 멤버에 접근할 수 있음
  • 객체 멤버 메서드에서는 this로 객체 멤버, 정적 멤버에 접근 가능

같은 패키지 내에서 Class A를 상속하지 않은 ClassB 클래스가 있다.

  • 클래스명 또는 객체 멤버를 생성하여 접근 가능
  • priavate 키워드 속성을 제외한 객체 멤버, 정적 멤버에 접근할 수 있음.

다른 패키지 내에서 Class A를 상속한 ClassAB 클래스가 있다.

  • protected과 public 속성의 객체 멤버와 정적 멤버에 접근할 수 있음
  • 클래스명 또는 객체 멤버를 생성하여 접근 가능
  • 객체 멤버 메서드에서는 this로 객체 멤버, 정적 멤버에 접근 가능

다른 패키지 내에서 Class A를 상속하지 않은 ClassC 클래스가 있다.

  • public 속성의 객체 멤버와 정적 멤버에 접근할 수 있음
  • 클래스명 또는 객체 멤버를 생성하여 접근 가능

상속을 받지 않았다면 객체 멤버는 객체를 생성한 후 객체 참조 변수를 이용해 접근해야 한다.
정적 멤버는 클래스명.정적멤버 형식으로 접근하는 것을 권장한다.
Pasted image 20231218165616.png

물리적 접근을 보면 정적멤버클래스명으로 접근하는 것이 더 효율적이다.

참조 변수의 복사

참고
스프링 입문을 위한 자바 객체 지향의 원리와 이해 - 2. 자바와 절차적⁄구조적 프로그램

기본 자료형 변수를 복사하는 경우 Call By Value(값에 의한 호출)에 의해 그 값이 복사되며 두 개의 변수는 서로에게 영향을 주지 않는다.

객체 참조 변수를 복사하는 경우

package reference;

public class CallByReference {
	public static void main(String[] args) {
		Animal ref_a = new Animal();
		Animal ref_b = ref_a;
		
		ref_a.age = 10;
		ref_b.age = 20;
		
		System.out.println(ref_a.age); // 20
		System.out.println(ref_b.age); // 20
	}
}

class Animal {
	public int age;
}

Call By Value와 Call By Reference는 본질적으로 차이가 없다.

  • 기본 자료형 변수는 저장하고 있는 값을 그 값 자체로 해석
  • 객체 참조 변수는 저장하고 있는 값을 주소로 해석

Pasted image 20231220000826.png

Animal 객체의 주소가 100번지라고 가정하면 CallByReference 실행 전과 후의 T 메모리는 다음과 같다.
Pasted image 20231220001026.png

System.out.println(ref_a);를 찍어보면 객체에 대한 고유값을 보여준다.

  • reference.Animal@15db9742
    System.out.println(ref_b);도 위와 같은 값이 출력된다.
    ref_a와 ref_b는 결국 같은 값을 가지고 있다는 것을 알 수 있다.

정리

  • 기본 자료형 변수는 값 자체로 판단함
  • 참조 자료형 변수는 값을 주소(포인터)로 판단함
  • 기본 자료형 변수를 복사할 때, 참조 자료형 변수를 복사할 때 일어나는 일은 같음
    • 가지고 있는 값을 그대로 복사해서 넘겨 줌

정리 - 자바 키워드와 OOP 4대 특성

OOP 4대 특성

  • 캡슐화
  • 상속
  • 추상화
  • 다형성

객체 지향 4대 특성 자바 키워드

  • new
  • private
  • protected
  • public
  • extends
  • static
  • class