객체지향 프로그래밍(OOP)
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. 상속
-
자손클래스가 부모클래스의 속성과 기능을 물려 받는 것
- 단점
- 상위 클래스 변경이 어려움 : 상위 클래스의 변경이 모든 하위 클래스의 변경을 이끔
- 클래스 수 증가
- 상속 오용
- 단점을 극복하기 위해 조립(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 접근 가능.
- 인스턴스 내부클래스 - 인스턴스, 상수 O / 스태틱 정의 X
- 상수 : final static => static이지만 예외로 사용가능
-
스태틱 내부클래스 - 인스턴스, 상수, 스태틱 정의 O
- 지역 내부클래스 - 인스턴스, 상수 O / 스태틱 정의 X
- 지역 내부클래스 안의 메서드의 지역변수는 상수(final)여야함. => 메서드 안에서 값 변경X
- 값을 변경하고 싶으면 외부클래스의 멤버변수로 작성.
- 익명 내부클래스 - 클래스없이 객체화 가능, 추상클래스&인터페이스 객체화 가능.
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();
}
}