Skip to content

Latest commit

 

History

History
215 lines (193 loc) · 17.6 KB

java-the-oop-part-1.md

File metadata and controls

215 lines (193 loc) · 17.6 KB

Java의 정석 객체지향 프로그래밍 1 내용 정리

객체지향 기본

  • 객체지향이론의 기본 개념: '실제 세계는 사물(객체)로 이루어져 있으며, 발생하는 모든 사건들은 사물간의 상호작용이다.'

    • 실제 객체지향이론의 발단은 모의실험(simulation)에서 왔다. 그러니 현실 세계와 완전히 똑같은 복제본이라기 보다는 유사한 가상 세계를 구현하는 것이라고 보는 것이 더 좋겠다. (이렇게 이해하면 객체지향 프로그래밍을 할 때, 걸림돌이 되기 쉽기 때문이다.)
    • 실제 사물의 속성과 기능을 분석해서 데이터와 함수로 정의해서 실제 세계를 모방하는 것이다.
  • 이 이론은 현재에 이르기까지 상속, 추상화, 캡슐화 개념을 바탕으로 발전했다.

    • 자바 이전에도 많은 객체지향 언어들이 발표되었지만, Java가 인터넷의 발전과 함께 크게 유행해서 현재의 입지를 차지하고 있다.
  • 객체지향 언어는 '절차적(procedural)' 언어와 대비되곤 한다. 절차지향이 아니다.

    • 코드간에 관계를 맺어줄 수 있게 되었다.
      • 코드의 재사용성이 높다. (기존 코드를 쉽게 재사용할 수 있다.)
      • 코드의 관리가 용이하다. (관게를 통해 이해할 수 있다.)
    • 신뢰성이 높은 프로그래밍을 가능하게 한다. (코드 중복으로 인한 오동작을 사전에 막을 수 있다.)
    • 이를 요약하면 '코드의 재사용성이 높고, 유지보수하기 용이하다.'
      • 상속, 다형성, 캡슐화 등의 개념을 이것과 연결시켜서 학습해야 한다. (+ 중복 제거)
  • 초보자가 처음 부터 객체지향적으로 프로그래밍하기는 어렵다. 먼저 동작하는 프로그램을 만들고, 이를 어떻게 객체지향적으로 만들 수 있을지 고민하여 개선하는 것이 그 다음이다.

  • 클래스란

    • 객체를 정의해놓은 것, 객체의 설계도 또는 틀, 청사진
    • 프로그래밍적으로는 데이터와 함수를 결합한 것이다. 변수와 함수를 유기적으로 연결해서 작업을 좀 더 사람이 이해하기 쉽게 한 것이다.
  • 객체란

    • 유형적인 것 뿐만 아니라, 무형적인 것도 포함하는 실재하는 무엇
    • 클래스의 정의대로 메모리에 생성된 것
  • 따라서 우리가 실제 필요한 것은 객체이지 클래스가 아님. 대신 클래스를 잘 만들어야 객체를 만들기가 수월해짐.

  • 클래스로 부터 객체를 만드는 것을 클래스의 인스턴스화라고 하며, 클래스로 부터 만들어진 객체를 그 클래스의 인스턴스라고 한다.

    • 객체는 보다 포괄적인 의미이고, 인스턴스는 좀 더 구체적인 의미라고 볼 수 있다.
  • 객체는 속성과 기능으로 이루어져 있으며, 보통 멤버 변수, 메서드로 표현한다.

  • 보통 객체는 클래스 타입으로 정의된 참조변수를 선언하고 거기에 인스턴스를 생성해서 접근할 수 있도록 한다.
    간단히 말해서 아래와 같은 형식으로 생성한다. (물론 Class는 예약어여서 동작하진 않는다.)

    Class variable = new Class();
  • 해당 클래스를 다루려면 해당 클래스의 참조변수가 필요하다. 이는 '인터페이스'에 대해서 공부하면 더 알 수 있다.

  • 객체 배열은 사실 참조변수의 배열이다.

변수와 메서드

  • 변수의 종류
    • 선언된 위치에 따라 변수의 종류가 결정된다.
    • 멤버 변수를 제외한 나머지 변수는 모두 지역변수다.
    • 멤버 변수에 static이 붙었으면 클래스 변수, 아니면 인스턴스 변수다.
    • 인스턴스 변수는 인스턴스 생성 시점에 만들어진다. 인스턴스 마다 독립적인 값을 가진다.
    • 클래스 변수는 모든 인스턴스들이 공유한다. 모두가 공통된 값을 가져야 하는 경우 선언할 수 있다.
    • 클래스 변수는 클래스로도 접근이 가능하다. 그리고 인스턴스로 접근할 수도 있지만, 헷갈리기 때문에 지양한다.
    • 클래스 변수는 클래스가 메모리 영역에 로딩되는 시점에 생성된다. public을 붙이면 전역 변수의 성격을 갖는다. 자바엔 전역변수가 없지만 전역변수처럼 쓸 수 있다는 의미다.
    • 지역변수는 해당 블록 내에서만 사용가능하며, 블록을 벗어나면 소멸되어 사용할 수 없게 된다.
  • 메서드
    • 메서드는 특정 작업을 수행하는 일련의 문장을 하나로 묶은 것
    • 메서드가 어떤 과정을 거쳐서 결과를 만들어내는지 전혀 몰라도 된다. '인터페이스'와 관계있다.
    • 메서드를 사용하는 이유
      • 높은 재사용성
      • 중복된 코드의 제거
      • 프로그램의 구조화(작업 단위로 나눠서 프로그램의 구조를 단순화 시킨다.)
    • 선언부와 구현부
      • 선언부는 보통 메서드 시그니처라고도 한다.
        • 메서드의 이름, 매개변수, 반환 타입을 가지고 어떤 값을 필요로 하고, 어떤 값을 반환하는지 알려주는 역할이다.
        • 후에 변경사항이 발생하지 않도록 신중히 작성한다. (호출되는 부분까지 전부 변경해야하기 때문이다.) => 리팩토링 도구를 활용하는 방법도 있다.
        • 매개변수는 필요한 값들을 입력받기 위해 필요한 것이다. 값을 입력받을 필요가 없다면 입력하지 않아도 된다.
        • 메서드의 이름은 동사로 짓는게 일반적이다. 변수와 마찬가지로 의미있는 이름을 사용한다.
      • 구현부는 메서드를 호출했을 때, 사용되는 문장들을 작성한다.
        • 리턴타입이 void가 아닌경우 return문이 반드시 있어야 한다. 그리고 return 되는 값은 리턴타입과 같은 타입이거나 자동 형변환 가능해야 한다.
        • 사실 return문은 필수다. void 타입은 컴파일러가 알아서 넣어준다.
        • 리턴 값은 단 하나만 허용된다.
    • 메서드의 호출
      • 메서드이름(인자1, 인자2); 와 같은 식으로 호출할 수 있다.
      • 반환 값이 있다면 해당 타입 변수에 저장할 수 있다.
      • 메서드는 호출되면 호출된 메서드를 실행하고 다시 원래 실행하던 메서드로 돌아온다.
    • 매개변수의 유효성 검사도 필요하다.
      • 메서드를 작성하는 사람은 '호출하는 쪽에서 어떤 값을 넘겨줄지 모른다'고 가정하고 작성하는게 맞다. 가능한 모든 경우의 수에 대해서 고민하고, 특히 타입으로 넘어오는 경우 null 값임에 주의한다.

JVM의 메모리 구조

이 부분이 왜 객체지향 파트에 있어서 어렵게 만드는지 모르겠다.

JVM의 메모리 영역은 다음과 같이 구분할 수 있다.

  • 메서드 영역(method area)
    • 클래스 파일을 읽어서 클래스에 대한 정보를 저장한다.
    • 클래스 변수도 이 영역에 저장된다.
  • 힙(heap)
    • 인스턴스가 생성되는 공간이다.
    • GC의 대상이다.
  • 호출 스택(call stack)
    • 호출 스택은 메서드의 작업에 필요한 메모리 공간을 제공한다.
    • 메서드가 작업을 수행하는 동안 지역변수들과 연산의 중간결과 등을 저장하는데 사용된다.
    • 메서드가 작업을 마치면 할당되었던 메모리 공간은 반환되어 비워진다.
    • 스택이라는 것에서 유추할 수 있듯이 메서드는 실행된 순서대로 차곡차곡 쌓이고, 이것이 한계에 도달하면 스택오버플로우(StackOverflow)가 발생하게 된다.
    • 호출 스택을 조사하면 메서드 간의 호출관계와 현재 수행중인 메서드가 어느 것인지도 알 수 있게된다.
      • 호출 스택의 맨 위의 메서드가 현재 수행중인 메서드이고, 그 아래에 위치한 메서드가 해당 메서드를 호출한 메서드다.

기본형 매개변수와 참조형 매개변수

  • Call By Value 얘기라고도 한다.
  • 기본적으로 기본형의 경우에는 값을 복사하지만, 참조형 매개변수는 인스턴스 주소가 복사되기 때문에 여기서 오는 차이가 있다.
  • 기본형 매개변수는 값을 읽어오는 것만 가능하지만, 참조형 매개변수는 값을 읽고 변경할 수 있다.
    • 여기서 변경가능한 것은 멤버변수의 얘기다. 인스턴스 자체를 바꾸는 것은 불가능하다.
  • 이를 이용해서 반환값이 있는 메서드를 없는 메서드로 바꿀 수 있다.
  • 이를 이용해서 여러 개의 반환값을 리턴하는 메서드로 만들 수도 있다.
  • 반환 타입이 참조형이라는 얘기는 메서드가 '객체의 주소'를 반환한다는 것이다.

재귀호출

  • 메서드 내부에서 메서드 자신을 다시 호출하는 것
  • 재귀 호출은 메서드가 call by value로 동작하기 때문에 복사된 값으로 작업해서 독립적으로 작업이 가능하다.
  • 재귀함수는 탈출 조건이 반드시 있어야한다. 없으면 무한루프에 빠지게 된다.
  • 보통 반복문보다 재귀호출이 더 느린경향이 있다.(메서드를 계속해서 생성해야 하므로)
  • 그럼에도 불구하고 재귀호출을 사용하는 이유는 논리적 간결함 때문이다.
  • 반복문과 조건문으로 복잡해진 반복구조를 재귀구초로 풀어내면 간단해지는 경우가 있다.
  • 이는 읽기 좋은 코드와도 연관이 있다. 유지보수하기가 더 편해지는 코드가 되는 것이다.
  • 반복적으로 처리하는 작업을 상대하는 요령은 먼저 반복문으로 접근해보고, 너무 복잡해질 때 재귀호출로 접근하는 것이다.
    • 재귀호출은 비효율적임을 명심하고, 간결함이 주는 이득이 클 때 적용하도록 하자.
  • 재귀는 계속해서 쓰다보면 익숙해지는 것이지, 처음부터 이해하고 사용가능한 것이 아니다.

클래스 메서드(static 메서드)와 인스턴스 메서드

  • 메서드에 static 키워드가 붙어있으면 클래스 메서드다. 없으면 인스턴스 메서드다.
  • 클래스 메서드를 정하는 기준이 무엇일까?
    • 인스턴스 메서드는 인스턴스 변수와 관련된 작업을 하는 메서드다.
    • 인스턴스와 관계 없는 작업(인스턴스 변수나 인스턴스 메서드를 사용하지 않는)을 하는 메서드를 클래스 메서드로 정의한다. 반드시는 아니지만 특별한 이유가 없다면 그렇게 하는 것이 일반적이다.
  • 클래스 설계 요령
    • 멤버변수 중 모든 인스턴스에서 공통으로 사용하는 것을 클래스 변수로 만든다.(static 변수)
    • 클래스 변수는 인스턴스를 생성하지 않아도 사용할 수 있음을 알고있어야 한다.
    • 클래스 메서드(static 메서드)는 인스턴스 변수를 사용할 수 없다.
    • 메서드 내에서 인스턴스 변수를 사용하지 않는다면 static을 붙이는 것을 고려한다.
      • 메서드 호출시간이 짧아지므로 성능이 향상되기 때문이다.
      • 붙이지 않으면 실행시 호출 될 메서드를찾아야 하므로 시간이 더 걸리는 편이다.
  • 인스턴스 멤버가 존재하는 시점에 클래스 멤버는 항상 존재하지만, 클래스 멤버가 존재하는 시점에 인스턴스 멤버가 존재하지 않을 수도 있기 때문에 주의한다.
    • 이런 경우가 발생한다면, 인스턴스 메서드여야 하는 메서드를 클래스 메서드로 만든 것은 아닌지 생각해본다.
  • 인스턴스 멤버간의 호출은 아무런 문제가 없다. 생성되는 시점의 차이를 잘 알고 있어야 한다.

오버로딩

  • 메서드는 메서드 명과, 매개변수로 구별될 수 있다. 따라서 같은 메서드 명을 가진 메서드를 한 클래스 안에 여러개 만들 수 있는데, 이것이 메서드 오버로딩이라고 하는 것이다.
  • 오버로딩의 조건
    • 메서드 이름이 같아야 한다.
    • 매개변수의 개수 또는 타입이 달라야 한다.
    • 리턴타입은 영향을 미치지 않는다.(메서드 시그니처가 구분해주는 것이 아님)
      • 어떤 메서드가 호출될 지 컴파일러가 모르기 때문이다.
  • 오버로딩의 장점
    • 같은 기능을 하는 메서드를 모두 동일한 이름으로 정의할 수 있다.
      • 정의하는 쪽은 메서드 명을 짓느라 골치아프지 않아도 된다.
      • 사용하는 쪽은 메서드를 전부 외우지 않아도 된다.
  • 가변인자(varargs)와 오버로딩
    • JDK1.5부터 매개변수 개수를 동적으로 지정해줄 수 있다.
    • Type... 변수명의 형식으로 선언한다.
    • 가변인자 외의 매개변수가 더 있다면, 가변인자를 마지막에 작성해준다.
    • 배열도 인자로 사용할 수 있다. (정확히는 가변인자가 내부적으로 배열로 처리된다.)
    • 편리하지만 비효율적인 면이 눈에 들어오지 않으므로 꼭 필요한 경우에만 사용하도록 한다.
    • 배열을 사용하는 것과의 차이는 불필요하게 null이나 길이가 0인 배열을 사용하지 않아도 된다.
    • 주의점
      • 가변인자를 선언한 메서드를 오버로딩하면, 메서드를 호출했을 때, 구분이 되지 않는 경우가 발생하기 쉽다. 가급적이면 가변인자를 사용한 메서드는 오버로딩하지 말자.

생성자

  • 생성자란 인스턴스가 생성될 때 호출되는 '인스턴스 초기화 메서드'이다.
  • 인스턴스가 생성되는 시점에 실행되어야 하는 작업을 위해서도 사용된다.
  • 메서드와 구조가 유사하지만, 리턴 값이 존재하지 않는다.
  • 생성자의 이름은 클래스 이름과 같아야 한다.
  • 생성자도 오버로딩이 가능하다.
  • 모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 한다.
  • 기본 생성자
    • 컴파일러가 기본 생성자를 제공한다.
    • 기본 생성자는 매개변수도 없고 아무런 내용도 없다.
    • 정의된 생성자가 없는 경우에만 생성해주기 때문에 주의한다.
  • 매개변수가 있는 생성자
    • 보통 값을 넘겨받아 인스턴스의 멤버변수의 값을 초기화하는데 사용한다.
    • 생성과 동시에 초기화가 가능하기 때문에 인스턴스 별로 다른 값을 가져야하는 경우 유용하다.
    • 직접 값을 세팅하는 것보다 간결하다.
    • 다양한 생성자를 정의해서 별도로 초기화를 하지 않아도 되도록 하는 것이 바람직하다.
  • 생성자에서 다른 생성자 호출하기 this()
    • 생성자의 이름으로 클래스 이름 대신 this()를 사용한다.
    • 한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 호출이 가능하다.
      • 초기화 작업 도중에 다른 생성자를 호출하면 이전 작업이 무의미해질 가능성이 있기 때문이다.
    • this를 사용해서 인스턴스 변수와 생성자의 매개변수 명이 같을 때 구분지어줄 수 있다.
    • this는 참조변수로 인스턴스 자신을 가리킨다. this를 사용할 수 있는 것은 인스턴스 멤버뿐이다.
    • 모든 인스턴스 메서드에는 this가 지역변수로 숨겨진 채로 존재한다.
  • 생성자를 이용한 인스턴스의 복사
    • Object 클래스의 clone()을 이용하면 간단히 복사할 수 있다.
    • 무작정 새로 코드를 작성하는 것 보다 기존의 코드를 활용할 수 없는지 고민해야한다.
    • 생성자를 잘 활용하면 간결하고 직관적인, 객체지향 코드를 작성할 수 있다.
  • 인스턴스를 생성할 때는 다음의 2가지 사항을 결정한다.
    • 클래스: 어떤 클래스의 인스턴스를 생성할 것인가?
    • 생성자: 선택한 클래스의 어떤 생성자로 인스턴스를 생성할 것인가?

변수의 초기화

  • 변수를 선언하고 처음으로 값을 저장하는 것
  • 가능하면 선언과 동시에 적절한 값으로 초기화 하는 것이 바람직함.
  • 멤버 변수는 자동으로 기본값으로 초기화된다. 하지만 지역변수는 사용하기 전 반드시 초기화를 해야한다.
    • 기본값은 알아두는 것이 좋다.
  • 멤버변수의 초기화 방법
    • 명시적 초기화
      • 기본적이면서도 간단한 초기화 방법이므로 가장 우선적으로 고려되어야 한다.
    • 생성자
      • 보다 복잡한 초기화 작업이 필요한 경우 사용
      • 인스턴스 변수의 초기화에 많이 사용
    • 초기화 블럭
      • 인스턴스 초기화 블럭: 인스턴스 변수를 초기화하는데 사용
        • 생성자보다 먼저 수행된다.
        • 모든 생성자에서 공통으로 수행돼야 하는 코드를 넣는데 사용된다.
      • 클래스 초기화 블럭: 클래스 변수를 초기화하는데 사용
        • 클래스가 메모리에 처음 로딩될 때 한 번만 수행된다.
  • 항상 코드의 중복을 제거하기 위해 노력하자.
    • 코드의 신뢰성을 높여주고, 오류의 발생가능성을 낮춰준다. (재사용성 높이고, 중복은 줄인다.)
    • 객체지향프로그래밍의 궁극적인 목표
  • 멤버변수의 초기화 시기와 순서
    • 클래스 변수
      • 초기화 시점: 클래스가 처음 로딩될 때
      • 초기화 순서: 기본값 -> 명시적 초기화 -> 클래스 초기화 블럭
    • 인스턴스 변수
      • 초기화 시점: 인스턴스가 생성될 때 마다 인스턴스 별로 개별적으로
      • 초기화 순서: 기본값 -> 명시적 초기화 -> 인스턴스 초기화 블럭 -> 생성자
    • 클래스의 로딩 시기는 JVM마다 조금씩 다르다.