[java] 스프링 입문을 위한 자바 객체지향의 원리와 이해 정리 3

자바와 객체 지향

객체 지향은 인간 지향이다.

우리가 눈으로 보고, 느끼고, 생활하는 현실 세계처럼 프로그래밍 할 수는 없을까?, 우리가 주변에서 사물을 인지하는 방식대로 프로그래밍 할 수 있지 않을까? 라는 고민에서 탄생한 객체지향

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

객체 지향의 4대 특성

클래스 vs. 객체 = 붕어빵틀 vs. 붕어빵??

클래스는 분류에 대한 개념이지 실체가 아니다. 하지만 객체는 실체다

클래스 : 객체 = 펭귄 : 뽀로로 = 사람 : 김연아

추상화: 모델링

추상화는 사실적인 인물의 그림을 그린 것이 아니다. 실제 모습을 그리지 않고 추상적인 모습을 그리고 오히려 그 인물의 특징을 더욱 정확하게 묘사하려는 노력이 컸다.

추상: 여러가지 사물이나 개념에서 공통되는 특성이나 속성 따위를 추출하여 파악하는 작용

추상화는 모델링이다.

객체: 세상에 존재하는 유일무이한 사물이며, 생물이건 무생물이건 고유한 속성과 기능을 가지고 있다.

클래스: 분류, 집합, 같은 속성과 기능을 가진 객체를 총칭

클래스를 이용해 object를 만들었다는 것을 강조할 때는 object보다는 instance라는 표현을 쓴다.

ex) 사람 클래스

속성: 시력, 몸무게, 혈액형, 키, 나이, 직업, 취미, 등등

메서드(기능,행위): 먹다, 자다, 일하다 운전하다, 울다 등등

위와 같이 사람 클래스를 만들 때 사람의 속성과 메서드를 전부 나열하는 것은 불필요하다. 이 부분에서 어플리케이션 경계 라는 개념이 나온다. 어플리케이션 경계를 알기 위해서는 내가 만들고자 하는 애플리케이션은 어디에서 사용될 것인가?라는 질문으로 확립이 가능하다.

추상화: 구체적인 것을 분해해서 관심영역에 있는 특성만 가지고 재조합 하는것 => 모델링

여기서 모델링이란 실제 사물을 정확히 복제하는게 아니라 목적에 맞게 관심 있는 특성만을 추출해서 표현하는 것이다.

즉 모델은 추상화를 통해 실제 사물을 단순하게 묘사하는 것이다.

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

그리고 JAVA는 class 키워드를 통해서 객체 지향의 추상화를 지원하고 있다.

추상화 === 모델링 === Java의 class 키워드

추상화의 결과물은 모델(논리적 설계) => 모델은 자바에서 클래스로 표현(물리적 설계)

Java에서 클래스와 객체 관계는 아래와 같이 표현한다.

클래스 객체_참조_변수 = new 클래스();

위 코드를 풀어 설명하면 새로운 객체 하나를 생성해서 그 객체의 주소값(포인터)을 객체 참조 변수에 할당 과 같다.

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

  public void sing() {
    System.out.println(name + "찍찍!!!");
  }
}

T 메모리의 스태틱 영역에 있는 위의 Mouse클래스는 name, age, countOfTail의 값을 갖고 있지 않다. static 이라고 표시되지 않은 변수는 정적,클래스 멤버 변수가 아닌 객체 멤버 변수이기 때문에 객체가 힙 메모리에 존재 할 때 그때서야 값을 할당하여 힙 메모리에 그가 값을 지니고 있게 된다.

스태틱 영역은 메인 메서드의 닫는 괄호가 나타나고 나서야 해당 영역에 저장되어 있는 클래스들이 사라진다.

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

클래스와 객체를 간단히 구분하는법

그렇다면

모든 질문의 답은 한개인데 각 항목(미키, 제리, 쥐)마다 답이 있다. 꼬리 개수는 객체의 속성이지만 모든 객체가 같은 값을 가지고 있기 때문에 클래스를 통해 질문해도 답은 하나의 꼬리이다. 이런 경우는 각 객체들이 공통된 값을 굳이 하나씩 다 가지고 있기 때문에 아까운 메모리를 잡아먹게 된다. 그래서 이를

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

  public void sing() {
    System.out.println(name + "찍찍!");
  }
}

위의 클래스는 T메모리의 스태틱 영역에서 단 하나의 저장 공간을 갖게 된다.

위와 같이 static 키워드를 붙인 속성을 클래스 멤버 속성이라고 한다. 그리고 붙이지 않은 속성을 객체 멤버 속성이라고 한다.

뿐만 아니라 static 키워드를 붙인 메서드는 클래스 멤버 메서드라고 하며 붙이지 않은 속성은 객체 멤버 메서드라고 한다.

정리하면

클래스 멤버 = static 멤버 = 정적 멤버

**정적멤버라는 말을 더 많이 사용한다.

객체 멤버 = 인스턴스 멤버

이처럼 정적 속성은 해당 클래스의 모든 객체가 같은 값을 가질 때 사용하는 것이 기본이다.

그렇다면 정적 메서드는 어떨 때 사용하는 것이 좋을까?

** 보통 UML 표기 법에서는 정적 멤버에 대해서는 밑줄을 사용해 표시하도록 규정한다.

용어 정리

클래스 멤버 === 정적 멤버 === 스태틱 멤버

객체 멤버 === 오브젝트 멤버 === 인스턴스 멤버

필드 === 속성 === 프로퍼티

상속: 재사용 + 확장

상속은 계층도, 조직도가 아닌 분류도이다.

계층도: 할아버지 - 아버지 - 딸

분류도: 동물 - 포유류 - 고래

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

상위 클래스 쪽으로 갈수록 추상화, 일반화 됐다고 말하며, 하위 클래스로 갈수록 구체화, 특수화 됐다고 말한다.

상속 관계에서 하위 클래스는 상위클래스다 라는 문장은 굉장히 중요하다.

이 문장은 LSP(리스코프 치환 원칙)를 나타내는 말과 같다.

아버지 영희아빠 = new 딸();

// 딸을 낳으니 아버지 역학을 하는 영희 아빠라 이름 됐다?

// 는 말이 안된다.

동물 뽀로로 = new 펭귄();

// 펭귄을 낳으니 동물 역할을 하는 뽀로로라 이름 지었다.

// 는 말이 된다.

자바에서 상속은 확장, 세분화가 어울린다는 이유는 자바에서 inheritance라는 키워드는 존재하지 않고 상속을 사용하는 키워드는 extends로 사용하기 때문이다.(자바의 아버지 제임스 고슬링은 이를 정확히 이해하고 있었다.)

상속의 강력함

객체의 특성은 유일무이하고 객체 참조 변수는 결국 객체를 참조해서 사용하기 때문에 이름을 고민하여 지어야한다.

즉 클래스 명은 분류 스럽게, 객체 참조 변수 명은 유일무이한 사물처럼 작명해야 한다.

상속은 is a 관계를 만족해야 하나?

틀렸다. 상속은 is a kind of 관계를 만족해야 한다.

펭귄 is a kind of 동물 -> 펭귄은 동물의 한 분류다. 고래 is a kind of 동물 -> 고래는 동물의 한 분류다.

정리하면

- 객체 지향의 상속은 상위 클래스의 특성을 재사용하는 것이다.
- 객체 지향의 상속은 상위 클래스의 특성을 확장하는 것이다.
- 객체 지향의 상속은 is a kind of 를 만족하는 것이다.

다중 상속과 자바

인어 extends 물고기 extends 사람

만약 물고기도 수영이 가능하고 사람도 수영이 가능하다면 물고기처럼 수영해야할까? 사람처럼 수영해야 할까?

이와 같은 문제를 다중 상속의 다이아몬드 문제라고 한다.

JAVA는 이를 인터페이스를 도입하여 다중상속의 득은 취하고 실은 과감히 버렸다.

상속과 인터페이스

인터페이스는 상속과 다르게 is able to를 만족해야 한다

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

인터페이스는 클래스가 무엇을 할 수 있다라고 하는 기능을 구현하도록 강제하게 된다.

여기서

상위 클래스는 하위 클래스에게 물려줄 특성이 많을수록 좋을까? 적을수록 좋을까?

인터페이스는 구현을 강제할 메서드가 많을수록 좋을까? 적을수록 좋을까?

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

위의 이유는 LSP에 의거한다.

상속과 T 메모리

위 그림에서 보듯이 Dolphin 객체를 생성하면 그의 상위 클래스인 Animal 클래스의 인스턴스도 heap에 생성된다. 추가로 각 객체의 최상위 클래스인 Object도 저 Animal 옆에 붙어있다.

그리고 gari 객체 참조 변수는 Dolphin 인스턴스가 아닌 Animal 인스턴스를 가리키고 있다. 이는 Animal gari = new Dolphin();의 결과물이고 형변환이 일어난것이다.

암묵적 형변환: 데이터 손실의 우려가 없고, 작은 데이터 타입에서 큰 데이터 타입으로 타입이 바뀌는 연산

명시적 형변환: (byte)123 과 같이 괄호를 통해 int 형인 123을 byte형으로 변환하는 것을 의미한다. 데이터 손실의 우려가 있고 큰 데이터 타입에서 작은 데이터 타입으로 타입이 바뀌는 연산

다형성: 사용편의성

오버라이딩: 재정의: 상위 클래스의 메서드와 같은 메서드 이름, 같은 인자 리스트

오버로딩: 중복정의: 같은 메서드 이름, 다른 인자 리스트

다형성과 T 메모리

위 그림은에서 pororo는 penguin 클래스가 animal 클래스의 showname 메서드를 오버라이딩, 오버로딩 했음을 의미한다.

하지만 pingu는 Animal 클래스의 객체 참조변수로 받았기 때문에 penguin을 가리키며, 상위 클래스 타입의 객체 참조 변수를 사용하더라도 하위 클래스에서 오바리이딩한 메서드가 호출된다.

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

다형성이 지원되지 않는다면 같은 기능을 하더라도 다른 자료형의 인자를 받는 여러개의 함수를 만들어야한다.

하지만 JAVA는 다형성을 지원하고 오버로딩과 오버라이딩 같은 방식으로 위와 같은 문제를 해결한다.

제네릭을 이용하면 하나의 함수만 구현해도 다수의 함수를 구현한 효과를 낼 수 있다.

** 상위 클래스 타입의 객체 참조 변수에서 하위 클래스가 오버라이딩한 메서드를 자동으로 호출해준다.

캡슐화: 정보 은닉

public

protected

위에서 public은 모든게 허용되는 제한자라 비교를 위해 클래스가 접근가능한지만 알아봤었고, 아래사진과 같이 protected와 private는 클래스 선언자체가 불가능합니다.

그러므로 protected 접근 제한자는 같은 패키지에 속해있는 클래스에서 생성자와 메소드, 필드(객체)만 호출 할 수 있고, 다른 패키지에 있는 생성자, 메소드, 필드는 호출하지 못하지만 상속관계에 있는 클래스라면 예외로 호출이 가능합니다.

같은 패키지 안에 있는 클래스끼리는 호출을 허용하니 생략하도록 하고, 위 사진은 패키지가 다른 클래스에 있는 protected 생성자/메소드/필드를 가져오려면 상속을 받아 사용이 가능하게 한 예제입니다.

해석을 하자면… 패키지1에있는 test1클래스에서 다른 패키지에 있는 protected생성자를 가져오기 위해 test3 클래스를 상속받았더니 오류가 뜨지 않는거죠. 반대로 test3에 test1을 상속받고자 하면 오류가 생깁니다.

( ★ protected가 들어있는 클래스가 부모 클래스라면 가능 )

default

앞에 public이나 protected, private를 추가하지 않았다면 기본적으로 default 접근 제한자가 되어 다른 패키지와의 클래스 선언과 생성자/메소드/필드의 호출이 불가능해집니다. 물론 동일 패키지안에서는 모든게 허용됩니다.

동일 패키지간의 허용은 private(전체 비공개)말고 전부 허용이 가능하므로 다른 패키지간의 관계만을 따져보는게 좋다고 생각했습니다. 이번에도 해석하자면 test1클래스에 3번째 필드선언은 앞에 접근제한자가 생략되있는데 그것을 default 접근제한자로 부르고 다른패키지에 있는 test3클래스에서 호출해봤으나 접근이 불가하여 오류가 났습니다.

(★ default는 friedly(프랜들리)라고도 부른다고 하네요.)

두가지 사항을 기억하자

참조 변수의 복사

Call by Value, Call by Reference를 다르다고 이해하기 보다는 기본 자료형 변수는 저장하고 있는 값을 그 값 자체로 판단하고 참조 변수는 저장하고 있는 값을 주소로 판단한다고 이해하는 것이 더 쉽다.

즉 기본 자료형 변수를 복사할 때, 참조 자료형 변수를 복사할 때 일어나는 일은 같다. 가지고 있는 값을 그대로 복사해서 넘겨준다.

참고:
https://wayhome25.github.io/cs/2017/04/11/cs-13/
https://server-engineer.tistory.com/224
https://brad903.tistory.com/entry/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%9E%90%EB%B0%94-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%9D%98-%EC%9B%90%EB%A6%AC%EC%99%80-%EC%9D%B4%ED%95%B44?category=838571