-
객체지향이론의 기본 개념: '실제 세계는 사물(객체)로 이루어져 있으며, 발생하는 모든 사건들은 사물간의 상호작용이다.'
- 실제 객체지향이론의 발단은 모의실험(simulation)에서 왔다. 그러니 현실 세계와 완전히 똑같은 복제본이라기 보다는 유사한 가상 세계를 구현하는 것이라고 보는 것이 더 좋겠다. (이렇게 이해하면 객체지향 프로그래밍을 할 때, 걸림돌이 되기 쉽기 때문이다.)
- 실제 사물의 속성과 기능을 분석해서 데이터와 함수로 정의해서 실제 세계를 모방하는 것이다.
-
이 이론은 현재에 이르기까지 상속, 추상화, 캡슐화 개념을 바탕으로 발전했다.
- 자바 이전에도 많은 객체지향 언어들이 발표되었지만, Java가 인터넷의 발전과 함께 크게 유행해서 현재의 입지를 차지하고 있다.
-
객체지향 언어는 '절차적(procedural)' 언어와 대비되곤 한다. 절차지향이 아니다.
- 코드간에 관계를 맺어줄 수 있게 되었다.
- 코드의 재사용성이 높다. (기존 코드를 쉽게 재사용할 수 있다.)
- 코드의 관리가 용이하다. (관게를 통해 이해할 수 있다.)
- 신뢰성이 높은 프로그래밍을 가능하게 한다. (코드 중복으로 인한 오동작을 사전에 막을 수 있다.)
- 이를 요약하면 '코드의 재사용성이 높고, 유지보수하기 용이하다.'
- 상속, 다형성, 캡슐화 등의 개념을 이것과 연결시켜서 학습해야 한다. (+ 중복 제거)
- 코드간에 관계를 맺어줄 수 있게 되었다.
-
초보자가 처음 부터 객체지향적으로 프로그래밍하기는 어렵다. 먼저 동작하는 프로그램을 만들고, 이를 어떻게 객체지향적으로 만들 수 있을지 고민하여 개선하는 것이 그 다음이다.
-
클래스란
- 객체를 정의해놓은 것, 객체의 설계도 또는 틀, 청사진
- 프로그래밍적으로는 데이터와 함수를 결합한 것이다. 변수와 함수를 유기적으로 연결해서 작업을 좀 더 사람이 이해하기 쉽게 한 것이다.
-
객체란
- 유형적인 것 뿐만 아니라, 무형적인 것도 포함하는 실재하는 무엇
- 클래스의 정의대로 메모리에 생성된 것
-
따라서 우리가 실제 필요한 것은 객체이지 클래스가 아님. 대신 클래스를 잘 만들어야 객체를 만들기가 수월해짐.
-
클래스로 부터 객체를 만드는 것을 클래스의 인스턴스화라고 하며, 클래스로 부터 만들어진 객체를 그 클래스의 인스턴스라고 한다.
- 객체는 보다 포괄적인 의미이고, 인스턴스는 좀 더 구체적인 의미라고 볼 수 있다.
-
객체는 속성과 기능으로 이루어져 있으며, 보통 멤버 변수, 메서드로 표현한다.
-
보통 객체는 클래스 타입으로 정의된 참조변수를 선언하고 거기에 인스턴스를 생성해서 접근할 수 있도록 한다.
간단히 말해서 아래와 같은 형식으로 생성한다. (물론Class
는 예약어여서 동작하진 않는다.)Class variable = new Class();
-
해당 클래스를 다루려면 해당 클래스의 참조변수가 필요하다. 이는 '인터페이스'에 대해서 공부하면 더 알 수 있다.
-
객체 배열은 사실 참조변수의 배열이다.
- 변수의 종류
- 선언된 위치에 따라 변수의 종류가 결정된다.
- 멤버 변수를 제외한 나머지 변수는 모두 지역변수다.
- 멤버 변수에
static
이 붙었으면 클래스 변수, 아니면 인스턴스 변수다. - 인스턴스 변수는 인스턴스 생성 시점에 만들어진다. 인스턴스 마다 독립적인 값을 가진다.
- 클래스 변수는 모든 인스턴스들이 공유한다. 모두가 공통된 값을 가져야 하는 경우 선언할 수 있다.
- 클래스 변수는 클래스로도 접근이 가능하다. 그리고 인스턴스로 접근할 수도 있지만, 헷갈리기 때문에 지양한다.
- 클래스 변수는 클래스가 메모리 영역에 로딩되는 시점에 생성된다.
public
을 붙이면 전역 변수의 성격을 갖는다. 자바엔 전역변수가 없지만 전역변수처럼 쓸 수 있다는 의미다. - 지역변수는 해당 블록 내에서만 사용가능하며, 블록을 벗어나면 소멸되어 사용할 수 없게 된다.
- 메서드
- 메서드는 특정 작업을 수행하는 일련의 문장을 하나로 묶은 것
- 메서드가 어떤 과정을 거쳐서 결과를 만들어내는지 전혀 몰라도 된다. '인터페이스'와 관계있다.
- 메서드를 사용하는 이유
- 높은 재사용성
- 중복된 코드의 제거
- 프로그램의 구조화(작업 단위로 나눠서 프로그램의 구조를 단순화 시킨다.)
- 선언부와 구현부
- 선언부는 보통 메서드 시그니처라고도 한다.
- 메서드의 이름, 매개변수, 반환 타입을 가지고 어떤 값을 필요로 하고, 어떤 값을 반환하는지 알려주는 역할이다.
- 후에 변경사항이 발생하지 않도록 신중히 작성한다. (호출되는 부분까지 전부 변경해야하기 때문이다.) => 리팩토링 도구를 활용하는 방법도 있다.
- 매개변수는 필요한 값들을 입력받기 위해 필요한 것이다. 값을 입력받을 필요가 없다면 입력하지 않아도 된다.
- 메서드의 이름은 동사로 짓는게 일반적이다. 변수와 마찬가지로 의미있는 이름을 사용한다.
- 구현부는 메서드를 호출했을 때, 사용되는 문장들을 작성한다.
- 리턴타입이
void
가 아닌경우return
문이 반드시 있어야 한다. 그리고 return 되는 값은 리턴타입과 같은 타입이거나 자동 형변환 가능해야 한다. - 사실
return
문은 필수다.void
타입은 컴파일러가 알아서 넣어준다. - 리턴 값은 단 하나만 허용된다.
- 리턴타입이
- 선언부는 보통 메서드 시그니처라고도 한다.
- 메서드의 호출
메서드이름(인자1, 인자2);
와 같은 식으로 호출할 수 있다.- 반환 값이 있다면 해당 타입 변수에 저장할 수 있다.
- 메서드는 호출되면 호출된 메서드를 실행하고 다시 원래 실행하던 메서드로 돌아온다.
- 매개변수의 유효성 검사도 필요하다.
- 메서드를 작성하는 사람은 '호출하는 쪽에서 어떤 값을 넘겨줄지 모른다'고 가정하고 작성하는게 맞다. 가능한 모든 경우의 수에 대해서 고민하고, 특히 타입으로 넘어오는 경우
null
값임에 주의한다.
- 메서드를 작성하는 사람은 '호출하는 쪽에서 어떤 값을 넘겨줄지 모른다'고 가정하고 작성하는게 맞다. 가능한 모든 경우의 수에 대해서 고민하고, 특히 타입으로 넘어오는 경우
이 부분이 왜 객체지향 파트에 있어서 어렵게 만드는지 모르겠다.
JVM의 메모리 영역은 다음과 같이 구분할 수 있다.
- 메서드 영역(method area)
- 클래스 파일을 읽어서 클래스에 대한 정보를 저장한다.
- 클래스 변수도 이 영역에 저장된다.
- 힙(heap)
- 인스턴스가 생성되는 공간이다.
- GC의 대상이다.
- 호출 스택(call stack)
- 호출 스택은 메서드의 작업에 필요한 메모리 공간을 제공한다.
- 메서드가 작업을 수행하는 동안 지역변수들과 연산의 중간결과 등을 저장하는데 사용된다.
- 메서드가 작업을 마치면 할당되었던 메모리 공간은 반환되어 비워진다.
- 스택이라는 것에서 유추할 수 있듯이 메서드는 실행된 순서대로 차곡차곡 쌓이고, 이것이 한계에 도달하면 스택오버플로우(StackOverflow)가 발생하게 된다.
- 호출 스택을 조사하면 메서드 간의 호출관계와 현재 수행중인 메서드가 어느 것인지도 알 수 있게된다.
- 호출 스택의 맨 위의 메서드가 현재 수행중인 메서드이고, 그 아래에 위치한 메서드가 해당 메서드를 호출한 메서드다.
- Call By Value 얘기라고도 한다.
- 기본적으로 기본형의 경우에는 값을 복사하지만, 참조형 매개변수는 인스턴스 주소가 복사되기 때문에 여기서 오는 차이가 있다.
- 기본형 매개변수는 값을 읽어오는 것만 가능하지만, 참조형 매개변수는 값을 읽고 변경할 수 있다.
- 여기서 변경가능한 것은 멤버변수의 얘기다. 인스턴스 자체를 바꾸는 것은 불가능하다.
- 이를 이용해서 반환값이 있는 메서드를 없는 메서드로 바꿀 수 있다.
- 이를 이용해서 여러 개의 반환값을 리턴하는 메서드로 만들 수도 있다.
- 반환 타입이 참조형이라는 얘기는 메서드가 '객체의 주소'를 반환한다는 것이다.
- 메서드 내부에서 메서드 자신을 다시 호출하는 것
- 재귀 호출은 메서드가 call by value로 동작하기 때문에 복사된 값으로 작업해서 독립적으로 작업이 가능하다.
- 재귀함수는 탈출 조건이 반드시 있어야한다. 없으면 무한루프에 빠지게 된다.
- 보통 반복문보다 재귀호출이 더 느린경향이 있다.(메서드를 계속해서 생성해야 하므로)
- 그럼에도 불구하고 재귀호출을 사용하는 이유는 논리적 간결함 때문이다.
- 반복문과 조건문으로 복잡해진 반복구조를 재귀구초로 풀어내면 간단해지는 경우가 있다.
- 이는 읽기 좋은 코드와도 연관이 있다. 유지보수하기가 더 편해지는 코드가 되는 것이다.
- 반복적으로 처리하는 작업을 상대하는 요령은 먼저 반복문으로 접근해보고, 너무 복잡해질 때 재귀호출로 접근하는 것이다.
- 재귀호출은 비효율적임을 명심하고, 간결함이 주는 이득이 클 때 적용하도록 하자.
- 재귀는 계속해서 쓰다보면 익숙해지는 것이지, 처음부터 이해하고 사용가능한 것이 아니다.
- 메서드에
static
키워드가 붙어있으면 클래스 메서드다. 없으면 인스턴스 메서드다. - 클래스 메서드를 정하는 기준이 무엇일까?
- 인스턴스 메서드는 인스턴스 변수와 관련된 작업을 하는 메서드다.
- 인스턴스와 관계 없는 작업(인스턴스 변수나 인스턴스 메서드를 사용하지 않는)을 하는 메서드를 클래스 메서드로 정의한다. 반드시는 아니지만 특별한 이유가 없다면 그렇게 하는 것이 일반적이다.
- 클래스 설계 요령
- 멤버변수 중 모든 인스턴스에서 공통으로 사용하는 것을 클래스 변수로 만든다.(
static
변수) - 클래스 변수는 인스턴스를 생성하지 않아도 사용할 수 있음을 알고있어야 한다.
- 클래스 메서드(
static
메서드)는 인스턴스 변수를 사용할 수 없다. - 메서드 내에서 인스턴스 변수를 사용하지 않는다면
static
을 붙이는 것을 고려한다.- 메서드 호출시간이 짧아지므로 성능이 향상되기 때문이다.
- 붙이지 않으면 실행시 호출 될 메서드를찾아야 하므로 시간이 더 걸리는 편이다.
- 멤버변수 중 모든 인스턴스에서 공통으로 사용하는 것을 클래스 변수로 만든다.(
- 인스턴스 멤버가 존재하는 시점에 클래스 멤버는 항상 존재하지만, 클래스 멤버가 존재하는 시점에 인스턴스 멤버가 존재하지 않을 수도 있기 때문에 주의한다.
- 이런 경우가 발생한다면, 인스턴스 메서드여야 하는 메서드를 클래스 메서드로 만든 것은 아닌지 생각해본다.
- 인스턴스 멤버간의 호출은 아무런 문제가 없다. 생성되는 시점의 차이를 잘 알고 있어야 한다.
- 메서드는 메서드 명과, 매개변수로 구별될 수 있다. 따라서 같은 메서드 명을 가진 메서드를 한 클래스 안에 여러개 만들 수 있는데, 이것이 메서드 오버로딩이라고 하는 것이다.
- 오버로딩의 조건
- 메서드 이름이 같아야 한다.
- 매개변수의 개수 또는 타입이 달라야 한다.
- 리턴타입은 영향을 미치지 않는다.(메서드 시그니처가 구분해주는 것이 아님)
- 어떤 메서드가 호출될 지 컴파일러가 모르기 때문이다.
- 오버로딩의 장점
- 같은 기능을 하는 메서드를 모두 동일한 이름으로 정의할 수 있다.
- 정의하는 쪽은 메서드 명을 짓느라 골치아프지 않아도 된다.
- 사용하는 쪽은 메서드를 전부 외우지 않아도 된다.
- 같은 기능을 하는 메서드를 모두 동일한 이름으로 정의할 수 있다.
- 가변인자(varargs)와 오버로딩
- JDK1.5부터 매개변수 개수를 동적으로 지정해줄 수 있다.
Type... 변수명
의 형식으로 선언한다.- 가변인자 외의 매개변수가 더 있다면, 가변인자를 마지막에 작성해준다.
- 배열도 인자로 사용할 수 있다. (정확히는 가변인자가 내부적으로 배열로 처리된다.)
- 편리하지만 비효율적인 면이 눈에 들어오지 않으므로 꼭 필요한 경우에만 사용하도록 한다.
- 배열을 사용하는 것과의 차이는 불필요하게
null
이나 길이가 0인 배열을 사용하지 않아도 된다. - 주의점
- 가변인자를 선언한 메서드를 오버로딩하면, 메서드를 호출했을 때, 구분이 되지 않는 경우가 발생하기 쉽다. 가급적이면 가변인자를 사용한 메서드는 오버로딩하지 말자.
- 생성자란 인스턴스가 생성될 때 호출되는 '인스턴스 초기화 메서드'이다.
- 인스턴스가 생성되는 시점에 실행되어야 하는 작업을 위해서도 사용된다.
- 메서드와 구조가 유사하지만, 리턴 값이 존재하지 않는다.
- 생성자의 이름은 클래스 이름과 같아야 한다.
- 생성자도 오버로딩이 가능하다.
- 모든 클래스에는 반드시 하나 이상의 생성자가 정의되어 있어야 한다.
- 기본 생성자
- 컴파일러가 기본 생성자를 제공한다.
- 기본 생성자는 매개변수도 없고 아무런 내용도 없다.
- 정의된 생성자가 없는 경우에만 생성해주기 때문에 주의한다.
- 매개변수가 있는 생성자
- 보통 값을 넘겨받아 인스턴스의 멤버변수의 값을 초기화하는데 사용한다.
- 생성과 동시에 초기화가 가능하기 때문에 인스턴스 별로 다른 값을 가져야하는 경우 유용하다.
- 직접 값을 세팅하는 것보다 간결하다.
- 다양한 생성자를 정의해서 별도로 초기화를 하지 않아도 되도록 하는 것이 바람직하다.
- 생성자에서 다른 생성자 호출하기
this()
- 생성자의 이름으로 클래스 이름 대신
this()
를 사용한다. - 한 생성자에서 다른 생성자를 호출할 때는 반드시 첫 줄에서만 호출이 가능하다.
- 초기화 작업 도중에 다른 생성자를 호출하면 이전 작업이 무의미해질 가능성이 있기 때문이다.
this
를 사용해서 인스턴스 변수와 생성자의 매개변수 명이 같을 때 구분지어줄 수 있다.this
는 참조변수로 인스턴스 자신을 가리킨다.this
를 사용할 수 있는 것은 인스턴스 멤버뿐이다.- 모든 인스턴스 메서드에는
this
가 지역변수로 숨겨진 채로 존재한다.
- 생성자의 이름으로 클래스 이름 대신
- 생성자를 이용한 인스턴스의 복사
Object
클래스의clone()
을 이용하면 간단히 복사할 수 있다.- 무작정 새로 코드를 작성하는 것 보다 기존의 코드를 활용할 수 없는지 고민해야한다.
- 생성자를 잘 활용하면 간결하고 직관적인, 객체지향 코드를 작성할 수 있다.
- 인스턴스를 생성할 때는 다음의 2가지 사항을 결정한다.
- 클래스: 어떤 클래스의 인스턴스를 생성할 것인가?
- 생성자: 선택한 클래스의 어떤 생성자로 인스턴스를 생성할 것인가?
- 변수를 선언하고 처음으로 값을 저장하는 것
- 가능하면 선언과 동시에 적절한 값으로 초기화 하는 것이 바람직함.
- 멤버 변수는 자동으로 기본값으로 초기화된다. 하지만 지역변수는 사용하기 전 반드시 초기화를 해야한다.
- 기본값은 알아두는 것이 좋다.
- 멤버변수의 초기화 방법
- 명시적 초기화
- 기본적이면서도 간단한 초기화 방법이므로 가장 우선적으로 고려되어야 한다.
- 생성자
- 보다 복잡한 초기화 작업이 필요한 경우 사용
- 인스턴스 변수의 초기화에 많이 사용
- 초기화 블럭
- 인스턴스 초기화 블럭: 인스턴스 변수를 초기화하는데 사용
- 생성자보다 먼저 수행된다.
- 모든 생성자에서 공통으로 수행돼야 하는 코드를 넣는데 사용된다.
- 클래스 초기화 블럭: 클래스 변수를 초기화하는데 사용
- 클래스가 메모리에 처음 로딩될 때 한 번만 수행된다.
- 인스턴스 초기화 블럭: 인스턴스 변수를 초기화하는데 사용
- 명시적 초기화
- 항상 코드의 중복을 제거하기 위해 노력하자.
- 코드의 신뢰성을 높여주고, 오류의 발생가능성을 낮춰준다. (재사용성 높이고, 중복은 줄인다.)
- 객체지향프로그래밍의 궁극적인 목표
- 멤버변수의 초기화 시기와 순서
- 클래스 변수
- 초기화 시점: 클래스가 처음 로딩될 때
- 초기화 순서: 기본값 -> 명시적 초기화 -> 클래스 초기화 블럭
- 인스턴스 변수
- 초기화 시점: 인스턴스가 생성될 때 마다 인스턴스 별로 개별적으로
- 초기화 순서: 기본값 -> 명시적 초기화 -> 인스턴스 초기화 블럭 -> 생성자
- 클래스의 로딩 시기는 JVM마다 조금씩 다르다.
- 클래스 변수