ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java]STEP1 - 7) 객체 지향 프로그래밍, 객체와 클래스, 클래스를 구성하는 필드/생성자/메소드, 생성자 오버로딩, 메소드 오버로딩
    개발 공부/Java 2020. 9. 10. 20:26

     

    본 게시글은 도서 "이것이 자바다" 동영상 강의를 듣고 기록하는 TIL(Today I Learned) 입니다.
    모든 저작권은 출판사 한빛미디어와 신용권님께 있음을 알립니다.👍

     

     

    이번 게시물에서는 자바에서의 객체지향프로그래밍 개념에 대해 알아보도록 하겠습니다.

    객체지향이란 무엇인지? 객체와 클래스란? 클래스를 구성하는 필드, 생성자, 메소드란? 무엇인지 알아봅니다.

     

     

    1. 객체 지향 프로그래밍(OOP: Object Oriented Programming)


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

     

    1-1) 객체란?

    • 물리적으로 존재하거나 추상적으로 생각할 수 있는 것 중에서 자신의 속성을 가지고 다른 것과 식별 가능한 것
    • 속성과 동작으로 구성 (ex. 사람은 이름, 나이 등의 속성과 걷다, 뛰다 등의 동작이 있다.)
    • 자바에서는 이러한 속성과 동작을 각각 필드(Field)메소드(Method)라고 부른다

     

    1-2) 객체의 상호 작용

    • 객체들은 각각 독립적으로 존재하고, 다른 객체와 서로 메소드를 통해 상호작용한다.
    • 메소드를 호출하기 위해서는 객체에 도트(.) 연산자를 붙이고 메소드 이름을 기술하면 된다.
      (ex. 객체. 메소드(매개변수1, 매개변수2 ...) )
    • 객체는 각각 집합 관계, 사용 관계, 상속 관계를 통해 다른 객체와 관계를 맺는다.

      집합 관계: 자동차는 엔진, 타이어, 핸들 객체들의 집합 객체이다.
      사용 관계: 사람 객체는 자동차 객체를 사용한다.
      상속 관계: 자동차 객체(하위)는 기계 객체(상위)의 한 종류이다.

     

    1-3) 객체 지향 프로그래밍의 특징

    객체 지향 프로그램의 특징으로는 캡슐화, 상속, 다형성이 있다.

     

     캡슐화(Encapsulation) : 객체의 필드, 메소드를 하나로 묶고, 실제 구현 내용을 감추는 것.

     필드와 메소드를 캡슐화하는 이유는 외부의 잘못된 사용으로 인해 객체가 손상되지 않도록 하는데 있다.
     
     자바 언어는 캡슐화된 멤버의 노출 여부를 접근 제한자(Access Modifier)를 사용하여 결정한다.

     

     상속(Inheritance) : 상위 객체가 자기가 가지고 있는 필드와 메소드를 하위 객체에게 물려주는 것

     상위 객체를 재사용해서 하위 객체를 쉽고 빠르게 설계할 수 있도록 도와주어 반복된 코드의 중복을 줄여준다.

     상위 객체를 수정하는 것만으로, 모든 하위 객체들의 수정 효과를 가져오기 때문에 유지 보수도 용이하다.

     

     다형성(Polymorphism) : 같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질

     부모 타입에는 모든 자식 객체가 대입될 수 있고, 인터페이스 타입에는 모든 구현 객체가 대입될 수 있다.

     즉 객체의 부품화가 가능하다는 말인데,
     자동차로 예를 들면 자동차의 타이어 부분을 일정 규격으로 정해 놓으면
     해당 타이어 부분에 한국 타이어가 오든 금호 타이어가 오든, 일정 규격에만 맞으면 다양하게 사용할 수 있다는 말이다.
     하지만 한국 타이어를 사용하냐, 금호 타이어를 사용하냐에 따라 성능의 결과는 다르게 나올 수 있다.

     


     

    2. 객체와 클래스


    현실에서 객체가 설계도를 바탕으로 만들어지듯, 자바에서도 클래스라는 설계도를 통해 객체가 만들어진다.

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

    이때, 클래스로부터 만들어진 객체를 해당 클래스의 인스턴스(Instance)라고 한다.

     

    2-1) 클래스 선언

    • 하나 이상의 문자로 이루어진다. (Car, Person...)
    • 첫 번째 글자는 숫자가 올 수 없다. (2People은 안된다.)
    • '$', '_'외의 특수 문자는 사용할 수 없다. ($Person은 되지만, @Person은 안된다.)
    • 자바 키워드는 사용할 수 없다. (int, for, static 등은 안된다.)
    • 클래스 이름은 반드시 소스파일의 파일명과 동일해야 한다. (ex. 클래스이름이 Person이라면 소스파일은 Person.java)
    public class Person {
        클래스 내용
    }

    위 처럼 클래스를 선언할 수 있다. 중괄호는 클래스의 시작과 끝의 범위를 지정한다.

    한 소스파일에 두 개 이상의 클래스를 선언 할 수 있지만, 권장하진 않는다.

     

    2-2) 객체 생성

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

    new는 클래스로부터 객체를 생성시키는 연산자이다. 힙 영역에 객체를 생성시킨 후 객체의 주소를 리턴하도록 되어 있다.

    Person p1;
    p1 = new Person();

    혹은

    Person p2 = new Person();

    으로 생성할 수 있다.

     

    또한, 위에서 p1과 p2는 같은 Person 클래스로부터 생성된 객체이지만, 서로 다른 주소를 참조한다.

    이는 new 연산자를 사용한 만큼 객체가 메모리에 생성되기 때문이다. 즉 p1과 p2는 완전히 독립된 다른 객체라는 뜻이다.

     

    정리하자면! Person은 클래스이고 new 연산자를 통해 Person 클래스로 부터 생성된 p1과 p2는 객체(인스턴스)이다.

     


     

    3. 클래스의 구성 멤버(필드, 생성자, 메소드)


    클래스에는 객체가 가져야할 구성 멤버인 필드, 생성자, 메소드가 있다.

     

    필드(Field)는 객체의 고유 데이터, 부품 객체, 상태 정보를 저장하는 곳이다. 변수와 비슷하지만 필드는 생성자와 메소드 전체에서 사용되며 객체와 함께 존재한다.

     

    생성자(Constructor)는 객체 생성 시 초기화를 담당하며 new 연산자로 호출되는 중괄호 블록이다. 클래스 이름으로 되어있고 리턴 타입이 없다.

     

    메소드(Method)는 객체의 동작에 해당하는 중괄호 블록이다. 필드를 읽고 수정하거나 다른 객체를 생성하고 객체 간의 데이터 전달의 수단으로 사용된다. 리턴 타입은 있을 수도 없을 수도 있다.

     

    각각을 좀 더 자세히 알아보자.

     

    3-1) 필드(Field)

    객체의 고유 데이터, 객체가 가져야 할 부품, 객체의 현재 상태 데이터를 저장하는 곳
    public class Person {
        String name;
        String gender;
        String height;
        String address;
    }

    위는 Person 클래스의 정보를 name, gender, height, address 필드로 선언한 것이다.

    필드는 변수와 비슷해 보일 수 있는데, 엄연히 다른 개념이다.

    변수는 생성자나 메소드 내에서만 사용되고 생성자와 메소드가 종료되면 자동 소멸되는 것이고,

    필드는 생성자 메소드 전체에서 사용되고 객체가 소멸되기 전까지는 소멸되지 않는다.

     

    // 클래스 내부에서는 단순히 필드 이름으로 읽고 변경 가능
    public class Person {

        String name;
        String gender;
        String height;
        String address;

         void method() {

                  address = "Paris, France"
         }
    }

    // 클래스 외부에서는 객체를 우선 생성하고, 도트(.)연산자를 이용해 필드 이름을 읽고 변경 가능
    public class Friend {
          ...
          void method() {
                  Person myPerson = new Person();
        
                  myPerson.address = "Seoul, Korea";
          }
    }

    위 처럼 필드를 클래스 내부 혹은 외부에서 사용할 수 있는데, 필드는 객체와 함께 존재하는 것이므로

    클래스의 외부에서 필드를 사용하려면 new를 통해 먼저 객체를 생성해줘야 한다.

     

     

    3-2) 생성자(Constructor)

    new 연산자와 같이 사용되어 클래스로부터 객체를 생성할 때 호출되어 객체의 초기화를 담당한다.

    객체의 초기화란?  필드를 초기화하거나, 메소드를 호출해서 객체를 사용할 준비를 하는 것을 말한다.

     

    생성자를 실행시키지 않고는 클래스로부터 객체를 만들 수 없다.

    new 연산자에 의해 생성자가 성공적으로 실행되면 힙 영역에 객체가 생성되고 객체의 주소가 리턴된다.

    // 기본 생성자
    public class Person {
        public Person() { }    // 자동 생성
    }

    다음과 같이 중괄호 안이 비어있는 생성자를 기본 생성자라고 한다.
    기본 생성자는 굳이 따로 선언하지 않아도, 컴파일 시 자동으로 생성된다.

    만약 기본 생성자가 아니라 개발자가 명시적으로 생성자를 선언하고 싶다면 아래와 같이 하면 된다.


    // 생성자 선언
    public class Person {
        String name;
        String address;

        public Person(String n, String a) {
            name = n;
            address = a;
        }
    }

    다음과 같이 생성자에 여러 매개변수를 넣을 수도 있고, 필드의 값을 변경할 수도 있다.
    만약 기본 생성자가 아니라 명시적으로 선언한 생성자라면, 호출 할 때도 그 형식에 맞게 호출 해야 한다.

    new Person(); 의 형태로 객체를 생성하려면 오류가 난다. 매개변수를 넣지 않았기 때문이다.
    따라서 올바르게 생성하려면 new Person("Kyle Kim", "Seoul, Korea"); 의 형태로 객체를 생성해야 한다.

     

    외부에서 제공되는 다양한 데이터를 이용해서 객체를 초기화하려면, 생성자도 다양화될 필요가 있다.

    생성자 안에 들어갈 매개변수의 타입은 다양할 수 있기 때문에, 이를 모두 만족하는 여러 생성자가 필요하다.

     

    생성자 오버로딩(Overloading)은 생성자의 이름은 같으나, 매개 변수를 달리하여 생성자를 여러 개 선언하는 기능이다.

    public class Person {
        Person() { }  // 기본 생성자

        Person(String name, String address) { ... }  // name, address를 매개변수로 가지는 생성자

        Person(String name, String address, String gender) { ... }  // name, address, gender를 매개변수로 가지는 생성자
    }

    위와 같은 방법으로, 생성자의 이름은 모두 Person으로 동일하나, 매개 변수가 다르기 때문에 여러 생성자를 선언할 수 있다.

    단! 생성자 오버로딩 시, 매개 변수의 타입/개수/순서가 달라야 한다. 그렇지 않으면 생성자 오버로딩이 아니다.
    단순히 매개 변수의 이름만 다른 것이 아니라, 타입(int, double, String...) / 개수(1개, 2개...) / 순서가 달라야한다.

     

    생성자 오버로딩이 많아질 경우, 생성자 간의 중복된 코드가 발생할 확률이 높다.

    이는 매개 변수의 수만 달리하고 필드 초기화 내용이 비슷한 생성자 사이에서 많이 일어난다.

    따라서 필드 초기화 내용은 한 생성자에만 집중적으로 작성하고 나머지 생성자는 초기화 내용을 가지고 있는 생성자를 호출하는 방법으로 이를 개선할 수 있다.

    Person (String name) {
        this(name, null, 0);  // 맨 아래에 있는 Person 생성자 호출
    }

    Person (String name, String address) {
        this(name, address, 0);  // 맨 아래에 있는 Person 생성자 호출
    }

    Person (String name, String address, int age) {
        // 이 생성자에서 필드 초기화 내용을 집중적으로 작성
        this.name = name;
        this.address = address;
        this.age = age;
    }

    this() 코드를 사용하면 다른 생성자를 호출하여 필드 초기화를 맡길 수 있다.
    this() 코드는 반드시 해당 생성자의 첫 줄에서만 사용할 수 있다.

    마지막의 Person 생성자에서 필드 초기화 내용을 모두 몰아 넣고
    나머지 Person 생성자에서는 this()를 이용해서 해당 초기화 내용을 받아오는 것이다.
    이 때, 기본 초기값은 문자열일 때는 null, 정수일 때는 0이므로 넣을 값이 없을 때는 null이나 0을 넣었다.

     

     

    3-3) 메소드(Method)

    객체의 동작에 해당하는 중괄호 { } 블록을 말한다.
    필드를 읽고 수정 / 다른 객체 생성하여 다양한 기능 수행 / 객체 간의 데이터 전달의 수단으로 이용된다.

     

    메소드는 리턴타입, 메소드이름, 매개 변수 선언, 실행 블록으로 구성된다.

     

     

    리턴 타입: 메소드가 실행 후 리턴하는 값의 타입을 말한다. 값이 있다면 원하는 타입(int, double, char...) 을 기입해 주어야 하고, 해당 리턴 값을 받는 변수도 같은 타입으로 지정해야 한다.

     

    리턴 값이 있는 메소드에서는 return 문이 없다면 컴파일 에러가 나므로 주의해야 한다.

    return 문 뒤에는 별도의 실행문이 나타나게 되면 에러가 나므로 반드시 return에서 끝내야 한다.

    리턴 값은 반드시 해당 메소드의 리턴 타입이거나 리턴 타입으로 자동 변환 될 수 있어야 한다.

     

    리턴 값이 없는 메소드에서는 return문이 메소드 실행을 강제 종료시키는 기능을 한다.

    마치 반복문의 break문과 같이, return; 이라고 입력하면 해당 메소드를 바로 종료한다.

     

     

    메소드 이름: 숫자로 시작하면 안되고, $와 _를 제외한 특수문자는 사용이 불가능 하다. 관례적으로 소문자로 작성한다.

     

    객체 내부에서 메소드를 호출 할 때는 단순히 메소드이름(); 의 형태로 호출 할 수 있다.

    객체 외부에서 메소드를 호출 할 때는 객체를 먼저 만들고, 객체.메소드이름(); 의 형태로 호출 할 수 있다.

     

     

    매개 변수 선언: 매개 변수는 메소드가 실행할 때 필요한 데이터를 외부로부터 받기 위해 사용된다. 매개 변수가 있을 수도 있고 없을 수도 있다. 매개 변수도 미리 타입을 지정해야 하는데, 해당 타입에 맞는 값만 들어와야 한다.

     

    만약 매개 변수의 수를 모르는 경우라면 매개 변수를 배열 타입으로 선언해도 된다.

    int sum (int[] values) {}
    혹은
    int sum (int ... values) {}

    이런 식으로 선언하게 되면, 매개 변수에 넣는 숫자의 개수에 따라 자동으로 배열이 생성되고 매개 값으로 사용된다.

    단 int[] values 같은 경우는 반드시 배열의 형태로 넣어 줘야 하지만
    int...values 같은 경우는 배열로 넣어도 되고 아니면 단순한 숫자의 나열로도 가능하다는 장점이 있다.

     

    메소드도 생성자와 마찬가지로 오버로딩이 존재한다. 이를 메소드 오버로딩이라고 하며, 메소드의 이름은 같으나 매개 변수의 타입/개수/순서가 달라 메소드를 여러 개 선언할 수 있는 기능을 말한다.

    int plus (int x, int y) {
        return x + y;
    }

    int plus (double x, double y) {
        return x + y;
    }

    plus 메소드와 같이 숫자를 더하는 메소드의 경우는, 정수/실수 상관없이 더해주어야 한다.
    하지만 메소드의 매개 변수 타입에는 정수와 실수를 모두 받을 수 있는 타입이 없기 때문에
    메소드 오버로딩을 통해서 이름은 plus로 동일하지만 매개 변수의 타입을 달리하여 여러 메소드를 생성하는 것으로 문제를 해결할 수 있다.

     

    메소드 오버로딩의 대표적인 예로 등장하는 것이 System.out.println(); 메소드이다.

    println() 안에는 정수/ 실수 / 문자열 등등 어떠한 타입이 들어가도 모두 출력해 주는데, 이것은 내부적으로 메소드 오버로딩을 통해서 이름은 println()으로 동일하지만 여러 매개 변수 타입으로 여러 메소드를 생성한 것이라고 볼 수 있다.

     

     

     

    본 게시글은 여기서 마치겠습니다.

    읽어주셔서 감사하고, 혹시나 틀린 부분이나 보완해야할 부분이 있다면 댓글에 남겨주세요~!

    댓글

Designed by Tistory.