Posts 학원 #23일차: 다형성 문법(오버로딩, 오버라이딩), 캡슐화 문법
Post
Cancel

학원 #23일차: 다형성 문법(오버로딩, 오버라이딩), 캡슐화 문법

객체지향적 관점

1. 피연산자와 연산자의 관점

  • 메서드 == 연산자
  • 인스턴스 + 파라미터 == 피연산자
  • 데이터에 대해서 연산을 가하는 개념

2. 사람처럼 생각

  • 주어 == 인스턴스, 동사 == 메서드
  • 모든 객체들을 사람처럼 생각하고 다룬다. 메서드를 객체에게 명령을 내리는 것으로 생각한다. (인스턴스는 변수 덩어리이기 때문에 메서드는 명령일 뿐이다. 인스턴스 안에 메서드가 있는 것이 아니다!)
  • 프로그램 자체가 하나의 이야기가 된다.

오버로딩

메서드 오버로딩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// function prototype = method signature
int plus(int, int); 
// float plus(float, float);
// err: conflicting types for 'plus'
// c언어에서는 같은 이름의 함수 만들 수 없다. 
float plusf(float, float);

int main() {
    int result = plusf(100f, 200f);
    printf("%d\n", result);
    return 0;
}
int plus(int a, int b){
    return a + b;
}
float plusf(float a, float b){
    return a + b;
}

위와 같이 c에서는 오버로딩을 허락하지 않는다.

메서드 시그너처: 함수의 이름파라미터 타입리턴 타입을 가리킨다. c에서는 function prototype이라고 부른다.

그러나 자바에서는 파라미터의 타입이나 개수가 다르더라도 같은 일을 하는 메서드에 대해서는 같은 이름을 갖게 한다. 프로그램의 일관성을 확보하는 문법이 오버로딩이다.

오버로딩 조건

  1. 메서드의 이름이 같다.
  2. 파라미터의 데이터타입이나 순서, 개수가 다르다.
  3. 같은 클래스와 상위 클래스에 있는 메서드에 대해서 오버로딩을 할 수 있다.
  • 주의: 파라미터 변수명, 리턴 타입과는 상관이 없다.

오버로딩 사례

  • System.out.println(): 다양한 파라미터 값을 받음
  • Integer.valueOf(): 외부에서 값을 받아 Integer 객체를 생성
1
2
3
4
5
6
7
System.out.println(100);
System.out.println(true);
System.out.println("hello");

Integer obj1 = Integer.valueOf(100);
Integer obj2 = Integer.valueOf("100");
Integer obj3 = Integer.valueOf("64", 16);

오버라이딩

주의

하위 클래스의 인스턴스로 다른 클래스에서 오버라이딩 이전의 멤버(필드, 메서드)를 사용할 수 없다. 오버라이딩의 목적 자체가 하위 클래스에 적절하게 메서드를 변형하는 것이기 때문에 애초에 오버라이딩 이전의 것을 꺼낼 필요가 없기 때문이다. 하위 클래스에 있는 인스턴스 메서드의 built-in 변수인 super를 통해 꺼내는 방법 뿐이다. 다만, 필드의 경우 상위 클래스로 형변환을 시키면 오버라이딩 전의 필드를 사용할 수 있다. 그러나 인스턴스 필드를 오버라이딩 하는 것은 헷갈리고 복잡하기 때문에 실무에서 보기 힘들다.

1
2
3
4
5
6
7
8
obj1.name = "홍길동";
obj1.age = "20";
//obj1.age = 20; => 컴파일오류
//obj1.super.age = 20; => 컴파일 오류!
((A3)obj1).age = 20;
// 형변환 한 레퍼런스의 클래스를 기준으로 인스턴스 변수를 찾는다.
obj1.print();
obj1.print3();

인스턴스 필드와 달리 메서드의 경우 상위 레퍼런스가 하위 인스턴스를 가리킬 경우 호출하는 메서드는 그 인스턴스의 클래스에서 찾는다. 없다면 상위 클래스로 따라 올라가면서 찾는다.

1
2
3
4
5
6
7
8
9
10
11
  ((X3)x4).m1(); // x4.m1()
  ((X2)x4).m1(); // x4.m1()
  ((X)x4).m1(); // x4.m1()

  X3 x3 = x4;
  X2 x2 = x4;
  X x = x4;

  x3.m1(); // x4.m1()
  x2.m1(); // x4.m1()
  x.m1();  // x4.m1()

메서드 오버라이딩

배경

부모 클래스로부터 상속 받은 메서드를 재정의할 수 없어서 새 메서드를 만들어야 한다면,같은(또는 유사한) 일을 하는 메서드에 대해 다른 이름으로 메서드를 만들어야 하기 때문에 개발자는 여러 개의 메서드를 암기해야 하는 번거로움이 생긴다. 이런 문제를 해결하기 위해 등장한 문법이 “오버라이딩(overriding)”이다.

정의

오버라이딩이란 부모로부터 상속 받은 메서드 중에서 자신(서브클래스)의 역할에 맞지 않는 메서드가 있다면, 자신(서브클래스)의 역할에 맞춰 상속받은 메서드를 재정의하여 프로그램의 일관성을 확보하는 문법이다.

방법

부모 클래스의 메서드와 같은 시그너처(signature)를 갖는 메서드를 만든다.

메서드 시그너처(method signature) = 함수 프로토타입(function prototype): 메서드명, 파라미터 타입/개수, 리턴 타입

내장변수 super

하위 클래스의 인스턴스 메서드에는 자기 자신의 주소를 참조하는 this와, 인스턴스 자신 중에서도 상위 클래스에서 상속받은 멤버만을 참조하는 super가 있다.

필드 오버라이딩

  • this.필드명: 현재 클래스에서 해당 필드를 찾는다. 없으면 상위 클래스로 따라 올라가면서 찾는다.
  • super.필드명: 상위 클래스에서부터 해당 필드를 찾는다. 없으면 계속 상위 클래스로 따라 올라간다.

메서드 오버라이딩

  • this.메서드() 호출: 현재 클래스부터 호출할 메서드를 찾아 올라간다.
  • super.메서드() 호출: 부모 클래스부터 호출할 메서드를 찾아 올라간다.

@Override

  • 컴파일러야, 내가 상속받은 메서드를 재정의한다고 했는데, 혹시 실수는 없는지 검사해 줄래?
  • 오버라이딩을 하려다 파라미터의 타입이나 개수, 순서를 달리해서 오버로딩이 되는 경우가 있다. 이를 방지하기 위해 오버라이딩을 하는 메서드 앞에 @Override 애노테이션을 붙임으로써 잘못 사용하는 경우를 방지할 수 있다. 오버라이딩을 제대로 했는지 컴파일러에게 검사하도록 요청하는 것이다.

@는 애노테이션 문법으로, 컴파일러나 JVM에게 전달하는 특별한 주석이다. 개발자도 자신의 애노테이션을 정의하고 사용할 수 있다.

오버로딩 vs 오버라이딩

오버로딩은 같은 일을 하는 메서드에 대해서 (파라미터의 개수나 타입은 다르지만 ) 같은 이름을 부여함으로써 프로그래밍에 일관성을 갖게 하는 문법이다. 오버라이드란 부모 메서드와 상속받은 메서드를 서브 클래스의 역할에 맞게끔 재정의하는 문법이다. (똑같은 시그너처를 갖는 메서드를 만든다.)

멤버의 접근 범위와 오버라이딩

접근제어자 종류

  1. private: 같은 클래스
  2. (default): 같은 클래스 + 같은 패키지
  3. protected: 같은 클래스 + 같은 패키지 + 서브 클래스
  4. public: 모두

접근제어자의 사용

초보 단계에서는 일단 무조건 private으로 만들어 모든 접근을 막는다. 막은 후 오로지 공개할 대상만 public으로 풀어라. 이후 경험이 쌓이면서 default로 할 지, protected로 할 지 판단할 수 있을 것이다.

접근제어자와 오버라이딩

  • 접근 권한이 없는 메서드는 오버라이딩을 할 수 없다.
    • 접근 권한이 없는 메서드를 @Override 없이 오버라이딩을 시도하면, 에러가 뜨지 않는다. 그러나 이 경우는 오버라이딩이 성공적으로 된 것이 아니라 서브클래스에 새로운 메서드가 추가된 것임을 주의하자.
  • 오버라이딩한 메서드는 기존 메서드보다 접근권한을 좁힐 수는 없다.

다형적 변수와 오버라이딩

Exam01: 하위클래스에서 먼저 찾는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// A3 extends A2 extends A

A a = new A();
a.m(); // A의 멤버 호출. OK!
//((A2)a).x(); // A 객체를 A2 객체라 우기면, 컴파일러는 통과! 실행은 오류!
System.out.println("--------------------");

A a3 = new A2(); 
a3.m(); // A2의 m() 호출. 
// 레퍼런스가 하위 클래스의 인스턴스를 가리킬 때, 
// => 레퍼런스를 통해 호출하는 메서드는
//    레퍼런스가 실제 가리키는 하위 클래스에서 찾아 올라간다.

// 그렇다고 해서 A2에서 추가한 메서드를 호출할 수는 없다.
// => 즉 레퍼런스의 클래스를 벗어나서 사용할 수는 없다.
//    컴파일러가 허락하지 않는다.
//a3.x(); // 컴파일 오류!

// 물론 a3가 실제 A2 객체를 가리키기 때문에 
// A2로 형변환을 수행한 후에는 A2의 멤버를 사용할 수 있다.
((A2)a3).x(); // OK!

메서드를 호출할 때는 실제 가리키는 인스턴스의 클래스부터 찾아간다.

final 문법

final class

1
puublic final calss FinalClass {}
  • 클래스에 final을 붙이면 해당 클래스를 상속받을 수 없다.
  • final 클래스를 확장하려 하면 cannot subclass the final class 에러가 뜬다.
  • 예시: public final class String

final method

  • 메서드에 final을 붙이면 오버라이딩 할 수 없다.

  • 오버라이딩 하려고 하면cannot override the final method 에러가 뜬다.
  • 하위 클래스에서 해당 코드를 재정의하지 못하게 강제한다. 따라서 하위클래스에서 상위클래스와 똑같은 시그니처의 메서드를 만들 수 없다.
  • 예시
    • 보안에 관련된 일을 하는 메서드
    • 템플릿 메서드 디자인 패턴에서처럼 전체적인 작업 흐름을 정의한 메서드

final variables

상수 필드
  • final은 필드를 상수 필드로 만든다.
  • 생성자에서 초기화시켜야 한다.
    • 변수 초기화 문장으로도, 인스턴스 초기화 블록에서도 값을 초기화시켜도 되는데, 이는 결국 컴파일 과정에서 생성자에 복사되기 때문이다.
  • 상수 필드는 보통 static field이다.
  • 값을 바꾸지도 못하는 상수를 굳이 인스턴스마다 만들 필요가 없기 때문이다.
  • 공개할 경우 public 으로 선언한다.
로컬 변수
파라미터
  • 파라미터는 메서드가 호출될 때 외부 값을 받는 용도의 변수이다.
  • 메서드 안에서 파라미터 값을 임의로 변경하게 되면 처음 받은 파라미터 값을 사용하지 못하는 상황이 발생한다. 이런 상황을 피하고자 보통 실무에서 파라미터를 final로 선언한다. (방어적인 프로그래밍과 안정성을 중요시하는 개발)

이클립스에는 저장할 때마다 private fields, parameter, local variables에 자동으로 final을 붙일 수 있는 기능이 있다.

캡슐화(encapsulation)

개발자의 의도대로 프로그래밍을 계속 유지할 수 있도록 특정 필드에 접근을 제어할 수 있는 문법이 있다. 유효하지 않은 데이터를 넣게 되면 클래스를 정의한 이유를 상실하게 된다. 이를 방지하기 위해 만든 문법이 “캡슐화(encapsulation)”이다. 인스턴스의 변수에 추상화 목적에 맞는 유효한 값만 넣을 수 있도록 외부 접근을 제한하는 문법이다. ex) 이어폰: 최대 출력을 특정 값 이상 올리지 못하도록 소프트웨어적으로 제약을 가함.

제한 범위

  • private : 클래스 내부에서만 접근 가능
  • (default): 클래스 내부 + 같은 패키지 접근 가능
  • protected: 클래스 내부 + 같은 패키지 + 자식클래스 접근 가능
  • public: 모두 접근 가능!
1
2
3
4
5
6
7
8
9
10
11
12
13
Exam0210 obj4 = new Exam0210();
//obj4.privateVar = 100; // 접근 불가! C 클래스에서만 접근 가능
//obj4.defaultVar = 100; // 접근 불가! C 클래스와 같은 패키지가 아니다.
obj4.protectedVar = 100; // OK! Exam0210은 C의 자식 클래스이며,
// 또한 C로부터 상속 받아서 만든 자기 변수이다.
obj4.publicVar = 100;

C obj3 = new C();
//obj3.privateVar = 100; // 접근 불가! 오직 그 클래스 안에서만 사용 가능.
//obj3.defaultVar = 100; // 접근 불가! 같은 패키지까지만 접근 가능.
//obj3.protectedVar = 100; // 접근 불가! 같은 패키지 또는 자식 클래스 접근 가능
// 자식 클래스인데 접근 불가?
// 이유 => 자기의 인스턴스 변수가 아니다.
  • 자식클래스를 통해서 만든 변수일 경우
  • 자식클래스라고 다 허락한 게 아닌 거
  • // protected : (default) + 자식 클래스에서 자신이 만든 변수일 경우

  • protected: 자식클래스이면서 자식클래스 설계도에 따라 만든 인스턴스일 경우에만 접근 가능하다

    • (default)+ 서브 클래스로 만든 변수인 경우 서브클래스에서 접근 가능!!!!!

getter/setter(필드 접근 제한)

값을 직접 변경하지는 못하더라도 외부에서 간접적으로 값을 조회할 수 있는 방법/수단(method)을 제공해야 한다. 필드의 값을 조회하는 용도로 사용되는 메서드를 getter라고 부른다. 보통 get필드명()형태로 이름을 붙인다. 이렇게 만들어진 getter는 공개모드로 설정한다.

직접 필드의 값을 바꾸게 하지 않고 메서드를 통해 값을 변경하게 유도할 수 있다. 이때 사용하는 메서드를 setter라고 한다. 보통 필드의 값을 바꿀 때 마다 뭔가를 수행해야 하는 경우 사용한다.

즉 필드는 캡슐화 문법을 통해 외부의 접근 제한하고, 세터/게터 메서드를 통해 값을 설정/조회하게 만든다. 이때 게터와 세터를 “프로퍼티(property)“라 부른다. 보통 프로그래밍의 일관성을 위해서 간접적으로 값을 설정하거나 조회하지 않아도 되는 변수까지 캡슐화하고 setter/getter 만들어준다.

확장성을 고려한 프로그래밍: 당장 필요 없지만 나중에 필요하게 될 것을 대비하여 코드를 짜는 것을 말한다. 당장 비가 오지 않는데도 항상 우산을 챙겨다니는 것과 비슷하다. 겟터와 셋터 생성이 대표적이다. 비어 있는 겟터와 셋터는 실질적으로 값을 직접 넣고 수정하는 것과 차이가 없다. 그러나 변수를 직접 사용하면 변수를 제어하는 코드(유효성 검증 코드 등)를 삽입하기 어려워진다. 따라서 나중에 해당 메서드에 코드를 추가할 경우를 대비하기 위해 보통 겟터와 셋터로 변수의 값을 설정하는 방법을 선호한다.

패키지 멤버 클래스

중첩 클래스(nested class)

실무에서의 접근권한 설정

  • 인스턴스 변수는 보통 private으로 접근을 제한한다.
  • 겟터,셋터는 public으로 접근을 모두에게 공개한다.
  • 일반 메서드도 public으로 접근을 모두에게 공개한다.
  • 그 클래스 내부에서만 사용되는 메서드는 private으로 접근을 제한한다.
  • 자식 클래스의 접근을 허용할 필요가 있을 경우에만 protected로 만든다.
  • 다른 개발자가 사용할 클래스 모음을 만들 때 그 모음집 내에서만 사용될 변수나 메서드인 경우 (default)로 접근을 제한한다. 즉 라이브러리를 만드는 개발자인 경우 (default)를 사용하는 경우가 있다.

생성자 대신 객체를 생성해주는 클래스 메서드 사용

인스턴스를 직접 생성하지 않고 클래스 메서드를 통해 인스턴스를 생성하는 이유에는 2가지가 있다.

  1. 인스턴스 생성과정이 복잡하기 때문에 코드의 간결성을 위해서: Factory Method
  2. 클래스당 오직 하나의 인스턴스만 만들어 사용하기 위해서: Singleton Method

Factory Method

객체 생성 과정이 복잡한 경우 사용한다. 인스턴스를 제대로 초기화하는 것이 단지 생성자 하나로 될 문제가 아닐 때 등등이 있다. 그러면 생성자를 private으로 막고 클래스 메서드를 만들어 해당 메서드로 인스턴스를 생성하게끔 유도한다.

1
2
3
인테리어업자 builder = new 인테리어업자();
김밥천국 factory = builder.build();
김밥 obj = factory.create("참치");
1
2
3
4
5
6
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory =
  new SqlSessionFactoryBuilder().build(inputStream);

SqlSession session = sqlSessionFactory.openSession()
java.util.Calendar

대표적인 Factory Method 패턴를 사용한 클래스

  • 보통 개발자가 클래스를 만들 때는 자신 만의 패키지에 넣어서 만든다. java.util 패키지를 자신의 클래스를 두기 위해 사용하지는 않을 것이다. 따라서 Calendar 생성자가 protected로 되어 있다는 것은 개발자가 직접 생성자를 호출하지는 말라는 의미다.
  • 물론 Calendar를 만든 자신들은 나중에 Calendar의 서브 클래스를 만들 때 이 생성자를 직접 사용하겠다는 의도로 생성자를 protected 했음을 알 수 있다. 대신 개발자들이 이 클래스의 인스턴스를 만들 수 있도록 public으로 공개된 스태틱 메서드(getInstance())를 제공한다.
1
2
3
4
5
6
// 생성자
protected Calendar()
    {
        this(TimeZone.getDefaultRef(), Locale.getDefault(Locale.Category.FORMAT));
        sharedZone = true;
    }

OOP vs Functional Programming

객체지향 프로그래밍

  • 코드가 막 몇만단위로 넘어가니까 조직적으로 관리하기 위해 클래스 문법 나옴
  • 큰 시스템에서 소스코드를 효과적으로 관리하기에 좋다.
  • ex) c, c++, java
  • c++: 일반 function은 function, 클래스 안에 있는 건 member function.
  • java: 반면 자바는 절대 글로벌 함수를 만들 수 없다. 함수는 무조건 클래스 안에서만 만들 수 있다.

함수형 프로그래밍

  • 객체보다는 함수 위주의 언어
  • 작은 프로그램인 경우는 클래스까지 다루기보다는 직접 function을 써서 짬
  • 시스템이 웹 기반으로 바뀌면서 함수형 언어들이 인기를 얻게 되었다.
    • 웹의 경우 각각의 페이지가 하나의 작은 프로그램이기 때문이다. (코드는 해당 HTML 페이지만 통제할 수 있다. 따라서 자바스크립트 코드는 짧게 된다.)
    • 따라서 대단위 프로그램을 짜는 데 유용한 클래스가 크게 필요가 없다.
  • ex) javascript

테스트

  • 알파테스트: 테스트팀이 주도하여 시나리오를 작성하고, 의도한 대로 제대로 나오는지 테스트
  • 베타테스트: 사용자에게 직접. 의도하지 않는 대로 막 누를 테니까.
  • 일본/외국은 테스트를 많이 함. 코드량이 훨씬 적음.
This post is licensed under CC BY 4.0 by the author.

학원 #22일차: 상속과 다형성

학원 #24일차: java.lang.Object 메서드

Loading comments from Disqus ...