OOP 특징

1. 캡슐화

  • 속성과 기능을 하나의 객체인 클래스로 묶는 것
  • 클래스로 묶어 정보은닉 가능
    • 정보은닉 : 기능을 제공하고 상세한 구현은 감추는 것
  • 외부의 영향 없이 객체 내부 구현을 유연하게 변경 가능
class Account {
	private Membership membership;
	private LocalDate expDate;

	public boolean hasRegularPermission() {
		return membership == REGULAR && expDate.isAfter(now())
	}
}
  • 정회원의 조건이 달라지면 외부 코드는 변경할 필요 없이 hasRegularPermission()만 수정하면 된다

캡슐화를 위한 규칙

1. Tell, Don’t Ask - 데이터를 달라고 요청하지 말고 해달라고 말하기

// Bad
if (acc.getMembership() == REGULAR) {
	...
}

// Good
if (acc.hasRegularPermission()) {
	...
}


2. Demeter’s Law - 메서드 하나만 호출하는 방식

  • 메서드에서 생성한 객체의 메서드만 호출해라
  • 파라미터로 받은 객체의 메서드만 호출해라
  • 필드로 참조하는 객체의 메서드만 호출해라
// Bad
acc.getExpDate().isAfter(now);

// Good
acc.isValid(now);

2. 추상화

  • 공통의 기능을 묶어 필요한 정보만 간략하게 한 것
  • 추상 타입을 사용하여 구현체의 변경에 유연해 질 수 있다 (OCP, 디자인패턴 - 전략패턴)
interface Notifier {
	public void notify();
}

class KakaoNotifier implements Notifier {
	@Override
	public void notify() {
		// kakao 사용
	}
}

class SmsNotifier implements Notifier {
	@Override
	public void notify() {
		// SMS 사용
	}
}
// 주문취소
public void cancel(String no) {
	Notifier notifier = getNotifier(...);
	notifier.notify();

	private Notifier getNotifier(...) {
		if(...) {
			return new KakaoNotifier();
		} else {
			return new SmsNotifier();
		}
	}
}
  • 구현체가 변경되어도 기존의 코드를 변경할 필요가 없어진다

3. 상속

  • 자손클래스가 부모클래스의 속성과 기능을 물려 받는 것

  • 단점
    1. 상위 클래스 변경이 어려움 : 상위 클래스의 변경이 모든 하위 클래스의 변경을 이끔
    2. 클래스 수 증가
    3. 상속 오용
  • 단점을 극복하기 위해 조립(Composition) 사용 가능

4. 다형성

  • 부모 타입의 참조변수로 자손 타입의 인스턴스를 참조할 수 있는 것
    Tv t = new CaptionTv() : 참조변수타입-Tv / 인스턴스타입-CaptionTv
class Tv {
	boolean power;
	int channel;

	void power();		// 구현 생략..
	void channelUp();
	void channelDown();
}

class CaptionTv extends Tv {
	String text;

	void caption();		// 구현 생략..
}

class Main {
	public static void main(String args[]) {
		Tv t = new CaptionTv();			// t는 Tv멤버만 사용 가능
		CaptionTv c = new CaptionTv();	// c는 CaptionTv멤버 모두 사용 가능

		t.text;		// 사용 불가능
		c.text;		// 사용 가능

		CaptionTv c2 = (CaptionTv) t;

		c2.text;	// 사용 가능
	}
}
  • 참조변수의 타입에 따라 사용할 수 있는 멤버의 갯수가 달라진다
  • 참조변수가 사용 할 수 있는 멤버의 갯수는 인스턴스의 멤버 갯수보다 같거나 작아야 한다

캐스팅

  • 자식 -> 부모(업캐스팅) : 오토캐스팅 가능
  • 부모 -> 자식(다운캐스팅) : 오토캐스팅 불가능 (명시적 캐스팅을 해야 한다)
  • 형변환은 참조변수의 타입을 변환하는 것이지 인스턴스를 변환하는 것이 아니기 때문에 참조변수의 형변환 인스턴스에 아무런 영향을 미치지 않는다
  • 참조변수의 형변환을 통해 참조하고 있는 인스턴스에서 사용할 수 있는 멤버의 갯수를 조절하는 것 뿐이다

  • 바인딩 : 부모클래스와 자손클래스가 동일한 멤버를 소유 하는 경우에 참조변수와 객체의 관계
  • 멤버변수 - 참조변수의 자료형
  • 멤버메서드 - 오버라이딩된 메서드
  • ex) 참조변수가 부모 타입 or 어느 타입이여도 오버라이딩된 메서드 실행

final 제어자

  • 클래스 : 상속 X
  • 메서드 : 오버라이딩 X
  • 변수 : 수정 X (단, 명시적 초기화를 하지 않은 경우 생성자에서 한번 가능)

추상클래스

  • 변수 + 추상메서드 + 일반메서드

  • 추상클래스의 상속
    • 추상클래스 간의 상속 가능 (다중상속 X)
    • 추상클래스 간의 상속시 상속 받은 추상클래스를 꼭 정의할 필요 X
    • 추상클래스를 상속받은 추상클래스를 일반클래스에서 구현할 때 상속받은 모든 추상클래스를 구현해야 함.
  • 추상메서드가 하위메서드에서 구현되면 상위(추상)클래스에서 사용가능. (∵ 오버라이딩 된 메서드는 타입에 상관없이 실행됨)

인터페이스(접근제어자 : public)

  • 상수 + 추상메서드 + 일반메서드&static메서드(From 8버전)

  • 기본값
    • 추상메서드 : public abstract
    • 상수 : public static final
    • 인터페이스를 하위클래스에 구현할 때 접근제어자는 public만 가능함
  • 추상클래스의 일종
  • 인터페이스간의 다중상속 가능(extends 사용)    ex) interface in3 extends in1, in2

  • JDK 1.8부터 추상메서드와 상수뿐만 아니라 static메서드와 default메서드 추가 가능.
       (default메서드의 경우 default를 꼭 명시해줘야함 (∵ 기본값은 public abstract))
추상클래스 인터페이스
추상메서드 가능(선언부만 있고 구현부가 없음)
일반메서드도 가능(구현부 존재)
인스턴스생성 불가
상속받은 하위클래스에서 반드시 구현필요
다중상속 X 다중상속 O
변수 가능 상수만 가능
public 이외의 접근제어자도 가능 public만 가능

내부 클래스

  • 특징
    • 자료형으로 사용됨, 객체화 가능, Object의 하위 클래스임.
    • 멤버(변수, 메서드, 생성자) 소유가능.
    • 외부 클래스의 멤버임 => 외부클래스의 private 접근 가능.
  1. 인스턴스 내부클래스 - 인스턴스, 상수 O / 스태틱 정의 X           
    • 상수 : final static => static이지만 예외로 사용가능
  2. 스태틱 내부클래스 - 인스턴스, 상수, 스태틱 정의 O

  3. 지역 내부클래스 - 인스턴스, 상수 O / 스태틱 정의 X
    • 지역 내부클래스 안의 메서드의 지역변수는 상수(final)여야함. => 메서드 안에서 값 변경X
    • 값을 변경하고 싶으면 외부클래스의 멤버변수로 작성.
  4. 익명 내부클래스 - 클래스없이 객체화 가능, 추상클래스&인터페이스 객체화 가능.
public class InnerEx1 {

	public static void main(String[] args) {
		Outer.StaticInner si = new Outer.StaticInner();
		System.out.println(si.iv);
		System.out.println(Outer.StaticInner.cv);
		System.out.println(Outer.StaticInner.MAX);
		
//		Outer.InstanceInner ii = new Outer.InstanceInner();
		Outer.InstanceInner ii = new Outer().new InstanceInner();
		
		Outer outer = new Outer();
		Outer.InstanceInner ii2 = outer.new InstanceInner();
		System.out.println(ii2.iv);
		System.out.println(ii2.MAX);
		System.out.println(Outer.InstanceInner.MAX);
		outer.method();
	}

}

class Outer {
	class InstanceInner {	// 인스턴스 내부클래스
		int iv = 100;				    //인스턴스 o
//		static int cv = 10;			    //스태틱 x
		final static int MAX = 200;		//변수 o
	}
	
	static class StaticInner {	// 스태틱 내부클래스
		int iv = 300;				    //인스턴스 o
		static int cv = 400;			//스태틱 o
		final static int MAX = 500;		//변수 o
	}
	
	void method() {	// 인스턴스 메서드
		class LocalInner {	    // 지역 내부클래스
			int iv = 600;			    //인스턴스 o
//			static int cv = 700;		//스태틱 x
			final static int MAX = 700;	//변수 o
		}
		
		LocalInner lc = new LocalInner();		// 지역 내부클래스가 만들어진 메서드 내에서만 사용 O
		System.out.println(lc.iv);
		System.out.println(lc.MAX);
		System.out.println(LocalInner.MAX);
	}
	
	void method2() {
//		LocalInner lc = new LocalInner();		// 지역 내부클래스는 정의되지 않은 다른 메서드에서 사용 X
	}
}
package ch07;

public class InnerEx2 {

	public static void main(String[] args) {
		Outer2 out = new Outer2();
		out.method(1);
		Outer2.InstanceInner i2 = out.new InstanceInner();
		System.out.println(i2.iiv);
		System.out.println(i2.iiv2);
		
		Outer2.StaticInner i3 = new Outer2.StaticInner();
		System.out.println(i3.siv);
		System.out.println(Outer2.StaticInner.scv);
	}

}

class Outer2 {
	private int outeriv = 10;		// 인스턴스 변수(외부)
	private static int outercv = 20; // 스태틱 변수(외부)
	class InstanceInner {			// 인스턴스 내부클래스 
		int iiv = outeriv;
		int iiv2 = outercv;
	}
	static class StaticInner {		// 스태틱 내부클래스
		int siv = new Outer2().outeriv;
		static int scv = outercv;
	}
	static StaticInner cv = new StaticInner();
	InstanceInner iv = new InstanceInner();
	static InstanceInner cv2 = new Outer2().new InstanceInner();
	static StaticInner iv2 = new StaticInner();
	
	
	void method(final int pv) {				//지역내부클래스에서 사용되는 메서드의 지역변수는 final여야함.
		int i = 0;
//		pv++;			//변경불가 (cuz, final)
		class LocalInner {			// 지역 내부클래스
			int liv = outeriv;				
			int liv2 = outercv;				
			void method() {					
//				i = 100;					//지역내부클래스에 속한 메서드 안에서 지역변수 변경 X(final로 여겨짐)
//				pv++;	//변경불가 (cuz, final)
				System.out.println("pv = " + pv);
				System.out.println(liv + ", " + liv2);
				System.out.println(outeriv + ", " + outercv);
			}
		}
		LocalInner li = new LocalInner();
		li.method();
	}
	
}