본문 바로가기

카테고리 없음

이것이 자바다 신용권의 Java 프로그래밍 정복 - 6장

6장

* 해당 글은  "이것이 자바다 신용권의 Java 프로그래밍 정복"을 기반으로 작성하였으며

책의 모든 목차를 다루는 것은 아니기에 내용중에 목차에서 빠진 부분도 있다는점 참고바랍니다.


 

6장 목차

  • 6.1 객체 지향 프로그래밍
  • 6.2 객체와 클래스
  • 6.3 클래스 선언
  • 6.4 객체 생성과 클래스 변수
  • 6.5 클래스의 구성 멤버
  • 6.6 필드
  • 6.7 생성자
  • 6.8 메소드
  • 6.9 인스턴스 멤버와 this
  • 6.10 정적 멤버와 static
  • 6.11 final 필드와 상수
  • 6.12 패키지
  • 6.13 접근 제한자
  • 6.14 Getter와 Setter 메소드
  • 6.15 어노테이션

6.1 객체 지향 프로그래밍

객체 지향 프로그래밍 ( OOP : Object Oriented Programming) 이란,

부품에 해당하는 객체들을 먼저 만들고, 이것들을 하나씩 조립해서 완성된 프로그램을 만드는 기법을 객체 지향 프로그래밍이라고 한다.


6.1.1 객체란?

객체란 물리적으로 존재하거나 추상적으로 생각할 수 있는 것 중에서

자신의 속성을 가지고 있고 다른 것과 식별 가능한 것을 말한다.

 

현실 세계의 객체를 소프트웨어 객체로 설계하는 것을 객체 모델링이라고 한다.

객체 모델링은 현실 세계 객체의 속성과 동작을 추려내어 소프트웨어 객체의 필드와 메소드로 정의하는 과정이다.


6.1.3 객체 간의 관계

객체는 개별적으로 사용될 수 있지만, 대부분 다른 객체와 관계를 맺고있는데 

이 관계의 종류에는 집합 관계, 사용 관계, 상속 관계가 있다. 

 

집합 관계 - 부품과 완성품과의 관계

ex) 자동차, 엔진, 타이어, 핸들이 있다고 하면 자동차와 나머지들은 집합 관계

 

사용 관계 - 객체 간의 상호작용

ex) 사람이 자동차를 사용하므로 사람과 자동차는 사용 관계

 

상속 관계 - 상위(부모)객체를 기반으로 하위(자식) 객체를 생성하는 관계

일반적으로 상위객체는 종류를 의미하고 하위객체는 구체적인 사물에 해당

ex) 자동차는 기계의 종류. ( 자동차 - 하위 , 기계 - 상위 )

 


6.1.4 객체 지향 프로그래밍의 특징

캡슐화

캡슐화란 객체의 필드, 메소드를 하나로 묶고, 실제 구현 내용을 감추는 것을 말한다. 

캡슐화를 하면 외부 객체는 객체 내부의 구조를 알지 못하여 객체가 노출해서 제공하는 필드와 메소드만 이용할 수 있다.

 

캡슐화가 왜 필요할까?

필드와 메소드를 캡슐화를 하는 이유는 외부의 잘못된 사용으로 인해 객체가 손상되지 않도록 하기 위함이다.

 

상속

상위 객체는 자기가 가지고 있는 필드와 메소드를 하위 객체에게 물려주어 하위 객체가 사용할 수 있도록 해준다.

 

상속이 가지는 장점은 무엇일까?

상속을 사용하면 이미 잘 개발된 객체를 재사용해서 새로운 객체를 만들 수 있기 때문에 반복된 코드의 중복도 줄여주고 개발시간도 절약시켜준다. 

또한 상속은 상위 개체의 수정으로 모든 하위 객체들의 수정 효과를 가져올 수 있으므로 유지 보수 시간을 최소화시켜준다.

정말 좋은 놈인 것 같다.

 

다형성

다형성은 같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질을 말한다.

코드 측면에서보면 다형성은 하나의 타입에 여러 객체를 대입함으로써 다양한 기능을 이용할 수 있도록 해준다.

자바는 다형성을 위해 부모 클래스 또는 인터페이스의 타입 변환을 허용한다.

 


6.2 객체와 클래스

자바에서 클래스(class)는 설계도이다.

메모리에서 사용하고 싶은 객체가 있다면 클래스로 객체를 만드는 작업이 필요하다.

 

클래스에는 객체를 생성하기 위한 필드와 메소드가 정의되어있다.

클래스로부터 만들어진 객체를 해당 클래스의 인스턴스(instance) 라고 하며,

클래스토부터 객체를 만드는 과정을 인스턴스화라고 한다.

 

따라서 자동차 객체는 자동차 클래스의 인스턴스라고 말할 수 있으며,

하나의 클래스로부터 여러 개의 인스턴스를 만들 수 있다.


6.3 클래스 선언

소스 파일당 두 개 이상의 클래스 선언도 가능하지만, 일반적으로 하나의 클래스를 선언한다.

가급적이면 소스 파일 하나당 동일한 이름의 클래스 하나를 선언하는 것이 좋다.


6.4 객체 생성과 클래스 변수

클래스로부터 객체를 생성하는 방법은 다음과 같이 new 연산자를 사용하면 된다.

new는 클래스로부터 객체를 생성시키는 연산자이다. 

 

new 연산자로 생성된 객체는 메모리 힙(heap) 영역에 생성된다.

new 연산자는 힙 영역에 객체를 생성시킨 후, 객체의 주소를 리턴한다.

 

같은 클래스로부터 생성되었다고 하더라도 new에 의해 생성되었다면,

완전히 독립된 서로 다른 객체이다.


6.5 클래스의 구성 멤버

클래스 구성 멤버에는 필드, 생성자, 메소드가 있으며 이 구성 멤버들은 생략되거나 복수 개가 작성될 수 있다.


6.9 인스턴스 멤버와 this

인스턴스 멤버란 객체(인스턴스)를 생성한 후 사용할 수 있는 필드와 메소드를 말하며,

이들을 각각 인스턴스 필드, 인스턴스 메소드 라고 부른다.

 

객체 외부에서 인스턴스 멤버에 접근하기 위해 참조 변수를 사용하는 것과 마찬가지로 객체 내부에서도 인스턴스 멤버에 접근하기 위해 this를 사용할 수 있다. 

ex) this.model 은 자신이 가지고있는 model 필드라는 뜻이다.

 


6.10 정적 멤버와 static

정적 멤버란 클래스에 고정된 멤버로서 객체를 생성하지 않고 사용할 수 있는 필드와 메소드를 말한다.

이들을 각각 정적 필드, 정적 메소드라고 부른다.

정적 멤버는 객체(인스턴스)에 소속된 멤버가 아니라 클래스에 소속된 멤버이기 때문에 클래스 멤버라고도 한다.

 


6.10.1 정적 멤버 선언

정적 필드와 정적 메소드는 클래스에 고정된 멤버이므로 클래스 로더가 클래스(바이트 코드)를

로딩해서 메소드 메모리 영역에 적재할 때 클래스별로 관리된다.

따라서 클래스의 로딩이 끝나면 바로 사용할 수 있다.

 

객체마다 가지고 있어야 할 데이터라면 인스턴스 필드로 선언하고,

객체마다 가지고 있을 필요성이 없는 공용적인 데이터라면 정적 필드로 선언하는 것이 좋다.

 


6.10.3 정적 초기화 블록

정적 필드는 다음과 같이 필드 선언과 동시에 초기값을 주는 것이 일반적이다.

static double pi = 3.141592;

 

하지만 계산이 필요한 초기화 작업이 있을 수 있다.

인스턴스 필드는 생성자에서 초기화하지만,

정적 필드는 객체 생성 없이도 사용해야 하므로 생성자에서 초기화 작업을 할 수 없다.

 

그래서 Java에서는 정적 필드의 복잡한 초기화 작업을 위해 정적 블록(static block)을 제공한다.

정적 블록의 형태는 다음과 같다.

static{
 ...
 }

정적 블록은 클래스가 메모리로 로딩될 때 자동적으로 실행되며,

선언된 순서대로 실행된다.


6.10.4 정적 메소드와 블록 선언 시 주의할 점

static 메소드와 static 블록을 선언할 때 주의할 점은 객체가 없어도 실행된다는 특징 때문에,

내부에 인스턴스 필드나 인스턴스 메소드를 사용할 수 없다는 것이다.

또한 객체 자신의 참조인 this 키워드 또한 사용이 불가하다.

 

그래서 static 메소드와 static 블록에서 인스턴스 멤버를 사용하고 싶다면,

객체를 먼저 생성하고 참조 변수로 접근해야한다.


6.10.5 싱글톤(Singleton)

전체 프로그램에서 단 하나의 객체만 만들도록 보장해야 하는 경우가 있는데,

단 하나만 생성된다고 해서 이 객체를 싱글톤이라고 한다.

 

그럼 어떻게하면 객체를 하나만 만들도록 보장할 수 있을까?

객체를 생성하는 방법은 new 연산자를 통해서 생성한다.

그렇다면 new 연산자로 생성자를 호출할 수 없도록하면 될 것이다.

 

이처럼 생성자를 외부에서 호출할 수 없도록 하려면 생성자 앞에 private 접근 제한자를 붙여주면 된다.

 

다음 코드는 싱글톤을 만드는 코드이다.

public class Bang{
	// 정적 필드
    private static Bang singleton = new Bang();
    
    //생성자
    private Bang() {}
    
    //정적 메소드
    static Bang getInstance() {
    	return singleton;
    }
}

위와 같이 코드를 작성하면 외부에서 객체를 얻는 유일한 방법은 getInstance() 메소드 호출이며,

getInstance() 메소드는 단 하나의 객체만 리턴한다.

즉, getInstance() 메소드를 여러번 사용해도 리턴되는 객체가 같다는 뜻이다.


6.11 final 필드와 상수

6.11.1  final 필드

final 필드는 초기값이 저장되면 이것이 최종적인 값이 되어서 프로그램 실행 도중에 수정 할 수 없다.


6.11.2 상수(static final)

일반적으로 불변의 값을 상수라고 부른다.

static final double PI = 3.141952;

위와 같이 코드를 작성했을 경우 static 이기에 공용성을 띄고 있고, final이기때문에 변하지 않는다.

따라서 이러한 값을 상수라고 부른다.


6.12 패키지

패키지는 클래스를 컴파일하는 과정에서 자동적으로 생성되는 폴더이다.

프로젝트를 개발하다보면 수십,수백개의 클래스를 작성해야하는데,

클래스를 체계적으로 관리하지 않으면 클래스 간의 관계가 뒤엉켜서 복잡하고 난해한 프로그램이 되어

결국 유지 보수가 어렵게 된다.

 

클래스의 전체 이름은 "패키지명 + 클래스명" 인데,

패키지가 상 하위로 구분되어 있다면 도트(.) 를 사용해서 다음과 같이 표현한다.


6.12.1 패키지 선언

여러 개발 회사가 함계 참여하는 대규모 프로젝트나, 다른 회사의 패키지를 이용해서 개발할 경우, 

패키지 이름이 중복될 가능성이 있다.

그래서 회사들 간에 패키지가 서로 중복되지 않도록 흔히 회사의 도메인 이름으로 패키지를 만든다.

 


6.13 접근 제한자

접근 제한자는 public, protected, default, private와 같이 네 가지 종류가 있다.

 

아래 사진은 제한자들의 제한 종류와 적용할 대상이다.


6.13.2 생성자의 접근 제한

생성자도 어떤 접근 제한을 갖느냐에 따라 호출 가능 여부가 결정된다.

 


6.14 Getter와 Setter 메소드

앞서 말했듯이 객체 지향 프로그래밍은 캡슐화라는 특징이 있기 때문에

객체 외부에서 객체의 데이터에 직접적으로 접근하는 것을 막는다.

 

그렇기에 객체 지향 프로그래밍에서는 메소드를 통해서 데이터를 변경하는 방법을 선호하는데,

데이터는 외부에서 접근할 수 없도록 막고 메소드는 공개해서 외부에서 메소드를 통해 데이터에 접근하도록 유도한다.

이렇게 하면 메소드에서 매개값을 검증해서 유효한 값만 데이터로 저장할 수 있다는 장점이 있다.

이러한 역할을 하는 메소드를 Setter 라고 한다.

 

또한 객체 외부에서 객체의 필드값을 사용하기에 부적절한 경우도 있는데,

이런 경우에는 메소드로 필드값을 가공한 후 외부로 전달하면 된다.

이러한 역할을 하는 메소드를 Getter 라고 한다.


6.15 어노테이션

어노테이션은 메타데이터라고 볼 수 있다.

메타데이터란 애플리케이션이 처리해야 할 데이터가 아니라,

컴파일 과정과 실행 과정에서 코드를 어떻게 컴파일하고 처리할 것인지를 알려주는 정보이다.

 

어노테이션은 다음과 같은 형태로 작성된다.

@AnnotationName

어노테이션은 세 가지 용도로 사용된다.

  1. 컴파일러에게 코드 문법 에러를 체크하도록 정보를 제공
    ex) @Override 는 메소드 선언 시 사용하는데, 메소드가 오버라이드 (재정의)된 것임을 컴파일러에게 알려주어
    컴파일러가 오버라이드 검사를 하도록 해준다.
  2. 소프트웨어 개발 툴이 빌드나 배치 시 코드를 자동으로 생성할 수 있도록 정보를 제공
  3. 실행 시 (런타임 시) 특정 기능을 실행하도록 정보를 제공

6.15.1 어노테이션 타입 정의와 적용

어노테이션 타입을 정의하는 방법은 인터페이스를 정의하는 것과 유사하다.

다음과 같이 @interface를 사용해서 어노테이션을 정의하며, 그 뒤에 사용할 어노테이션 이름을 작성한다.

public @interface AnnotationName{

}

이렇게 정의한 어노테이션은 코드에서 "@AnnotaionName" 으로 사용한다.

 

어노테이션은 엘리먼트를 멤버로 가질 수 있고,

각 엘리먼트는 타입과 이름으로 구성되며, 디폴트 값을 가질 수 있다.

public @interface AnnotationName{
	String elementName1();
	int elementName2() default 5;
}

위와 같이 정의한 어노테이션을 코드에서 적용할 때에는 다음과 같이 작성한다.

@AnnotationName(elementName1 = "값", elementName2=3);
@AnnotationName(elementName1 = "값");

elementName1 은 default 값이 없기 때문에 반드시 값을 적어야하고

elementName2 는 default 값이 존재하기 때문에 생략해도 된다.