객체 지향 프로그래밍과 CLASS(feat. field와 static 그리고 singleton…)🙆‍♂️

김민제·2023년 10월 14일
0
post-thumbnail

객체 지향 프로그래밍

객체

  • 물리적으로 존재하거나 추상적으로 생각할 수 있는 것 중에서 자신의 속성을 가지고 있으며 식별 가능한 것을 말한다.
  • 클래스가 설계도라면 이 클래스가 인스턴스가 되어 메모리에 올라갔을 때, 이것을 객체라고 한다.

추상화

  • 중요한 것은 남기고, 불필요한 것들은 제거한다.

캡슐화(encapsulation)

  • 관련된 것을 잘 모아서 갖게 하는것, 관련된 것을 잘 가지고 있을 수록 응집도가 높다고 한다.
  • ex. 앞서 본 Math라는 클래스는 수학과 관련된 기능들만 가지고 있다.
    • Math 클래스에 주소 찾는 기능이 있으면 안될 것

객체 지향 프로그래밍

  • 많은 객체들이 모여서 상호협력하며 데이터를 처리하는 방식의 프로그래밍 설계 방법을 일컫는다.
  • 쉽게 말하자면 프로그램을 묶음 단위로 쪼개서 추후에 가져다 쓰기 편하게 만들어 놓은 프로그래밍 방식이라고 생각하면 된다.
  • 자바는 객체 지향 언어이다.(클래스 지향 x)
  • 반대 개념으로는 절차적 프로그래밍이 있다.
    • 함수(function)를 이용하여 정리정돈하는 프로그래밍 기법이다.
  • 객체 지향은 사용 기법이 아닌 설계 기법이다.

객체의 역할과 책임

  • 좋은 객체란 역할과 책임에 충실하며 다른 객체와 잘 협력하여 동작하는 객체를 말한다.
    • 좋은 객체는 응집도가 높고 결합도(Coupling)는 낮다.
      • 응집도가 높다 ⇒ 관련된 기능만 모아서 잘 가지고 있다.
        • 세탁기가 세탁을 잘해야지 세탁은 못하는데 부가적인 기능만 잘하면 안 될 것이다.
      • 결합도가 낮다. ⇒ 객체와 객체의 결합도가 낮아야 사용성이 편하다.
        • 컴퓨터가 있는데 부가적인 기능이 꼭 있어야만 사용이 가능하다면 사용성이 떨어질 것이다.
  • 나쁜 객체란 여러가지 역할을 한 가지 객체에게 부여하거나 이름과는 맞지않는 속성과 기능을 가지도록 하거나 제대로 동작하지 않는 객체를 말한다. 또한 다른 객체와의 협력도 매끄럽지 않은 객체를 의미한다.

클래스

  • 자동차를 만들기 위해서는 설계도를 보고 만들어야한다.
  • 자바에서는 이 설계도가 클래스, 클래스로 만들어진 내용을 인스턴스(객체), 클래스로 인스턴스를 만드는 것을 인스턴스화라고 한다.
    • 하나의 설계도로 무수히 많은 자동차를 만들 수 있는 것처럼 무수히 많은 인스턴스를 생성할 수 있다.

클래스 선언

  • 클래스의 선언 규칙
    • 하나 이상의 문자로 이루어져야 한다. → Car, SuperCar
    • 첫글자에는 숫자가 올 수 없다. → 1Car, 2SuperCar (x)
    • ‘$’, ‘_’외의 특수문자는 올 수 없다. → $Car, _Car (o) @Car, #Car (x)
    • 자바의 키워드는 사용할 수 없다. →int, for (x)
    • 단어의 첫 문자는 대문자로 정해야한다. →Car, SuperCar
      • UpperCamelCase

객체 생성

  • 클래스를 만들었다면 설계도는 만들었다. 그러면 인스턴스는 어떻게 생성할까?
  • 인스턴스는 new라는 키워드로 생성한다.
    클래스 변수명 = new 클래스();
    • 위와 같은 형식으로 인스턴스를 생성하면 힙영역에 클래스의 인스턴스가 생성되고 스택영역의 변수명에는 이 인스턴스의 주소 번지가 저장된다.
    • 예제
      public class Car{}
      public class CarExam{
      	public static void main(String[] args) {
              Car car = new Car();
        }
      }

클래스의 용도는 두가지가 있다. 하나는 API용이고 다른 하나는 실행용이다. API는 다른 클래스에서 이용할 목적으로 설계되고 실행 클래스는 main메소드를 제공하는 역할을 한다. 위 예에서 Car는 API용이고 CarExam은 실행 클래스이다.

클래스의 구성 멤버

필드

  • 객체의 고유 데이터, 부품 객체, 상태 정보를 저장하는 곳이다.
  • 생성자와 메소드 전체에서 사용되며 객체가 소멸되지 않는 한 객체와 함께 존재한다.
  • 클래스 내부에서 선언 가능하고 외부에서 사용할 때는 객체를 생성하고 참조 변수를 통해 사용해야 한다.
  • 클래스가 가지는 속성이자 다른 언어에서는 멤버 변수라고 하는 경우도 있다.
  • 필드 선언 방법
    • [접근 제한자][static] [final] 타입 필드명 [=초기값];
    • 대괄호 안에 있는 내용은 생략 가능하다는 뜻
    • 접근 제한자는 public, protected, private 아무것도 없는 경우는 (default)
    • 필드명은 첫번째 문자가 소문자로 시작
    • 타입은 기본형 타입(boolean, int, double… 등 값을 가질 수 있는 타입)과 참조 타입(class, 인터페이스, 배열) 등이 나올 수 있다.
    • 초기값이 없는 경우
      • 참조형일 경우 ⇒null
      • boolean일 경우 ⇒ False
      • 나머지 기본형은 다 0으로 초기화

생성자

  • 생성자는 new 연산자로 호출되는 특별한 중괄호 {} 블록이다.
  • 객체 생성 시 초기화를 위해 사용하며 객체를 사용할 준비를 한다.
    • 객체 초기화 : 필드를 초기화하거나 메소드를 호출하여 객체를 사용할 준비를 하는것
    • 생성자를 실행하지 않고는 클래스로부터 객체를 만들 수 없다,
  • 클래스 이름으로 되어있고 리턴 타입이 없다.
  • 모든 클래스는 생성자가 반드시 존재하며 생성자를 하나 이상 가질 수 있다. 우리가 생성자 선언을 생략했다면 컴파일러는 바이트 코드에 자동으로 기본 생성자를 추가한다.
    • 예제
      public class Car{
      	//public Car(){} -> 자동 추가, 기본 생성자
      }
      public class CarExam{
      	public static void main(String[] args) {
              Car car = new Car();
        }
      }
      • 위 코드에서도 생성자를 생성하지 않았지만 자동으로 Car()라는 기본 생성자가 추가되어 new 키워드 뒤에 생성자 메소드 Car()를 사용할 수 있다.
      • class가 public으로 선언되어있으면 생성자도 public으로 선언된다. class에 public이 사라지면 생성자에도 public이 붙지 않는다.
  • 생성자 선언
    • 기본 구조
      클래스명(){};
    • 예제
      public Car(String name, int studentId, int birthData){...}
      
  • 생성자 선언 후 매개변수로 필드 초기화
    • 예제

      public class Car {
          String name;
          int studentId;
          int birthData;
          public Car(String name, int studentId, int birthData){
              this.name = name;
              this.studentId = studentId;
              this.birthData = birthData;
          }
          public void print(){
              System.out.println("name = " + name);
              System.out.println("studentId = " + studentId);
              System.out.println("birthData = " + birthData);
          }
      }
      
      public class CarExam {
          public static void main(String[] args) {
              Car car = new Car("kimminje", 100, 1999);
              car.print();
          }
      }
      ->name = kimminje
      	studentId = 100
      	birthData = 1999
    • 위 코드에서 Car 클래스는 생성자를 통해 현재 객체의 필드에 받은 값을 넣어주고 print 메소드를 통해 출력해주는 클래스로 구성되었다.

    • this라는 키워드는 객체 자신의 참조라는 뜻이며 위처럼 ‘this.필드’로 사용하면 객체 자신의 필드를 뜻한다.

생성자 오버로딩

  • 생성자는 여러가지 타입의 매개변수를 받아서 사용하기 위해 메소드 오버로딩 기능을 제공한다.
  • 메소드 오버로딩은 아래 메소드 항목에서 더 공부한다.
  • 예제
    public class Car {
        String name;
        int studentId;
        int birthData;
        public Car(String name, int studentId){
            this.name = name;
            this.studentId = studentId;
        }
        public Car(String name, int studentId, int birthData){
            this.name = name;
            this.studentId = studentId;
            this.birthData = birthData;
        }
        public void print(){
            System.out.println("name = " + name);
            System.out.println("studentId = " + studentId);
            System.out.println("birthData = " + birthData);
        }
    }
    
    public class CarExam {
        public static void main(String[] args) {
            Car car1 = new Car ("minjekim",50);
            Car car2 = new Car("kimminje", 100, 1999);
            car1.print();
            car2.print();
        }
    }
    
    ->name = minjekim
    	studentId = 50
    	birthData = 0
    	name = kimminje
    	studentId = 100
    	birthData = 1999
    • 위와 같이 동일한 메소드 명으로 매개변수를 다르게 해주고 사용하면 같은 생성자로 매개변수를 다르게 받아서 초기화할 수 있다.

메소드

  • 객체의 동작에 해당하는 중괄호 {}블록이다.
  • 메소드를 호출하면 중괄호 안에 있는 코드들이 실행된다.
  • 메소드 선언
    • 기본 구조
      리턴타입 메소드명(){};
      • 리턴 타입은 이 메소드가 반환해주는 값의 타입을 말한다. 리턴 값이 있는 경우는 타입을 명시해줘야하고 리턴 타입이 없는 경우에는 void를 사용한다.
    • 메소드명 규칙
      • 하나 이상의 문자로 이루어져야 한다. → car, superCar
      • 첫글자에는 숫자가 올 수 없다. → 1Car, 2SuperCar (x)
      • ‘$’, ‘_’외의 특수문자는 올 수 없다. → $Car, _Car (o) @Car, #Car (x)
      • 여러 단어가 있는 경우 첫 단어빼고 뒤이어 오는 단어의 첫 글자는 대문자로 작성한다. → superCar
  • 매개변수의 개수를 모를 경우 꿀팁
    • 매개변수의 개수를 모를 경우 매개변수를 배열 타입으로 받으면 된다.

      public class MethodExam {
          static int sum(int[] value){
              int sum = 0;
              for (int i = 0; i<value.length; i++){
                  sum+=value[i];
              }
              return sum;
          }
          public static void main(String[] args) {
              int s = sum(new int[]{1,2,3,4,5});
              System.out.println(s);
          }
      }
      -> 15

메소드 오버로딩

  • 클래스 내 같은 이름의 메소드를 여러 개 선언하는 것을 의미한다.
  • 매개변수를 다양하게 받아 처리하기 위해 사용한다.
  • 예제
    public class Cal {
        public int plus(int x, int y){
            return x+y;
        }
        public double plus(double x, double y){
            return x+y;
        }
    }
    
    public class Exam {
        public static void main(String[] args) {
            Cal sum = new Cal();
            int int_plus = sum.plus(1,2);
            double double_plus = sum.plus(1.5,2.8);
            
            System.out.println("int_plus = " + int_plus);
            System.out.println("double_plus = " + double_plus);
        }
    }
    ->int_plus = 3
    	double_plus = 4.3
    • 위 코드처럼 다양한 매개변수를 받아 사용하고 싶을 때 메소드 오버로딩을 사용할 수 있다.

인스턴스 멤버와 정적 멤버

  • 만약 객체마다 필드 값이 달라야한다면 필드를 객체마다 다 가지고 있는 것이 맞지만 필드 값이 객체에서 다 같아야한다면 모든 객체가 필드를 가지고 있을 필요는 없다.
  • 자바는 이런 경우를 위해 클래스 멤버를 인스턴스 멤버와 정적 멤버로 구분하여 선언할 수 있도록 한다.
  • 인스턴스 멤버는 객체마다 가지고 있는 멤버를 말하고 정적 멤버는 클래스에 위치시키고 객체들이 공유하는 멤버를 말한다.

인스턴스 멤버

  • 객체(인스턴스)를 생성한 후 사용할 수 있는 필드와 메소드를 말한다.
  • 우리가 위에서 사용했던 예제인 Car 예제를 보자
    public class Car {
        String name;
        int studentId;
        int birthData;
        public Car(String name, int studentId, int birthData){
            this.name = name;
            this.studentId = studentId;
            this.birthData = birthData;
        }
        public void print(){
            System.out.println("name = " + name);
            System.out.println("studentId = " + studentId);
            System.out.println("birthData = " + birthData);
        }
    }
    
    public class CarExam {
        public static void main(String[] args) {
            Car car = new Car("kimminje", 100, 1999);
            car.print();
        }
    }
    • 여기서 Car 클래스의 메소드를 사용하기 위해 Car car = new Car()의 코드를 통해 인스턴스를 생성했다.
    • 위 Car 클래스에서name, studentId, birthData는 인스턴스 필드이고 print 메소드는 인스턴스 메소드이다.
    • 인스턴스 멤버들에 접근하기 위해서는 Car 인스턴스를 생성하여 접근해야한다.

정적 멤버와 static

  • 정적(static)은 ‘고정된’이라는 의미를 가지고 있다
  • 정적멤버는 클래스에 고정된 멤버로 객체를 생성하지않고 사용할 수 있는 필드와 메소드를 말한다.
  • 이들을 정적 필드(static field)와 정적 메소드(static method)라 부른다.
  • static한 필드와 메소드는 객체(인스턴스)에 소속된 멤버가 아니라 클래스에 고정된 멤버이므로 클래스들이 할당하기 때문에 클래스 로딩이 끝나는 즉시 사용 가능하다.
  • static으로 생성된 멤버들은 메모리에서 Static 영역에 따로 할당된다.
  • Static 영역에 할당된 메모리들은 모든 객체가 공유하여 하나의 멤버를 어디서든지 참조할 수 있는 장점을 가지지만 Garbage Collctor의 관리 영역 밖에 존재하기에 Static 영역에 멤버들은 프로그램 종료시까지 메모리가 할당된 채 존재하게 된다.
  • static을 너무 많이 사용하게 되면 메모리 측면에서 비효율적으로 시스템에 악영향이 갈 수 있다.
  • 정적 메소드들은 호출할 때 인스턴스를 따로 생성하지 않아도 사용할 수 있다.
  • 정적 멤버는 static키워드로 선언하고 인스턴스를 생성하지 않고 바로 사용이 가능하다.
  • 그럼 설명이 너무 길었으니 예제를 보자
  • 우리가 위 Cal예제에서 인스턴스 생성의 불필요함을 느끼고 있다고 생각하면 아래와 같이 코드를 바꿀 수 있다.
    public class Cal {
        public static int plus(int x, int y){
            return x+y;
        }
        public static double plus(double x, double y){
            return x+y;
        }
    }
    
    public class Exam {
        public static void main(String[] args) {
            int int_plus = Cal.plus(1,2);
            double double_plus = Cal.plus(1.5,2.8);
    
            System.out.println("int_plus = " + int_plus);
            System.out.println("double_plus = " + double_plus);
        }
    }
    ->int_plus = 3
    	double_plus = 4.3
    • 위 코드는 plus메소드를 static 선언하여 인스턴스를 생성하지 않고 바로 사용하였다.
  • 정적 메소드 선언 시 주의점
    • 정적 메소드 내부에서 인스턴스 필드나 인스턴스 메소드를 사용할 수 없다

    • 확인해보자

      int i = 0;
      static int j = 1;
      void exam2(){}
      static void exam3(){}
      public static int exam2(){
          //컴파일 에러
          this.i = 1;
          i=1;
          this.exam2();
          exam2();
      
          //실행 성공
          j=1;
          exam3();
      }
    • this 키워드를 통한 인스턴스 필드나 인스턴스 메소드에 접근이 안되는 것을 알 수 있다.

    • 그러면 main 메소드도 static이니까 바깥에서 선언된 필드에 접근 못하나??

      int i = 0;
      static int j = 0;
      public static void main(String[] args) {
          //컴파일 에러
          this.i=1;
          i=1;
          
          //실행 성공
          j=1;
      }
    • main 메소드도 똑같은 것을 확인할 수 있다.

싱글톤

  • 프로그램에서 단 하나의 객체만 만들도록 보장해야 하는 경우가 있다. 이러한 객체를 단 하나만 생성해야한다고 해서 싱글톤(singleton)이라고 한다.
  • 싱글톤을 만드려면 클래스 외부에서 new 연산자로 생성자를 호출할 수 없도록 막아야한다.

생성 과정

  • 클래스 외부에서 생성자를 호출할 수 없도록 생성자 앞에 private 접근 제한자를 붙여준다.
  • 자신의 타입인 정적 필드를 private으로 외부에서 접근하지 못하도록 하나 선언하고 자신의 객체를 생성하여 초기화한다.
    • 생성자 접근 제한자를 private으로 하였기 때문에 클래스 내부에서는 접근 가능
  • 외부에서 호출할 수 있는 정적 메소드인 getInstance를 선언하고 정적 필드에서 참조하고있는 자기 자신의 객체를 반환해준다.
  • 기본 구조
    public class 클래스 {
        private static 클래스 singleton = new 클래스();
        private 클래스(){}
        static 클래스 getInstance(){
            return singleton;
        }
    }
    • 예제

      public class Exam {
          private static Exam singleton = new Exam();
          private Exam(){}
          static Exam getInstance(){
              return singleton;
          }
      }
      public class Main{
      		public static void main(String[] args) {
      				Exam exam1 = Exam.getInstance();
      				Exam exam2 = Exam.getInstance();
      			
      				if (exam1==exam2){
      						System.out.println("같은 singleton 객체");
      				}else{
      						System.out.println("다른 singleton 객체");
      				}
      		}
      }
      ->같은 singleton 객체
profile
블로그 이전했습니다!! 👉 https://alswp006.github.io/

0개의 댓글