CH_06 클래스

한지혜·2025년 3월 7일

객체 지향 프로그래밍

(OOP; Object Oriented Programming)

  • 객체
    • 물리적으로 존재하거나 개념적인 것 중에서 다른 것과 식별 가능한 것
    • 속성동작으로 구성 : '사람'이라는 객체의 속성은 '이름, 나이', 동작은 '웃다, 걷다'
    • 자바에서는 이러한 속성과 동작을 필드메소드라고 부른다.
  • 객체 모델링
    • 현실 세계의 객체(속성, 동작)를 소프트웨어 객체(필드, 메소드)로 설계하는 것
  • 메소드 : 객체들 사이의 상호작용 수단

    • 객체가 다른 객체의 기능을 이용할 때 메소드를 호출한다.
      int result = add(10,20)
  • 객체 간의 관계

    • 객체는 대부분 다른 객체와 관계를 맺고 있다.
    1. 집합 관계 : 완성품과 부품의 관계
    2. 사용 관계 : 다른 객체의 필드를 읽고 변경하거나 메소드를 호출하는 관계
    3. 상속 관계 : 부모와 자식 관계
  • 객체 지향 프로그래밍의 특징
    1. 캡슐화 : 객체의 데이터, 동작을 하나로 묶고 실제 구현 내용을 외부에 감추는 것
    2. 상속 : 부모 객체는 자기가 가지고 있는 필드와 메소드를 자식 객체에게 물려주어 자식 객체가 사용할 수 있도록 한다.
    3. 다형성 : 사용 방법은 동일하지만 실행 결과가 다양하게 나오는 성질
      이를 구현하기 위해서는 자동 타입 변환과 재정의 기술(상속과 인터페이스 구현)이 필요하다.
  • 객체와 클래스
    • 클래스 : 객체의 설계도
    • 인스턴스 : 클래스로부터 생성된 객체
    • 인스턴스화 : 클래스로부터 객체를 만드는 과정
    • 클래스 선언 : 객체 생성을 위한 설계도를 작성하는 작업

클래스

  • 클래스명 : 첫 문자 대문자, 캐멀 스타일, 첫 문자는 숫자가 될 수 없음, $과 _포함 가능
  • 하나의 소스 파일에 복수 개의 클래스를 선언할 수 있다.
    • 소스 파일명과 동일한 클래스만 public class(공개 클래스)로 선언할 수 있다.
    • 그러나 소스 파일 하나당 클래스 하나를 선언하는 것을 권장
  • 라이브러리 클래스와 실행 클래스 , 이 두 가지 용도가 있다.
    • 라이브러리 클래스 : 실행할 수 없음, 다른 클래스에서 이용함
    • 실행 클래스 : main() 메소드를 가지고 있는 실행 가능한 클래스

new 연산자

  • 클래스로부터 객체를 생성하는 객체 생성 연산자
  • 객체를 생성시킨 후 객체의 주소를 리턴한다.
  • new연산자는 객체 생성 후 연이어 생성자를 호출해서 객체를 초기화하는 역할을 한다.
    Animal cat = new Animal();

클래스 구성 멤버 - 생성자, 필드, 메소드

필드

  • 객체의 데이터가 저장되는 곳
  • 고유 데이터, 현재 상태 데이터, 부품 데이터가 있다.
  • 필드명 : 첫 문자 소문자, 캐멀 스타일
  • 초기값을 제공하지 않을 경우, 객체 생성 시 자동으로 기본값으로 초기화된다.
  • 외부 객체에서는 참조 변수와 도트(.)연산자를 이용해서 필드를 읽고 변경해야 한다.
    • 도트(.) : 객체 접근 연산자
  • 필드와 로컬변수의 차이

생성자

  • 객체 생성 시 초기화 역할 담당
    • 객체 초기화 : 필드 초기화를 하거나 메소드를 호출해서 객체를 사용할 준비를 하는 것
  • 기본 생성자 : 모든 클래스는 생성자가 존재, 하나 이상을 가질 수 있음. 생성자 선언 없을 시 컴파일러가 기본 생성자를 바이트코드 파일에 자동으로 추가시킴.
  • 개발자가 명시적으로 선언한 생성자가 있다면 컴파일러는 기본 생성자를 추가하지 않는다.
  public class Car {
    String model;
    String color;
    int maxSpeed;

    Car(String model, String color, int maxSpeed) {
        this.model = model;
        this.color = color;
        this.maxSpeed = maxSpeed;
 	}
  }
 
  public class CarExample {
    public static void main(String[] args) {
        Car myCar = new Car("model y", "white", 350);
//    Car myCar2 = new Car(); // 기본 생성자 호출 못함
    }
  }
  • 생성자 오버로딩 : 매개변수를 달리하는 생성자를 여러 개 선언하는 것
  • 다른 생성자 호출 :
    생성자 오버로딩이 많아질 경우, 생성자 간의 중복된 코드가 발생할 수 있다.
    이 경우, 공통 코드를 한 생성자에만 집중적으로 작성하고, 나머지 생성자는 this(...)를 사용하여 공통 코드를 가지고 있는 생성자를 호출하는 방법으로 개선할 수 있다.
  public class Car {
    String company = "Tesla";
    String model;
    String color;
    int maxSpeed;

    Car(String model) {
        this(model, "silver", 350);
    }

    Car(String model, String color) {
        this(model, color, 350);
    }

    Car(String model, String color, int maxSpeed) {
        this.model = model;
        this.color = color;
        this.maxSpeed = maxSpeed;
    }
}
public class CarExample {
    public static void main(String[] args) {
        Car myCar = new Car("model x");
        out(myCar);

        Car myCar2 = new Car("model x", "red");
        out(myCar2);

        Car myCar3 = new Car("model y", "blue", 450);
        out(myCar3);

    }

    private static void out(Car car) {
        System.out.println(car.company);
        System.out.println(car.model);
        System.out.println(car.color);
        System.out.println(car.maxSpeed);
        System.out.println();
    }
}

메소드

  • 객체의 동작으로 호출 시 실행하는 블록
  • 리턴 타입 : 메소드가 실행한 후 호출한 곳으로 전달하는 결과값의 타입
  • 메소드명 : 첫 문자는 소문자, 캐멀 스타일
  void run() { ... };
  void setSpeed(int speed) { ... };
  String getName() { ... };
  • 메소드를 호출한다 == 메소드 블록을 실행한다
  • 클래스로부터 객체 생성 후, 메소드는 생성자와 다른 메소드 내부, 객체 외부에서도 호출됨
  • 외부 객체에서는 참조 변수와 도트(.)연산자를 이용해 메소드를 호출한다.
  • 객체 내부에서는 단순히 메소드명으로 호출한다.
  • 가변길이 매개변수
    • 매개값들은 자동으로 배열 항목으로 변환되어 메소드에서 사용된다.
    • 메서드 호출 시 직접 배열을 매개값으로 제공해도 된다.
public class Computer {
    
    // 가변길이 매개변수를 갖는 메서드 선언
    int sum(int ...values) {
        int sum = 0;

        for (int value : values) {
            sum += value;
        }
        return sum;
    }
}
public class ComputerExample {
    public static void main(String[] args) {
        
        Computer com = new Computer();
        
        // 메서드 호출 시 배열 제공 1
        int result = com.sum(new int[] {1,2,3});
        System.out.println("1 : " + result);

        // 메서드 호출 시 배열 제공 2
        int[] nums = {1,2,3};
        result = com.sum(nums);
        System.out.println("2 : " + result);

        // 메서드 호출 시 매개값 1, 2, 3 제공
        result = com.sum(1,2,3);
        System.out.println("3 : " + result);
    }
}
  • return
    • 메소드의 실행을 강제 종료 후 호출한 곳으로 돌아간다는 의미
    • return문 이후 실행문 작성 시 Unreachable code 컴파일 에러 발생
  • 메소드 오버로딩
    • 메소드 이름은 같되 매개변수의 타입, 개수, 순서가 다른 메소드를 여러 개 선언하는 것

필드와 메소드는 선언 방법에 따라 인스턴스 멤버정적 멤버로 분류할 수 있다.

  • 인스턴스 멤버로 선언되면 객체 생성 후 사용 가능
  • 정적 멤버로 선언되면 객체 생성 없이도 사용 가능

인스턴스 멤버

  • 객체에 소속된 멤버 -> 객체가 있어야만 사용 가능한 멤버
public class Car {
	int gas;
    
    void setSpeed(int speed) { ... }
}
  • gas필드와 setSpeed()메소드는 인스턴스 멤버이다.
Car myCar = new Car();
myCar.gas = 10;
myCar.setSpeed(60);
Car yourCar = new Car();
yourCar.gas = 20;
yourCar.setSpeed(80);

  • gas필드는 객체마다 따로 존재하며, setSpeed()메소드는 각 객체마다 존재하지 않고 메소드 영역에 저장되고 공유된다.
  • 메소드는 객체마다 저장한다면 중복 저장으로 인해 메모리 효율이 떨어진다.
    따라서 메소드 코드는 메소드 영역에 두되 공유해서 사용하고, 이때 객체 없이는 사용하지 못하도록 제한을 걸어둔 것이다.

this 키워드

  • 객체 자신을 가리킨다.
  • 객체 내부에서 인스턴스 멤버에 접근하기 위해 사용할 수 있다.

정적 멤버

자바는 클래스 로더를 이용해서 클래스를 메소드 영역에 저장하고 사용한다.

  • 메소드 영역의 클래스에 고정적으로 위치하는 멤버
  • 객체를 생성하지 않아도 클래스를 통해 바로 사용 가능하다.

  • 객체마다 가지고 있을 필요성이 없는 공용적인 필드는 정적 필드로 선언하는 것이 좋다.
  • 인스턴스 필드를 이용하지 않는 메소드는 정적 메소드로 선언하는 것이 좋다.
public class Calculate {
	String color;				// 계산기별로 색깔이 다를 수 있다.
    static double pi = 3.14159; // 계산기에서 사용하는 파이 값은 동일하다.

	setColor(String color) { this.color = color; }  // 인스턴스 메소드
    static int plus(int x, int y) { return x + y; } // 정적 메소드
}

  • 복잡한 초기화 작업이 필요하다면 정적 블록을 이용해야 한다.
public class Television {
	static String company = "com";
    static String model = "LCD";
    static String info;
    
    // 정적 블록
    static {
    	info = company + "-" + model;
        // Television.info는 "com-LCD"로 출력됨
    }
}
  • 정적 메소드는 내부에 인스턴스 필드나 인스턴스 메소드, 그리고 this를 사용할 수 없다.
  • 정적 메소드와 정적 블록에서 인스턴스 멤버를 사용하고 싶다면, 객체를 먼저 생성하고 참조 변수로 접근해야 한다.
static void Methid3() {
	ClassName obj = new ClassName();
    obj.field1 = 10;
    dbj.methid1();
}
  • main()메소드도 정적 메소드이므로 동일한 규칙이 적용된다.

final 필드와 상수

final 필드는 초기값이 저장되면 이것이 최종적인 값이 되어서 프로그램 실행 도중에 수정할 수 없게 된다.

  • final 필드에 초기값을 주는 방법 1. 필드 선언 시에 초기값 대입 2. 생성자에서 초기값 대입

상수는 불변의 값을 저장하는 필드이다.

  • 상수는 static이면서 final인 특성을 가진다.
  • 상수명은 모두 대문자로 작성하는 것이 관례이다. *ex) APPLE_PIZZA

패키지

  • 자바의 패키지는 클래스의 일부분이며, 클래스를 식별하는 용도로 사용된다.
  • import문은 하위 패키지를 포함하지 않는다.
    • com.hankook패키지도 사용해야하고 com.hankook.project패키지에 있는 클래스도 사용해야 한다면 두 개의 import문이 필요하다.

접근 제한자

접근제한자제한 대상제한 범위
public클래스, 필드, 생성자, 메소드없음
protected필드, 생성자, 메소드같은 패키지, 자식 객체만 사용 가능
(default)클래스, 필드, 생성자, 메소드같은 패키지
private필드, 생성자, 메소드객체 내부
  • 생성자, 필드, 메소드는 public, default, private 접근 제한을 가질 수 있다.

Getter와 Setter

  • 객체의 무결성을 보장하기 위해 사용하는 메소드들
  • OOP에서는 직접적인 외부에서의 필드 접근을 막고 대신 메소드를 통해 필드에 접근하는 것을 선호
  • getter에서 변환 코드를 작성하거나, setter에서 검증 코드를 작성할 수 있다.
public class Car {
	private int speed;
    private boolean stop;
    
    // getter
    public int getSpeed() {
    	return speed;
    }
    
    public boolean isStop() {
    	return stop;
    }
    
    // setter
    public void setSpeed(int speed) {
    	if (speed < 0 ) {
        	this.speed = 0;
            return;
        } else {
        	this.speed = speed;
        }
    }
    
    public void setStop(boolean stop) {
    	this.stop = stop;
    	if (stop == true) this.speed = 0;
    }
}

싱글톤 패턴

  • 애플리케이션 전체에서 단 한 개의 객체만 생성해서 사용하고 싶다면 싱글톤 패턴을 적용할 수 있다.
  • 생성자를 private 접근 제한해서 외부에서 new연산자로 생성자를 호출 못 하도록 막는 것
    • 외부에서 마음대로 객체 생성 불가
    • 외부에서는 정적 메소드를 통해 간접적으로 객체를 얻을 수 있다.
public class Singleton {
	// private 접근 권한을 갖는 정적 필드 선언과 초기화
	private static Singleton singleton = new Singleton();
    
    // private 접근 권한을 갖는 생성자 선언
    private Singleton() {
    }
    
    // public(defalut 아닌가?) 접근 권한을 갖는 정적 메소드 선언
    static Singleton getInstance() {
    	return singleton;
    }
}


public class SingletonEx {
	public static void main(String[] args) {
    	// 둘은 동일한 객체를 참조한다.
 		Singleton obj1 = Singleton.getInstance();
        Singleton obj2 = Singleton.getInstance();
    }
}

profile
공부

0개의 댓글