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

김성혁·2021년 2월 27일
1

객체 지향의 4대 특성 : "캡! 상추다"

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

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

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

"객체 지향의 추상화는 곧 모델이다" 라는 관점

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

여기서 모델은 실제 사물을 정확히 복제하는 게 아니라 목적에 맞게 관심 있는 특성만을 추출해서 표현하는 것이다. 즉 추상화를 통해 실제 사물을 단순하게 묘사하는 것이다. 이런 모델링(추상화)은 객체 지향에서 클래스를 설계할 때 필요한 기법이고 또한 데이터베이스의 테이블을 설계할 때 필요한 기법이다.

  • OOP의 추상화는 모델링이다.
  • 클래스 : 객체 = 펭귄 : 뽀로로
  • 클래스 설계에서 추상화가 사용된다.
  • 클래스 설계를 위해서는 애플리케이션 경계부터 정해야 한다.
  • 객체 지향에서 추상화의 결과는 클래스다.
    • 상속을 통한 추상화, 구체화
    • 인터페이스를 통한 추상화
    • 다형성을 통한 추상화

객체 : 세상에 존재하는 유일무이한 사물

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

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

static 변수

  • 클래스 [멤버] 속성, 정적 변수, 정적 속성 ...
  • 스태틱 영역

인스턴스 변수

  • 객체 [멤버] 속성, 객체 변수 ...
  • 힙 영역

local 변수

  • 지역 변수
  • 스택 영역(스택 프레임 내부)

상속

  • 재사용확장
  • 객체 지향의 상속은 상위 클래스의 특성을 재사용하는 것
  • 객체 지향의 상속은 상위 클래스의 특성을 확장하는 것
  • 객체 지향의 상속은 is a kind of 관계를 만족해야 한다.
  • 상속은 확장, 세분화, 슈퍼클래스 - 서브 클래스(상위 클래스 - 하위 클래스) 개념
  • 상위 클래스 쪽으로 갈수록 추상화, 일반화됐다고 말하며, 하위 클래스 쪽으로 갈수록 구체화, 특수화됐다고 말함.
  • 상속의 개념은 조직도나 계층도가 아닌 분류도다!!
  • "하위 클래스는 상위 클래스다"가 통해야 한다.

객체 참조 변수명은 객체스럽게, 클래스명은 클래스명답게 정하는 습관을 들이기

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 kind of 상위 클래스
  • 해석: 하위 클래스는 상위 클래스의 한 분류다.
  • 예제: 고래는 동물의 한 분류다.
  • 다중 상속을 허용하지 않는다.

인터페이스

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

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

상속과 T 메모리

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

다형성

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

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

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.println("어머 내 이름은 알아서 뭐하게요?");
	}

	// 오버로딩 - 중복정의: 같은 메서드 이름, 다른 인자 리스트
	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 pingu = new Penguin();

		pingu.name = "핑구";
		pingu.showName(); // 어머 내 이름은 알아서 뭐하게요? 

		Animal pingu2 = new Animal();
		pingu2.name = "핑구2"
		pingu2.showName(); // 안녕 나는 핑구2야. 반가워
	}
}

상위 클래스 타입의 객체 참조 변수를 사용하더라도 하위 클래스에서 오버라이딩(재정의)한 메서드가 호출된다.

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

오버라이딩을 통한 메서드 재정의, 오버로딩을 통한 메서드 중복 정의를 통해 다형성을 제공함으로써 사용편의성을 준다.

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

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

		for(int i = 0; i < 동물들.length; i++) {
			동물들[i].울어보세요();
		}
	}
}

//출력
나는 쥐!!!
나는 고양이!야옹!야옹!
나는 강아지!!!
나는 송아지!음메!음메!

캡슐화: 정보은닉


접근 제어자는 절대 단순하지 않다.

package encapsulation01.packageOne;

public class ClassA {
	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;

	void runSomething() {
		pri = 1;
		this.pri = 1;
		def = 1;
		this.def = 1;
		pro = 1;
		this.pro = 1;
		pub = 1;
		this.pub = 1;

		// 정적 멤버는 클래스명.정적멤버 형태의 접근을 권장
		ClassA.priStatic = 1;
		priStatic = 1;
		this.priStatic = 1;
		ClassA.defStatic = 1;
		defStatic = 1;
		this.defStatic = 1;
		ClassA.proStatic = 1;
		proStatic = 1;
		this.proStatic = 1;
		ClassA.pubStatic = 1;
		pubStatic = 1;
		this.pubStatic = 1;
	}

	static void runStaticThing() {
		// 객체를 생성하지 않고는 객체 멤버 접근 불가
		// pri = 1; this.pri = 1;
		// def = 1; this.def = 1;
		// pro = 1; this.pro = 1;
		// pub = 1; this.pub = 1;

		// 정적 멤버는 클래스명.정적멤버 형태의 접근을 권장
		ClassA.priStatic = 1;
		priStatic = 1; // this.priStatic = 1;
		ClassA.defStatic = 1;
		defStatic = 1; // this.defStatic = 1;
		ClassA.proStatic = 1;
		proStatic = 1; // this.proStatic = 1;
		ClassA.pubStatic = 1;
		pubStatic = 1; // this.pubStatic = 1;

		// 객체 멤버를 객체 생성 후에 객체 참조 변수를 통해 접근 가능
		ClassA ca = new ClassA();
		ca.pri = 1;
		ca.def = 1;
		ca.pro = 1;
		ca.pub = 1;

		// 객체 참조 변수를 통해 정적 멤버도 접근 가능, 권장하지는 않음
		ca.priStatic = 1;
		ca.defStatic = 1;
		ca.proStatic = 1;
		ca.pubStatic = 1;
	}
}

package encapsulation01.packageOne;

public class ClassAA extends ClassA {	
	void runSomething() {
		//pri = 1;	this.pri = 1;
		def = 1;	this.def = 1;
		pro = 1;	this.pro = 1;
		pub = 1;	this.pub = 1;
		
		// 정적 멤버는 클래스명.정적멤버 형태의 접근을 권장
		//ClassA.priStatic = 1;	priStatic = 1;	this.priStatic = 1;
		ClassA.defStatic = 1;	defStatic = 1;	this.defStatic = 1;
		ClassA.proStatic = 1;	proStatic = 1;	this.proStatic = 1;
		ClassA.pubStatic = 1;	pubStatic = 1;	this.pubStatic = 1;
	}
	
	static void runStaticThing() {
		// 객체를 생성하지 않고는 객체 멤버 접근 불가
		//pri = 1;	this.pri = 1;
		//def = 1;	this.def = 1;
		//pro = 1;	this.pro = 1;
		//pub = 1;	this.pub = 1;
		
		// 정적 멤버는 클래스명.정적멤버 형태의 접근을 권장
		//ClassA.priStatic = 1;	priStatic = 1;	//this.priStatic = 1;
		ClassA.defStatic = 1;	defStatic = 1;	//this.defStatic = 1;
		ClassA.proStatic = 1;	proStatic = 1;	//this.proStatic = 1;
		ClassA.pubStatic = 1;	pubStatic = 1;	//this.pubStatic = 1;
		
		// 객체 멤버를 객체 생성 후에 객체 참조 변수를 통해 접근 가능
		ClassAA caa = new ClassAA();
		//ca.pri = 1;
		caa.def = 1;
		caa.pro = 1;
		caa.pub = 1;
		
		// 객체 참조 변수를 통해 정적 멤버도 접근 가능, 권장하지는 않음
		//ca.priStatic = 1;
		caa.defStatic = 1;
		caa.proStatic = 1;
		caa.pubStatic = 1;
	}
}

package encapsulation01.packageOne;

public class ClassB {
	void runSomething() {
		// 상속을 받지 않았기에 ClassA 의 객체 멤버는 객체 생성 후에 접근 가능
		//pri = 1;	this.pri = 1;
		//def = 1;	this.def = 1;
		//pro = 1;	this.pro = 1;
		//pub = 1;	this.pub = 1;
		
		// 정적 멤버는 클래스명.정적멤버 형태의 접근을 권장
		//ClassA.priStatic = 1;	//priStatic = 1;	this.priStatic = 1;
		ClassA.defStatic = 1;	//defStatic = 1;	this.defStatic = 1;
		ClassA.proStatic = 1;	//proStatic = 1;	this.proStatic = 1;
		ClassA.pubStatic = 1;	//pubStatic = 1;	this.pubStatic = 1;

		// 객체 멤버를 객체 생성 후에 객체 참조 변수를 통해 접근 가능
		ClassA ca = new ClassA();
		//ca.pri = 1;
		ca.def = 1;
		ca.pro = 1;
		ca.pub = 1;
	}
	
	static void runStaticThing() {
		// 객체를 생성하지 않고는 객체 멤버 접근 불가
		//pri = 1;	this.pri = 1;
		//def = 1;	this.def = 1;
		//pro = 1;	this.pro = 1;
		//pub = 1;	this.pub = 1;
		
		// 정적 멤버는 클래스명.정적멤버 형태의 접근을 권장
		//ClassA.priStatic = 1;	//priStatic = 1;	//this.priStatic = 1;
		ClassA.defStatic = 1;	//defStatic = 1;	//this.defStatic = 1;
		ClassA.proStatic = 1;	//proStatic = 1;	//this.proStatic = 1;
		ClassA.pubStatic = 1;	//pubStatic = 1;	//this.pubStatic = 1;
		
		// 객체 멤버를 객체 생성 후에 객체 참조 변수를 통해 접근 가능
		ClassA ca = new ClassA();
		//ca.pri = 1;
		ca.def = 1;
		ca.pro = 1;
		ca.pub = 1;
		
		// 객체 참조 변수를 통해 정적 멤버도 접근 가능, 권장하지는 않음
		//ca.priStatic = 1;
		ca.defStatic = 1;
		ca.proStatic = 1;
		ca.pubStatic = 1;
	}
}

package encapsulation01.packageTwo;

import encapsulation01.packageOne.ClassA;

public class ClassAB  extends ClassA {	
	void runSomething() {
		//pri = 1;	this.pri = 1;
		//def = 1;	this.def = 1;
		pro = 1;	this.pro = 1;
		pub = 1;	this.pub = 1;
		
		// 정적 멤버는 클래스명.정적멤버 형태의 접근을 권장
		//ClassA.priStatic = 1;	priStatic = 1;	this.priStatic = 1;
		//ClassA.defStatic = 1;	defStatic = 1;	this.defStatic = 1;
		ClassA.proStatic = 1;	proStatic = 1;	this.proStatic = 1;
		ClassA.pubStatic = 1;	pubStatic = 1;	this.pubStatic = 1;
	}
	
	static void runStaticThing() {
		// 객체를 생성하지 않고는 객체 멤버 접근 불가
		//pri = 1;	this.pri = 1;
		//def = 1;	this.def = 1;
		//pro = 1;	this.pro = 1;
		//pub = 1;	this.pub = 1;
		
		// 정적 멤버는 클래스명.정적멤버 형태의 접근을 권장
		//ClassA.priStatic = 1;	priStatic = 1;	//this.priStatic = 1;
		//ClassA.defStatic = 1;	defStatic = 1;	//this.defStatic = 1;
		ClassA.proStatic = 1;	proStatic = 1;	//this.proStatic = 1;
		ClassA.pubStatic = 1;	pubStatic = 1;	//this.pubStatic = 1;
		
		// 객체 멤버를 객체 생성 후에 객체 참조 변수를 통해 접근 가능
		ClassAB cab = new ClassAB();
		//ca.pri = 1;
		//ca.def = 1;
		cab.pro = 1;
		cab.pub = 1;
		
		// 객체 참조 변수를 통해 정적 멤버도 접근 가능, 권장하지는 않음
		//ca.priStatic = 1;
		//cab.defStatic = 1;
		cab.proStatic = 1;
		cab.pubStatic = 1;
	}
}

package encapsulation01.packageTwo;

import encapsulation01.packageOne.ClassA;

public class ClassC {
	void runSomething() {
		// 상속을 받지 않았기에 ClassA 의 객체 멤버는 객체 생성 후에 접근 가능
		//pri = 1;	this.pri = 1;
		//def = 1;	this.def = 1;
		//pro = 1;	this.pro = 1;
		//pub = 1;	this.pub = 1;
		
		// 정적 멤버는 클래스명.정적멤버 형태의 접근을 권장
		//ClassA.priStatic = 1;	//priStatic = 1;	this.priStatic = 1;
		//ClassA.defStatic = 1;	//defStatic = 1;	this.defStatic = 1;
		//ClassA.proStatic = 1;	//proStatic = 1;	this.proStatic = 1;
		ClassA.pubStatic = 1;	//pubStatic = 1;	this.pubStatic = 1;

		// 객체 멤버를 객체 생성 후에 객체 참조 변수를 통해 접근 가능
		ClassA ca = new ClassA();
		//ca.pri = 1;
		//ca.def = 1;
		//ca.pro = 1;
		ca.pub = 1;
	}
	
	static void runStaticThing() {
		// 객체를 생성하지 않고는 객체 멤버 접근 불가
		//pri = 1;	this.pri = 1;
		//def = 1;	this.def = 1;
		//pro = 1;	this.pro = 1;
		//pub = 1;	this.pub = 1;
		
		// 정적 멤버는 클래스명.정적멤버 형태의 접근을 권장
		//ClassA.priStatic = 1;	//priStatic = 1;	//this.priStatic = 1;
		//ClassA.defStatic = 1;	//defStatic = 1;	//this.defStatic = 1;
		//ClassA.proStatic = 1;	//proStatic = 1;	//this.proStatic = 1;
		ClassA.pubStatic = 1;	//pubStatic = 1;	//this.pubStatic = 1;
		
		// 객체 멤버를 객체 생성 후에 객체 참조 변수를 통해 접근 가능
		ClassA ca = new ClassA();
		//ca.pri = 1;
		//ca.def = 1;
		//ca.pro = 1;
		ca.pub = 1;
		
		// 객체 참조 변수를 통해 정적 멤버도 접근 가능, 권장하지는 않음
		//ca.priStatic = 1;
		//ca.defStatic = 1;
		//ca.proStatic = 1;
		ca.pubStatic = 1;
	}

}
  • 상속을 받지 않았다면 객체 멤버는 객체를 생성한 후 객체 참조 변수를 이용해 접근해야 한다.
  • 정적 멤버는 클래스명.정적멤버 형식으로 접근하는 것을 권장한다.
    • 사람.인구, 고양이.다리개수 형식으로 접근하는 것이 홍길동.인구수, 키티.다리개수 형식으로 접근하는 것보다 권장

참조 변수의 복사

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

0개의 댓글