Java/스프링 입문을 위한 자바 객체 지향의 원리와 이해

Ch 03. 자바와 객체 지향

계란💕 2023. 11. 25. 23:53

객체 지향

  • 객체 지향은 '인간' 지향 언어이다. 
  • 객체 지향은 현실 세계를 반영한다. 
  • 기계에 맞춰서 프로그래밍하던 방식(절차 지향 프로그래밍)을 버리고 인간이 사물을 인지하는 방식대로 프로그래밍하고자 만들고자 객체 지향 프로그래밍 언어가 탄생했다. 
  • 클래스와 객체의 관계는 붕어빵틀과 붕어빵이라기보다는 분류실체의 개념에 가깝다. 
  • 클래스는 분류, 집합, 같은 속성과 기능을 가진 객체를 총칭하는 개념이다. 

 


객체지향의 대표적인 특성

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

 


 

추상화 = 모델링

  • Object 의 의미는 '객체'보다는 '개체'라는 뜻에 더 가깝다. 
  • 객체는 세상에 존재하는 유일무이한 사물을 말한다. (By 저자)
  • 객체를 특성(속성, 기능)에 따라 분류해보니 객체를 통칭할 수 있는 집합적인 개념인  "클래스"가 등장한다. 
  • 어떤 클래스를 이용해서 Object를 만들었다는 점을 강조할 때 "인스턴스" 라는 표현을 쓴다. 
  • 애플리케이션 경계: "컨텍스트"와 같다. 
  • 추상화: 구체적인 것을 분해해서 관심 영역에 있는 특성만 가지고 재조합(모델링)하는 것을 말한다. 
    • 모델링: 추상화란 구체적인 것을 분해해서 '관심 영역'(애플리케이션 경계)에 있는 특성만 가지고 재조합하는 것을 말한다. 
    • 추상화 = 모델링 = 자바의 class 키워드
  • 추상화를 넓게 보면 상속을 통한 추상화, 인터페이스를 통한 추상화, 다형성을 통한 추상화를 모두 포함한다.

 


  • 클래스 멤버 = static 멤버 = 정적 멤버
  • 객체 멤버 = 인스턴스 멤버
    • 정적 속성은 모든 객체가 같은 값을 가질 때 사용하는 게 기본이다. 이외에도 사용 가능하지만 정단한 이유가 있어야한다. 

 


Java의 main 메서드가 static 메서드인 이유

  • Java로 작성된 프로그램을 실행하기 위해서는 main() 메서드가 필요하다. 
  • 그런데 이 메서드가 static인 이유는 뭘까? 
    • T 메모리가 초기화된 순간, 객체는 하나도 존재하지 않는다. 따라서, 객체 멤버 메서드를 바로 실행할 수 없다. 
    • main 메서드는 반드시 static이어야만 한다. 

 


지역변수는 초기화하지만 전역 변수는 초기화하지 않아도 되는 이유

  • 지역변수는 개발자가 필수적으로 초기화해야하지만 클래스 속성객체 속성컴파일러 자동으로 초기화해준다. 
  • 클래스 변수는 프로그램 어디에서든 접근 가능한 변수이다. 그렇다면 이걸 어느 클래스에서 초기화해야한다고 규정할 수 있을까? 
    • 없다. 따라서 기본값으로 자동 초기화된다. 
  • 지역변수는 초기화하지 않으면 쓰레기 값을 가진다. 

 


클래스와 인터페이스

  • 상위 클래스는 하위 클래스에 물려줄 특성이 많을수록 좋고 인터페이스는 구현을 강제할 메서드가 적어야 좋다. 
    • 상위 클래스는 하위 클래스에 물려줄 특성이 많아야 좋은 이유: LSP(리스코프 치환 법칙)
    • 인터페이스는 구현을 강제할 메서드가 적어야 좋은 이유: ISP(인터페이스 분리 원칙)

 


상속과 T 메모리

  • 다음 예제를 실행할 때 T 메모리가 어떻게 구성되는지 살펴보자.

 

  Ex 1) 

  • 펭귄 클래스
<hide/>
public class Penguin extends Animal{
    public String habitat;
    public void showHabitat() {
        System.out.printf("%s는 %s에 살아\n", name, habitat);
    }
}

 

  • 동물 클래스
<hide/>
public class Animal {
    public String name;
    public void showName(){
        System.out.printf("안녕 나는 %s야. 반가워\n", name);
    }
}

 

  •  Driver 클래스
<hide/>
public class Driver {
    public static void main(String[] args) {
        Penguin pororo = new Penguin();
        pororo.name = "뽀로로";
        pororo.habitat = "남극";
        
        pororo.showName();
        pororo.showHabitat();
        
        Animal pingu = new Penguin();
        pingu.name = "핑구";
//        pingu.habitat = "EBS"; 컴파일 에러
        pingu.showName();
//        pingu.showHabitat(); 컴파일 에러
//        Penguin happyfeet = new Animal(); 컴파일 에러

    }
}

 

  Note) 실행 결과

  • static  영역
    • 세 개의 클래스 - Penguin, Animal, Driver
    • main() 메서드
  • stack  영역
    • showName()
    • showHabitat()
    • 지역 변수: pororo, pingu 는 각각 heap  영역의 인스턴스를 가리킨다. 
  • heap  영역
    • 인스턴스 - Penguin, Animal
    • 인스턴스 변수- habitat, name

 

 

1) Penguin pororo = new Penguin(); 실행 후, T메모리 구조

  • Penguin 의 상위 클래스인 Animal 도 heap에 생성된다. (깊게 따져보면 Object 클래스의 인스턴스도 생성된다. )

 

 

2)  Animal pingu = new Penguin(); 실행 후, T 메모리 구조

  • static  영역의 pororo는 heap 영역의 Penguin 을 가리키고, pingu는 Penguin이 아닌 Animal 을 가리킨다. 
  • 따라서, 자식 클래스인 Penguin 클래스에 정의된 메서드를 사용할 수 없다. 
  • 묵시적 형변환이 일어난다. 

 


다형성: 사용 편의

  • 오버로딩, 오버라이딩
  • 앞에서 본 예제에 코드를 추가해서 T 메모리 구조를 살펴보려한다. 

 

  Ex 2) 다형성

 

  • Penguin
    • showName() 메서드를 오버라이드했다. 
<hide/>
public class Penguin extends Animal{
    public String habitat;
    public void showHabitat() {
        System.out.printf("%s는 %s에 살아\n", name, habitat);
    }
    
    /*** 여기서부터 추가 */
    @Override
    public void showName() {
        System.out.println("어머 내이름은 알아서 뭐하게요?");
    }
    public void showName(String yourName) {
        System.out.printf("%s, 안녕 나는 %s 라고 해\n", yourName, super.name);
    }
}

 

 

  • Animal
<hide/>
public String name;
public void showName(){
  System.out.printf("안녕 나는 %s야. 반가워\n", name);
}

 

 

  • Driver
<hide/>
public static void main(String[] args) {
      Penguin pororo = new Penguin();
      pororo.name = "뽀로로";
      pororo.habitat = "남극";
      pororo.showName();
      pororo.showName("초보람보");
      pororo.showHabitat();
      Animal pingu = new Penguin();
      pingu.name = "핑구";
      pingu.showName();
}

 

 

  Note) 실행 결과

  • Penguin 타입 참조 변수로 선언한 pororo와 Animal 타입 참조변수로 선언한 pingu 에 대해서 showName()를 실행하면 하위 클래스에서 오버라이드한 메서드가 호출된다. 상위 클래스에서 정의한 showName()은 무시되고 있다.
  • 결론: 객체 참조 변수를 사용해도 하위 클래스에서 오버라이딩한 메서드가 호출된다. 

 

  Ex 2 - 1)  pororo 생성 시, T 메모리 구조

 

  • showName() 을 호출하면 하위 클래스에 재정의된 showName()이 호출된다.

 

 

  Ex 2 - 2)  pingu 생성 시, T 메모리 구조

 

  • showName()을 호출하면 Animal로 선언한 변수 pingu임에도 불구하고 하위 클래스에서 오버라이드한 메서드가 호출된다. 

 

 

 

  Ex) 다형성과 제네릭

  • 만약 다형성이 지원되지 않는다면 오버로딩이 불가능할것이다.
  • 두 개의 값을 매개변수를 받아서 더하는 add()라는 함수를 만든다고 가정하자. 
  • add() 의 매개변수로 어떤 정수형(byte, short, int, long, char) 이나 실수형(double, float)이 들어가도 덧셈이 가능하도록 만들어야한다.
  • 위의 7개의 자료형에 대해 add() 를 오버로딩하면 ( 7 * 7 ) - 7 = 42 개의 add() 메서드가 필요하다. 
  • 그런데 여기서  제네릭을 이용하면  add 메서드는 딱 하나만 있어도 위의 42개의 메서드를 구현한 것과 같은 효과를 낼 수 있다. 
  • 위와 같이 선언하면 printValue()안에 어떤 데이터 타입이든 받아서 출력 가능하다. 
<hide/>
public class GenericExample {
    public static <T> void printValue(T value) {
        System.out.println(value);
    }

    public static void main(String[] args) {
        printValue(10);       // 정수 출력
        printValue(3.14);     // 실수 출력
        printValue("Hello");  // 문자열 출력
    }
}

 

 


 

캡슐화 : 정보 은닉

  • 접근 제어자가 캡슐화의 대표적인 예시이다. 
  • 접근 제어자에 대한 UML 표기법
    • - : private 
    • ~: default 
    • #: protected  
    • +: public
    • _: 속성이나 메서드 아래에 언더바를 쓰면 static 을 의미한다. 

 


title

  • con
  • con

 


알아보기

  • 클래스에는 물려줄 속성이 많을수록  인터페이스에는 구현할 메서드가 적어야 좋은 이유
  • 명시적 형변환, 묵시적 형변환

 

출처 - 「 스프링 입문을 위한 자바 객체 지향의 원리와 이해 - 김종민 」