[JAVA] 이것이 자바다_5

Updated:

객체 지향 프로그래밍
잘 알고 있지만, 정리를 통해 확실히 알아두자.

객체 지향 프로그래밍(OOP)


현실에서 어떤 제품을 만들 때, 부품을 먼저 개발하고 이 부품들을 하나씩 조립해서 완성된 제품을 만들 듯이, 소프트웨어를 개발할 때에도 부품에 해당하는 객체 들을 먼저 만들고, 이것들을 하나씩 조립해서 완성된 프로그램을 만드는 기법을 객체 지향 프로그래밍(OOP: Object Oriented Programming) 이라고 한다.


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


캡슐화(Encapsulation)

캡슐화란 객체의 필드, 메소드를 하나로 묶고, 실제 구현 내용을 감추는 것을 말한다.
외부 객체는 객체 내부의 구조를 알지 못하며 객체가 노출해서 제공하는 필드와 메소드만 이용할 수 있다.
필드와 메소드를 캡슐화하여 보호하는 이유는 외부의 잘못된 사용으로 인해 객체가 손상되지 않도록 하는데 있다.
예를 들어 TV의 중요 부품이 바깥으로 노출되어 있다면, 사용자의 실수로 인해 고장날 수도 있다.
이런 중요한 부품은 TV 안쪽으로 캡슐화하여 숨겨두어야 한다.
자바는 캡슐화된 멤버를 노출시킬 것인지, 숨길 것인지를 결정하기 위해 접근 제한자(Access Modifier) 를 사용한다.

접근 제한자는 객체의 필드와 메소드의 사용 범위를 제한함으로써 외부로부터 보호한다.

상속(Inheritance)

일반적으로 상속은 부모가 가지고 있는 재산을 자식에게 물려주는 것을 의미한다.
자식은 특별한 노력없이 부모가 물려준 재산을 갖게 된다.
객체 지향 프로그래밍에서도 부모 역할의 상위 객체와 자식 역할의 하위 객체가 있다.
상위 객체는 자기가 가지고 있는 필드와 메소드를 하위 객체에게 물려주어 하위 객체가 사용할 수 있도록 해준다.

상속은 상위 객체를 재사용해서 하위 객체를 쉽고 빨리 설계할 수 있도록 도와주고, 이미 잘 개발된 객체를 재사용해서 새로운 객체를 만들기 때문에 반복된 코드의 중복을 줄여준다.

예를 들어 객체 B, C가 객체 A를 상속할 경우 A의 필드와 메소드를 수정함으로써 객체 B, C를 수정하지 않아도 객체 A의 수정된 필드와 메소드를 사용할 수 있다.

다형성(Polymorphism)

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

코드 측면에서 보면 다형성은 하나의 타입에 여러 객체를 대입함으로써 다양한 기능을 이용할 수 있도록 해준다.
자바는 다형성을 위해 부모 클래스 또는 인터페이스의 타입 변환을 허용한다.
부모 타입에는 모든 자식 객체가 대입될 수 있고, 인터페이스 타입에는 모든 구현 객체가 대입될 수 있다.
다형성의 효과로 객체는 부품화가 가능하다.
예를 들어 자동차를 설계할 때 타이어 인터페이스 타입을 적용했다면 이 인터페이스를 구현한 실제 타이어들은 어떤 것이든 상관없이 장착(대입)이 가능하다.

ex) 자동차는 타이어 타입으로 한국 타이어와 금호 타이어를 사용하지만 각 타이어의 성능은 다르게 나온다.(다형성)


객체와 클래스


현실에서 객체는 갑자기 하늘에서 떨어지는 것이 아니라, 설계도를 바탕으로 만들어진다.
예를 들어 사람들이 자동차를 이용하기 위해서는 우선 공장에서 설계도를 보고 자동차를 만들어야 한다.

객체 지향 프로그래밍에서도 마찬가지다.

메모리에서 사용하고 싶은 객체가 있다면 우선 설계도로 해당 객체를 만드는 작업이 필요하다.
자바에서는 설계도가 바로 클래스(class) 이다.
클래스에는 객체를 생성하기 위한 필드와 메소드가 정의되어 있다.
클래스로부터 만들어진 객체를 해당 클래스의 인스턴스(instance) 라고 한다.

자동차 객체는 자동차 클래스의 인스턴스인 셈이다.

그리고 클래스로부터 객체를 만드는 과정을 인스턴스화라고 한다.
하나의 클래스로부터 여러 개의 인스턴스를 만들 수 있는데, 이것을 동일한 설계도로부터 여러 대의 자동차를 만드는 것과 동일하다.

객체 지향 프로그래밍 개발은 세 가지 단계가 있다.
첫째, 클래스를 설계한다.
둘째, 설계된 클래스로 사용할 객체를 생성한다.
셋째, 생성된 객체를 이용한다.


클래스 선언


사용하고자 하는 객체를 구상했다면, 그 객체의 대표 이름을 하나 결정하고 이것을 클래스 이름으로 한다.
예를 들어 사람 객체의 클래스는 Person으로, 자동차 객체의 클래스는 Car라는 이름으로 줄 수 있다.
클래스 이름은 다른 클래스와 식별할 목적으로 사용되므로 자바의 식별자 작성 규칙에 따라서 만들어야 한다.

번호 작성 규칙
1 하나 이상의 문자로 이루어져야 한다. Car, SportsCar
2 첫 번째 글자는 숫자가 올 수 없다. Car, 3Car(x)
3 ’$’,’_’ 외의 특수 문자는 사용할 수 없다. $Car, _Car, @Car(x), #Car(x)
4 자바 키워드는 사용할 수 없다. int(x), for(x)

클래스 이름은 한글이든 영어든 상관없지만, 한글로 클래스 이름을 만드는 경우는 거의 없다.
자바 언어는 영어 대소문자를 다른 문자로 취급하기 때문에 클래스 이름도 영어 대소문자를 구분한다.

클래스 이름을 정했따면 “클래스이름.java”로 소스 파일을 생성한다.
소스 파일 이름 역시 대소문자를 구분하므로 반드시 클래스 이름과 대소문자가 같도록 해야 한다.
소스 파일을 생성했다면 소스 파일을 열고 다음과 같이 클래스를 작성한다.

public class 클래스이름{

}

여기서 public class 키워드는 클래스를 선언할 때 사용하며 반드시 소문자로 작성해야 한다.
클래스 이름 뒤에는 클래스 선언의 시작과 끝을 알려주기 위해 반드시 중괄호 {}를 붙여준다.

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

public class Car{

}
class Tire{
}

두 개 이상의 클래스가 선언된 소스 파일을 컴파일하면 바이트 코드 파일은(.class) 클래스를 선언한 개수만큼 생긴다.
결국 소스 파일은 클래스 선언을 담고 있는 저장 단위일 뿐, 클래스 자체가 아니다.
위의 코드를 컴파일하면 Car.class와 Tire.class가 각각 생성된다.
주의할 점은 파일 이름과 동일한 이름의 클래스 선언에만 public 접근 제한자를 붙일 수 있다.
만약 파일 이름과 일치하지 않는 클래스 선언에 public 접근 제한자를 붙이면 컴파일 에러가 발생한다.

가급적 소스 파일 하나당 동일한 이름의 클래스 하나를 선언하자.


객체 생성과 클래스 변수


클래스를 선언한 다음, 컴파일을 했다면 객체를 생성할 설계도가 만들어진 셈이다.
클래스로부터 객체를 생성하는 방법은 다음과 같이 new 연산자를 사용하면 된다.

new 클래스();

new는 클래스로부터 객체를 생성시키는 연산자이다.
new 연산자 뒤에는 생성자가 오는데, 생성자는 클래스() 형태를 가지고 있다.

new 연산자로 생성된 객체는 메모리 힙(heap) 영역에 생성된다.
현실에서 물건의 위치를 모르면 물건을 사용할 수 없듯이, 객체 지향 프로그램에서도 메모리 내에서 생성된 객체의 위치를 모르면 객체를 사용할 수 없다.
그래서 new 연산자는 힙 영역에 객체를 생성시킨 후, 객체의 주소를 리턴하도록 되어 있다.
이 주소를 참조 타입인 클래스 변수에 저장해 두면, 변수를 통해 객체를 사용할 수 있다.

다음은 클래스 타입으로 선언된 변수에 new 연산자가 리턴한 객체의 주소를 저장하는 코드이다.

클래스 변수;
변수 = new 클래스();

클래스 변수 선언과 객체 생성을 한 개의 실행문으로 작성할 수도 있다.

클래스 변수 = new 클래스();

Student 클래스를 선언하고 StudentExample 클래스의 main() 메소드에서 Student 객체를 생성하는 코드를 보자.

public class Student{
    // 클래스 선언
}
public class StudentExample{
    public static void main(String[] args){
        Student s1 = new Student();
        System.out.println("s1 변수가 Student 객체를 참조합니다.");
    
        Student s2 = new Student();
        System.out.println("s2 변수가 또 다른 Student 객체를 참조합니다.");
    }
}

실행결과
예쁘게 잘나온다.

Student 클래스는 하나지만 new 연산자를 사용한 만큼 객체가 메모리에 생성된다.
이러한 객체들은 Student 클래스의 인스턴스들이다.
비록 같은 클래스로부터 생성되었지만 각각의 Student 객체는 자신만의 고유 데이터를 가지면서 메모리에서 활동하게 된다.
s1과 s2가 참조하는 Student 객체는 완전히 독립된 서로 다른 객체이다.

Student와 StudentExample 클래스의 용도에 대해 알아보자.
클래스는 두 가지 용도가 있다.

하나는 라이브러리(API: Application Program Interface)용이고 다른 하나는 실행용이다.

라이브러리 클래스는 다른 클래스에서 이용할 목적으로 설계된다.
프로그램 전체에서 사용되는 클래스가 100개라면 99개는 라이브러리이고 단 하나가 실행 클래스이다.
실행 클래스는 프로그램의 실행 진입점인 main() 메소드를 제공하는 역할을 한다.
Student는 라이브러리 클래스이고 StudentExample은 실행 클래스이다.
아래와 같이 Student에 main() 메소드를 작성해서 라이브러리인 동시에 실행 클래스를 만들 수도 있다.

public class Student{
    // 라이브러리로서의 코드(필드, 생성자, 메소드)
    ...
    // 실행하기 위한 코드
    public static void main(String[] args){
        Student s1 = new Student();
        System.out.println("s1 변수가 Student 객체를 참조합니다.");
    
        Student s2 = new Student();
        System.out.println("s2 변수가 또 다른 Student 객체를 참조합니다.");
    }
}

프로그램이 단 하나의 클래스로 구성된다면 위처럼 작성하는 것이 좋을 수 있다.
하지만 대부분의 객체 지향 프로그램은 라이브러리와 실행 클래스가 분리되어 있다.
가급적이면 분리해서 작성하자.


JAVA의 객체 지향 프로그래밍을 정리해보았다.
아직 온전히 정리한 것은 아니다.
양이 많으니 두 번이나 세 번에 걸쳐서 포스팅해야겠다.
중요한 부분이니 꼼꼼히 정리하자.

Leave a comment