본문 바로가기

Java/Java 2

[Java] 4. 객체지향 개념과 자바

반응형

1. 객체 클래스 인스턴스

객체지향

현실 세계의 객체 모델을 바탕으로 프로그램을 구조화하고 개발하는 프로그래밍 기법

 

객체

객체는 해당 객체가 가지고 있는 속성과 객체가 할 수 있는 동작으로 설명

 

공통적인 성질을 가지고 있지만 구체적인 값들이 다른 여러 객체가 있을 수 있기 때문에 공통적인 속성과 동작을 가지는 상위 개념을 정의할 수 있는데 그것이 바로 클래스

 

클래스

클래스는 객체를 정의하기 위한 틀로써 표현하고자 하는 객체들의 속성과 동작을 정의하고 있습니다. 앞의 소나타 예에서 소나타는 Car 라고 하는 클래스로 정의할 수 있는 것입니다.

 

실제 프로그램 안에서 속성은 필드(멤버변수)의 형태로 동작은 메서드의 형태로 표현되게 됩니다. 이해를 돕기위해 구조를 조금 단순화해서 자동차 클래스를 구현

 

class Car {
    String color;
    String model;
    int power;

    public void go() {
    }

    public void break() {
    }
}

 

클래스를 통해 여러 객체를 생성할 수 있는데 클래스를 통해 생성된 객체를 인스턴스(Instance)

 

인스턴스

인스턴스는 클래스를 통해 만들어진 구체적인 객체라고 볼 수 있습니다. 앞에서 만든 Car 클래스를 통해 다음과 같이 여러 자동차 인스턴스를 만들 수 있습니다. 이때 new 연산자가 사용되며 클래스의 생성자 메서드를 통해 객체를 초기화 하게 됩니다.

기본 생성자는 생략이 가능하며 필요에 따라 인자가 있는 생성자를 만들 수 있습니다.

Car sonata = new Car();
Car grandeur = new Car('그랜저','red',180);

sonata.go();
grandeur.break();
...

 


2. 상속과 오버라이딩

상속

클래스간의 상하 관계로 추상적인 슈퍼클래스(Super Class) 혹은 부모 클래스로 부터 서브클래스(Sub Class) 혹은 자식 클래스를 만드는 것으로 상속이라는 관계를 통해 계층구조를 형성

 

  • 슈퍼 클래스에서 정의된 필드와 메서드를 물려 받는다.
  • 새로운 필드나 메서드를 추가할 수 있다.
  • 슈퍼 클래스에서 물려 받은 메서드를 수정할 수 있다.(오버라이딩, Overriding)
  • 동일 슈퍼클래스를 상속하는 모든 서브클래스는 타입 호환이 된다.

이러한 상속을 이용하면 코드의 재사용이 가능해지고 부모클래스 레벨에서 호환되는 서브클래스를 사용해 다형성의 기반을 마련할 수 있습니다.

프로그램 문법상으로는 extends 키워드를 사용하며 자바의 경우 두개 이상의 클래스를 동시에 상속받는 다중 상속은 지원하지 않습니다. 대신 뒤에서 배우게 될 인터페이스를 이용해 여러 클래스의 속성을 가지는 서브 클래스 구현이 가능 합니다.

 

Class SubClass extends SuperClass {

}

샷건은 근거리에 효과가 좋고 단발 사격이 되는 반면 기관총은 연발이 가능하고 중거리에서 유용합니다. 저격소총은 대부분 단발이며 장거리에서 효과

Class Gun {
}

Class ShotGun extends Gun{
}

Class M416 extends Gun {
}

 

오버라이딩

슈퍼클래스로 부터 상속받은 메서드를 다시 정의하는 것

메서드의 이름과 리턴 타입, 인자등이 모두 동일해야 하며 다를 경우 새로운 메서드가 추가되는 형식

 

오버라이딩을 통해 객체지향의 특징중 하나인 다형성 구현이 가능

 

예를들어 애완동물 클래스가 있고 bark() 라는 메서드가 있다고 했을때 애안동물 클래스를 상속받는 강아지와 고양이 클래스를 만들경우 bark() 메서드의 오버라이딩을 통해 서로 다른 동작이 가능

class Pet {
    void bark() {
        System.out.println("pipi");
    }
}

class Dog extends Pet {
    void bark() {
        System.out.println("woof woof");
    }
}

class Cat extends Pet {
        void bark() {
        System.out.println("mew mew");
    }
}

 

Pet 클래스 타입으로 Dog 클래스의 인스턴스를 생성해 bark() 메서드를 호출하면 woof woof 가 출력

 

Pet pet = new Dog();
pet.bark();     // woof woof

Dog 대신 Cat 인스턴스를 생성하면 동일 코드에서 mew mew 가 출력되어

동일한 구조에서 다른 동작 즉 다형성을 구현 가능

 

여기에서 Pet 클래스의 bark() 메서드는 사실상 구현할 필요가 없는 메서드

구체적인 애완동물 클래스가 구현되어야 세부 내용이 결정될 것이기 때문에 굳이 메서드 구현을 해둘 필요가 없다

 

이러한 경우 일반적인 클래스가 아닌 추상클래스나 인터페이스를 통해 구체적인 내용을 구현하는 것이 아닌 규격을 제시 하는 형태로 구현하는 것이 더 낫다.

 


3. 추상클래스와 인터페이스

추상클래스

추상클래스는 추상메서드(abstract method)를 포함하고 있는 클래스를 말합니다. 추상메서드란 앞의 Pet 클래스와 같이 구체적이지 않은 내용을 정의한 메서드를 말하는 것으로 추상 메서드로 정의된 메서드는 서브 클래스에서 반드시 오버라이딩을 통해 구현해야 합니다.

따라서 추상클래스 자체는 new 를 통해 인스턴스로 만들 수 없고 반드시 상속을 통해 구체적인 클래스를 만들어 사용해야 합니다. 소프트웨어 디자인 패턴에서는 이렇게 추상클래스를 상속해서 구현하는 클래스를 콘크리트 클래스(Concrete Class) 라고도 부릅니다.

추상클래스는 추상메서드 이외에 다른 메서드를 포함할 수 있으면 멤버 필드역시 가질 수 있습니다. 앞의 Pet 클래스를 추상클래스로 정의 하면 다음과 같이 됩니다.

 

abstract class Pet {
    abstract void bark(){};
}

이클립스와 같은 개발도구를 사용하는 경우 추상클래스 상속시 자동으로 추상메서드 구현을 알려주고 기본 코드를 생성해 구현부를 작성할 수 있도록 도와주게 됩니다.

 

 

인터페이스

인터페이스는 말 그대로 무언가를 이어주기 위한 연결고리로 추상클래스와 유사하지만 상수와 추상메서드로만 구성된 형태를 말합니다. 추상메서드로만 구성이 되기 때문에 그 자체로는 아무런 기능을 하지 않지만 마치 설계도 처럼 향후 구현될 클래스들을 연결해 사용할 수 있는 기반 구조를 제공하고 있습니다.

  • 멤버필드는 모두 public static final 만 가능하며 제어자는 생략이 가능.
  • 모든 메서드는 public abstract 만 가능하며 제어자는 생략이 가능.

Pet 클래스를 인터페이스로 정의

interface Pet {
    void bark();
}

 

추상클래스와 달리 인터페이스는 상속(extends)이 아니라 구현(implements)을 통해 클래스를 정의

class Dog implements Pet {
    ...
}

 

또한 일반적인 클래스들은 다중상속이 안되지만 인터페이스의 경우 다중 구현이 가능합니다.

예를들어 로봇 애완견을 만들기 위해 Pet 과 Robot 인터페이스를 동시에 구현

class RobotDog implments Pet, Robot {
    ...
}

양쪽 인터페이스 모두의 추상메서드가 구현되어야 한다.

또한 상속받을 클래스가 있다면 상속과 함께 사용하는 것도 가능

 

class RobotCat extends Pet implements Robot {
    ...
}

 

일반적으로 설계의 관점에서는 인터페이스를 사용하고 구현시 유연함을 위해서는 추상클래스를 사용할 수 있다

 

JDK 8 에서 추가된 default 를 이용하면 인터페이스에 메서드 구현이 가능하고 이를 구현하는 클래스에서 해당 메서드의 오버라이딩도 가능해져 사실상 추상클래스의 역할을 겸하게 되었다

 

 

추가적으로 static 메서드의 구현도 가능해져 인터페이스의 활용도가 높아졌다고 볼 수 있습니다. 물론 실제 구현에는 추상클래스, 인터페이스 등이 모두 혼재해서 사용되고 있으니 필요에 따라 적절히 선택해 사용하면 된다.

interface Pet {
    void bark();
    default eat(int amount) {
        ...
    };
    static void wake() {
        ....
    }
}
  • bark() 는 구현 클래스에서 반드시 구현해야 하는 추상 메서드
  • eat(int amount) 는 기본제공되는 메서드로 필요시 오버라이딩 가능
  • wake() 는 static 메서드로 구현클래스 없이 Pet.wake() 로 사용 가능

 


4. 패키지와 제어자

패키지

패키지는 자바 클래스들을 구분하기 위해 사용

 

객체지향 프로그래밍에서는 여러 클래스들이 사용되고 내가 직접 만들지 않은 클래스 혹은 라이브러리들을 사용하는 경우, 이때 클래스들의 이름이 모두 동일하다면 문제가 발생할 수 있다.

 

예를들어 회원관리 프로그램에 Login 클래스가 있을수 있으며

카페나 블로그, 쇼핑몰 쪽에서도 Login 클래스가 있을 수 있다.

 

이처럼 서로 다른 목적에서 개발된 클래스들을 서로 구분하기 위해 패키지를 사용하게 된다.

 

역 도메인(reverse domain) 방식

: 이러한 패키지들을 전세계적으로 문제 없이 관리하기 위한 일종의 규약이 존재하는데 패키지 이름의 관리

도메인은 인터넷에서 기관을 이름으로 구분한 것인데 패키지는 이를 뒤집어서 사용하게 된다.

 

예를들어 네이버의 도메인은 naver.com 인데 만일 네이버 개발자들이 자바 클래스를 개발하게 된다면 com.naver 를 루트 패키지명으로 사용하게 된다.

 

실제 네이버는 카페, 블로그, 메일 등과 같은 서비스를 운영하고 있기 때문에 이들에 사용되는 클래스들은 각각 com.naver.cafe, com.naver.blog, com.naver.mail 과 같은 패키지명을 사용하게 되는 것이다.

 

이와같은 규칙 때문에 체계적으로 클래스들을 관리할 수 있고 또 서로 다른 기관에서 만든 클래스들도 혼선없이 사용할 수 있다.

 

처음 자바를 시작하는 경우 학습의 목적이므로 별도의 패키지를 만들지 않는 경우가 많으나 가급적 패키지명을 사용하는 습관을 들이는것이 좋다.

 

package 선언

클래스에 패키지를 선언하는 것은 package 키워드를 이용해 소스 상단에 패키지 이름을 넣어주는 것

package com.my.study;

class MyClass {

}

 

package import

현재 구현중인 클래스에서 동일패키지에 있는 클래스가 아닌 다른 클래스(외부 라이브러리 등)를 사용하는 경우

반드시 import 문을 사용해 해당 클래스의 패키지를 명시해 주어야 한다.

만일 import 문을 사용하지 않는다면 소스코드에서 해당 클래스 사용시 매번 클래스 이름 앞에 패키지명까지 붙여주어야 한다.

class MyClass {
 java.util.Scanner scan = new java.util.Scanner(System.in);   
}

대부분의 경우 매번 패키지명을 붙여주는 방식 보다는 해당 클래스의 패키지를 import 해주는 방법을 사용

import java.util.Scanner;

class MyClass {
    Scanner scan = new Scanner(System.in);
}

원칙적으로는 사용되는 모든 클래스를 import 해주는 것이며 특정 패키지의 모든 클래스를 한번에 import 하기 위해서는 다음과 같이 *을 사용하기도 한다.

import java.util.*;

이러한 방식은 많은 라이브러리를 동시에 사용하는 경우 클래스 이름 중복으로 인한 문제가 발생할 수 있으므로 주의

 

보통은 개발도구에서 자동으로 패키지 import 를 관리하므로 신경쓰지 말고 개별 클래스들이 import 될 수 있도록 하는 것이 좋다.

 

실제 패키지는 디스크 상에는 디렉토리 개념으로 소스 폴더를 열어보면 com 폴더 아래 my 폴더가 있고 그아래 study 폴더에 자바 소스파일들이 위치한 것을 확인 가능

 

제어자

제어자는 클래스, 변수, 메서드의 선언부에 사용되어 부가적인 의미를 부여

 

이미 여러 코드에서 나온 public, static 같은 키워드들이 여기에 해당되는데,

이러한 제어자에는 클래스의 접근 범위와 관련된 접근 제어자(access modifier)와 일반 제어자가 있다.

  • 접근제어자 : public, protected, default, private
  • 일반 제어자 : static, final, abstract, native, transient, synchronized, volatile, strictfp

이들 제어자는 상황에 따라 클래스, 메서드, 변수등에 사용하며 하나의 대상에 여러 개의 제어자를 조합해서 사용할 수 있으나, 접근제어자는 단 하나만 사용 가능

 

static

클래스 혹은 공통적인 이라는 의미를 가지고 있으며 앞에서 배운 클래스 변수나 메서드의 선언에 사용

  • 멤버변수와 메서드, 초기화 블럭은 인스턴스를 생성하지 않고도 사용할 수 있다.
  • 모든 인스턴스에 공통적으로 사용되는 클래스 변수.
  • 클래스변수는 인스턴스를 생성하지 않고도 사용가능.
  • 클래스가 메모리에 로드될 때 생성.
  • 인스턴스 생성 없이 클래스이름.메서드명() 으로 사용가능한 static 메서드 선언.

final

변경할 수 없다는 의미를 가지고 있으며 변수나 메서드, 클래스에 사용

  • 메서드에 사용할 경우 오버라이딩을 통해 재정의 할 수 없음.
  • 변수에 사용할 경우 상수가 됨.
  • 클래스에 사용할 경우 더이상 상속을 할 수 없는 클래스가 됨.

abstract

앞에서 살펴본 추상 클래스와 추상 메서드를 선언할때 사용

  • 메서드의 선언부만 작성하고 실제 수행 내용은 구현하지 않은 추상메서드를 선언하는데 사용.
  • 추상 메서드가 있는 클래스는 반드시 추상클래스로 선언 되어야 함.

접근 제어자(access modifier)

접근 제어자는 멤버 또는 클래스에 사용하며 외부에서의 접근을 제어하기 위해 사용

예를 들어 내가 만드는 패키지의 클래스들중 일부는 외부에서 사용할 수 있도록 하고 일부 클래스는 내가 만든 클래스에서만 사용할 수 있도록 하는등의 제어가 가능

 

보통 자바를 처음 학습하는 동안에는 특별한 사용이 필요 없지만 패키지구조가 복잡한 프로젝트를 진행하거나 라이브러리를 만드는 등의 작업을 하는 경우에는 접근 제어자를 적절하게 사용해야 한다. 지정되어 있지 않다면 default

 

  • public : 접근 제한이 없음.
  • protected : 같은 패키지 내에서 혹은 다른 패키지의 자손클래스에서 접근이 가능.
  • default : 같은 패키지 내에서만 접근이 가능.
  • private : 같은 클래스 내에서만 접근이 가능.

유형별로는 다음과 같은 접근제어자 사용 가능

  • 클래스 : public, default
  • 메서드, 멤버변수 : public, protected, default, private
  • 지역변수 : 지역변수 자체가 해당 블럭에서만 사용가능하므로 접근제어자의 의미가 없음.

일반적으로 생성자의 접근 제어자는 클래스의 접근 제어자와 일치하며 생성자에 접근 제어자를 사용하는 경우 인스턴스의 생성을 제한 가능

 

이는 소프트웨어 디자인 패턴에서 단일 인스턴스를 보장하는 싱글턴 패턴(Singleton Pattern)의 구현에 활용

 

캡슐화와 접근 제어자

캡슐화는 객체지향 프로그램의 대표적인 특징중 하나

 

접근 제어자를 사용하면 클래스 외부로 부터의 접근을 제어할 수 있으므로 객체를 캡슐화 할 수 있는데,

예를들어 private의 경우 동일 클래스 내에서만 접근이 가능하므로 멤버필드에 private 를 선언하면 해당 변수를 클래스 외부에서 접근할 수 없게 된다.

 

이 경우 클래스 외부에 해당 멤버의 접근을 제공하기 위해 getter, setter 메서드를 제공하는 방식을 사용하게 되고

 

getter 는 멤버값을 제공하기 위해 setter 는 멤버값의 변경을 위해 사용

메서드 생성 규칙은 멤버타입 getXxx(), setXxx(멤버타입 인자)

 

이클립스와 같은 개발도구에서는 private 멤버변수들에 대해 자동으로 getter, setter 메서드 생성을 제공하는 기능

private int count;

public int getCount() {
    return count;
}

public void setCount(int count) {
    this.count = count;
}

보통 위와 같은 형식을 취하며 단순히 값을 넘겨주거나 설정하기도 하고 원하는대로 데이터를 조작하거나 처리 기능을 구현하면 된다.

반응형

'Java > Java 2' 카테고리의 다른 글

[Java] 5. 자바 중급 활용  (0) 2021.07.16
[Java 실습] 4. 객체지향 개념과 자바  (0) 2021.07.15
[Java 실습] 3. 자바 기본문법2  (0) 2021.07.15
[Java] 3. 자바 기본문법 2  (0) 2021.07.15
[Java 실습] 2. 자바 기본문법  (0) 2021.07.15