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

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

: 부품 객체를 먼저 만들고, 하나씩 조립해 완성된 프로그램을 만드는 기법

객체(object)

  • 물리적으로 존재하는 것
  • 추상적인 것 중 자신의 속성과 동작을 가지는 모든 것
  • 객체는 필드(속성)메소드(동작)로 구성된 자바 객체로 모델링 가능
  • 객체들은 서로 간에 기능(동작)을 이용하고 데이터를 주고받음

객체와 클래스

개발자 → (설계) → 클래스 → (인스턴스화) → 인스턴스(객체)

  • 클래스에는 객체를 생성하기 위한 필드와 메소드 정의
  • 클래스로부터 만들어진 객체를 해당 클래스의 인스턴스
  • 하나의 클래스로부터 여러 개의 인스턴스 생성 가능
  • 객체 = 클래스의 인스턴스

절차지향형 vs 객체지향형

  • 절차지향형 프로그래밍: 순서에 맞추어 단계적으로 실행 → 기능 중심
  • 객체지향형 프로그래밍: 객체를 구성하고 객체단위로 프로그래밍 → 객체 중심
// 객체지향프로그래밍(OOP)
// 모든 사물을 객체(물건)로 추상화(모델링, 설계)하여
// 프로그래밍하는 기법
// 예) 자동차
// 속성(변수, 필드)과 행동(메서드, 함수)으로 구분한다.

// 클래스 선언부
class Car {
    // 속성 = 변수(필드) = 멤버 변수
    int price = 1000;

    // 행동(동작) = 함수(메서드) = 멤버 함수
    void run() {
        System.out.println("달린다. ");
    }
}

public class ex25 {
    public static void main(String[] args) {
        // 클래스 생성 및 호출부
        // 클래스이름 객체이름 = new 클래스이름();
        Car objCar = new Car();
        // 객체이름 뒤에 점(.)을 찍으면 멤버변수/함수에 접근가능
        System.out.println(objCar.price);
        objCar.run();
        // System.out.println(objCar.run()); // void 타입은 값이 아님!(오류발생)

    }
}

this

자신이 속한 클래스의 객체

  • 모든 필드와 메서드 활용시에는 소속과 함께 표기해야 한다.
  • 클래스 내부의 필드와 메서드에 소속을 표기하지 않는 경우, 컴파일러가 자동으로 this 붙여줌
  • static에서는 this 키워드 사용 X
  • 지역 변수에는 this 붙지 않음

클래스와 객체

클래스 이름

  • 첫글자는 대문자
  • 자바 키워드 사용 X
  • $, _ 이외의 특수문자 X

클래스 선언과 컴파일

소스 코드 작성 → 컴파일(javac.exe) → 클래스이름.class

  • 소스 파일 하나 당 하나의 클래스 선언하는 것이 관례
  • 두 개 이상의 클래스도 선언 가능하지만. 소스 파일 이름과 동일한 클래스만 public으로 선언 가능
  • 선언한 개수만큼 바이트 코드 파일 생성됨

new 연산자

  • 객체 생성 역할: new 클래스();
  • 생성자를 호출하는 코드로, 생성된 객체는 힙 메모리 영역에 생성된다.
  • new 연산자는 객체 생성 후, 객체 생성 번지 리턴
  • A a = new A();
    • A: 클래스
    • a: 참조변수
    • new: heap영역에 넣어라
    • A(): 생성자
  • 모든 생성 객체는 동일한 메서드를 가진다. 따라서, 모든 객체는 메서드를 공유한다. → 각 다른 메모리 주소에 할당됨.

클래스 구성 멤버

public class ClassName{
	// 필드
	int fieldName;
	
	// 생성자
	ClassName() {...} 

	// 메소드
	void methodName() {...}
}
  1. 필드=멤버변수=속성=변수: 객체의 데이터가 저장되는 곳
  2. 생성자: 객체 생성시 초기화 역할 담당 → 사용할 수도 안할 수도 있음
  3. 메소드=행동=동작=함수=멤버 함수: 객체의 동작에 해당하는 실행 블록

연습문제

// 연습문제 - 클래스 설계
// 카페를 클래스로 설계해보자
// 클래스이름 : Cafe
// 속성 : coffeeCount   커피갯수는 10개로 초기화
// 행동 : sale          출력값은 "커피를 판다"
// 클래스를 설계하고, 객체를 생성해서 속성값을 출력하고,
//  행동을 실행시켜보자.
// sale함수를 호출하면 coffeeCount가 하나 준다.
// sale함수를 3번 호출후, coffeeCount를 출력하시오.

// 2.
// 당근농장을 클래스로 설계해 보자
// 클래스이름 : Farm
// 속성 : carrot 당근의 갯수 초기값은 0
// 행동 : plant() 호출시마다 당근을 하나씩 생산하고,
//       속성 carrot++를 해준다.
//       호출시 "당근을 1개 생산했습니다." 출력한다.
// 당근을 plant()함수를 이용하여 5개 생산한 후 당근 갯수를
// 출력하시오.
class Cafe {
    int coffeeCount = 10; // 커피 갯수

    void sale() {
        System.out.println("커피를 판다.");
        int coffeeCount = 5; // sale 메서드 안에서 사용 가능
        // this는 자기 클래스를 의미한다.
        this.coffeeCount -= 1; // 멤버변수 의미
    }
}

class Farm {
    int carrot = 0;

    void plant() {
        this.carrot += 1;
        System.out.println("당근을 1개 생산했습니다.");
    }
}

public class ex26 {
    public static void main(String[] args) {
        Cafe cafe = new Cafe();

        System.out.println("처음 커피 갯수>> " + cafe.coffeeCount);
        cafe.sale();
        cafe.sale();
        cafe.sale();
        System.out.println("3번 호출 후 커피 갯수>> " + cafe.coffeeCount);

        Farm farm = new Farm();
        farm.plant();
        farm.plant();
        farm.plant();
        farm.plant();
        farm.plant();
        System.out.println("당근 갯수>> " + farm.carrot);
    }
}

메소드

객체의 동작에 해당하는 실행 블록

(접근지정자) (static) 반환타입 메서드이름 (매개변수){
	메서드 내용
}
public static int sum(int a, int b){
	
}
  • 리턴(반환)타입: 메서드 완료 후 반환되는 타입으로, 메서드 내에 return이 존재
    • 리턴값이 없는 경우 void로 선언
  • 중복 코드 재사용 가능
  • 코드의 모듈화를 통해 코드 가독성 향상
class MyClass {
    // 메소드 4가지 패턴
    // 선언부
    // 매개변수 X 반환형 X
    void func1() {
        System.out.println("func1");
    }

    // 매개변수 O 반환형 X
    void func2(int x, int y) {
        System.out.println("func2");
        System.out.println(x + " " + y);
    }

    // 반환형이 있는 경우, 리턴 값과 타입이 일치해야함
    // 매개변수 X 반환형 O
    int func3() {
        System.out.println("func3");
        return 10;
    }

    // 매개변수 O 반환형 O
    int func4(int x, int y) {
        System.out.println("func4");
        return x + y;
    }

}

public class ex28 {
    public static void main(String[] args) {
        // 호출부
        MyClass myClass = new MyClass();
        myClass.func1();
        myClass.func2(10, 20);
        int result = myClass.func3();
        System.out.println(result);
        result = myClass.func4(10, 20);
        System.out.println(result);
    }
}

static 변수/함수

static 키워드: 객체 생성 없이 바로 사용 가능

class A{
	int m = 3; // 인스턴스 필드는 객체 생성 후 사용 가능
	static int n = 5; // static 필드는 객체 생성없이 사용 가능
}
A a = new A();
System.out.println(a.m); // 3

System.out.println(A.n); // 5
  • static 필드는 힙 메모리 객체에는 저장공간이 없음! static 영역에 존재!
    class(code) 영역, static 영역, final 영역, 메서드 영역Stack 영역Heap 영역
// static 변수/함수에 대하여
// static 예약어: 정적 변수(객체)/함수를 지정할 때 사용
// 의미: 프로그램 구동시에 고정된 메모리 번지에 들어감. (자동 new)
//      프로그램 종료시까지 변경되지 않음
// 사용이유: 1. 시작점(Entry Point)를 지정할 때 사용
//         2. 중요한 데이터를 안정적으로 저장할 때 주로 사용
//         3. 자주 사용하는 유틸성 클래스에 지정한다.
//         4.  new를 안해도 클래스 함수 사용 가능

class BallFactory {
    int ballCount = 100;

    void make() {
        this.ballCount += 1;
        System.out.println("축구공 생산!");
    }
}

class FoodFactory {
    static int foodCount = 200;

    static void make() {
        foodCount += 1;
        System.out.println("도시락 생산!");
    }
}

public class ex27 {
    // 전역 변수 또는 중요한 데이터 저장용
    public static BallFactory myBallFactory;

    public static void main(String[] args) {
        BallFactory ballFactory = new BallFactory();
        System.out.println(ballFactory.ballCount);

        // static 변수/함수는 클래스이름 뒤에 점을 찍어서 접근
        System.out.println(FoodFactory.foodCount);
        FoodFactory.make();
        // 예)
        System.out.println(Math.random());
    }
}

접근 제어자

클래스 및 클래스의 구성 멤버에 대한 접근을 제한하는 역할

  • 다른 패키지에서 클래스 사용하지 못하도록 → 클래스 제한
  • 클래스로부터 객체를 생성하지 못하도록 → 생성자 제한
  • 특정 필드와 메소드를 숨김 처리 → 필드와 메소드 제한
  • 클래스 선언할 때 필드는 일반적으로 private 접근 제한
    • Getter/Setter 필요
    • Getter: private 필드의 값을 리턴하는 역할
    • Setter: 외부에서 주어진 값을 필드 값으로 수정
접근범위 넓음public동일 패키지의 모든 클래스 + 다른 패키지의 모든 클래스에서 사용 가능
protected동일 패키지의 모든 클래스 + 다른 패키지의 자식 클래스에서 사용 가능
default동일 패키지의 모든 클래스에서 사용 가능
접근범위 낮음private동일 클래스에서 사용 가능
// 접근제한자
//  : 클래스, 함수, 변수 앞에 위치하여 접근을 제한할 때 사용
// * C언어:  접근제한자가 없어서, 모든 곳에서 접근가능.
//       :  변수의 변경을 감지하기 어렵다. 유지보수가 힘들다.
// public: 같은 폴더(패키지)에서, 다른 폴더의 클래스에서 접근 가능
// protected: 같은 폴더 + 상속관계 클래스에서 접근 가능
// default: 같은 폴더
// private: 같은 클래스안에서 접근 가능(캡슐화, 은닉)
//        : Getter/Setter 함수를 통해서 접근 가능하도록 허용

class Hong {
    String name = "홍길동"; // default: 같은 폴더 + 자기 클래스
    public int age = 30; // public: 모든 폴더+상속관계+같은 폴더+자기 클래스
    protected int korScore = 80; // protected: 같은 폴더+상속관계 클래스
    private int engScore = 90; // private: 자기 클래스에서만 접근 가능
    // Getter/Setter 함수를 통해서 접근 가능하도록 한다.

    public int getEngScore() {
        return engScore;
    }

    public void setEngScore(int engScore) {
        this.engScore = engScore;
    }

    void printEngScore() {
        System.out.println(this.engScore);
    }

}

public class ex29 {
    public static void main(String[] args) {
        Hong hong = new Hong();
        // hong.engScore; // 다른 클래스에서 접근 불가 - error 발생
        System.out.println(hong.getEngScore());
        hong.setEngScore(70);
    }
}

메서드 오버로딩

동일한 메서드 명의 매개변수 타입과 개수를 다르게 정의하여 함수의 기능을 확장한 것

  • 컴파일러는 메서드 시그니처(메서드 이름 혹은 매개변수 타입)가 다르면 메서드 이름이 동일해도 다른 메서드로 인식
public class ex31 {
    // main 함수가 static이므로, 함수도 static이어야 함.
    // static은 미리 공간을 확보하기 때문에, main 함수에서 사용하는 함수 역시 공간을 미리 확보해주어야 함.
    static void echo() {
        System.out.println("echo");
    }

    static void echo(int x) {
        System.out.println("echo: " + x);
    }

    static void echo(String msg) {
        System.out.println("echo: " + msg);
    }

    static void calc(int num) {
        System.out.println(num);
    }

    static void calc(int num1, int num2) {
        System.out.println(num1 + num2);
    }

    static void calc(int num1, int num2, int num3) {
        int max = Math.max(num1, num2);
        max = Math.max(max, num3);
        System.out.println(max);
    }

    public static void main(String[] args) {
        // 메소드 오버로딩(Overloading, 과적)
        //      : 매개변수의 타입과 갯수를 다르게 함으로
        //        함수의 기능을 확장하는 것.
        //      : 같은 이름의 함수를 사용할 수 있다.
        // echo();
        // echo(10);
        // // ex) println
        // System.out.println();
        // System.out.println(10);
        // System.out.println("야호");

        // 연습문제 - 오버로딩 연습
        // 메소드(함수) 이름 : calc
        // 반환형은 없음.
        // 1. 매개변수 정수형 1개일때는 그냥 변수값만 출력
        // 2. 매개변수 정수형 2개일때는 두 변수의 합계를 출력
        // 3. 매개변수 정수형 3개일때는 세 변수중에서
        //   최대값을 출력하시오.
        calc(1);
        calc(1, 2);
        calc(1, 2, 3);

    }
}

싱글톤(Singleton)

프로그램안에서의 유일한 클래스 객체

// 방법1
class ClassName {
	private static ClassName singleton =
									new ClassName();

	static ClassName getInstance(){
			return singleton;
	}
}
// 방법2
class ClassName {
	private static ClassName singleton;

	static ClassName getInstance(){
			if(singleton == null) {
				singleton = new ClassName();
			}
			return singleton;
	}
}
// 싱글톤(Singleton)
//      : 프로그램안에서의 유일한 클래스 객체
//      : new 를 통해서 여러개의 객체를 반복적으로 찍어낼 수 있다.
//      : 예) 붕어빵1, 붕어빵2, ...
//           절대 붕어빵(유일한 붕어빵)
// 유일한 객체가 필요한 이유
//  : 유일한 정보를 저장하기 위해서
//  : 데이터를 안정적으로 가지고 있을 수 있다.
class FishBun { // 일반 붕어빵: 일반 객체
    int bunNo = 10;
}

class UniqueFishBun { // 절대 붕어빵: 싱글톤
    int bunNo = 30;

    // 내부적으로 가지고 있음
    private static UniqueFishBun singleton =
            new UniqueFishBun();

    static UniqueFishBun getInstance() {
        return singleton;
    }
}

class UniqueFishBun2 { // 절대 붕어빵: 싱글톤
    int bunNo = 30;

    // 내부적으로 가지고 있음
    private static UniqueFishBun2 singleton;

    static UniqueFishBun2 getInstance() {
        // null 일 때만 싱글톤 호출하고, 그 이후로는 호출 X
        if (singleton == null) {
            singleton = new UniqueFishBun2();
        }
        return singleton;
    }
}

public class ex32 {
    public static void main(String[] args) {
        // uBun1과 uBun2는 메모리 주소가 같음 -> 번지수가 같음
        UniqueFishBun uBun1 = UniqueFishBun.getInstance();
        UniqueFishBun uBun2 = UniqueFishBun.getInstance();
        System.out.println(uBun1); // 7a81197d
        System.out.println(uBun2); // 7a81197d

        // bun1, bun2, bun3는 각각 생성되어 메모리 주소가 다름
        FishBun bun1 = new FishBun();
        FishBun bun2 = new FishBun();
        FishBun bun3 = new FishBun();
        System.out.println(bun1); // 메모리 주소: 7a81197d
        bun1.bunNo = 20;
        System.out.println(bun2); // 메모리 주소: 5ca881b5
        System.out.println(bun1.bunNo);
        System.out.println(bun2.bunNo);
        // 일반 객체는 일관된 데이터를 저장할 수 없다.
        // dynamic 하다. 객체의 생성과 소멸이 자유롭게 이루어짐.
        // GC(Garbage Collector)가 자동으로 메모리를 회수함.

    }
}

생성자 함수(Constructor)

클래스 객체 생성될 때(new) 자동으로 호출되는 함수(메소드)

(public) 반환타입생략 클래스이름() { }

  • 용도: 객체 생성 시 해당 클래스 초기화하기 위해 사용
  • 일반함수는 클래스이름과 동일하면 안됨
  • 생성자함수는 클래스이름과 동일해야함, 반환타입 X
  • 모든 클래스는 생성자가 반드시 존재하며 하나 이상 가질 수 있음!
    • 생성자가 하나도 존재하지 않는 경우, 컴파일러가 스스로 기본 생성자 추가해줌!
// 생성자 함수 - Constructor
// : 클래스 객체가 생성될 때(new) 자동으로 호출되는 함수(메소드)
class Book {
    // 속성
    int price = 1000;

    // 행동
    void read() {
        System.out.println("책을 읽는다.");
    }

    // 생성자 함수
    // 패턴: public 반환타입생략 클래스이름(){ }
    public Book() {
        this.price = 2000;
        System.out.println("생성자 함수 호출됨.");
    }
}

public class ex34 {
    public static void main(String[] args) {
        Book book = new Book();
        System.out.println(book.price); // 2000
    }
}
  • 생성자도 오버로딩 가능!
    // 생성자 함수 - 메소드 오버로딩 가능
    //        : 매개변수의 타입과 개수를 다르게함으로 함수를 확장하는 것.
    class Robot {
        String color = "빨강";
        int price = 1000;
    
        // 기본(매개변수가 없는) 생성자함수
        public Robot() {
            System.out.println("기본 생성자함수");
        }
    
        // 매개변수가 있는 생성자함수
        public Robot(String color) {
            this.color = color;
            System.out.println("매개변수가 있는 생성자함수");
        }
    
        public Robot(String color, int price) {
            this.color = color;
            this.price = price;
        }
    }
    
    public class ex38 {
        public static void main(String[] args) {
            Robot r1 = new Robot(); // 기본생성자 호출
            Robot r2 = new Robot("파랑"); // 매개변수가 color인 생성자 호출
            Robot r3 = new Robot("보라", 2000); // 매개변수가 color, price인 생성자 호출
        }
    }
    
  • this() 메서드는 생성자 내부에서만 사용 가능!
    • 자기 클래스 내부의 다른 생성자를 호출

    • 반드시 중괄호 이후 첫 줄에 위치해야함

      class A {
      	A() {
      		System.out.println("첫번째 생성자");
      	}
      	A(int a){
      		this(); // 첫줄에 위치
      		System.out.println("두번째 생성자");
      	}
      }
      
      public static void main(String[] args) {
      		A a = new A(3);
      		// 첫번째 생성자
      		// 두번째 생성자    
      }

static 초기화 블럭

static 키워드: 객체 생성 없이 사용 가능

class, static, final, 함수StackHeap
  • new로 객체 생성 후 사용해도 되지만, 굳이 안그래도됨 → 지양
  • static 필드프로그램 초기화 시 바로 메모리에 공간이 할당되어 사용 가능
  • static 메서드는 static 변수만 접근 가능 → 객체 생성 이전에 사용해야하기 때문!
  • static 메서드 안에서 this 사용 불가!
  • static 초기화 블록클래스 호출 시 처음 한번만 호출됨
// static 초기화 블럭
class StaticClass {
    int a;
    
		// 프로그램 시작시 초기화됨
		static int b; // static int b = 0; // 암묵적으로 0으로 초기화됨

    static { // => 클래스 호출 시 처음 한번만 호출됨
        b = 5;
        System.out.println("static block!");
    }

    // 생성자 함수 - new를 통해 객체가 생성될 때 호출된다!
    StaticClass() {
        this.a = 3;
        b = 10;
        System.out.println("constructor block");
    }
}

public class ex35 {
    public static void main(String[] args) {
        System.out.println(StaticClass.b); // 5
        StaticClass s = new StaticClass();
        System.out.println(StaticClass.b); // 10
    }
}

클래스 객체 배열

// 클래스 객체 배열 사용하기
class Snack {
    String name = "새우깡";
    int price = 1000;

    // 필드가 있는 생성자
    public Snack(String name, int price) {
        this.name = name;
        this.price = price;
    }
}

public class ex42 {
    public static void main(String[] args) {
        // 정수형 1차 배열
        int[] nums = new int[5];
        // 클래스 객체 1차 배열
        Snack[] snacks = new Snack[5];
        // 객체니까 new 로 객체 생성해서 배열에 할당
        snacks[0] = new Snack("짱구", 2000);
        snacks[1] = new Snack("새우깡", 3000);
        snacks[2] = new Snack("포카칩", 4000);
        snacks[3] = new Snack("홈런볼", 3500);
        snacks[4] = new Snack("프링글스", 5000);

        for (Snack snack : snacks) {
            System.out.println(snack.name);
            System.out.println(snack.price);
        }

    }
}

0개의 댓글