Spring/객체지향

Java와 객체 지향 : OOP 4대 특성

mint* 2024. 3. 25. 16:09
728x90

자바와 객체 지향

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

절차적/구조적 프로그래밍까지의 과정은 기계 종속적인 개발이었다.
구조적 프로그래밍에서 제일 중요한 것은 함수이다. 복잡한 문제를 작은 문제로 분할하여 정복하는 방법이다.
그러나 몇 천, 몇 만 라인의 명령어를 논리적인 단위로 나누어 블록화하여 작성해야한다.

 

그 후 객체 지향 프로그래밍이 등장했다.

객체 지향의 출발은 "우리가 주변에서 사물을 인식하는 방식대로 프로그래밍할 수 있지 않겠는가?"라는 물음에서 시작되었다.


0,1로 대변하는 기계(컴퓨터)에 맞춰 사고하던 방식을 버리고 현실세계를 인지하는 방식으로 프로그램을 만들자는 것이다.

그래서 객체 지향은 직관적이다.

 

객체 지향 이해하기

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

객체 지향은 인간의 사고 방식을 그대로 프로그래밍에 접목하여 직관적이고 쉽다.
객체 지향은 가장 인간을 지향하는 프로그래밍이다.

 

객체 지향의 4대 특성

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

 

클래스와 객체

클래스(Class) : 객체들의 분류, 집합, 설계도(blueprint)
객체(Object) : 세상에 존재하는 유일무이한 사물, 실체

세상에 존재하는 유일무이한 객체를 특성(속성+기능)에 따라 분류해 보니 객체를 통칭할 수 있는 집합적 개념, 즉 클래스(분류)가 나온다.

 

클래스 : 객체 = 사람 : 김연아 = 판다 : 푸바오

클래스를 이용해 객체를 만들었다는 것을 강조할 때는 object 표현보다는 클래스의 instance라는 표현을 쓴다.
객체(object) = 클래스의 인스턴스

 

추상화 : 모델링

추상(abstract) : 여러 가지 사물이나 개념에서 공통되는 특성이나 속성 따위를 추출하여 파악하려는 작용
추상화 : 구체적인 것을 분해해서 관찰자가 관심 있는 특성만 가지고 재조합하는 것

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

개발자는 애플리케이션의 창조자가 된다.
우리는 객체 지향 프로그래밍을 할 때 클래스를 먼저 설계하게 된다.
클래스 생성시 객체들이 가진 공통된 특성을 찾게 된다.

    • 명사로 표현되는 공통 특성 : 속성
      • ex) 시력, 몸무게, 혈액형, 키, 나이, 직업, 취미
    • 동사로 표현되는 공통 특성 : 메서드(기능/행위)
      • ex) 먹다, 자다, 일하다

클래스 설계에서 추상화가 사용된다.

 

애플리케이션 경계(Context)

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

    • 병원 애플리케이션 : 사람 => 환자
    • 은행 애플리케이션 : 사람 => 고객

애플리케이션 경계에 따라 클래스 설계가 달라질 수 있다.
사람이라는 큰 클래스(분류)에 대해 애플리케이션 경계에 맞게 더 구체적으로 클래스를 정의할 수 있고, 필요하지 않는 속성을 배제할 수 있다.

 

추상화

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

지구본은 지구를 모델링한다. 지구의 굴곡이나 기후 등 모든 부분을 표현하지 않고 표현할 수도 없지만 지구의 주요 특성들을 표현한다.
태양계 모델은 태양계를 모델링한다. 태양계를 정확히 묘사하진 않지만, 태양을 축구공 크기로 표현할 때 수성은 좁쌀 모양의 크기로 표현한다. 즉, 상대적인 크기에 대한 특성을 표현한다.

태양계에서 보여주고 싶은 주요 특성을 뽑아 태양계 모델을 만든다.

 

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

실제 사물은 애플리케이션 경계에 맞춰 관심있는 특성만 표현되어 단순하게 묘사된다.
객체 지향에서 추상화의 결과는 클래스이다.

 

    • 논리적 설계 : 개발 환경(언어 등)에 영향을 받지 않는 설계
    • 물리적 설계 : 개발 환경에 맞춰진 설계

코드로 표현하기 위해 논리적 설계를 물리적 설계로 바꿔야 한다.

 

java에서 추상화 지원

자바는 class로 객체 지향의 추상화를 지원한다.
추상화 = 모델링 = 자바의 class 키워드

클래스 이름은 집합, 분류를 더 잘 나타내도록 짓자.
객체는 유일무이한 사물처럼 짓자.

클래스 객체_참조_변수 = new 클래스();
1. 객체 참조 변수를 선언한다.
2. 새로운 객체를 하나 생성한다.
3. 객체의 주소값을 객체 참조 변수에 할당한다.

추상화에서 기억할 부분

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

 

추상화와 T 메모리

Mouse 클래스

package abstraction01;

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

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

name, age, countOfTail 은 static 키워드가 붙지 않았으므로 객체 참조 변수이다.
객체 참조 변수들은 new 키워드로 객체가 생길 때 함께 생기며(힙에 존재) 객체에 속하는 변수이다.

 

테스트용 클래스

package abstraction01;

public class MouseDriver{
    public static void main(String[] args){
        Mouse mickey = new Mouse();
        mickey.name = "미키";
        mickey.age = 85;
        mickey.countOfTail = 1; // 9번째 줄

        mickey.sing(); // 11번째 줄

        mickey = null;
    }
}

 

9번째 줄 메모리 상태

new 키워드로 클래스의 인스턴스가 생기면 heap에 할당된다.

  • static : "고정된"이라는 뜻, static 영역에 올라간 정보는 main 메서드가 시작되기 전에 올라가서 main 메서드가 종료된 후 내려올 정도로 단단히 스태틱 영역에 고정되어있다.
  • 스택 : LIFO(선입후출구조), 사이즈가 고정(프로그램 시작시 결정), 작은 데이터 빠르게 처리 가능, 함수 호출 관리에 적합
  • 힙 : 대용량 자료를 저장할 수 있다 - 크기 유연하게 할당 가능, 개발자가 직접 할당하고 해제하거나 garbage collector가 수거

 

11번째 줄 메모리 상태
  mickey = null;

객체 참조 변수 mickey가 더이상 힙 영역에 존재하는 Mouse 객체를 참조하지 않는다.

  • 가비지 컬렉터*는 아무도 참조하지 않는 Mouse 객체를 쓰레기로 인지하여 수거한다.

Mouse mickey = new Mouse();

  1. Mouse mickey; // Mouse 객체에 대한 참조 변수 mickey를 만든다.
  2. mickey = new Mouse(); // Mouse 클래스의 인스턴스를 만들어 힙에 배치한다.
  3. 객체 참조 변수에 생성한 객체에 대한 주소(포인터)를 할당한다.

 

static 멤버 vs 인스턴스 멤버

모든 쥐의 꼬리는 1개이다.
객체 수만큼 저장하는 것보다 그 값을 쥐 클래스에 저장하자. => static 사용

static

해당 클래스의 모든 객체들이 같은 값을 가질 때 사용

package abstraction01;

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

    public void sing(){
        System.out.println(name + "찍찍!!");
    }
}
    • 객체_참조_변수.속성명 또는 클래스명.속성명으로 접근할 수 있다. 정적 멤버는 클래스명.속성명으로 접근하는 것을 권장한다.
package abstraction01;

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

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

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

        // 클래스명.countOfTail
        System.out.println(Mouse.countOfTail); // 1
    }

}
  • 클래스 멤버 = static 멤버 = 정적 멤버
  • 객체 멤버 = 인스턴스 멤버

정적 메서드는 객체들의 존재 여부에 관계없이 쓸 수 있는 메서드이다.
정적 메서드는 객체가 아닌 클래스에 속해 있다.
=> 클래스는 JVM 구동시 메모리의 스태틱 영역에 바로 배치되기 때문에 객체의 존재 여부에 관계 없이 쓸 수 있다.

 

어떤 메서드가 정적 메서드이어야 할까?

프로그램 실행시 어떠한 객체도 존재하지 않기 때문에, 프로그램을 실행하는 main 메서드는 정적 메서드여야한다.

  • main() 메서드
  • main() 메서드의 논리를 함수로 분할해서 사용하는 경우
  • 정적 변수에 대한 getter(), setter() => 정적 변수를 조회하거나 설정
  • 클래스의 인스턴스를 만들지 않고 사용하는 유틸리티성 메서드 ex) Math 클래스의 정적 메서드

 

static 변수 vs 인스턴스 변수 vs local 변수

static 변수와 인스턴스 변수는 기본값으로 초기화된다.

정수형 : 0, 부동소수점형 : 0.0, 논리형 : false, 객체 : null

 

지역변수는 개발자가 별도로 초기화하지 않으면 쓰레기값을 갖게 된다.

 

상속 : 재사용 + 확장

  • 객체 지향의 상속은 상위-하위 클래스 또는 슈퍼-서브 클래스이다.

    • 객체 지향의 상속은 부모-자식 클래스가 아니다.
  • 객체 지향의 상속은 분류도이다.
    • 계층도(할아버지-아버지) 또는 조직도가 아니다.
  • 객체 지향의 상속은 재사용과 확장이다.

 

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

 

하위 클래스는 상위 클래스다.

  • 뽀로로는 펭귄이다

펭귄은 뽀로로다(X) : 뽀로로가 펭귄을 대표할 수 없기 때문이다.
뽀로로는 펭귄이다 : 펭귄이 뽀로로와 그 외 수많은 펭귄 1,2,3..을 대표할 수 있다.

  • 펭귄은 동물이다.
동물 뽀로로를 선언하고, 펭귄을 생성한 후 펭귄을 뽀로로에 할당한다.
펭귄은 동물이고 동물은 펭귄을 대표할 수 있다.

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

  • 포유류는 동물이다.
  • 고래는 포유류다.
하위 클래스를 상위 클래스에 할당하는 것이 자연스럽다.
상위 클래스는 하위 클래스를 포함한다.

 

  • java에서 상속을 표현할 때 inheritance라는 키워드가 아닌 extends(확장) 키워드로 표현한다.
자식 클래스가 부모 클래스를 확장하기 때문이다.

 

하위 클래스는 상위 클래스이다.(상위 클래스로 표현할 수 있다, 상위 클래스를 포함한다, 확장한다.)
상위 클래스에 하위 클래스를 할당할 수 있다. => 하지만 그때부터 상위 클래스의 필드와 메서드만 접근할 수 있다. 하위 클래스에서 확장한 프로퍼티와 메서드는 사용할 수 없다.

 

예제 코드

package inheritance01;

public class 동물 {
    String myClass; // default 접근제어자(같은 패키지에서 접근 가능)

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

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

public class 포유류 extends 동물 {
    포유류(){
        myClass = "포유류";
    }
}
  • 자식 클래스는 부모 클래스의 필드와 메서드를 상속받는다.
package interitance01;

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

public class Driver01 {
    public static void main(String[] args){
        동물 animal = new 동물();
        포유류 mammalia = new 포유류();
        고래 whale = new 고래();

        animal.showMe();
        mammalia.showMe();
        whale.showMe();
    }
}
  • 상위 클래스에서 구현한 메서드를 하위 클래스에서 사용 가능하다.
package inheritance01;

public class Driver01 {
    public static void main(String[] args){
        동물 animal = new 동물();
        동물 mammalia = new 포유류();
        동물 whale = new 고래();

        animal.showMe();
        mammalia.showMe();
        whale.showMe();
    }
}
  • 하위 클래스는 상위 클래스를 할당할 수 있다.
package inheritance01;

public class Driver01 {
    public static void main(String[] args){
        동물[] animals = new 동물[7];
        동물[0] = new 동물();
        동물[1] = new 포유류();
        동물[2] = new 고래();

        for (int index=0;index<animals.length();i++){
            animals[i].showMe();
        }
    }
}
  • 또한 모든 클래스는 결국 Object를 물려받는다. 그래서 어떤 클래스의 인스턴스이든 상관없이 개발자는 toString() 메서드를 사용할 수 있다.
package Practice;  

public class prac15 {  
    public static class Animal {  
        public void eat(){  
            System.out.println("Animal is eating food");  
        }  

    }  
    public static void main(String[] args){  
        Animal animal = new Animal();  
        animal.eat();  
        System.out.println(animal.toString()); // Practice.prac15$Animal@2d209079  
    }  
}

 

상속은 is kind of 관계이다.

  • 하위 클래스 is kind of 상위 클래스
    • 하위 클래스는 상위 클래스의 한 분류이다.
  • 펭귄 is kind of 조류
  • 뽀로로 is kind of 펭귄

 

상속에서 기억할 부분

  • 객체 지향의 상속은 상위 클래스의 특성을 재사용하는 것이다.
  • 객체 지향의 상속은 상위 클래스의 특성을 확장하는 것이다.
  • 객체 지햐의 상속은 is kind of 관계를 만족해야 한다.

 

다중 상속

c++과 달리 java와 c#은 다이아몬드 문제를 발생시키는 다중 상속을 포기했다.
하지만 java는 c++에는 없는 인터페이스를 도입해 다중 상속의 득만 취하고 실은 버렸다.

 

 

인터페이스는 is able to 관계이다.

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

java API에서 인터페이스

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

 

분류도에 적용

 

예제 코드

package inheritance01;

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

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

    public void swim(){ // 구현 강제
        System.out.println(myClass + " 수영 중");
    }
}
package inheritance01;

public class Driver{
    public static void main(String[] args){

    }
}

 

상속과 T 메모리

package inheritance02;

public class Animal{
    public String name;

    public void showName(){
        System.out.printf("안녕 나는 %s야", name);
    }

}
package inheritance02;

public class Penguin extends Animal{
    public String habitat;

    public void showHabitat(){
        System.out.printf("%s는 %s에 살아", name, habitat);
    }
}
package inheritance02;

public class Driver {
    public static void main(String[] args){
        Penguin pororo = new Penguin(); 

        pororo.name = "뽀로로";
        pororo.habitat = "남극";

        pororo.showName();
        pororo.showHabitat();

        Animal pengsu = new Penguin(); // 13번째 줄

        pengsu.name = "펭수";
        // pengsu.habitat = "남극";

        pengsu.showName();
        // pengsu.showHabitat();

    }
}
  • pororo는 Penguin 클래스의 인스턴스이고, pengsu는 Animal 클래스의 인스턴스이다.
  • pengsu는 Animal 클래스의 인스턴스이다.
    하위 클래스인 Penguin을 상위 클래스 Animal에 할당했다.
    하위 클래스는 상위 클래스에서 확장된 것이므로 대입은 가능하지만, Penguin에서 확장한 프로퍼티와 메서드는 사용할 수 없다. 즉, 하위 클래스인 Penguin 클래스의 프로퍼티와 메서드에 접근할 수 없다.

 

코드 읽는 연습
코드는 인간의 언어로 번역하며 읽어야한다.
그렇게 읽을 때 논리적으로 이해하기 쉬운 코드가 되어야한다.
객체 지향은 인간 지향이기 때문이다.
코드를 보면서 번역해서 읽기 힘들다면 객체 지향 언어의 아름다움을 충분히 활용하지 못하고 있는 것이다.

 

13번째 줄 실행 후 T 메모리 구조

  • Penguin 클래스의 인스턴스 뿐만 아니라 상위 클래스인 Animal 클래스의 인스턴스도 함께 Heap 영역에 생겼다.
    • 하위 클래스의 인스턴스가 생성될 때 상위 클래스의 인스턴스도 함께 생성된다.
    • 모든 클래스의 최상위 클래스인 Object 클래스의 인스턴스도 함께 생성된다.

 

인터페이스를 통해 다형성 제공하기

인터페이스 자체는 객체를 생성할 수 없지만, 인터페이스를 구현한 클래스의 객체는 생성할 수 있다.

interface Flyable {
    void fly();
}

class Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("새가 날아갑니다.");
    }
}

class Airplane implements Flyable {
    @Override
    public void fly() {
        System.out.println("비행기가 날아갑니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        // 인터페이스 객체 생성 불가능 
        // Flyable f = new Flyable(); // 오류 발생
        
        Flyable f1 = new Bird();
        f1.fly(); // "새가 날아갑니다." 출력

        Flyable f2 = new Airplane();
        f2.fly(); // "비행기가 날아갑니다." 출력
    }
}

 

다형성 : 사용편의성

오버라이딩과 오버로딩

  • 오버라이딩(overriding): 같은 메서드 이름, 같은 인자 목록으로 상위 클래스의 메서드를 재정의
  • 오버로딩(overloading) : 같은 메서드 이름, 다른 인자 목록으로 다수의 메서드를 중복 정의

인공위성에서 볼 때 오토바이를 올라탄(riding) 사람은 오토바이가 가려져 사람만 보인다.
짐이 적재(loading)될 때 옆으로 적재된 모든 적재물이 보인다.
즉, 오버라이딩은 하위 클래스로 인해 상위 클래스의 메서드 정의를 덮어씌우고, 오버로딩은 메서드 여러개를 추가적으로 중복 정의한다.

 

예제 코드

package polymorphism01;

public class Animal {
    public String name;

    public void showName(){
        System.out.printf("안녕 %s야 반가워\n", name);
    }

}
package polymorphism01;

public class Penguin extends Animal {
    public String habitat;

    public void showHabitat(){
        System.out.printf("%s는 %s에 살아\n", name, habitat);
    }

    // 오버라이딩 - 재정의
    public void showName(){
        System.out.printf("펭귄의 이름은 비밀이야\n");
    }

    // 오버로딩
    public void showName(String yourName){
        System.out.printf("%s야 안녕, 나는 %s야\n", yourName, name);
    }

}
package polymorphism01;

public class Driver{
    public static void main(String[] args){
        Penguin pororo = new Penguin();

        pororo.name = "뽀로로";
        pororo.habitat = "남극";

        pororo.showName();
        pororo.showName("초보람보");
        pororo.showHabitat();

        Animal pengsu = new Penguin(); // 14번째 줄

        pengsu.name = "펭수";
        pengsu.showName();
    }

}

 

출력

펭귄의 이름은 비밀이야
초코람보야 안녕, 나는 뽀로로야
뽀로로는 남극에 살아
펭귄의 이름은 비밀이야
pengsu.showName();
  • Animal 객체에 있는 showName()메서드는 Penguin 객체에 있는 showName() 메서드에 의해 재정의, 즉 가려졌기에 Penguin 객체에서 재정의한 showName() 메서드가 호출된다.
    => 상위 클래스 타입의 객체 참조 변수를 사용하더라도 하위 클래스에서 오버라이딩(재정의)한 메서드가 호출된다.

 

14번째 줄 실행 후 T 메모리

  • 객체 참조 변수 : pororo는 Penguin을, pingu(코드에서 pengsu)는 Animal을 가리킨다.
  • pengsu는 Penguin 클래스의 인스턴스이지만 Animal(상위) 클래스 타입의 객체 참조 변수를 가진다. showName()은 Penguin 클래스에서 오버라이딩(덮어쓰기)되었으므로, showName()을 호출하면 덮어쓰기한 penguin의 showName()이 호출된다.
  • pengsu는 Animal 클래스 타입의 객체 참조 변수이므로 오버라이딩된 메서드 이외에 Penguin 클래스의 프로퍼티와 메서드에 여전히 접근할 수 없다.

 

다형성이 사용편의성인 이유

    • 오버로딩 : 함수명 하나를 가지고 인자 목록만 다르게 하면 된다.
      • ex) 다형성이 지원되지 않는다면 addIntInt, addIntDouble, addDoubleInt 메서드명 다 다르게 만들어야한다.
      • 제네릭을 사용하면 함수명 하나를 가지고 다수의 함수를 구현한 효과를 낼 수 있다.
    • 오버라이딩 : 하위 클래스가 재정의한 메서드를 알아서 호출해준다.
      • 상위 클래스 타입의 객체 참조 변수여도 하위 클래스가 오버라이딩한 메서드를 자동으로 호출해준다.
상위 클래스로 큰 분류를 묶어 각각의 하위 클래스 객체를 생성할 수 있다.
상위 클래스 타입의 객체 참조 변수여도 각 하위 클래스 타입의 메서드를 호출해줌으로써 각 하위 클래스 인스턴스들의 특성을 오버라이딩된 하나의 함수로 드러낼 수 있다.
  •  

 

예제 코드

class Driver{
    public static void main(String[] args){
        동물[] 동물들 = new 동물[3];

        동물[0] = new 쥐();
        동물[1] = new 고양이();
        동물[2] = new 강아지();

        for (int i=0;i<동물들.length();i++){
            동물[i].cry(); // 다형성
        }
    }
}
찍찍
야옹
멍멍

 

캡슐화 : 정보 은닉

객체 멤버의 접근제어자

  • 정의만 외우면 안된다. 생각보다 단순하지 않다.

 

접근 범위 맞추기

구조도

 

각 클래스의 runSomeThing() 메서드와 runStaticThing() 메서드(static)에서 접근할 수 있는 classA의 속성은 무엇일까?

 

classA

public class ClassA {  
    private int pri;  
    int def;  
    protected int pro;  
    public int pub;  

    public void runSomething() {  
    }  

    public static void runStaticThing() {  
    }  
}

답!

 

ClassA(같은 클래스)에서 멤버 접근하기

public class ClassA {  
    private int pri;  
    int def;  
    protected int pro;  
    public int pub;  

    public void runSomething() {  
        // non-static 메서드에서는 모든 멤버 접근 가능  
        System.out.println("runSomething()");  
        System.out.println(pri);  
        System.out.println(def);  
        System.out.println(pro);  
        System.out.println(pub);  
    }  

    public static void runStaticThing() {  
        System.out.println("runStaticThing()");  
        // static 메서드에서는 인스턴스 멤버 접근 불가  
        // System.out.println(pri); // 컴파일 에러  
        // 대신 클래스 멤버에는 접근 가능  
        System.out.println(new ClassA().def);  
        System.out.println(new ClassA().pro);  
        System.out.println(new ClassA().pub);  
    }  

    public static void main(String[] args) {  
        new ClassA().runSomething();  
        ClassA.runStaticThing();  
    }  
}
  • runSomething() => pri, def, pro, pub : 같은 클래스이므로 모든 속성 접근 가능
  • runStaticThing()=> def, pro, pub : static 메서드이므로 static이 아닌 인스턴스 멤버에 바로 접근할 수 없다. 객체를 생성하여 객체 참조 변수로 접근해야하는데, private은 외부에서 접근이 불가능하다.

 

ClassB (다른 클래스, 같은 패키지)

public class ClassB {
    void runSomething() {
        ClassA a = new ClassA();
        // System.out.println(a.pri); // private 접근 불가
        System.out.println(a.def);
        System.out.println(a.pro);
        System.out.println(a.pub);
    }

    static void runStaticThing() {
        ClassA a = new ClassA();
        System.out.println(a.def);
        System.out.println(a.pro);
        System.out.println(a.pub);
    }
}
  • runSomething() => def, pro, pub
  • runStaticThing()=> def, pro, pub

 

ClassAA (같은 패키지, 상속)

public class ClassAA {
    void runSomething() {
        ClassA a = new ClassA();
        // System.out.println(a.pri); // private 접근 불가
        System.out.println(a.def);
        System.out.println(a.pro);
        System.out.println(a.pub);
    }

    static void runStaticThing() {
        ClassA a = new ClassA();
        System.out.println(a.def);
        System.out.println(a.pro);
        System.out.println(a.pub);
    }
}
  • runSomething() => def, pro, pub
  • runStaticThing()=> def, pro, pub

 

ClassAB (다른 패키지, 상속)

package Practice.Practice2;  

import Practice.Practice1.ClassA;  

public class ClassAB extends ClassA {  
    public void runSomething() {  
        System.out.println("runSomething()");  
        System.out.println(pro); // ClassA 로부터 상속받았으므로 접근 가능  
        System.out.println(pub);  

        // 여기 ClassA는 단지 다른 패키지의 클래스를 호출한 것, 상속 관계와 상관없음  
        ClassA a = new ClassA();  
//        System.out.println(a.pro); // 다른 패키지이고 상속과 상관없으므로 접근 불가  
        System.out.println(a.pub);  

        ClassAB ab = new ClassAB();  
        System.out.println(ab.pro); // ClassA로부터 상속받았으므로 접근 가능  
        System.out.println(ab.pub);  
    }  

    public static void runStaticThing() {  
        System.out.println("runStaticThing()");  
        // 인스턴스 생성해서만 접근 가능  
        ClassA a = new ClassA();  
//        System.out.println(a.pro); // 다른 패키지이고 상속과 상관없으므로 접근 불가  
        System.out.println(a.pub);  

        // ClassAB에 대한 멤버 참조이므로 정답과는 관련 없음
        ClassAB ab = new ClassAB();  
        System.out.println(ab.pro);  
        System.out.println(ab.pub);
    }  

    public static void main(String[] args) {  
        new ClassAB().runSomething();  
        ClassAB.runStaticThing();  
    }  
}
    • runSomething() => pro, pub
    • runStaticThing()=> pub
여기서 "pro"는 this.pro (ClassAB의 pro)를 뜻한다.

 

static 메서드에서 protected 멤버에 직접 접근할 수 없는 이유는 static 메서드가 클래스 레벨에서 작동하고, 인스턴스(객체) 레벨의 속성이나 메서드에 대한 직접적인 참조를 가질 수 없기 때문이다.
static은 인스턴스 없이 호출되므로(클래스에 속해있음) static 메서드 내부에서는 this를 참조할 수 없다. 즉, 상속받은 멤버에 접근할 수 없다.

 

참고 코드 - protected 멤버에 접근

public class Parent {
    protected int protectedVar = 10;
}

public class Child extends Parent {
    public void instanceMethod() {
        System.out.println(this.protectedVar); // 'this'를 사용하여 접근
        System.out.println(protectedVar); // 직접 접근도 가능
    }
}

 

ClassC (다른 패키지)

public class ClassC {
    void runSomething() {
        ClassA a = new ClassA();
        System.out.println(a.pub);
    }

    static void runStaticThing() {
        ClassA a = new ClassA();
        System.out.println(a.pub);
    }
}
  • runSomething() => pub
  • runStaticThing()=> pub

 

classA의 모든 프로퍼티가 static으로 바뀐 경우

 

class A

public class ClassA {  
    private static int pri;  
    static int def;  
    protected static int pro;  
    public static int pub;  

    public void runSomething() {  
    }  

    public static void runStaticThing() {  
    }  
}

 

classA에서 접근 (같은 클래스)

public class ClassA {  
    private static int pri;  
    static int def;  
    protected static int pro;  
    public static int pub;  

    public void runSomething() {  
        // 모든 멤버 접근 가능  
        System.out.println("runSomething()");  
        System.out.println(pri);  
        System.out.println(def);  
        System.out.println(pro);  
        System.out.println(pub);  
    }  

    public static void runStaticThing() {  
        System.out.println("runStaticThing()");  
        // static 멤버 바로 접근 가능 (인스턴스 생성할 필요 X)           
        System.out.println(pri);  
        System.out.println(def);  
        System.out.println(pro);  
        System.out.println(pub);  

        // 클래스로 바로 접근 가능  
        System.out.println(ClassA.pri); // 같은 클래스이므로  
        System.out.println(ClassA.def);  
        System.out.println(ClassA.pro);  
        System.out.println(ClassA.pub);  
    }  

    public static void main(String[] args) {  
        new ClassA().runSomething();  
        ClassA.runStaticThing();  
    }  
}
  • static 멤버 바로 접근 가능. 클래스명으로 접근 가능 => static 멤버는 클래스 레벨에 존재
  • runSomeThing() => pri, def, pro, pub
  • runStaticThing()=> def, pro, pub

 

ClassB (같은 패키지)

public class ClassB {  
  void runSomething() {  
//         System.out.println(ClassA.pri); // private 접근 불가  
      System.out.println(ClassA.def);  
      System.out.println(ClassA.pro);  
      System.out.println(ClassA.pub);  
  }  

  static void runStaticThing() {  
      System.out.println(ClassA.def);  
      System.out.println(ClassA.pro);  
      System.out.println(ClassA.pub);  
  }  

  public static void main(String[] args) {  
      new ClassA().runSomething();  
      ClassA.runStaticThing();  
  }  
}
  • runSomeThing() => def, pro, pub
  • runStaticThing()=> def, pro, pub

 

ClassAA (같은 패키지, 상속)

public class ClassAA extends ClassA {  

    public void runSomething() {  
//         System.out.println(pri); // private 접근 불가  
        System.out.println(def);  
        System.out.println(pro);  
        System.out.println(pub);  
    }  

    public static void runStaticThing() {  
        System.out.println(def);  
        System.out.println(pro);  
        System.out.println(pub);  
    }  

    public static void main(String[] args) {  
        new ClassA().runSomething();  
        ClassA.runStaticThing();  
    }  
}
  • runSomeThing() => def, pro, pub
  • runStaticThing()=> def, pro, pub

 

ClassAB (다른 패키지, 상속)

import Practice.Practice1.ClassA;  

public class ClassAB extends ClassA {  
    public void runSomething() {  
        System.out.println("runSomething()");  
        System.out.println(pro); // ClassA 로부터 상속받았으므로 접근 가능  
        System.out.println(pub);  

        System.out.println(ClassA.pro);
        System.out.println(ClassA.pub);  

        ClassAB ab = new ClassAB();  
        System.out.println(ClassAB.pro); // ClassA로부터 상속받았으므로 접근 가능  
        System.out.println(ClassAB.pub);  
    }  

    public static void runStaticThing() {  
        System.out.println("runStaticThing()");  
        // 클래스 레벨에서 접근 가능  
        System.out.println(ClassA.pro); //상속받은 클래스에서도 클래스 이름으로 접근 가능  
        System.out.println(ClassA.pub);  
    }  

    public static void main(String[] args) {  
        new ClassAB().runSomething();  
        ClassAB.runStaticThing();  
    }  
}
    • runSomeThing() => pro, pub
    • runStaticThing()=> pro, pub

static 메서드에서도 Protected 속성에 접근한다는 점이 이전 non-static 속성과 다르다.
static 멤버는 클래스에서 관리하므로 static 메서드가 클래스에 속하여 인스턴스와 관계가 없더라도 상속받았음을 알고 접근할 수 있다.

 

ClassC (다른 패키지)

package Practice.Practice2;  
import Practice.Practice1.ClassA;  

public class ClassC {  
    void runSomething() {  
        System.out.println(ClassA.pub);  
    }  

    static void runStaticThing() {  
        System.out.println(ClassA.pub);  
    }  

    public static void main(String[] args) {  
        new ClassAB().runSomething();  
        ClassAB.runStaticThing();  
    }  
}
  • runSomething() => pub
  • runStaticThing()=> pub

 

기억할 부분

  • 상속을 받지 않았다면 객체 멤버는 객체를 생성한 후 객체 참조 변수를 이용해 접근해야한다.
  • 정적 멤버는 클래스명.정적멤버 형식으로 접근하는 것을 권장한다.
    • 클래스 인스턴스들의 공통된 특성이므로 클래스가 관리한다. 클래스로 접근하자.
    • 메모리의 물리적 접근 측면에서 바로 클래스에서 접근하는 것이 스택과 힙을 거쳐 클래스로 값을 접근하는 것보다 효율적이다.

 

참조 변수의 복사

  • Call By Value(값에 의한 호출)
  • Call By Reference(참조에 의한 호출) = Call By Address(주소에 의한 호출)

 

Call By Value

public class CallByValue {
    public static void main(String[] args){
        int a = 10;
        int b = a; // Call By Value

        a = 20

        System.out.println(a); // 10
        System.out.println(b); // 20
    }

}
  • a가 가진 값이 b에 복사된다.

 

Call By Reference

public class CallByReference{
  public static void main(String[] args){
      Animal ref_a = new Animal();
      Animal ref_b = ref_a; // Call By Reference

      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;
}
  • ref_a가 가진 주소가 ref_b에 복사된다.
    • 두 변수가 같은 객체를 참조한다.

즉, 기본 자료형 변수는 저장하고 있는 값을 그 자체로 판단하고, 참조 변수는 저장하고 있는 값을 주소로 판단한다.

 

기억할 부분

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

 

결론

: 캡슐화 (정보 은닉) - 접근 제어자 public, protected, default, private
: 상속(재사용과 확장) - extends, implements

  • 상속 : 하위 클래스 is kind of 상위 클래스, 분류도
  • 인터페이스 : 클래스 is able to 인터페이스

: 추상화(모델링) - class, objects

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

: 다형성(사용편의성) - 오버라이딩, 오버로딩

  • 오버로딩 : 함수명 하나를 가지고 인자 목록만 다르게 하면 된다.
  • 오버라이딩 : 하위 클래스가 재정의한 메서드를 알아서 호출해준다.

 

Reference

https://m.yes24.com/Goods/Detail/17350624

 

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

자바 엔터프라이즈 개발을 편하게 해주는 오픈소스 경량 애플리케이션 프레임워크인 스프링은 자바와 객체 지향이라는 기반 위에 굳건히 세워져 있다. 따라서 스프링을 제대로 이해하고 활용

m.yes24.com

+ 책 내용 기반으로 추가적으로 내용을 작성했는데 오개념이 있다면 지적 부탁드립니다~!!

728x90